MathJax icon indicating copy to clipboard operation
MathJax copied to clipboard

Unexpected access to cdn.jsdelivr.net in v4

Open alderg opened this issue 4 months ago • 17 comments

We've updated from 3.2.2 to 4.0.0 and want to deliver it from our own server, but we see access to cdn.jsdelivr.net for svg.js, script.js and double-struck.js from startup.js. Is this expected? How can we fix it?

Image
  • MathJax Version: 4.0.0
  • Client OS: macOS 15.6.1 (24G90)
  • Browser: Chrome 140.0.7339.133

We are using the following MathJax configuration:

options:
{
	skipHtmlTags: {'[+]': ['text']},
	ignoreHtmlClass: 'geDisableMathJax'
},
loader:
{
	load: [(urlParams['math-output'] == 'html') ?
		'output/chtml' : 'output/svg', 'input/tex',
		'input/asciimath', 'ui/safe']
}

and loading MathJax locally via

<script src="BASE_URL/startup.js"></script>

alderg avatar Sep 17 '25 04:09 alderg

Because MathJax now offers a number of font options, if you want to host your own copy of MathJax completely locally, you will need to obtain a copy of the font you plan to use as well, and you need to configure MathJax to know where you have put it. MathJax can't tell if you have install the font you are using and even if you have, it doesn't know where it is unless you tell it, so it defaults to obtaining the font and its data from cdn.jsdelivr.net.

This is the expected behavior and is documented in the Obtaining the needed fonts section of the MathJax documentation on Hosting your own copy of MathJax. That section needs to be slightly updated; while it indicates that installing MathJax will get you the mathjax-newcm font, it fails to indicate that you need to move that to your server and configure the font path for it (it only mentions this in relation to other fonts).

So if you have installed MathJax via npm install mathjax@4, then you will have moved node_modules/MathJax to your server, but you also need to move node_modules/@mathjax/mathjax-newcm to your server as well, and incorporate

output: {
    fontPath: 'BASE_URL/mathjax-newcm'
}

into your configuration as well.

dpvc avatar Sep 17 '25 11:09 dpvc

With output/svg I tried the following with the idea to get it to load svg.js from BASE_URL/output (assuming SVG output does not use an actual font):

options:
{
	skipHtmlTags: {'[+]': ['text']},
	ignoreHtmlClass: 'geDisableMathJax'
},
output:
{
	fontPath: 'BASE_URL/output'
},
loader:
{
	load: [(urlParams['math-output'] == 'html') ?
		'output/chtml' : 'output/svg', 'input/tex',
		'input/asciimath', 'ui/safe']
}

This results in MathJax(?): Cannot read properties of null (reading 'OPTIONS') in the browser console.

alderg avatar Sep 18 '25 06:09 alderg

The svg.js file that you are seeing coming from jsdelivr is not output/svg.js, but rather mathjax-newcm/svg.js. You need to put the mathjax-newcm directory somewhere on your server and set fontPath to point to that location.

dpvc avatar Sep 18 '25 13:09 dpvc

