From 9226b16c72349ebedf5a538c99be226c4e0a8393 Mon Sep 17 00:00:00 2001
From: Ying Zhong <0x00eeee@gmail.com>
Date: Wed, 30 Sep 2026 20:34:04 +0800
Subject: [PATCH] Pin and conditionally load Mermaid in styled HTML exports
Closes #203
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
dist/markedit-preview.js | 32 +++++-----
src/render.ts | 39 ++++++------
tests/build.test.ts | 3 +
tests/render.styles.test.ts | 117 ++++++++++++++++++++++++++++++++++++
types/env.d.ts | 5 ++
vite.config.mts | 2 +
vitest.config.ts | 2 +
7 files changed, 167 insertions(+), 33 deletions(-)
create mode 100644 tests/render.styles.test.ts
diff --git a/dist/markedit-preview.js b/dist/markedit-preview.js
index 28b64e9..8b7dc94 100644
--- a/dist/markedit-preview.js
+++ b/dist/markedit-preview.js
@@ -1982,22 +1982,22 @@ ${n}`}function Whe(t="auto"){if(Yhe)return[`:root { color-scheme: ${t==="auto"?"
${n}
`,r=['
',`
${t}
-
`,e(M$e(zg)),e(Whe(zg)),e(P$e(zg)),e(Xhe()),""];{r.push(e(Khe(zg)));const{default:n}=await Promise.resolve().then(()=>OG);r.push(e(n));const i=`
- `;
- components.push(mermaid);
+ const template = document.createElement('template');
+ template.innerHTML = html;
+
+ if (template.content.querySelector('.mermaid') !== null) {
+ const mermaid = `
+ `;
+ components.push(mermaid);
+ }
}
return components.join('\n');
diff --git a/tests/build.test.ts b/tests/build.test.ts
index d53fb36..aee647a 100644
--- a/tests/build.test.ts
+++ b/tests/build.test.ts
@@ -9,6 +9,7 @@ describe('build variants', () => {
loader: 'ts',
define: { __FULL_BUILD__: String(fullBuild) },
treeShaking: true,
+ minifySyntax: true,
});
const lite = await transformRenderer(false);
@@ -24,6 +25,8 @@ describe('build variants', () => {
expect(lite.code).not.toMatch(moduleImport);
expect(full.code).toMatch(moduleImport);
});
+ expect(lite.code).not.toContain('https://cdn.jsdelivr.net/npm/mermaid@');
+ expect(full.code).toContain('https://cdn.jsdelivr.net/npm/mermaid@');
expect(lite.code).toMatch(/renderToString:\s*\(\.\.\._args\) => ""/);
expect(lite.code).toMatch(/render:\s*async \(\) => \(\{\s*svg: ""\s*\}\)/);
});
diff --git a/tests/render.styles.test.ts b/tests/render.styles.test.ts
new file mode 100644
index 0000000..a96f2bb
--- /dev/null
+++ b/tests/render.styles.test.ts
@@ -0,0 +1,117 @@
+// @vitest-environment happy-dom
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import mermaidPackage from 'mermaid/package.json';
+import type * as Settings from '../src/support/settings';
+import { applyStyles, renderMarkdown } from '../src/render';
+import { generateStaticHtml, renderStaticHtml, saveStyledHtml } from '../src/view';
+
+const mocks = vi.hoisted(() => ({
+ getText: vi.fn(() => '# Hello'),
+ getFileInfo: vi.fn(async () => ({ filePath: '/example.md' })),
+ showSavePanel: vi.fn(),
+ settings: { styledHtmlColorScheme: 'auto' },
+}));
+
+vi.mock('markedit-api', () => ({
+ MarkEdit: {
+ editorAPI: { getText: mocks.getText },
+ getFileInfo: mocks.getFileInfo,
+ showSavePanel: mocks.showSavePanel,
+ },
+}));
+
+vi.mock('../src/support/settings', async importOriginal => ({
+ ...await importOriginal(),
+ get styledHtmlColorScheme() { return mocks.settings.styledHtmlColorScheme; },
+}));
+
+const mermaidMarkdown = '```mermaid\ngraph TD\n A --> B\n```';
+const mermaidURL = `https://cdn.jsdelivr.net/npm/mermaid@${mermaidPackage.version}/dist/mermaid.esm.min.mjs`;
+
+function scripts(html: string) {
+ const template = document.createElement('template');
+ template.innerHTML = html;
+ return template.content.querySelectorAll('script');
+}
+
+beforeEach(() => {
+ mocks.getText.mockReturnValue('# Hello');
+ mocks.getFileInfo.mockResolvedValue({ filePath: '/example.md' });
+ mocks.showSavePanel.mockClear();
+ mocks.settings.styledHtmlColorScheme = 'auto';
+});
+
+describe('styled HTML remote scripts', () => {
+ it.each([
+ '# Hello',
+ 'The word mermaid is ordinary text.',
+ '```html\ngraph TD
\n```',
+ '',
+ 'Plain content
',
+ '$E = mc^2$',
+ ])('does not add scripts when no Mermaid element is present: %s', async markdown => {
+ const html = await applyStyles(await renderMarkdown(markdown));
+ expect(scripts(html)).toHaveLength(0);
+ expect(html).not.toContain(mermaidURL);
+ expect(html).toContain('