MathJax ES6 Modules — MathJax 4.0 documentation (original) (raw)
When MathJax was first released, the current version of JavaScript was ES5, so when the code base was moved to Typescript for v3, it was down-compiled to produce ES5 code. Modern browsers support ES6, which include many new features, such as true object class creation and inheritance, proper import and export commands, Set andMap objects, promises, iterators, and many other features that make JavaScript programs faster and more reliable.
Along with new language features, ES6 introduced a new module structure that affects how individual javascript files obtain values from other files, and how they make their own definitions available to others. ES6 modules (which we will refer to as MJS modules) use the new import and export commands to do this, while the older CommonJS module format (which we will call CJS) used require()and the module.exports object to perform those functions.
MathJax v3 uses CommonJS modules with ES5 code (though this was not quite pure ES5, since one of its dependencies was actually ES6), but modern JavaScript applications are moving more and more to MJS format. Beginning with version 4, MathJax offers both MJS and CJS versions, with the MJS version being ES6, but the CJS version remaining ES5, as in past versions. The webpacked components for use in web pages are now based on the MJS versions.
Implications for MathJax in Web Pages
The webpacked MJS files are smaller than the earlier webpacked CJS files, so that should mean faster download and compile times, and the ES6 code is more efficient, so should run faster. But since this version is no longer ES5, the es5 directory that was part of the URLs for accessing MathJax from a CDN is no longer correct. The details of how the directories have been adjusted are given in the next section, but for use on the web, the only important difference introduced by the change to ES6 is that you simply remove the /es5from the url. For example, you would use
https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js
to load the tex-mml-chtml.js combined component.
Support for IE11 has been dropped in v4, as it does not support enough of the ES6 standards. (It is possible to webpack the CJS versions, so you can build your own ES5 version if that is necessary for you. This is described at the end of this section below.) In version 3, we recommended a link to polyfill.io in order to support IE11. This should now be removed since version 4 will not work with IE11 even with the polyfill, and especially since the polyfill.io has been bought by a Chinese entity that has used it to insert malignant code into web sites that use it.
If your only usage is in a web browser, you can skip the next section.
New Directory Structure
MathJax’s new dual distribution of both MJS and CJS modules requires a new directory structure in the mathjax/MathJax-src repository and its associated @mathjax/src npm package in order to accommodate both versions. In the past, the compiled JavaScript code was found in thejs directory, and the webpacked components were in the es5directory. Now that there are both CommonJS and ES-module versions of the compiled code, these are stored in the cjs and mjsdirectories, respectively.
The webpacked components are now based on the new mjs files, hence they are ES6 files, and so the es5 directory has been removed, with the components now being placed in the new generically namedbundle directory. That way, if there is a move to ES7 or higher, the directory name doesn’t need to change again. The mathjax/MathJax repository and associatedmathjax package have also eliminated the es5 directory, and the combined components and component directories are now at the top level of the repository. That means you can access them without the need for /es5 in the URL that was needed in v3. (See theAccessing MathJax v4 section for more information on how to access v4.0 in a browser.)
Existing node applications that use MathJax (e.g., the v3 examples in the MathJax-demos-node repository) may have code that refers to the mathjax-full/js,mathjax-full/es5, or mathjax/es5 directories that no longer exist. To accommodate these, v4 includes an exports section in its package.json file that maps these references to the proper new locations. In particular, references to @mathjax/src/es5 are routed to @mathjax/src/bundle automatically, and similarly,mathjax/es5 is routed to mathjax. For the @mathjax/src/jsdirectory, references will be routed to @mathjax/src/mjs or@mathjax/src/cjs depending on whether the reference is from animport statement or a require() call. That means that ES modules (using import) will get the mjs versions, while CommonJS modules (using require()) will get the cjs ones.
The main package.json file now includes a "type": "module"line so that the .js files are considered to be MJS files automatically. The cjs directory (and other directories that need to be marked as CJS files) contain separate package.json files that set the type to commonjs so that the .js files they contain will be treated as CJS files.
Similarly, the font directories in the font packages have mjs andcjs directories, with exports sections in theirpackage.json files to route import to the former andrequire() to the latter.
Changes to components/src
The files that are used to create the webpacked component files are ES6 modules, since they use import and export, and in previous versions of MathJax, you needed to use node -r esm to be able torequire() these in your own programs as you can’t use require()to load ES modules directly. Although you could load the webpacked versions of the component files in either MJS or CJS applications, the fact that the source component files are MJS modules made it difficult to use the source versions of the components in CJS applications.
Now that MathJax provides both MJS and CJS versions, the source component files are made available in both forms as well. Originally, the component files were found in components/src; with this beta version, those are now in components/mjs, since they are ES modules.
Prior to version 4, MathJax used Babel to convert these to ES5 during the webpack process, but since the webpacked versions are now ES6, that is no longer necessary, and Babel is no longer needed as a dependency for MathJax. Instead, for those who wish to use the components from source in a CommonJS node application, we use Typescript to down-compile the components/mjs files into thecomponents/cjs directory as CJS modules of ES5 code.
In previous versions, require('mathjax-full') would loadcomponents/src/node-main/node-main.js, which would load the components from source rather than the webpacked versions. With themathjax package, which only includes the webpacked versions,require('mathjax') would get es5/node-main.js, the webpacked version. In version 4, the two have been standardized so that they both load the webpacked version. When used with require(), you will get bundle/node-main.cjs, while import will loadbundle/node-main.mjs. This is accomplished via the exportssection of the package.json file. Of course, you use@mathjax/src in place of mathjax-full.
In order to get the source versions in @mathjax/src, use
const MathJax = require('@mathjax/src/source');
or
import MathJax from '@mathjax/src/source';
and then call MathJax.init(...). These loadcomponents/mjs/node-main/node-main.mjs orcomponents/cjs/node-main/node-main.cjs, respectively.
For those who have been using components/src to load individual components from source, we map components/js to components/mjswhen included via an import command, and to components/cjswhen included via require(), so you can use components/js to get the correct files in either case. For backward compatibility,components/src has been mapped the same way.
The end result is that you should always get an appropriate version for your situation, whether you are importing MathJax into an MJS application or requiring it into a CJS one.
More MJS/CJS Issues
Since MathJax now needs to produce javascript files in two different formats, we use different typescript configuration files for the different setups. These are stored in the tsconfig directory. The MJS files are produced using tsconfig/mjs.json and the CJS one usetsconfig/cjs.json. Both of these call in tsconfig/common.json to set the parameters that are common to both, and then specify thetarget and module values to be correct for the desired JavaScript version and module format. The main tsconfig.json file simply calls in tsconfig/mjs.def and is there as a convenience for tools that expect a tsconfig.json file in the main directory.
In order to support both MJS and CJS versions, MathJax’s dependencies also must provide both versions. The speech-rule-engine,mj-context-menu, and mhchemparser packages all now include both module formats using dual directories similar to MathJax itself. This means that the imports used by MathJax for these packages need to change depending on which module version is being created. In order to accomplish this, the references to those packages are handled using pseudo-package references that are remapped to the correct locations via the tsconfig.json and package.json files.
To this end, MathJax now uses the #sre, #menu, and #mhchempseudo-package names to refer to these packages. The mainpackage.json file uses the imports section to map these to the actual package directories that contain their MJS JavaScript files. E.g., #mhchem/* is mapped to mhchemparser/esm/*, to obtain the ES module versions of the parser. Conversely, the package.jsonfile that is placed in the cjs directory maps #mhchem/* tomhchemparser/js/* to obtain the CommonJS versions. Similar mappings are done for the other two packages.
In addition to the mappings in the package.json file to let node (and webpack) know which directory to use, Typescript must also be told where to look for the .d.ts files for these packages. This is accomplished through the tsconfig json files via the pathsarray.
MathJax takes its default font from one of the MathJax font packages, and that also has separate MJS and CJS directories, so the MathJax code uses a pseudo-package, #default-font, to link to the propermjs or cjs directory in the font package. This also provides a means of specifying what the default font is (mathjax-newcm by default), as changing the mappings in the package.json files and the tsconfig files would change the default font.
For the most part, MathJax’s typescript source code can be used to produce either ES modules or CommonJS modules without alteration. But there are a few differences between MJS and CJS code that do need to be taken into account. For example, CommonJS code provides__dirname for the location of the file being compiled, but this is not available in MJS modules; meanwhile, MJS files must use new URL(import.meta.url).pathname to get that data, and import is not available in CJS modules. That means there is no common method that can be used for this in both cases, and so MathJax has some module-specific files to handle the few instances where module-specific code is needed.
To accommodate this, we introduce additional pseudo-package names that can be used to select between MJS and CJS files that export the needed data using the module-specific mechanisms. The #root and#mml3 pseudo-package names are used for these two situations in the Typescript code, and #js and #source are used in thecomponents/mjs definitions to link to the mjs or cjsJavaScript code and to module-specific code for obtaining the directory name. These are mapped to the proper locations in thepackage.json files and the tsconfig files. The module-specific code that these link to are stored in mjs andcjs directories where they are needed, so that #root/root.jsgets mapped to ts/components/mjs/root.js when MJS files are being produced, but to ts/components/cjs/root.js for CJS files, and similarly for #root/sre-root.js. The tsconfig files exclude the directories for the other format so that they are not compiled when not needed.
Finally, since modern browsers can import MJS files, it is possible to load the MathJax files into a browser directly via a <script type="module"> tag. To do so, however, you need to include a<script type="importmap"> tag that tells the browser how to find the pseudo-packages described above. Something like
should do the trick. Then you can set up your MathJax configuration in a file mathjax-config.js as in
import {source} from '@mathjax/src/components/js/source.js';
window.MathJax = { loader: { load: ['input/tex', 'output/chtml'], source: source } };
and then use
to load MathJax components via their source code rather than webpacked files.
This is useful for testing changes to MathJax, but should not be used in production, as the number of files that will be loaded can be quite large, and each file will need to be retrieved separately, making for unneeded network overhead.
Component JSON files
In past versions of MathJax, the components/src directory contained files that control the production of the webpacked component files in the es5 directory. Each component had a subdirectory that contained files that told the MathJax build tools how to construct the component. These included at least one .js file that imported the needed MathJax modules and did any setup needed for the component, along with one or more of build.json,copy.json, and webpack.config.js that contain the data needed for the build tools to process the component.
In version 4, these three .json files have been combined into a single config.json file that contains sections for each of the three original ones. The build property of config.jsoncontains the data that used to be in build.json, the copyproperty holds what was in copy.json, and the webpack property holds the data needed to pack the component.
The webpack data primarily consists of the data that used to be passed to the PACKAGE() function in webpack.config.js, but as named properties, and only those that differ from the defaults need to be included (unlike the calls to PACKAGE() where all arguments were needed). The defaults are set up so that most properties don’t need to be specified, just the name of the component and the libs array, in most cases. There are some additional properties that control whether the default font should be included in the packed file, or whether a function from an external file should be called to modify the default webpack configuration after it has been constructed. See the config.json files in the variouscomponents/mjs subdirectories for examples.
Building MJS and CJS versions
There are a large number of new package scripts used for building the files used by MathJax. The main changes are that there are separate commands for building the MJS and CJS files, along with new commands to make everything all in one step, and for making single components individually.
The pnpm -s compile and pnpm -s make-components scripts perform these steps for the MJS versions, and there is a new
command that does both of these at once. The command
not only compiles and packs the MJS versions, but also compiles the CJS versions.
Additional commands and details are given in the section onChanges to the Build Tools.
Reproducing the Old ES5 Webpack Files
The webpacked components in the bundle directory are based on the MJS files, which are ES6 JavaScript files. In contrast, the earlier versions of MathJax had CJS versions of ES5 code. If you need to support an ES5 environment, it is possible to build that using
npm run -s compile-cjs npm run -s make-cjs-components
which will create a bundle-cjs directory that contains ES5 versions of the webpacked components, comparable to the old es5 directory.
The tsconfig.json file has the settings for the MJS versions of the JavaScript files, and if you use an editor like emacs, the Typescript editing mode automatically compiles the .ts files based on tsconfig.json into the mjs directory. If you need to make modifications to the typescript files and your application links to the CJS versions of the compiled files, it may be convenient to switch the tsconfig.json file to produce the CJS versions instead. To do that, use
which will point the tsconfig.json file to the parameters for compiling into the cjs directory instead. When you are done,
will set things back to the original arrangement.