Thanks, this works. However, in v4, extensions like mhchem seem to require separate fonts (https://cdn.jsdelivr.net/npm/@mathjax/mathjax-mhchem-font-extension/svg.js). Is there a way to put them into one directory? In v3 we got away with just https://github.com/jgraph/drawio/tree/dev/src/main/webapp/math/es5/input/tex/extensions

alderg avatar Sep 22 '25 05:09 alderg

Is there a way to put them into one directory?

The font distributions can be quite large, and we have not included them in the core MathJax package to keep its footprint smaller (it is already large on its own). One thing we have considered is moving some of the extensions into separate NPM packages. Should mhchem be repackaged separately, then it would make more sense to include the font with it.

dpvc avatar Sep 25 '25 11:09 dpvc

FYI, you can install the font extensions on your server as well, and configure a loader path for mathjax-mhchem-extension to point to the location of the package. The other extensions that have fonts are dsfont, bbx, and bboldx.

dpvc avatar Sep 25 '25 11:09 dpvc

How do I configure the loader path for mathjax-mhchem-extension? I read https://docs.mathjax.org/en/v4.0/options/startup/loader.html but sadly I don't get it.

alderg avatar Oct 23 '25 09:10 alderg

Use something like

MathJax = {
  loader: {
    paths: {
      'mathjax-mhchem-extension': '<path-to-your-copy-of-mathjax-mhchem-font-extension>',
    }
  }
};

dpvc avatar Oct 23 '25 10:10 dpvc

Thanks, that works. But further testing shows that many files in the font directories will be required. Since we have a limit on the number of files for our offline apps, we'll have to stick with v3 as there were only these 2 files needed for all SVG output:

  • https://github.com/jgraph/MathJax/blob/master/es5/output/svg.js
  • https://github.com/jgraph/MathJax/blob/master/es5/output/svg/fonts/tex.js

alderg avatar Oct 23 '25 12:10 alderg

Yes, the new fonts in v4 have much greater coverage, and are broken down into a number of smaller pieces.

But MathJax v4 does include the older mathjax-tex font, which is not broken up, so you could use the svg.js file from that package (and only need one file rather than two). In fact, you could use the tex-mml-svg-mathjax-tex.js file from the mathjax-tex-font package to get a single-file version of tex-mml-svg.js + the mathjax-tex SVG font. In that case, you should configure loader.paths.mathjax to point to your main mathjax directory so that it can obtain tex extensions, if any are needed.

dpvc avatar Oct 23 '25 12:10 dpvc

PS, some TeX extensions need their own fonts (like mhchem and bbm), so if you plan to use those locally, you would need to make them available as well and configure their paths). The mhchem extension, for example, only needs one file for SVG, and two for CHTML.

dpvc avatar Oct 23 '25 12:10 dpvc

Where is the svg.js file for the mathjax-tex package and how do I use it? I think for our configuration we can't use a combined JS file, but a single font file and a few extra files for extensions would solve the problem. Our current v4 configuration is:

options:
{
	skipHtmlTags: {'[+]': ['text']},
	ignoreHtmlClass: 'geDisableMathJax'
},
loader:
{
	load: ['output/svg', 'input/tex', 'input/asciimath', 'ui/safe'],
	paths:
	{
		'mathjax-mhchem-extension': DRAW_MATH_URL + '/fonts/mathjax-mhchem-font-extension'
	}
},
output:
{
	fontPath: DRAW_MATH_URL + '/fonts/mathjax-newcm-font'
},
startup:
{
	pageReady: function()
	{
		for (var i = 0; i < Editor.mathJaxQueue.length; i++)	
		{	
			Editor.doMathJaxRender(Editor.mathJaxQueue[i]);	
		}
	}
}

alderg avatar Oct 23 '25 13:10 alderg

The needed font files can be obtained from:

  • https://cdn.jsdelivr.net/npm/@mathjax/mathjax-tex-font@4/svg.js
  • https://cdn.jsdelivr.net/npm/@mathjax/mathjax-mhchem-font-extension@4/svg.js

or you could get them from the npm packages @mathjax/mathjax-tex-font and @mathjax/mathjax-mhchem-font-extension.

I have put together a minimal list of files for an SVG installation that includes the components you have listed above. You don't give the script command for the component you are actually loading, so I don't know if it is startup.js or one of the combined components. I'm assuming that it is tex-sig.js so that you get the usual set of default extensions (like the ams extension, the require and autoload extensions, the newcommand extension, the menu extension, and the accessibility extensions). If you using startup.js, then that is much more limited, and the list below would need to be altered (both to add some and to remove some).

Here are the files I would recommend. I've placed them in a directory called minimal, but you can put them on your server wherever you like.

