CodeBlockNode 组件
CodeBlockNode 是库中用于渲染富交互代码块的组件。对于需要高亮、File/Diff surface 与交互的场景,推荐安装 stream-diffs。Vue 组件负责流式结束、可见性和卸载;stream-diffs 根 runtime 只负责与框架无关的 DOM surface。
快速概览
- 增强模式(安装
stream-diffs)— 结束态 File/FileDiff surface,支持语法高亮与 diff 交互 - 降级模式 — 未安装
stream-diffs时回退为纯<pre><code>渲染
Props
完整签名请参阅 src/types/component-props.ts。关键 props:
node— code_block 节点(必需)loading、stream、isShowPreviewcodeBlockOptions— 与 renderer 无关的排版、布局、File/FileDiff、交互、annotation 与 callback 配置;NodeRenderer/MarkdownRender顶层也提供同名 prop。theme— 已注册的 string 名称或{ dark, light };themes是要加载的[dark, light]名称对。- 头部控制:
showHeader、showCollapseButton、showCopyButton、showExpandButton、showPreviewButton、showFontSizeButtons、showTooltips - HTML preview sandbox:
htmlPreviewAllowScripts默认false,htmlPreviewSandbox可直接覆盖 iframe sandbox token
内置 inline HTML preview 默认使用 sandbox="",因此不可信预览文档不会默认执行脚本,也不会继承宿主页面 origin。htmlPreviewSandbox 的优先级高于 htmlPreviewAllowScripts;传入 htmlPreviewSandbox="" 会保留完整 sandbox,不传 htmlPreviewSandbox 时由 htmlPreviewAllowScripts 控制,而 null 这类无效非 string override 会回退到安全默认值。只有在可信 demo 场景下才建议显式开启 htmlPreviewAllowScripts;对于不可信预览内容,不要把 allow-scripts 和 allow-same-origin 组合在一起。
未提供 codeBlockOptions override 时,最终 enhanced diff 的默认行为:
diffStyle: 'split'expandUnchanged: falsecollapsedContextThreshold: 5hunkSeparators: 'line-info'parseDiffOptions: { context: 2 }
fallback 与最终 surface 使用同一组折叠配置;流式状态不会覆盖用户设置的 expandUnchanged。即使提供 codeBlockOptions,主题、内容/语言、header、挂载/显示时机与释放仍由宿主管理。
overflow 同时控制 fallback <pre> 和 enhanced surface。默认值是 'wrap';需要不换行并使用横向滚动时传入 'scroll'。视觉换行不会增加逻辑 diff 行,也不会改变内置 - / + 统计。
Diff 代码块的内置 header 现在也会显示 - / + 行数统计。
Slots 插槽
header-left— 替换左侧头部header-right— 替换右侧头部loading— 自定义流式禁用时的占位符
Emits 事件
copy(text: string)— 点击复制时触发previewCode(payload)— 仅在你监听@preview-code时才会触发;payload 为{ node, artifactType, artifactTitle, id }
示例
安装并运行
pnpm add stream-diffs基础示例
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'js',
code: 'console.log(1)',
raw: 'console.log(1)',
} satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" />
</template>替换头部并隐藏复制按钮
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'js',
code: 'console.log(1)',
raw: 'console.log(1)',
} satisfies CodeBlockNodeProps['node']
function runSnippet() {}
</script>
<template>
<CodeBlockNode :node="node" :show-copy-button="false">
<template #header-left>
<div class="flex items-center">
自定义左侧
</div>
</template>
<template #header-right>
<button @click="runSnippet">
运行
</button>
</template>
</CodeBlockNode>
</template>配置代码 surface
<script setup lang="ts">
import type { CodeBlockNodeProps, CodeBlockOptions } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'ts',
code: 'const answer = 42',
raw: 'const answer = 42',
} satisfies CodeBlockNodeProps['node']
const codeBlockOptions: CodeBlockOptions = {
fontSize: 13,
lineHeight: 20,
overflow: 'wrap',
disableLineNumbers: true,
enableLineSelection: true,
}
</script>
<template>
<CodeBlockNode :node="node" :code-block-options="codeBlockOptions" />
</template>fontSize、lineHeight、fontFamily、number 类型且单位为 px 的 maxHeight、number 类型且单位为 px 的上下对称 padding 与 tabSize 由宿主管理,以确保 fallback 与最终 surface 的几何一致。其余受支持字段传给 stream-diffs;header 与 toolbar 继续使用独立的组件 props。
自定义加载占位符
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = {
type: 'code_block',
language: 'ts',
code: 'console.log("loading")',
raw: 'console.log("loading")',
} satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" :stream="false" :loading="true">
<template #loading="{ loading, stream }">
<div v-if="loading && !stream">
正在加载编辑器资源…
</div>
</template>
</CodeBlockNode>
</template>主题切换
CodeBlockNode 支持基于深色/浅色模式的自动主题切换。使用 @vueuse/core 的 useDark composable 来追踪主题状态,并将主题名称传递给 MarkdownRender 或 CodeBlockNode。
在独立的 Vue 应用中使用 @vueuse/core
<script setup lang="ts">
import { useDark, useToggle } from '@vueuse/core'
import MarkdownRender from 'markstream-vue'
const isDark = useDark() // Ref<boolean> 响应式系统/主题偏好
const toggleDark = useToggle(isDark)
const content = '# 示例\n\n```js\nconsole.log("深色模式")\n```'
// 深色/浅色主题对
const themes = [
'vitesse-dark',
'vitesse-light',
] as const
</script>
<template>
<div>
<button @click="toggleDark()">
切换主题
</button>
<MarkdownRender
:is-dark="isDark"
:code-block-props="{ theme: { dark: 'vitesse-dark', light: 'vitesse-light' } }"
:themes="themes"
:content="content"
/>
</div>
</template>VitePress 集成
对于 VitePress,使用 VitePress 内置的 useData() 中的 isDark:
// docs/.vitepress/theme/composables/useDark.ts
import { useData } from 'vitepress'
/**
* VitePress 主题 composable 用于深色模式
* 使用 VitePress 内置的 useData() 获取 isDark
*/
export function useDark() {
const { isDark } = useData()
return isDark
}<!-- 在任意 .md 文件或组件中 -->
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import { useDark } from '../../.vitepress/theme'
const isDark = useDark()
const content = '# 示例\n\n```js\nconsole.log("深色模式")\n```'
const themes = [
'vitesse-dark',
'vitesse-light',
] as const
</script>
<template>
<MarkdownRender
:is-dark="isDark"
:code-block-props="{ theme: { dark: 'vitesse-dark', light: 'vitesse-light' } }"
:themes="themes"
:content="content"
/>
</template>工作原理:
theme prop 支持固定主题或明暗配对:
<!-- 自动切换明暗(推荐) -->
<CodeBlockNode :theme="{ light: 'vitesse-light', dark: 'vitesse-dark' }" />
<!-- 固定主题(忽略 isDark) -->
<CodeBlockNode theme="monokai" />使用 { dark, light } 配对时,组件根据 isDark prop 自动切换。
themes prop 只接受一个 [深色, 浅色] 二元主题对。旧 Monaco JSON theme object 在 2.0 中不是有效的 theme 值;先调用 stream-diffs/pierre 的 registerCustomTheme,再传入注册名称。
向后兼容:
darkTheme/lightThemeprops 仍然可用但已废弃。推荐使用统一的themeprop。
CodeBlockNode 的关键差异:
| Prop | 直接使用 CodeBlockNode | 通过 MarkdownRender |
|---|---|---|
isDark | 直接传给 <CodeBlockNode :is-dark="isDark" /> | 通过 <MarkdownRender :is-dark="isDark" /> 传入并自动转发 |
| 主题 | :theme="{ dark: 'vitesse-dark', light: 'vitesse-light' }" | :code-block-props="{ theme: { dark: 'vitesse-dark', light: 'vitesse-light' } }" |
| 主题对 | :themes="['vitesse-dark', 'vitesse-light']" | :themes="['vitesse-dark', 'vitesse-light']" |
注意事项
- CodeBlock 头部 API 在 codeblock-header 中有文档说明(包含替换头部和自定义加载占位符的示例)。
CodeBlockNode和MermaidBlockNode的copy事件 payload 不同:CodeBlockNode触发copy(text: string),而MermaidBlockNode触发copy(ev: MermaidBlockEvent<{ type: 'copy'; text: string }>)(支持preventDefault())。- 大代码块(数万行)默认在主线程做 Shiki 分词。要移到 Web Worker,可通过
setStreamDiffsWorkerPool(...)注入上游@pierre/diffs的WorkerPoolManager;CodeBlockNode会把它作为workerManager选项转发,并保持主题同步。未注入 pool(或 pool 报告不可用)时自动回退主线程高亮。接入方式见 Code Block Runtime。
快速尝试 — 简单的行内用法示例:
<script setup lang="ts">
import type { CodeBlockNodeProps } from 'markstream-vue'
import { CodeBlockNode } from 'markstream-vue'
const node = { type: 'code_block', language: 'js', code: 'console.log("hello")', raw: 'console.log("hello")' } satisfies CodeBlockNodeProps['node']
</script>
<template>
<CodeBlockNode :node="node" />
</template>