Skip to content

CommonJS source-component loading fails on Windows because source paths resolve to file: URLs #3585

Description

@HeRaNO

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:

node repro.cjs

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    AcceptedIssue has been reproduced by MathJax teamFixedv4v4.1

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions