Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8206e86
Use top-level static MathJax import in LaTeX webview
cursoragent Jun 6, 2026
c6b2d22
Keep latex setup handler async for readability
cursoragent Jun 6, 2026
bb2a807
Disable MathJax a11y speech worker in VS Code webview
cursoragent Jun 6, 2026
3e34703
Copy MathJax sre assets into webview dist via Vite plugin
cursoragent Jun 6, 2026
238302f
Add @prosemark/latex vite-plugin-mathjax and self-host MathJax
cursoragent Jun 6, 2026
4e13e20
Keep latex setup handler async
cursoragent Jun 6, 2026
0a54f8a
Fix MathJax URL resolution when multiple webview scripts load
cursoragent Jun 6, 2026
b609e10
Pass MathJax package URL from extension host via asWebviewUri
cursoragent Jun 6, 2026
8b26bc6
Remove mathJaxPackageUrlFromWebviewScript from @prosemark/latex
cursoragent Jun 6, 2026
36a184a
Pass SubExtensionCallback context as a single object
cursoragent Jun 6, 2026
43e9216
Document MathJax a11y/speech worker options for static-import
cursoragent Jun 6, 2026
cf1e3a6
Fix strict lint issues in latex package and extension
cursoragent Jun 6, 2026
587e185
Fix MathJax 4 config: use startup.typeset instead of skipStartupTypeset
cursoragent Jun 6, 2026
3893b8e
Revert vite.config.mts in latex-integration eslint files list
cursoragent Jun 6, 2026
8eeaec2
Docs: drop unsupported claim that editor preview typically skips a11y
cursoragent Jun 6, 2026
d2a90dc
Changeset: use patch bumps while packages are 0.x
cursoragent Jun 6, 2026
ae2b946
Changeset: drop startup.typeset note (introduced in this PR)
cursoragent Jun 6, 2026
9e2b1ce
Changeset: document pre-0.0.2 VS Code math rendering regression
cursoragent Jun 6, 2026
dbd0dc9
Changeset: split per package and fix VS Code regression wording
cursoragent Jun 6, 2026
6268c1f
Changeset: describe VS Code math bug as blocked CDN access
cursoragent Jun 6, 2026
8d9b2d2
Changeset: emphasize webview in integrator callback context
cursoragent Jun 6, 2026
8e545e6
Fix LaTeX math rendering in VS Code webview
jsimonrichard Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/integrator-sub-extension-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@prosemark/vscode-extension-integrator': patch
---

Pass sub-extension callback context as a single object including `webview`, so companion extensions can resolve webview resource URLs.
5 changes: 5 additions & 0 deletions .changeset/latex-vite-plugin-mathjax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@prosemark/latex': patch
---

Add `@prosemark/latex/vite-plugin-mathjax` to copy configurable paths from the `mathjax` npm package into a Vite build output.
5 changes: 5 additions & 0 deletions .changeset/vscode-latex-mathjax-url-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
vscode-prosemark-latex-integration: patch
---

