常见web代码编辑器框架
在接到在线代码编辑器的需求之后,就开始了相关的调研,以下目前社区最受欢迎的三个开源方案:
这里就不展开比较三者的优劣,具体这篇文章:「译」Ace,CodeMirror 和 Monaco:Web 代码编辑器的对比,已经很详细的对这三大框架做了比较,大家可以结合实际业务场景,做出自己的技术选型。
最终,我们团队决定采用 Codemirror
Codemirror踩坑
在决定采用Codemirror
之后,立即就开始尝试去写一些demo,但是由于前期不了解Codemirror版本较多,且基于react,vue等框架,社区有很多不同的实现库,那到底用哪个版本,用哪个库呢?这中间也踩了不少坑,在这里给大家梳理一下,让有需求的小伙伴儿们能够少踩一些坑。
首先,说明一下Codemirror的版本,目前主要是用的是两个版本:
- Codemirror6
- 文档:codemirror.net/
- github:github.com/codemirror
- Codemirror5
- 文档:codemirror.net/5/
- github:github.com/codemirror/…
Codemirror6和Codemirror5的主要区别体现在以下几个方面:
- 架构:CM6是对CM5进行了全面重写和重构的版本,采用了新的架构。CM6的设计目标是提供更好的可扩展性、模块化和定制性,以适应不同的编辑器需求。
- 模块化:CM6引入了模块化的概念,使得编辑器的功能可以以更灵活的方式组织和扩展。它提供了一套核心模块,以及可选的插件和扩展模块,使开发人员能够根据自己的需求选择和组合功能。
- 插件系统:CM6的插件系统更加强大和灵活。它采用了新的基于状态和触发器的机制,使得插件能够更好地与编辑器交互并响应变化。这使得开发人员能够创建更复杂和定制化的编辑器功能。
- 渲染方式:CM6使用新的渲染引擎,采用了虚拟DOM的概念,以提高性能和响应能力。它还支持可选的受限制的线性渲染模式,可以在大型文档上提供更好的性能
因此,2023年的我们,推荐大家使用Codemirror6
注意❗
注意,大家在网上搜到的大多数关于codemirror的文章依然是v5的版本,想要直接copy代码调试的小伙伴儿们要注意了哦,如何判断呢?典型的一个判断标准的就是V6的版本采用的ESM模块化的方式去引入,而V5依然是传统的非模块的方式,大家可以根据这去判断搜到的代码是采用的哪个版本。
确定使用V6
的版本之后,接下来就是要考虑,社区是否有现成的基于React,Vue封装好的Codemirror组件呢?当然是有的:
- uiwjs/react-codemirror:
- github:github.com/uiwjs/react…
- 实现:基于
codemirror6
以及react16.8+
实现 - 优缺点:优点就是简单易用,直接引入react组件即可,常见的语言设置,主题设置等功能都支持,但是相对于原生的codemirror来说,功能还是有限,如果需要对codemirror进行更复杂的自定义,建议使用原生的codemirror,基于原生的codemirror,自己封装相应的react组件。
- react-codemirror2
- github:github.com/scniro/reac…
- 实现:基于
codemirror5
以及react16
实现 - 优缺点:相对比较老了,最后一次维护也是在3年前了,在如今较新版本的react中可能会有问题,例如:react18+ 中初始化会报错。
因此,如果只是简单场景的在线编辑器,uiwjs/react-codemirror
这个库基本可以满足条件,但是如果想自定义相对比较复杂的场景,建议还是基于原生的codemirror6
去自己封装相应的组件。
确定使用原生的codemirror6
之后,接下来我们就看看其基本使用啦。
常见场景
基本使用
Codemirror6采用现代的JavaScript模块化和构建工具,并通过一组模块化的包来实现不同的功能。其中,@codemirror/view
和@codemirror/state
是CodeMirror 6(以下简称CM6)库的核心包之一,用于构建可扩展的代码编辑器。
@codemirror/view
:这个包提供了构建编辑器用户界面(UI) 的核心功能。它定义了编辑器的视图组件、输入处理、渲染逻辑等。@codemirror/view
中的主要类是EditorView
,它代表了一个CM6编辑器实例的视图。你可以使用@codemirror/view
来创建、控制和自定义编辑器的外观和交互行为。@codemirror/state
:这个包定义了编辑器的状态管理和文档模型。它提供了EditorState
类,用于表示编辑器的完整状态,包括文本内容、光标位置、选中范围、编辑器的配置选项等。通过@codemirror/state
,你可以创建和管理编辑器的状态,执行编辑操作、查询编辑器状态,以及响应状态的变化。
以下就是一个基于react最小版本的codemirror在线编辑器的实现:
// myCodemirror.tsx
import React, { useRef, useEffect } from 'react';
import { basicSetup } from 'codemirror';
import { EditorView, keymap } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
const CodeMirror: React.FC = () => {
const editorRef = useRef(null);
useEffect(() => {
// 初始化CodeMirror编辑器
const state = EditorState.create({
doc: 'hello world!',
extensions: [
basicSetup,
],
});
const editor = new EditorView({
state,
parent: editorRef.current,
});
return () => {
editor.destroy(); // 注意:此后此处要随组件销毁
};
}, []);
return <div ref={editorRef}></div>;
};
export default CodeMirror;
语言设置
最小版本的codemirror代码编辑器实现以后,接下来,我们一般都需要进一步指定对应的语言,通过 codemirror6 官方github可以看到,其不同语言都有一个对应的包来维护,我们在项目中,只需要引入相应的包,并且在extensions
配置即可。
例如:我们要引入sql语言,首先安装依赖包:
pnpm install @codemirror/lang-sql --save
然后,在基于我们刚刚的最小编辑器代码的基础上,引入该依赖包,就以下两步:
import { sql } from '@codemirror/lang-sql'; // 引入语言包
const state = EditorState.create({
doc: 'hello world!',
extensions: [
basicSetup,
sql(), // 在extensions中配置语言
],
});
完整代码如下:
// myCodemirror.tsx
import React, { useRef, useEffect } from 'react';
import { basicSetup } from 'codemirror';
import { EditorView, keymap } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { sql } from '@codemirror/lang-sql'; // 引入语言包
const CodeMirror: React.FC = () => {
const editorRef = useRef(null);
useEffect(() => {
const state = EditorState.create({
doc: 'hello world!',
extensions: [
basicSetup,
sql(), // 在extensions中配置语言
],
});
const editor = new EditorView({
state,
parent: editorRef.current,
});
return () => {
editor.destroy();
};
}, []);
return <div ref={editorRef}></div>;
};
export default CodeMirror;
主题设置
语言设置完以后,接下来就是设置一个自己喜欢的主题啦,默认情况下,codemirror6 只包含一个主题:@codemirror/theme-one-dark, 如需要使用,直接在项目中安装引入即可
// 安装主题
pnpm install @codemirror/theme-one-dark
// import引入,然后在extensions属性中配置即可。
import { oneDark } from '@codemirror/theme-one-dark'
const state = EditorState.create({
doc: 'hello world!',
extensions: [
//...
oneDark
],
});
当然,如果还想引用其他主题,也可以参考如下两个网站:
里面有很多好看的主题,效果如下:
使用配置方法都类似,大家选择自己喜欢的主题即可。
监听doc变化
如何监听代码变化呢?在extensions
中配置 EditorView.updateListener
即可。代码如下:
const state = EditorState.create({
doc: 'select * from table',
extensions: [
// ...
EditorView.updateListener.of((v) => {
// 文档更新后再触发
if(v.docChanged) {
console.log(v.state.doc.toString()) //监测得到的最新代码
}
}),
],
});
自定义autocomplete
在实际使用中,经常需要自定义一些关键词,例如:公司内部常用的表,字段等信息,我们需要配置到autocompletion中,这样我们输入的时候,就可以快速提示,快捷输入。
在codemirror中也提供了相应的功能,核心是依赖@codemirror/autocomplete
这个库。
- 首先是安装依赖
pnpm i @codemirror/autocomplete --save
- 引入代码,及其配置
import { autocompletion } from '@codemirror/autocomplete';
// 自定义关键词函数
function myCompletions(context: CompletionContext) {
let word = context.matchBefore(/\w*/)
if (word.from === word.to && !context.explicit)
return null
return {
from: word.from,
options: [
{label: "match", type: "keyword"},
{label: "hello", type: "variable", info: "(World)"},
{label: "magic", type: "text", apply: "⠁⭒*.✩.*⭒⠁", detail: "macro"}
]
}
}
/// 在extensions中,引入myCompletions
const state = EditorState.create({
doc,
extensions: [
// ...
autocompletion({ override: [myCompletions]})
],
});
实现如下如下:
注意❗
注意:以上这种方式,自定义的自动提示会直接覆盖sql语言自带的自动提示,显然我们只是想拓展,并不是直接覆盖,这部分如何实现还再进一步调研中,了解的小伙伴儿欢迎分享。
完整代码
以下是用react封装的一个codemirror组件,包含了基础功能
,语言设置
,主题设置
,doc变化监听
等功能。有需要的小伙伴儿可以直接复制到一个.tsx文件中,在本地查看效果。
import React, { useRef, useEffect, useState } from 'react';
import { basicSetup } from 'codemirror';
import { EditorView, keymap } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { sql } from '@codemirror/lang-sql';
import { ayuLight } from 'thememirror';
const sqlLang = sql();
const CodeMirror: React.FC = () => {
const editorRef = useRef(null);
const [doc, setDock] = useState();
useEffect(() => {
// 初始化CodeMirror编辑器
const state = EditorState.create({
doc,
extensions: [
basicSetup,
sqlLang,
ayuLight,
EditorView.updateListener.of((v) => {
if (v.docChanged) {
setDock(v.state.doc.toString());
}
}),
],
});
const editor = new EditorView({
state,
parent: editorRef.current,
});
return () => {
editor.destroy(); // 注意:此后此处要随组件销毁
};
}, []);
return <div ref={editorRef}></div>;
};
export default CodeMirror;
效果如下:
后续迭代过程中,有新的一些功能实现点,或者坑都会在这里陆续更新和完善。
总结
在整个过程中,主要就是前期对codemirror的版本,以及社区基于react,vue等有很多不同的封装库,有点混乱,不知道该选择哪一个,这篇文章为大家整体梳理了一下,同时,把codemirror6的常用功能进行了整理,希望可以让需要的小伙伴儿可以少走一些坑,快速接入项目中。