Slidev Monaco 代码运行器(Code Runners)自定义配置完全指南:为 `monaco-run` 扩展任意语言执行能力
Slidev 的 {monaco-run} 代码块默认即可在浏览器中直接运行 JavaScript 与 TypeScript,但内置的运行器是无沙箱的简单实现,适合演示、不适合承载任意语言的执行逻辑。本篇以 Configure Code Runners 文档为核心骨架,结合仓库内类型定义、客户端运行管线与虚拟模块源码,系统讲解如何通过 ./setup/code-runners.ts 为 Monaco Editor 注册自定义语言运行器、理解 Runner Context 与输出协议,并为运行器注入额外的模块依赖,最终让演示文稿获得"可运行、可演算"的交互式代码能力。
运行器机制概览:从 monaco-run 到自定义 Runner
在 Slidev 中,{monaco-run} 指令会把一个代码块变成一个带 "Run" 按钮的 Monaco 编辑器,代码执行结果会实时显示在代码块下方;修改代码后结果也会即时重新求值,还可用 {autorun:false} 关闭自动运行、用 showOutputAt 把输出挂接到指定点击步进。这一交互功能的说明详见 Monaco Runner。
在底层,{monaco-run} 编辑器的执行逻辑由"代码运行器(Code Runner)"承担。代码运行器是一个以语言 id(language id)为键、以 (code, ctx) => outputs 为值的函数映射,它负责把用户写下的代码变成可展示的输出结果。其核心类型定义位于 code-runner.ts:
export type CodeRunner = (code: string, ctx: CodeRunnerContext) => Awaitable<CodeRunnerOutputs>
export type CodeRunnerProviders = Record<string, CodeRunner>
Slidev 内置的 JavaScript / TypeScript 运行器已经在客户端入口预置好了。在 客户端运行器实现 中可以看到默认注册表:
const runners: Record<string, CodeRunner> = {
javascript: runTypeScript,
js: runTypeScript,
typescript: runTypeScript,
ts: runTypeScript,
}
runTypeScript 内部先使用 TypeScript 编译器把代码转译为 ES2022,再将静态 import 语句改写为动态 import,最终在浏览器端通过 new Function 构造并求值。需要特别注意的是:整个执行过程没有任何沙箱隔离——它运行在页面当前的 JavaScript 上下文中,只是替换了 console 为可捕获的输出记录器。这意味着内置运行器只适合运行可信代码,这一点在官方文档中亦有明确说明。
因此,当演示需要运行 Python、HTML、Vue SFC 乃至任何远程语言时,就需要通过 setup 文件注册自定义语言运行器,把代码送往远程服务器执行、交给 Web Worker 处理,或在客户端做任意自定义处理——Slidev 对运行器内部的实现方式完全不设限。
编写 ./setup/code-runners.ts:向运行器注册表添加语言
自定义运行器约定放在项目根目录的 ./setup/code-runners.ts(与 setup/monaco.ts、setup/shiki.ts 等 setup 文件遵循同一目录约定,可参见 Directory Structure)。文件通过从 @slidev/types 导入的 defineCodeRunnersSetup 工厂函数进行类型安全的声明,该工厂定义于 setups.ts。
官方文档给出的完整示例结构如下:
/* eslint-disable import/first */
declare const executePythonCodeRemotely: (code: string) => Promise<string>
declare const sanitizeHtml: (html: string) => string
// ---cut---
import { defineCodeRunnersSetup } from '@slidev/types'
export default defineCodeRunnersSetup(() => {
return {
async python(code, ctx) {
// Somehow execute the code and return the result
const result = await executePythonCodeRemotely(code)
return {
text: result
}
},
html(code, ctx) {
return {
html: sanitizeHtml(code)
}
},
// or other languages, key is the language id
}
})
要正确理解这份示例,关键在于抓住两点:
- 返回对象的键即语言 id。键
python、html对应代码块的语言标识符。也就是说,在幻灯片中写下```python {monaco-run}时,运行器会以"python"为键查找并调用你注册的处理器。 - 每个处理器接收两个参数:第一参数
code是编辑器中的源码字符串;第二参数ctx是下文的 Runner Context,提供选项与工具函数。
客户端如何合并自定义运行器
从源码可以确认自定义运行器与内置运行器的合并顺序与行为。在 客户端代码 中:
for (const setup of setups) {
const result = await setup(runners)
Object.assign(runners, result)
}
也就是说,运行器注册表先装载内置的 js/ts 运行器,再以 Object.assign 依次合并所有 setup 文件的返回结果。因此:
- 你的自定义键会新增语言能力,不会破坏内置语言;
- 若你想覆盖某内置语言(例如
typescript),直接用同名键返回即可,Object.assign会以 setup 结果为最终值; - 若想完全禁用运行某语言而无需自定义逻辑,你可以在 setup 文件中返回一个抛出可读错误信息的运行器,因为客户端在 run 函数 中会捕获所有异常并把
error展示为输出。
此外该 run 函数正是 ctx.run 的底层实现——它接受 (code, lang, options),其中 options 默认取编辑器传入的 runnerOptions ?? {}(见 CodeRunner.vue),并在找不到对应语言的运行器时抛出 Runner for language "${lang}" not found。
Runner Context:ctx 中可用的三样工具
每个运行器的第二个参数 ctx 是类型为 CodeRunnerContext 的运行上下文。官方文档与 code-runner.ts 中的定义完全一致,包含三个属性:
| 属性 | 类型 | 用途说明 |
|---|---|---|
options |
Record<string, unknown> |
通过 Monaco 代码块 meta 上的 runnerOptions prop 传入的选项(例如 {monaco-run} {runnerOptions:{...}} 中声明的内容),可用于向运行器传参;客户端在调用运行器时会将 props.runnerOptions ?? {} 注入其中 |
highlight |
(code, lang, options?) => string |
用 Slidev 已配置好的 Shiki 高亮器把代码渲染为带语法高亮的 HTML 字符串,内部委托给 eager highlighter 的 codeToHtml(见 code-runners.ts),适合在运行结果中嵌入代码片段 |
run |
(code, lang) => Promise<CodeRunnerOutputs> |
复用其它语言运行器执行代码:例如你的 Python 运行器内部想先用某预处理语言跑一遍,或想让 HTML 输出中嵌套一段 JavaScript 结果,都可直接调用 ctx.run 递归复用运行时管线 |
三者配合的典型场景是:用 options 决定执行策略(本地还是远程),用 highlight 让返回的文本带上高亮,用 run 编排多语言管道。
Runner Output 协议:运行器可以返回什么
运行器函数体可以返回文本、HTML、DOM 元素或错误信息,甚至可以返回数组、ref 或 getter。官方文档提示完整输出协议位于 packages/types/src/code-runner.ts,其实际定义拆分为以下几种输出形态:
export interface CodeRunnerOutputHtml {
html: string // 要渲染的 HTML
}
export interface CodeRunnerOutputDom {
element: HTMLElement // 要挂载的 DOM 元素
}
export interface CodeRunnerOutputError {
error: string // 要展示的错误信息
}
export interface CodeRunnerOutputText {
text: string // 要展示的文本
class?: string // 应用到该文本上的 class
highlightLang?: string // 文本的语法高亮语言
}
汇总后的类型是:
export type CodeRunnerOutputs =
| CodeRunnerOutputHtml
| CodeRunnerOutputError
| CodeRunnerOutputText
| CodeRunnerOutputTextArray // CodeRunnerOutputText[]
| CodeRunnerOutputDom
export type CodeRunnerOutput = /* 上述单条输出的联合 */
export type CodeRunnerOutputs = MaybeRefOrGetter<Arrayable<CodeRunnerOutput>>
使用时的几点重要约束:
- 返回
{ text: string }是最常见的形态。若代码执行产生多行日志,直接返回CodeRunnerOutputText[]数组即可逐行展示;内置的 js/ts 运行器正是把每一条console.log输出收集成数组后返回的(见 code-runners.ts,其中Set、Map、Error、嵌套对象等都会被序列化为可读文本)。 { html: string }不会被消毒。类型注释中明确写到:Slidev does NOT sanitize the HTML for you——HTML 必须来自可信来源,或在使用前自行做净化处理(如示例中的sanitizeHtml),否则存在注入风险。{ element: HTMLElement }支持挂载任意 DOM。客户端提供DomElement.vue来承载这类输出,是富交互场景(如实时渲染图形、嵌套组件)的出口。- 输出可以是响应式数据。
MaybeRefOrGetter表明你可以返回一个 Vueref或 getter,实现输出的动态更新;输出类型本身即允许你在运行器内返回ref数组。这使得诸如异步分批产出结果、流式展示成为可能。
关于运行器抛出异常的处理:客户端 run 函数会 try/catch 包裹所有运行器调用,把错误信息转为 { error: String(e) } 输出并写入控制台,因此运行器内部可以不捕获异常,直接交给框架兜底展示。
实战案例:为 Vue SFC 与远程 Python 定制运行器
从官方 Demo 学习"返回元素"的完整链路
仓库在 demo/vue-runner/slides.md 中演示了如何用 ```vue {monaco-run} 运行一个带 <script setup>、<template> 与交互按钮的 Vue 单文件组件。其对应的运行器实现位于 demo/vue-runner/setup/code-runners.ts,核心流程为:
- 用
@vue/compiler-sfc的parse+compileScript把 SFC 编译为脚本,inlineTemplate: true内联模板; - 将源码中的
import { ... } from 'vue'改写为从运行环境拿到的Vue对象解构(示例注释特别说明:这只是简化做法,不能处理来自其它包的 import); - 通过
new Function求值得到组件对象,再用Vue.createApp(component).mount(el)挂载到新建的<div>上; - 返回
{ element: el },交由 DOM 元素输出通道渲染。
该 Demo 明确标注了它是为证明 Slidev Code Runners 的可扩展性而写:不做样式隔离、不做沙箱、对其它包导入与边界情况没有处理,若要用于生产仍需自行加固。它演示的正是 Code Runner 输出协议中"返回可挂载 DOM 元素"这一高级用法,也是将运行器接入任意前端运行时(Web Component、Canvas、iframe 等)的通用模板。
结合"文本输出 + 远程执行"的通用形态
回到官方文档的 python 示例,它是大多数自定义运行器(尤其是连接远程解释器)的典型骨架:
async python(code, ctx) {
// 把代码 POST 到远程服务或交给 Web Worker,等待返回字符串
const result = await executePythonCodeRemotely(code)
return { text: result }
}
无论执行发生在远程服务器、本地 Web Worker 还是其它进程,只要最终产出 text/html/element 三者之一,Slidev 都会负责把它渲染到代码块下方。运行器本身是普通的 async 函数,因此网络请求、worker 消息等异步方案都天然支持。
Additional Runner Dependencies:为运行器注入额外模块
默认情况下,Slidev 会扫描 Markdown 源码中 Monaco 代码块、自动分析并打包运行所需的依赖。若你想为运行器(或其执行的用户代码)手动声明额外依赖,可在**首屏 frontmatter(headmatter)**中通过 monacoRunAdditionalDeps 配置,用法如下(该配置的默认值为 [],见 parser 默认配置 与 frontmatter 类型定义):
---
monacoRunAdditionalDeps:
- ./path/to/dependency
- lodash-es
---
这一配置需要特别留意两条约定:
- 路径相对于
snippets目录解析。这在虚拟模块 monaco-deps.ts 中得到印证:解析导入时以resolve(userRoot, './snippets/__importer__.ts')作为解析入口文件——即相对路径(如./path/to/dependency)是相对项目snippets/目录的,而不是相对幻灯片文件或根目录。 - 依赖名必须与代码中的导入说明符(specifier)完全一致。该虚拟模块会把列出的依赖逐一
import * as vendored{i}后聚合成一张以原始说明符为键的映射表;客户端运行时在 runJavaScript 中根据用户代码里的导入字符串精确查找deps[specifier],找不到会抛出Module not found: <specifier>并列出可用模块。举例而言,代码里写的是import { uniq } from 'lodash-es',配置里就必须写lodash-es(npm 包名),不能写别名或本地文件名。
此外可以注意到,该虚拟模块同时把 features.monaco.deps(自动扫描结果)与 monacoTypesAdditionalPackages(用于类型导入的额外包)一并合并进依赖映射,因此这两个 headmatter 项与 monacoRunAdditionalDeps 存在协同关系——后者的定位是"确保运行时可被 import 的模块",而不只是类型层面。
与运行器相关的全局选项与安全边界
配置好运行器后,还有两个与执行行为密切相关的细节值得一并掌握:
- 严格模式开关
monacoRunUseStrict。默认情况下,内置 Monaco 运行器的代码以严格模式("use strict")执行,这一点可在 Monaco 配置文档 中确认,其取值会直接拼入客户端 runJavaScript 构造的执行函数中。若你的代码依赖非严格模式行为,可在 headmatter 中设置monacoRunUseStrict: false。 - 功能开关
monaco与前置条件。Monaco 编辑器默认开启、按需打包;若完全不需要运行器能力,可将 headmatter 中的monaco设为false(或条件性的dev/build),虚拟模块 monaco-deps.ts 在monaco关闭时甚至不会生成任何依赖模块。完整编辑器配置请参考 Configure Monaco,其中已显式将 Code Runners 作为 Monaco 体系的扩展点进行引用。 - 安全边界务必牢记:内置运行器在浏览器中直接求值、无沙箱隔离;
html输出不被消毒;Demo 运行器也明确指出"NOT secure"。在幻灯片里放入运行器代码等同于放行代码执行,请确保内容可信,并对来自编辑器且将被渲染为 HTML 的输出做净化处理。
小结
Code Runners 是 Slidev Monaco 体系中最灵活的扩展点之一:通过 setup/code-runners.ts 声明式的注册、标准的 Runner Context(options/highlight/run)以及四种输出协议(text/html/error/element),你可以为任意语言接入远程执行、Worker 运算或前端运行时,配合 monacoRunAdditionalDeps 补齐模块依赖,即可让 {monaco-run} 代码块从"只能跑 JS/TS"进化为承载 Python、Vue SFC 乃至任意领域 DSL 的交互式实验台。若需进一步阅读,可直接查阅本指南的源码依据:Code Runner 输出类型、客户端运行管线、运行依赖虚拟模块,以及可运行的真实示例 vue-runner demo。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00