在 Vite 项目封装 VScode 官方编辑器 monaco-editor
前言
在很多网站的在线编辑代码都有非常智能的代码提示,其中大部分网站都会使用monaco-editor
, vscode 的代码编辑器就是monaco-editor 所以我们能在网页使用到 vscode 的代码编辑。今天就和大家一起来封装一个简单的编辑器组件。
效果
开整
先通过 npm 下载monaco-editor
,我这里使用 pnpm。
pnpm add monaco-editor
我们成功安装了 monaco-editor
,我这里安装的版本是最新版 0.44.0
"dependencies": {
"monaco-editor": "^0.44.0",
"vue": "^3.3.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.4.5"
}
然后我们在components
目录下新建一个codeEdit
组件
html 部分我们只需要一个有 ID 属性 div 即可
<template>
<div id="monaco"></div>
</template>
然后是 js 部分
首先导入依赖
import * as monaco from 'monaco-editor'
// 下面几个都是基于web-worker的语法提示依赖
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import { nextTick, ref, onBeforeUnmount, defineProps } from 'vue'
然后使用 web-worker
让编辑器支持对应的语法提示
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new jsonWorker()
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorker()
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorker()
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker()
}
return new editorWorker()
},
}
然后我们新建一个初始化编辑器的方法,对应的注释我已经写上,大家可以自行修改自己的需求
let editor = null
const editorInit = () => {
nextTick(() => {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSyntaxValidation: false,
})
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016, //
allowNonTsExtensions: true,
})
!editor
? (editor = monaco.editor.create(document.getElementById('monaco'), {
language: 'javascript', // 语言
value: '', // 编辑器初始显示文字
automaticLayout: true, // 自适应布局
theme: Props.theme, // 官方自带三种主题vs, hc-black, or vs-dark
foldingStrategy: 'indentation', // 折叠代码策略 indentation 缩进,markbegin 标记
renderLineHighlight: 'all', // 行亮
selectOnLineNumbers: true, // 显示行号
minimap: {
enabled: false,
},
readOnly: false, // 只读
fontSize: 16, // 字体大小
scrollBeyondLastLine: false, // 取消代码后面一大段空白
overviewRulerBorder: false, // 不要滚动条的边框
cursorStyle: 'line', // 光标样式
automaticLayout: false, // 自动布局
tabSize: 2, // tab缩进长度
autoIndent: true, // 自动缩进
cursorStyle: 'line', // 光标样式
automaticLayout: false, // 自动布局
tabSize: 2, // tab缩进长度
autoIndent: true, // 自动缩进
}))
: editor.setValue('')
})
}
别忘了调用
editorInit()
然后还需要在页面销毁时,销毁编辑器实例
onBeforeUnmount(() => {
editor.dispose()
})
然后我们在 App.vue 里面导入这个组件看看效果
<script setup>
import codeEdit from './components/codeEdit/index.vue'
</script>
<template>
<codeEdit />
</template>
就不贴图了,大家自行测试吧。
封装
在前面我们在配置中写死了一些配置,例如主题,语言,value 等,我们一般在项目中使用到编辑器一般是做为子组件的,而且需要支持各种语法,所以我们对组件改造一下,让他支持自定义主题和语法提示,并且在修改代码时通过 emit 把代码传递给父组件
在codeEdit
组件内定义如下 Props:
const Props = defineProps({
/**
* 编辑器的语言类型 支持javascript css html typescript等
*/
type: {
type: String,
default: 'javascript',
},
/**
* 代码
*/
content: {
type: String,
default: '',
},
/**
* 编辑器的主题 vs, hc-black,vs-dark
*/
theme: {
type: String,
default: 'vs',
},
})
再定义 emit 事件
const Emits = defineEmits(['change']) // 大家可以自行添加其他事件 我这里就做一个change事件
在编辑器的配置方法中修改对应值:
language: Props.type, //[!code focus]
value: Props.content, //[!code focus]
automaticLayout: true,
theme: Props.theme, //[!code focus]
foldingStrategy: 'indentation',
renderLineHighlight: 'all',
selectOnLineNumbers: true,
然后加上值的变化事件
// 监听值的变化
editor.onDidChangeModelContent((val) => {
Emits('change', editor.getValue())
})
在父组件中调用组件的地方加上以下代码:
<script setup>
import { ref } from 'vue'
import codeEdit from './components/codeEdit/index.vue'
function contentChange(value) {
console.log(value)
}
const code = ref('')
const type = 'javascript'
const theme = 'vs'
</script>
<template>
<codeEdit :type="type" :theme="theme" :content="code" @change="contentChange" />
</template>
ok,这样我们就实现了通过父组件控制编辑器的语法提示,主题,还有传递的值。
最后来看看完整的代码
<script setup>
import { ref } from 'vue'
import codeEdit from './components/codeEdit/index.vue'
function contentChange(value) {
console.log(value)
}
const code = ref('')
const type = 'javascript'
const theme = 'vs'
</script>
<template>
<codeEdit :type="type" :theme="theme" :content="code" @change="contentChange" />
</template>
<template>
<div id="monaco"></div>
</template>
<script setup>
import * as monaco from 'monaco-editor'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
import { nextTick, onBeforeUnmount, defineProps } from 'vue'
const Props = defineProps({
/**
* 编辑器的语言类型 支持javascript css html typescript等
*/
type: {
type: String,
default: 'javascript',
},
/**
* 代码
*/
content: {
type: String,
default: '',
},
/**
* 编辑器的主题 vs, hc-black,vs-dark
*/
theme: {
type: String,
default: 'vs',
},
})
const Emits = defineEmits(['change', 'save', 'save-error', 'load', 'load-error'])
onBeforeUnmount(() => {
editor.dispose()
})
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new jsonWorker()
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new cssWorker()
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new htmlWorker()
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker()
}
return new editorWorker()
},
}
let editor = null
const editorInit = () => {
nextTick(() => {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSyntaxValidation: false,
})
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016,
allowNonTsExtensions: true,
})
!editor
? (editor = monaco.editor.create(document.getElementById('monaco'), {
language: Props.type, // 语言
value: Props.content, // 编辑器初始显示文字
automaticLayout: true, // 自适应布局
theme: Props.theme, // 官方自带三种主题vs, hc-black, or vs-dark
foldingStrategy: 'indentation', // 折叠代码策略 indentation 缩进,markbegin 标记
renderLineHighlight: 'all', // 行亮
selectOnLineNumbers: true, // 显示行号
minimap: {
enabled: false,
},
readOnly: false, // 只读
fontSize: 16, // 字体大小
scrollBeyondLastLine: false, // 取消代码后面一大段空白
overviewRulerBorder: false, // 不要滚动条的边框
cursorStyle: 'line', // 光标样式
automaticLayout: false, // 自动布局
tabSize: 2, // tab缩进长度
autoIndent: true, // 自动缩进
}))
: editor.setValue('')
// 监听值的变化
editor.onDidChangeModelContent((val) => {
Emits('change', editor.getValue())
})
})
}
editorInit()
</script>
结语
这个例子没有放出 css 样式,大家自行设置 div 的宽高
如果需要别的语法提示,需要导入相关的包,大家可以在项目的
node_modules/monaco-editor/min/vs/basic-languages
目录下能看到支持的语言类型
自己第一次用这个编辑器的时候除了一点问题,还找到了尤大回答的解决方案 https://github.com/vitejs/vite/discussions/1791#discussioncomment-321046
最后附上一些链接:
如果大家是使用的是 webpack
的话,请查看这篇文章:https://juejin.cn/post/6984683777343619102