Skip to content

Commit

Permalink
fix: use magic-string-stack (#1545)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 17, 2024
1 parent e8a14c7 commit 867dee6
Show file tree
Hide file tree
Showing 21 changed files with 356 additions and 260 deletions.
24 changes: 21 additions & 3 deletions packages/slidev/node/syntax/markdown-it/markdown-it-v-drag.ts
@@ -1,12 +1,30 @@
import type MarkdownIt from 'markdown-it'
import type { SourceMapConsumer } from 'source-map-js'
import type MagicString from 'magic-string-stack'
import { SourceMapConsumer } from 'source-map-js'

export default function markdownItVDrag(md: MarkdownIt, sourceMapConsumers: Record<string, SourceMapConsumer>) {
export default function markdownItVDrag(md: MarkdownIt, markdownTransformMap: Map<string, MagicString>) {
const visited = new WeakSet()
const sourceMapConsumers = new WeakMap<MagicString, SourceMapConsumer>()

function getSourceMapConsumer(id: string) {
const s = markdownTransformMap.get(id)
if (!s)
return undefined
let smc = sourceMapConsumers.get(s)
if (smc)
return smc
const sourceMap = s.generateMap()
smc = new SourceMapConsumer({
...sourceMap,
version: sourceMap.version.toString(),
})
sourceMapConsumers.set(s, smc)
return smc
}

const _parse = md.parse
md.parse = function (src, env) {
const smc = sourceMapConsumers[env.id]
const smc = getSourceMapConsumer(env.id)
const toOriginalPos = smc
? (line: number) => smc.originalPositionFor({ line, column: 0 }).line
: (line: number) => line
Expand Down
5 changes: 1 addition & 4 deletions packages/slidev/node/syntax/transform/code-wrapper.ts
Expand Up @@ -9,13 +9,10 @@ export const reCodeBlock = /^```([\w'-]+?)(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?(.*?
export function transformCodeWrapper(ctx: MarkdownTransformContext) {
ctx.s.replace(
reCodeBlock,
(full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string, index: number) => {
if (ctx.isIgnored(index))
return full
(full, lang = '', rangeStr: string = '', options = '', attrs = '', code: string) => {
const ranges = normalizeRangeStr(rangeStr)
code = code.trimEnd()
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `\n<CodeBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>\n\n\`\`\`${lang}${attrs}\n${code}\n\`\`\`\n\n</CodeBlockWrapper>`
},
)
Expand Down
10 changes: 6 additions & 4 deletions packages/slidev/node/syntax/transform/in-page-css.ts
@@ -1,18 +1,20 @@
import type { MarkdownTransformContext } from '@slidev/types'
import { getCodeBlocks } from './utils'

/**
* Transform <style> in markdown to scoped style with page selector
*/
export function transformPageCSS(ctx: MarkdownTransformContext, id: string) {
const page = id.match(/(\d+)\.md$/)?.[1]
export function transformPageCSS(ctx: MarkdownTransformContext) {
const page = ctx.id.match(/(\d+)\.md$/)?.[1]
if (!page)
return

const codeBlocks = getCodeBlocks(ctx.s.original)

ctx.s.replace(
/(\n<style[^>]*?>)([\s\S]+?)(<\/style>)/g,
(full, start, css, end, index) => {
// don't replace `<style>` inside code blocks, #101
if (ctx.isIgnored(index))
if (codeBlocks.isInsideCodeblocks(index))
return full
if (!start.includes('scoped'))
start = start.replace('<style', '<style scoped')
Expand Down
3 changes: 1 addition & 2 deletions packages/slidev/node/syntax/transform/katex-wrapper.ts
Expand Up @@ -6,11 +6,10 @@ import type { MarkdownTransformContext } from '@slidev/types'
export function transformKaTexWrapper(ctx: MarkdownTransformContext) {
ctx.s.replace(
/^\$\$(?:\s*{([\d\w*,\|-]+)}\s*?({.*?})?\s*?)?\n([\s\S]+?)^\$\$/mg,
(full, rangeStr: string = '', options = '', code: string, index: number) => {
(full, rangeStr: string = '', options = '', code: string) => {
const ranges = !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
code = code.trimEnd()
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `<KaTexBlockWrapper v-bind="${options}" :ranges='${JSON.stringify(ranges)}'>\n\n\$\$\n${code}\n\$\$\n</KaTexBlockWrapper>\n`
},
)
Expand Down
42 changes: 20 additions & 22 deletions packages/slidev/node/syntax/transform/magic-move.ts
Expand Up @@ -12,32 +12,30 @@ const reMagicMoveBlock = /^````(?:md|markdown) magic-move(?:[ ]*(\{.*?\})?([^\n]
* Transform magic-move code blocks
*/
export function transformMagicMove(
ctx: MarkdownTransformContext,
shiki: Highlighter | undefined,
shikiOptions: MarkdownItShikiOptions | undefined,
) {
ctx.s.replace(
reMagicMoveBlock,
(full, options = '{}', _attrs = '', body: string, index: number) => {
if (!shiki || !shikiOptions)
throw new Error('Shiki is required for Magic Move. You may need to set `highlighter: shiki` in your Slidev config.')
return (ctx: MarkdownTransformContext) => {
ctx.s.replace(
reMagicMoveBlock,
(full, options = '{}', _attrs = '', body: string) => {
if (!shiki || !shikiOptions)
throw new Error('Shiki is required for Magic Move. You may need to set `highlighter: shiki` in your Slidev config.')

const matches = Array.from(body.matchAll(reCodeBlock))
const matches = Array.from(body.matchAll(reCodeBlock))

if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')
if (!matches.length)
throw new Error('Magic Move block must contain at least one code block')

const ranges = matches.map(i => normalizeRangeStr(i[2]))
const steps = matches.map(i => codeToKeyedTokens(shiki, i[5].trimEnd(), {
...shikiOptions,
lang: i[1] as any,
}),
)
const compressed = lz.compressToBase64(JSON.stringify(steps))

ctx.ignores.push([index, index + full.length])

return `<ShikiMagicMove v-bind="${options}" steps-lz="${compressed}" :step-ranges='${JSON.stringify(ranges)}' />`
},
)
const ranges = matches.map(i => normalizeRangeStr(i[2]))
const steps = matches.map(i => codeToKeyedTokens(shiki, i[5].trimEnd(), {
...shikiOptions,
lang: i[1] as any,
}),
)
const compressed = lz.compressToBase64(JSON.stringify(steps))
return `<ShikiMagicMove v-bind="${options}" steps-lz="${compressed}" :step-ranges='${JSON.stringify(ranges)}' />`
},
)
}
}
3 changes: 1 addition & 2 deletions packages/slidev/node/syntax/transform/mermaid.ts
Expand Up @@ -5,11 +5,10 @@ import type { MarkdownTransformContext } from '@slidev/types'
* Transform Mermaid code blocks (render done on client side)
*/
export function transformMermaid(ctx: MarkdownTransformContext) {
ctx.s.replace(/^```mermaid\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', code = '', index: number) => {
ctx.s.replace(/^```mermaid\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', code = '') => {
code = code.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Mermaid code-lz="${encoded}" v-bind="${options}" />`
})
}
13 changes: 6 additions & 7 deletions packages/slidev/node/syntax/transform/monaco.ts
@@ -1,7 +1,9 @@
import lz from 'lz-string'
import type { MarkdownTransformContext } from '@slidev/types'

export function transformMonaco(ctx: MarkdownTransformContext, enabled = true) {
export function transformMonaco(ctx: MarkdownTransformContext) {
const enabled = (ctx.options.data.config.monaco === true || ctx.options.data.config.monaco === ctx.options.mode)

if (!enabled) {
ctx.s.replace(/{monaco([\w:,-]*)}/g, '')
return
Expand All @@ -10,32 +12,29 @@ export function transformMonaco(ctx: MarkdownTransformContext, enabled = true) {
// transform monaco
ctx.s.replace(
/^```(\w+?)\s*{monaco-diff}\s*?({.*?})?\s*?\n([\s\S]+?)^~~~\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, diff: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string, diff: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
const encodedDiff = lz.compressToBase64(diff)
ctx.ignores.push([index, index + full.length])
return `<Monaco code-lz="${encoded}" diff-lz="${encodedDiff}" lang="${lang}" v-bind="${options}" />`
},
)
ctx.s.replace(
/^```(\w+?)\s*{monaco}\s*?({.*?})?\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Monaco code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`
},
)
ctx.s.replace(
/^```(\w+?)\s*{monaco-run}\s*?({.*?})?\s*?\n([\s\S]+?)^```/mg,
(full, lang = 'ts', options = '{}', code: string, index: number) => {
(full, lang = 'ts', options = '{}', code: string) => {
lang = lang.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Monaco runnable code-lz="${encoded}" lang="${lang}" v-bind="${options}" />`
},
)
Expand Down
6 changes: 3 additions & 3 deletions packages/slidev/node/syntax/transform/plant-uml.ts
@@ -1,11 +1,11 @@
import { encode as encodePlantUml } from 'plantuml-encoder'
import type { MarkdownTransformContext } from '@slidev/types'

export function transformPlantUml(ctx: MarkdownTransformContext, server: string) {
ctx.s.replace(/^```plantuml\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', content = '', index: number) => {
export function transformPlantUml(ctx: MarkdownTransformContext) {
const server = ctx.options.data.config.plantUmlServer
ctx.s.replace(/^```plantuml\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', content = '') => {
const code = encodePlantUml(content.trim())
options = options.trim() || '{}'
ctx.ignores.push([index, index + full.length])
return `<PlantUml :code="'${code}'" :server="'${server}'" v-bind="${options}" />`
})
}
6 changes: 4 additions & 2 deletions packages/slidev/node/syntax/transform/slot-sugar.ts
@@ -1,9 +1,12 @@
import type { MarkdownTransformContext } from '@slidev/types'
import { getCodeBlocks } from './utils'

export function transformSlotSugar(
ctx: MarkdownTransformContext,
) {
const linesWithNewline = ctx.s.original.split(/(\r?\n)/g)
const codeBlocks = getCodeBlocks(ctx.s.original)

const lines: string[] = []
for (let i = 0; i < linesWithNewline.length; i += 2) {
const line = linesWithNewline[i]
Expand All @@ -17,9 +20,8 @@ export function transformSlotSugar(
lines.forEach((line) => {
const start = offset
offset += line.length
if (ctx.isIgnored(start))
if (codeBlocks.isInsideCodeblocks(offset))
return

const match = line.match(/^::\s*([\w\.\-\:]+)\s*::(\s*)?$/)
if (match) {
ctx.s.overwrite(start, offset - match[2].length, `${prevSlot ? '\n\n</template>\n' : '\n'}<template v-slot:${match[1]}="slotProps">\n`)
Expand Down
7 changes: 4 additions & 3 deletions packages/slidev/node/syntax/transform/snippet.ts
Expand Up @@ -2,7 +2,7 @@

import path from 'node:path'
import fs from 'fs-extra'
import type { MarkdownTransformContext, ResolvedSlidevOptions } from '@slidev/types'
import type { MarkdownTransformContext } from '@slidev/types'
import { slash } from '@antfu/utils'

function dedent(text: string): string {
Expand Down Expand Up @@ -81,8 +81,9 @@ function findRegion(lines: Array<string>, regionName: string) {
*
* captures: ['/path/to/file.extension', '#region', 'language', '{meta}']
*/
export function transformSnippet(ctx: MarkdownTransformContext, options: ResolvedSlidevOptions, id: string) {
const slideId = (id as string).match(/(\d+)\.md$/)?.[1]
export function transformSnippet(ctx: MarkdownTransformContext) {
const options = ctx.options
const slideId = (ctx.id as string).match(/(\d+)\.md$/)?.[1]
if (!slideId)
return

Expand Down
22 changes: 22 additions & 0 deletions packages/slidev/node/syntax/transform/utils.ts
Expand Up @@ -2,6 +2,28 @@ export function normalizeRangeStr(rangeStr = '') {
return !rangeStr.trim() ? [] : rangeStr.trim().split(/\|/g).map(i => i.trim())
}

export function getCodeBlocks(md: string) {
const codeblocks = Array
.from(md.matchAll(/^```[\s\S]*?^```/mg))
.map((m) => {
const start = m.index!
const end = m.index! + m[0].length
const startLine = md.slice(0, start).match(/\n/g)?.length || 0
const endLine = md.slice(0, end).match(/\n/g)?.length || 0
return [start, end, startLine, endLine]
})

return {
codeblocks,
isInsideCodeblocks(idx: number) {
return codeblocks.some(([s, e]) => s <= idx && idx <= e)
},
isLineInsideCodeblocks(line: number) {
return codeblocks.some(([, , s, e]) => s <= line && line <= e)
},
}
}

/**
* Escape `{{` in code block to prevent Vue interpret it, #99, #1316
*/
Expand Down

0 comments on commit 867dee6

Please sign in to comment.