首页
/ Slidev Monaco 代码运行器(Code Runners)自定义配置完全指南:为 `monaco-run` 扩展任意语言执行能力

Slidev Monaco 代码运行器(Code Runners)自定义配置完全指南:为 `monaco-run` 扩展任意语言执行能力

2026-09-07 20:54:53作者:卓艾滢Kingsley

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.tssetup/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。键 pythonhtml 对应代码块的语言标识符。也就是说,在幻灯片中写下 ```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,其中 SetMapError、嵌套对象等都会被序列化为可读文本)。
  • { html: string } 不会被消毒。类型注释中明确写到:Slidev does NOT sanitize the HTML for you——HTML 必须来自可信来源,或在使用前自行做净化处理(如示例中的 sanitizeHtml),否则存在注入风险。
  • { element: HTMLElement } 支持挂载任意 DOM。客户端提供 DomElement.vue 来承载这类输出,是富交互场景(如实时渲染图形、嵌套组件)的出口。
  • 输出可以是响应式数据MaybeRefOrGetter 表明你可以返回一个 Vue ref 或 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,核心流程为:

  1. @vue/compiler-sfcparse + compileScript 把 SFC 编译为脚本,inlineTemplate: true 内联模板;
  2. 将源码中的 import { ... } from 'vue' 改写为从运行环境拿到的 Vue 对象解构(示例注释特别说明:这只是简化做法,不能处理来自其它包的 import);
  3. 通过 new Function 求值得到组件对象,再用 Vue.createApp(component).mount(el) 挂载到新建的 <div> 上;
  4. 返回 { 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
---

这一配置需要特别留意两条约定:

  1. 路径相对于 snippets 目录解析。这在虚拟模块 monaco-deps.ts 中得到印证:解析导入时以 resolve(userRoot, './snippets/__importer__.ts') 作为解析入口文件——即相对路径(如 ./path/to/dependency)是相对项目 snippets/ 目录的,而不是相对幻灯片文件或根目录。
  2. 依赖名必须与代码中的导入说明符(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.tsmonaco 关闭时甚至不会生成任何依赖模块。完整编辑器配置请参考 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

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388