文章

Vue3 Vite Monaco Editor

Vue3 Vite Monaco Editor

Monaco Editor 官方 rep: https://microsoft.github.io/monaco-editor/

演示效果

演示站点:https://boommanpro.github.io/vue3-vite-monaco-editor/

整体链路

①创建vite项目

1. nvm use v18.20 确定node版本

2. 安装vite ~ yarn create vite console --template vue

3. 查看项目是否正常启动 yarn run dev

②安装monaco

1. 安装monaco => yarn add monaco-editor@0.50.0

2. 创建 worker.js

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';

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();
    }
};

monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);

3. main.js 引入 -> import './worker.js'

4. 创建组件 MonacoVite.vue 组件实现了双向绑定

<template>
  <div
    ref="editorRef"
    :style="{
      height: computedHeight + 'px',
      width: computedWidth + 'px'
    }"
  ></div>
</template>

<script setup>
import {
  onUnmounted,
  toRefs,
  defineEmits,
  defineProps,
  onMounted,
  ref,
  toRaw,
  watch,
  nextTick
} from "vue";

import { debounce } from "lodash-es"; // 引入lodash的debounce函数
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";

const emits = defineEmits(["update:modelValue"]);

const props = defineProps({
  height: {
    type: Number,
    default: null
  },
  width: {
    type: Number,
    default: null
  },
  modelValue: {
    type: String,
    default: ""
  },
  language: {
    type: String,
    default: "json"
  },
  theme: {
    type: String,
    default: "vs-dark"
  }
});

const {height, width} = toRefs(props);
const editorRef = ref(null);
const editorInstance = ref(null);

const computedHeight = ref(height.value || 0);
const computedWidth = ref(width.value || 0);

const updateEditorSize = () => {
  const parentRect = editorRef.value.parentElement.getBoundingClientRect();
  computedHeight.value = height.value ?? parentRect.height;
  computedWidth.value = width.value ?? parentRect.width;
};

const debouncedUpdateModelValue = debounce(() => {
  emits("update:modelValue", toRaw(editorInstance.value).getValue());
}, 500); // 防抖时间间隔为500毫秒

async function increment() {
  await nextTick();
  updateEditorSize();
  if (editorRef.value && !editorInstance.value) {
    editorInstance.value = monaco.editor.create(editorRef.value, {
      value: props.modelValue,
      language: props.language,
      theme: props.theme,
      scrollBeyondLastLine: false,
      automaticLayout: true
    });
    editorInstance.value.onDidChangeModelContent(event => {
      debouncedUpdateModelValue(); // 使用防抖后的函数
    });
  }
}

onMounted(() => {
  increment();
});

// 监听外部code变化,更新内部状态
watch(
  () => props.modelValue,
  (newVal, oldVal) => {
    let currValue = toRaw(editorInstance.value).getValue();
    if (newVal !== currValue) {
      toRaw(editorInstance.value).setValue(newVal);
    }
  },
  {deep: true}
);
// 清除防抖定时器
onUnmounted(() => {
  debouncedUpdateModelValue.cancel();
});
</script>

5. 引入组件

<monaco-vite :width="500" :height="500" v-model:="code" language="json"></monaco-vite>