vue3+vite+ts使用monaco-editor编辑器的简单步骤

前言

近期要完成一个代码编辑器的内容,用的vue3.0+ts+vite架构,学习尚浅, 常在插件上遇坑

特此记录下在monaco-editor的使用

需求:yaml和sql的文件的高亮、补全

实现

1.安装

// ^0.34.1
yarn add monaco-editor

2.在vite.config.js中配置(如果不需要ts\js\html就不需要这么做)

// 强制预构建插件包
 optimizeDeps: {
 include: [
 `monaco-editor/esm/vs/language/json/json.worker`,
 `monaco-editor/esm/vs/language/css/css.worker`,
 `monaco-editor/esm/vs/language/html/html.worker`,
 `monaco-editor/esm/vs/language/typescript/ts.worker`,
 `monaco-editor/esm/vs/editor/editor.worker`
 ], 
 },

3.组件封装与使用

monacoEditor.vue组件

<template>
 <div
 ref="codeEditBox"
 class="codeEditBox"
 :class="hightChange&&'codeEditBox1'"
 />
</template>
<script lang="ts">
	import {
	 defineComponent, onBeforeUnmount, onMounted, ref, watch,
	} from 'vue'
	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 EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
	import * as monaco from 'monaco-editor'
	import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
	import { language as yamlLanguage } from 'monaco-editor/esm/vs/basic-languages/yaml/yaml.js';
	import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
	import { editorProps } from './monacoEditorType'
	export default defineComponent({
	 name: 'MonacoEditor',
	 props: editorProps,
	 emits: ['update:modelValue', 'change', 'editor-mounted'],
	 setup(props, { emit }) {
	 (self as any).MonacoEnvironment = {
	 getWorker(_: string, label: string) {
	 if (label === 'json') {
	 return new JsonWorker()
	 }
	 if (['css', 'scss', 'less'].includes(label)) {
	 return new CssWorker()
	 }
	 if (['html', 'handlebars', 'razor'].includes(label)) {
	 return new HtmlWorker()
	 }
	 if (['typescript', 'javascript'].includes(label)) {
	 return new TsWorker()
	 }
	 return new EditorWorker()
	 },
	 }
	 let editor: any
	 const codeEditBox = ref()
	
	 const init = () => {
	 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
	 noSemanticValidation: true,
	 noSyntaxValidation: false,
	 })
	 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
	 target: monaco.languages.typescript.ScriptTarget.ES2020,
	 allowNonTsExtensions: true,
	 })
	 monaco.languages.registerCompletionItemProvider('sql', {
	 provideCompletionItems() {
	 const suggestions:any = [];
	 // 这个keywords就是sql.js文件中有的
	 sqlLanguage.keywords.forEach((item:any) => {
	 suggestions.push({
	 label: item,
	 kind: monaco.languages.CompletionItemKind.Keyword,
	 insertText: item,
	 });
	 })
	 sqlLanguage.operators.forEach((item:any) => {
	 suggestions.push({
	 label: item,
	 kind: monaco.languages.CompletionItemKind.Operator,
	 insertText: item,
	 });
	 })
	 sqlLanguage.builtinFunctions.forEach((item:any) => {
	 suggestions.push({
	 label: item,
	 kind: monaco.languages.CompletionItemKind.Function,
	 insertText: item,
	 });
	 })
	 sqlLanguage.builtinVariables.forEach((item:any) => {
	 suggestions.push({
	 label: item,
	 kind: monaco.languages.CompletionItemKind.Variable,
	 insertText: item,
	 });
	 })
	 return {
	 // 最后要返回一个数组
	 suggestions,
	 };
	 },
	 })
	 monaco.languages.registerCompletionItemProvider('yaml', {
	 provideCompletionItems() {
	 const suggestions:any = [];
	 // 这个keywords就是python.js文件中有的
	 yamlLanguage.keywords.forEach((item:any) => {
	 suggestions.push({
	 label: item,
	 kind: monaco.languages.CompletionItemKind.Keyword,
	 insertText: item,
	 });
	 })
	 return {
	 // 最后要返回一个数组
	 suggestions,
	 };
	 },
	 })
	
	 editor = monaco.editor.create(codeEditBox.value, {
	 value: props.modelValue,
	 language: props.language,
	 readOnly: props.readOnly,
	 theme: props.theme,
	 ...props.options,
	 })

 // 监听值的变化
 editor.onDidChangeModelContent(() => {
 const value = editor.getValue() // 给父组件实时返回最新文本
 emit('update:modelValue', value)
 emit('change', value)
 })

 emit('editor-mounted', editor)
 }
 watch(
 () => props.modelValue,
 (newValue) => {
 if (editor) {
 const value = editor.getValue()
 if (newValue !== value) {
 editor.setValue(newValue)
 }
 }
 },
 )

 watch(
 () => props.options,
 (newValue) => {
 editor.updateOptions(newValue)
 },
 { deep: true },
 )
 watch(
 () => props.readOnly,
 () => {
 console.log('props.readOnly', props.readOnly)
 editor.updateOptions({ readOnly: props.readOnly })
 },
 { deep: true },
 )

 watch(
 () => props.language,
 (newValue) => {
 monaco.editor.setModelLanguage(editor.getModel()!, newValue)
 },
 )

 onBeforeUnmount(() => {
 editor.dispose()
 })

 onMounted(() => {
 init()
 })

 return { codeEditBox }
 },
})
</script>
 <style lang="scss" scoped>
 .codeEditBox {
 width: 100%;
 flex: 1;
 min-height: 100px;
 // height: 200px;
 overflow-y: auto;
 }
 .codeEditBox1{
 height: calc(100% - 323px);
 }
 </style>

