build: upgrade Mermaid to 11.17.2 in the notebook renderer and the diagram preview, unpin cytoscape - #1189
Conversation
…ew's Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…cape Mermaid ~11.12.0 (resolves 11.12.3) matches Quarto CLI's 11.12. Its render API is async, so the fence rule now emits an empty placeholder with a unique id, and once the host has written the cell's HTML, each diagram is rendered and the placeholder filled with the same inert <img> data URL as before (or an error <pre>). Mermaid is loaded on the first diagram, and the theme is read at render time. The snapshot tests no longer need a Mermaid stub (removed in the previous commit). The lockfile drops mermaid 9's closure (script-deleted, then re-resolved by yarn install), which also releases the cytoscape 3.23.0 hold. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
11.12.x is affected by six Mermaid advisories that are fixed in 11.14.1/11.16.1. Lockfile re-resolved by script-deleting mermaid 11.12's closure and re-running yarn install. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A copy of dist/mermaid.min.js from the mermaid 11.17.2 npm package. The 11.12.0 file it replaces was byte-identical to that release's dist/mermaid.min.js, which is also what Quarto CLI vendors. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mermaid 11.17 builds no longer embed package.json, so the version is also read from the render() call that passes it to diagram renderers. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # apps/vscode/CHANGELOG.md # yarn.lock
juliasilge
left a comment
There was a problem hiding this comment.
Unfortunately, I do think something's not quite right with this one currently.
For an .ipynb in the legacy Jupyter notebook editor, every Mermaid diagram fails with the same error, for all diagram types:
Failed to render mermaid diagram. h.default.extend is not a function
I think that the cause is fastdom, which Mermaid 11.17 uses. fastdom has a UMD wrapper. If a global AMD define exists, the wrapper calls define() and does not set module.exports. Rolldown wraps fastdom as a CommonJS module, so h.default is {}. Then Mermaid core fails at load time on this line, before it parses any diagram:
var rt = h.default.extend({ raf(e) { ... } }).extend(ee.default);I reproduced this in Node with the built chunk (out/markdownit/chunk-GMAD6QVW-*.js):
- With no global
define, the chunk loads. - With
globalThis.define = function () {}, the chunk throwsh.default.extend is not a function.
I did not yet find what sets define in the notebook webview. I think that another notebook renderer in the same webview loads RequireJS, for example the Jupyter widget renderer maybe? The headless Chromium check in this PR did not catch this problem because that page had no define global.
One possible fix is to add define: 'undefined' to the define block in apps/vscode-markdownit/vite.config.ts, because then typeof define in bundled UMD code is always "undefined". What do you think about that? A regression test could load the chunks with a stub define global.
Also FWIW the notebook checks in this PR apply to Positron only in the legacy Jupyter notebook editor. The Positron Notebook editor is the default, and it uses its own Markdown and Mermaid rendering, not the Quarto renderer.
… define Another renderer in the notebook webview can load an AMD loader. fastdom's UMD wrapper then calls define() instead of setting module.exports, so Mermaid 11.17 fails to load with "h.default.extend is not a function". Define `define` as undefined in the renderer build, and test that no built chunk still checks for it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
nealrichardson
left a comment
There was a problem hiding this comment.
🤖 Thanks, you're right, and I reproduced it: with a stub globalThis.define (plus define.amd), the core chunk and architectureDiagram both throw h.default.extend is not a function. Fixed in 4488ce2, as you suggested:
apps/vscode-markdownit/vite.config.tsnow hasdefine: 'undefined', so no bundled UMD wrapper finds adefineto call. After a clean rebuild, none of the 95 chunks containstypeof define, and all of them load in Node with the stubdefineglobal.- Regression test: a new
Notebook renderer buildsuite inmermaidVersion.test.tsfails if any chunk inout/markdownitstill checkstypeof define. It checks the build output as text instead of importing the chunks with a stubdefine, because the extension tests are CommonJS and run in the extension host. It would have flagged 5 chunks before the fix.
I haven't tracked down what sets define in the webview either. The fix holds whichever renderer it is.
I also updated the description: in Positron, the manual checks apply only to the legacy Jupyter notebook editor.
|
🤖 Thanks, you're right, and I reproduced it: with a stub
I haven't tracked down what sets I also updated the description: in Positron, the manual checks apply only to the legacy Jupyter notebook editor. |

