Using MathJax Components in Node

It is possible to use MathJax in a node application in essentially the same way that it is used in a browser. In particular, you can load MathJax components and configure MathJax using a global MathJax object and load a combined component file or the startup component via node’s import or require() commands.

First get a copy of the MathJax code library. Here, we will assume you have used npm or pnpm to install the @mathjax/src@4 package. You will need to change the require() or import statements accordingly in the examples below if you have loaded mathjax@4 or obtained MathJax from the MathJax-src GitHub repository.

In MathJax, the loading of components is asynchronous, and so you may need to use promises or the await command to mediate the flow of your program, particularly program startup. Once MathJax’s components are loaded, however, you can call the non-promise-based functions, but should use the promise-based ones if you want to support autoloading of extensions, the \require macro in TeX input, or the v4 fonts with larger character coverage.

Warning

In MathJax v4, with the introduction of new fonts that include many more characters than the original MathJax TeX fonts did, the fonts have been broken into smaller pieces so that your readers don’t have to download the entire font and its data for characters that may never be used. That means that typesetting mathematics may need to operate asynchronously even if the TeX doesn’t include \require or any auto-loaded extensions, as the output itself could need extra font data files to be loaded. Thus in version 4, it is always best to use the promise-based commands.

The synchronous examples show how to operate synchronously from the outset, if that is required.


Configuring and Loading Components in Node

As with MathJax in a browser, using MathJax components in a node application consists of two steps: configuring MathJax, and Loading a MathJax combined component to process the configuration. Just as in a browser, the configuration is specified in the global MathJax variable.

Configuration for Node vs. the Web

If you are using MathJax components as part of a larger application that will be bundled for use in a web browser, then your MathJax configuration should be the same as the configuration you would use if you were loading MathJax into the web page directly, with one exception: you may need to provide the URL where MathJax should load any extensions that are needed once MathJax is running. Normally, MathJax determines that URL based on the src attribute of the script tag that loaded it, but since MathJax is part of your larger application, that URL is probably not appropriate, so you will need to specify the correct one yourself.

This is done using the mathjax property of the paths section in the loader block of your MathJax configuration. For example,

global.MathJax = {
  loader: {
    paths: {
      mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@4'
    }
  }
}

would set things up so that extensions would be loaded from the jsDelivr CDN. You could, of course, make the extensions available on your own server and set the URL to point to that.

If you are using MathJax components as part of a server-side or command-line application, you should set the mathjax path to @mathjax/src/bundle instead:

global.MathJax = {
  loader: {
    paths: {
      mathjax: '@mathjax/src/bundle'
    }
  }
}

so that MathJax will take additional components from the bundle directory.

Note

In version 4, the bundle directory replaces the es5 directory from version 3.

For non-browser applications, there are two additional steps you need to take. First, you must tell MathJax to use import() or require() as the mechanism for loading external files, and second, you need to load a non-browser DOM adaptor. MathJax provides a light-weight DOM implementation (called liteDOM) that is sufficient for MathJax’s needs without unnecessary overhead, so you probably want to use that. If you need a more full-featured DOM implementation, you can use another one, such as jsdom or linkedom (MathJax does provide adaptors for these). It is even possible to use puppeteer with headless Chrome in order to be able to access a full DOM implementation from node.

Both of these features are set in the loader block of your MathJax configuration object, as illustrated in the sections below.

Configuring MathJax for Use with import

Because the configuration must be in place before the MathJax component is loaded, if you are using import commands rather than require(), that means you either need to put the MathJax configuration into a separate file to be imported before MathJax itself, or you need to use the promise-based import() function to load MathJax.

So you could create a file called mathjax-config.mjs containing

global.MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: (file => import(file))
  },
  // additional configuration here
};

and then use

import './mathjax-config.js';
import '@mathjax/src/bundle/tex-chtml.js';
await MathJax.startup.promise;

// your code that uses MathJax here

MathJax.done();

to load the tex-chtml combined component with that configuration, and wait for MathJax to set itself up.

Note

In MathJax v4, the speech generation is performed in web-workers (in the browser) or worker-threads (in node applications), and once these are started, they will prevent the node application from ending if they are not shut down. So v4 includes the MathJax.done() function that terminates the workers, thus allowing the node program to end. You should call this when your program is ready to end so that it can shut down properly.

Alternatively, you could do

global.MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: (file => import(file))
  },
  // additional configuration here
};
await import('@mathjax/src/bundle/tex-chtml.js');
await MathJax.startup.promise;

// your code that uses MathJax here

MathJax.done();

to include the configuration in-line before loading the tex-chtml component.

Note

ES6 modules usually use import, and require() is not available, but it is possible to define require() if you are making a command-line or server-side application. MathJax provides a file that does that for you, so if you add

import '@mathjax/src/bundle/require.mjs';

to your code, you can then use require() as described in the following section.

Configuring the Speech Locale

The default speech language is English, and the default Braille code is Nemeth. You can use the sre block of the options section of your MathJax configuration to specify a different locale or Braille version, as illustrated below.

global.MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: (file) => import(file)
  },
  options: {
    sre: {
      locale: 'de'
    }
  }
  // additional configuration here
};

await import('@mathjax/src/bundle/tex-chtml.js');
await MathJax.startup.promise;

// your code that uses MathJax here

MathJax.done();

which configures MathJax to produce speech strings in German rather than English.

Configuring MathJax for Use with require()

To use MathJax components in a CommonJS module, first set up the MathJax configuration, and then require() the combined component you want to load. So you can do

MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: require
  },
  // additional configuration here
};
require('@mathjax/src/bundle/tex-chtml.js');
MathJax.startup.promise
  .then(() => {
    //your MathJax code here
  })
  .catch((err) => console.error(err.message))
  .then(() => MathJax.done());