4.monacoEditorType.ts类型定义文件

import { PropType } from 'vue'
	
	export type Theme = 'vs' | 'hc-black' | 'vs-dark'
	export type FoldingStrategy = 'auto' | 'indentation'
	export type RenderLineHighlight = 'all' | 'line' | 'none' | 'gutter'
	export interface Options {
	 automaticLayout: boolean // 自适应布局
	 foldingStrategy: FoldingStrategy // 折叠方式 auto | indentation
	 renderLineHighlight: RenderLineHighlight // 行亮
	 selectOnLineNumbers: boolean // 显示行号
	 placeholder:string
	 minimap: {
	 // 关闭小地图
	 enabled: boolean
	 }
	 // readOnly: Boolean // 只读
	 fontSize: number // 字体大小
	 scrollBeyondLastLine: boolean // 取消代码后面一大段空白
	 overviewRulerBorder: boolean // 不要滚动条的边框
	}
	
	export const editorProps = {
	 modelValue: {
	 type: String as PropType<string>,
	 default: null,
	 },
	 hightChange: {
	 type: Boolean,
	 default: false,
	 },
	 width: {
	 type: [String, Number] as PropType<string | number>,
	 default: '100%',
	 },
	 height: {
	 type: [String, Number] as PropType<string | number>,
	 default: '100%',
	 },
	 language: {
	 type: String as PropType<string>,
	 default: 'javascript',
	 },
	 readOnly: {
	 type: Boolean,
	 default: false,
	 },
	 theme: {
	 type: String as PropType<Theme>,
	 validator(value: string): boolean {
	 return ['vs', 'hc-black', 'vs-dark', 'hc-light'].includes(value)
	 },
	 default: 'vs',
	 },
	 options: {
	 type: Object as PropType<Options>,
	 default() {
	 return {
	 automaticLayout: true,
	 // foldingStrategy: 'indentation',
	 foldingStrategy: 'indentation', // 折叠方式 auto | indentation
	 // renderLineHighlight: 'all',
	 renderLineHighlight: 'all' || 'line' || 'none' || 'gutter', // 行亮
	 selectOnLineNumbers: true, // 显示行号
	 minimap: {
	 // 关闭小地图
	 enabled: false,
	 },
	 placeholder: 'ss',
	 // readOnly: false, // 只读
	 fontSize: 16, // 字体大小
	 scrollBeyondLastLine: false, // 取消代码后面一大段空白
	 overviewRulerBorder: false, // 不要滚动条的边框
	 }
	 },
	 },
	}

5.在父组件中使用

<monacoEditor
 	v-model="value"
 :language="language"
 :hight-change="hightChange"
 :read-only="tablist.length===0"
 width="100%"
 height="100%"
 @editor-mounted="editorMounted"
 />
 import monacoEditor from './monacoEditor.vue'
 const value = ref('-- select * from infrastructure;')
 const language = ref('sql')
 const hightChange = ref<any>(false)
 const editorMounted = (editor: any) => {
 console.log('editor实例加载完成', editor)
 }

    value:编辑器代码显示的值
    language:要加载的语言类型
    hightChange:改变编辑器的高度(可去掉,我这边有个sql查表的实现,需要在编辑区下面加一个sql查询的表格,所以需要这个参数)
    read-only:编辑区是否是只读的,当左侧文件树为空时没有文件,编辑区不可写
    editorMounted:加载完成操作

//记得在env.d.ts配置包的
declare module 'monaco-editor';
declare module 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
declare module 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
declare module 'monaco-editor/esm/vs/basic-languages/yaml/yaml.js';
declare module 'monaco-editor/esm/vs/language/json/json.worker?worker'
declare module 'monaco-editor/esm/vs/language/css/css.worker?worker'
declare module 'monaco-editor/esm/vs/language/html/html.worker?worker'
declare module 'monaco-editor/esm/vs/editor/editor.worker?worker'
declare module 'monaco-editor/esm/vs/basic-languages/javascript/javascript.js'

打包报错的处理

yarn build 时会发生下述报错

我在这里看到了解决办法https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md

在monacoEditor.vue组件,重写getWorker方法

<template>
 <div
 ref="codeEditBox"
 class="codeEditBox"
 :class="hightChange&&'codeEditBox1'"
 />
