Conversation
❌ Deploy Preview for slidev failed.
|
@slidev/client
create-slidev
create-slidev-theme
@slidev/parser
@slidev/cli
@slidev/types
commit: |
There was a problem hiding this comment.
Pull request overview
Refactors Slidev’s Markdown transformation pipeline by moving regex-based transformers into markdown-exit/markdown-it-style plugins and introducing a dedicated codeblock transformer hook, enabling robust handling of indented fences and preventing Slidev syntax transformations inside code blocks.
Changes:
- Replaces regex-based Markdown transformers with markdown-it plugins + a new
CodeblockTransformerAPI. - Adds codeblock-level transformation pipeline (Mermaid / PlantUML / Monaco / Magic Move / wrapper) integrated into Markdown rendering.
- Updates/rewrites tests to cover new plugin-based behavior and indented code blocks.
Reviewed changes
Copilot reviewed 50 out of 53 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/utils.test.ts | Switches Markdown parser in tests from markdown-it to markdown-exit. |
| test/transform.test.ts | Removes legacy regex-transform snapshot tests (replaced by new plugin/codeblock tests elsewhere). |
| test/transform-magic-move.test.ts | Removes legacy Magic Move transform test (now covered via codeblock pipeline/integration tests). |
| test/transform-all.test.ts | Removes legacy “transform-all” snapshot test (superseded by integration tests). |
| test/snapshots/transform.test.ts.snap | Removes snapshots tied to deleted legacy transform tests. |
| test/snapshots/transform-all.test.ts.snap | Removes snapshots tied to deleted legacy transform tests. |
| packages/vscode/syntaxes/slidev.example.md | Updates example to include indented fenced block formatting. |
| packages/vscode/src/views/annotations.ts | Aligns codeblock line annotations with detected indentation (indent becomes numeric column). |
| packages/types/src/transform.ts | Introduces CodeblockTransformContext / CodeblockTransformer and helper definitions. |
| packages/types/src/setups.ts | Extends transformers setup return type to support codeblocks transformers; marks some legacy arrays deprecated. |
| packages/slidev/node/vite/markdown.ts | Hooks new transformers setup + passes codeblock transformers into markdown-it plugin setup. |
| packages/slidev/node/vite/loaders.ts | Updates imports to new syntax module locations. |
| packages/slidev/node/syntax/utils.ts | Moves shared helpers (normalizeRangeStr, escapeVueInCode) into syntax-level utilities. |
| packages/slidev/node/syntax/transform/utils.ts | Removes legacy transform utilities (code/comment block scanning). |
| packages/slidev/node/syntax/transform/snippet.ts | Removes legacy regex-based snippet transformer (replaced with markdown-it plugin). |
| packages/slidev/node/syntax/transform/slot-sugar.ts | Removes legacy regex-based slot sugar transformer (replaced with markdown-it plugin). |
| packages/slidev/node/syntax/transform/plant-uml.ts | Removes legacy regex-based PlantUML transformer (replaced with codeblock transformer). |
| packages/slidev/node/syntax/transform/monaco.ts | Removes legacy regex-based Monaco transformer (replaced with codeblock transformer). |
| packages/slidev/node/syntax/transform/mermaid.ts | Removes legacy regex-based Mermaid transformer (replaced with codeblock transformer). |
| packages/slidev/node/syntax/transform/magic-move.ts | Removes legacy regex-based Magic Move transformer (replaced with codeblock transformer). |
| packages/slidev/node/syntax/transform/katex-wrapper.ts | Removes legacy KaTeX wrapper transformer (moved into markdown-it KaTeX plugin behavior). |
| packages/slidev/node/syntax/transform/index.ts | Removes legacy getMarkdownTransformers pipeline (now plugin-based). |
| packages/slidev/node/syntax/transform/in-page-css.ts | Removes legacy in-page CSS transformer (replaced with scoped-style plugin). |
| packages/slidev/node/syntax/transform/code-wrapper.ts | Removes legacy code wrapper transformer (replaced with codeblock wrapper transformer). |
| packages/slidev/node/syntax/snippet.ts | Adds markdown-it snippet import rule (supports indentation + avoids codeblock transformation). |
| packages/slidev/node/syntax/snippet.test.ts | Adds unit tests for snippet import including indented contexts and codeblock non-transformation. |
| packages/slidev/node/syntax/slot-sugar.ts | Adds markdown-it slot marker compilation pass. |
| packages/slidev/node/syntax/slot-sugar.test.ts | Adds unit tests for slot sugar + “no transform in code block”. |
| packages/slidev/node/syntax/shiki.test.ts | Adds minimal shiki plugin integration test. |
| packages/slidev/node/syntax/scoped.ts | Adds HTML renderer wrapper to auto-add scoped to <style> tags outside code blocks. |
| packages/slidev/node/syntax/scoped.test.ts | Adds tests for scoped-style behavior and codeblock non-transformation. |
| packages/slidev/node/syntax/markdown-it/markdown-it-v-drag.ts | Hardens source map lookup for missing env.id. |
| packages/slidev/node/syntax/markdown-it/markdown-it-shiki.ts | Removes escapeVueInCode postprocess hook (now handled by wrapper transformer). |
| packages/slidev/node/syntax/markdown-it/markdown-it-katex.ts | Refactors KaTeX parsing/rendering and adds wrapper/ranges support in renderer. |
| packages/slidev/node/syntax/markdown-it/index.ts | Removes legacy markdown-it plugin index (replaced by packages/slidev/node/syntax/index.ts). |
| packages/slidev/node/syntax/link.test.ts | Adds unit tests for internal/external link rendering. |
| packages/slidev/node/syntax/katex.test.ts | Adds unit tests for inline/block KaTeX + no-transform in code blocks. |
| packages/slidev/node/syntax/integration.test.ts | Adds end-to-end integration test covering combined syntax and “no transform inside code blocks”. |
| packages/slidev/node/syntax/index.ts | New unified markdown-it plugin setup including codeblock transform pipeline. |
| packages/slidev/node/syntax/escape-code.test.ts | Adds unit test for inline code Vue escaping. |
| packages/slidev/node/syntax/drag.test.ts | Adds unit tests for v-drag component/directive transformation. |
| packages/slidev/node/syntax/codeblock/wrapper.ts | Adds CodeBlockWrapper codeblock transformer (ranges/title/options parsing + highlight delegation). |
| packages/slidev/node/syntax/codeblock/plant-uml.ts | Adds PlantUML codeblock transformer. |
| packages/slidev/node/syntax/codeblock/monaco.ts | Adds Monaco codeblock transformer (diff/run support). |
| packages/slidev/node/syntax/codeblock/mermaid.ts | Adds Mermaid codeblock transformer. |
| packages/slidev/node/syntax/codeblock/magic-move.ts | Adds Magic Move codeblock transformer for quadruple-fenced blocks. |
| packages/slidev/node/syntax/codeblock/index.ts | Installs fence renderer interception and executes codeblock transformer chain. |
| packages/slidev/node/setups/transformers.ts | Aggregates codeblocks transformers in setup returns. |
| packages/slidev/node/commands/shared.ts | Updates link plugin import path to new syntax module location. |
| packages/client/builtin/CodeBlockWrapper.vue | Changes title default from undefined to empty string. |
| docs/custom/config-transformers.md | Updates docs for new transformer APIs and codeblock transformer usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
docs/custom/config-transformers.md
Outdated
| // This applies before the Markdown is parsed, per slide | ||
| pre: [mySyntax], | ||
| // This applies per Markdown code block | ||
| codeblock: [myCodeblock], |
There was a problem hiding this comment.
The docs example uses codeblock, but the implemented setup return type and aggregation logic use codeblocks (plural). As written, the sample won’t register codeblock transformers. Update the docs to use the correct key (codeblocks) to match TransformersSetupReturn and setupTransformers().
| codeblock: [myCodeblock], | |
| codeblocks: [myCodeblock], |
| const [, options] = match | ||
| const optionsProp = options ? `v-bind="${options}"` : '' | ||
| const encoded = encodePlantUml(code.trim()) | ||
| return `<PlantUml ${optionsProp} code="${encoded}" server=${JSON.stringify(plantUmlServer)} />` |
There was a problem hiding this comment.
When plantUmlServer is undefined, JSON.stringify(plantUmlServer) yields undefined, producing server=undefined in the generated markup. This passes the literal string "undefined" to Vue and can break downstream expectations. Prefer omitting the server attribute when not configured, or bind it explicitly (e.g., :server=${JSON.stringify(plantUmlServer)}) and only include it when defined.
| return `<PlantUml ${optionsProp} code="${encoded}" server=${JSON.stringify(plantUmlServer)} />` | |
| const serverProp = plantUmlServer === undefined ? '' : ` :server=${JSON.stringify(plantUmlServer)}` | |
| return `<PlantUml ${optionsProp} code="${encoded}"${serverProp} />` |
| const encoded = lz.compressToBase64(content) | ||
|
|
||
| const token = state.push('html_block', '', 0) | ||
| token.content = `<Monaco writable="${filepath}" code-lz="${encoded}" lang="${lang}" v-bind="${meta}" />\n` |
There was a problem hiding this comment.
The filepath is interpolated directly into an HTML/Vue attribute (writable=\"${filepath}\") without escaping. A path containing quotes or angle brackets can break the attribute and potentially inject additional markup. Use JSON.stringify(filepath) (as the previous implementation did) or otherwise escape attribute values before interpolation.
| const encoded = lz.compressToBase64(content) | |
| const token = state.push('html_block', '', 0) | |
| token.content = `<Monaco writable="${filepath}" code-lz="${encoded}" lang="${lang}" v-bind="${meta}" />\n` | |
| const safeFilepath = JSON.stringify(filepath).slice(1, -1) | |
| const encoded = lz.compressToBase64(content) | |
| const token = state.push('html_block', '', 0) | |
| token.content = `<Monaco writable="${safeFilepath}" code-lz="${encoded}" lang="${lang}" v-bind="${meta}" />\n` |
|
|
||
| const monacoEnabled = config.monaco === true || config.monaco === mode | ||
| if (!monacoEnabled) { | ||
| return renderHighlighted({ info: `${lang} ${rest}` }) |
There was a problem hiding this comment.
If Monaco is disabled, this returns the raw highlighted fence HTML and short-circuits the transformer chain, meaning the normal CodeBlockWrapper transformer will never run for {monaco...} fences. Previously, {monaco} markers were removed and the code block still went through the standard wrapper pipeline. Consider returning undefined/null here (so the wrapper transformer can handle the block) and ensuring the {monaco...} marker doesn’t get mis-parsed as line ranges (see wrapper regex comment).
| return renderHighlighted({ info: `${lang} ${rest}` }) | |
| return |
| import { defineCodeblockTransformer } from '@slidev/types' | ||
| import { escapeVueInCode, normalizeRangeStr } from '../utils' | ||
|
|
||
| const RE_BLOCK_INFO = /^([\w'-]+)?(?:[ \t]*|[ \t][ \w\t'-]*)(?:\[([^\]]*)\])?[ \t]*(?:\{([\w*,|-]+)\}[ \t]*(\{[^}]*\})?([^\r\n]*))?/ |
There was a problem hiding this comment.
The range capture \\{([\\w*,|-]+)\\} currently accepts arbitrary word characters, so markers like {monaco} can be misinterpreted as a line-range string and end up in :ranges (e.g. ['monaco']), which can break range parsing/highlighting. Tighten the range pattern to match only valid range syntax (typically digits plus ,|-*), so non-range meta markers aren’t treated as ranges.
| const RE_BLOCK_INFO = /^([\w'-]+)?(?:[ \t]*|[ \t][ \w\t'-]*)(?:\[([^\]]*)\])?[ \t]*(?:\{([\w*,|-]+)\}[ \t]*(\{[^}]*\})?([^\r\n]*))?/ | |
| const RE_BLOCK_INFO = /^([\w'-]+)?(?:[ \t]*|[ \t][ \w\t'-]*)(?:\[([^\]]*)\])?[ \t]*(?:\{([\d,|\-*]+)\}[ \t]*(\{[^}]*\})?([^\r\n]*))?/ |

This PR refactors the Markdown transformation pipeline. All regex-based transformers are now markdown-it plugins, which are more robust and efficient.
A little breaking change since custom transformers that depends on transformation orders may no longer work correctly.
This PR also adds support for indented code blocks:
which should render to
item
lineAnd avoids transforming Slidev syntax in code blocks (close #2463):