Monaco Editor 是一个从 VS Code 中分离出来的、网页版代码编辑器,由微软开源、界面美观、功能强大、开箱即用。(PS:要吐槽一下 Monaco Editor 的官方文档 ...... 是真的难读😕 )
❓ What is the relationship between VS Code and the Monaco Editor? The Monaco Editor is generated straight from VS Code's sources with some shims around services the code needs to make it run in a web browser outside of its home.
Monaco Editor 在开源社区也是非常火爆的一个项目(36.2k star),如果你需要在产品中嵌入一个代码编辑器组件,可以先试试 Monaco Editor。😄
Monaco Editor 不仅是一个功能强大的代码编辑器,它还内置了一个代码比对组件(DiffEditor)(如下图)。
下面将完整演示如何在Vite、React环境下接入微软开源组件 Monaco Editor,并使用它的代码比对(DiffEditor)特性。
首先,使用Vite脚手架快速搭建一个基于 Vite 的 React 应用。
npm create vite@latest react-monaco-diff -- --template react-ts
然后,在项目中安装 Monaco Editor 组件。
npm install monaco-editor
Moncao Editor 的运行,需要一些 web worker 支撑,它们负责对 Monaco Editor 提供语言服务(例如:语法高亮、语法检验等)。(PS:我们不需了解这些 web worker 的工作细节,只需要让它们跑起来😄)
❓ Why all these web workers and why should I care? Language services create web workers to compute heavy stuff outside of the UI thread. They cost hardly anything in terms of resource overhead and you shouldn't worry too much about them, as long as you get them to work (see above the cross-domain case).
在 Vite 环境下,Monaco Editor在官方文档已经给出了配置方法,我们可以选择在应用入口引入如下代码:
// Since packaging is done by you, you need
// to instruct the editor how you named the
// bundles that contain the web workers.
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === 'json') {
return './json.worker.bundle.js';
}
if (label === 'css' || label === 'scss' || label === 'less') {
return './css.worker.bundle.js';
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return './html.worker.bundle.js';
}
if (label === 'typescript' || label === 'javascript') {
return './ts.worker.bundle.js';
}
return './editor.worker.bundle.js';
}
};
PS:如果你使用的是Webpack,MonacoEditor也给出了相关说明。
MonacoEditor 需要一个 div
作为渲染容器。
注:MonacoEditor 不会自动“撑开”这个渲染容器,所以别忘了给容器设置样式,让它可见。
const diffDOM = useRef<HTMLDivElement>(null);
<div ref={diffDOM} style={{ height: 400 }}></div>
MonacoEditor 的代码比对能力,由 DiffEditor 组件提供,可以通过monaco.editor.createDiffEditor
创建,并通过setModel
接口设置需要比对的代码片段。
// 创建 DiffEditor 实例
const editorRef = useRef<monaco.editor.IStandaloneDiffEditor>(null);
editorRef.current = monaco.editor.createDiffEditor(diffDOM.current!, {
diffCodeLens: true,
readOnly: true,
automaticLayout: true,
});
// 给 DiffEditor 设置需要比对的代码
const originalModel = monaco.editor.createModel(oldCode, "text/plain");
const modifiedModel = monaco.editor.createModel(newCode, "text/plain");
editorRef.current.setModel({
original: originalModel,
modified: modifiedModel,
});
MonacoEditor 使用完成后,别忘了及时销毁前面创建出来的实例对象。
// 销毁 DiffEditor 实例
editorRef.current?.dispose();
import { useRef, useEffect } from "react";
import * as monaco from "monaco-editor";
self.MonacoEnvironment = {
getWorker: function (workerId, label) {
const getWorkerModule = (moduleUrl, label) => {
return new Worker(self.MonacoEnvironment.getWorkerUrl(moduleUrl), {
name: label,
type: "module",
});
};
switch (label) {
case "json":
return getWorkerModule(
"/monaco-editor/esm/vs/language/json/json.worker?worker",
label
);
case "css":
case "scss":
case "less":
return getWorkerModule(
"/monaco-editor/esm/vs/language/css/css.worker?worker",
label
);
case "html":
case "handlebars":
case "razor":
return getWorkerModule(
"/monaco-editor/esm/vs/language/html/html.worker?worker",
label
);
case "typescript":
case "javascript":
return getWorkerModule(
"/monaco-editor/esm/vs/language/typescript/ts.worker?worker",
label
);
default:
return getWorkerModule(
"/monaco-editor/esm/vs/editor/editor.worker?worker",
label
);
}
},
};
const oldCode = `function bubble() {
const arr = new Array(100).fill(1).map(() => Math.random());
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
const t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}
}`;
const newCode = `function bubble() {
const arr = new Array(100).fill(1).map((t, i) => '张三'+i);
for (let i = 0; i < arr.length; i++) {
console.log('round:'+i);
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
const t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}
}`;
function App() {
const diffDOM = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneDiffEditor>(null);
useEffect(() => {
editorRef.current = monaco.editor.createDiffEditor(diffDOM.current!, {
diffCodeLens: true,
readOnly: true,
automaticLayout: true,
});
const originalModel = monaco.editor.createModel(oldCode, "text/plain");
const modifiedModel = monaco.editor.createModel(newCode, "text/plain");
editorRef.current.setModel({
original: originalModel,
modified: modifiedModel,
});
return () => {
editorRef.current?.dispose();
};
}, []);
return (
<div style={{ padding: 32 }}>
<div ref={diffDOM} style={{ height: 400 }}></div>
</div>
);
}
export default App;
参考