react-codemirror2 vs codemirror vs monaco-editor
Web 代码编辑器架构选型:CodeMirror vs Monaco vs React 封装
react-codemirror2codemirrormonaco-editor类似的npm包:

Web 代码编辑器架构选型:CodeMirror vs Monaco vs React 封装

codemirror 是一个轻量级、模块化的代码编辑器核心库(当前主流为 v6 版本),适合需要高度定制和较小体积的场景。monaco-editor 是 VS Code 的核心引擎,功能极其强大但体积较重,适合需要 IDE 级体验的场景。react-codemirror2 是 CodeMirror v5 的 React 封装组件,由于锁定在旧版核心,新项目中需谨慎评估。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
react-codemirror2312,2441,70271.5 kB951 年前MIT
codemirror011321.3 kB01 年前MIT
monaco-editor046,896102 MB8881 个月前MIT

CodeMirror vs Monaco Editor vs React-Codemirror2:架构、集成与选型深度对比

在 Web 应用中嵌入代码编辑器是一个常见但复杂的需求。codemirror、monaco-editor 和 react-codemirror2 代表了三种不同的解决思路:现代核心库、重型 IDE 引擎、以及旧版框架封装。它们在设计哲学、集成成本和功能边界上有显著差异。让我们从架构师的角度深入对比。

🏗️ 核心架构:模块化 vs 单体引擎 vs 旧版封装

codemirror (v6) 采用完全模块化的设计。

  • 核心极小,功能通过扩展(Extensions)按需加载。
  • 状态管理不可变,适合现代前端框架。
  • 需要手动组装编辑器功能。
// codemirror v6: 手动组装扩展
import { EditorState } from "@codemirror/state";
import { EditorView, basicSetup } from "@codemirror/view";
import { javascript } from "@codemirror/lang-javascript";

const state = EditorState.create({
  doc: "console.log('Hello')",
  extensions: [basicSetup, javascript()]
});
const view = new EditorView({ state, parent: document.body });

monaco-editor 是一个完整的 IDE 引擎。

  • 内置大量功能(智能感知、差异对比、迷你地图)。
  • 架构较重,加载成本高。
  • 配置驱动,通过对象配置开启功能。
// monaco-editor: 配置驱动
import * as monaco from 'monaco-editor';

monaco.editor.create(document.getElementById('container'), {
  value: "console.log('Hello')",
  language: 'javascript',
  minimap: { enabled: true },
  automaticLayout: true
});

react-codemirror2 是 CodeMirror v5 的 React 封装。

  • 基于旧版 CM5 架构(非模块化)。
  • 提供现成的 React 组件,集成简单。
  • 无法享受 CM6 的性能和新特性。
// react-codemirror2: 组件化封装 (CM5)
import { Controlled as ControlledEditor } from 'react-codemirror2';
import 'codemirror/lib/codemirror.css';

function Editor() {
  return (
    <ControlledEditor
      value="console.log('Hello')"
      options={{ mode: 'javascript', lineNumbers: true }}
    />
  );
}

⚛️ React 集成成本:手动绑定 vs 原生支持 vs 开箱即用

codemirror (v6) 需要手动绑定 React 生命周期。

  • 需要在 useEffect 中初始化 EditorView。
  • 需要手动处理销毁和更新。
  • 灵活性最高,但样板代码较多。
// codemirror v6 + React: 手动管理
useEffect(() => {
  const view = new EditorView({ state, parent: containerRef.current });
  return () => view.destroy(); // 必须手动清理
}, []);

monaco-editor 需要处理 Loader 和生命周期。

  • 需要配置 Webpack/loader 处理静态资源。
  • 需要在 React 中手动调用 create 和 dispose。
  • 社区有封装库(如 @monaco-editor/react),但核心包需手动集成。
// monaco-editor + React: 手动管理实例
useEffect(() => {
  const editor = monaco.editor.create(containerRef.current, options);
  return () => editor.dispose(); // 必须手动销毁
}, []);

react-codemirror2 提供原生 React 组件。

  • 直接作为组件使用,无需关心底层初始化。
  • 通过 props 控制值和配置。
  • 集成最快,但受限于 CM5 架构。
// react-codemirror2: 直接使用组件
<ControlledEditor
  onBeforeChange={(editor, data, value) => setValue(value)}
  value={value}
/>

🛠️ 定制能力:扩展系统 vs 配置文件 vs 有限选项

codemirror (v6) 拥有强大的扩展系统。

  • 可以编写自定义扩展修改编辑器行为。
  • 支持状态字段(State Fields)和效果(Effects)。
  • 适合需要特殊交互逻辑的场景。
