WEB代码编辑器哪家强

13,125 阅读7分钟

作为一名coder,像VS Code这样的代码编辑器自然是必不可少的,你还可以使用类似CodeSandbox这样的online编辑器开发demo程序。编辑器更多是作为工具方便我们进行日常的代码开发工作,倘若将编辑器视作产品的一部分为其提供可扩展的能力,我们该如何应对。答案是:web编辑器,现在热门的可视化页面搭建系统便是一个典型的案例。

今天要介绍的是三款开源的主流web编辑器:
Ace
CodeMirror
Monaco Editor

前世今生

Ace是来自Ajax.org Cloud9 Editor的一个独立的代码编辑器,它的前身是Bespin和后来的Skywriter。这两者最开始走的路线不一样,Bespin基于canvas,Ace基于DOM。Ace发布于2010年,之后Skywriter团队将Skywriter的插件系统和可扩展性融合到了Ace中,便形成了现在的Ace编辑器。现在,Ajax.org和Mozilla都在积极的开发和维护Ace。

CodeMirror的第一版于2007年发布,该版本基于浏览器的contentEditable属性实现。2010年发布的Ace采用了新的技术并证明即使是使用javascript操作数以千行的DOM也不存在性能问题,这驱使了CodeMirror的重构并发布了第二个版本,弃用了之前的contentEditable,性能得到了很大提升。如今,CodeMirror即将发布最新的重构版本6。

Monaco Editor算是后起之秀,随着2015年VS Code的发布而诞生,它与VS Code使用同样的核心代码。

快速开始

这里以不引入任何框架的,纯粹的html+css+js形式展示三类编辑器的使用。

Ace

官方示例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Editor</title>
  <style type="text/css" media="screen">
    body {
        overflow: hidden;
    }

    #editor {
        margin: 0;
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
    }
  </style>
</head>
<body>

<pre id="editor">function foo(items) {
    var i;
    for (i = 0; i &lt; items.length; i++) {
        alert("Ace Rocks " + items[i]);
    }
}</pre>

<script src="src-noconflict/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
    var editor = ace.edit("editor");
    editor.setTheme("ace/theme/twilight");
    editor.session.setMode("ace/mode/javascript");
</script>

</body>
</html>

这里承载编辑器内容的html标签是pre,实际开发中并不常见,可以替换成div。
注意ace的引用方式。ace-builds repository是ace的最新发布包,直接将src* 子目录拷贝到项目中即可使用,发布包总共有四个版本。

  • src (完整版本)
  • src-min (压缩版本)
  • src-noconflict (ace.require完整版本)
  • src-min-noconflict (ace.require压缩版本) 当然我们也可以自行打包,从github仓库拉取源码,执行下面的脚本。
npm install
node ./Makefile.dryice.js

于是就得到了完整的版本,即上述四个版本中的第一个,还可以通过脚本选项生成另外三个版本或是指定输出目录。

CodeMirror

直接下载zip文件并拷贝到项目中即可使用。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="codemirror/lib/codemirror.css" />
    <script src="codemirror/lib/codemirror.js"></script>
    <script src="codemirror/mode/javascript/javascript.js"></script>
    <style>
      .CodeMirror {
        border: 1px solid black;
      }
    </style>
  </head>
  <body>
    <div>
      <textarea id="editor"></textarea>
      <div id="editor2"></div>
    </div>
    <script>
      // 方式1
      var ele = document.getElementById("editor");
      var editor = CodeMirror.fromTextArea(ele, {
        lineNumbers: true,
        mode: "javascript"
      });
      editor.setValue("var a = 'hello world';")
      // 方式2
      var editor2 = CodeMirror(document.getElementById("editor2"), {
        lineNumbers: true,
        value: "console.log('hello world');",
        mode: "javascript"
      })
    </script>
  </body>
</html>

这里有两种方式创建编辑器,fromTextArea的方式具有一些额外的特性。详情戳这里

Monaco Editor

通过npm安装

npm install monaco-editor

安装完成后生成三个版本。

  • esm (es模块化版本,兼容webpack)
  • dev (完整的amd模块化版本)
  • min (压缩的amd模块化版本) 另外还有一个min版本的source maps文件夹和一个编辑器api描述文件monaco.d.ts
    AMD版本的官方示例
