浏览器中的编辑器
我们已经习惯了使用编辑器来编写代码,作为一名程序员无论选择什么编辑器来开发,都是为了提高我们的工作效率,以及撸代码爽的程度。
作为一名前端开发人员,有时候难免会遇到需要在浏览器中书写代码的场景。无论是用户需要还是我们自己为了开发便利,这都将是有益的。
在网页中编辑很容易,但是能不能让我们像在本地编辑器中那样编写代码,提供丰富的功能呢?目前有很多个开源项目针对此问题实现了各自的一套解决方案。在此就以monaco-editor集成到vue项目中为例,为大家解决此类问题,以及期望能够帮助大家快速入手实践。
如上图所示:这是在网页中编辑代码的效果,没错,这不是在vscode中,就是浏览器中的页面上。不但提供了代码高亮、代码提示、代码补全、搜索、替换等功能,还有右键格式化等其他功能,以及命令面板的功能等。可以说是能力丰富、功能强大。下面就逐步说明从开始到应用的集成步骤。
一、安装我一共用到了五个npm包:
npm install monaco-editor -S
npm install monaco-editor-nls -S
npm install monaco-editor-webpack-plugin -D
npm install monaco-editor-esm-webpack-plugin -D
npm install js-beautify -S
下面来对这五个包做一下简要的说明:
monaco-editor:是网页编辑器的核心包,整体非常大,因为支持了很多的语言与很多的扩展功能。
monaco-editor-webpack-plugin:因为monaco-editor直接单独引入的情况下所支持的基本使用不能满足我们的需求,我们还需要支持智能提示等功能,所以需要额外单独做一些配置操作,虽然官网文档说明已经很清晰,但是配置起来还是不免比较繁琐,因此提供了这个webpack插件来帮助我们自动处理这些事情,简化我们的操作。
monaco-editor-nls:是对整个编辑器的汉化处理,如果需要进行汉化,那么需要安装此包。
monaco-editor-esm-webpack-plugin:针对汉化包所做的webpack插件,需要和汉化包配合使用。
js-beautify:是用来做代码美化的,主要是做一些格式化的工作。
安装完成之后,就可以配置到我们的项目中进行应用啦。
二、配置vue.config.js:首先要在这个文件中引入插件,并设置相应的配置项。
vue.config.js配置
首先将插件引入进来。
const MonacoWebpackPlugin = require('monaco-editor-esm-webpack-plugin')
然后设置对应的属性。
module: {
rules: [
{
test: /\.js/,
enforce: 'pre',
include: /node_modules[\\\/]monaco-editor[\\\/]esm/,
use: MonacoWebpackPlugin.loader
}
]
},
plugins: [
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'html', 'css', 'json']
})
]
其中languages指定要支持的语言,因为完整版支持的语言非常多,如果使用默认的配置,那么包会非常的大,因此只选择我们需要的语言。其中javascript和typescript必须同时出现。根据官网的说法是:由于是使用web worker的方式进行实现的,每个web worker负责的工作内容不同,某些语言可能会共用同一个webwork,但是有一些语言需要额外引入其他语言的支持来负责实例化该语言的共享工作进程。如下:
需要额外的语言支持的部分语言
即:要引入language语言,必须引入instantiator。
另外还有一个跟languages同级的features属性来精确的配置要使用的特性。这里默认使用全部。
项目vue文件:在自己的项目vue文件中,引入monaco-editro及其他相关项。
const beautify = require('js-beautify')
import { setLocaleData } from 'monaco-editor-nls'
import zh_CN from 'monaco-editor-nls/locale/zh-hans'
setLocaleData(zh_CN)
// import * as monaco from 'monaco-editor'
const monaco = require('monaco-editor')
const beautify_js = beautify.js
const beautify_css = beautify.css
const beautify_html = beautify.html
其中:
const beautify = require('js-beautify')
引入美化代码工具,可分别对js、css、html代码进行格式化。
import { setLocaleData } from 'monaco-editor-nls'
import zh_CN from 'monaco-editor-nls/locale/zh-hans'
setLocaleData(zh_CN)
完成monaco-editor的汉化操作。
const monaco = require('monaco-editor')
引入编辑器核心包,注意:如果引入了汉化包,那么必须要用require的形式引入该项,否则可以使用import的形式。而且必须要在汉化完成之后再引入,不然汉化将不生效。
<template>
<div class="monaco-editor" id="monaco-editor">
</div>
</template>
在template中设定编辑器元素,作为根元素来渲染整个编辑器,注意该元素不能有子元素存在。
editorBox = monaco.editor.create(document.getElementById("monaco-editor"), {
value: "let a = 1;const b = 2",
language: "javascript",
theme: "vs-dark",
tabSize: 2
})
实例化编辑器,此时可以在页面中进行使用。第一个参数为要挂载的元素,第二个参数为配置选项。
value:编辑器的内容。
language:编辑器的当前实例所支持的语言。
theme:编辑器的主题样式,支持vs、vs-dark、hc-black三种。
tabSize:表示缩进的距离。
更多配置项可以查阅官网进行设置。
到此为止我们就可以在网页中编辑代码啦!
三、高级功能右键:
editorBox.addAction({
id: 'formateCodeForce',
label: '强制格式化',
keybindings: [monaco.KeyMod.CtrlCmd|monaco.KeyMod.Alt|monaco.KeyCode.KeyF],
contextMenuGroupId: '2_customCommand',
run(ed, opt) {
let a = editorBox.getValue()
let b = beautify_js(a)
// editorBox.setValue(b)
editorBox.executeEdits("", [
{
range: new monaco.Range(1, 1, editorBox.getModel().getLineCount() 1, 1),
text: b
}
])
}
})
可以在右键面板中增加自定义功能:
id:该选项的唯一标识。
label:选项显示的名称。
keybindings:绑定的快捷键,多个快捷键用竖线分割。每个按键要使用monaco的内置枚举类型来设定。
contextMenuGroupId:选项所属组的id,内置了三个组id(navigation:该组是右键的第一栏,1_modification:修改组,9_cutcopypaste:剪切复制粘贴组)。
run:选择该选项之后的回调函数,第一个参数为editorBox实例,第二个参数为一个剩余参数的数组,注意如果通过快捷键触发那么第二个参数不存在。因为我实现了一个格式化代码的功能,所以我在run函数中主要进行了三步操作:
1.使用getValue获取编辑器内容,这里有一个注意的点,如果实例化的编辑器是用vue中的data里面定义的数据接收的,由于vue的响应式更新,会造成死循环而无法获取导致页面卡死,可以用三个方法解决,根据不同的情况选择不同的方式,第一如果是用this.editorBox接收的,那么editorBox不要写在data里面,这样就不会做数据追踪,第二可以使用非vue实例化的属性。第三可以用toRaw来获取原始数据,也可以解决。
2.使用beautify_js对代码进行格式化。
3.将格式化后的代码重新回写到编辑器中,这里有两种方式setValue和executeEdits。区别是setValue不能撤销回上一步,而executeEdits可以。其中range表示选择的范围,我这里从第一行第一列一直到最后一行的下一行的第一列,相当于是全部文档,然后用text的值,也就是格式化后的代码来进行替换。
右键面板
自定义补全:
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems(model, pos) {
return {
suggestions: [
{
label: "row-col",
insertText: beautify_js(`[{"cols":[{"span":6},{"span":6}]}]`),
kind: monaco.languages.CompletionItemKind["Snippet"],
detail: "插入行列",
},
{
label: "for-full",
insertText: beautify_js(`for (let i = 0; i < len; i ) {}`),
kind: monaco.languages.CompletionItemKind["Snippet"],
detail: "完整for循环",
}
],
dispose() {
const line = pos.lineNumber
const column = pos.column
if(model.getValueInRange(new monaco.Range(line, column - 1, line, column)) !== "/") {
return
}
editorBox.executeEdits("", [
{
range: new monaco.Range(line, column - 1, line, column),
text: null
}
])
}
}
},
triggerCharacters: ['/']
})
triggerCharacters表示触发补全操作的快捷键,编辑器会提示出suggestions中设置的内容。注意回车之后输入的快捷键仍然是存在的。
suggestions中的label表示提示的名称,insertText表示回车之后输入的内容,kind用来设定提示片段的类型,即最前面的图标标识,detail是提示的说明,位于提示行的末尾。
效果如下图所示:
补全提示
由于使用triggerCharacters设定的快捷键输入,回车之后该快捷键依然存在,对于此场景来说相当于多余的字符,所以我做了一下处理,在回车之后将"/"删除掉,dispose回调函数就是用来做这个事情的。由于其他正常提示的情况下是不需要删除符号的,因此做了一下判断。
(提示:自定义的补全提示功能,除了使用triggerCharacters定义的快捷键会触发定义的一组提示,输入label中的首个字符,也会触发相应提示,这种情况不会产生多余的字符)
差异对比:
let curModel = monaco.editor.createModel("let a = 1\nlet c = 3\nlet n = 2", "text/plain")
let oldModel = monaco.editor.createModel("let a = 1\nlet n = 2", "text/plain")
let diffEditor = monaco.editor.createDiffEditor(document.getElementById("monaco-editor"), {
theme: "vs-dark"
})
diffEditor.setModel({
original: oldModel,
modified: curModel
})
通过创建两个不同的model来对比代码的变化
代码差异对比
注意事项:由于版本的不同可能会导致无法使用的问题。
对于较低版本vue-cli创建的项目,默认使用webpack4,那么对于最新的monaco-editor,则会出现问题,最大的支持版本为0.30.1,其他包的版本也需要相应调整。
对于高版本vue-cli创建的项目,默认使用webpack5,或者使用vite创建的项目是可以支持最新的monaco-editor版本的。
对于版本的支持,做如下标示:
较低版本
monaco-editor@0.30.1
monaco-editor-webpack-plugin@6.0.0
monaco-editor-nls@2.0.0
较高版本
monaco-editor@0.32.0 //或更新
monaco-editor-webpack-plugin //已经与monaco-editor统一管理,最新版本即可
monaco-editor-nls@3.0.0 //或更新
monaco-editor支持的功能非常丰富,几乎包含了vscode所有的基础功能,大家可以在此基础上自由发挥,实现更多有用的功能。希望能为各位开发者们略尽我的绵薄之力,更好的帮助大家完成工作需要。
,