Fix LaTeX math rendering issue in the VS Code extension webview by self-hosting MathJax inside the extension instead of using a CDN.
23 changes: 22 additions & 1 deletion apps/docs/src/content/docs/guides/latex-math.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ If you assemble markdown extensions yourself (without `prosemarkMarkdownSyntaxEx

## How MathJax is loaded

In **`url-import`** mode (the default), this package sets `window.MathJax` with `skipStartupTypeset: true` and loader paths, then dynamically imports the startup bundle. Do not set conflicting `tex` / `svg` / `chtml` options on `window.MathJax` yourself before that runs.
In **`url-import`** mode (the default), this package sets `window.MathJax` with `startup.typeset: false` and loader paths, then dynamically imports the startup bundle. Do not set conflicting `tex` / `svg` / `chtml` options on `window.MathJax` yourself before that runs.

### Runtime URL — `mathJaxLoadMode: 'url-import'` (default)

Expand Down Expand Up @@ -113,6 +113,27 @@ import 'mathjax/tex-svg.js';
}),
```

**Accessibility / speech worker:** the combined startup bundle includes MathJax a11y support and loads **`sre/speech-worker.js` as a web worker** at runtime. Your bundler typically only ships the main JS file, so the worker is missing unless you also copy `sre/` and configure MathJax’s loader paths, or use **`url-import`** with a self-hosted `mathjax/` folder (see [`@prosemark/latex/vite-plugin-mathjax`](https://www.npmjs.com/package/@prosemark/latex) on npm).

If you do not need speech or enrichment and want to avoid shipping the `sre/` worker, configure `window.MathJax` **before** importing MathJax:

```javascript
window.MathJax = {
startup: {
typeset: false,
},
options: {
enableSpeech: false,
enableBraille: false,
enableEnrichment: false,
menuOptions: {
settings: { enrich: false, speech: false, braille: false },
},
},
};
import 'mathjax/tex-svg.js';
```

Use one load mode per page (`url-import` or `static-import`, not both).

## Block vs inline
Expand Down
2 changes: 1 addition & 1 deletion apps/vscode-extensions/core/src/sub-extensions/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export class Core implements SubExtension<
export function createCore(
extensionUri: vscode.Uri,
): SubExtensionCallback<typeof extId, unknown, unknown> {
return (document, callProcAndForget, _callProcWithReturnValue) => {
return ({ document, callProcAndForget }) => {
return new Core(extensionUri, document, callProcAndForget);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const createWordCountStatusBarItem: SubExtensionCallback<
typeof extId,
Record<string, never>,
WordCountVSCodeProcs
> = (document) => {
> = ({ document }) => {
return new WordCountStatusBarItem(document);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function createCSpellIntegration(
extensionUri: vscode.Uri,
cSpellApi: CSpell.ExtensionApi,
): SubExtensionCallback<typeof extId, WebviewProcMap, VSCodeExtensionProcMap> {
return (document, callProcAndForget, callProcWithReturnValue) => {
return ({ document, callProcAndForget, callProcWithReturnValue }) => {
return new CSpellIntegration(
extensionUri,
cSpellApi,
Expand Down
33 changes: 32 additions & 1 deletion apps/vscode-extensions/latex-integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,35 @@ Please report bugs on the [GitHub issues page](https://github.com/jsimonrichard/

## Developing this extension

After `bun install`, run `bun run build`. **Vite** produces `dist/webview/webview.js` with MathJax SVG output inlined into that bundle (see `src/webview/main.ts`). Bump the **`mathjax`** dependency when you want a different MathJax version.
After `bun install`, run `bun run build`. **Vite** produces `dist/webview/webview.js` and copies `tex-svg.js` plus `sre/` from the **`mathjax`** npm package into `dist/webview/mathjax/` via `@prosemark/latex/vite-plugin-mathjax`. The webview loads MathJax with `url-import` from that folder. Bump the **`mathjax`** dependency when you want a different MathJax version.

### MathJax load modes

This extension uses **`url-import`**: the extension host passes a webview URL for `dist/webview/mathjax/`, and the webview dynamically imports `tex-svg.js` from there. That keeps `webview.js` small and lets MathJax load `sre/speech-worker.js` as a separate file (copied by the Vite plugin).

If you switch to **`static-import`** (`import 'mathjax/tex-svg.js'` bundled into `webview.js`), note that the combined `tex-svg` startup includes MathJax **a11y** support. It tries to load **`sre/speech-worker.js` as a web worker** at runtime. A bundler usually only emits your main bundle, so the worker is missing unless you handle it explicitly.

Pick one approach:

1. **Disable a11y** (simplest with `static-import` when you are not copying `sre/`) — set options on `window.MathJax` **before** the static import:

```ts
window.MathJax = {
startup: {
typeset: false,
},
options: {
enableSpeech: false,
enableBraille: false,
enableEnrichment: false,
menuOptions: {
settings: { enrich: false, speech: false, braille: false },
},
},
};
import 'mathjax/tex-svg.js';
```

2. **Keep a11y** — copy `sre/` from the `mathjax` npm package next to where `tex-svg.js` is served, and point MathJax at that root (for example with `preconfigureMathJaxLoader` and `loader.paths.mathjax`, or `@prosemark/latex/vite-plugin-mathjax` with `url-import`). The worker cannot be inlined into an IIFE the way the main startup script can.

The shipped extension uses **url-import + copied `sre/`** so math renders without bundling ~1.8 MB of MathJax into `webview.js`.
6 changes: 5 additions & 1 deletion apps/vscode-extensions/latex-integration/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/** VS Code extension host: no procedures exposed to the webview for LaTeX. */
export type VSCodeExtensionProcMap = Record<string, never>;

export interface LatexSetupConfig {
mathJaxPackageUrl: string;
}

export interface WebviewProcMap {
setup: () => Promise<void>;
setup: (config: LatexSetupConfig) => Promise<void>;
}
2 changes: 0 additions & 2 deletions apps/vscode-extensions/latex-integration/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
declare module '*.css';

declare module 'mathjax/tex-svg.js';
25 changes: 23 additions & 2 deletions apps/vscode-extensions/latex-integration/src/sub-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ export class LatexIntegration implements SubExtension<
VSCodeExtensionProcMap
> {
#extensionUri: vscode.Uri;
#webview: vscode.Webview;
#callProcWithReturnValue: CallProcWithReturnValue<WebviewProcMap>;

constructor(
extensionUri: vscode.Uri,
webview: vscode.Webview,
_document: vscode.TextDocument,
_callProcAndForget: CallProc<WebviewProcMap>,
callProcWithReturnValue: CallProcWithReturnValue<WebviewProcMap>,
) {
this.#extensionUri = extensionUri;
this.#webview = webview;
this.#callProcWithReturnValue = callProcWithReturnValue;
}

Expand Down Expand Up @@ -52,8 +55,20 @@ export class LatexIntegration implements SubExtension<
return [this.#extensionUri];
}

#getMathJaxPackageUrl(): string {
const mathJaxUri = vscode.Uri.joinPath(
this.#extensionUri,
'dist',
'webview',
'mathjax',
);
return this.#webview.asWebviewUri(mathJaxUri).toString().replace(/\/$/, '');
}

onReady(): void {
void this.#callProcWithReturnValue('setup').catch((e: unknown) => {
void this.#callProcWithReturnValue('setup', {
mathJaxPackageUrl: this.#getMathJaxPackageUrl(),
}).catch((e: unknown) => {
console.error(e);
});
}
Expand All @@ -64,9 +79,15 @@ export class LatexIntegration implements SubExtension<
export function createLatexIntegration(
extensionUri: vscode.Uri,
): SubExtensionCallback<typeof extId, WebviewProcMap, VSCodeExtensionProcMap> {
return (document, callProcAndForget, callProcWithReturnValue) => {
return ({
document,
callProcAndForget,
callProcWithReturnValue,
webview,
}) => {
return new LatexIntegration(
extensionUri,
webview,
document,
callProcAndForget,
callProcWithReturnValue,
Expand Down
48 changes: 33 additions & 15 deletions apps/vscode-extensions/latex-integration/src/webview/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,34 @@ import type {
CallbackFromProcMap,
WebviewVSCodeApiWithPostMessage,
} from '@prosemark/vscode-extension-integrator/types';
import {
latexMarkdownEditorExtensions,
latexMarkdownSyntaxTheme,
} from '@prosemark/latex';

import './style.css';

type LatexWebviewVscodeApi = WebviewVSCodeApiWithPostMessage<
CallbackFromProcMap<'latex-integration', WebviewProcMap>
>;

const isLatexWebviewVscodeApi = (
value: unknown,
): value is LatexWebviewVscodeApi => {
if (typeof value !== 'object' || value === null) {
return false;
}
if (!('postMessage' in value)) {
return false;
}
return typeof value.postMessage === 'function';
};

let latexSetupDone = false;

const procs: WebviewProcMap = {
setup: async () => {
// eslint-disable-next-line @typescript-eslint/require-await -- async setup matches other integrations
setup: async ({ mathJaxPackageUrl }) => {
const view = window.proseMark?.view;
if (!view) {
console.warn('[ProseMark] latex-integration setup: no view');
Expand All @@ -22,30 +43,27 @@ const procs: WebviewProcMap = {
if (latexSetupDone) {
return;
}
latexSetupDone = true;

try {
// Bundle MathJax tex-svg into webview.js (Vite resolves `mathjax/...` from npm).
await import('mathjax/tex-svg.js');
const latex = await import('@prosemark/latex');
appendToExtraCodeMirrorExtensions(view, [
...latex.latexMarkdownSyntaxTheme,
...latex.latexMarkdownEditorExtensions({
mathJaxLoadMode: 'static-import',
...latexMarkdownSyntaxTheme,
...latexMarkdownEditorExtensions({
mathJaxLoadMode: 'url-import',
mathJaxPackageUrl,
output: 'svg',
}),
]);
latexSetupDone = true;
} catch (err: unknown) {
console.error('[ProseMark] latex-integration setup failed', err);
throw err;
}
},
};

registerWebviewMessageHandler(
'latex-integration',
procs,
window.proseMark?.vscode as WebviewVSCodeApiWithPostMessage<
CallbackFromProcMap<'latex-integration', WebviewProcMap>
>,
);
const vscodeApi = window.proseMark?.vscode;
if (isLatexWebviewVscodeApi(vscodeApi)) {
registerWebviewMessageHandler('latex-integration', procs, vscodeApi);
} else {
console.error('[ProseMark] latex-integration: vscode API is not available');
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
import proseMarkVSCodeExtensionIntegratorPlugin from '@prosemark/vscode-extension-integrator/rolldown-plugin';
import {
copyMathJaxAssets,
MATHJAX_TEX_SVG_COPY_ENTRIES,
} from '@prosemark/latex/vite-plugin-mathjax';

const webviewOutDir = resolve(__dirname, 'dist/webview');

export default defineConfig({
plugins: [proseMarkVSCodeExtensionIntegratorPlugin()],
plugins: [
proseMarkVSCodeExtensionIntegratorPlugin(),
copyMathJaxAssets({
outDir: webviewOutDir,
copy: MATHJAX_TEX_SVG_COPY_ENTRIES,
}),
],
build: {
outDir: 'dist/webview',
target: 'es2022',
Expand Down
Loading
Loading