diff --git a/scripts/katexContribFlavour.test.ts b/scripts/katexContribFlavour.test.ts new file mode 100644 index 00000000..f7d26b89 --- /dev/null +++ b/scripts/katexContribFlavour.test.ts @@ -0,0 +1,19 @@ +/** + * KaTeX publishes every contrib file twice: `contrib/*.mjs` behind the + * `import` condition, `contrib/*.js` (CommonJS, `require("katex")`) behind + * `require`. Only the `.mjs` shares the preview's KaTeX instance; the `.js` + * gets the bundler's CommonJS copy, and a macro mhchem registers there never + * reaches `renderToString` (#745). vitest and node both dedupe the two, so the + * only runtime that shows the split is a production build — which is why this + * is asserted on the source: the loader may not name the CommonJS flavour. + */ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { readSource } from './sourceTree.ts'; + +test('the rich-content loader imports KaTeX contribs through the exports map', () => { + const source = readSource('src/lib/utils/richContent.ts'); + assert.doesNotMatch(source, /import\('katex\/dist\/contrib\//); + assert.match(source, /import\('katex\/contrib\/mhchem'\)/); +}); diff --git a/src/globals.d.ts b/src/globals.d.ts index df803f22..acb270a4 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -1,3 +1,3 @@ -declare module 'katex/dist/contrib/auto-render.js'; -declare module 'katex/dist/contrib/mhchem.js'; +declare module 'katex/contrib/auto-render'; +declare module 'katex/contrib/mhchem'; declare module 'highlightjs-svelte'; diff --git a/src/lib/utils/richContent.ts b/src/lib/utils/richContent.ts index af7e233c..2bf1029f 100644 --- a/src/lib/utils/richContent.ts +++ b/src/lib/utils/richContent.ts @@ -90,8 +90,6 @@ export function loadRichContentLibraries(): Promise { } const katex = (katexMainModule as any).default; - // mhchem is a KaTeX extension that binds to the global. - (window as any).katex = katex; // `copy-tex` used to be loaded here too. It installs its own document // `copy` listener, which fires only when the selection contains math and @@ -100,9 +98,17 @@ export function loadRichContentLibraries(): Promise { // Its two worthwhile behaviours, whole-formula expansion and TeX as the // plain text, are implemented in `utils/previewCopy.ts` instead, where // they apply to one copy path rather than a second one. + // The bare `katex/contrib/…` subpaths, not `katex/dist/contrib/….js`: + // KaTeX publishes every file twice, `.mjs` behind the `import` condition + // and `.js` (CommonJS, `require("katex")`) behind `require`. Ask for the + // `.js` and the bundler hands it the CommonJS copy of KaTeX — a second + // parser with its own macro table — so mhchem registered `\ce` on a + // KaTeX the preview never calls, and `$$\ce{…}$$` came out as a parse + // error (#745). The subpath resolves to the `.mjs`, which imports + // `../katex.mjs`: the same module `import('katex')` above returned. const [autoRenderModule] = await Promise.all([ - import('katex/dist/contrib/auto-render.js'), - import('katex/dist/contrib/mhchem.js'), + import('katex/contrib/auto-render'), + import('katex/contrib/mhchem'), ]); return {