Monaco-editor 在Vue3当中使用姿势

4,038 阅读3分钟

Monaco-editor 在Vue3当中使用姿势

项目介绍

项目环境:vue3、typescript、vite

monaco-editor官网地址:microsoft.github.io/monaco-edit…

由于官网看起来晦涩难懂,指路官方文档看法指导:www.cnblogs.com/jdWu-d/p/16…

使用方式

  1. 安装:

    pnpm install monaco-editor@0.34.0 
    
  2. main.js当中:

    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'// @ts-ignore: worker
    self.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()
      }
    }
    
  3. 封装VMonacoEditor组件:

    <template>
      <div ref="codeEditBox" class="codeEditBox"></div>
    </template><script lang="ts">
    import { defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue'
    import { editorProps } from '../monacoEditorType'
    import * as monaco from 'monaco-editor'export default defineComponent({
      name: 'MonacoEditor',
      props: editorProps,
      emits: ['update:modelValue', 'change', 'editor-mounted'],
      setup(props, { emit }) {
        let editor: monaco.editor.IStandaloneCodeEditor
        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
          })
    ​
          editor = monaco.editor.create(codeEditBox.value, {
            value: props.modelValue,
            language: props.language,
            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.language,
          newValue => {
            monaco.editor.setModelLanguage(editor.getModel()!, newValue)
          }
        )
    ​
        onBeforeUnmount(() => {
          editor.dispose()
        })
    ​
        onMounted(() => {
          init()
        })
    ​
        return { codeEditBox }
      }
    })
    </script><style lang="scss" scoped>
    .codeEditBox {
      width: v-bind(width);
      height: v-bind(height);
    }
    </style>
    
  4. 类型定义文件:

    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 // 显示行号
      minimap?: {
        // 关闭小地图
        enabled: boolean
      }
      readOnly: boolean // 只读
      contextmenu: boolean
      fontSize?: number // 字体大小
      scrollBeyondLastLine?: boolean // 取消代码后面一大段空白
      overviewRulerBorder?: boolean // 不要滚动条的边框
    }
    ​
    export const editorProps = {
      modelValue: {
        type: String as PropType<string>,
        default: null
      },
      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'
      },
      theme: {
        type: String as PropType<Theme>,
        validator(value: string): boolean {
          return ['vs', 'hc-black', 'vs-dark'].includes(value)
        },
        default: 'vs-dark'
      },
      options: {
        type: Object as PropType<Options>,
        default() {
          return {
            automaticLayout: true,
            foldingStrategy: 'indentation',
            renderLineHighlight: 'all',
            selectOnLineNumbers: true,
            minimap: {
              enabled: true
            },
            readOnly: false,
            contextmenu: true,
            fontSize: 16,
            scrollBeyondLastLine: false,
            overviewRulerBorder: false
          }
        }
      }
    }
    
  5. 项目中使用:

    <script lang="ts" setup>
    import * as monaco from 'monaco-editor'
    const content = ref('{"text":"1"}')
    const language = ref('json')
    const editorMounted = (editor: monaco.editor.IStandaloneCodeEditor) => {
       console.log('editor实例加载完成', editor)
    }
    </script>
    <template>
        <VMonacoEditor
          v-model="content"
          :language="language"
          height="500px"
          style="margin-top: 40px"
          @editor-mounted="editorMounted"
         ></VMonacoEditor>
    </template>
    

diff效果的编辑器封装

<template>
  <div
    ref="container"
    class="monaco-editor"
    :style="`height: ${height}px`"
  ></div>
</template>
<script>
import * as monaco from 'monaco-editor'
export default {
  name: 'AcMonaco',
  props: {
    opts: {
      type: Object,
      default() {
        return {}
      }
    },
    oldValue: {
      type: String,
      default: ''
    },
    newValue: {
      type: String,
      default: ''
    },
    height: {
      type: Number,
      default: 300
    }
  },
  data() {
    return {
      // 主要配置
      defaultOpts: {
        value: '', // 编辑器的值
        // theme: 'vs-dark', // 编辑器主题:vs, hc-black, or vs-dark,更多选择详见官网
        roundedSelection: true, // 右侧不显示编辑器预览框
        autoIndent: true // 自动缩进
      },
      // 编辑器对象
      monacoEditor: {}
    }
  },
  watch: {
    opts: {
      handler() {
        this.init()
      },
      deep: true
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      // 初始化container的内容,销毁之前生成的编辑器
      this.$refs.container.innerHTML = ''
      // 生成编辑器配置
      const editorOptions = Object.assign(this.defaultOpts, this.opts)
      // 初始化编辑器实例
      this.monacoDiffInstance = monaco.editor.createDiffEditor(
        this.$refs['container'],
        editorOptions
      )
      this.monacoDiffInstance.setModel({
        // oldValue为以前的值
        original: monaco.editor.createModel(
          this.oldValue,
          editorOptions.language
        ),
        // oldValue为新的值
        modified: monaco.editor.createModel(
          this.newValue,
          editorOptions.language
        )
      })
    },
    // 供父组件调用手动获取值
    getVal() {
      return this.monacoEditor.getValue()
    }
  }
}
</script>
//使用
<script setup lang="ts">
//VMonacoCompare
const opts = reactive({
    readOnly: true,
    contextmenu: false,
    automaticLayout: true,
    language: 'yaml'
  })
​
const content = ref(''),
  previousContent = ref(''),
  language = ref('yaml')
​
</script>
<template>
    <VMonacoCompare
        :newValue="content"
        :oldValue="previousContent"
        :opts="opts"
        :height="450"
    ></VMonacoCompare>
</template>
<style lang="scss" scoped>
</style>
​

