From c5f03980107e123210fb602f4c31f5ae950e2af4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 1 Jan 2021 23:33:34 +0900 Subject: [PATCH] Close #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. --- CHANGES | 2 ++ sphinx/application.py | 62 +++++++++++++++++++++++--------- sphinx/builders/html/__init__.py | 25 +++++++++---- sphinx/registry.py | 8 ++--- tests/test_build_html.py | 29 +++++++++++++++ 5 files changed, 98 insertions(+), 28 deletions(-) diff --git a/CHANGES b/CHANGES index 51d0a6400..993145cc2 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,8 @@ Features added value of the variable if docstring contains ``:meta hide-value:`` in info-field-list * #8619: html: kbd role generates customizable HTML tags for compound keys +* #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter + for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()` * #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright` Bugs fixed diff --git a/sphinx/application.py b/sphinx/application.py index 2253ce306..39ab93ed1 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -916,21 +916,21 @@ class Sphinx: """ self.registry.add_post_transform(transform) - def add_javascript(self, filename: str, **kwargs: str) -> None: + def add_javascript(self, filename: str, **kwargs: Any) -> None: """An alias of :meth:`add_js_file`.""" warnings.warn('The app.add_javascript() is deprecated. ' 'Please use app.add_js_file() instead.', RemovedInSphinx40Warning, stacklevel=2) self.add_js_file(filename, **kwargs) - def add_js_file(self, filename: str, **kwargs: str) -> None: + def add_js_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a JavaScript file to include in the HTML output. Add *filename* to the list of JavaScript files that the default HTML - template will include. The filename must be relative to the HTML - static path , or a full URI with scheme. If the keyword argument - ``body`` is given, its value will be added between the - `` + .. list-table:: priority range for JavaScript files + :widths: 20,80 + + * - Priority + - Main purpose in Sphinx + * - 200 + - default priority for built-in JavaScript files + * - 500 + - default priority for extensions + * - 800 + - default priority for :confval:`html_js_files` + .. versionadded:: 0.5 .. versionchanged:: 1.8 Renamed from ``app.add_javascript()``. And it allows keyword arguments as attributes of script tag. - """ - self.registry.add_js_file(filename, **kwargs) - if hasattr(self.builder, 'add_js_file'): - self.builder.add_js_file(filename, **kwargs) # type: ignore - def add_css_file(self, filename: str, **kwargs: str) -> None: + .. versionchanged:: 3.5 + Take priority argument. + """ + self.registry.add_js_file(filename, priority=priority, **kwargs) + if hasattr(self.builder, 'add_js_file'): + self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore + + def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a stylesheet to include in the HTML output. Add *filename* to the list of CSS files that the default HTML template - will include. The filename must be relative to the HTML static path, - or a full URI with scheme. The keyword arguments are also accepted for - attributes of ```` tag. + will include in order of *priority* (ascending). The filename must be + relative to the HTML static path, or a full URI with scheme. The + eyword arguments are also accepted for attributes of ```` tag. Example:: @@ -975,6 +990,16 @@ class Sphinx: # => + .. list-table:: priority range for CSS files + :widths: 20,80 + + * - Priority + - Main purpose in Sphinx + * - 500 + - default priority for extensions + * - 800 + - default priority for :confval:`html_css_files` + .. versionadded:: 1.0 .. versionchanged:: 1.6 @@ -987,11 +1012,14 @@ class Sphinx: .. versionchanged:: 1.8 Renamed from ``app.add_stylesheet()``. And it allows keyword arguments as attributes of link tag. + + .. versionchanged:: 3.5 + Take priority argument. """ logger.debug('[app] adding stylesheet: %r', filename) - self.registry.add_css_files(filename, **kwargs) + self.registry.add_css_files(filename, priority=priority, **kwargs) if hasattr(self.builder, 'add_css_file'): - self.builder.add_css_file(filename, **kwargs) # type: ignore + self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None ) -> None: @@ -1000,7 +1028,7 @@ class Sphinx: 'Please use app.add_css_file() instead.', RemovedInSphinx40Warning, stacklevel=2) - attributes = {} # type: Dict[str, str] + attributes = {} # type: Dict[str, Any] if alternate: attributes['rel'] = 'alternate stylesheet' else: diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 117395ba1..84efa0cc0 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -90,10 +90,13 @@ class Stylesheet(str): attributes = None # type: Dict[str, str] filename = None # type: str + priority = None # type: int - def __new__(cls, filename: str, *args: str, **attributes: str) -> "Stylesheet": + def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any + ) -> "Stylesheet": self = str.__new__(cls, filename) # type: ignore self.filename = filename + self.priority = priority self.attributes = attributes self.attributes.setdefault('rel', 'stylesheet') self.attributes.setdefault('type', 'text/css') @@ -113,10 +116,12 @@ class JavaScript(str): attributes = None # type: Dict[str, str] filename = None # type: str + priority = None # type: int - def __new__(cls, filename: str, **attributes: str) -> "JavaScript": + def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> "JavaScript": self = str.__new__(cls, filename) # type: ignore self.filename = filename + self.priority = priority self.attributes = attributes return self @@ -290,29 +295,31 @@ class StandaloneHTMLBuilder(Builder): self.add_css_file(filename, **attrs) for filename, attrs in self.get_builder_config('css_files', 'html'): + attrs.setdefault('priority', 800) # User's CSSs are loaded after extensions' self.add_css_file(filename, **attrs) - def add_css_file(self, filename: str, **kwargs: str) -> None: + def add_css_file(self, filename: str, **kwargs: Any) -> None: if '://' not in filename: filename = posixpath.join('_static', filename) self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore def init_js_files(self) -> None: - self.add_js_file('jquery.js') - self.add_js_file('underscore.js') - self.add_js_file('doctools.js') + self.add_js_file('jquery.js', priority=200) + self.add_js_file('underscore.js', priority=200) + self.add_js_file('doctools.js', priority=200) for filename, attrs in self.app.registry.js_files: self.add_js_file(filename, **attrs) for filename, attrs in self.get_builder_config('js_files', 'html'): + attrs.setdefault('priority', 800) # User's JSs are loaded after extensions' self.add_js_file(filename, **attrs) if self.config.language and self._get_translations_js(): self.add_js_file('translations.js') - def add_js_file(self, filename: str, **kwargs: str) -> None: + def add_js_file(self, filename: str, **kwargs: Any) -> None: if filename and '://' not in filename: filename = posixpath.join('_static', filename) @@ -1015,6 +1022,10 @@ class StandaloneHTMLBuilder(Builder): if newtmpl: templatename = newtmpl + # sort JS/CSS before rendering HTML + ctx['script_files'].sort(key=lambda js: js.priority) + ctx['css_files'].sort(key=lambda js: js.priority) + try: output = self.templates.render(templatename, ctx) except UnicodeError: diff --git a/sphinx/registry.py b/sphinx/registry.py index 8a988cb98..c6a249e74 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -63,7 +63,7 @@ class SphinxComponentRegistry: self.documenters = {} # type: Dict[str, Type[Documenter]] #: css_files; a list of tuple of filename and attributes - self.css_files = [] # type: List[Tuple[str, Dict[str, str]]] + self.css_files = [] # type: List[Tuple[str, Dict[str, Any]]] #: domains; a dict of domain name -> domain class self.domains = {} # type: Dict[str, Type[Domain]] @@ -94,7 +94,7 @@ class SphinxComponentRegistry: self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]] #: js_files; list of JS paths or URLs - self.js_files = [] # type: List[Tuple[str, Dict[str, str]]] + self.js_files = [] # type: List[Tuple[str, Dict[str, Any]]] #: LaTeX packages; list of package names and its options self.latex_packages = [] # type: List[Tuple[str, str]] @@ -361,10 +361,10 @@ class SphinxComponentRegistry: attrgetter: Callable[[Any, str, Any], Any]) -> None: self.autodoc_attrgettrs[typ] = attrgetter - def add_css_files(self, filename: str, **attributes: str) -> None: + def add_css_files(self, filename: str, **attributes: Any) -> None: self.css_files.append((filename, attributes)) - def add_js_file(self, filename: str, **attributes: str) -> None: + def add_js_file(self, filename: str, **attributes: Any) -> None: logger.debug('[app] adding js_file: %r, %r', filename, attributes) self.js_files.append((filename, attributes)) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 9eddee6d6..e773c0ac4 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1228,6 +1228,35 @@ def test_html_assets(app): '' in content) +@pytest.mark.sphinx('html', testroot='html_assets') +def test_assets_order(app): + app.add_css_file('normal.css') + app.add_css_file('early.css', priority=100) + app.add_css_file('late.css', priority=750) + app.add_css_file('lazy.css', priority=900) + app.add_js_file('normal.js') + app.add_js_file('early.js', priority=100) + app.add_js_file('late.js', priority=750) + app.add_js_file('lazy.js', priority=900) + + app.builder.build_all() + content = (app.outdir / 'index.html').read_text() + + # css_files + expected = ['_static/pygments.css', '_static/alabaster.css', '_static/early.css', + '_static/normal.css', '_static/late.css', '_static/css/style.css', + 'https://example.com/custom.css', '_static/lazy.css'] + pattern = '.*'.join('href="%s"' % f for f in expected) + assert re.search(pattern, content, re.S) + + # js_files + expected = ['_static/early.js', '_static/jquery.js', '_static/underscore.js', + '_static/doctools.js', '_static/normal.js', '_static/late.js', + '_static/js/custom.js', 'https://example.com/script.js', '_static/lazy.js'] + pattern = '.*'.join('src="%s"' % f for f in expected) + assert re.search(pattern, content, re.S) + + @pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False}) def test_html_copy_source(app): app.builder.build_all()