
Slidev {monaco-write} 可写 Monaco 编辑器幻灯片内编辑代码并保存回文件的完整链路【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev本篇讲解 Slidev 的「可写 Monaco 编辑器」monaco-write功能通过在代码导入语法后追加{monaco-write}指令将幻灯片中的 Monaco 编辑器与磁盘上的真实文件绑定直接在演示中修改代码并通过快捷键把变更写回文件。读完本文你将掌握该功能的完整用法、适用限制以及从 Markdown 解析、白名单注册到 WebSocket 落盘的保存回写机制。功能定位只读代码块升级为「文件级编辑器」普通代码块和{monaco}代码编辑器都只操作内存中的文本演示结束后内容即丢失。{monaco-write}解决了「边讲边改、改完即存」的场景它复用 Slidev 的 Import Code Snippets 语法把文件内容载入编辑器并额外把该文件注册为「可写目标」使 Monaco Editor 从展示工具变成真正的文件编辑器。根据该功能文档的 frontmatter此特性自v0.49.5起提供标签为codeblock与editor见 docs/features/monaco-write.md。基本用法在导入语法的元信息中加入{monaco-write} ./some-file.ts {monaco-write}此时幻灯片会渲染出一个 Monaco 编辑器其初始内容来自./some-file.ts的真实文件内容。在编辑器中修改代码后按CtrlSmacOS 为 CmdS即可将编辑器当前内容写回该文件。由于导入语法本身支持的能力以下用法同样有效详见 docs/features/import-snippet.md /snippets/snippet.ts {monaco-write} # 使用 指向包根目录 /src/app.ts ts {monaco-write} # 显式指定语言标识 /src/app.ts {monaco-write}{lines:true} # 叠加行号等其它代码块特性其中对应项目的根目录。官方文档建议把片段文件放在/snippets下以便与 Monaco 编辑器配合使用也支持相对路径导入。警告原文档强调修改会直接保存回文件本身因此使用monaco-write之前务必备份文件。编辑器端的保存动作保存行为由内置 Monaco 组件注册的一个编辑器 Action 实现。在 Monaco.vue 中editableEditor.addAction({ id: slidev-save, label: Save, keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], run: () { if (!isWritable.value || !import.meta.hot?.send) { console.warn([Slidev] this monaco editor is not writable, save action is ignored.) return } import.meta.hot.send(slidev:monaco-write, { file: props.writable!, content: editableEditor.getValue(), }) }, })三个关键事实可以从这段源码确认保存走的是 Vite HMR WebSocketimport.meta.hot.send(slidev:monaco-write, ...)依赖 Vite 开发服务器的 HMR 通道消息体是{ file, content }。isWritable有三重约束在 Monaco.vue 中const isWritable computed(() props.writable !props.readonly __DEV__)。即只有「带writable路径属性 非只读 开发环境__DEV__」同时满足时CtrlS 才生效生产构建或非serve场景下按保存键只会输出一条控制台警告。writable属性就是目标文件路径该属性由服务端解析阶段注入见下一节编辑器本身并不知道文件在哪只知道往谁身上写。服务端解析白名单注册与 Token 生成{monaco-write}的处理发生在 Markdown → Vue 的解析阶段。snippet.ts 中的 markdown-it 插件拦截导入语法后if (meta.includes({monaco-write})) { monacoWriterWhitelist.add(filepath) lang lang.trim() meta meta.replace({monaco-write}, ).trim() || {} 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 }这段逻辑说明了完整的「装配」过程文件路径被登记进白名单monacoWriterWhitelist一个模块级Setstring这是后续服务端授权写盘的前提文件内容用 lz-string 压缩为 Base64code-lz避免特殊字符破坏 HTML 属性也减小模板体积路径经过JSON.stringify(...).slice(1, -1)转义safeFilepath防止引号等字符破坏属性语法{monaco-write}从 meta 中剥离剩余 meta如{lines:true}原样透传给 Monaco 组件最终输出的是Monaco writable... code-lz... lang... v-bind... /组件调用——writable属性正是编辑器端判断「能否保存」的依据。落盘链路Vite 插件 双重校验真正执行写文件的是 monacoWrite.ts 中定义的 Vite 插件slidev:monaco-write在 vite/index.ts 注册export function createMonacoWriterPlugin({ userRoot }: ResolvedSlidevOptions): Plugin { return { name: slidev:monaco-write, apply: serve, configureServer(server) { server.ws.on(connection, (socket) { socket.on(message, async (data) { // ... if (json.type custom json.event slidev:monaco-write) { const { file, content } json.data if (!monacoWriterWhitelist.has(file)) { console.error([Slidev] Unauthorized file write: ${file}) return } const filepath path.resolve(userRoot, file) const rel path.relative(userRoot, filepath) if (rel.startsWith(..) || path.isAbsolute(rel)) { console.error([slidev] Refusing monaco write outside project root: ${file}) return } console.log([Slidev] Writing file:, filepath) await fs.writeFile(filepath, content, utf-8) } }) }) }, } }从源码结构可以确认以下安全与边界设计仅限开发模式apply: serve表明插件只在slidev dev启动的 Vite 开发服务器中生效slidev build产物不具备写回能力——与编辑器端__DEV__的约束相互印证。白名单校验消息中的file必须精确命中解析阶段登记的monacoWriterWhitelist否则直接拒绝并打印Unauthorized file write。客户端伪造一个任意文件路径是无效的只有幻灯片里实际用{monaco-write}导入过的文件才能被写。路径穿越防护即使路径在理论上通过了白名单path.resolve(userRoot, file)后若相对结果以..开头或是绝对路径同样被拒绝保证写入永远落在项目根目录之内。落盘是全文覆盖fs.writeFile(filepath, content, utf-8)直接用编辑器当前全文替换文件内容没有 diff 或合并逻辑。这也解释了为何原文档反复强调先备份。使用限制与注意事项结合文档说明与源码事实使用monaco-write前需要明确的前提与限制只适用于slidev devserve 场景写入插件apply: serve编辑器端又要求__DEV__与 HMR 通道因此slidev build、导出 PDF 或部署后的静态页面中编辑器会退化为只读展示。写回是整文件覆盖fs.writeFile直接以编辑器内容替换磁盘文件无版本管理、无撤销。原文档明确建议「使用此功能前务必备份文件」。与 region 导入组合要格外小心 file.ts#region {monaco-write}只会把 region 内的代码载入编辑器但保存时写入的是整份文件内容。从源码链路内容解析见 snippet.ts 的 region 截取逻辑写盘见 monacoWrite.ts可以推断这样组合会导致 region 之外的原有代码被覆盖不建议使用。编辑器可叠加普通 Monaco 配置{monaco-write}之外的 meta 原样透传例如 Monaco Editor 文档 中的{height:auto}、{lines:true}等均可继续生效编辑器选项层面的定制可参考 配置 Monaco。只读模式下保存被忽略若代码块带有readonly标记isWritable为 falseCtrlS 会打印this monaco editor is not writable, save action is ignored.警告而不做任何事。保存动作的完整链路总结把前后端两侧串起来一次 CtrlS 保存的完整调用链为解析阶段 ./some-file.ts {monaco-write}被 markdown-it 插件识别./some-file.ts登记进monacoWriterWhitelist文件内容经 lz-string 压缩后生成Monaco writable... code-lz...组件调用snippet.ts编辑阶段Monaco.vue懒加载 monaco用codeLz解压初始化 model并注册slidev-save快捷键动作Monaco.vue发送阶段CtrlS 触发import.meta.hot.send(slidev:monaco-write, { file, content })经 Vite HMR WebSocket 到达开发服务器校验落盘slidev:monaco-write插件校验事件类型custom、白名单命中、路径未越出项目根目录后fs.writeFile覆盖写回文件monacoWrite.ts。这条「白名单 路径校验 serve-only」的设计在让幻灯片具备真实代码编辑能力的同时把写盘权限严格限定在幻灯片作者显式声明的文件集合内。对于「live coding」式的技术演示这正是既安全又实用的实现方式。相关功能Import Code Snippets文件导入语法是monaco-write的语法基础Monaco Editor{monaco}/{monaco-diff}编辑器及其高度、行号等选项Configure Monaco通过项目配置定制 Monaco 编辑器行为。【免费下载链接】slidevPresentation Slides for Developers项目地址: https://gitcode.com/GitHub_Trending/sl/slidev创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考