蓝戒博客

  • 首页
  • 研发说
  • 架构论
  • 效能录
  • AI谈
  • 随笔集
智构苍穹
AI为翼,架构为骨,文化为魂,实践探新境,价值筑长青。
  1. 首页
  2. 研发说
  3. 正文

Monaco Editor真香,从对比到实战封装,一篇讲透

2025年11月17日 155点热度 0人点赞 0条评论

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)

选型建议(快速)

  • 需要 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。下面给出可复制的基本实现。

依赖

Bash
npm install monaco-editor monaco-editor-webpack-plugin --save

vue.config.js(关键点:配置 publicPath / plugin)

JavaScript
// 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)

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(示例)

JavaScript
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)

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)

依赖安装

Bash
npm install monaco-editor
# 建议安装适配 vite 的 monaco 插件
npm install vite-plugin-monaco-editor --save-dev

vite.config.js(简化示例)

JavaScript
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 封装(示例)

Lit
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)

常见坑与调试建议

  1. 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)
  2. 体积问题:Monaco 功能强但文件大。生产环境建议按需开启语言与 feature,或选择 lazy load 编辑器(按需异步加载 monaco-editor)。(Hacker News)
  3. 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)
标签: CodeMirror对比 Monaco编辑器 在线代码编辑器
最后更新:2025年11月21日

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

cywcd

我始终相信,技术不仅是解决问题的工具,更是推动思维进化和创造价值的方式。从研发到架构,追求极致效能;在随笔中沉淀思考,于 AI 中对话未来。

最新 热点 随机
最新 热点 随机
npm 安全更新:把握令牌变更与发布体系的迁移参考指南 TresJS:用 Vue 构建现代化交互式 3D 体验 i18n 高效实现方案:前端国际化神器安利一波 前端国际化 i18n 实践:从项目到组件库的全链路方案 GEO(生成引擎优化)完整指南:AI 搜索时代的企业内容新机会 NativeScript:用 JavaScript / TypeScript 构建真正的原生应用
前端开源工具 PinMe:极简部署体验分享大屏适配的核心痛点与一行 autofit 解决方案markdown-exit:现代化的 Markdown 解析工具Lerna + Monorepo:前端多仓库管理的最佳实践CrewAI:基于角色协作的 AI Agent 团队框架浅析2025 最推荐的 uni-app 技术栈:unibest + uView Pro 高效开发全攻略
JS判断移动设备浏览器信息 UniApp 从入门到实战:一套代码多端运行的最佳实践 jquery.fullCalendar日程管理控件 中文API css清除浮动方法及优缺点解析 构建高效 AI 工作流的工程化实践 js判断浏览器类型并区分IE不同版本
最近评论
渔夫 发布于 1 个月前(11月05日) 学到了,感谢博主分享
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
沙拉小王子 发布于 8 年前(11月30日) 适合vue入门者学习,赞一个
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8
cywcd 发布于 9 年前(04月27日) 请参考一下这篇文章http://www.jianshu.com/p/fa4460e75cd8

COPYRIGHT © 2025 蓝戒博客_智构苍穹-专注于大前端领域技术生态. ALL RIGHTS RESERVED.

京ICP备12026697号-2