Skip to content

Commit 29b3ed0

Browse files
authored
feat: add Mermaid renderer plugin (#2465)
1 parent 6a6c719 commit 29b3ed0

7 files changed

Lines changed: 79 additions & 2 deletions

File tree

docs/.vitepress/customizations.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export default [
4343
text: 'Configure Mermaid',
4444
link: '/custom/config-mermaid',
4545
},
46+
{
47+
text: 'Configure Mermaid Renderer',
48+
link: '/custom/config-mermaid-renderer',
49+
},
4650
{
4751
text: 'Configure Routes',
4852
link: '/custom/config-routes',
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Configure Mermaid Renderer
2+
3+
<Environment type="client" />
4+
5+
1. The user installs the Mermaid library they want to use. e.g.) `npm install beautiful-mermaid`
6+
2. Create `./setup/mermaid-renderer.ts` with the following content:
7+
8+
```ts
9+
// setup/mermaid-renderer.ts
10+
import { defineMermaidRendererSetup } from '@slidev/types'
11+
// example. https://github.com/lukilabs/beautiful-mermaid?tab=readme-ov-file#readme
12+
import { renderMermaid } from 'beautiful-mermaid'
13+
14+
export default defineMermaidRendererSetup(() => {
15+
return (code, _options) => renderMermaid(code)
16+
})
17+
```
18+
19+
This setting allows you to use the 3rd party Mermaid library. Replace the `renderMermaid()` part with the render function of the library.

packages/client/modules/mermaid.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import mermaidRenderers from '#slidev/setups/mermaid-renderer'
12
import { clearUndefined } from '@antfu/utils'
23
import lz from 'lz-string'
34
import mermaid from 'mermaid/dist/mermaid.esm.mjs'
@@ -17,12 +18,24 @@ export async function renderMermaid(lzEncoded: string, options: any) {
1718
if (_cache)
1819
return _cache
1920

21+
const code = lz.decompressFromBase64(lzEncoded)
22+
23+
// custom renderer
24+
for (const setup of mermaidRenderers) {
25+
const renderer = await setup()
26+
if (renderer) {
27+
const svg = await renderer(code, options)
28+
cache.set(key, svg)
29+
return svg
30+
}
31+
}
32+
33+
// fallback: existing mermaid
2034
mermaid.initialize({
2135
startOnLoad: false,
2236
...clearUndefined(await setupMermaid() || {}),
2337
...clearUndefined(options),
2438
})
25-
const code = lz.decompressFromBase64(lzEncoded)
2639
const id = makeId()
2740
const { svg } = await mermaid.render(id, code, containerElement)
2841
cache.set(key, svg)

packages/slidev/node/virtual/setups.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ function createSetupTemplate(name: string): VirtualModuleTemplate {
1616
}
1717

1818
// setups
19-
const setupModules = ['shiki', 'code-runners', 'monaco', 'mermaid', 'main', 'root', 'routes', 'shortcuts', 'context-menu']
19+
const setupModules = ['shiki', 'code-runners', 'monaco', 'mermaid', 'mermaid-renderer', 'main', 'root', 'routes', 'shortcuts', 'context-menu']
2020

2121
export const templateSetups = setupModules.map(createSetupTemplate)

packages/types/client.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ declare module '#slidev/setups/mermaid' {
6868
export default setups
6969
}
7070

71+
declare module '#slidev/setups/mermaid-renderer' {
72+
import type { MermaidRendererSetup } from '@slidev/types'
73+
74+
const setups: MermaidRendererSetup[]
75+
export default setups
76+
}
77+
7178
declare module '#slidev/setups/main' {
7279
import type { AppSetup } from '@slidev/types'
7380

packages/types/src/setups.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export type AppSetup = (context: AppContext) => Awaitable<void>
8888
export type RootSetup = () => Awaitable<void>
8989
export type RoutesSetup = (routes: RouteRecordRaw[]) => RouteRecordRaw[]
9090
export type MermaidSetup = () => Awaitable<Partial<MermaidConfig> | void>
91+
export type MermaidRenderFn = (code: string, options: Record<string, any>) => Awaitable<string>
92+
export type MermaidRendererSetup = () => Awaitable<MermaidRenderFn | void>
9193
export type ShortcutsSetup = (nav: NavOperations, defaultShortcuts: ShortcutOptions[]) => Array<ShortcutOptions>
9294
export type CodeRunnersSetup = (runners: CodeRunnerProviders) => Awaitable<CodeRunnerProviders | void>
9395
export type ContextMenuSetup = (items: ComputedRef<ContextMenuItem[]>) => ComputedRef<ContextMenuItem[]>
@@ -103,6 +105,7 @@ export const defineAppSetup = defineSetup<AppSetup>
103105
export const defineRootSetup = defineSetup<RootSetup>
104106
export const defineRoutesSetup = defineSetup<RoutesSetup>
105107
export const defineMermaidSetup = defineSetup<MermaidSetup>
108+
export const defineMermaidRendererSetup = defineSetup<MermaidRendererSetup>
106109
export const defineKatexSetup = defineSetup<KatexSetup>
107110
export const defineShortcutsSetup = defineSetup<ShortcutsSetup>
108111
export const defineTransformersSetup = defineSetup<TransformersSetup>

test/mermaid-renderer.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import type { ResolvedSlidevOptions } from '@slidev/types'
2+
import type { PluginContext } from 'rollup'
3+
import { describe, expect, it } from 'vitest'
4+
import { templateSetups } from '../packages/slidev/node/virtual/setups'
5+
6+
describe('mermaid-renderer virtual module', () => {
7+
it('registers /@slidev/setups/mermaid-renderer', () => {
8+
const ids = templateSetups.map(t => t.id)
9+
expect(ids).toContain('/@slidev/setups/mermaid-renderer')
10+
})
11+
12+
it('generates content with mermaid-renderer glob', () => {
13+
const template = templateSetups.find(t => t.id === '/@slidev/setups/mermaid-renderer')!
14+
const content = template.getContent.call({} as PluginContext, {
15+
userRoot: '/user/project',
16+
roots: ['/user/project'],
17+
} as ResolvedSlidevOptions)
18+
19+
expect(content).toContain('mermaid-renderer')
20+
expect(content).toContain('export default')
21+
expect(content).toContain('filter(Boolean)')
22+
})
23+
24+
it('comes after mermaid in templateSetups', () => {
25+
const ids = templateSetups.map(t => t.id)
26+
const mermaidIdx = ids.indexOf('/@slidev/setups/mermaid')
27+
const rendererIdx = ids.indexOf('/@slidev/setups/mermaid-renderer')
28+
expect(mermaidIdx).toBeGreaterThanOrEqual(0)
29+
expect(rendererIdx).toBeGreaterThan(mermaidIdx)
30+
})
31+
})

0 commit comments

Comments
 (0)