Skip to content

Commit 1bfd79d

Browse files
authored
♻️ Serve static assets via html_static_path (#276)
Replace the hand-rolled static-asset copying (update_css_js/update_css_links) with Sphinx's standard html_static_path mechanism, gated to HTML-format builders. Assets move to sphinx_design/static/ with output names unchanged; non-HTML builds no longer gain a spurious _sphinx_design_static directory, and nothing is written into outdir outside Sphinx's own copying. The Sphinx floor moves to >=7.2 (native ?v= cache-busting) and the private _compat.findall shim is removed. Closes #200 Closes #235
1 parent 9f62c7d commit 1bfd79d

15 files changed

Lines changed: 113 additions & 91 deletions

‎.pre-commit-config.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ repos:
4545
entry: npm run css
4646
require_serial: true
4747
pass_filenames: false
48-
# args: [--style=compressed, --no-source-map, style/index.scss, sphinx_design/compiled/style.min.css]
48+
# args: [--style=compressed, --no-source-map, style/index.scss, sphinx_design/static/sphinx-design.min.css]
4949

5050
- id: tsc
5151
name: tsc (jsdoc)

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ The extension uses SASS for styling:
294294

295295
1. SASS source files are in `style/`
296296
2. Compiled using `npm run css` (requires Node.js)
297-
3. Output goes to `sphinx_design/compiled/style.min.css`
297+
3. Output goes to `sphinx_design/static/sphinx-design.min.css`
298298
4. CSS is automatically copied to build output during Sphinx builds
299299

300300
## Key Files
@@ -339,7 +339,7 @@ The extension uses SASS for styling:
339339

340340
1. Edit SASS files in `style/`
341341
2. Run `npm run css` to compile (or `pre-commit run --all css`)
342-
3. Compiled output goes to `sphinx_design/compiled/style.min.css`
342+
3. Compiled output goes to `sphinx_design/static/sphinx-design.min.css`
343343
4. Test with different themes to ensure compatibility
344344

345345
## Reference Documentation

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## Unreleased
44

5+
- ♻️ IMPROVE: Static assets (CSS/JS) are now served via Sphinx's standard
6+
`html_static_path` mechanism, rather than being written directly into the
7+
build output; non-HTML builds no longer gain a spurious
8+
`_sphinx_design_static` directory ({pr}`276`, {issue}`200`, {issue}`235`).
9+
A stale `_sphinx_design_static` directory left in an existing HTML build
10+
directory by previous versions is unused and can safely be deleted.
11+
- ⬆️ UPGRADE: Sphinx `>=7.2` is now required ({pr}`276`)
12+
- 🗑️ REMOVE: The private `sphinx_design._compat.findall` helper has been
13+
removed (docutils `Element.findall` is guaranteed by the Sphinx floor);
14+
any code importing it should call `node.findall(...)` directly ({pr}`276`)
515
- 🐛 FIX: buttons are no longer destroyed by gettext translation:
616
translated `button-link`/`button-ref` keep their styling and links
717
(gettext now targets only the button text), thanks to {user}`sneakers-the-rat`

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.0.1",
44
"description": "Scripts for compiling the sphinx-design assets",
55
"scripts": {
6-
"css": "sass --style=compressed --no-source-map style/index.scss sphinx_design/compiled/style.min.css"
6+
"css": "sass --style=compressed --no-source-map style/index.scss sphinx_design/static/sphinx-design.min.css"
77
},
88
"dependencies": {
99
"sass": "^1.35.2"

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ classifiers = [
2727
]
2828
keywords = ["sphinx", "extension", "material design", "web components"]
2929
requires-python = ">=3.11"
30-
dependencies = ["sphinx>=7,<10"]
30+
dependencies = ["sphinx>=7.2,<10"]
3131

3232
[project.urls]
3333
Homepage = "https://github.com/executablebooks/sphinx-design"

‎sphinx_design/_compat.py‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
"""Helpers for cross compatibility across dependency versions."""
22

3-
from collections.abc import Callable, Iterable
43
from importlib import resources
54

6-
from docutils.nodes import Element
7-
8-
9-
def findall(node: Element) -> Callable[..., Iterable[Element]]:
10-
"""Iterate through"""
11-
# findall replaces traverse in docutils v0.18
12-
# note a difference is that findall is an iterator
13-
return getattr(node, "findall", node.traverse)
14-
155

166
def read_text(module: resources.Package, filename: str) -> str:
177
return resources.files(module).joinpath(filename).read_text()

‎sphinx_design/cards.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from sphinx.util.docutils import SphinxDirective
1010
from sphinx.util.logging import getLogger
1111

12-
from ._compat import findall
1312
from .shared import (
1413
WARNING_TYPE,
1514
PassthroughTextElement,
@@ -247,9 +246,9 @@ def _create_component(
247246
@staticmethod
248247
def add_card_child_classes(node):
249248
"""Add classes to specific child nodes."""
250-
for para in findall(node)(nodes.paragraph):
249+
for para in node.findall(nodes.paragraph):
251250
para["classes"] = [*para.get("classes", []), "sd-card-text"]
252-
# for title in findall(node)(nodes.title):
251+
# for title in node.findall(nodes.title):
253252
# title["classes"] = ([] if "classes" not in title else title["classes"]) + [
254253
# "sd-card-title"
255254
# ]

‎sphinx_design/dropdown.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
margin_option,
1717
)
1818

19-
from ._compat import findall
2019
from .icons import get_octicon, list_octicons
2120

2221

@@ -153,7 +152,7 @@ class DropdownHtmlTransform(SphinxPostTransform):
153152
def run(self, **kwargs: Any) -> None:
154153
"""Run the transform"""
155154
document: nodes.document = self.document
156-
for node in findall(document)(lambda node: is_component(node, "dropdown")):
155+
for node in document.findall(lambda node: is_component(node, "dropdown")):
157156
# TODO option to not have card css (but requires more formatting)
158157
use_card = True
159158

@@ -230,7 +229,7 @@ def run(self, **kwargs: Any) -> None:
230229
children=body_children,
231230
)
232231
if use_card:
233-
for para in findall(body_node)(nodes.paragraph):
232+
for para in body_node.findall(nodes.paragraph):
234233
para["classes"] = ([] if "classes" in para else para["classes"]) + [
235234
"sd-card-text"
236235
]

‎sphinx_design/extension.py‎

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
from contextlib import contextmanager
22
from functools import partial
3-
import hashlib
43
from pathlib import Path
54

65
from docutils import nodes
76
from docutils.parsers.rst import directives
8-
from sphinx import version_info as sphinx_version
97
from sphinx.application import Sphinx
10-
from sphinx.environment import BuildEnvironment
118
from sphinx.transforms import SphinxTransform
129

13-
from . import compiled as static_module
14-
from ._compat import findall, read_text
1510
from .article_info import setup_article_info
1611
from .badges_buttons import setup_badges_and_buttons
1712
from .cards import setup_cards
@@ -27,12 +22,13 @@
2722
)
2823
from .tabs import setup_tabs
2924

25+
STATIC_DIR = Path(__file__).parent / "static"
26+
3027

3128
def setup_extension(app: Sphinx) -> None:
3229
"""Set up the sphinx extension."""
3330
setup_sd_config(app)
34-
app.connect("builder-inited", update_css_js)
35-
app.connect("env-updated", update_css_links)
31+
app.connect("builder-inited", add_static_assets)
3632
# we override container html visitors, to stop the default behaviour
3733
# of adding the `container` class to all nodes.container
3834
app.add_node(
@@ -77,45 +73,13 @@ def _add_directive(name, directive, **kwargs):
7773
app.add_directive = add_directive # type: ignore[method-assign]
7874

7975

80-
def update_css_js(app: Sphinx):
81-
"""Copy the CSS to the build directory."""
82-
# reset changed identifier
83-
app.env.sphinx_design_css_changed = False # type: ignore[attr-defined]
84-
# setup up new static path in output dir
85-
static_path = (Path(app.outdir) / "_sphinx_design_static").absolute()
86-
static_existed = static_path.exists()
87-
static_path.mkdir(exist_ok=True)
88-
app.config.html_static_path.append(str(static_path))
89-
# Copy JS to the build directory.
90-
js_path = static_path / "design-tabs.js"
91-
app.add_js_file(js_path.name)
92-
if not js_path.exists():
93-
content = read_text(static_module, "sd_tabs.js")
94-
js_path.write_text(content)
95-
# Read the css content and hash it
96-
content = read_text(static_module, "style.min.css")
97-
# Write the css file
98-
if sphinx_version < (7, 1):
99-
hash = hashlib.md5(content.encode("utf8"), usedforsecurity=False).hexdigest()
100-
css_path = static_path / f"sphinx-design.{hash}.min.css"
101-
else:
102-
# since sphinx 7.1 a checksum is added to the css file URL, so there is no need to do it here
103-
# https://github.com/sphinx-doc/sphinx/pull/11415
104-
css_path = static_path / "sphinx-design.min.css"
105-
app.add_css_file(css_path.name)
106-
if css_path.exists():
76+
def add_static_assets(app: Sphinx) -> None:
77+
"""Register the extension's static assets (HTML-format builders only)."""
78+
if app.builder.format != "html":
10779
return
108-
if static_existed:
109-
app.env.sphinx_design_css_changed = True # type: ignore[attr-defined]
110-
for path in static_path.glob("*.css"):
111-
path.unlink()
112-
css_path.write_text(content, encoding="utf8")
113-
114-
115-
def update_css_links(app: Sphinx, env: BuildEnvironment):
116-
"""If CSS has changed, all files must be re-written, to include the correct stylesheets."""
117-
if env.sphinx_design_css_changed: # type: ignore[attr-defined]
118-
return list(env.all_docs.keys())
80+
app.config.html_static_path.append(str(STATIC_DIR))
81+
app.add_css_file("sphinx-design.min.css")
82+
app.add_js_file("design-tabs.js")
11983

12084

12185
def visit_container(self, node: nodes.Node):
@@ -175,15 +139,15 @@ class AddFirstTitleCss(SphinxTransform):
175139

176140
def apply(self):
177141
hide = False
178-
for docinfo in findall(self.document)(nodes.docinfo):
179-
for name in findall(docinfo)(nodes.field_name):
142+
for docinfo in self.document.findall(nodes.docinfo):
143+
for name in docinfo.findall(nodes.field_name):
180144
if name.astext() == "sd_hide_title":
181145
hide = True
182146
break
183147
break
184148
if not hide:
185149
return
186-
for section in findall(self.document)(nodes.section):
150+
for section in self.document.findall(nodes.section):
187151
if isinstance(section.children[0], nodes.title):
188152
if "classes" in section.children[0]:
189153
section.children[0]["classes"].append("sd-d-none")

0 commit comments

Comments
 (0)