本文正在参加「金石计划」
前言
在笔者参与的一个低码项目中,在线编辑代码可以说是非常重要的一个功能。使用低码工具开发页面时,通过可视化拖拽、配置组件的一些属性后,针对组件的事件,按钮的点击等逻辑代码需要在线编写,因此 在线编辑代码 是刚需,本文跟各位掘友详细聊聊我在项目中通过集成Monaco Editor实现的一些需求点,如果在后面各位用到能帮到你的话就更好了😃!
关于 Monaco Editor
如果你不认识它,但我想你一定认识 vscode
,你把它当做 vscode
的编辑器即可,并且是开源的。
The Monaco Editor is the fully featured code editor from VS Code
笔者在这里也不过多介绍,反正很牛批,感兴趣可以看官网的文档:
好了,废话不多说,走进正文。
初始化编辑器
我们可以通过实现一个组件来稍微封装一下:
<template>
<div id="editor-box" :style="width: width, height: height"></div>
</template>
<script>
import * as monaco from 'monaco-editor'
export default {
name: 'VMonacoEditor',
props: {
width: String, // 宽度
height: String, // 高度
editorOptions: Object // 编辑器初始化属性配置
},
mounted() {
this.init()
},
methods: {
init() {
const editorOptions = {
//编辑器初始显示代码
value: this.code,
language: 'javascript',
// 语言支持自行查阅demo
automaticLayout: true,//自动布局
theme: 'vs-black',
//官方白带三种主题vS, hc-btack, or vs-dark
tabsize: 2,
...this.editorOptions
}
// 初始化编辑器,确保dom已经渲染
this.editor = monaco.editor.create(document.getelementById('editor-box'), editorOptions)
}
}
}
</script>
编辑器的校验、编译配置
// validation settings
monaco.languages.typescript.javascriptDefaults.setDiagnostics0ptions({
noSemanticValidation: true,
noSyntaValidation: true
})
// compiler options
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2017,
strict: true,
allowNonTsExtensions: true
})
设置代码
this.editor.setValue(code)
获取编辑器代码
this.editor.getValue()
设置主题、语言
// 设置主题
function themeChange(val) {
monaco.editor.setTheme(val || 'vs')
}
// // 设置语言
function languageChange(val) {
monaco.editor.setModelLanguage(this.editor.getModel(), val || 'javascript')
}
使用 prettier 格式化代码
如何使用 prettier 来格式化代码呢?关键就在于 Monaco Editor 提供了你自定义格式化代码的能力,通过registerDocumentFormattingEditProvider
这个 API 来注册你的格式化规则,当触发格式化时就会调用。
详细步骤如下:
安装 prettier
yarn add prettier
注册 格式化规则
// ...
import prettier from 'prettier'
import parserBabel from 'prettier/parser-babel'
// ...
// 格式化逻辑
function spliceSemiAndDoubleQoute(val) {
return prettier.format(val, {
parser: 'babel',
//不保留行尾分号去掉,开发规范统一
semi: false,
//字符串用单引号包,裹,开发规范统一
singleQuote: true,
// 代码每行宇符数
printWidth: 500,
//jsx中'>'保持在一行
bracketSameLine: true,
//对象空格
bracketSpacing: true,
//行尾逗号
trailingComma: 'none',
// (x) => {}
arrowParens: 'avoid',
//函数后不带空格
spaceBeforeFunctionParen: false,
plugins: [parserBabel],
})
}
const formatProvider = {
provideDocumentFormattingEdits(model, options, token) {
return [{
text: spliceSemiAndDoubleQoute(model.getValue()),
range: model.getFullModelRange()
}]
}
}
monaco.languages.registerDocumentFormattingEditProvider('javascript', formatProvider)
由于很多需求都需要在 monaco 这个对象上去设置,并且要先设置,再去初始化编辑器,所以笔者将以上逻辑提取出来单独一个文件 decorateMonacoEditor.js
,最后将 monaco
export
出去,然后在初始化的文件中直接import monaco from 'decorateMonacoEditor.js'
即可。
触发格式化规则
编辑器右键是有 格式化文件 的选项的,但有些时候我们需要自动的触发格式化逻辑:
this.editor.getAction(['editor.action.formatDocument' ])._run()
收起、展开代码块
// 收起
this.editor.getAction(['editor.foldAll'])._run()
// 展开
this.editor.getAction(['editor.unfoldAll'])._run()
设置编辑区域滚动高度
this.editor.setScrollTop(srollTop)
代码区域滚动事件监听
在什么时候会用到这个呢?举一个🌰:当我们离开编辑区,如果做了自动格式化代码或其他一些操作,滚动条就会回到顶部,我们需要优化体验,希望我在点击回到代码编辑区还是上次滚动的位置,此时就可以使用这个事件记录一下滚动位置,当点击回来时自动的滚动到上次离开的位置。
this.editor.onDidScrollChange (() = {
// 你的逻辑
})
编辑区域失去焦点
this.editor.onDidBlurEditorWidget(() => {
// 你的逻辑
})
自定代码块
可以新建一个文件用于定义自定义代码块,比如:
// my-snippets.js
export default {
log: `console.log()`,
for: `for (let index = 0; index < array.length; index++) {
const element = array[index];
}`,
// 更多自定义代码块......
}
然后通过以下方式将自定义代码块注册到编辑器中:
import snippets from './my-snippets'
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: () => {
const suggestions = Object.keys(snippets).map(key => ({
label: key,
kind: monaco.languages.CompletionItemKind.Enum,
insertText: snippets[key],
detail: snippets[key]
}))
return {
incomplete: true,
suggestions: [...suggestions]
}
}
})
大概就是这么个效果:
代码提示
如下图所示,在 vscode 的 JS 文件编辑器中定义一个数组,当我们使用这个变量的时候,会提示这个数组对象上可用的属性、方法,这就是根据类型推断的提示。
当然 Monaco Editor 已经内置了 JS 对象的代码提示,我在这里要说的是假如我们封装了一些对象,如何让编辑器提示。如果你的项目是 TS 开发的,那就非常简单,只需要将类型文件加载到编辑器应该就 OK,由于笔者项目不是 TS 写的,本文主要讲一下 JS 开发的 对象 如何让 MonacoEditor 自动提示。下面是一个例子:
export default {
/**
* @param {*} code
* @returns {Instance} 返回
*/
getInstance() {
return new Instance()
}
}
class Instance {
constructor() {
this.id = Date.now()
this.logTime = 0
}
logId() {
console.log()
this.logTime++
}
getLogTime() {
return this.logTime
}
}
// 上面的是我们封装的,当我们在编辑器编写以下代码,希望编辑器能够提示 Instance 的实例属性,方法
function test(context) { // 假如该函数运行时,context是上面的 { getInstance }
const inst = context.getInstance()
// inst.
}
可以通过以下代码来实现:
const apiType = `const context = {
/**
* @param {*} code
* @returns {Instance} 返回Instance实例
*/
getInstance() {
}
}
const Instance = {
id,
logTime,
logId(),
getLogTime()
}`
monaco.Languages.typescript.javascriptDefaults.addExtralib(apiType, 'myapi.js')
看看效果:
可能你想为你的属性、方法添加一些描述信息:
const apiType = `const context = {
/**
* @param {*} code
* @returns {Instance} 返回Instance实例
*/
getInstance() {
}
}
const Instance = {
/** 实例 ID*/
id,
/** 实例打印次数 */
logTime,
/** 输出实例 ID */
logId(),
/** 获取打印次数 */
getLogTime()
}`
按照 jsdoc 的规范来写即可,结果如下:
当然你肯定还会有疑问,难道我定义了一堆 API,我要一个一个手敲在这里才行吗?
答案肯定不是,我们从构造函数的原型上也可以获取到属性和方法,然后写个转换逻辑生成我们需要的格式即可。比如笔者就是这么玩的:
总结
以上就是笔者在项目中遇到的一些需求,都是通过阅读官方文档得出的方案,其余笔者未实现的需求请查阅文档哦😁!觉得对您有帮助的话,可以点个赞支持下,谢谢!
原文链接:https://juejin.cn/post/7218419207129415738 作者:Lvzl