代码块 Runtime
本页说明 CodeBlockNode 使用的可选 stream-diffs runtime。2.0 移除了 stream-monaco 回退,stream-diffs 是唯一增强型代码块 surface;受支持的配置统一通过与 renderer 无关的 CodeBlockOptions API 暴露。
安装
pnpm add stream-diffs不需要 worker plugin,也不需要额外导入包专用 CSS。
如果未安装 stream-diffs,loader 会保留 <pre> 回退,以纯文本形式渲染代码块,不启用增强 surface。
Runtime 职责边界
markstream-vue stream-diffs
--------------- ------------
CodeBlockNode controller + DOM surface
- Vue props / unmount - HTMLElement target
- 流式结束判断 - code 或 diff 数据
- 可视区域判断 - File / FileDiff 渲染
- 标题和工具栏 - syntax highlightingstream-diffs 根入口与框架无关:它不导入 Vue,也不拥有 Vue lifecycle。包内另有 stream-diffs/vue 这个可选便捷入口,供直接使用 Vue 的业务接入;markstream-vue 当前不使用该入口。
CodeBlockNode 切换流程
CodeBlockNode 只有一条稳定的视觉路径:
- code 正在流式输出时,Vue 渲染共享
PreCodeBlock;renderCodeBlocksAsPre直接使用同一个组件和同一套视觉默认参数。 - code 结束且进入可视区域后,组件动态加载
stream-diffs根 runtime,并在既有容器中挂载一个 File 或 FileDiff surface。 - 组件把当前 theme 应用到 surface;surface ready 后才移除临时
<pre>。显示前,两层已共享字体指标、padding、gutter 几何、overflow 与主题背景;流式普通代码块不得继承旧的恢复高度 floor。 - 组件卸载时,由 Vue 适配层 dispose controller。
结束态、可见性与卸载都是 CodeBlockNode 的职责,并不是 stream-diffs 的生命周期 hook。
CodeBlockShell 负责标题和操作栏。创建 File surface 时会关闭内部 data-diffs-header,DOM 始终只有一个 header。
主题
theme 可传已注册的 string 名称或 { dark, light };themes 是 runtime 可用的 [dark, light] 名称对。CodeBlockNode 会把主题变化传给已挂载的 surface,不会重建 Vue 组件。
Monaco JSON theme object 不会在 2.0 中直接改名。自定义主题需先调用 stream-diffs/pierre 的 registerCustomTheme,再传入注册名称。
Options 透传
直接使用 CodeBlockNode,或通过顶层 NodeRenderer / MarkdownRender,都可以传 codeBlockOptions。Vue 3、React、Octane、Svelte、Angular 与 Vue 2 使用同一套 CodeBlockOptions 约定。
受支持的 surface 包括:
- 宿主管理的排版与布局:
fontSize、lineHeight、fontFamily、number 类型且单位为 px 的maxHeight、number 类型且单位为 px 的上下对称padding、tabSize; - File 配置,例如
disableLineNumbers、overflow、高亮长度限制,以及虚拟化/高亮器控制;overflow: 'wrap'会通过兼容 runtime 映射为wordWrap: 'on',overflow: 'scroll'映射为wordWrap: 'off';默认值是overflow: 'wrap'。 - FileDiff 布局、indicator、未变化区域折叠与 line diff 控制;
- line/token 交互、selection callback、annotation、
onController与workerManager。
Markstream 会把宿主管理字段同时应用到流式 <pre> 与最终 surface,再把其余受支持字段传给 stream-diffs。主题、语言/内容、流式状态、唯一 header、挂载/显示时机与释放仍由宿主管理,优先于冲突的 runtime 值。
可选预热
如果路由确定会出现已经完成且位于可视区域的代码块,可以在空闲时预热 module:
import { preloadCodeBlockRuntime } from 'markstream-vue'
void preloadCodeBlockRuntime()这个调用只预热可选 module;不会创建 surface、不会完成仍在流式输出的代码块,也不会绕过结束态和可见性 gate。
Worker 池(离主线程高亮)
默认情况下 stream-diffs 在主线程用 Shiki 高亮。渲染数万行代码时,整个高亮过程会阻塞 UI。@pierre/diffs 提供了实验性的 WorkerPoolManager,可以把 Shiki 分词移到 Web Worker;markstream-vue 会把注入的 worker 池作为 workerManager runtime 选项转发给每一个增强 surface。
markstream-vue 不会自己打包或创建 worker——worker 资产与打包器强相关,在跨打包器库内部实现很脆弱。正确做法是宿主应用用自己的打包器创建 worker 池,然后注入一次:
import { getOrCreateWorkerPoolSingleton } from '@pierre/diffs/worker'
// vite.config.ts / 任意在代码块渲染前执行一次的模块
import DiffsWorker from '@pierre/diffs/worker/worker.js?worker'
import { setStreamDiffsWorkerPool } from 'markstream-vue'
const pool = getOrCreateWorkerPoolSingleton({
poolOptions: {
poolSize: 4,
workerFactory: () => new DiffsWorker(),
},
highlighterOptions: {
// 与代码块使用的主题保持一致
theme: { dark: 'pierre-dark', light: 'pierre-light' },
},
})
setStreamDiffsWorkerPool(pool)请使用与你的打包器对应的 worker 导入方式(webpack 5、Rollup 等)。同一个 manager 会在所有代码块之间共享;生命周期与终止仍由应用控制:
import { clearStreamDiffsWorkerPool, terminateStreamDiffsWorkerPool } from 'markstream-vue'
terminateStreamDiffsWorkerPool() // 调用 pool.terminate()(如果可用)并清除
clearStreamDiffsWorkerPool() // 仅清除,不终止(宿主保留所有权)行为说明:
- 未注入 pool — 高亮保持主线程,与之前完全一致。这是默认行为。
- 主题同步 —
CodeBlockNode会在每次主题变化时通过setRenderOptions把当前主题同步给 pool,保证 worker 生成的 token 与请求主题一致,宿主无需额外接线。 - 按块覆盖 — 单个块传入
codeBlockOptions.workerManager时,优先于全局注入的 pool。 - 自动回退 — 如果 pool 报告自己不可用(
isWorkingPool() === false),@pierre/diffs会自动回退到主线程高亮。损坏或已终止的 pool 永远不会阻塞渲染。
Diff 交互
diff block 使用相同的适配边界。未提供对应 codeBlockOptions 时,增强 diff surface 使用 stream-diffs 默认值;可通过 diffStyle、expandUnchanged、collapsedContextThreshold、hunkSeparators、lineDiffType 与 parseDiffOptions 配置布局和折叠。
fallback 与最终 surface 使用相同的 collapsedContextThreshold 判断:只有未变化区域的隐藏行数大于阈值时才折叠。unified 与 split diff fallback 都根据每个源文本真实的末尾换行状态生成 No newline at end of file,使用与最终 surface 一致的中性 metadata 前景色与背景色,并保持相同可见高度。新增/删除行 fill 在每个视觉区域只合成一次,使最终颜色与 enhanced surface 一致。换行模式下,内容背景、gutter 标记与行号背景覆盖测量后的完整逻辑行高度;在换行与滚动模式之间切换时,会在下一次布局绘制前清除旧的同步行高。最终 host 必须完整保留 maxHeight 以下的所有行,并在超过限制时使用可滚动 overflow;不得把超出的 diff 行隐藏在更大的 shell 内。