Merge pull request #8639 from tk0miya/8634_css_priority

Close #8634: html: Allow to change the order of JS/CSS
This commit is contained in:
Takeshi KOMIYA 2021-01-02 23:34:18 +09:00 committed by GitHub
commit 1b7d16505e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 28 deletions

View File

@ -20,6 +20,8 @@ Features added
value of the variable if docstring contains ``:meta hide-value:`` in value of the variable if docstring contains ``:meta hide-value:`` in
info-field-list info-field-list
* #8619: html: kbd role generates customizable HTML tags for compound keys * #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` * #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright`
Bugs fixed Bugs fixed

View File

@ -916,21 +916,21 @@ class Sphinx:
""" """
self.registry.add_post_transform(transform) 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`.""" """An alias of :meth:`add_js_file`."""
warnings.warn('The app.add_javascript() is deprecated. ' warnings.warn('The app.add_javascript() is deprecated. '
'Please use app.add_js_file() instead.', 'Please use app.add_js_file() instead.',
RemovedInSphinx40Warning, stacklevel=2) RemovedInSphinx40Warning, stacklevel=2)
self.add_js_file(filename, **kwargs) 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. """Register a JavaScript file to include in the HTML output.
Add *filename* to the list of JavaScript files that the default HTML Add *filename* to the list of JavaScript files that the default HTML
template will include. The filename must be relative to the HTML template will include in order of *priority* (ascending). The filename
static path , or a full URI with scheme. If the keyword argument must be relative to the HTML static path , or a full URI with scheme.
``body`` is given, its value will be added between the If the keyword argument ``body`` is given, its value will be added
``<script>`` tags. Extra keyword arguments are included as between the ``<script>`` tags. Extra keyword arguments are included as
attributes of the ``<script>`` tag. attributes of the ``<script>`` tag.
Example:: Example::
@ -944,23 +944,38 @@ class Sphinx:
app.add_js_file(None, body="var myVariable = 'foo';") app.add_js_file(None, body="var myVariable = 'foo';")
# => <script>var myVariable = 'foo';</script> # => <script>var myVariable = 'foo';</script>
.. 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 .. versionadded:: 0.5
.. versionchanged:: 1.8 .. versionchanged:: 1.8
Renamed from ``app.add_javascript()``. Renamed from ``app.add_javascript()``.
And it allows keyword arguments as attributes of script tag. 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. """Register a stylesheet to include in the HTML output.
Add *filename* to the list of CSS files that the default HTML template 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, will include in order of *priority* (ascending). The filename must be
or a full URI with scheme. The keyword arguments are also accepted for relative to the HTML static path, or a full URI with scheme. The
attributes of ``<link>`` tag. eyword arguments are also accepted for attributes of ``<link>`` tag.
Example:: Example::
@ -975,6 +990,16 @@ class Sphinx:
# => <link rel="alternate stylesheet" href="_static/fancy.css" # => <link rel="alternate stylesheet" href="_static/fancy.css"
# type="text/css" title="fancy" /> # type="text/css" title="fancy" />
.. 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 .. versionadded:: 1.0
.. versionchanged:: 1.6 .. versionchanged:: 1.6
@ -987,11 +1012,14 @@ class Sphinx:
.. versionchanged:: 1.8 .. versionchanged:: 1.8
Renamed from ``app.add_stylesheet()``. Renamed from ``app.add_stylesheet()``.
And it allows keyword arguments as attributes of link tag. And it allows keyword arguments as attributes of link tag.
.. versionchanged:: 3.5
Take priority argument.
""" """
logger.debug('[app] adding stylesheet: %r', filename) 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'): 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 def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None
) -> None: ) -> None:
@ -1000,7 +1028,7 @@ class Sphinx:
'Please use app.add_css_file() instead.', 'Please use app.add_css_file() instead.',
RemovedInSphinx40Warning, stacklevel=2) RemovedInSphinx40Warning, stacklevel=2)
attributes = {} # type: Dict[str, str] attributes = {} # type: Dict[str, Any]
if alternate: if alternate:
attributes['rel'] = 'alternate stylesheet' attributes['rel'] = 'alternate stylesheet'
else: else:

View File

@ -90,10 +90,13 @@ class Stylesheet(str):
attributes = None # type: Dict[str, str] attributes = None # type: Dict[str, str]
filename = None # type: 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 = str.__new__(cls, filename) # type: ignore
self.filename = filename self.filename = filename
self.priority = priority
self.attributes = attributes self.attributes = attributes
self.attributes.setdefault('rel', 'stylesheet') self.attributes.setdefault('rel', 'stylesheet')
self.attributes.setdefault('type', 'text/css') self.attributes.setdefault('type', 'text/css')
@ -113,10 +116,12 @@ class JavaScript(str):
attributes = None # type: Dict[str, str] attributes = None # type: Dict[str, str]
filename = None # type: 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 = str.__new__(cls, filename) # type: ignore
self.filename = filename self.filename = filename
self.priority = priority
self.attributes = attributes self.attributes = attributes
return self return self
@ -290,29 +295,31 @@ class StandaloneHTMLBuilder(Builder):
self.add_css_file(filename, **attrs) self.add_css_file(filename, **attrs)
for filename, attrs in self.get_builder_config('css_files', 'html'): 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) 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: if '://' not in filename:
filename = posixpath.join('_static', filename) filename = posixpath.join('_static', filename)
self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore self.css_files.append(Stylesheet(filename, **kwargs)) # type: ignore
def init_js_files(self) -> None: def init_js_files(self) -> None:
self.add_js_file('jquery.js') self.add_js_file('jquery.js', priority=200)
self.add_js_file('underscore.js') self.add_js_file('underscore.js', priority=200)
self.add_js_file('doctools.js') self.add_js_file('doctools.js', priority=200)
for filename, attrs in self.app.registry.js_files: for filename, attrs in self.app.registry.js_files:
self.add_js_file(filename, **attrs) self.add_js_file(filename, **attrs)
for filename, attrs in self.get_builder_config('js_files', 'html'): 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) self.add_js_file(filename, **attrs)
if self.config.language and self._get_translations_js(): if self.config.language and self._get_translations_js():
self.add_js_file('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: if filename and '://' not in filename:
filename = posixpath.join('_static', filename) filename = posixpath.join('_static', filename)
@ -1015,6 +1022,10 @@ class StandaloneHTMLBuilder(Builder):
if newtmpl: if newtmpl:
templatename = 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: try:
output = self.templates.render(templatename, ctx) output = self.templates.render(templatename, ctx)
except UnicodeError: except UnicodeError:

View File

@ -63,7 +63,7 @@ class SphinxComponentRegistry:
self.documenters = {} # type: Dict[str, Type[Documenter]] self.documenters = {} # type: Dict[str, Type[Documenter]]
#: css_files; a list of tuple of filename and attributes #: 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 #: domains; a dict of domain name -> domain class
self.domains = {} # type: Dict[str, Type[Domain]] self.domains = {} # type: Dict[str, Type[Domain]]
@ -94,7 +94,7 @@ class SphinxComponentRegistry:
self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]] self.html_block_math_renderers = {} # type: Dict[str, Tuple[Callable, Callable]]
#: js_files; list of JS paths or URLs #: 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 #: LaTeX packages; list of package names and its options
self.latex_packages = [] # type: List[Tuple[str, str]] self.latex_packages = [] # type: List[Tuple[str, str]]
@ -361,10 +361,10 @@ class SphinxComponentRegistry:
attrgetter: Callable[[Any, str, Any], Any]) -> None: attrgetter: Callable[[Any, str, Any], Any]) -> None:
self.autodoc_attrgettrs[typ] = attrgetter 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)) 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) logger.debug('[app] adding js_file: %r, %r', filename, attributes)
self.js_files.append((filename, attributes)) self.js_files.append((filename, attributes))

View File

@ -1228,6 +1228,35 @@ def test_html_assets(app):
'</script>' in content) '</script>' 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}) @pytest.mark.sphinx('html', testroot='basic', confoverrides={'html_copy_source': False})
def test_html_copy_source(app): def test_html_copy_source(app):
app.builder.build_all() app.builder.build_all()