Skip to content

HTML: upgrade to MathJax 4 for browser rendering#2818

Draft
rbeezer wants to merge 4 commits intoPreTeXtBook:masterfrom
rbeezer:mathjax4-phase1
Draft

HTML: upgrade to MathJax 4 for browser rendering#2818
rbeezer wants to merge 4 commits intoPreTeXtBook:masterfrom
rbeezer:mathjax4-phase1

Conversation

@rbeezer
Copy link
Copy Markdown
Collaborator

@rbeezer rbeezer commented Apr 4, 2026

Upgrade HTML output from MathJax 3 to MathJax 4 (CDN stable release 4.1.1).

  • Incorporates Andrew Scholer's mathjax_startup.js module from MathJax 4 knowl fix #2180, which inlines the knowl extension and exports a configuration function
  • Replaces the inline MathJax 3 configuration block in pretext-html.xsl with a module import calling startMathJax(), plus the MathJax 4 CDN script with defer
  • Adds defer to Runestone service script tags for correct load ordering
  • Removes mathjaxknowl3.js (superseded by the startup module)
  • Removes debug.mathjax4 parameter and $mathjax4-testing variable (no longer needed since MJ4 is now the only path)

The textmacros extension, added to the MJ3 configuration in #2817, is loaded by default in MathJax 4 and does not need to be listed explicitly.

This is Phase 1 of the MJ4 migration (issue #1841), covering browser-side rendering only. The offline Node.js processing in mj-sre-page.js (used for EPUB and braille) is independent and unchanged.

Claude Opus 4.6, acting as a coding assistant for Rob Beezer

ascholerChemeketa and others added 3 commits April 4, 2026 09:18
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 4, 2026

Now available:

  • For testing when you grab this PR.
  • For developing further modifications in advance of merging this.

See #1841 for new ideas and for things to test.

@rbeezer rbeezer mentioned this pull request Apr 4, 2026
@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 4, 2026

@ascholerChemeketa should be moving here from his good work on #2180.

@bnmnetp - can you take this for a drive on Runestone Academy? I've built the sample book for local hosting and math in RS components seems to be working pretty well.

@rbeezer rbeezer mentioned this pull request Apr 4, 2026
@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 4, 2026

Pinging @dpvc and @zorkow if they are interested.

@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 4, 2026

@bnmnetp - there are some new defer attributes on script/module loading related to Runestone Services. @ascholerChemeketa has these in #2180, and Claude picked up on them. They should be evaluated carefully.

@rbeezer rbeezer marked this pull request as draft April 4, 2026 17:17
@rbeezer rbeezer added the mathjax label Apr 4, 2026
@ascholerChemeketa
Copy link
Copy Markdown
Contributor

I have no recollection of why I added the defer attributes. I am pretty sure there have been other MathJax/Runestone timing issues that have been resolved since I made that proof of concept. So whatever I was trying to do could already be fixed.

MJ4 script tag has defer on it. MJ3 did not. So this also could have been me just trying to avoid blocking browser parsing of the HTML while loading the JS. Which is best practice (if it doesn't break things).

I would recommend starting with getting rid of the defer on both. the mathjax and Runestone and start from there.

@dpvc
Copy link
Copy Markdown

dpvc commented Apr 5, 2026

MJ4 script tag has defer on it. MJ3 did not.

MathJax v3 recommended async, while v4 recommends defer. The difference is that async would allow the code to run even while the HTML page is being downloaded and parsed, possibly slowing the initial page display, and introducing some potential timing issues with other code that uses MathJax, while defer puts off running the code until after the page is loaded, and preserves the order of (deferred) script execution, which should reduce execution race conditions.

I would recommend starting with getting rid of the defer on both.

You will want to use either defer or async on MathJax's script, otherwise the page will block until MathJax is loaded, parsed, and executed. Because MathJax is large (and can't rally do its job until the page is fully loaded anyway), you don't want the page to block waiting for it. I don' know the Runestone code, so can't comment on whether it should be deferred or not.

Copy link
Copy Markdown

@dpvc dpvc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few recommendation for streamlining the configuration.

Comment on lines +19 to +24
"base",
"ams",
"amscd",
"color",
"newcommand",
"knowl"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base, ams, and newcommend packages are already in the packages list (you are loading tex-mml-chtml.js, which already includes them and several others), so there is not need to include them here. The [+] is saying add these to the existing package list, but they are already there. It doesn't care any errors, but is redundant.

});

function GetArgumentMML(parser, name) {
const NodeUtil = MathJax._.input.tex.NodeUtil.default;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could move this line outside the GetAargumentMML() function, as it is a value that doesn't change. That would avoid having to look it up each time GetArgumentMML() is called.

Comment on lines +112 to +118
new CommandMap(
"knowl",
{
knowl: ["Knowl"]
},
mathjaxKnowl
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version 4 has a more compact format for defining a CommandMap. You don't have to have a separate mathjaxKnowl object, but can just put the Knowl() function as the value for the knowl entry in the CommandMap:

new CommandMap(
  "knowl",
  {
    knowl: (parser, name) => {
      const url = parser.GetArgument(name);
      const arg = GetArgumentMML(parser, name);
      const mrow = parser.create("node", "mrow", [arg], { tabindex: '0', "data-knowl": url });
      parser.Push(mrow);
    }
  }
);

In fact, you can probably just do

new CommandMap(
  "knowl",
  {
    knowl(parser, name) {
      const url = parser.GetArgument(name);
      const arg = GetArgumentMML(parser, name);
      const mrow = parser.create("node", "mrow", [arg], { tabindex: '0', "data-knowl": url });
      parser.Push(mrow);
    }
  }
);

Comment on lines +122 to +127
pageReady() {
return MathJax.startup.defaultPageReady().then(function () {
rsMathReady();
}
)
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to

pageReady() {
  return MathJax.startup.defaultPageReady().then(rsMathReady);
}

or even

pageReady: () => MathJax.startup.defaultPageReady().then(rsMathReady);

if(opts.htmlPresentation) {
mathJaxOpts['options']['menuOptions'] = {
"settings": {
"zoom": "Click",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not recommend using Click for this unless you also include one of the modifier keys, as clicking (and double clicking) is used with the expression explorer, and this will cause it to both start the explorer and show the zoom.

@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 5, 2026

Thanks very much @dpvc! I'll see about getting those in place.

You are right about "Click". We also support knowls inside display math, which are activated/opened by a mouse click. We will need to think more carefully about how we want to separate that from the Explorer functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 5, 2026

Latest commit applies David Cervone's configuration recommendations from the PR discussion:

  • Remove base, ams, newcommand from [+] packages and [tex]/newcommand from loader (already bundled in tex-mml-chtml.js)
  • Move NodeUtil lookup outside GetArgumentMML to avoid repeated lookups
  • Use MJ4 compact CommandMap format with inline function
  • Simplify pageReady callback

Did not change defer attributes (under separate evaluation) or Click zoom (conflicts with expression explorer, needs more thought).

Claude Opus 4.6, acting as a coding assistant for Rob Beezer

@rbeezer
Copy link
Copy Markdown
Collaborator Author

rbeezer commented Apr 5, 2026

Changes seem fine visually. Thanks again, @dpvc.

I made you the author of the commit, I can walk that back once we merge, if you prefer. And likely I'll rotate this down to immediately follow Andrew's original version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants