Skip to content

在 Vite 项目封装 VScode 官方编辑器 monaco-editor

前言

在很多网站的在线编辑代码都有非常智能的代码提示,其中大部分网站都会使用monaco-editor, vscode 的代码编辑器就是monaco-editor 所以我们能在网页使用到 vscode 的代码编辑。今天就和大家一起来封装一个简单的编辑器组件。

效果

开整

先通过 npm 下载monaco-editor,我这里使用 pnpm。

bash
pnpm add monaco-editor

我们成功安装了 monaco-editor,我这里安装的版本是最新版 0.44.0

js
  "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 即可

vue
<template>
  <div id="monaco"></div>
</template>

然后是 js 部分

首先导入依赖

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 让编辑器支持对应的语法提示

js
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()
  },
}

然后我们新建一个初始化编辑器的方法,对应的注释我已经写上,大家可以自行修改自己的需求

js
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('')
  })
}

别忘了调用

js
editorInit()

然后还需要在页面销毁时,销毁编辑器实例

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

然后我们在 App.vue 里面导入这个组件看看效果

vue
<script setup>
import codeEdit from './components/codeEdit/index.vue'
</script>

<template>
  <codeEdit />
</template>

就不贴图了,大家自行测试吧。

封装

在前面我们在配置中写死了一些配置,例如主题,语言,value 等,我们一般在项目中使用到编辑器一般是做为子组件的,而且需要支持各种语法,所以我们对组件改造一下,让他支持自定义主题和语法提示,并且在修改代码时通过 emit 把代码传递给父组件

codeEdit组件内定义如下 Props:

js
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 事件

js
const Emits = defineEmits(['change']) // 大家可以自行添加其他事件  我这里就做一个change事件

在编辑器的配置方法中修改对应值:

js
language: Props.type, //[!code focus]
value: Props.content, //[!code focus]
automaticLayout: true,
theme: Props.theme, //[!code focus]
foldingStrategy: 'indentation',
renderLineHighlight: 'all',
selectOnLineNumbers: true,

然后加上值的变化事件

js
// 监听值的变化
editor.onDidChangeModelContent((val) => {
  Emits('change', editor.getValue())
})

在父组件中调用组件的地方加上以下代码:

vue
<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,这样我们就实现了通过父组件控制编辑器的语法提示,主题,还有传递的值。

最后来看看完整的代码

vue
<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>
vue
<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

赣ICP备2023003243号