
我們已經(jīng)習(xí)慣了使用編輯器來編寫代碼,作為一名程序員無論選擇什么編輯器來開發(fā),都是為了提高我們的工作效率,以及擼代碼爽的程度。
作為一名前端開發(fā)人員,有時候難免會遇到需要在瀏覽器中書寫代碼的場景。無論是用戶需要還是我們自己為了開發(fā)便利,這都將是有益的。
在網(wǎng)頁中編輯很容易,但是能不能讓我們像在本地編輯器中那樣編寫代碼,提供豐富的功能呢?目前有很多個開源項目針對此問題實現(xiàn)了各自的一套解決方案。在此就以monaco-editor集成到vue項目中為例,為大家解決此類問題,以及期望能夠幫助大家快速入手實踐。

如上圖所示:這是在網(wǎng)頁中編輯代碼的效果,沒錯,這不是在vscode中,就是瀏覽器中的頁面上。不但提供了代碼高亮、代碼提示、代碼補(bǔ)全、搜索、替換等功能,還有右鍵格式化等其他功能,以及命令面板的功能等??梢哉f是能力豐富、功能強(qiáng)大。下面就逐步說明從開始到應(yīng)用的集成步驟。
一、安裝
我一共用到了五個npm包:
npminstallmonaco-editor -Snpminstallmonaco-editor-nls -Snpminstallmonaco-editor-webpack-plugin-Dnpminstallmonaco-editor-esm-webpack-plugin-Dnpminstalljs-beautify -S
下面來對這五個包做一下簡要的說明:
monaco-editor:是網(wǎng)頁編輯器的核心包,整體非常大,因為支持了很多的語言與很多的擴(kuò)展功能。
monaco-editor-webpack-plugin:因為monaco-editor直接單獨(dú)引入的情況下所支持的基本使用不能滿足我們的需求,我們還需要支持智能提示等功能,所以需要額外單獨(dú)做一些配置操作,雖然官網(wǎng)文檔說明已經(jīng)很清晰,但是配置起來還是不免比較繁瑣,因此提供了這個webpack插件來幫助我們自動處理這些事情,簡化我們的操作。
monaco-editor-nls:是對整個編輯器的漢化處理,如果需要進(jìn)行漢化,那么需要安裝此包。
monaco-editor-esm-webpack-plugin:針對漢化包所做的webpack插件,需要和漢化包配合使用。
js-beautify:是用來做代碼美化的,主要是做一些格式化的工作。
安裝完成之后,就可以配置到我們的項目中進(jìn)行應(yīng)用啦。
二、配置
vue.config.js:首先要在這個文件中引入插件,并設(shè)置相應(yīng)的配置項。

首先將插件引入進(jìn)來。
const?MonacoWebpackPlugin?=?require('monaco-editor-esm-webpack-plugin')
然后設(shè)置對應(yīng)的屬性。
module: {rules: [? ? {test: /\.js/,enforce:'pre',include: /node_modules[\\\/]monaco-editor[\\\/]esm/,use: MonacoWebpackPlugin.loader? ? }? ]},plugins: [? new MonacoWebpackPlugin({languages: ['javascript','typescript','html','css','json']? })]
其中l(wèi)anguages指定要支持的語言,因為完整版支持的語言非常多,如果使用默認(rèn)的配置,那么包會非常的大,因此只選擇我們需要的語言。其中javascript和typescript必須同時出現(xiàn)。根據(jù)官網(wǎng)的說法是:由于是使用web worker的方式進(jìn)行實現(xiàn)的,每個web worker負(fù)責(zé)的工作內(nèi)容不同,某些語言可能會共用同一個webwork,但是有一些語言需要額外引入其他語言的支持來負(fù)責(zé)實例化該語言的共享工作進(jìn)程。如下:

即:要引入language語言,必須引入instantiator。
另外還有一個跟languages同級的features屬性來精確的配置要使用的特性。這里默認(rèn)使用全部。
項目vue文件:在自己的項目vue文件中,引入monaco-editro及其他相關(guān)項。
constbeautify =require('js-beautify')import{ setLocaleData }from'monaco-editor-nls'importzh_CNfrom'monaco-editor-nls/locale/zh-hans'setLocaleData(zh_CN)// import * as monaco from 'monaco-editor'constmonaco =require('monaco-editor')constbeautify_js = beautify.jsconstbeautify_css = beautify.cssconstbeautify_html = beautify.html
其中:
const?beautify?=?require('js-beautify')
引入美化代碼工具,可分別對js、css、html代碼進(jìn)行格式化。
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中設(shè)定編輯器元素,作為根元素來渲染整個編輯器,注意該元素不能有子元素存在。
editorBox = monaco.editor.create(document.getElementById("monaco-editor"), {value:"let a = 1;const b = 2",language:"javascript",? theme:"vs-dark",? tabSize:2})
實例化編輯器,此時可以在頁面中進(jìn)行使用。第一個參數(shù)為要掛載的元素,第二個參數(shù)為配置選項。
value:編輯器的內(nèi)容。
language:編輯器的當(dāng)前實例所支持的語言。
theme:編輯器的主題樣式,支持vs、vs-dark、hc-black三種。
tabSize:表示縮進(jìn)的距離。
更多配置項可以查閱官網(wǎng)進(jìn)行設(shè)置。
到此為止我們就可以在網(wǎng)頁中編輯代碼啦!
三、高級功能
右鍵:
editorBox.addAction({id:'formateCodeForce',? label:'強(qiáng)制格式化',? 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:該選項的唯一標(biāo)識。
label:選項顯示的名稱。
keybindings:綁定的快捷鍵,多個快捷鍵用豎線分割。每個按鍵要使用monaco的內(nèi)置枚舉類型來設(shè)定。
contextMenuGroupId:選項所屬組的id,內(nèi)置了三個組id(navigation:該組是右鍵的第一欄,1_modification:修改組,9_cutcopypaste:剪切復(fù)制粘貼組)。
run:選擇該選項之后的回調(diào)函數(shù),第一個參數(shù)為editorBox實例,第二個參數(shù)為一個剩余參數(shù)的數(shù)組,注意如果通過快捷鍵觸發(fā)那么第二個參數(shù)不存在。因為我實現(xiàn)了一個格式化代碼的功能,所以我在run函數(shù)中主要進(jìn)行了三步操作:
1.使用getValue獲取編輯器內(nèi)容,這里有一個注意的點(diǎn),如果實例化的編輯器是用vue中的data里面定義的數(shù)據(jù)接收的,由于vue的響應(yīng)式更新,會造成死循環(huán)而無法獲取導(dǎo)致頁面卡死,可以用三個方法解決,根據(jù)不同的情況選擇不同的方式,第一如果是用this.editorBox接收的,那么editorBox不要寫在data里面,這樣就不會做數(shù)據(jù)追蹤,第二可以使用非vue實例化的屬性。第三可以用toRaw來獲取原始數(shù)據(jù),也可以解決。
2.使用beautify_js對代碼進(jìn)行格式化。
3.將格式化后的代碼重新回寫到編輯器中,這里有兩種方式setValue和executeEdits。區(qū)別是setValue不能撤銷回上一步,而executeEdits可以。其中range表示選擇的范圍,我這里從第一行第一列一直到最后一行的下一行的第一列,相當(dāng)于是全部文檔,然后用text的值,也就是格式化后的代碼來進(jìn)行替換。

自定義補(bǔ)全:
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(leti =0; i < len; i++) {}`),? ? ? ? ? kind: monaco.languages.CompletionItemKind["Snippet"],? ? ? ? ? detail:"完整for循環(huán)",? ? ? ? }? ? ? ],? ? ? dispose() {constline = pos.lineNumberconstcolumn = pos.columnif(model.getValueInRange(new monaco.Range(line, column -1, line, column)) !=="/") {return}? ? ? ? editorBox.executeEdits("", [? ? ? ? ? {? ? ? ? ? ? range:newmonaco.Range(line, column -1, line, column),? ? ? ? ? ? text:null}? ? ? ? ])? ? ? }? ? }? },? triggerCharacters: ['/']})
triggerCharacters表示觸發(fā)補(bǔ)全操作的快捷鍵,編輯器會提示出suggestions中設(shè)置的內(nèi)容。注意回車之后輸入的快捷鍵仍然是存在的。
suggestions中的label表示提示的名稱,insertText表示回車之后輸入的內(nèi)容,kind用來設(shè)定提示片段的類型,即最前面的圖標(biāo)標(biāo)識,detail是提示的說明,位于提示行的末尾。
效果如下圖所示:

由于使用triggerCharacters設(shè)定的快捷鍵輸入,回車之后該快捷鍵依然存在,對于此場景來說相當(dāng)于多余的字符,所以我做了一下處理,在回車之后將"/"刪除掉,dispose回調(diào)函數(shù)就是用來做這個事情的。由于其他正常提示的情況下是不需要刪除符號的,因此做了一下判斷。
(提示:自定義的補(bǔ)全提示功能,除了使用triggerCharacters定義的快捷鍵會觸發(fā)定義的一組提示,輸入label中的首個字符,也會觸發(fā)相應(yīng)提示,這種情況不會產(chǎn)生多余的字符)
差異對比:
letcurModel = monaco.editor.createModel("let a = 1\nlet c = 3\nlet n = 2","text/plain")letoldModel = monaco.editor.createModel("let a = 1\nlet n = 2","text/plain")letdiffEditor = monaco.editor.createDiffEditor(document.getElementById("monaco-editor"), {theme:"vs-dark"})diffEditor.setModel({original: oldModel,modified: curModel})
通過創(chuàng)建兩個不同的model來對比代碼的變化

注意事項:
由于版本的不同可能會導(dǎo)致無法使用的問題。
對于較低版本vue-cli創(chuàng)建的項目,默認(rèn)使用webpack4,那么對于最新的monaco-editor,則會出現(xiàn)問題,最大的支持版本為0.30.1,其他包的版本也需要相應(yīng)調(diào)整。
對于高版本vue-cli創(chuàng)建的項目,默認(rèn)使用webpack5,或者使用vite創(chuàng)建的項目是可以支持最新的monaco-editor版本的。
對于版本的支持,做如下標(biāo)示:
較低版本monaco-editor@0.30.1monaco-editor-webpack-plugin@6.0.0monaco-editor-nls@2.0.0
較高版本monaco-editor@0.32.0//或更新monaco-editor-webpack-plugin//已經(jīng)與monaco-editor統(tǒng)一管理,最新版本即可monaco-editor-nls@3.0.0//或更新
總的來說
monaco-editor支持的功能非常豐富,幾乎包含了vscode所有的基礎(chǔ)功能,大家可以在此基礎(chǔ)上自由發(fā)揮,實現(xiàn)更多有用的功能。希望能為各位開發(fā)者們略盡我的綿薄之力,更好的幫助大家完成工作需要。