vitepress icon indicating copy to clipboard operation
vitepress copied to clipboard

equation numbering in math with markdown-it-mathjax3 triggers duplicate label errors

Open jkieboom opened this issue 6 months ago • 2 comments

Describe the bug

I'm using vitepress with math through markdown-it-mathjax3 as shown in the documentation. When I write equations with numbering and labels I get errors in dev mode about duplicate labels being defined. If I turn off duplicate label errors in the config then whenever there is an update in dev mode the equation numbering increases.

Reproduction

  1. Enable math and install markdown-it-mathjax3
  2. Create a simple page with an equation using \begin{align} ... \label{eq} \end{align}
  3. Run vitepress in dev mode

Expected behavior

Equations should be shown and numbered starting at 1 even when updating the markdown page and using hmr.

System Info

System:
    OS: macOS 15.4.1
    CPU: (12) arm64 Apple M2 Pro
    Memory: 168.23 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.14.0 - ~/.nvm/versions/node/v22.14.0/bin/node
    Yarn: 4.5.1 - ~/.nvm/versions/node/v22.14.0/bin/yarn
    npm: 10.9.2 - ~/.nvm/versions/node/v22.14.0/bin/npm
  Browsers:
    Chrome: 136.0.7103.93
    Chrome Canary: 138.0.7168.0
    Edge: 136.0.3240.50
    Safari: 18.4
    Safari Technology Preview: 18.4

Additional context

The problem is caused by mathjax needing a state reset (through texReset) whenever content is re-rendered. Otherwise the state contains previously defined labels and equation numbering. I managed to work around this locally by forking markdown-it-mathjax3 and monkey patching md.render so reset the InputJax state:

  // Patch of original plugin. Monkey patch md.render and reset InputJax to fix equation label
  // counts and refs
  const originalRender = md.render;

  md.render = function (src: string, env?: any) {
    documentOptions.InputJax.reset();
    return originalRender.call(this, src, env);
  };

This isn't a proper solution of course, and I'm not sure there is something vitepress can do by itself, or if this would need to be fixed in the plugin and/or markdown-it.

Relevant markdown-it-mathjax3 issue: https://github.com/tani/markdown-it-mathjax3/issues/55#issuecomment-1100608009

Validations

jkieboom avatar May 08 '25 21:05 jkieboom

Hmm, yeah not sure what we can do here since it doesn't expose the mathjax instance. Maybe you can use some different plugin for now:

In .vitepress/config.ts

import { createMathjaxInstance, mathjax } from '@mdit/plugin-mathjax'
import { defineConfig } from 'vitepress'

const mathjaxInstance = createMathjaxInstance({
  transformer: (content) => content.replace(/^<mjx-container/, '<mjx-container v-pre'),
  tex: {
    tags: 'ams'
  }
})!

const virtualModuleId = 'virtual:mathjax-styles.css'
const resolvedVirtualModuleId = '\0' + virtualModuleId

export default defineConfig({
  markdown: {
    config(md) {
      md.use(mathjax, mathjaxInstance)
      const orig = md.renderAsync  // use md.render if you're on vitepress v1
      md.renderAsync = function (...args) {
        mathjaxInstance.reset()
        return orig.apply(this, args)
      }
    }
  },
  vite: {
    plugins: [
      {
        name: 'mathjax-styles',
        resolveId(id) {
          if (id === virtualModuleId) {
            return resolvedVirtualModuleId
          }
        },
        load(id) {
          if (id === resolvedVirtualModuleId) {
            return mathjaxInstance.outputStyle()
          }
        }
      }
    ]
  }
})

In .vitepress/theme/index.ts

import DefaultTheme from 'vitepress/theme'
import 'virtual:mathjax-styles.css'

export default DefaultTheme

HMR for mathjax css is not implemented. I don't think it's necessary but it is straightforward to add:

// ...
import type { ViteDevServer } from 'vite'

let server: ViteDevServer | undefined

// ...

      md.renderAsync = async function (...args) {
        mathjaxInstance.reset()
        const res = await orig.apply(this, args)
        if (server) {
          const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId)
          if (mod) {
            server.moduleGraph.invalidateModule(mod)
            server.reloadModule(mod)
          }
        }
        return res
      }

// ...

  vite: {
    plugins: [
      {
        name: 'mathjax-styles',
        configureServer(_server) {
          server = _server
        },

// ...

brc-dd avatar May 09 '25 04:05 brc-dd

@brc-dd I tried your suggestion, but for me this leads to a FATAL ERROR: Reached heap limit Allocation failed after about 7 hmr reloads :/ With the markdown-it-mathjax3 this only happens after around 22 hmr.

I could not figure out what is eating memory yet, but I suspect it has to do with mathjax

manuelmeister avatar Jun 04 '25 07:06 manuelmeister