<!DOCTYPE html>
<html>
  <head>
    <title>browser-amd-editor</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <h2>Monaco Editor Sample</h2>
    <div
      id="container"
      style="width: 800px; height: 600px; border: 1px solid grey"
    ></div>

    <!-- OR ANY OTHER AMD LOADER HERE INSTEAD OF loader.js -->
    <script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
    <script>
      require.config({ paths: { vs: "../node_modules/monaco-editor/min/vs" } });

      require(["vs/editor/editor.main"], function () {
        var editor = monaco.editor.create(
          document.getElementById("container"),
          {
            value: [
              "function x() {",
              '\tconsole.log("Hello world!");',
              "}",
            ].join("\n"),
            language: "javascript",
          }
        );
      });
    </script>
  </body>
</html>

实际项目基本都是基于前端框架开发,下面就以Vue为例介绍三类编辑器的使用。

在Vue项目中的使用

Ace

  • 安装
npm install ace-builds
  • 引入
import ace from "ace-builds"

实际使用过程中我们一般都会指定代码的语言类型,还有可能修改编辑器的默认样式。这样的话,我们还需要引入相关的语言类型文件和主题文件。

import "ace-builds/src-noconflict/mode-javascript.js"
import "ace-builds/src-noconflict/theme-tomorrow.js"

但是,如果我们想动态切换语言类型或是主题,是不是应该把对应的js文件全部引入呢?Ace为我们提供了更简洁的方法。

import "ace-builds/webpack-resolver"

这样就完成了语言类型和主题的动态加载,前提是项目基于webpack构建的。

  • 常用api 设置主题
myEditor.setTheme("ace/theme/tomorrow")

设置语言类型

// 一个editor可能存在多个session
myEditor.session.setMode("ace/mode/javascript")
myEditor.setMode("ace/mode/javascript")

设置/获取值

myEditor.setValue("the new text here")
myEditor.session.setValue("the new text here")
myEditor.getValue() // or session.getValue

设置tab大小

myEditor.session.setTabSize(4)

是否只读

myEditor.setReadOnly(true)	// false可编辑

当我们要设置多个属性值时,除了单独调用每个api,还可以使用下面这种方式。

myEditor.setOptions({
  mode: "ace/mode/javascript",
  theme: "ace/theme/tomorrow",
  value: "hello world"
})

有了这些常用的api,编辑器基本成型了。可以参考官网或是源码ace.d.ts查看全部的接口。

  • 高级特性 Ace自带语法检查功能,目前支持JavaScript, JSON, PHP, CoffeeScript, CSS, XQuery,XML,HTML。
    效果如下:

除了语法检查,还可以设置代码提示和自动补全。只需要引入语言构建扩展,并设置相关属性即可。

import "ace-builds/src-noconflict/ext-language_tools"

myEditor.setOptions({
  enableBasicAutocompletion: true,
  enableSnippets: true,
  enableLiveAutocompletion: true,
});

效果如下:

图中的提示及补全片段都可以在源码中找到,我们可以按照源码中的语法添加自定义的提示及补全片段信息。

CodeMirror

  • 安装
npm install codemirror
  • 引入
import CodeMirror from "codemirror/lib/codemirror.js"
import "codemirror/lib/codemirror.css"

同样,对于主题和语言类型也需要引入相应的文件。

import "codemirror/theme/material.css"
import "codemirror/mode/javascript/javascript.js"
  • 常用api 设置属性
myEditor.setOption("mode", "text/javascript")
myEditor.setOption("value", "hello world")

设置/获取值

myEditor.getValue()
myEditor.setValue("hello world")
  • 高级特性 CodeMirror默认是没有语法检查功能的,需要利用插件addon进行扩展。
import "codemirror/addon/lint/lint.css"
import "codemirror/addon/lint/lint.js"
import "codemirror/addon/lint/javascript-lint.js"
this.editor = CodeMirror.fromTextArea(this.$refs.editor, {
  mode: "text/javascript",
  gutters: ["CodeMirror-lint-markers"],
  lint: true
})

另外还需要引入语言对应的检查工具,可以在项目的index.html中引入。

<script src="https://unpkg.com/jshint@2.9.6/dist/jshint.js"></script>

效果如下。

代码提示和智能补全,需要引入相关插件,设置属性并绑定快捷键触发。