minimal/mathjax
minimal/mathjax/a11y/complexity.js
minimal/mathjax/input/asciimath.js
minimal/mathjax/input/tex.js
minimal/mathjax/input/tex/extensions
minimal/mathjax/input/tex/extensions/action.js
minimal/mathjax/input/tex/extensions/amscd.js
minimal/mathjax/input/tex/extensions/bbox.js
minimal/mathjax/input/tex/extensions/begingroup.js
minimal/mathjax/input/tex/extensions/boldsymbol.js
minimal/mathjax/input/tex/extensions/braket.js
minimal/mathjax/input/tex/extensions/bussproofs.js
minimal/mathjax/input/tex/extensions/cancel.js
minimal/mathjax/input/tex/extensions/cases.js
minimal/mathjax/input/tex/extensions/centernot.js
minimal/mathjax/input/tex/extensions/color.js
minimal/mathjax/input/tex/extensions/colortbl.js
minimal/mathjax/input/tex/extensions/empheq.js
minimal/mathjax/input/tex/extensions/enclose.js
minimal/mathjax/input/tex/extensions/extpfeil.js
minimal/mathjax/input/tex/extensions/gensymb.js
minimal/mathjax/input/tex/extensions/html.js
minimal/mathjax/input/tex/extensions/mathtools.js
minimal/mathjax/input/tex/extensions/mhchem.js
minimal/mathjax/input/tex/extensions/physics.js
minimal/mathjax/input/tex/extensions/textcomp.js
minimal/mathjax/input/tex/extensions/unicode.js
minimal/mathjax/input/tex/extensions/units.js
minimal/mathjax/input/tex/extensions/upgreek.js
minimal/mathjax/input/tex/extensions/verb.js
minimal/mathjax/sre/mathmaps
minimal/mathjax/sre/mathmaps/base.json
minimal/mathjax/sre/mathmaps/en.json
minimal/mathjax/sre/mathmaps/nemeth.json
minimal/mathjax/sre/speech-worker.js
minimal/mathjax/tex-svg-nofont.js
minimal/mathjax/ui/safe.js

minimal/fonts/mathjax-mhchem-font-extension/svg.js
minimal/fonts/mathjax-tex-font/svg.js

The files in minimal/mathjax come from the mathjax npm package (or this GitHub repository). The minimal/fonts files come from the packages or links listed at the top.

I've removed some of the TeX extensions that you don't need, either because they are already included in tex-svg.js, or because you aren't including them in your configuration and they don't make sense to be auto-loaded or required. You may wish to remove additional ones, but then should modify the autoload and safe configurations to make attempts to load them produce appropriate messages.

Here is a configuration that sets things up to use this directory structure:

<script>
const DRAW_MATH_URL = './minimal';  // use your own URL here.

MathJax = {
  options: {
    skipHtmlTags: {'[+]': ['text']},
    ignoreHtmlClass: 'geDisableMathJax',
  },
  output: {
    font: 'mathjax-tex',  // tell MathJax to use the old MathJax TeX font
  },
  loader: {
    load: ['input/asciimath', 'ui/safe'],
    paths: {
      'fonts': '[mathjax]/../fonts',  // URL where fonts will be found (next to the directory where MathJax is found)
      'mathjax-tex': '[fonts]/mathjax-tex-font',
      'mathjax-mhchem-extension': '[fonts]/mathjax-mhchem-font-extension',
    },
  },
  startup: {
    pageReady() {
      //
      //  Mark this as preloaded (the a11y extensions load this but forgot to mark it as preloaded)
      //
      MathJax.loader.preLoaded('input/mml');
      //
      //  Here we disable some menu items that aren't supported with the limited
      //  files given above.
      //
      const menu = MathJax.startup.document.menu.menu;
      menu.findID('Settings', 'Renderer').disable();
      menu.findID('Speech', 'A11yLanguage').disable();
      menu.findID('Braille', 'euro').hide();
//      menu.findID('Options', 'Collapsible').disable();
      menu.findID('Options', 'AssistiveMml').disable();
      menu.constructor.DynamicSubmenus.delete('A11yLanguage');

      //
      //  Do your rendering (more succinctly)
      //
      Editor.mathJaxQueue.forEach((math) => Editor.doMathJaxRender(math));

      //
      //  Do the usual default Page Ready actions (e.g., render anything else on the page).
      //  the pageReady() function must return a promise in order to have synchronization
      //  with MathJax be correct, so either do the default pageReady(), or return
      //  a resolved promise.  If your doMathJaxRender() function calls promise-based
      //  MathJax calls, then you should collect those promises and return
      //  Promise.all(...) for the array of them instead.
      //
      return MathJax.startup.defaultPageReady();
      // return Promise.resolve();
    },
  },
};

//
//  Here we load tex-svg-nofont.js since we are using a non-default font.
//  This will be a smaller download for your users especially for SVG output.
//
const script = document.createElement('script');
script.src = DRAW_MATH_URL + '/mathjax/tex-svg-nofont.js';
document.head.append(script);

