Monaco Editor(即 VS Code 使用的编辑器引擎)功能强大、内置语言服务(LSP-like)与智能补全,但体积与 worker 配置复杂;CodeMirror(尤其是 CodeMirror 6)更轻量、模块化、易扩展。对于需要接近 IDE 体验、重度编辑器功能(诊断、refactor、复杂语言支持)的应用推荐 Monaco;对追求轻量、可控 bundle 与高度定制化的内嵌编辑器场景推荐 CodeMirror。文末给出在三种常见前端技术栈下的可复制封装代码与 worker 打包配置
Monaco vs CodeMirror(精要对比)
- 功能面
- Monaco:几乎是 VSCode 的编辑器内核,支持语法高亮、智能提示、代码诊断、重构相关的 API。适合做“接近 IDE”的 Web 编辑体验。(GitHub)
- CodeMirror(6):现代化重构,模块化、体积可控、可按需引入扩展,适合作为轻量嵌入式编辑器。
- 体积与加载
- Monaco 体积较大(历史上在一些项目中被认为“很重”),并且依赖若干 web worker 来保持 UI 流畅。需要特殊处理 worker 的打包/加载。(Hacker News)
- 可定制性
- CodeMirror 更“从零开始”重构,插件化设计便于定制。Monaco 提供更高阶的内置功能,但某些深度定制需要理解全局单例式的 language 注册与 worker 机制。(sourcegraph.com)
- 工程集成(Webpack / Vite / Worker)
- Monaco 在 webpack 上可以借助
monaco-editor-webpack-plugin;在 Vite 上通常借助社区插件(例如vite-plugin-monaco-editor或其他 ESM-friendly 插件),并需要注意 worker 的导入方式与 publicPath。(npmjs.com)
- Monaco 在 webpack 上可以借助
选型建议(快速)
- 需要 VSCode 等级语言体验、错误提示、复杂语言支持 -> Monaco
- 追求轻量、包体积敏感、简单语法高亮或自定义编辑器 -> CodeMirror 6
实战:封装与打包配置(三种栈)
每个示例都给出:组件封装(最小可用示例) + 必要的构建配置(webpack/vite) + 重要说明(中文注释)。代码注释全部为中文。
一、Vue 2 + webpack(vue.config.js)示例
说明:在 Vue 2 + webpack 场景下,常见做法是使用 monaco-editor-webpack-plugin 将 Monaco 的 worker 输出到指定目录,并通过 MonacoEnvironment.getWorkerUrl 或 window.MonacoEnvironment 指定 worker 的 publicPath。下面给出可复制的基本实现。
依赖
npm install monaco-editor monaco-editor-webpack-plugin --save
vue.config.js(关键点:配置 publicPath / plugin)
// vue.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
// 如果部署在非根路径,需设置 publicPath,确保 worker 能被正确加载
publicPath: process.env.NODE_ENV === 'production' ? '/static/' : '/',
configureWebpack: {
plugins: [
new MonacoWebpackPlugin({
// 按需配置语言/功能,减少打包体积
// languages: ['javascript', 'typescript', 'css', 'html', 'json'],
// features: ['!gotoSymbol'] // 也可以排除某些 feature
})
]
},
devServer: {
// 若 worker 在 dev 环境无法创建,确认 devServer 的 publicPath / 静态服务配置
}
}
说明:
monaco-editor-webpack-plugin会把 worker 打包为静态文件,需要确保publicPath指向正确位置(StackOverflow/issue 中常见错误来源)。(npmjs.com)
Vue 2 单文件组件包装(MonacoEditor.vue)
<template>
<div ref="container" class="monaco-container" style="height:100%;"></div>
</template>
<script>
import * as monaco from 'monaco-editor';
export default {
name: 'MonacoEditor',
props: {
value: { type: String, default: '' },
language: { type: String, default: 'javascript' },
options: { type: Object, default: () => ({}) }
},
mounted() {
// 为确保 worker 能被正确加载,设置 MonacoEnvironment 的 getWorkerUrl
// 这是在使用 monaco-editor-webpack-plugin 常见的配置方式之一
// 例如插件会输出 worker 到 /static/js/monaco-editor-worker*.js(取决于 publicPath)
if (typeof window !== 'undefined') {
window.MonacoEnvironment = {
getWorkerUrl: function(moduleId, label) {
// 为了兼容各个 worker,采用插件默认输出的路径
return '/static/js/' + 'monaco-editor-worker.js'; // 根据实际输出调整
}
};
}
this.editor = monaco.editor.create(this.$refs.container, {
value: this.value,
language: this.language,
automaticLayout: true,
...this.options
});
// 监听内容变化,向外抛出 input 事件以支持 v-model
this.editor.getModel().onDidChangeContent(() => {
this.$emit('input', this.editor.getValue());
});
},
beforeDestroy() {
if (this.editor) {
this.editor.dispose();
}
}
};
</script>
<style>
.monaco-container { width: 100%; height: 400px; }
</style>
注意事项
getWorkerUrl的返回路径必须与 webpack 输出的 worker 文件实际路径一致(publicPath/插件配置相关)。如果 worker 加载失败,控制台常见提示"Could not create web worker(s)"。若遇到问题,请检查 publicPath 与插件输出路径。(Stack Overflow)- 可在
MonacoWebpackPlugin中按需配置languages与features以减少输出。
二、Vue 3 + Vite(推荐用法 + 示例)
说明:Vite 是基于 ESM 的构建工具,Monaco 的 worker 机制与传统 webpack 不同;有若干社区插件帮助处理,例如 vite-plugin-monaco-editor / vite-plugin-monaco-editor-esm 等,但社区生态多样,选择时注意插件是否维护且与 Vite 版本兼容。下例采用常见思路:使用社区插件并在运行时用 MonacoEnvironment 指定 worker。
安装(示例)
npm install monaco-editor
# 推荐安装某个适配 Vite 的插件(此处以示例插件名,实际选用请参考插件 README)
npm install vite-plugin-monaco-editor --save-dev
vite.config.ts(示例)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
export default defineConfig({
plugins: [
vue(),
monacoEditorPlugin({
// 插件选项:按需语言/特性等(参考插件 README)
// languages: ['javascript','typescript','json'],
})
],
// 如果需要对 worker 做特定处理可以在 build.rollupOptions 中配置
build: {
rollupOptions: {
// ...
}
}
});
说明:Vite 插件通常会把 worker 打包成模块并通过 dev server 中间件代理加载,避免上面 webpack 那样的 publicPath 手动工作。务必阅读插件 README 以获取最新用法。(GitHub)
Vue 3 组件封装(MonacoEditor.vue)
<template>
<div ref="container" style="height:400px;width:100%;"></div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
import * as monaco from 'monaco-editor';
const props = defineProps({
modelValue: { type: String, default: '' },
language: { type: String, default: 'javascript' },
options: { type: Object, default: () => ({}) }
});
const emit = defineEmits(['update:modelValue']);
const container = ref(null);
let editor = null;
onMounted(() => {
// Vite 插件通常已经为 worker 提供了解决方案,但在某些插件/配置下
// 仍然需要手动设置 MonacoEnvironment.getWorkerUrl
if (typeof window !== 'undefined' && !window.MonacoEnvironment) {
window.MonacoEnvironment = {
getWorkerUrl: function(moduleId, label) {
// 如果使用插件,这里通常不需要改变;否则需指向正确的位置
return '/assets/monaco-worker.js'; // 根据实际情况替换
}
};
}
editor = monaco.editor.create(container.value, {
value: props.modelValue,
language: props.language,
automaticLayout: true,
...props.options
});
const model = editor.getModel();
const changeDisposable = model.onDidChangeContent(() => {
emit('update:modelValue', editor.getValue());
});
onBeforeUnmount(() => {
changeDisposable.dispose();
editor.dispose();
});
});
watch(() => props.modelValue, (val) => {
if (editor && editor.getValue() !== val) editor.setValue(val);
});
</script>
<!-- 代码注释:这段封装展示了如何在 Vue3 中包装 Monaco 并支持 v-model -->
注意事项
- 不同的 vite-plugin 实现策略不同:某些插件会在 dev 时用中间件按需 bundle worker,生产构建会把 worker 打包为静态文件或 module worker;务必阅读插件 README 并测试 dev/production 两种模式。(GitHub)
- 社区中也有
monaco-editor-workers、vite-plugin-monaco-editor-esm等包可选,选用时关注许可证与维护状态。(npmjs.com)
三、Lit + Vite(Web Component 场景)
说明:如果你在做 Web Component(如基于 Lit 的组件库),想把 Monaco 封装为自定义元素,流程类似:在组件内部创建 editor,在构建时确保 worker 可被正确引入。社区里有文章示范把 Monaco 封装到 Lit 元件中(示例与思路可参考)。(Medium)
依赖安装
npm install monaco-editor
# 建议安装适配 vite 的 monaco 插件
npm install vite-plugin-monaco-editor --save-dev
vite.config.js(简化示例)
import { defineConfig } from 'vite';
import { resolve } from 'path';
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
export default defineConfig({
plugins: [
monacoEditorPlugin({
// 插件选项
})
],
build: {
rollupOptions: {
// 如需把 worker 放到特定目录,配置 output.assetFileNames / publicPath
}
}
});
Lit Web Component 封装(示例)
import { LitElement, html, css } from 'lit';
import * as monaco from 'monaco-editor';
export class MonacoElement extends LitElement {
static styles = css`
:host { display:block; height:400px; }
.editor { width:100%; height:100%; }
`;
firstUpdated() {
// 如同前面示例,若插件未处理 worker,需要设置 MonacoEnvironment
if (typeof window !== 'undefined' && !window.MonacoEnvironment) {
window.MonacoEnvironment = {
getWorkerUrl: function(moduleId, label) {
return '/assets/monaco-worker.js'; // 根据构建输出实际路径调整
}
};
}
this.editor = monaco.editor.create(this.renderRoot.querySelector('.editor'), {
value: '',
language: 'javascript',
automaticLayout: true
});
}
disconnectedCallback() {
if (this.editor) this.editor.dispose();
super.disconnectedCallback();
}
render() {
return html`<div class="editor"></div>`;
}
}
customElements.define('monaco-element', MonacoElement);
注意事项
- 在 Web Component 场景中需要留意:CSS / 容器大小 /
automaticLayout的行为,通常需要在容器可见且尺寸确定后再layout。另外 worker 路径同样是关键点,需要和构建输出一致。(Medium)
常见坑与调试建议
- Worker 无法创建 / 找不到:控制台会报
Could not create web worker(s). Falling back...或Unexpected usage等错误。先检查 worker 文件是否被正确生成、publicPath 或 plugin 输出路径是否一致;在 webpack 场景下,monaco-editor-webpack-plugin会生成 worker 文件,确保output.publicPath与MonacoEnvironment.getWorkerUrl指定路径匹配。(Stack Overflow) - 体积问题:Monaco 功能强但文件大。生产环境建议按需开启语言与 feature,或选择 lazy load 编辑器(按需异步加载 monaco-editor)。(Hacker News)
- Vite + Monaco 的兼容性:Vite 与 Monaco 的集成在社区有多种解决方案,务必选用针对你所用 Vite 版本维护良好的插件;遇到 worker 加载问题时在 GitHub issues 与 plugin README 中寻找现成方案。(GitHub)
参考
- Monaco Editor 官方仓库与说明(README、集成文档)。(GitHub)
- 社区对 Monaco 体积的讨论与对比(历史讨论)。(Hacker News)
- monaco-editor-webpack-plugin(webpack 场景常用插件)。(npmjs.com)
- vite-plugin-monaco-editor(Vite 集成示例插件)。(GitHub)
- 把 Monaco 封装到 Lit 的博文示例(实现思路参考)。(Medium)
小结
- 如果你要实现一个 在线 IDE、代码分析、智能补全 等高级功能,优先考虑 Monaco,但要为 worker 与体积付出工程成本(按需构建、插件选择、publicPath 校准)。(GitHub)
- 如果你强调 轻量、可裁剪、灵活扩展,并且不需要 VSCode 级别的语言服务,CodeMirror 6 会是更合适的选择。(agenthicks.com)
文章评论