CodeMirror is a very old Web editor, currently in version v6, although V6 is still in beta, and today I introduce it in version 5.57.0. The Monaco Editor is much younger, but no less famous; Because it’s the same core code VSCode uses. Next, the author will compare the two editors in terms of “usage method”, “scalability” and “performance”.

Method of use

Initialization method

CodeMirror has a relatively large number of initialization methods, while Monaco has only one (this does not affect the implementation of various functions); But Monaco wins with her own Diff editor.

CodeMirror

Method 1: Insert into a container node, such as body or #root.

<div id="root"></div>
Copy the code
import CodeMirror from "codemirror";
import "codemirror/mode/sql/sql";
import "codemirror/lib/codemirror.css";

CodeMirror(document.getElementById("root"), {
  value: `-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS \`val1\`, @'val2', @global.'sql_mode', AS \ 'float_val\',.14 AS \ 'another_float\', 0.09e3 AS \ 'int_with_ESP \', 0xFA5 AS \ 'hex\', x'fa5' AS \ 'hex2\', 0b101 AS \`bin\`, b'101' AS \`bin2\`, DATE '1994-01-01' AS \`sql_date\`, { T "1994-01-01" } AS \`odbc_date\`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0; `.mode: "text/x-sql".indentWithTabs: true.smartIndent: true.lineNumbers: true.matchBrackets: true.autofocus: true
});
Copy the code

Method 2: Do any node operations in a function, such as replaceWith (replacing a node), insertBefore (inserting before a node), append, and so on.

<div id="root">
  <div id="replace"></div>
</div>
Copy the code
import CodeMirror from "codemirror";
import "codemirror/mode/sql/sql";
import "codemirror/lib/codemirror.css";

CodeMirror(
  editor= > {
    document.getElementById("replace").replaceWith(editor);
  },
  {
    value: `-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS \`val1\`, @'val2', @global.'sql_mode', AS \ 'float_val\',.14 AS \ 'another_float\', 0.09e3 AS \ 'int_with_ESP \', 0xFA5 AS \ 'hex\', x'fa5' AS \ 'hex2\', 0b101 AS \`bin\`, b'101' AS \`bin2\`, DATE '1994-01-01' AS \`sql_date\`, { T "1994-01-01" } AS \`odbc_date\`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0; `.mode: "text/x-sql".indentWithTabs: true.smartIndent: true.lineNumbers: true.matchBrackets: true.autofocus: true});Copy the code

Method 3: Directly replace a Textarea and take the value of the Textarea as the initial value of the editor.

<div id="root">
  <textarea id="textarea">-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS `val1`, @'val2', @global.'sql_mode', AS 'float_val',.14 AS 'another_float', 0.09E3 AS 'int_with_ESP', 0xFA5 AS 'hex', x'fa5' AS 'hex2', 0b101 AS 'bin', b'101' AS `bin2`, DATE '1994-01-01' AS `sql_date`, { T "1994-01-01" } AS `odbc_date`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0;</textarea>
</div>
Copy the code
import CodeMirror from "codemirror";
// The 5. X CodeMirror core code has been converted to es6 Module syntax;
// All mode files still use the requireJs writing syntax, so just import it
import "codemirror/mode/sql/sql";
import "codemirror/lib/codemirror.css";

CodeMirror.fromTextArea(document.getElementById("textarea"), {
  mode: "text/x-sql".indentWithTabs: true.smartIndent: true.lineNumbers: true.matchBrackets: true.autofocus: true
});

Copy the code
Monaco Editor

There are not that many ways to initialize the Monaco Editor, only the following, but this does not affect the requirements of various scenarios.

<div id="root"></div>
Copy the code
import { editor } from "monaco-editor";

// Make sure the container has a certain width and height
editor.create(document.getElementById("root"), {
  language: "sql".value: `-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS \`val1\`, @'val2', @global.'sql_mode', AS \ 'float_val\',.14 AS \ 'another_float\', 0.09e3 AS \ 'int_with_ESP \', 0xFA5 AS \ 'hex\', x'fa5' AS \ 'hex2\', 0b101 AS \`bin\`, b'101' AS \`bin2\`, DATE '1994-01-01' AS \`sql_date\`, { T "1994-01-01" } AS \`odbc_date\`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0; `
});
Copy the code

Effect of Diff

CodeMirror

CodeMirror has a diff mode to choose from among the existing modes. The specific effects are as follows:

.CodeMirror {
  border-top: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
}
span.cm-meta {
  color: #a0b ! important;
}
span.cm-error {
  background-color: black;
  opacity: 0.4;
}
span.cm-error.cm-string {
  background-color: red;
}
span.cm-error.cm-tag {
  background-color: #2b2;
}