</script>

You could save another file by disabling the collapsible math item (commented out above) and removing the minimal/mathjax/a11y directory and its contents.

I think that about covers it.

dpvc avatar Oct 25 '25 21:10 dpvc

Thanks a ton! We're using startup.js and in v3 we had the following list of files for the desktop app:

		"math/es5/startup.js",
		"math/es5/core.js",
		"math/es5/ui/safe.js",
		"math/es5/output/svg.js",
		"math/es5/input/tex.js",
		"math/es5/input/asciimath.js",
		"math/es5/output/svg/fonts/tex.js",

alderg avatar Oct 26 '25 05:10 alderg

Thanks a ton! We're using startup.js

Ok, then the file list will need to be updated, but first I need answers to a few questions:

  • Do you want your math to be accessible to people with screen readers or other assistive needs?
  • Do you want to include any other extensions than the ones already in input/tex?
  • You seem to want mhchem, but haven't includes the needed files in your v3 list of files, and it is not explicitly loaded in your new configuration. Do you want to add that?
  • The input/tex component includes require and autoload to accessing extensions (like mhchem). Do you want to be able to use \require or have other extensions autoload when used? If so, which ones?
  • You have not included the MathJax contextual menu. Is that intentional?

The question about \require and autoload is important, as it determines what tex extension files you need to include, and if you are limiting them, additional configuration will be needed. If you do want to allow \require and autoload, then you will need to be using the promise-based MathJax API calls in order to process them properly.

The question about the menu is important because it controls access to some important features, like being able to view the TeX source, or copy the MathML output, or adjust accessibility settings. These are particularly important for those with assistive needs.

dpvc avatar Oct 26 '25 14:10 dpvc