// codemirror v6: 自定义扩展
const myExtension = EditorView.theme({
  "&": { backgroundColor: "#f0f0f0" }
});
// 将扩展加入 extensions 数组即可生效

monaco-editor 通过配置和 API 定制。

  • 支持注册自定义语言、提示提供者。
  • 深度定制需要调用复杂的 API。
  • 样式定制相对困难(Shadow DOM 隔离)。
// monaco-editor: 注册自定义提示
monaco.languages.registerCompletionItemProvider('javascript', {
  provideCompletionItems: (model, position) => {
    return { suggestions: [...] };
  }
});

react-codemirror2 定制能力受限于 CM5。

  • 通过 options 对象配置。
  • 自定义功能需要操作底层 CM5 实例。
  • 扩展生态不如 CM6 活跃。
// react-codemirror2: 通过 options 配置
<ControlledEditor
  options={{
    mode: 'javascript',
    theme: 'dracula',
    lineWrapping: true
  }}
/>

⚠️ 版本与未来:CM6 现代架构 vs CM5 遗留维护

这是一个关键的架构决策点。

codemirror (npm 包) 当前默认指向 v6。

  • 这是未来的方向,性能更好,架构更现代。
  • 社区新扩展均基于 v6 开发。
  • 推荐用于新项目。

react-codemirror2 锁定在 CodeMirror v5。

  • CM5 已进入维护模式,不再添加新功能。
  • 无法使用 CM6 的模块化优势。
  • 新项目不建议使用,除非必须兼容旧系统。
  • 如果需要 CM6 的 React 封装,建议评估 @uiw/react-codemirror 等社区方案。

monaco-editor 独立版本迭代。

  • 跟随 VS Code 更新,功能稳定。
  • 不受 CM5/CM6 版本分歧影响。
  • 适合长期稳定的 IDE 需求。

📊 总结对比表

特性codemirror (v6)monaco-editorreact-codemirror2
核心版本✅ 现代 (v6)✅ 独立引擎❌ 遗留 (v5)
打包体积⚡ 轻量 (按需加载)🐘 重型 (完整 IDE)⚡ 轻量 (但含旧核)
React 集成🛠️ 手动/社区封装🛠️ 手动/社区封装✅ 开箱即用组件
定制能力🔓 极高 (扩展系统)🔓 高 (配置+API)🔒 中 (受限于 v5)
智能提示📦 需自行配置扩展✅ 开箱即用 (TS/JS)📦 需自行配置插件
维护状态🟢 活跃开发🟢 活跃开发🟡 维护模式 (CM5)

💡 架构师建议

codemirror (v6) 是大多数 Web 应用的最佳平衡点。它轻量、现代,且拥有活跃的生态。虽然 React 集成需要一点样板代码(或使用 @uiw/react-codemirror),但长期维护成本最低。

monaco-editor 是重型武器的选择。如果你正在构建在线 IDE、复杂的 JSON/YAML 配置器,或者用户强烈依赖 VS Code 的操作习惯,它是唯一选择。不要因为它功能多就在简单表单中使用,体积代价太高。

react-codemirror2 应被视为遗留技术。除非你正在维护一个无法迁移的 CM5 项目,否则不要在新项目中引入它。CodeMirror v6 的架构变化太大,无法平滑升级,现在选择 CM5 意味着未来必须重构。

最终思考:编辑器的选择不仅是选一个组件,而是选择一种维护负担。现代项目应优先拥抱模块化架构(CM6)或成熟引擎(Monaco),避免被旧版封装锁定技术栈。

如何选择: react-codemirror2 vs codemirror vs monaco-editor

  • react-codemirror2:

    选择 react-codemirror2 仅当你维护基于 CodeMirror v5 的遗留项目。由于它不支持最新的 CodeMirror v6 架构,新项目建议使用 codemirror 核心配合现代 React 封装(如 @uiw/react-codemirror)以获得长期支持。

  • codemirror:

    选择 codemirror 如果你需要现代模块化架构(v6),希望打包体积可控,且需要深度定制编辑器行为。它适合构建轻量级代码输入框、文档编辑器或需要高度灵活性的场景。

  • monaco-editor:

    选择 monaco-editor 如果你需要开箱即用的 IDE 级功能(如智能提示、重构、迷你地图),且不在乎较大的打包体积。它适合构建在线 IDE、复杂配置文件编辑器或需要 VS Code 体验的场景。

react-codemirror2的README

Coverage Downloads NPM Version

react-codemirror2

demo @ scniro.github.io/react-codemirror2

Install

npm install react-codemirror2 codemirror --save

react-codemirror2 ships with the notion of an uncontrolled and controlled component. UnControlled consists of a simple wrapper largely powered by the inner workings of codemirror itself, while Controlled will demand state management from the user, preventing codemirror changes unless properly handled via value. The latter will offer more control and likely be more appropriate with redux heavy apps.

