Issue Summary
CommonJS source-component loading fails on Windows with MathJax v4 because entries from @mathjax/src/components/js/source.js can resolve to file: URLs, but the documented CommonJS usage passes those entries to Node's require().
This looks related to the Windows path change made for ESM imports in mathjax/MathJax-src#1404 / #3481. That change is correct for import(), but it appears to leak ESM URL semantics into the CommonJS source-component path map.
Steps to Reproduce:
On Windows:
mkdir mathjax-cjs-file-url-repro
cd mathjax-cjs-file-url-repro
npm init -y
npm install @mathjax/src@4.1.2
Create repro.cjs:
const { source } = require('@mathjax/src/components/js/source.js');
console.log('platform:', process.platform);
console.log('source.core:', source.core);
console.log('source.tex-svg:', source['tex-svg']);
require(source.core);
Run:
On Windows, source.core resolves to something like:
file://C:/path/to/project/node_modules/@mathjax/src/components/cjs/core/core.js
Then CommonJS require(source.core) fails, for example:
Error: Cannot find module 'file://C:/path/to/project/node_modules/@mathjax/src/components/cjs/core/core.js'
I hit the same failure through a downstream package that uses MathJax's CommonJS loader:
Error: Cannot find module 'file://E:/Blog/node_modules/@mathjax/src/components/cjs/core/core.js'
Require stack:
- E:\Blog\node_modules\hexo-filter-mathjax\lib\filter.js
- E:\Blog\node_modules\hexo-filter-mathjax\index.js
at new PackageError (...\node_modules\@mathjax\src\cjs\components\package.js:60:28)
...
{
package: 'core'
}
Technical details:
- MathJax Version: 4.1.2
- Client OS: Windows 11
Supporting information:
The MathJax v4 documentation explicitly documents CommonJS source-component loading like this:
const {source} = require('@mathjax/src/components/js/source.js');
MathJax = {
loader: {
paths: {mathjax: '@mathjax/src/bundle'},
load: ['adaptors/liteDOM'],
require: require,
source: source
}
};
require(source['tex-chtml']);
The docs also say @mathjax/src/components/js/source.js selects the mjs or cjs directory depending on whether it is loaded with import or require().
However, the current source map is built from:
import {dirname} from '#source/source.cjs';
import {context} from '#js/util/context.js';
const src = context.path(dirname);
and context.path() converts Windows absolute paths to file://....
That conversion is correct for ESM import(), and it was introduced to fix Windows ESM loading in mathjax/MathJax-src#1404. But when the same path form is returned from the CommonJS source map, it becomes incompatible with Node's CommonJS require().
The issue is therefore not that MathJax should stop producing file: URLs everywhere. The issue is that CJS and ESM consumers need different path/specifier forms:
ESM import() -> file: URL is appropriate
CJS require() -> filesystem path / require-able specifier is appropriate
Possible fix direction
I think the cleanest fix is to make the generated CommonJS source map return CommonJS-compatible filesystem paths, while keeping file: URLs for the ESM source map.
Conceptually:
components/mjs/source.js -> source entries suitable for import()
components/cjs/source.js -> source entries suitable for require()
A wrapper around loader.require can work around dynamic loads, e.g.:
const { fileURLToPath } = require('node:url');
function requireForMathJax(specifier) {
return require(
typeof specifier === 'string' && specifier.startsWith('file:')
? fileURLToPath(specifier)
: specifier
);
}
but that does not fully solve the documented source-component example, because the docs call require(source['tex-chtml']) directly before MathJax's loader has a chance to use loader.require.
So the more robust fix is probably at the source-map generation boundary, or the documentation should be updated to show a Windows-safe CommonJS wrapper around both the initial require(source[...]) and loader.require.
References
Issue Summary
CommonJS source-component loading fails on Windows with MathJax v4 because entries from
@mathjax/src/components/js/source.jscan resolve tofile:URLs, but the documented CommonJS usage passes those entries to Node'srequire().This looks related to the Windows path change made for ESM imports in mathjax/MathJax-src#1404 / #3481. That change is correct for
import(), but it appears to leak ESM URL semantics into the CommonJS source-component path map.Steps to Reproduce:
On Windows:
mkdir mathjax-cjs-file-url-repro cd mathjax-cjs-file-url-repro npm init -y npm install @mathjax/src@4.1.2Create
repro.cjs:Run:
On Windows,
source.coreresolves to something like:Then CommonJS
require(source.core)fails, for example:I hit the same failure through a downstream package that uses MathJax's CommonJS loader:
Technical details:
Supporting information:
The MathJax v4 documentation explicitly documents CommonJS source-component loading like this:
The docs also say
@mathjax/src/components/js/source.jsselects themjsorcjsdirectory depending on whether it is loaded withimportorrequire().However, the current source map is built from:
and
context.path()converts Windows absolute paths tofile://....That conversion is correct for ESM
import(), and it was introduced to fix Windows ESM loading in mathjax/MathJax-src#1404. But when the same path form is returned from the CommonJSsourcemap, it becomes incompatible with Node's CommonJSrequire().The issue is therefore not that MathJax should stop producing
file:URLs everywhere. The issue is that CJS and ESM consumers need different path/specifier forms:Possible fix direction
I think the cleanest fix is to make the generated CommonJS source map return CommonJS-compatible filesystem paths, while keeping
file:URLs for the ESM source map.Conceptually:
A wrapper around
loader.requirecan work around dynamic loads, e.g.:but that does not fully solve the documented source-component example, because the docs call
require(source['tex-chtml'])directly before MathJax's loader has a chance to useloader.require.So the more robust fix is probably at the source-map generation boundary, or the documentation should be updated to show a Windows-safe CommonJS wrapper around both the initial
require(source[...])andloader.require.References
MathJax documentation: Loading MathJax components from source
https://docs.mathjax.org/en/v4.0/server/components.html#loading-mathjax-components-from-source
MathJax documentation: CommonJS source-component example with
loader.require: requireandrequire(source['tex-chtml'])https://docs.mathjax.org/en/v4.0/server/components.html#loading-mathjax-components-from-source
@mathjax/src@4.1.2package exports map:./components/js/*selects./components/mjs/*forimportand./components/cjs/*forrequirehttps://github.com/mathjax/MathJax-src/blob/4.1.2/package.json
components/mjs/source.jsbuilds source paths usingcontext.path(dirname)https://github.com/mathjax/MathJax-src/blob/4.1.2/components/mjs/source.js
components/mjs/source.cjsexports__dirnamehttps://github.com/mathjax/MathJax-src/blob/4.1.2/components/mjs/source.cjs
context.path()converts Windows absolute paths tofile://...https://github.com/mathjax/MathJax-src/blob/4.1.2/ts/util/context.ts
Package.loadCustom()passes the resolved path directly toCONFIG.require(url)https://github.com/mathjax/MathJax-src/blob/4.1.2/ts/components/package.ts
Prior Windows ESM issue: In the Windows node environment, the module cannot be loaded #3481
In the Windows node environment, the module cannot be loaded #3481
PR that changed Windows paths to include
file://: Update windows paths to include file:// and fix tests. (mathjax/MathJax#3481). MathJax-src#1404Update windows paths to include file:// and fix tests. (mathjax/MathJax#3481). MathJax-src#1404
Node.js ESM docs: ES modules are resolved as URLs and
file:URLs are supportedhttps://nodejs.org/api/esm.html#urls
Node.js URL docs:
fileURLToPath('file:///C:/path/')converts to a Windows filesystem pathhttps://nodejs.org/api/url.html#urlfileurltopathurl-options
Node.js CommonJS docs:
require(id)takes a module name or pathhttps://nodejs.org/api/modules.html#requireid