CodeMirror — очень старомодный веб-редактор, он разработан до версии v6, но v6 все еще находится в стадии тестирования, сегодня я представлю его с версией 5.57.0. Редактор Monaco намного моложе, но, несмотря на это, репутация у него не маленькая, просто потому, что он использует тот же основной код, что и VSCode. Далее автор сравнит два редактора по трем аспектам: «метод использования», «масштабируемость» и «производительность».
инструкции
метод инициализации
Условно говоря, у CodeMirror методов инициализации больше, а у Monaco всего один (это не влияет на реализацию различных функций), но Monaco лучше собственного Diff-редактора.
CodeMirror
Способ 1. Вставьте в узел-контейнер, например body или #root.
<div id="root"></div>
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',
1.1 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
});
Способ 2. Выполните любую операцию с узлом в функции, например replaceWith (замените узел), insertBefore (вставьте перед узлом), добавьте и т. д.
<div id="root">
<div id="replace"></div>
</div>
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',
1.1 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
}
);
Способ 3: Непосредственно замените Textarea и используйте значение Textarea в качестве начального значения редактора.
<div id="root">
<textarea id="textarea">
-- SQL Mode for CodeMirror
SELECT SQL_NO_CACHE DISTINCT
@var1 AS `val1`, @'val2', @global.'sql_mode',
1.1 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>
import CodeMirror from "codemirror";
// 5.x版本的CodeMirror核心代码已经全部改造成了es6的Module语法;
// 但各种mode文件还依然使用着requireJs的书写语法,所以只要直接引入就好
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
});
Monaco Editor
Методов инициализации Monaco Editor не так много, только следующий, но это не влияет на требования различных сценариев.
<div id="root"></div>
import { editor } from "monaco-editor";
// 一定要保证容器有一定的宽度和高度
editor.create(document.getElementById("root"), {
language: "sql",
value: `-- SQL Mode for CodeMirror
SELECT SQL_NO_CACHE DISTINCT
@var1 AS \`val1\`, @'val2', @global.'sql_mode',
1.1 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;
`
});
Эффект разницы
CodeMirror
CodeMirror имеет режим сравнения в существующем режиме на выбор, конкретные эффекты заключаются в следующем:
.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;
}
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 = {
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
- autoMatchBrackets: true
+ autoMatchBrackets: true,
+ onGutterClick: function(x){console.log(x);}
});
</script>
</body>
diff --git a/lib/codemirror.js b/lib/codemirror.js
index 04646a9..9a39cc7 100644
--- a/lib/codemirror.js
+++ b/lib/codemirror.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();
@@ -808,7 +814,7 @@ var CodeMirror = (function() {
for (var i = showingFrom; i < showingTo; ++i) {
var marker = lines[i].gutterMarker;
if (marker) html.push('<div class="' + marker.style + '">' + htmlEscape(marker.text) + '</div>');
- else html.push("<div>" + (options.lineNumbers ? i + 1 : "\u00a0") + "</div>");
+ else html.push("<div>" + (options.lineNumbers ? i + options.firstLineNumber : "\u00a0") + "</div>");
}
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
});
Из значения примера видно, что при использовании diff вам нужно самостоятельно различать различия содержимого двух файлов, а затем собирать текстовое содержимое, как показано в примере, прежде чем использовать его для достижения ожидаемого эффекта. Схема эффекта выглядит следующим образом:
Monaco Editor
Напротив, функция сравнения Monaco Editor гораздо мощнее, см. пример:
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
});
Как видно из приведенного выше примера, если содержимое, которое необходимо сравнить, предоставлено напрямую, редактор различий Monaco автоматически сравнит и затем отобразит его. Можно настроить отображение эффекта в одном редакторе или отображение эффекта в двух редакторах. Это гораздо удобнее, чем режим сравнения CodeMirror. Схема эффекта выглядит следующим образом:
Масштабируемость
С точки зрения расширения автор сравнит и представит два аспекта «добавление нового языка (мода)» и «расширение функций».
Добавить новый язык (режим)
В настоящее время CodeMirror поддерживает более 100 типов.mode(язык), Monaco Editor поддерживает десятки из них, и оба охватывают почти основные языки. Кроме того, в этих двух редакторах есть возможность настройки языка, рассмотрим их отдельно.
CodeMirror
CodeMirror может пройтиCodeMirror.defineMode
зарегистрировать новый режим (справочная документация). Схематический код выглядит следующим образом:
// 第一个参数:用小写字母命名的modeName
// 第二个参数:回调函数,返回模式对象
CodeMirror.defineMode("sql", function(config, parserConfig) {
// config 是CodeMirror的配置对象
// parserConfig 是可选的模式配置对象
// 你的模式定义代码;解析编辑器的内容
return {
token: (stream, state) => { return style }, // 必选,返回高亮样式
indent: (state, textAfter) => { return 0 }, // 可选,定义缩进规则,返回缩进空格数
// 其他可选项
};
}
CodeMirror также предоставляетinnerMode
Используется для сценариев с вложенным синтаксисом, таких как HTML, где смешаны синтаксис CSS и JavaScript. Схематический код выглядит следующим образом:
// 代码来源于CodeMirror源码
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {
name: "xml",
htmlMode: true
});
return {
// 定义起始状态,这里可以区分当前是在哪个标签内(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};
},
// 其他项已经被省略,需要的小伙伴请自行查阅CodeMirror源码
};
}, "xml", "javascript", "css");
Примечание. Чтобы узнать, как использовать вложенные комбинации нескольких режимов, вы можете обратиться ксправочная документация режимасодержание, а в исходном кодеmode/htmlmixed
содержание в .
После того, как режим определен в CodeMirror, его необходимо связать с MIME.Режим, указанный при его использовании, на самом деле является MIME. Автор считает, что CodeMirror предназначен для решения проблемы определения сходных языков. Это очень распространено в языке SQL.Существует много видов реляционных баз данных.Основной синтаксис этих баз данных в основном одинаков, но нестандартный SQL (такой как MySQL, SQLServer и т. д.), используемый во многих базах данных, имеет свои особенности. собственные характеристики, поэтому используйте MIME для определения. Другой язык SQL идеально подходит. Пример определения MIME выглядит следующим образом:
CodeMirror.defineMIME("text/x-mysql", {
name: 'sql', // name 对应关联的mode
// 其他配置项,会作为 defineMode 第二个回调函数的第二个入参传入
});
Кроме того, CodeMirror также предоставляет определенияSimple ModeформаCodeMirror.defineSimpleMode
Совершенно новый язык можно определить более удобно, и заинтересованные читатели могут узнать о нем самостоятельно.
Monaco Editor
Monaco Editor также может регистрировать новые языки, простой пример:
// Register a new language
monaco.languages.register({ id: 'mySpecialLanguage' });
// Register a tokens provider for the language
// 定义新语言的主体内容在这里
monaco.languages.setMonarchTokensProvider('mySpecialLanguage', {
tokenizer: { // 相当于CodeMirror中的token;
root: [
[/\[error.*/, "custom-error"], // 第一项是匹配规则,第二项是token名
[/\[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
// 在CodeMirror中是直接在css文件中定义样式
monaco.editor.defineTheme('myCoolTheme', {
base: 'vs',
inherit: false,
rules: [
// 这里的token跟上面的tokenizer一一对应,
{ token: 'custom-info', foreground: '808080' },
{ token: 'custom-error', foreground: 'ff0000', fontStyle: 'bold' },
{ token: 'custom-notice', foreground: 'FFA500' },
{ token: 'custom-date', foreground: '008800' },
]
});
Определение языка Monaco настраивается, а вложение между языками осуществляется черезnextEmbedded
атрибут для настройки в теге HTMLstyle
Определение тега выглядит следующим образом (дополнительную информацию см.Monarch):
root: [
[/<style\s*>/, { token: 'keyword', bracket: '@open'
, next: '@css_block', nextEmbedded: 'text/css' }],
[/<\/style\s*>/, { token: 'keyword', bracket: '@close' }],
...
]
Monaco кажется проще в настройке, но на самом деле, чтобы понять смысл каждого атрибута конфигурации, стоимость обучения его использованию в начале немного высока.
расширение функции
В редакторе нашими наиболее распространенными функциями являются интеллектуальные подсказки, свертывание кода, автоматические закрывающие символы (такие как '', "") и так далее. Все они дополнительно подгружаются в виде аддонов в CodeMirror, а в Monaco уже встроены, главное, чтобы они были настроены и использовались по мере необходимости. Давайте возьмем умные подсказки в качестве примера, чтобы почувствовать разницу между ними.
CodeMirror
import CodeMirror from 'codemirror';
import 'codemirror/mode/sql/sql';
import 'codemirror/lib/codemirror.css';
// 引入智能提示插件
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',
1.1 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]) {
// 执行只能提示
cm.execCommand('autocomplete');
}
});
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',
1.1 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
},
];
};
}
});
При сравнении этих двух редакторов не сказано, какой из них более масштабируемый. Но автор считает, что инкапсуляция CodeMirror более свободная, и разработчики могут писать код на его основе «как хотят», и даже могут напрямую модифицировать исходный код мода или аддона; инкапсуляция Monaco Editor более строгая, и разработчики необходимо определить его в своей вторичной разработке, осуществляемой в рамках правил.
представление
Размер основного файла CodeMirror составляет всего 70+ КБ после сжатия, тогда как размер Monaco Editor после сжатия составляет 1,9 МБ, поэтому производительность CodeMirror во время инициализации немного выше. Причина такой большой разницы в файлах пакетов заключается в том, что автор считает, что благодаря свободной инкапсуляции CodeMirror, будь то обработка функций или языков, какие файлы необходимо использовать и какие файлы вводятся для решения; и Инкапсуляция Monaco Editor более Строго, будь то расширение функций редактора или новый язык, в Monaco это делается в виде конфигурации, а это значит, что парсинг конфигурации производится внутри редактора.
При обработке большого текста производительность Monaco Editor еще выше. Автор скопировал более 87M SQL-контента (около 2,7 млн строк контента) в два редактора, Monaco Editor застрял, но его все еще можно было нормально разобрать и отредактировать; но CodeMirror был в основном в приостановленном состоянии, потребовалось много времени, чтобы смотрите рендеринг. При нормальных обстоятельствах наш текстовый контент не будет таким большим, и CodeMirror не обязан обрабатывать контент размером более десяти мегабайт.
Я не собираюсь судить, какой из двух редакторов с открытым исходным кодом лучше — оба, несомненно, превосходны. Читатели могут свободно выбирать любой из них для использования в соответствии со своими предпочтениями.Я считаю, что независимо от того, какой из них используется, он вполне может удовлетворить потребности читателей.
Вышесказанное является личным мнением автора, если есть ошибки, прошу покритиковать и исправить!
Использованная литература: Справочная документация CodeMirror:codemirror.net/Исходный код CodeMirror:GitHub.com/co зеркало/…Справочная документация редактора Monaco:Microsoft.GitHub.IO/Monaco-edit…