to configure MathJax for use with the tex-chtml combined component, and then wait for MathJax to start up, perform your commands (with error trapping), and then shut down MathJax.

Loading Individual Components

If you are using MathJax components in a server-side or command-line application, the combined components that MathJax provides may include components that you don’t need (such as the menu code and expression explorer). So you may want to configure MathJax explicitly to use only the components that you need. You do this by listing the needed components in the load array of the loader section of the MathJx configuration, and then load the startup.js module rather than a combined component.

For example,

global.MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['input/tex', 'output/svg', 'adaptors/liteDOM'],
    require: (file => import(file)),
  },
  output: {font: 'mathjax-newcm'}
}
await import('@mathjax/src/bundle/startup.js');
await MathJax.startup.promise;

would load only the TeX input jax and the SVG output jax, along with the liteDOM adaptor, but without loading the menu code, the assistive tools, or any other components. Because the input/tex component includes the require and autoload extensions, the TeX that you process could still load TeX extensions that are needed.

Because the output/svg component does not include a font, you need to configure that separately in the output section of the configuration, as shown.

Loading MathJax Components from Source

The examples above all load the webpacked versions of MathJax’s components. It is possible to load the files from the source .js files in the mjs or cjs directories, which may be useful if you are modifying the MathJax source files and want to test your changes without having to repack all the components.

To do this, you should set the source mapping in the loader section of the MathJax configuration, and then load the combined component from its source file in the components directory rather than the bundle directory. The source.js file in components/mjs or components/cjs directory contains the mapping of component names to their source definitions, and you can use that to set the source field of your MathJax configuration.

You can obtain the source.js file using @mathjax/src/components/js/source.js, and it will select the mjs or cjs directory depending on whether you use import or require() to load it. So for use in ES6 modules, you can do

import {source} from '@mathjax/src/components/js/source.js';
import '@mathjax/src/bundle/require.mjs';  // needed by speech-rule engine

global.MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: (file => import(file)),
    source: source
  }
  // additional configuration here
}
await import(source['tex-chtml']);
await MathJax.startup.promise;

// your code that uses MathJax here

MathJax.done();

while for CommonJS modules, you can do

const {source} = require('@mathjax/src/components/js/source.js');

MathJax = {
  loader: {
    paths: {mathjax: '@mathjax/src/bundle'},
    load: ['adaptors/liteDOM'],
    require: require,
    source: source
  }
  // additional configuration here
}
require(source['tex-chtml']);
MathJax.startup.promise
  .then(() => {
    //your MathJax code here
  })
  .catch((err) => console.error(err.message))
  .then(() => MathJax.done());

Calling MathJax from Components

Once you have loaded a combined component file (or the startup component), you can use the normal MathJax commands to typeset mathematics. For example, in a browser application, you can call MathJax.typesetPromise() to typeset the page.

For a command-line application, you could do

const EM = 16;          // size of an em in pixels
const EX = 8;           // size of an ex in pixels
const WIDTH = 80 * EM;  // width of container for linebreaking

function typeset(math, display = true) {
  return MathJax.tex2svgPromise(math, {
    display: display,
    em: EM,
    ex: EX,
    containerWidth: WIDTH
  }).then((node) => {
     const adaptor = MathJax.startup.adaptor;
     return(adaptor.serializeXML(adaptor.tags(node, 'svg')[0]));
  }).catch(err => console.error(err));
}

to define a typeset() command that takes a TeX string and an optional boolean that specifies whether the typesetting should be in display mode or in-line mode and returns a promise that is resolved when the typesetting is complete (while handling any waiting that had to be done to load extensions, fonts, etc.).

The typeset() promise returns the serialized SVG output, so that you could do

const svg = await typeset('\\sqrt{1+x^2}');

to get the SVG output. See the Creating Stand-Alone SVG Images section for an example of generating SVG images that handles the CSS needed by some expressions in MathJax.


Examples of Components in Node

The following combines some of the ideas described above into a single, complete example of a command-line tool that takes three arguments: a TeX string to typeset, the language locale to use, and the Braille format to use. The last two are optional, and default to en and nemeth.

 1global.MathJax = {
 2  loader: {
 3    paths: {mathjax: '@mathjax/src/bundle'},
 4    load: ['adaptors/liteDOM'],
 5    require: (file) => import(file)
 6  },
 7  options: {
 8    sre: {
 9      locale: process.argv[3] || 'en',
10      braille: process.argv[4] || 'nemeth'
11    }
12  },
13  output: {
14    linebreaks: {
15      inline: false,
16    }
17  },
18  // additional configuration here
19};
20
21await import('@mathjax/src/bundle/tex-svg.js');
22await MathJax.startup.promise;
23
24const EM = 16;          // size of an em in pixels
25const EX = 8;           // size of an ex in pixels
26const WIDTH = 80 * EM;  // width of container for linebreaking
27
28function typeset(math, display = true) {
29  return MathJax.tex2svgPromise(math, {
30    display: display,
31    em: EM,
32    ex: EX,
33    containerWidth: WIDTH
34  }).then((node) => {
35    const adaptor = MathJax.startup.adaptor;
36    return(adaptor.serializeXML(adaptor.tags(node, 'svg')[0]));
37  }).catch(err => console.error(err));
38}
39
40const math = process.argv[2] || '';
41const svg = await typeset(math);
42console.log(svg);
43
44MathJax.done();

See the Creating Stand-Alone SVG Images section for an example of generating SVG images that handles the CSS needed by some expressions in MathJax.

See the MathJax node demos for more examples of how to use MathJax from a node application. In particular, see the component-based examples for illustrations of how to configure and load MathJax components.