Skip to content

Commit c5f0398

Browse files
committed
Close sphinx-doc#8634: html: Allow to change the order of JS/CSS
`Sphinx.add_js_file()` and `Sphinx.add_css_file()` take `priority` argument to change the order of JS/CSS files.
1 parent 7ecf6b8 commit c5f0398

File tree

5 files changed

+96
-26
lines changed

5 files changed

+96
-26
lines changed

CHANGES

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Features added
2020
value of the variable if docstring contains ``:meta hide-value:`` in
2121
info-field-list
2222
* #8619: html: kbd role generates customizable HTML tags for compound keys
23+
* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter
24+
for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()`
2325
* #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright`
2426

2527
Bugs fixed

sphinx/application.py

+43-15
Original file line numberDiff line numberDiff line change
@@ -916,21 +916,21 @@ def add_post_transform(self, transform: "Type[Transform]") -> None:
916916
"""
917917
self.registry.add_post_transform(transform)
918918

919-
def add_javascript(self, filename: str, **kwargs: str) -> None:
919+
def add_javascript(self, filename: str, **kwargs: Any) -> None:
920920
"""An alias of :meth:`add_js_file`."""
921921
warnings.warn('The app.add_javascript() is deprecated. '
922922
'Please use app.add_js_file() instead.',
923923
RemovedInSphinx40Warning, stacklevel=2)
924924
self.add_js_file(filename, **kwargs)
925925

926-
def add_js_file(self, filename: str, **kwargs: str) -> None:
926+
def add_js_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
927927
"""Register a JavaScript file to include in the HTML output.
928928
929929
Add *filename* to the list of JavaScript files that the default HTML
930-
template will include. The filename must be relative to the HTML
931-
static path , or a full URI with scheme. If the keyword argument
932-
``body`` is given, its value will be added between the
933-
``<script>`` tags. Extra keyword arguments are included as
930+
template will include in order of *priority* (ascending). The filename
931+
must be relative to the HTML static path , or a full URI with scheme.
932+
If the keyword argument ``body`` is given, its value will be added
933+
between the ``<script>`` tags. Extra keyword arguments are included as
934934
attributes of the ``<script>`` tag.
935935
936936
Example::
@@ -944,23 +944,38 @@ def add_js_file(self, filename: str, **kwargs: str) -> None:
944944
app.add_js_file(None, body="var myVariable = 'foo';")
945945
# => <script>var myVariable = 'foo';</script>
946946
947+
.. list-table:: priority range for JavaScript files
948+
:widths: 20,80
949+
950+
* - Priority
951+
- Main purpose in Sphinx
952+
* - 200
953+
- default priority for built-in JavaScript files
954+
* - 500
955+
- default priority for extensions
956+
* - 800
957+
- default priority for :confval:`html_js_files`
958+
947959
.. versionadded:: 0.5
948960
949961
.. versionchanged:: 1.8
950962
Renamed from ``app.add_javascript()``.
951963
And it allows keyword arguments as attributes of script tag.
964+
965+
.. versionchanged:: 3.5
966+
Take priority argument.
952967
"""
953-
self.registry.add_js_file(filename, **kwargs)
968+
self.registry.add_js_file(filename, priority=priority, **kwargs)
954969
if hasattr(self.builder, 'add_js_file'):
955-
self.builder.add_js_file(filename, **kwargs) # type: ignore
970+
self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore
956971

957-
def add_css_file(self, filename: str, **kwargs: str) -> None:
972+
def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
958973
"""Register a stylesheet to include in the HTML output.
959974
960975
Add *filename* to the list of CSS files that the default HTML template
961-
will include. The filename must be relative to the HTML static path,
962-
or a full URI with scheme. The keyword arguments are also accepted for
963-
attributes of ``<link>`` tag.
976+
will include in order of *priority* (ascending). The filename must be
977+
relative to the HTML static path, or a full URI with scheme. The
978+
eyword arguments are also accepted for attributes of ``<link>`` tag.
964979
965980
Example::
966981
@@ -975,6 +990,16 @@ def add_css_file(self, filename: str, **kwargs: str) -> None:
975990
# => <link rel="alternate stylesheet" href="_static/fancy.css"
976991
# type="text/css" title="fancy" />
977992
993+
.. list-table:: priority range for CSS files
994+
:widths: 20,80
995+
996+
* - Priority
997+
- Main purpose in Sphinx
998+
* - 500
999+
- default priority for extensions
1000+
* - 800
1001+
- default priority for :confval:`html_css_files`
1002+
9781003
.. versionadded:: 1.0
9791004
9801005
.. versionchanged:: 1.6
@@ -987,11 +1012,14 @@ def add_css_file(self, filename: str, **kwargs: str) -> None:
9871012
.. versionchanged:: 1.8
9881013
Renamed from ``app.add_stylesheet()``.
9891014
And it allows keyword arguments as attributes of link tag.
1015+
1016+
.. versionchanged:: 3.5
1017+
Take priority argument.
9901018
"""
9911019
logger.debug('[app] adding stylesheet: %r', filename)
992-
self.registry.add_css_files(filename, **kwargs)
1020+
self.registry.add_css_files(filename, priority=priority, **kwargs)
9931021
if hasattr(self.builder, 'add_css_file'):
994-
self.builder.add_css_file(filename, **kwargs) # type: ignore
1022+
self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore
9951023

9961024
def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None
9971025
) -> None:
@@ -1000,7 +1028,7 @@ def add_stylesheet(self, filename: str, alternate: bool = False, title: str = No
10001028
'Please use app.add_css_file() instead.',
10011029
RemovedInSphinx40Warning, stacklevel=2)
10021030

1003-
attributes = {} # type: Dict[str, str]
1031+
attributes = {} # type: Dict[str, Any]
10041032
if alternate:
10051033
attributes['rel'] = 'alternate stylesheet'
10061034
else:

sphinx/builders/html/__init__.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,13 @@ class Stylesheet(str):
9090

9191
attributes = None # type: Dict[str, str]
9292
filename = None # type: str
93+
priority = None # type: int
9394

94-
def __new__(cls, filename: str, *args: str, **attributes: str) -> "Stylesheet":
95+
def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any
96+
) -> "Stylesheet":
9597
self = str.__new__(cls, filename) # type: ignore
9698
self.filename = filename
99+
self.priority = priority
97100
self.attributes = attributes
98101
self.attributes.setdefault('rel', 'stylesheet')
99102
self.attributes.setdefault('type', 'text/css')
@@ -113,10 +116,12 @@ class JavaScript(str):
113116

114117
attributes = None # type: Dict[str, str]
115118
filename = None # type: str
119+
priority = None # type: int
116120

117-
def __new__(cls, filename: str, **attributes: str) -> "JavaScript":
121+
def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> "JavaScript":
118122
self = str.__new__(cls, filename) # type: ignore
119123
self.filename = filename
124+
self.priority = priority
120125
self.attributes = attributes
121126

122127
return self
@@ -290,29 +295,31 @@ def init_css_files(self) -> None:
290295
self.add_css_file(filename, **attrs)
291296

292297
for filename, attrs in self.get_builder_config('css_files', 'html'):
298+
attrs.setdefault('priority', 800) # User's CSSs are loaded after extensions'
293299
self.add_css_file(filename, **attrs)
294300

295-
def add_css_file(self, filename: str, **kwargs: str) -> None:
301+
def add_css_file(self, filename: str, **kwargs: Any) -> None:
296302
if '://' not in filename:
297303
filename = posixpath.join('_static', filename)
298304

299305
self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore
300306

301307
def init_js_files(self) -> None:
302-
self.add_js_file('jquery.js')
303-
self.add_js_file('underscore.js')
304-
self.add_js_file('doctools.js')
308+
self.add_js_file('jquery.js', priority=200)
309+
self.add_js_file('underscore.js', priority=200)
310+
self.add_js_file('doctools.js', priority=200)
305311

306312
for filename, attrs in self.app.registry.js_files:
307313
self.add_js_file(filename, **attrs)
308314

309315
for filename, attrs in self.get_builder_config('js_files', 'html'):
316+
attrs.setdefault('priority', 800) # User's JSs are loaded after extensions'
310317
self.add_js_file(filename, **attrs)
311318

312319
if self.config.language and self._get_translations_js():
313320
self.add_js_file('translations.js')
314321

315-
def add_js_file(self, filename: str, **kwargs: str) -> None:
322+
def add_js_file(self, filename: str, **kwargs: Any) -> None:
316323
if filename and '://' not in filename:
317324
filename = posixpath.join('_static', filename)
318325

@@ -1015,6 +1022,10 @@ def hasdoc(name: str) -> bool:
10151022
if newtmpl:
10161023
templatename = newtmpl
10171024

1025+
# sort JS/CSS before rendering HTML
1026+
ctx['script_files'].sort(key=lambda js: js.priority)
1027+
ctx['css_files'].sort(key=lambda js: js.priority)
1028+
10181029
try:
10191030
output = self.templates.render(templatename, ctx)
10201031
except UnicodeError:

sphinx/registry.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def __init__(self) -> None:
6363
self.documenters = {} # type: Dict[str, Type[Documenter]]
6464

6565
#: css_files; a list of tuple of filename and attributes
66-
self.css_files = [] # type: List[Tuple[str, Dict[str, str]]]
66+
self.css_files = [] # type: List[Tuple[str, Dict[str, Any]]]
6767

6868
#: domains; a dict of domain name -> domain class
6969
self.domains = {} # type: Dict[str, Type[Domain]]
@@ -94,7 +94,7 @@ def __init__(self) -> None:
9494
self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]]
9595

9696
#: js_files; list of JS paths or URLs
97-
self.js_files = [] # type: List[Tuple[str, Dict[str, str]]]
97+
self.js_files = [] # type: List[Tuple[str, Dict[str, Any]]]
9898

9999
#: LaTeX packages; list of package names and its options
100100
self.latex_packages = [] # type: List[Tuple[str, str]]
@@ -361,10 +361,10 @@ def add_autodoc_attrgetter(self, typ: "Type",
361361
attrgetter: Callable[[Any, str, Any], Any]) -> None:
362362
self.autodoc_attrgettrs[typ] = attrgetter
363363

364-
def add_css_files(self, filename: str, **attributes: str) -> None:
364+
def add_css_files(self, filename: str, **attributes: Any) -> None:
365365
self.css_files.append((filename, attributes))
366366

367-
def add_js_file(self, filename: str, **attributes: str) -> None:
367+
def add_js_file(self, filename: str, **attributes: Any) -> None:
368368
logger.debug('[app] adding js_file: %r, %r', filename, attributes)
369369
self.js_files.append((filename, attributes))
370370

tests/test_build_html.py

+29
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,35 @@ def test_html_assets(app):
12281228
'</script>' in content)
12291229

12301230

1231+
@pytest.mark.sphinx('html', testroot='html_assets')
1232+
def test_assets_order(app):
1233+
app.add_css_file('normal.css')
1234+
app.add_css_file('early.css', priority=100)
1235+
app.add_css_file('late.css', priority=750)
1236+
app.add_css_file('lazy.css', priority=900)
1237+
app.add_js_file('normal.js')
1238+
app.add_js_file('early.js', priority=100)
1239+
app.add_js_file('late.js', priority=750)
1240+
app.add_js_file('lazy.js', priority=900)
1241+
1242+
app.builder.build_all()
1243+
content = (app.outdir / 'index.html').read_text()
1244+
1245+
# css_files
1246+
expected = ['_static/pygments.css', '_static/alabaster.css', '_static/early.css',
1247+
'_static/normal.css', '_static/late.css', '_static/css/style.css',
1248+
'https://example.com/custom.css', '_static/lazy.css']
1249+
pattern = '.*'.join('href="%s"' % f for f in expected)
1250+
assert re.search(pattern, content, re.S)
1251+
1252+
# js_files
1253+
expected = ['_static/early.js', '_static/jquery.js', '_static/underscore.js',
1254+
'_static/doctools.js', '_static/normal.js', '_static/late.js',
1255+
'_static/js/custom.js', 'https://example.com/script.js', '_static/lazy.js']
1256+
pattern = '.*'.join('src="%s"' % f for f in expected)
1257+
assert re.search(pattern, content, re.S)
1258+
1259+
12311260
@pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False})
12321261
def test_html_copy_source(app):
12331262
app.builder.build_all()

0 commit comments

Comments
 (0)