diff --git a/scripts/docs-site/build.mjs b/scripts/docs-site/build.mjs
index 3cd880d50f..45e026ac0f 100644
--- a/scripts/docs-site/build.mjs
+++ b/scripts/docs-site/build.mjs
@@ -17,6 +17,7 @@ import { walkDocs } from "./document-files.mjs";
import { siteCss } from "./site-css.mjs";
import { siteJs } from "./site-js.mjs";
import { webVitalsAssetName, webVitalsRuntime } from "./web-vitals-runtime.mjs";
+import { peleAssetName, peleEntry, peleRuntime } from "./pele-runtime.mjs";
import { chromeStringsForLocale } from "./chrome-strings.mjs";
import { createMarkdownRenderer, renderMdxish, copyControlContent } from "./mdx-ish.mjs";
import { createRenderCache } from "./render-cache.mjs";
@@ -873,12 +874,8 @@ function writeStaticAssets() {
const glimmDir = path.dirname(glimmEntry);
fs.writeFileSync(path.join(assetsDir, glimmAssetName), glimmRuntime, "utf8");
fs.copyFileSync(path.join(glimmDir, "..", "LICENSE"), path.join(assetsDir, "glimm-LICENSE.txt"));
- const peleDir = path.join(root, "node_modules", "pele");
- const peleEntry = path.join(peleDir, "dist", "pele.js");
- if (fs.existsSync(peleEntry)) {
- fs.copyFileSync(peleEntry, path.join(assetsDir, "pele-1.6.0.js"));
- fs.copyFileSync(path.join(peleDir, "LICENSE"), path.join(assetsDir, "pele-LICENSE.txt"));
- }
+ fs.writeFileSync(path.join(assetsDir, peleAssetName), peleRuntime, "utf8");
+ fs.copyFileSync(path.join(path.dirname(peleEntry), "..", "LICENSE"), path.join(assetsDir, "pele-LICENSE.txt"));
fs.writeFileSync(path.join(outDir, ".nojekyll"), "", "utf8");
fs.writeFileSync(path.join(outDir, "og-card.png"), defaultOgPng);
fs.writeFileSync(path.join(outDir, "og-card.svg"), defaultOgSvg);
diff --git a/scripts/docs-site/diagram-rendering.test.mjs b/scripts/docs-site/diagram-rendering.test.mjs
index 15ce5137a9..24258c3613 100644
--- a/scripts/docs-site/diagram-rendering.test.mjs
+++ b/scripts/docs-site/diagram-rendering.test.mjs
@@ -1,10 +1,15 @@
import assert from "node:assert/strict";
import fs from "node:fs";
+import http from "node:http";
+import { createHash } from "node:crypto";
import path from "node:path";
+import { fileURLToPath } from "node:url";
import { test } from "node:test";
import { chromium, webkit } from "playwright";
import { fixture } from "./test-helpers/redirect-fixture.mjs";
+const runtimeUrl = "/assets/pele-" + createHash("sha256").update(fs.readFileSync(fileURLToPath(import.meta.resolve("pele")))).digest("hex").slice(0, 12) + ".js";
+
const source = 'flowchart LR\n accTitle: Public diagram\n accDescr: Ready becomes rendered\n A["Ready
state"] --> B[Rendered]';
const hostile = 'flowchart LR\n A[""] --> B[Safe]\n click B "javascript:alert(1)"';
const markdown = '# Diagrams\n\n' + [source, source, hostile, 'flowchart LR\n Broken -->'].map(text => '\x60\x60\x60mermaid\n' + text + '\n\x60\x60\x60').join('\n\n');
@@ -40,18 +45,18 @@ for (const [name, engine] of [["Chromium", chromium], ["WebKit", webkit]]) {
await page.goto("https://docs.example/guide", { waitUntil: "domcontentloaded" });
await fontStarted;
// Settle the renderer import without releasing the intentionally cold webfonts.
- await page.evaluate(async () => { await import("/assets/pele-1.6.0.js"); await new Promise(requestAnimationFrame); });
+ await page.evaluate(async url => { await import(url); await new Promise(requestAnimationFrame); }, runtimeUrl);
assert.equal(await page.locator(".oc-mermaid.is-rendered").count(), 0, "diagram layout must wait for the label font");
releaseFonts();
await page.waitForFunction(() => document.querySelectorAll(".oc-mermaid.is-rendered").length === 3);
assert.match(await page.locator(".oc-mermaid > svg").first().getAttribute("class"), /pele/);
assert.equal(await page.locator(".oc-mermaid > svg text").first().evaluate(text => getComputedStyle(text).fontSize), "16px");
- assert.ok(await page.locator(".oc-mermaid > svg").first().evaluate(async svg => {
- const pele = await import("/assets/pele-1.6.0.js");
+ assert.ok(await page.locator(".oc-mermaid > svg").first().evaluate(async (svg, url) => {
+ const pele = await import(url);
const fontFamily = getComputedStyle(svg).fontFamily;
const loaded = pele.render(svg.parentElement.dataset.mermaid, { fontFamily, fontSize: 16, links: false, images: false });
return svg.viewBox.baseVal.width === loaded.width && svg.viewBox.baseVal.height === loaded.height;
- }), "cold-load geometry must match the loaded font");
+ }, runtimeUrl), "cold-load geometry must match the loaded font");
const titleIds = await page.locator(".oc-mermaid > svg[aria-labelledby]").evaluateAll(svgs => svgs.map(svg => {
const title = document.getElementById(svg.getAttribute("aria-labelledby"));
const desc = document.getElementById(svg.getAttribute("aria-describedby"));
@@ -72,3 +77,43 @@ for (const [name, engine] of [["Chromium", chromium], ["WebKit", webkit]]) {
assert.deepEqual(diagramRequests, []);
});
}
+
+for (const [name, engine] of [["Chromium", chromium], ["WebKit", webkit]]) {
+ test(name + " loads the installed renderer after an immutable legacy asset was cached", async t => {
+ const f = fixture(t, [], { "guide.md": "# Diagram\n\n" + "\x60\x60\x60mermaid\nflowchart LR\n A --> B\n\x60\x60\x60" });
+ fs.symlinkSync(path.resolve("node_modules"), path.join(f.root, "node_modules"), process.platform === "win32" ? "junction" : "dir");
+ const built = f.build();
+ assert.equal(built.status, 0, built.stderr);
+ const site = path.join(f.root, "dist/docs-site");
+ const runtime = fs.readFileSync(fileURLToPath(import.meta.resolve("pele")), "utf8");
+ const shell = fs.readFileSync(path.join(site, "assets/docs-site.js"), "utf8");
+ const runtimeUrl = shell.match(/\/assets\/pele-[\w.]+\.js/)[0];
+ const server = http.createServer((req, res) => {
+ if (req.url === "/warm") return res.setHeader("content-type", "text/html").end("