在 Vue 3 项目里嵌入一个真正可用的 YAML 编辑器,并不是简单放一个 textarea 就能解决。YAML 对缩进和嵌套非常敏感,用户少打一个空格、写错一个键名、引用了不存在的锚点,都可能让配置在发布后报错。本文基于 Monaco Editor 和 monaco-yaml 插件,实现一个包含实时语法检查、错误标记、代码折叠和错误统计面板的 YAML 编辑组件。核心思路是把 yaml-language-server 的解析与诊断能力接到 Monaco 上,再通过 Vue 3 组合式 API 管理编辑器生命周期。

一、编辑器选型与依赖安装
在 Vue 3 项目中实现 YAML 编辑,常见的底层方案有 CodeMirror 6、Ace 和 Monaco Editor。CodeMirror 6 体积小、按需加载灵活,但 YAML 的语言支持需要手动组合 legacy 模式与自定义 lint;Ace 对 YAML 的支持停留在基础高亮,缺少完整的结构诊断。Monaco Editor 内置了语言服务协议,配合 monaco-yaml 插件可以直接接入 yaml-language-server 的解析、补全、折叠与诊断能力,整体开发成本更低。所以本文选择 Monaco Editor 作为编辑器内核。
先在项目里安装两个核心依赖。如果使用 Vite 构建,建议将 monaco-editor 和 monaco-yaml 加入 optimizeDeps.include,避免开发服务器冷启动时反复预构建导致页面卡顿。下面是安装命令与 vite.config.js 的配置片段。
npm install monaco-editor monaco-yaml
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
optimizeDeps: {
include: ['monaco-editor', 'monaco-yaml']
}
})
Monaco 的语言服务和 yaml-language-server 都运行在 Web Worker 里,因此必须正确配置 getWorker。若缺少这一步,编辑器只会显示普通文本,语法检查与折叠都不会生效,控制台通常会出现 worker 加载失败的错误。在入口文件或组件初始化前设置 self.MonacoEnvironment,yaml worker 使用 monaco-yaml 包内提供的文件即可。
import * as monaco from 'monaco-editor'
import { configureMonacoYaml } from 'monaco-yaml'
self.MonacoEnvironment = {
getWorker(moduleId, label) {
if (label === 'yaml') {
return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url))
}
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url))
}
}
二、接入实时语法检查与诊断面板
monaco-yaml 的 configureMonacoYaml 函数是连接 Monaco 与 yaml-language-server 的关键。开启 validate 后,编辑器会在输入过程中实时解析 YAML,常见的缩进层级错误、未闭合的双引号、重复键、无效的锚点引用都会以红色波浪线标出。把鼠标悬停在错误位置,还能看到具体的诊断信息。这些能力不需要手工编写解析规则,语言服务已经内置了完整的 YAML 规范。
除了编辑器内部的波浪线,实际业务中往往还需要一个独立的错误统计面板。我们可以监听 monaco.editor.onDidChangeMarkers 事件,当诊断标记变化时,调用 getModelMarkers 取出当前模型的所有标记,再根据 MarkerSeverity 过滤出真正的错误。错误数量绑定到 Vue 的响应式变量后,就能在工具栏中实时显示当前配置的健康状态。
import * as monaco from 'monaco-editor'
import { configureMonacoYaml } from 'monaco-yaml'
import { ref, onMounted, onBeforeUnmount } from 'vue'
const errorCount = ref(0)
let editor
let monacoYaml
onMounted(() => {
monacoYaml = configureMonacoYaml(monaco, {
validate: true,
format: true,
schemas: [
{
uri: 'https://ipipp.com/config-schema.json',
fileMatch: ['*'],
schema: {
type: 'object',
properties: {
name: { type: 'string' },
port: { type: 'number' }
}
}
}
]
})
editor = monaco.editor.create(document.getElementById('yaml-editor'), {
value: 'name: demo\nport: 8080\n',
language: 'yaml',
theme: 'vs-dark',
automaticLayout: true,
folding: true,
showFoldingControls: 'always',
minimap: { enabled: false }
})
const model = editor.getModel()
monaco.editor.onDidChangeMarkers((resource) => {
if (!model || resource !== model.uri) return
const markers = monaco.editor.getModelMarkers({ resource })
errorCount.value = markers.filter(m => m.severity === monaco.MarkerSeverity.Error).length
})
})
onBeforeUnmount(() => {
editor?.dispose()
monacoYaml?.dispose?.()
})
JSON Schema 校验是 monaco-yaml 的另一个实用能力。上面的 schemas 配置传入一个本地对象,指定 name 必须是字符串、port 必须是数字,这样当用户把 port 写成字符串时,编辑器会直接提示类型不匹配。对于 Kubernetes 清单、CI 配置等固定结构,可以把 schema 抽成独立 JSON 文件放到 public 目录,通过 uri 引入,也可以用 fileMatch 匹配文件名。
三、代码折叠与交互优化
YAML 配置层级深、字段多,折叠功能可以显著提高阅读效率。Monaco Editor 通过语言服务返回的 FoldingRange 决定哪些区域可以折叠。monaco-yaml 接入 yaml-language-server 后,会自动根据缩进和文档结构生成折叠范围。创建编辑器时需要显式打开 folding 选项,并设置 showFoldingControls 为 always,否则折叠图标只在鼠标悬停到行号左侧时才显示。
如果希望提供一键折叠或展开全部的操作,可以调用 Monaco 的内置命令。editor.foldAll 会折叠所有可折叠区域,editor.unfoldAll 则恢复展开。这些命令通过 editor.trigger 触发,使用标准动作名即可,无需自己遍历模型。在 Vue 模板中放两个按钮,对应调用下面两个函数。
<template>
<div class="yaml-editor-panel">
<div class="toolbar">
<button @click="foldAll">全部折叠</button>
<button @click="unfoldAll">全部展开</button>
<span>错误数:{{ errorCount }}</span>
</div>
<div id="yaml-editor" class="editor-container"></div>
</div>
</template>
function foldAll() {
editor?.trigger('fold', 'editor.foldAll')
}
function unfoldAll() {
editor?.trigger('fold', 'editor.unfoldAll')
}
编辑器容器需要显式设置高度,否则 Monaco 只会渲染一个很小的区域。推荐使用 automaticLayout 自动监听容器尺寸变化,配合 CSS 高度设置即可适配大部分页面布局。组件卸载时记得调用 dispose 释放编辑器和语言服务资源,避免内存泄漏。对于 Vue 3 组合式 API,可以在 onBeforeUnmount 里完成清理。
.editor-container {
width: 100%;
height: 600px;
}
除了折叠按钮,Monaco 自带的折叠快捷键也值得在界面中提示给用户:Windows/Linux 下是 Ctrl+Shift+[ 折叠、Ctrl+Shift+] 展开;macOS 下对应 Command+Option+[ 和 Command+Option+]。这些快捷键配合缩进折叠,在处理几百行的 YAML 配置时非常高效。
四、常见问题与优化建议
Worker 路径错误是集成 monaco-yaml 时最常见的问题。Vite 的 new URL 语法要求 worker 文件路径必须写包内真实存在的文件,不能随意拼接。若看到 Failed to construct Worker 的提示,先检查 getWorker 中 yaml 分支是否返回了 monaco-yaml/yaml.worker,editor 分支是否返回了 monaco-editor 的 editor.worker。同时确认没有把这两个路径写反。
Monaco Editor 本身的包体积比较大,如果编辑器页面不是首屏必需功能,建议使用路由懒加载将组件拆分为独立 chunk。也可以把 monaco-editor 通过 CDN 引入,使用全局 monaco 对象,不过这样会损失一些构建期的类型提示。对于内部管理系统,直接 npm 引入并按需加载是最省心的方案。
当 YAML 文件超过几千行时,实时诊断可能会让输入出现轻微卡顿。可以考虑在内容变化时做防抖处理:用户停止输入 300 毫秒后再更新编辑器模型,或者保存时才触发完整校验。monaco-yaml 没有直接暴露诊断延迟参数,但可以在 Vue 层通过 watch 控制 setValue 的时机,减少高频解析带来的压力。折叠功能本身在大文件下表现稳定,可以放心开启。