</template>

<script lang="ts">
import {
 defineComponent, onBeforeUnmount, onMounted, ref, watch,
} from 'vue'
// 减去以下包
-- 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 EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'

import * as monaco from 'monaco-editor'
import { language as sqlLanguage } from 'monaco-editor/esm/vs/basic-languages/sql/sql.js';
import { language as yamlLanguage } from 'monaco-editor/esm/vs/basic-languages/yaml/yaml.js';
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution'
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'
import { editorProps } from './monacoEditorType'

export default defineComponent({
 name: 'MonacoEditor',
 props: editorProps,
 emits: ['update:modelValue', 'change', 'editor-mounted'],
 setup(props, { emit }) {
 (self as any).MonacoEnvironment = {
 ++ getWorker: (_: string, label: string) => {
 ++ const getWorkerModule = (moduleUrl:string, label:string) => new Worker((self as any).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);
 ++ }
 ++ },
 // 原来的减去
 -- getWorker(_: string, label: string) {
 -- if (label === 'json') {
 -- return new JsonWorker()
 -- }
 -- if (['css', 'scss', 'less'].includes(label)) {
 -- return new CssWorker()
 -- }
 -- if (['html', 'handlebars', 'razor'].includes(label)) {
 -- return new HtmlWorker()
 -- }
 -- if (['typescript', 'javascript'].includes(label)) {
 -- return new TsWorker()
 -- }
 -- return new EditorWorker()
 -- },
 }
 let editor: any
 const codeEditBox = ref()

 const init = () => {
 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
 noSemanticValidation: true,
 noSyntaxValidation: false,
 })
 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2020,
 allowNonTsExtensions: true,
 })
 monaco.languages.registerCompletionItemProvider('sql', {
 provideCompletionItems() {
 const suggestions:any = [];
 // 这个keywords就是sql.js文件中有的
 sqlLanguage.keywords.forEach((item:any) => {
 suggestions.push({
 label: item,
 kind: monaco.languages.CompletionItemKind.Keyword,
 insertText: item,
 });
 })
 sqlLanguage.operators.forEach((item:any) => {
 suggestions.push({
 label: item,
 kind: monaco.languages.CompletionItemKind.Operator,
 insertText: item,
 });
 })
 sqlLanguage.builtinFunctions.forEach((item:any) => {
 suggestions.push({
 label: item,
 kind: monaco.languages.CompletionItemKind.Function,
 insertText: item,
 });
 })
 sqlLanguage.builtinVariables.forEach((item:any) => {
 suggestions.push({
 label: item,
 kind: monaco.languages.CompletionItemKind.Variable,
 insertText: item,
 });
 })
 return {
 // 最后要返回一个数组
 suggestions,
 };
 },
 })
 monaco.languages.registerCompletionItemProvider('yaml', {
 provideCompletionItems() {
 const suggestions:any = [];
 // 这个keywords就是python.js文件中有的
 yamlLanguage.keywords.forEach((item:any) => {
 suggestions.push({
 label: item,
 kind: monaco.languages.CompletionItemKind.Keyword,
 insertText: item,
 });
 })
 return {
 // 最后要返回一个数组
 suggestions,
 };
 },
 })

 editor = monaco.editor.create(codeEditBox.value, {
 value: props.modelValue,
 language: props.language,
 readOnly: props.readOnly,
 theme: props.theme,
 ...props.options,
 })

 // 监听值的变化
 editor.onDidChangeModelContent(() => {
 const value = editor.getValue() // 给父组件实时返回最新文本
 emit('update:modelValue', value)
 emit('change', value)
 })

 emit('editor-mounted', editor)
 }
 watch(
 () => props.modelValue,
 (newValue) => {
 if (editor) {
 const value = editor.getValue()
 if (newValue !== value) {
 editor.setValue(newValue)
 }
 }
 },
 )

 watch(
 () => props.options,
 (newValue) => {
 editor.updateOptions(newValue)
 },
 { deep: true },
 )
 watch(
 () => props.readOnly,
 () => {
 console.log('props.readOnly', props.readOnly)
 editor.updateOptions({ readOnly: props.readOnly })
 },
 { deep: true },
 )

 watch(
 () => props.language,
 (newValue) => {
 monaco.editor.setModelLanguage(editor.getModel()!, newValue)
 },
 )

 onBeforeUnmount(() => {
 editor.dispose()
 })

 onMounted(() => {
 init()
 })

 return { codeEditBox }
 },
})
</script>

 <style lang="scss" scoped>
 .codeEditBox {
 width: 100%;
 flex: 1;
 min-height: 100px;
 // height: 200px;
 overflow-y: auto;
 }
 .codeEditBox1{
 height: calc(100% - 323px);
 }
 </style>

总结 

作者:Best_卡卡原文地址:https://blog.csdn.net/weixin_43909743/article/details/127633552

%s 个评论

要回复文章请先登录注册