import "codemirror/addon/hint/show-hint.js"
import "codemirror/addon/hint/show-hint.css"
import "codemirror/addon/hint/javascript-hint.js"
myEditor = CodeMirror.fromTextArea(this.$refs.editor, {
  mode: "text/javascript",
  extraKeys: { "Ctrl-Enter": "autocomplete", "Cmd-Enter": "autocomplete" }
})
CodeMirror.commands.autocomplete = function (cm) {
  cm.showHint({ hint: CodeMirror.hint.javascript });
}

这里分别定义了windows和mac系统下触发代码提示和智能补全的组合键,Ctrl+Enter Cmd+Enter。效果如下。
CodeMirror支持diff模式,需要引入的插件及实现如下。

import "codemirror/addon/merge/merge.css"
import "codemirror/addon/merge/merge.js"

const orig1 = `import Vue from 'vue'
import App from './App.vue'\n

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')`;

const orig2 = `import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";

Vue.config.productionTip = false;

Vue.use(ElementUI);
Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    tabName: "",
  },
  mutations: {
    changeTab(state, tabName) {
      state.tabName = tabName;
    },
  },
});

new Vue({
  render: (h) => h(App),
  store: store,
}).$mount("#app");`;

myDiffEditor = CodeMirror.MergeView(
  document.getElementById("view"),
  {
    value: orig1,
    origLeft: null,
    orig: orig2,
    lineNumbers: true,
    mode: "text/javascript",
    highlightDifferences: true,
    connect: true,
    collapseIdentical: false
  }
);

diff功能需要依赖diff-match-patch开源库来计算差异。

<script src="https://cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js"></script>

最终效果如下。

Monaco Editor

  • 安装
npm install monaco-editor

此外,还需要安装配套的webpack插件。

npm install monaco-editor-webpack-plugin

在vue.config.js中添加插件。

const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = {
  configureWebpack: {
    plugins: [
      new MonacoWebpackPlugin()
    ]
  }
}
  • 引入
import * as monaco from "monaco-editor"
  • 常用api 设置语言类型
const model = myEditor.getModel()
monaco.editor.setModelLanuage(model, "javascript")

设置主题
monaco editor自带三种主题,默认的"vs"及"vs-dark"、"hc-black"。

monaco.editor.setTheme("vs-dark")

设置属性
例如,readOnly(是否只读)、renderLineHighlight(高亮行)、lineNumbers(是否显示行号)、fontSize(字体大小)等。

myEditor.updateOptions({
  [name]: value	// name表示属性名称,value为对应的属性值
})

这里仅列举了部分属性,完整的请参考官网或是源码monaco.d.ts。官网还提供了丰富的示例

  • 高级特性 monaco editor默认支持TypeScript, JavaScript, CSS, LESS, SCSS, JSON, HTML的语法校验及智能提示补全功能。效果如下。

和桌面端的vscode没有任何区别。
再看diff模式。

const originalModel = monaco.editor.createModel(
          `import Vue from 'vue'
import App from './App.vue'\n

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')`,
          "javascript"
        );
        
const modifiedModel = monaco.editor.createModel(
          `import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";

Vue.config.productionTip = false;

Vue.use(ElementUI);
Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    tabName: "",
  },
  mutations: {
    changeTab(state, tabName) {
      state.tabName = tabName;
    },
  },
});

new Vue({
  render: (h) => h(App),
  store: store,
}).$mount("#app");`,
          "javascript"
        );
        
myDiffEditor = monaco.editor.createDiffEditor(
  document.getElementById("monaco-editor")
);
myDiffEditor.setModel({
  original: originalModel,
  modified: modifiedModel,
});

效果如下。

总结

Ace,CodeMirror,Monaco Editor这三类编辑器的基本功能几乎相差无几,部分api高度相似,同样都具备部分语言的语法校验和代码智能提示及补全功能,只不过在实现方式上有所区别。自定义主题和语言,三者也都支持,实际项目遇到的可能性较小,文中没有介绍。Ace和CodeMirror的diff模式都依赖第三方开源库,而Monaco Editor自带diff功能。Monaco Editor与vscode同根同源,对于日常的vscode使用者,monaco editor相比之下无疑更加亲切,UI更加美观。CodeMirror重构之后的新版本同样值得期待。如果项目需要用到TypeScript,Ace和Monaco Editor可能是更好的选择。

最后附上编辑器demo的github链接,文中的代码片段均来自于此。