.. _node-preload: ############################## Using Components Synchronously ############################## MathJax components are designed to operate in a browser environment, where loading components is inherently asynchronous as the browser has to wait for files to be downloaded over the network. This means that using MathJax's promise-based typesetting and conversion functions is generally required, and so the rest of your code needs to be designed to handle the promises that are part of MathJax. That can complicate your code, and your work with other frameworks may require you to operate synchronously. In a `node` application, you can load components individually yourself via node's ``import`` or ``require()`` commands, rather than relying on MathJax's :ref:`loader-component`, which operates asynchronously. With a little care, this can give you the ability to work with MathJax synchronously (i.e., without the need to use promises). It also gives you more complete control over the loading of components, though in this case you do need to handle loading dependencies yourself, and make sure the components are loaded in the right order. This approach lets you take advantage of using the convenient packaging of MathJax into individual components, the configuration of MathJax through the global :js:data:`MathJax` variable, and its automatic creation of objects and methods by the :ref:`startup-component` component, while still allowing you to work completely synchronously with the MathJax code. Note, however, that this means you will not be able to use the ``\require`` macro in TeX expressions, and that TeX extensions will not be able to be auto-loaded, as those actions are asynchronous. .. 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, when possible. The examples below show how to pre-load the needed font data so that you can still work synchronously even with the larger v4 fonts. ----- .. _preload-basics: The Basics of Pre-Loading Extensions ==================================== First, :ref:`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 use ``@mathjax/src``, not just ``mathjax``, since the examples given here rely on access to the component definitions and other source code that is not part of the ``mathjax`` package, which includes only the bundled files. The examples linked below load the needed MathJax components my hand, using the source files in MathJax's ``components`` directories (the files used to create the bundled components found in the ``bundle`` directory). The examples use the path ``@mathjax/src/components/js`` to access the component definitions, as MathJax's :file:`package.json` file is set up to map this to the ``components/mjs`` directory when used in an ``import`` command, and to ``components/cjs`` when used in a ``require()`` command, so the same address will give you the proper ES or CommonJS module for the mechanism you are using to load it. All the examples begin with the following lines: .. code-block:: javascript import {MathJax} from '@mathjax/src/js/components/global.js'; import {insert} from '@mathjax/src/js/util/Options.js'; import '@mathjax/src/js/components/startup.js'; import '@mathjax/src/components/js/core/core.js'; import '@mathjax/src/components/js/adaptors/liteDOM/liteDOM.js'; (or the equivalent using ``require()``). The first line sets up the global :js:data:`MathJax` variable, handling any existing :js:data:`MathJax` value as a MathJax configuration (in case the examples below are included in a larger application that sets up its own configuration). The second line obtains a utility for inserting values into the MathJax configuration, overriding existing values, if any. The third line loads the :ref:`startup-component` component, which will be called later to instantiate the needed MathJax objects and create the various typeset and conversion functions. The fourth line loads the core component, and the fifth loads the :ref:`liteDOM-component` component, which implements a limited DOM for use in node. (You would not load this component if you were using this technique to make a browser-based application, and there are other alternative adaptors that could be used if you need a more robust DOM implementation.) Next we load the TeX components that we plan to use: .. code-block:: javascript import '@mathjax/src/components/js/input/tex-base/tex-base.js'; import '@mathjax/src/components/js/input/tex/extensions/ams/ams.js'; import '@mathjax/src/components/js/input/tex/extensions/newcommand/newcommand.js'; import '@mathjax/src/components/js/input/tex/extensions/color/color.js'; In this case, we use the `tex-base` component, which only includes the `base` configuration, whereas the :ref:`tex-component` component includes the :ref:`require ` and the :ref:`autoload ` components, which we can't support synchronously. We also load the :ref:`ams `, :ref:`newcommand `, and :ref:`color ` components. These are just for illustration; you can include whatever components you need for the expressions you will be processing. Next, we load the CommonHTML output jax .. code-block:: javascript import '@mathjax/src/components/js/output/chtml/chtml.js'; though you could use the SVG output jax if you prefer. Now that everything is loaded (though some of the examples load additional items), we configure the TeX input jax to use the pre-loaded extensions: .. code-block:: javascript insert(MathJax.config, { tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} } }, false); This uses the :meth:`insert()` function that we loaded earlier. Then we start up MathJax: .. code-block:: javascript MathJax.config.startup.ready(); and finally process some math and print the results: .. code-block:: javascript const math = process.argv[2] || ''; const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); Here, we take the math from the command line arguments, but you will likely obtain the math to be processed from elsewhere in your code. You may want to provide a :meth:`typeset()` function that encapsulates the code to do the typesetting. This is the outline that is illustrated in the examples below. They include variations on this theme that show how to handle fonts synchronously in several different ways. There is also an example that handles speech generation, though that requires one asynchronous step, and only creates speech for the top-level element of the resulting expression. ----- .. _preload-import-tex: Using the MathJax-TeX font ========================== This example uses the ``mathjax-tex`` font, which is the original font-set used by MathJax v2 and v3. Because this font has limited character coverage it is *not* broken into multiple pieces, so you don't have to worry about dynamically loaded font data, so preloading the TeX extensions is all you have to worry about in order to be able to process math synchronously. The lines that are required for this that differ from the outline given above are highlighted below. Note that you will need to use ``npm`` or ``pnpm`` to install the ``@mathjax/mathjax-tex-font`` package in order to use the ``mathjax-tex`` font. .. code-block:: javascript :linenos: :emphasize-lines: 20-23, 32-34 import {MathJax} from '@mathjax/src/js/components/global.js'; import {insert} from '@mathjax/src/js/util/Options.js'; import '@mathjax/src/js/components/startup.js'; import '@mathjax/src/components/js/core/core.js'; import '@mathjax/src/components/js/adaptors/liteDOM/liteDOM.js'; // // Load the TeX components that we want to use // import '@mathjax/src/components/js/input/tex-base/tex-base.js'; import '@mathjax/src/components/js/input/tex/extensions/ams/ams.js'; import '@mathjax/src/components/js/input/tex/extensions/newcommand/newcommand.js'; import '@mathjax/src/components/js/input/tex/extensions/color/color.js'; // // Load the output jax // import '@mathjax/src/components/js/output/chtml/chtml.js'; // // Load the mathjax-tex font // import {MathJaxTexFont} from '@mathjax/mathjax-tex-font/js/chtml.js'; // // Add the pre-loaded TeX extensions here, and specify the font // insert(MathJax.config, { tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} }, chtml: { fontData: MathJaxTexFont } }, false); // // Start up MathJax // MathJax.config.startup.ready(); // // Convert some math synchronously // const math = process.argv[2] || ''; const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); ----- .. _preload-import-newcm: Using the MathJax-NewCM font ============================ This example uses the default font for MathJax v4, the ``mathjax-newcm`` font, which is based on the New Computer Modern font. Because ``mathjax-newcm`` has extensive character coverage, it is broken into a number of separate files that are loaded dynamically when needed. That means producing output that uses ``mathjax-newcm`` can be asynchronous, as some character data may need to be loaded from additional files. To use this font synchronously, you need to pre-load the font data files for the characters that you expect to need. The example below loads the calligraphic characters, but you could include additional ``import`` commands to load other ranges of characters. These files can be found in the ``node_modules/@mathjax/mathjax-tex-font/mjs/chtml/dynamic`` directory. The ``mathjax-tex-font`` package should be installed automatically when you install the ``@mathjax/src`` npm package, so you shouldn't need to install it by hand, as you did for the ``mathjax-tex`` font in the previous example. The lines that are required for this that differ from the outline given above are highlighted below. .. code-block:: javascript :linenos: :emphasize-lines: 20-28, 37-39, 47-52 import {MathJax} from '@mathjax/src/js/components/global.js'; import {insert} from '@mathjax/src/js/util/Options.js'; import '@mathjax/src/js/components/startup.js'; import '@mathjax/src/components/js/core/core.js'; import '@mathjax/src/components/js/adaptors/liteDOM/liteDOM.js'; // // Load the TeX components that we want to use // import '@mathjax/src/components/js/input/tex-base/tex-base.js'; import '@mathjax/src/components/js/input/tex/extensions/ams/ams.js'; import '@mathjax/src/components/js/input/tex/extensions/newcommand/newcommand.js'; import '@mathjax/src/components/js/input/tex/extensions/color/color.js'; // // Load the output jax // import '@mathjax/src/components/js/output/chtml/chtml.js'; // // Load the font to use, and any dynamic font files // import {MathJaxNewcmFont} from '@mathjax/mathjax-newcm-font/js/chtml.js'; import '@mathjax/mathjax-newcm-font/js/chtml/dynamic/calligraphic.js'; // // ... load any additional ones here, and add them to the array below. // const fontPreloads = ['calligraphic']; // // Add the pre-loaded TeX extensions here // insert(MathJax.config, { tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} }, chtml: { fontData: MathJaxNewcmFont } }, false); // // Start up MathJax // MathJax.config.startup.ready(); // // Activate the dynamic font files // const font = MathJax.startup.document.outputJax.font; const dynamic = MathJaxNewcmFont.dynamicFiles; fontPreloads.forEach(name => dynamic[name].setup(font)); // // Convert some math synchronously // const math = process.argv[2] || ''; const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); This approach could also be used to handle any of the MathJax fonts available for v4 by first installing the needed font, and changing lines 23 to 28, line 38, and line 51 to refer to the correct font. Technically, lines 37 to 39 are not needed, since the ``mathjax-newcm`` font is the default for MathJax v4, but these lines show how to configure MathJax for any font. ----- .. _preload-require-newcm: A CommonJS Example ================== This example illustrates using ``require()`` rather than ``import`` in a CommonJS module. Because ``require()`` is synchronous, this makes it possible to load the dynamic font files in a loop rather than having to list them individually as we do above. MathJax provides a :js:meth:`loadDynamicFilesSync()` method for doing so, but it requires that you specify a mechanism for loading the files. This is usually an asynchronous method, but since we are using ``require()``, we indicate that it is actually synchronous. The changes needed for this are highlighted below. Of course, all the ``import`` commands have been changed to equivalent ``require()`` commands throughout. .. code-block:: javascript :linenos: :emphasize-lines: 7-11, 30-34, 45-50 const {MathJax, combineDefaults} = require('@mathjax/src/js/components/global.js'); const {insert} = require('@mathjax/src/js/util/Options.js'); require('@mathjax/src/js/components/startup.js'); require('@mathjax/src/components/js/core/core.js'); require('@mathjax/src/components/js/adaptors/liteDOM/liteDOM.js'); // // Needed for asyncLoad below // const {mathjax} = require('@mathjax/src/js/mathjax.js'); const {Package} = require('@mathjax/src/js/components/package.js'); // // Load the TeX components that we plan to use // require('@mathjax/src/components/js/input/tex-base/tex-base.js'); require('@mathjax/src/components/js/input/tex/extensions/ams/ams.js'); require('@mathjax/src/components/js/input/tex/extensions/newcommand/newcommand.js'); require('@mathjax/src/components/js/input/tex/extensions/color/color.js'); // // Load the output jax // require('@mathjax/src/components/js/output/chtml/chtml.js'); // // Set the font path and add the pre-loaded TeX extensions here // insert(MathJax.config, { loader: { paths: { 'mathjax-newcm': '@mathjax/mathjax-newcm-font/js' } }, tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} } }, false); // // Start up MathJax // MathJax.config.startup.ready(); // // Load the font dynamic files // mathjax.asyncLoad = (file) => require(Package.resolvePath(file)); mathjax.asyncIsSynchronous = true; MathJax.startup.document.outputJax.font.loadDynamicFilesSync(); // // Convert some math synchronously // const math = process.argv[2] || '' const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); This example loads **all** the dynamic font files, so you don't have to know which ones you will need. Note, however, that that can be a large number of files, with a large amount of data, much of which likely will never be used. This can increase the startup time for your application, so you may want to use the technique of individually loading only the files you actually need. In an ES module, one could use .. code-block:: javascript mathjax.asyncLoad = (file) => import(Package.resolvePath(file)); await MathJax.startup.document.outputJax.font.loadDynamicFiles(); to load all the font files, but that is asynchronous, so you need to use ``await`` to wait for the command to complete. Be aware that that means other parts of your code could run before that is completed, so you will need to be careful not to call MathJax commands until after the loading is complete. ----- .. _preload-import-cjs: Loading All Font Data Synchronously =================================== In the previous example, we took advantage of ``require()`` to make the :js:meth:`loadDynamicFilesSync()` method available to load all the font data before processing any math. Although ES modules (using ``import`` and ``export``) don't have a ``require()`` command, it is possible to define one that can be used to load CommonJS modules. MathJax provides a file that does that for you, so you don't need to know the details of how that works. This is done in line 12 below, which is the only new line added to the CommonJS example above. In order for this to work, you need to use the CommonJS versions of all the MathJax modules. That is done by changing the ``js`` directory name to ``cjs`` everywhere it occurs in the ``import`` commands and the loader path definition. .. code-block:: javascript :linenos: :emphasize-lines: 12 import {MathJax} from '@mathjax/src/cjs/components/global.js'; import {insert} from '@mathjax/src/cjs/util/Options.js'; import '@mathjax/src/cjs/components/startup.js'; import '@mathjax/src/components/cjs/core/core.js'; import '@mathjax/src/components/cjs/adaptors/liteDOM/liteDOM.js'; // // Needed for asyncLoad below // import {mathjax} from '@mathjax/src/cjs/mathjax.js'; import {Package} from '@mathjax/src/cjs/components/package.js'; import '@mathjax/src/components/require.mjs'; // // Load the TeX components that we want to use // import '@mathjax/src/components/cjs/input/tex-base/tex-base.js'; import '@mathjax/src/components/cjs/input/tex/extensions/ams/ams.js'; import '@mathjax/src/components/cjs/input/tex/extensions/newcommand/newcommand.js'; import '@mathjax/src/components/cjs/input/tex/extensions/color/color.js'; // // Load the output jax // import '@mathjax/src/components/cjs/output/chtml/chtml.js'; // // Set the font path and add the pre-loaded TeX extensions here // insert(MathJax.config, { loader: { paths: { 'mathjax-newcm': '@mathjax/mathjax-newcm-font/cjs' } }, tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} } }, false); // // Start up MathJax // MathJax.config.startup.ready(); // // Load the font dynamic files // mathjax.asyncLoad = (file) => require(Package.resolvePath(file)); mathjax.asyncIsSynchronous = true; MathJax.startup.document.outputJax.font.loadDynamicFilesSync(); // // Convert some math synchronously // const math = process.argv[2] || ''; const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); If you don't use the ``cjs`` paths explicitly, the ``import`` commands will load the ES Modules, while the ``asyncLoad`` command, using ``require()``, will load the CommonJS modules. That will cause all of MathJax to be loaded again from the ``cjs`` directories, and you will have two copies of everything. Then :js:meth:`loadDynamicFilesSync()` will load the dynamic files into the ``cjs`` copies, while your typesetting will be performed by the ``mjs`` versions, which don't have the dynamic files loaded, and that will lead to a ``MathJax retry`` error when any dynamic file is needed. That is, your loading of the dynamic files will have no effect, because they went into the wrong copy of MathJax. ----- .. _preload-import-speech: Synchronous Typesetting with Speech =================================== This example builds on the :ref:`preload-import-newcm` example by adding the needed code for generating speech output for the resulting expressions. This is accomplished by include the speech-rule-engine (SRE) code. Unfortunately, the startup code for SRE is inherently asynchronous, so you do need to handle one promise during initialization and wait for that before performing any typesetting operations, but once that one promise is resolved, you can work synchronously from there. Since SRE uses ``require()`` to load its dependencies when it is used in node, we include the :file:`components/require.mjs` file to make that available from our ES module. Lines 7 through 13 are the import commands needed to load SRE and other needed modules. Lines 47 through 68 define a function that adds a speech string to the root node of the internal MathML tree, and then adds a ``renderAction`` to the document options that performs the :meth:`addSpeech()` action. The ``aria-label`` attribute will be included in the DOM elements generated by MathJax later in the conversion step. Lines 70 through 75 give the asynchronous code that must be used to get SRE up and running before typesetting can be done synchronously. This waits for SRE to set itself up, passing it the locale and modality needed for the language specified as the second command-line argument to this script, then waits for SRE to be ready before startup up MathJax. You may need to use ``npm`` or ``pnpm`` to install the ``speech-rule-engine`` npm module, if it isn't already installed. .. code-block:: javascript :linenos: :emphasize-lines: 7-13, 47-68, 70-75 import {MathJax} from '@mathjax/src/js/components/global.js'; import {insert} from '@mathjax/src/js/util/Options.js'; import '@mathjax/src/js/components/startup.js'; import '@mathjax/src/components/js/core/core.js'; import '@mathjax/src/components/js/adaptors/liteDOM/liteDOM.js'; // // Load code for SRE // import '@mathjax/src/components/require.mjs'; import '@mathjax/src/components/js/a11y/semantic-enrich/semantic-enrich.js'; import {setupEngine, engineReady, toSpeech} from 'speech-rule-engine/js/common/system.js'; import {STATE} from '@mathjax/src/js/core/MathItem.js'; // // Load the TeX components that we want to use // import '@mathjax/src/components/js/input/tex-base/tex-base.js'; import '@mathjax/src/components/js/input/tex/extensions/ams/ams.js'; import '@mathjax/src/components/js/input/tex/extensions/newcommand/newcommand.js'; import '@mathjax/src/components/js/input/tex/extensions/color/color.js'; // // Load the output jax // import '@mathjax/src/components/js/output/chtml/chtml.js'; // // Load the font to use, and any dynamic font files // import {MathJaxNewcmFont} from '@mathjax/mathjax-newcm-font/js/chtml.js'; import '@mathjax/mathjax-newcm-font/js/chtml/dynamic/calligraphic.js'; // // ... load any additional ones here, and add them to the array below. // const fontPreloads = ['calligraphic']; // // Add the pre-loaded TeX extensions here // insert(MathJax.config, { tex: { packages: {'[+]': ['ams', 'newcommand', 'color']} } }, false); // // Add a speech string to the root math element // function addSpeech(item) { const speech = toSpeech(MathJax.startup.toMML(item.root)); item.root.attributes.set('aria-label', speech); } // // Add a render action that computes the speech // insert(MathJax.config, { options: { renderActions: { addSpeech: [ STATE.COMPILED + 10, (doc) => {for (const item of doc.math) addSpeech(item)}, (item, doc) => addSpeech(item) ] } } }, false); // // Start up SRE // const locale = process.argv[3] || 'en'; const modality = locale === 'nemeth' || locale === 'euro' ? 'braille' : 'speech'; await setupEngine({modality, locale}).then(() => engineReady()); // // Start up MathJax // MathJax.config.startup.ready(); // // Activate the dynamic font files // const font = MathJax.startup.document.outputJax.font; const dynamic = MathJaxNewcmFont.dynamicFiles; fontPreloads.forEach(name => dynamic[name].setup(font)); // // Convert some math synchronously // const math = process.argv[2] || ''; const adaptor = MathJax.startup.adaptor; console.log(adaptor.outerHTML(MathJax.tex2chtml(math))); If you don't want to, or can't, use ``await``, you can add one more ``then()`` clause whose function starts up the main code for your application instead. ----- More examples are available in the `MathJax node demos `__ for using MathJax from a node application. In particular, see the `preloading examples `__ for illustrations of how to load MathJax components by hand in a `node` application. |-----|