Is your feature request related to a problem? Please describe.
Currently in a documentation project we have many pages, of which only a very small subset needs mathjax. However, enabling the sphinx.ext.mathjax extension causes mathjax.js to be included in every page. This is a bit unfortunate since mathjax is huge.
Describe the solution you'd like
Mathjax.js is included in only the pages which need it, and not every page.
Describe alternatives you've considered
Current system works, but makes all pages that don't need mathjax bigger and slower than they need to.
Additional context
I mentioned this as a comment in #5497 , where it was suggested that I open a new issue.
https://github.com/executablebooks/sphinx-panels/issues/28#issuecomment-683748518
An idea for a Sphinx plugin that does what's been requested here.
Thinking about possible plugin implementations, would hooking into html-page-context be a good approach here? This is called on a per-page basis, and has the advantage that the content isn't mutated afterwards.
In order to not change the order of libraries and stylesheets included, I think conditionally removing js would work better, and the plugin configuration could specify a doctree node selector that is required to keep a js/css file.
Yup -- that's exactly what I had in mind -- filtering the JS files "out" based on some conditional for them. Something like "only remove if there's no .math elements".
The exact design is 100% in the air though -- I imagine callable functions maybe nice in the spirit of extensibility but that's complexity and so on. :)
Thinking about sphinx design, I'd say using any condition accepted by doctree.traverse should do. That would allow both the simple option of specifying node types, and arbitrary more complex ones.
I think the pattern you suggested to remove files would work. I just tried adding an html-page-context listener to conditionally remove another JS library (thebelab) when a page didn't have the node that would trigger it, and it seemed to work nicely 馃憤
Here's the code I used in case it's helpful:
def remove_thebe_if_not_needed(app, pagename, templatename, context, doctree):
if not doctree:
return
if not doctree.traverse(ThebeButtonNode):
# Remove thebe JS files
new_script_files = []
for ii in context["script_files"]:
if ii not in ["_static/sphinx-thebe.js", "https://unpkg.com/thebelab@latest/lib/index.js"]:
new_script_files.append(ii)
context["script_files"] = new_script_files
# Remove thebe CSS files
new_css_files = []
for ii in context["css_files"]:
if ii not in ['_static/thebelab.css', '_static/sphinx-thebe.css']:
new_css_files.append(ii)
context["css_files"] = new_css_files
Sweet! Now the real question is if it's even worth an extension of its own鈥攖he amount of code is minimal.
It would potentially make sense to make it a part of sphinx itself by allowing js and css files also specify a doctree selector for themselves to be included.
Not sure what would be a good interface. The obvious option is to allow
html_js_files = [
'js/custom.js', # included unconditionally
('js/large_optional_library.js', library_selector), # only if selector isn't empty
]
This is backwards compatible, but ugly.
hmmmm, I will think about it a bit more...another option is something like:
add_conditional_js_file or somethingadd_js_file except for another one called condition which would be a callable that takes all of the arguments of html-page-contexthtml-page-context. In that callback, the user-provided callable is called.not None then it does nothing.None, then it removes the JS/CSS file from the page contextThen extension authors could do whatever they want with callable. E.g. some might want the JS loaded only on pages that meet a user-provided variable like load_js_on_pages = ["index", "config"]. Others might want to parse the doctree. Others might want to check for a context variable like load_library=True.
If the function returns
not Nonethen it does nothing.If the function returns
None, then it removes the JS/CSS file from the page context
I'd use boolean values instead, but otherwise I like it.
If we're willing to be a bit... uhm... YOLO about this, we could even have the condition argument be added as a kw-only argument to add_js_file. I don't imagine anyone is setting that attribute on a JS file.
Does add_js_file cover the user-provided html_js_files?
Also: should the condition take all arguments passed to html-page-context or just a doctree traverse selector (that one is also callable and may do arbitrary stuff on the doctree).
What if we substituted
app.connect('env-updated', install_mathjax)
for
app.connect('html-page-context', install_mathjax)
That would provide the page name, and then the check could dive into whether that page has math tags?
I am prototyping this over here, if any others have thoughts or contributions: https://github.com/executablebooks/sphinx-conditional-asset/blob/main/sphinx_conditional_asset/__init__.py#L11
My plan is to release it as an extension once we have a pattern that makes sense, and then if Sphinx core thinks it'd be good to have, upstream it.
Most helpful comment
I'd use boolean values instead, but otherwise I like it.
If we're willing to be a bit... uhm... YOLO about this, we could even have the
conditionargument be added as a kw-only argument toadd_js_file. I don't imagine anyone is setting that attribute on a JS file.