From c413d22fd7ae52fc2240677e24d7b5f21d1578e3 Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:37:55 +0800 Subject: [PATCH] fix(preview): mhchem registers on the KaTeX the preview renders with (#745) KaTeX publishes every contrib file twice: .mjs behind the import condition, .js (CommonJS, require("katex")) behind require. The loader asked for katex/dist/contrib/mhchem.js, so the bundler gave mhchem the CommonJS copy of KaTeX and \ce landed on a parser the preview never calls; $$\ce{...}$$ rendered as a parse error. The bare katex/contrib/* subpaths resolve to the .mjs, which imports the same katex.mjs the preview does. The built app now carries one KaTeX 0.18.4 instead of two. --- scripts/katexContribFlavour.test.ts | 19 +++++++++++++++++++ src/globals.d.ts | 4 ++-- src/lib/utils/richContent.ts | 14 ++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 scripts/katexContribFlavour.test.ts 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 {