主题颜色切换

主题配置库: github.com/brijeshb42/…

pnpm install monaco-editor-webpack-plugin
pnpm install monaco-themes

可以在仓库中选择一个自己喜欢的主题,这里以Dawn.json为例,在本地新建dawn.ts文件,将仓库当中的源码拷贝到ts文件中,例如:

export const Dawn = {
  base: 'vs',
  inherit: true,
  rules: [
    {
      background: 'F9F9F9',
      token: ''
    },
    {
      foreground: '5a525f',
      fontStyle: 'italic',
      token: 'comment'
    },
    {
      foreground: '811f24',
      fontStyle: 'bold',
      token: 'constant'
    },
    {
      foreground: 'bf4f24',
      token: 'entity'
    },
    {
      foreground: '794938',
      token: 'keyword'
    },
    {
      foreground: 'a71d5d',
      fontStyle: 'italic',
      token: 'storage'
    },
    {
      foreground: '0b6125',
      token: 'string | punctuation.definition.string'
    },
    {
      foreground: '691c97',
      token: 'support'
    },
    {
      foreground: '234a97',
      token: 'variable'
    },
    {
      foreground: '794938',
      token: 'punctuation.separator'
    },
    {
      foreground: 'b52a1d',
      fontStyle: 'bold italic underline',
      token: 'invalid.deprecated'
    },
    {
      foreground: 'f8f8f8',
      background: 'b52a1d',
      fontStyle: 'italic underline',
      token: 'invalid.illegal'
    },
    {
      foreground: '080808',
      background: '6f8bba26',
      token: 'string source'
    },
    {
      foreground: '696969',
      fontStyle: 'bold',
      token: 'string constant'
    },
    {
      foreground: '234a97',
      token: 'string variable'
    },
    {
      foreground: 'cf5628',
      token: 'string.regexp'
    },
    {
      foreground: 'cf5628',
      fontStyle: 'bold italic',
      token: 'string.regexp.character-class'
    },
    {
      foreground: 'cf5628',
      fontStyle: 'bold italic',
      token: 'string.regexp constant.character.escaped'
    },
    {
      foreground: 'cf5628',
      fontStyle: 'bold italic',
      token: 'string.regexp source.ruby.embedded'
    },
    {
      foreground: 'cf5628',
      fontStyle: 'bold italic',
      token: 'string.regexp string.regexp.arbitrary-repitition'
    },
    {
      foreground: '811f24',
      fontStyle: 'bold',
      token: 'string.regexp constant.character.escape'
    },
    {
      background: '6f8bba26',
      token: 'text source'
    },
    {
      foreground: '693a17',
      token: 'support.function'
    },
    {
      foreground: 'b4371f',
      token: 'support.constant'
    },
    {
      foreground: '234a97',
      token: 'support.variable'
    },
    {
      foreground: '693a17',
      token: 'markup.list'
    },
    {
      foreground: '19356d',
      fontStyle: 'bold',
      token: 'markup.heading | markup.heading entity.name'
    },
    {
      foreground: '0b6125',
      background: 'bbbbbb30',
      fontStyle: 'italic',
      token: 'markup.quote'
    },
    {
      foreground: '080808',
      fontStyle: 'italic',
      token: 'markup.italic'
    },
    {
      foreground: '080808',
      fontStyle: 'bold',
      token: 'markup.bold'
    },
    {
      foreground: '080808',
      fontStyle: 'underline',
      token: 'markup.underline'
    },
    {
      foreground: '234a97',
      fontStyle: 'italic underline',
      token: 'markup.link'
    },
    {
      foreground: '234a97',
      background: 'bbbbbb30',
      token: 'markup.raw'
    },
    {
      foreground: 'b52a1d',
      token: 'markup.deleted'
    },
    {
      foreground: '19356d',
      background: 'dcdcdc',
      fontStyle: 'bold',
      token: 'meta.separator'
    }
  ],
  colors: {
    'editor.foreground': '#080808',
    'editor.background': '#F9F9F9',
    'editor.selectionBackground': '#275FFF4D',
    'editor.lineHighlightBackground': '#2463B41F',
    'editorCursor.foreground': '#000000',
    'editorWhitespace.foreground': '#4B4B7E80'
  }
}

在组件中引入:

//import { Dawn } from '../dawn'
//monaco.editor.defineTheme('myTheme', Dawn)
//monaco.editor.setTheme('myTheme')
​
//完整代码如下:
<template>
  <div ref="codeEditBox" class="codeEditBox"></div>
</template><script lang="ts">
import { defineComponent, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { editorProps } from '../monacoEditorType'
import * as monaco from 'monaco-editor'
import { Dawn } from '../dawn'export default defineComponent({
  name: 'MonacoEditor',
  props: editorProps,
  emits: ['update:modelValue', 'change', 'editor-mounted'],
  setup(props, { emit }) {
    let editor: monaco.editor.IStandaloneCodeEditor
    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
      })
      //@ts-ignore
      monaco.editor.defineTheme('myTheme', Dawn)
      monaco.editor.setTheme('myTheme')
​
      editor = monaco.editor.create(codeEditBox.value, {
        value: props.modelValue,
        language: props.language,
        // 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.language,
      newValue => {
        monaco.editor.setModelLanguage(editor.getModel()!, newValue)
      }
    )
​
    onBeforeUnmount(() => {
      editor.dispose()
    })
​
    onMounted(() => {
      init()
    })
​
    return { codeEditBox }
  }
})
</script><style lang="scss" scoped>
.codeEditBox {
  width: v-bind(width);
  height: v-bind(height);
}
</style>

其它使用参考:blog.csdn.net/weixin_4444…