From 0ef4fddb7ff087bd0a6c1aba61e3736ac9f14321 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 5 Apr 2024 10:09:13 +0200 Subject: [PATCH] feat: use magic-string to do markdown pre-transform (#1496) Co-authored-by: _Kerman --- .../node/syntax/transform/code-wrapper.ts | 10 +- .../node/syntax/transform/in-page-css.ts | 14 +- .../node/syntax/transform/katex-wrapper.ts | 9 +- .../node/syntax/transform/magic-move.ts | 10 +- .../slidev/node/syntax/transform/mermaid.ts | 17 ++- .../slidev/node/syntax/transform/monaco.ts | 29 ++-- .../slidev/node/syntax/transform/plant-uml.ts | 15 +- .../node/syntax/transform/slot-sugar.ts | 31 ++-- .../slidev/node/syntax/transform/snippet.ts | 10 +- .../slidev/node/syntax/transform/utils.ts | 22 --- packages/slidev/node/vite/markdown.ts | 37 +++-- packages/slidev/package.json | 1 + packages/types/src/index.ts | 1 + packages/types/src/transform.ts | 7 + pnpm-lock.yaml | 33 +++-- test/__snapshots__/transform.test.ts.snap | 10 ++ test/_tutils.ts | 13 ++ test/transform-magic-move.test.ts | 21 ++- test/transform.test.ts | 140 ++++++++++++++---- 19 files changed, 279 insertions(+), 151 deletions(-) create mode 100644 packages/types/src/transform.ts create mode 100644 test/_tutils.ts diff --git a/packages/slidev/node/syntax/transform/code-wrapper.ts b/packages/slidev/node/syntax/transform/code-wrapper.ts index ddde28acfe..a24fde680a 100644 --- a/packages/slidev/node/syntax/transform/code-wrapper.ts +++ b/packages/slidev/node/syntax/transform/code-wrapper.ts @@ -1,3 +1,4 @@ +import type { MarkdownTransformContext } from '@slidev/types' import { normalizeRangeStr } from './utils' export const reCodeBlock = /^```([\w'-]+?)(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?(.*?))?\n([\s\S]+?)^```$/mg @@ -5,13 +6,16 @@ export const reCodeBlock = /^```([\w'-]+?)(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?(.*? /** * Transform code block with wrapper */ -export function transformCodeWrapper(md: string) { - return md.replace( +export function transformCodeWrapper(ctx: MarkdownTransformContext) { + ctx.s.replace( reCodeBlock, - (full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string) => { + (full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string, index: number) => { + if (ctx.isIgnored(index)) + return full const ranges = normalizeRangeStr(rangeStr) code = code.trimEnd() options = options.trim() || '{}' + ctx.ignores.push([index, index + full.length]) return `\n\n\n\`\`\`${lang}${attrs}\n${code}\n\`\`\`\n\n` }, ) diff --git a/packages/slidev/node/syntax/transform/in-page-css.ts b/packages/slidev/node/syntax/transform/in-page-css.ts index 4633f3c062..cc5fef183f 100644 --- a/packages/slidev/node/syntax/transform/in-page-css.ts +++ b/packages/slidev/node/syntax/transform/in-page-css.ts @@ -1,26 +1,22 @@ -import { getCodeBlocks } from './utils' +import type { MarkdownTransformContext } from '@slidev/types' /** * Transform + + + \`\`\`css \`\`\` + + " `; @@ -71,12 +76,17 @@ Default Slot " diff --git a/test/_tutils.ts b/test/_tutils.ts new file mode 100644 index 0000000000..185174a6c9 --- /dev/null +++ b/test/_tutils.ts @@ -0,0 +1,13 @@ +import MagicString from 'magic-string' +import type { MarkdownTransformContext } from '@slidev/types' + +export function createTransformContext(code: string): MarkdownTransformContext { + const s = new MagicString(code) + return { + s, + ignores: [], + isIgnored(index: number) { + return this.ignores.some(([start, end]) => index >= start && index < end) + }, + } +} diff --git a/test/transform-magic-move.test.ts b/test/transform-magic-move.test.ts index e97c3e373f..3f1f7ae36a 100644 --- a/test/transform-magic-move.test.ts +++ b/test/transform-magic-move.test.ts @@ -1,6 +1,7 @@ import { expect, it } from 'vitest' import { getHighlighter } from 'shiki' import { transformMagicMove } from '../packages/slidev/node/syntax/transform' +import { createTransformContext } from './_tutils' it('basic', async () => { const code = ` @@ -25,13 +26,17 @@ Some text after langs: ['typescript'], }) - expect(transformMagicMove( - code, + const ctx = createTransformContext(code) + + transformMagicMove( + ctx, shiki, { theme: 'nord', }, - )) + ) + + expect(ctx.s.toString()) .toMatchInlineSnapshot(` " @@ -67,13 +72,17 @@ Some text after langs: ['angular-ts'], }) - expect(transformMagicMove( - code, + const ctx = createTransformContext(code) + + transformMagicMove( + ctx, shiki, { theme: 'nord', }, - )) + ) + + expect(ctx.s.toString()) .toMatchInlineSnapshot(` " diff --git a/test/transform.test.ts b/test/transform.test.ts index f8bd1971b4..accf92b318 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -1,13 +1,11 @@ import path from 'node:path' import { describe, expect, it } from 'vitest' -import { transformMermaid, transformPageCSS, transformPlantUml, transformSlotSugar, transformSnippet } from '../packages/slidev/node/syntax/transform' - -// const isMacOS = process.platform === 'darwin' -// const isNode18orAbove = +process.version.slice(1, 3) >= 18 +import { transformCodeWrapper, transformMermaid, transformPageCSS, transformPlantUml, transformSlotSugar, transformSnippet } from '../packages/slidev/node/syntax/transform' +import { createTransformContext } from './_tutils' describe('markdown transform', () => { it('slot-sugar', () => { - expect(transformSlotSugar(` + const ctx = createTransformContext(` # Page Default Slot @@ -15,11 +13,17 @@ Default Slot Right Slot ::left::
Left Slot
-`)).toMatchSnapshot() +`) + + transformCodeWrapper(ctx) + transformSlotSugar(ctx) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(`[]`) }) it('slot-sugar with default', () => { - expect(transformSlotSugar(` + const ctx = createTransformContext(` :: right:: Right Slot ::left :: @@ -27,11 +31,16 @@ Right Slot :: default :: # Page Default Slot -`)).toMatchSnapshot() +`) + + transformSlotSugar(ctx) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(`[]`) }) it('slot-sugar with code', () => { - expect(transformSlotSugar(` + const ctx = createTransformContext(` # Page Default Slot @@ -44,11 +53,24 @@ Slot Usage ::left:: \`\`\` -`)).toMatchSnapshot() +`) + + transformCodeWrapper(ctx) + transformSlotSugar(ctx) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(` + [ + [ + 34, + 73, + ], + ] + `) }) it('slot-sugar with symbols in name', () => { - expect(transformSlotSugar(` + const ctx = createTransformContext(` # Page Default Slot @@ -56,11 +78,16 @@ Default Slot First Slot ::slot.2:: Second Slot -`)).toMatchSnapshot() +`) + + transformSlotSugar(ctx) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(`[]`) }) it('inline CSS', () => { - expect(transformPageCSS(` + const ctx = createTransformContext(` # Page \`\`\` -`, '01.md')).toMatchSnapshot() +`) + + transformCodeWrapper(ctx) + transformPageCSS(ctx, '01.md') + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(` + [ + [ + 49, + 99, + ], + ] + `) }) it('mermaid', () => { - expect(transformMermaid(` + const ctx = createTransformContext(` # Page \`\`\`mermaid @@ -95,12 +135,27 @@ B[Text] --> C{Decision} C -->|One| D[Result 1] C -->|Two| E[Result 2] \`\`\` -`)).toMatchSnapshot() +`) + + transformMermaid(ctx) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(` + [ + [ + 10, + 126, + ], + [ + 128, + 252, + ], + ] + `) }) it('plantUML', () => { - const result = transformPlantUml( - ` + const ctx = createTransformContext(` # Page \`\`\`plantuml @@ -126,11 +181,23 @@ Alice <- Bob : Hello, too! *** Raspyfi => Volumio @endmindmap \`\`\` -`, - 'https://www.plantuml.com/plantuml', - ) +`) - expect(result).toContain(` { - expect(transformSnippet(` + const ctx = createTransformContext(` <<< @/snippets/snippet.ts#snippet ts {2|3|4}{lines:true} -`, { - userRoot: path.join(__dirname, './fixtures/'), - data: { - slides: [ - {} as any, - ], - watchFiles: [], - }, - } as any, `/@slidev/slides/1.md`)).toMatchSnapshot() +`) + + transformSnippet( + ctx, + { + userRoot: path.join(__dirname, './fixtures/'), + data: { + slides: [ + {} as any, + ], + watchFiles: [], + }, + } as any, +`/@slidev/slides/1.md`, + ) + + expect(ctx.s.toString()).toMatchSnapshot() + expect(ctx.ignores).toMatchInlineSnapshot(`[]`) }) })