Copy the code
import CodeMirror from "codemirror";
import "codemirror/mode/diff/diff";
import "codemirror/lib/codemirror.css";
import "./styles.css";

CodeMirror(document.getElementById("root"), {
  value: `diff --git a/index.html b/index.html index c1d9156.. 7764744 100644 -- a/index.html +++ + b/index.html @@-95,7 +95,8 @@stringstream. prototype = {  diff --git a/lib/codemirror.js b/lib/codemirror.js index 04646a9.. 9a39cc7 100644 -- a/lib/ codemiror.js +++ b/lib/ codemiror.js @@-399,10 +399,16 @@var codemirror = (function() {} function onMouseDown(e) { - var start = posFromMouse(e), last = start; + var start = posFromMouse(e), last = start, target = e.target(); if (! start) return; setCursor(start.line, start.ch, false); if (e.button() ! = 1) return; + if (target.parentNode == gutter) { + if (options.onGutterClick) + options.onGutterClick(indexOf(gutter.childNodes, target) + showingFrom); + return; + } + if (! focused) onFocus(); e.stop(); If (function() {for (var I = showingFrom; i < showingTo; ++i) { var marker = lines[i].gutterMarker; if (marker) html.push('
      
' + htmlEscape(marker.text) + '
'); - else html.push("
" + (options.lineNumbers ? i + 1 : "\u00a0") + "
"); + else html.push("
" + (options.lineNumbers ? i + options.firstLineNumber : "\u00a0") + "
"); } gutter.style.display = "none"; // TODO test whether this actually helps gutter.innerHTML = html.join(""); @@-1371,10 +1377,8 @@var CodeMirror = (function() {if (option == "parser") setParser(value); else if (option === "lineNumbers") setLineNumbers(value); else if (option === "gutter") setGutter(value); - else if (option === "readOnly") options.readOnly = value; - else if (option === "indentUnit") {options.indentUnit = indentUnit = value; setParser(options.parser); } - else if (/^(? :enterMode|tabMode|indentWithTabs|readOnly|autoMatchBrackets|undoDepth)$/.test(option)) options[option] = value; - else throw new Error("Can't set option " + option); + else if (option === "indentUnit") {options.indentUnit = value; setParser(options.parser); } + else options[option] = value; }, cursorCoords: cursorCoords, undo: Operation (undo), @@-1402,7 +1406,8 @@var CodeMirror = (function() {replaceRange: operation(replaceRange), operation: function(f){return operation(f)(); }, - refresh: function(){updateDisplay([{from: 0, to: lines.length}]); } + refresh: function(){updateDisplay([{from: 0, to: lines.length}]); }, + getInputField: function(){return input; }}; return instance; } @@-1420,6 +1425,7 @@var CodeMirror = (function() {readOnly: false, onChange: null, onCursorActivity: null, + onGutterClick: null, autoMatchBrackets: false, workTime: 200, workDelay: 300,`
.mode: "text/x-diff".indentWithTabs: true.smartIndent: true.lineNumbers: true.matchBrackets: true.autofocus: true }); Copy the code

As can be seen from the value value in the example, when using diff, you need to distinguish the content differences between the two files and then assemble the text content as shown in the example before using it, so as to achieve the desired effect. The renderings are as follows:

Monaco Editor

The Monaco Editor’s diff feature is much more powerful, for example:

import { editor } from "monaco-editor";
import "./styles.css";

const originalModel = editor.createModel(
  `(function (global, undefined) { "use strict"; undefinedVariable = {}; undefinedVariable.prop = 5; function initializeProperties(target, members) { var keys = Object.keys(members); var properties; var i, len; for (i = 0, len = keys.length; i < len; i++) { var key = keys[i]; var enumerable = key.charCodeAt(0) ! = = / * _ * / 95; var member = members[key]; if (member && typeof member === 'object') { if (member.value ! == undefined || typeof member.get === 'function' || typeof member.set === 'function') { if (member.enumerable === undefined) { member.enumerable = enumerable; } properties = properties || {}; properties[key] = member; continue; } } // These next lines will be deleted if (! enumerable) { properties = properties || {}; properties[key] = { value: member, enumerable: enumerable, configurable: true, writable: true } continue; } target[key] = member; } if (properties) { Object.defineProperties(target, properties); } } })(this); `."text/javascript"
);
var modifiedModel = editor.createModel(
  `(function (global, undefined) { "use strict"; var definedVariable = {}; definedVariable.prop = 5; function initializeProperties(target, members) { var keys = Object.keys(members); var properties; var i, len; for (i = 0, len = keys.length; i < len; i++) { var key = keys[i]; var enumerable = key.charCodeAt(0) ! = = / * _ * / 95; var member = members[key]; if (member && typeof member === 'object') { if (member.value ! == undefined || typeof member.get === 'function' || typeof member.set === 'function') { if (member.enumerable === undefined) { member.enumerable = enumerable; } properties = properties || {}; properties[key] = member; continue; } } target[key] = member; } if (properties) { Object.defineProperties(target, properties); } } })(this); `."text/javascript"
);

