Repository navigation
Conversation
Render Mermaid diagrams with Pele (https://pele.run), a dependency-free Mermaid renderer that computes its own layout without a DOM, in the editor, browser previews/exports and server/CLI exports. - Add a shared, DOM-free MermaidPreRenderer used by the exporters bundle and the server. It reports every diagram it could not render instead of claiming success, keeps the source in data-mermaid, preserves the max-width/max-height style and matches the exact `mermaid` class token. - The CLI fails, without writing the package, when a diagram cannot be rendered, instead of shipping it as source code (#2544). - Pre-render Mermaid in the HTTP export routes, API v1 and the public view, which only pre-rendered LaTeX. - The editor runtime ($exe.mermaid) loads Pele's browser build, vendored from the pinned package by scripts/vendor-pele.ts, and shows parse errors. - Approximate Mermaid's default theme with Pele CSS variables in base.css, shared by the workarea and every export format. - Remove the mermaid and jsdom dependencies, the vendored mermaid.min.js, the jsdom-based ServerMermaidPreRenderer, the /libs/mermaid routes and the per-exporter Mermaid CSS append. Fixes #2544 Fixes #2490
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2556 +/- ##
==========================================
+ Coverage 92.25% 92.36% +0.11%
==========================================
Files 479 478 -1
Lines 89517 89024 -493
==========================================
- Hits 82584 82228 -356
+ Misses 6933 6796 -137
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Deploying exelearning with
|
| Latest commit: |
c7fb139
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1d1bf659.exelearning.pages.dev |
| Branch Preview URL: | https://feat-replace-mermaid-with-pe.exelearning.pages.dev |
Bundle ReportChanges will increase total bundle size by 1.12kB (0.07%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: app-bundleAssets Changed:
view changes for bundle: exporters-bundleAssets Changed:
|
Keep main's vendor pipeline and jsdom (sanitizeHtml.test.js and --external jsdom) and replace mermaid with pele 1.6.0 in the live copy step, gitignore, e2e artifact, and Dependabot group. bun.lock was regenerated with bun install.
Collaborator
Author
…with-pele # Conflicts: # bun.lock # package.json
Drop public/app/common/MermaidPreRenderer.test.js: #2538 trimmed its timeouts on main, but this branch removes the module it tested. The replacement pre-renderer has its own spec under src/shared/export.
# Conflicts: # bun.lock # scripts/static-bundle/minify-dist.ts # scripts/static-bundle/static-index.html # src/shared/export/prerender/ServerMermaidPreRenderer.spec.ts # src/shared/export/prerender/ServerMermaidPreRenderer.ts # views/workarea/workarea.njk
Main added server-mermaid-rendering.spec.ts against ServerMermaidPreRenderer, which this branch removes. Render through MermaidPreRenderer with Pele's render, as CLI and server exports do, and match Pele's node markup (g.pele-node, its rect). Every case and geometry assertion is kept.
# Conflicts: # bun.lock # package.json
…with-pele # Conflicts: # bun.lock
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
eXeLearning now renders Mermaid diagrams with Pele instead of Mermaid.js, everywhere: the editor (saved iDevice views), browser previews and print previews, browser exports (HTML5, single page, SCORM 1.2/2004, IMS, EPUB3, ELPX) and server-side exports (CLI, HTTP export routes, API v1, public view).
Existing content keeps working unchanged: diagrams are still authored as
<pre class="mermaid">…</pre>, the TinyMCEexemermaidplugin is the same, and pre-rendered diagrams keep theexe-mermaid-renderedwrapper with the source indata-mermaid. Only the rendering library changes.Why Pele?
Pele has its own Mermaid parsers and layout engine, and it generates SVG without browser DOM measurement APIs, so the same code runs in a browser and in Bun. It was built for Obsidian and supports the diagram types built into Mermaid 12.1.0 (compatibility). It has no runtime dependencies.
That removes the reason server exports were broken. Mermaid.js needs a real browser to measure text and SVG boxes, and our jsdom shim could not provide that (#2544). Making it work would mean maintaining the kind of measurement layer that mermaid-js/mermaid#8210 is still building upstream.
Upstream benchmark figures (published by Pele, not measured here)
Benefits measured in eXeLearning
See Performance and dependency comparison for methodology.
main, every server-side export leaves diagrams as source text and still reports success (CLI export cannot pre-render Mermaid diagrams (CSSStyleSheet is not defined) and leaves them as plain text #2544). Here they are SVG.mermaid.min.js2,748,992 B (787,899 B gzip) →pele.global.js682,005 B (251,920 B gzip). It is still loaded only on demand, and never in exports.mermaid(113-package tree) andjsdom(39-package tree) are gone, andpeleadds one package with no dependencies. Productionnode_modules: 474,160 KB → 292,444 KB (−38%).getBBox/text-measurement polyfills, hidden-element retry loops, two separate renderers (browserMermaidPreRenderer.jsand serverServerMermaidPreRenderer.ts) and the/libs/mermaidroutes are gone. One DOM-freeMermaidPreRenderer.tsserves the browser and the server. Net diff: +1,449 / −5,816 lines (−2.7 MB of vendored JavaScript).pele@1.6.0.Implementation details
Shared renderer:
src/shared/export/prerender/MermaidPreRenderer.tsrenderfunction: the server importspele, and the browser hook useswindow.pele. That keeps Pele (~660 KB) out ofexporters.bundle.js, which loads at workarea start-up; it stays at ~314 KB.<pre>elements whose class list contains the exactmermaidtoken. The old regex also matchedlanguage-mermaid(code highlighted as Mermaid) and theexe-mermaid-*classes.<br>line breaks (TinyMCE stores Enter inside a<pre>as<br>). It strips an SVG left inside the<pre>by the old Mermaid.js runtime and renders the remaining source.max-width/max-heightstyle set by the TinyMCE dialog on the wrapper. Before, the style was dropped in exports.data-idevice-json-data), as before.failures: [{ code, message, source }], wherecodeis Pele'ssyntax/unsupported-diagram/semantic/limit, orerror. A failed diagram keeps its<pre class="mermaid">source, so it can still be recovered and edited.mermaidRenderedonly means the HTML changed. It no longer stands for "everything rendered".Server and CLI
elp:exportuses the shared renderer. If any diagram fails, the command fails without writing the package and lists each diagram with Pele's message, e.g."flowchart LR": Parse error on line 3: Expecting 'NODE_STRING', got 'EOF'. Before, it printedSUCCESSand shipped source text.routes/export.ts,routes/api/v1/export.tsandservices/public-view-content.tspre-rendered LaTeX but never Mermaid, so those exports also showed diagrams as text. They now get the Mermaid hook too. They do not fail on an invalid diagram; it stays as source. That avoids changing the API v1 error contract for integrations.jsdomis removed (it was only used for Mermaid SSR), and so is--external jsdomin the build.jsdom@26remains in the lockfile only as an optional dependency of the dev-onlyfabric.Browser
exporters.bundle.js→getMermaidPreRendererHooks()loads Pele on demand through the editor runtime, the same way the LaTeX path reuses$exe.math.loadMathJax. Failures are logged withconsole.warn. It is exposed onSharedExportersso thatYjsProjectBridge.exportToElpxuses it instead of the removedwindow.MermaidPreRenderer.common.js→$exe.mermaidnow hasenginePath()/load()(a promise, loaded once) /renderDiagrams()/init(). It renders everypre.mermaid:not([data-processed])withpele.render(). Pele does not measure the DOM, so the visibility and width checks, the pending state and the retry timers are gone, and so is the matching re-render inmenuStructureBehaviour.js. An invalid diagram keeps its source and gets a<p class="exe-mermaid-error" role="alert">with Pele's message (set viatextContent). In exported packagesenginePath()isnull, so nothing is ever requested (before,libs/mermaid/mermaid.min.jsgot a 404).render()rather thanmount(): the diagrams are static, andmount()'s resize/font observers would need teardown on every page change in the workarea for no visible benefit.$exe.mermaid.load()and ignores a failed preload, sinceinit()reports it.Packaging
scripts/vendor-pele.tscopiespele.global.jsand itsLICENSEfrom the pinned package into the gitignoredpublic/app/common/pele/, likevendor-edicuatex(whose drift/write helpers it reuses). It runs inbuild:all, the Dockerfile checks it with--check, and the e2e workflow uploads it with the other generated assets.mermaidandjsdomdependencies,public/app/common/mermaid/mermaid.min.js,public/app/common/MermaidPreRenderer.js(+ test),ServerMermaidPreRenderer.ts(+ spec), theMermaidPreRenderer.jsscript tags inworkarea.njk/static-index.html, the/libs/mermaidroutes, andgetPreRenderedMermaidCss()with themermaidWasRenderedplumbing in seven exporters.public/libs/README.md, the OSS-scanner threat model and thebun-runtimeskill (both copies) are updated.Visual compatibility
Pele does not implement Mermaid's named themes, and its layout engine places nodes and routes edges differently. To keep existing content familiar,
public/style/workarea/base.cssdefines Pele's CSS variables once on.pele. Their values were read from what Mermaid 11.12 (the version the editor shipped) actually renders withtheme: 'default': node fill#ececff, border#9370db, lines and text#333, subgraph fill#ffffde, square corners (--pele-radius: 0) and the"trebuchet ms", verdana, arial, sans-seriffont stack. Pele also writes that stack into each SVG and measures labels with it.base.cssis both the workarea stylesheet and every export'scontent/css/base.css, so the editor, previews and all formats share this single definition. eXe styles can override the variables. Explicitstyle,classDefandlinkStylecolours from authors are kept.Remaining differences:
directioninside a subgraph that is linked to outside nodes, and Mermaid ignores it.#aaaa33). Pele has a single border variable.Mermaid 11.12 (left) vs Pele 1.6 with this theme, server-rendered (right):
More diagram types (nested subgraphs, class, state, ER, mindmap, timeline, gitGraph, journey)
HTML5 export produced by the CLI, opened offline in WebKit:
Fixed issues
dist/cli.js, now contains<div class="exe-mermaid-rendered" data-mermaid="…"><svg class="pele pele-flowchart" viewBox="0 0 160 64">. There is no<pre class="mermaid">and no reference tolibs/mermaid/mermaid.min.js, and the page renders offline in Chromium, Firefox and WebKit. Gantt charts get a real width (800 px instead of 0), and flowcharts get a content-sized viewBox instead of-8 -8 50656 32. The regression tests inelp-export.spec.tsexport the issue's project to html5, html5-sp, scorm12, scorm2004, ims and epub3 and check every SVG's viewBox. A broken diagram now makes the CLI fail instead of reporting success. The shared renderer's spec requires a real SVG for every supported type; nothing accepts the unrendered fallback any more.measureTextin browsers, a built-in width table on the server) and draws them as SVG text in a box sized from that measurement. Regression tests:mermaid-pele.spec.ts(Playwright, Chromium and Firefox) renders the issue's diagram at device scale factors 0.9, 1 and 1.75 through the browser export hook and through the editor runtime. It asserts that the label wraps onto several lines with no text lost, and that its bounding box fits inside the node's box.text.spec.tschecks the same label in the preview generated from a real iDevice.MermaidPreRenderer.spec.tschecks the server output.Related pull requests
mermaiddependency left to bump. It also stops being blocked on SSR: its review asked to hold it until server-side pre-rendering works and to require a real SVG in tests. This PR covers both, without Mermaid. I have not touched it.getBBox/font-metric estimators, draft): eXeLearning no longer depends on it. Server rendering no longer goes through a DOM at all.Performance and dependency comparison
All local figures were measured on this branch versus
main(0d5451a), on macOS (Apple silicon), Bun 1.4.3.main)mermaid.min.js11.12.0)pele.global.js1.6.0)exporters.bundle.jsbun install --production)node_modulesmermaid@11.17.2/jsdom@30.0.1alonepele@1.6.0: 1 (3,560 KB)CSSStyleSheet is not defined)app/common/shrinks by 2,066,987 B (−75%)Methodology:
package.json+bun.lockfrom each side, copied to an empty directory;bun install --production --frozen-lockfile --ignore-scripts, thendu -sk node_modules. The per-package figures come from installing each package alone into an empty project.main) runsmermaid.render()withtheme: 'default', and Pele runsrender(), in the same Chromium page; the Bun figures usepele.render().bun dist/cli.js elp:export.In practice: the editor downloads about 536 KB less (gzip) the first time a page with a diagram opens. Exported packages never contained a renderer and still don't, but their SVGs are about 5× smaller. Server, Docker and CLI exports render diagrams at all. Two fewer runtime dependencies (184 fewer installed packages) means less to install, audit and keep updated. And one renderer, one version and one theme replace two code paths that had drifted apart.
Testing
Run locally on this branch:
make fixbun run build:allmake test-unitmake test-frontendmake test-integrationbun x playwright test mermaid-pele.spec.ts idevices/text.spec.ts -g "Pele|mermaid|Mermaid" --project=chromium --project=firefoxThe full
make test-e2eandmake test-e2e-staticruns are left to CI.New and updated tests:
MermaidPreRenderer.spec.ts(100% line coverage): every diagram type produces a valid SVG (positive, finite viewBox; noNaN/Infinity; text present), source preservation and entity/<br>decoding, style preservation,language-mermaidignored, multiple diagrams without duplicate ids, legacy runtime markup, JSON iDevice data, syntax/unsupported/limit failures, partial rendering accounting, an unavailable renderer, and the CLI export cannot pre-render Mermaid diagrams (CSSStyleSheet is not defined) and leaves them as plain text #2544/Mermaid: long node labels are cut off in Chrome and Edge at some zoom levels, and the cut is saved in previews and exports #2490 regressions.elp-export.spec.ts: the CLI export cannot pre-render Mermaid diagrams (CSSStyleSheet is not defined) and leaves them as plain text #2544 project in six formats, ELPX keeping the source, and the CLI failing on a broken diagram.routes/export.spec.ts,routes/api/v1/export.spec.ts,public-view-content.spec.ts: the server paths pre-render Mermaid.common.test.js(the new$exe.mermaidblock has 100% line coverage),exemermaid-plugin.test.js,YjsProjectBridge.test.jsandvendor-pele.spec.ts.test/e2e/.../mermaid-pele.spec.ts(new) and the Mermaid test intext.spec.ts. The latter used to accept either a pre-rendered or a runtime SVG. It now requires the editor view to render with Pele, the preview to be pre-rendered with no renderer script or global, and the Mermaid: long node labels are cut off in Chrome and Edge at some zoom levels, and the cut is saved in previews and exports #2490 label to fit.base.cssships unchanged, because the styles live in it.Also checked outside the repo suite, using Playwright 1.64.1 because the repo's pinned 1.58.2 has no WebKit build installed and the
webkitproject is commented out inplaywright.config.ts:file://) in Chromium, Firefox and WebKit. Every format and engine shows all 10 SVGs at a non-zero size with the theme applied. Nopre.mermaidremains, the EPUB XHTML has no XML parse errors, and there are no requests for any renderer.javascript:/data:links are dropped, HTML in labels is escaped or stripped (<img onerror>and<script>never reach the SVG),stylevalues withurl(javascript:)/expression()are rejected as syntax errors, sources over 50,000 characters are rejected withlimit, and the SVGs contain noidattributes, so several diagrams on one page cannot collide.The only line not covered is
vendor-pele.ts'sif (import.meta.main)entry guard, the same as invendor-edicuatex.ts.Compatibility and limitations
data-mermaidand ELPXcontent.xmlare unchanged. Nothing in existing projects is migrated or rewritten.<b>/<strong>/<i>/<em>and<br>work;<u>,<sub>and<sup>keep their text but lose the formatting.clickJavaScript callbacks are ignored; links still work forhttp,https,mailto,teland relative URLs.fa:icons are dropped and their text is kept. The editor never loaded Font Awesome for Mermaid, so these did not render before either.%%{init}%%themes andthemeVariablesno longer change colours.<div class="mermaid">(as opposed to<pre>) are not rendered in the editor any more. They never rendered in exports, and the TinyMCE plugin only creates<pre>.console.warnin the browser) rather than failing the whole export. The author already sees Pele's error in the editor. Only the CLI fails the export.