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\n
graph 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('