const diffEditor = editor.createDiffEditor(
  document.getElementById("root"),
  {
    enableSplitViewResizing: false}); diffEditor.setModel({original: originalModel,
  modified: modifiedModel
});

Copy the code

As you can see from the example above, once you provide what you want to compare directly, Monaco’s Diff Editor automatically compares and displays it. It is configurable to display the effects in one editor or in two. This is much more convenient than CodeMirror’s Diff mode. The renderings are as follows:

scalability

In terms of extension, the author will compare and introduce “add a new language (mode)” and “function extension”.

Add a new language (mode)

CodeMirror currently supports more than 100 modes (languages) and Monaco Editor supports dozens, both of which cover almost all the major languages. The other two editors have the ability to customize languages, so let’s look at them separately.

CodeMirror

CodeMirror registers a new mode (help document) with codemiror.definemode. The schematic code is as follows:

// The first argument: modeName with a lowercase name
// The second argument: a callback function that returns a pattern object
CodeMirror.defineMode("sql".function(config, parserConfig) {
  // config is the CodeMirror configuration object
  // parserConfig is an optional schema configuration object
  
  // Your schema definition code; Parse the contents of the editor
  
  return {
    token: (stream, state) = > { return style }, // Mandatory, returns the highlight style
    indent: (state, textAfter) = > { return 0 }, // Optional, define the indentation rule, return the number of indent Spaces
    // Other options
  };
}
Copy the code

CodeMirror also provides scenarios where innerMode is used for nested syntax, such as HTML mixing CSS and JavaScript syntax. The schematic code is as follows:

// CodeMirror source code
CodeMirror.defineMode("htmlmixed".function (config, parserConfig) {
	var htmlMode = CodeMirror.getMode(config, {
      name: "xml".htmlMode: true
    });
  
  return {
    // Define the initial state, which can be used to distinguish the current tag (style/script)
    startState: function () {
      var state = CodeMirror.startState(htmlMode);
      return {token: html, inTag: null.localMode: null.localState: null.htmlState: state};
    },
  	token: function (stream, state) {
      return state.token(stream, state);
    },
    innerMode: function (state) {
      return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
    },
    // Other items have been omitted, please consult CodeMirror source code
  };
}, "xml"."javascript"."css");
Copy the code

Note: To learn how to use nested modes, refer to the mode help documentation and the mode/ HTMLMixed content in the source code.

Once the mode is defined in CodeMirror, it needs to be associated with MIME, and the mode specified when used is actually MIME. CodeMirror is thus designed to solve the problem of quick definitions for similar languages. This is very common in SQL language, there are many kinds of relational databases, the basic syntax of these databases is basically the same, but many databases use non-standard SQL (such as MySQL, SQLServer, etc.), has its own characteristics, so using MIME to define different SQL language is very appropriate. An example of defining MIME is as follows:

CodeMirror.defineMIME("text/x-mysql", {
  name: 'sql'.// name Indicates the associated mode
  // Other configuration items are passed in as the second entry to the defineMode second callback
});
Copy the code

CodeMirror also provides the definition in the form of Simple Mode CodeMirror. DefineSimpleMode can be more convenient to define a new language, readers are interested to know.

Monaco Editor

The Monaco Editor can also register new languages, as shown in the following simple example:

// Register a new language
monaco.languages.register({ id: 'mySpecialLanguage' });

// Register a tokens provider for the language
// Define the body content of the new language here
monaco.languages.setMonarchTokensProvider('mySpecialLanguage', {
	tokenizer: { // CodeMirror token;
		root: [[/\[error.*/."custom-error"].// The first item is the matching rule, and the second item is the token name
			[/\[notice.*/."custom-notice"],
			[/\[info.*/."custom-info"],
			[/\[[a-zA-Z 0-9:]+\]/."custom-date"]]}});// Define a new theme that contains only rules that match this language
// In CodeMirror, styles are defined directly in the CSS file
monaco.editor.defineTheme('myCoolTheme', {
	base: 'vs'.inherit: false.rules: [
    // The token here corresponds to the tokenizer above,
		{ token: 'custom-info'.foreground: '808080' },
		{ token: 'custom-error'.foreground: 'ff0000'.fontStyle: 'bold' },
		{ token: 'custom-notice'.foreground: 'FFA500' },
		{ token: 'custom-date'.foreground: '008800'}]});Copy the code

Monaco’s language definition is configured, and nesting between languages is configured via the nextEmbedded property. The style tag in the HTML tag is defined as follows (see Monarch for more information) :

root: [
  [/<style\s*>/,   { token: 'keyword'.bracket: '@open'
                   , next: '@css_block'.nextEmbedded: 'text/css' }],

  [/<\/style\s*>/, { token: 'keyword'.bracket: '@close'}],... ]Copy the code

Monaco may seem easier to configure, but it is actually a bit more expensive to learn to use at first because you need to understand the meaning of each configuration attribute.

Function extension

In the editor, our most common features are intelligent hints, code folding, automatic closing of characters (such as ‘, “”), and so on. All of these are extra loaded as Addon in CodeMirror and built in at Monaco, as long as they are configured to be used as needed. Let’s take intelligent hints as an example to feel the difference.

CodeMirror
import CodeMirror from 'codemirror';
import 'codemirror/mode/sql/sql';
import 'codemirror/lib/codemirror.css';
// Introduce smart prompt plug-in
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/sql-hint';
import 'codemirror/addon/hint/show-hint.css';

const myCodeMirror = CodeMirror(document.getElementById('root'), {
  value: `-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS \`val1\`, @'val2', @global.'sql_mode', AS \ 'float_val\',.14 AS \ 'another_float\', 0.09e3 AS \ 'int_with_ESP \', 0xFA5 AS \ 'hex\', x'fa5' AS \ 'hex2\', 0b101 AS \`bin\`, b'101' AS \`bin2\`, DATE '1994-01-01' AS \`sql_date\`, { T "1994-01-01" } AS \`odbc_date\`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0; `.mode: 'text/x-sql'.indentWithTabs: true.smartIndent: true.lineNumbers: true.matchBrackets: true.autofocus: true}); myCodeMirror.on('change'.(cm, changeObj) = >{,const { origin, text = [] } = changeObj;
  if (origin === '+input' && text[0]) {
    // Execution can only be prompted
    cm.execCommand('autocomplete'); }});Copy the code
Monaco Editor
monaco.editor.create(document.getElementById("container"), {
    value: `-- SQL Mode for CodeMirror SELECT SQL_NO_CACHE DISTINCT @var1 AS \`val1\`, @'val2', @global.'sql_mode', AS \ 'float_val\',.14 AS \ 'another_float\', 0.09e3 AS \ 'int_with_ESP \', 0xFA5 AS \ 'hex\', x'fa5' AS \ 'hex2\', 0b101 AS \`bin\`, b'101' AS \`bin2\`, DATE '1994-01-01' AS \`sql_date\`, { T "1994-01-01" } AS \`odbc_date\`, 'my string', _utf8'your string', N'her string', TRUE, FALSE, UNKNOWN FROM DUAL -- space needed after '--' # 1 line comment /* multiline comment! */ LIMIT 1 OFFSET 0; `.language: "sql"
});

monaco.languages.registerCompletionItemProvider('json', {
  provideCompletionItems: function(model, position) {
    return {
      suggestions: [{label: '"lodash"'.kind: monaco.languages.CompletionItemKind.Function,
          documentation: "The Lodash library exported as Node.js modules.".insertText: '"lodash": "*"'.range: range }, ]; }; }});Copy the code

In the comparison of the two editors, it’s not clear which one has the better scalability. CodeMirror, however, is more loosely packaged, allowing developers to write whatever they want and even modify the mode or Addon source code directly. The Monaco Editor is more tightly encapsulated, requiring developers to re-develop within the framework rules it defines.

performance

The CodeMirror core file is only 70+KB compressed, while the Monaco Editor is 1.9 MB compressed, so CodeMirror performs slightly better when initialized. The reason for such a big difference in package files is that the author thinks that due to the loose encapsulation of CodeMirror, the processing of both functions and languages needs to be solved by what files are introduced. The Monaco Editor is more tightly encapsulated, with both Editor extensions and new languages being configured in Monaco, which means that configuration parsing is done inside the Editor.

The Monaco Editor’s performance is even better for large text processing. We copied 87M of SQL content (about 2.7 million lines) into two editors. The Monaco Editor felt stuck, but it was still able to parse and edit normally. But CodeMirror is basically in suspended animation, waiting for a long time to render. Normally, our text content would not be this large, and CodeMirror would be stress-free to handle content in the tens of megabytes.

I’m not trying to judge which of the two open source editors is better — both are definitely good. Readers can freely choose any one of them according to their preferences, and I believe that no matter which one can well meet the needs of readers.

The above is the author’s personal opinion, if there are mistakes, welcome to criticize and correct!

CodeMirror help: codemirror.net/ CodeMirror source code: github.com/codemirror/… Monaco Editor documentation: Microsoft. Making. IO/Monaco – edit…