Upgrades the Mermaid in both of the extension's Mermaid previews to 11.17.2:
apps/vscode-markdownit, npmmermaid), from 9.4.3;apps/vscode/assets/www/diagram/mermaid.min.js, vendored), from 11.12.0.It also releases the
cytoscape3.23.0 hold from #1176.Why 11.17.2 and not Quarto CLI's 11.12:
venn-beta, andpersonused below). They deprecateflowchart.htmlLabelsandmermaidAPI.setConfig(), and nothing here uses either.quarto render(11.12) accepts also renders here. The reverse doesn't always hold, since newer syntax can preview here and then fail in the CLI.mermaidVersion.test.tsalready allows us to be ahead of the CLI; it fails only if we fall behind.classDiagramand C4 moved to Mermaid's unified (v2) renderer. Class and C4 diagrams will look somewhat different from the CLI's 11.12 output until the CLI catches up.I'll open a quarto-cli issue to bump its Mermaid once this merges.
Out of scope, and still deferred to the UX review: whether to keep our notebook Mermaid rendering next to VS Code 1.121's built-in notebook Mermaid, and
securityLevel: "loose"vs. inline SVG. The notebook output model is unchanged: an inert<img>with the SVG as a data URL.Changes
Packages.
mermaid^9.1.7→~11.17.2inapps/vscode-markdownit, the only workspace that imports it.yarn install. Entries shared with other workspaces kept their locks (d3-array/d3-dsvfor OJS,lodash-es,lodash, …). Noresolutions.mermaidcytoscapecytoscape-fcose/-cose-bilkentd3dompurifydagre-d3-es@mermaid-js/parserkatexuuidmarked16.4.2,roughjs4.6.6,@iconify/utils3.1.7,es-toolkit1.52.0,fastdom1.0.12 (+strictdom),@upsetjs/venn.js2.0.0,dayjs1.11.23elkjs,web-worker,non-layered-tidy-tree-layout,heapcytoscape,dompurify,katex,d3and@mermaid-js/parser. The rest:d3-sankey0.12.3 (a Mermaid dependency) still requires the d3 v1/v2 line, sod3-array2.12.1,d3-path1,d3-shape1 andinternmap1 sit next to the v3 copies, as they do upstream.marked16 (Mermaid) is separate frommarked18 (vsce, dev-only).uuid14 (Mermaid) is separate fromuuid8 (exceljs, OJS, dev-only).dayjs1.11.23 (Mermaid) is separate from 1.11.13 (exceljs, OJS, dev-only).Diagram preview.
assets/www/diagram/mermaid.min.jsis nowdist/mermaid.min.jsfrom themermaid@11.17.2npm tarball.mermaid@11.12.0'sdist/mermaid.min.js, which is what quarto-cli vendors.globalThis.mermaid, anddiagram.jsworks unchanged with its asyncparse/renderandinitialize({ startOnLoad: false }).Async rendering in the notebook renderer (
apps/vscode-markdownit/src/mermaid.ts). Mermaid 10 removedmermaidAPI.render's synchronous callback, but markdown-it renderer rules have to return a string synchronously. So now:<div class="quarto-mermaid" id="quarto-mermaid-N"></div>.Ncomes from a module-wide counter, so ids stay unique across renders. It wrapsmd.renderer.renderto collect the diagrams from that call.previewNode.innerHTMLsynchronously right aftermd.renderreturns. So a microtask then loads Mermaid (on the first diagram only, withimport("mermaid")), callsmermaid.initialize, and awaitsmermaid.renderfor each diagram. Afterwards it looks the placeholder up in the cell's shadow root, viaenv.outputItem.id, the same hook VS Code's built-in Mermaid renderer uses. If that fails, it searches the open shadow roots.<img>built with DOM APIs. Itsmax-widthis taken from the SVG's rootstyle, since the old code read it from Mermaid 9's temporary element. On an error, the placeholder instead gets a<pre>Failed to render mermaid diagram. …</pre>, set throughtextContent. The error only replaces that one diagram, so other diagrams and cells still render.suppressErrorRendering: truestops Mermaid from drawing its own error diagram.securityLevel: "loose"stays as inert as before inside the<img>. The placeholder class isquarto-mermaid, notmermaid, so VS Code's built-in renderer (it selects.mermaid) ignores it.darkordefault, from the samevscode-dark/vscode-high-contrastbody classes as before. It's now read on every render instead of once at activation, so a re-rendered cell picks up a theme switch. The callout colors inindex.tsare still read once.define: another renderer in the notebook webview can load an AMD loader (RequireJS). fastdom's UMD wrapper (new in Mermaid 11.17) then callsdefine()instead of settingmodule.exports, and Mermaid fails to load with "h.default.extend is not a function".vite.config.tsnow definesdefineasundefined, so no bundled UMD wrapper checks for it....optionsspread intomermaid.initialize, which passed our{ dark }through as a Mermaid config key. I also removed the stale// TODO: mermaid breaks other plugins. It dates from April 2023, when the plugin was briefly commented out, and was left in when it was re-enabled the next day. The rendering check below renders Mermaid next to callouts, divs and footnotes.Tests:
quarto-syntaxsnapshot changes only where the two Mermaid fences were. They used to be<img>s of the stub's fake SVG, and now they're the two placeholders.Notebook renderer buildtest fails if any built chunk inout/markdownitstill has atypeof definecheck.mermaidVersion.test.ts:node_modules/mermaid/dist/. So an exact match is cheap to keep, and it makes a diagram render identically in both. The version is read from the renderer's hashed chunks, and the test fails if stale chunks contain more than one version.pre-releasechannel this is still only a warning.name:"mermaid",version:"…". The reader now also recognizes the version literal thatrender()passes to each diagram's renderer (renderer.draw(text, id, "11.17.2", diagram)), in both minified and unminified builds. It still reads 11.12-style builds, such as the CLI's current one.Verification
On Node 24.21:
yarn install --frozen-lockfileleaves the lockfile unchanged.yarn build --force: 14/14.yarn test-packages: 13/13, including the renderer snapshot tests.yarn lint --continue: 17/17, 0 errors, the same 17 warnings.yarn test-vscode, headless (Xvfb in a linux-arm64 container, VS Code 1.139.1, Quarto 1.10.18 with Mermaid 11.12.0, a clean extract of this branch): main 169 passing and 3 pending (mainis at 168; the extra one is the new Mermaid version test), r-project 3 passing. All three Mermaid version tests pass, including "not behind the installed Quarto CLI" against the CLI's 11.12.0.The other bundles are unaffected.
grep -c mermaidAPIgives 0 forout/main.js,out/lsp/lsp.jsandassets/www/editor/index.js, and their sizes are byte-identical tomain(4,345,005 / 2,869,803 / 10,350,698 B).out/markdownit(measured after a clean build):main(Mermaid 9.4.3)index.js, 5 chunks,styles.css)index.js, 95 chunks,styles.css)index.jsflowchart-elk)Mermaid 11 doesn't bundle ELK (it's the separate
@mermaid-js/layout-elkpackage, which the IIFE build doesn't include either). The build is still an ES library with lazy chunks, which rolldown#11001 doesn't affect (that's a UMD issue), and the chunks load (below).Notebook renderer check in headless Chromium. I served the built
out/markdownitand mimickedvscode.markdown-it-renderer: a markdown-it instance with VS Code's options andhighlight, plus arenderOutputItemthat renders into a shadow root and passes{ outputItem: { id } }.ALLOWED_TAGS(simulating Restricted Mode):A@{ shape: text }/cylnode, anA@{ shape: person }node, and avenn-betadiagram;.column-margindiv and a footnote;{mermaid}attribute fence, and a cell re-rendered twice synchronously.<img>with a non-zero size, with the dark theme's colors in dark. The class and C4 diagrams render with the new renderer.define, so it missed the AMD failure above. After the fix, I loaded each of the 95 built chunks in Node with a stubdefine(anddefine.amd) global, and they all load. Before the fix, the core chunk andarchitectureDiagramthrew "h.default.extend is not a function".Diagram preview check in headless Chromium. I loaded the preview page's scripts (
lodash, the newmermaid.min.js, d3, graphviz,diagram.js) with a stubbedacquireVsCodeApi, and postedrendermessages.window.mermaid.parse/render/initializeare functions.personnode and a classDiagram rendered as SVG.Manual checks, please (Extension Development Host, and Positron's legacy Jupyter notebook editor; the default Positron Notebook editor does its own Markdown and Mermaid rendering and doesn't use this renderer). Open an
.ipynbwhose Markdown cells contain:A@{ shape: text },venn-beta), and check that they render and look reasonable next toquarto render's output.classDiagram(with members, methods and a few relationships) againstquarto renderon the CLI's 11.12. Differences are expected from the unified renderer; check that nothing is broken or unreadable, in both light and dark..qmd, "Preview Diagram" on a{mermaid}cell (including aclassDiagram) renders, and an invalid one shows the error banner.```mermaidfences should render once, with our renderer (the<img>), and not twice. Note any interaction with the built-in renderer (its::: mermaidcontainer syntax is separate). This is input for the deferred UX review.🤖 Generated with Claude Code