uncontrolled usage

import {UnControlled as CodeMirror} from 'react-codemirror2'

<CodeMirror
  value='<h1>I ♥ react-codemirror2</h1>'
  options={{
    mode: 'xml',
    theme: 'material',
    lineNumbers: true
  }}
  onChange={(editor, data, value) => {
  }}
/>

controlled usage

import {Controlled as CodeMirror} from 'react-codemirror2'

<CodeMirror
  value={this.state.value}
  options={options}
  onBeforeChange={(editor, data, value) => {
    this.setState({value});
  }}
  onChange={(editor, data, value) => {
  }}
/>

requiring codemirror resources

codemirror comes as a peer dependency, meaning you'll need to require it in your project in addition to react-codemirror2. This prevents any versioning conflicts that would arise if codemirror came as a dependency through this wrapper. It's been observed that version mismatches can cause difficult to trace issues such as syntax highlighting disappearing without any explicit errors/warnings

  • additional

Since codemirror ships mostly unconfigured, the user is left with the responsibility for requiring any additional resources should they be necessary. This is often the case when specifying certain language modes and themes. How to import/require these assets will vary according to the specifics of your development environment. Below is a sample to include the assets necessary to specify a mode of xml (HTML) and a material theme.

note that the base codemirror.css file is required in all use cases

@import 'codemirror/lib/codemirror.css';
@import 'codemirror/theme/material.css';
import CodeMirror from 'react-codemirror2';
require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');

props

proptype defaultcomponentsdescription
autoCursorboolean trueControlled UnControlledshould component cursor position correct when value changed
autoScrollboolean trueControlled UnControlledshould component scroll cursor position into view when value changed
classNamestringControlled UnControlledpass through class class="react-codemirror2 className"
defineModeobjectControlled UnControlledpass a custom mode via {name: 'custom', fn: myModeFn}
detachbooleanUnControlledshould component ignore new props
optionsobjectControlled UnControlledcodemirror configuration
valuestring*Controlled UnControlled* component value must be managed for controlled components

props cont. (wrapped codemirror programming api)

will programmatically set cursor to the position specified

<CodeMirror
  [...]
  cursor={{
    line: 5,
    ch: 10
  }}
  onCursor={(editor, data) => {}}
/>

will programmatically scroll to the specified coordinate

<CodeMirror
  [...]
  scroll={{
    x: 50,
    y: 50
  }}
  onScroll={(editor, data) => {}}
/>
  • selection={{ranges: array<{anchor, head}>, focus?: boolean} - setSelections

will programmatically select the ranges specified

<CodeMirror
  [...]
  selection={{
    ranges: [{
      anchor: {ch: 8, line: 5},
      head: {ch: 37, line: 5}
    }],
    focus: true // defaults false if not specified
  }}
  onSelection={(editor, data) => {}}
/>

events

eventcomponentsdescription
editorDidAttach(editor)UnControlledcomponent is now responding to new props
editorDidConfigure(editor)Controlled UnControlledcomponent configuration has been set
editorDidDetach(editor)UnControlledcomponent is now ignoring new props
editorDidMount(editor, [next])Controlled UnControlled* invoking optional next will trigger editorDidConfigure
editorWillUnmount(editor)Controlled UnControlledinvoked before componentWillUnmount
onBeforeChange(editor, data, value, [next])Controlled UnControlled* if used, next is returned via UnControlled and must be invoked to trigger onChange
onChange(editor, data, value)Controlled UnControlledthe component value has been changed

events cont. wrapped codemirror events

FAQ

  • Is server side rendering supported?

Yes. react-codemirror2 will prevent rendering in absence of window. You can also force the component to not render via a PREVENT_CODEMIRROR_RENDER global.

  • How can I get the instance?

The recommended technique to get the instance is to persist the editor returned via event callbacks. There is no static method to get it on demand, e.g. CodeMirror.getInstance(). Example...

constructor() {
  this.instance = null;
}

render() {
  <CodeMirror editorDidMount={editor => { this.instance = editor }}/>
}
  • How can I have a resizable editor?

Check out bokuweb/re-resizable. Wrapping your component with <Resizable/>'s works well

Contributing

Pull Requests are welcome. Be mindful of the available scripts below to help submitting a well-received contribution.

  • npm run start to run the app on localhost:8000
  • npm run test to ensure tests continue to pass
  • npm run build to generate the demo bundle

note that it's necessary to bump the package.json version prior to final npm run build so we can grab the proposed new version as seen in the demo header. Also note, the core changes are to be made in src/index.tsx as ./index.js and ./index.d.ts are generated

MIT © 2020 scniro