If we can get a minimal configuration working we can move to v4 and sort the details later:

  • No accessibility for screen readers and assistive needs
  • No extensions and only SVG output in the offline app (with the idea to get the minimal number of files as in https://github.com/mathjax/MathJax/issues/3436#issuecomment-3448037723 for the v3 offline app)
  • In the online app, we want to be able to use \require to autoload all extensions listed in https://github.com/jgraph/MathJax/tree/master/es5/input/tex/extensions
  • If possible we want to support output/chtml as an option in the online app, and host the fonts for mchem, dsfont, bbx, and bboldx
  • Not including MathJax contextual menu was intentional (we are a diagramming app with its own context menu). Would it be possible to view the TeX source or copy the MathML output via the MathJax API when we detect MathJax under the mouse?

We are using the promise-based MathJax API, so require/autoload should work (see https://github.com/jgraph/drawio/blob/dev/src/main/webapp/js/diagramly/Editor.js#L3109).

Thanks in advance for your help!

alderg avatar Oct 30 '25 07:10 alderg

OK, here is the file list for the minimal off-line app that you describe:

minimal/mathjax/ui/safe.js
minimal/mathjax/core.js
minimal/mathjax/input/asciimath.js
minimal/mathjax/input/tex.js
minimal/mathjax/startup.js
minimal/mathjax/output/svg.js
minimal/fonts/mathjax-tex-font/svg.js

It is essentially the same as you had for v3, with the font location changed. Here is a configuration for that:

<script>
const DRAW_MATH_URL = './minimal';
MathJax = {
  options: {
    skipHtmlTags: {'[+]': ['text']},
    ignoreHtmlClass: 'geDisableMathJax',
  },
  output: {
    font: 'mathjax-tex',
  },
  tex: {
    packages: {'[-]': ['require', 'autoload']},
  },
  loader: {
    load: ['input/tex', 'input/asciimath', 'output/svg', 'ui/safe'],
    paths: {
      'fonts': '[mathjax]/../fonts',
      'mathjax-tex': '[fonts]/mathjax-tex-font',
    },
  },
};

const script = document.createElement('script');
script.src = DRAW_MATH_URL + '/mathjax/startup.js';
document.head.append(script);

</script>

Note that I have removed the require and autoload packaged from the tex.packages lists, since they won't work with this minimal configuration. it would be possible to make a configuration that loads the seven files listed above from your site, but loads other extensions remotely, when possible, if you want that.

For the on-line app, you would need to add

minimal/mathjax/input/tex/extensions/action.js
minimal/mathjax/input/tex/extensions/amscd.js
minimal/mathjax/input/tex/extensions/bbm.js
minimal/mathjax/input/tex/extensions/bboldx.js
minimal/mathjax/input/tex/extensions/bbox.js
minimal/mathjax/input/tex/extensions/begingroup.js
minimal/mathjax/input/tex/extensions/boldsymbol.js
minimal/mathjax/input/tex/extensions/braket.js
minimal/mathjax/input/tex/extensions/bussproofs.js
minimal/mathjax/input/tex/extensions/cancel.js
minimal/mathjax/input/tex/extensions/cases.js
minimal/mathjax/input/tex/extensions/centernot.js
minimal/mathjax/input/tex/extensions/color.js
minimal/mathjax/input/tex/extensions/colortbl.js
minimal/mathjax/input/tex/extensions/dsfont.js
minimal/mathjax/input/tex/extensions/empheq.js
minimal/mathjax/input/tex/extensions/enclose.js
minimal/mathjax/input/tex/extensions/extpfeil.js
minimal/mathjax/input/tex/extensions/gensymb.js
minimal/mathjax/input/tex/extensions/html.js
minimal/mathjax/input/tex/extensions/mathtools.js
minimal/mathjax/input/tex/extensions/mhchem.js
minimal/mathjax/input/tex/extensions/physics.js
minimal/mathjax/input/tex/extensions/textcomp.js
minimal/mathjax/input/tex/extensions/unicode.js
minimal/mathjax/input/tex/extensions/units.js
minimal/mathjax/input/tex/extensions/upgreek.js
minimal/mathjax/input/tex/extensions/verb.js

I've removed the ones that are already included in input/tex (like ams, and require), and the ones that don't make sense to be required (like configmacros and tagformat) or that you would not want to be required (like setoptions). The rest of these should be OK to load.

You will also need to include the fonts for mhchem, bbm, bboldx, and dsfont) to support those extensions. That means you will need the svg.js files from those packages (use URLs like the links I gave in my earlier comment) and would need to configure their paths. E.g.,

  loader: {
    load: ['input/tex', 'input/asciimath', 'output/svg', 'ui/safe'],
    paths: {
      'fonts': '[mathjax]/../fonts',
      'mathjax-tex': '[fonts]/mathjax-tex-font',
      'mathjax-mhchem-extension': '[fonts]/mathjax-mhchem-font-extension',
      'mathjax-bbm-extension': '[fonts]/mathjax-bbm-font-extension',
      'mathjax-bboldx-extension': '[fonts]/mathjax-bboldx-font-extension',
      'mathjax-dsfont-extension': '[fonts]/mathjax-dsfont-font-extension',
    },
  },

If you want to be able to handle CHTML output as well, then you need to include the output/chtml.js file, as well as the chtml.js files for all the fonts, together with the chtml/woff2 directories from them. If course, without the menu, the user doesn't have a means of switching renderer (unless you have an option of your own that sets the MathJax configuration to replace output/svg buy output/chtml in the MathJax loader.load array).

Would it be possible to view the TeX source or copy the MathML output via the MathJax API when we detect MathJax under the mouse?

The functions to do that are part of the menu code, so without the menu, you would have to provided you own versions. One could probably copy the ones from the menu code as a starting point, but there would likely need to be adjustments. I haven't looked at that code recently. Alternatively, you could load the menu components but set options.enableMenu to false so that you would have access to the menu code, but there would be no menus attached to the typeset math. Again, I would need to look into this more fully to see if that would really work, but It could probably be made to work.

We are using the promise-based MathJax API,

The line you link to is not (all the promise-based typesetting and conversion functions end in Promise), but it looks like you are trapping the errors and handling the retry by hand. It would be easier just to use the MathJax.typesetPromse() call initially via something like

MathJax.typesetPromise().then(mathjaxDone).catch((e) => {
  console.log('Error in MathJax.typesetPromise: ' + e.toString());
  mathJaxDone();
});

rather than having to handle the retries yourself.

Anyway, I think that should get you the list of files you need.

dpvc avatar Nov 03 '25 17:11 dpvc