15 KiB
15 KiB
CodeMirror 6 编辑器文档
项目: U-Desk 组件: CodeEditor.vue 更新日期: 2026-02-05 维护者: 开发团队
📚 目录
简介
什么是 CodeMirror 6?
CodeMirror 6 是一个基于 TypeScript 重写的现代代码编辑器,采用模块化架构,提供:
- 🚀 高性能: 比 v5 快 40%,内存少 35%
- 📦 模块化: 只加载需要的功能
- 🎨 可定制: 灵活的主题和扩展系统
- 🔍 准确: 基于 Lezer 的语法解析
- 💪 类型安全: 完整的 TypeScript 支持
为什么选择 CodeMirror 6?
| 特性 | CodeMirror 6 | Monaco (VS Code) | Ace |
|---|---|---|---|
| 包体积 | ~50KB (gzip) | ~2MB | ~300KB |
| TypeScript | ✅ 原生支持 | ✅ 支持 | ⚠️ 部分 |
| 模块化 | ✅ 高度模块化 | ❌ 单体 | ⚠️ 中等 |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 移动端 | ✅ 良好支持 | ⚠️ 一般 | ⚠️ 一般 |
版本信息
当前使用的版本
{
"@codemirror/view": "6.39.8",
"@codemirror/state": "6.5.3",
"@codemirror/language": "6.12.1",
"@codemirror/commands": "6.10.1",
"@codemirror/highlight": "0.19.8",
"@codemirror/theme-one-dark": "6.1.3",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-python": "6.2.1",
"@codemirror/lang-go": "6.0.1",
"@codemirror/lang-json": "6.0.2",
"@codemirror/legacy-modes": "6.5.2"
}
最新版本(2026-02)
- @codemirror/view: 6.39.12 (2026-01-30)
- @codemirror/state: 6.5.3 (当前版本)
- @codemirror/language: 6.12.1 (当前版本)
注:我们的版本略旧但稳定,建议在下次迭代时更新
核心架构
包结构
@codemirror/
├── view/ # 编辑器视图和 DOM 交互
├── state/ # 编辑器状态和事务
├── language/ # 语言支持和高亮
├── commands/ # 内置命令
├── search/ # 搜索和替换
├── autocomplete/ # 自动补全
├── lint/ # 代码检查
├── lang-*/ # 语言包
└── legacy-modes/ # 旧版语言模式
核心概念
1. EditorState(状态)
编辑器的不可变状态,包含文档内容:
import { EditorState } from '@codemirror/state'
const state = EditorState.create({
doc: 'console.log("Hello, World!")',
extensions: [/* ... */]
})
2. EditorView(视图)
编辑器的 UI 表示:
import { EditorView } from '@codemirror/view'
const view = new EditorView({
state: state,
parent: document.body
})
3. Extensions(扩展)
配置编辑器功能的核心机制:
const extensions = [
lineNumbers(), // 显示行号
highlightActiveLine(), // 高亮当前行
history(), // 撤销/重做
keymap.of(defaultKeymap) // 键盘映射
]
主题系统
主题构成
CodeMirror 6 的主题由两部分组成:
- 基础样式 (
EditorView.theme) - UI 元素样式 - 高亮样式 (
HighlightStyle.define) - 语法高亮颜色
暗色主题(One Dark)
import { oneDark } from '@codemirror/theme-one-dark'
// 直接使用
extensions.push(oneDark)
亮色主题(自定义)
import { HighlightStyle } from '@codemirror/language'
import { tags } from '@lezer/highlight'
// 1. 定义语法高亮样式
const lightHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#d73a49', fontWeight: 'bold' },
{ tag: tags.string, color: '#032f62' },
{ tag: tags.number, color: '#005cc5' },
{ tag: tags.comment, color: '#6a737d', fontStyle: 'italic' },
{ tag: tags.function(tags.variableName), color: '#6f42c1' },
{ tag: tags.className, color: '#22863a' },
{ tag: tags.propertyName, color: '#e36209' },
{ tag: tags.variableName, color: '#005cc5' }
])
// 2. 定义基础主题样式
const lightTheme = EditorView.theme({
'&': { backgroundColor: '#ffffff' },
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999' },
'.cm-line': { caretColor: '#000' },
'.cm-selection': { backgroundColor: '#d9d9d9' }
})
// 3. 应用主题
extensions.push(lightTheme, lightHighlightStyle)
主题切换
import { Compartment } from '@codemirror/state'
// 创建主题隔离区
const themeCompartment = new Compartment()
// 初始化
const view = new EditorView({
extensions: [
themeCompartment.of(oneDark) // 初始主题
]
})
// 切换主题
function switchTheme(isDark) {
view.dispatch({
effects: themeCompartment.reconfigure(
isDark ? oneDark : lightTheme
)
})
}
可用的标签(Tags)
import { tags } from '@lezer/highlight'
// 基础标签
tags.keyword // 关键字 (const, var, function)
tags.string // 字符串
tags.number // 数字
tags.comment // 注释
tags.variableName // 变量名
tags.function // 函数
tags.className // 类名
tags.propertyName // 属性名
tags.operator // 操作符
tags.tagName // HTML/XML 标签
tags.attributeName // 属性名
tags.bool // 布尔值
tags.null // null 值
// 组合标签
tags.function(tags.variableName) // 函数调用的变量名
tags.definition(tags.name) // 定义时的名称
语言支持
现代语言包
支持 30+ 编程语言,动态加载:
// JavaScript/TypeScript
import { javascript } from '@codemirror/lang-javascript'
javascript({ jsx: true, typescript: true })
// Python
import { python } from '@codemirror/lang-python'
python()
// Go
import { go } from '@codemirror/lang-go'
go()
// JSON
import { json } from '@codemirror/lang-json'
json()
// Markdown
import { markdown } from '@codemirror/lang-markdown'
markdown({ codeLanguages: languages })
Legacy 语言包
通过 StreamLanguage 包装旧模式:
import { StreamLanguage } from '@codemirror/language'
import { ruby } from '@codemirror/legacy-modes/mode/ruby'
StreamLanguage.define(ruby)
文件扩展名映射
const langMap = {
// JavaScript/TypeScript
'js': 'javascript', 'jsx': 'javascript',
'ts': 'typescript', 'tsx': 'typescript',
// 样式
'css': 'css', 'scss': 'css', 'less': 'css',
// 数据
'json': 'json', 'yaml': 'yaml', 'xml': 'xml',
// 脚本
'py': 'python', 'rb': 'ruby', 'sh': 'shell',
// 编译型
'go': 'go', 'rs': 'rust', 'cpp': 'cpp'
}
动态加载语言
我们的实现使用缓存和动态导入:
// 1. 语言缓存
const languageCache = new Map()
// 2. 动态导入
export async function loadLanguageExtension(language) {
if (languageCache.has(language)) {
return languageCache.get(language)
}
try {
// 动态导入语言包
const mod = await import(`@codemirror/lang-${language}`)
const extension = mod[language]()
languageCache.set(language, extension)
return extension
} catch (error) {
console.error(`加载语言包失败: ${language}`, error)
return null
}
}
API 参考
核心属性
const props = {
modelValue: String, // 编辑器内容 (v-model)
fileExtension: String // 文件扩展名 (如 'js', 'py')
}
核心事件
const emit = {
'update:modelValue': String // 内容变化时触发
}
主要方法
createEditor(docContent)
创建编辑器实例:
const createEditor = async (docContent = '') => {
const extensions = await createExtensions()
const state = EditorState.create({
doc: docContent,
extensions
})
view = new EditorView({ state, parent: editorContainer.value })
}
recreateEditor()
重建编辑器(切换主题/语言时):
const recreateEditor = async () => {
if (!view) return
const currentDoc = view.state.doc.toString()
view.destroy()
await createEditor(currentDoc)
}
createExtensions()
构建扩展配置:
const createExtensions = async () => {
const extensions = [
// 基础功能
lineNumbers(),
highlightActiveLineGutter(),
history(),
keymap.of(defaultKeymap),
bracketMatching(),
// 事件监听
EditorView.updateListener.of((update) => {
if (update.docChanged) {
emit('update:modelValue', update.state.doc.toString())
}
}),
// 自定义样式
EditorView.theme({ /* ... */ })
]
// 主题
if (themeStore.isDark) {
extensions.push(oneDark)
} else {
extensions.push(lightTheme, lightHighlightStyle)
}
// 语言支持
const language = getLanguageFromExtension(props.fileExtension)
if (language !== 'text') {
const langExtension = await loadLanguageExtension(language)
if (langExtension) {
extensions.push(langExtension)
}
}
return extensions
}
最佳实践
1. 使用 Compartment 动态切换
❌ 不好的做法:重建整个编辑器
// 每次切换都重建,性能差
watch(language, () => {
view.destroy()
view = new EditorView({ /* ... */ })
})
✅ 推荐做法:使用 Compartment
const languageCompartment = new Compartment()
watch(language, async (newLang) => {
const lang = await loadLanguageExtension(newLang)
view.dispatch({
effects: languageCompartment.reconfigure(lang)
})
})
2. 异步加载语言包
// 预加载常用语言
export async function preloadCommonLanguages() {
await Promise.all([
'javascript',
'json',
'markdown',
'python',
'sql'
].map(loadLanguageExtension))
}
// 在应用启动时调用
onMounted(() => {
preloadCommonLanguages()
})
3. 防抖更新
import { debounce } from 'lodash-es'
const debouncedUpdate = debounce((value) => {
emit('update:modelValue', value)
}, 300)
EditorView.updateListener.of((update) => {
if (update.docChanged) {
debouncedUpdate(update.state.doc.toString())
}
})
4. 内存管理
onBeforeUnmount(() => {
// 务必销毁编辑器
view?.destroy()
view = null
})
5. 主题持久化
// 从 localStorage 读取
const savedTheme = localStorage.getItem('editor-theme') || 'dark'
// 保存主题变化
watch(theme, (newTheme) => {
localStorage.setItem('editor-theme', newTheme)
})
常见问题
Q1: 语法高亮不显示?
可能原因:
- 语言扩展未正确加载
- 主题样式未配置
- 文件扩展名映射错误
解决方案:
// 检查语言是否加载
console.log('Language:', language, 'Extension:', langExtension)
// 确保主题包含高亮样式
extensions.push(lightHighlightStyle)
Q2: 切换主题时编辑器闪烁?
原因:重建整个编辑器导致
解决方案:使用 Compartment
const themeCompartment = new Compartment()
view.dispatch({
effects: themeCompartment.reconfigure(newTheme)
})
Q3: 大文件性能差?
优化方案:
// 虚拟滚动已内置,但可以调整
const virtualScroll = new Compartment()
extensions.push(
virtualScroll.of({
// 调整渲染窗口
viewportMargin: 1000
})
)
Q4: 如何添加自定义语言?
// 1. 使用 Lezer 定义语法
import { parser } from '@lezer/generator'
// 2. 创建语言包
import { LanguageSupport } from '@codemirror/language'
const myLanguage = new LanguageSupport(parser)
// 3. 使用
extensions.push(myLanguage)
升级指南
从 v5 升级到 v6
主要变化:
| v5 | v6 |
|---|---|
CodeMirror(document) |
new EditorView({ state }) |
{line, ch} 位置 |
数字偏移量 |
getValue() / setValue() |
state.doc.toString() / dispatch() |
setOption() |
使用 Compartment.reconfigure() |
完整迁移指南:https://codemirror.net/docs/migration/
升级步骤
- 更新依赖
npm install @codemirror/view@latest @codemirror/state@latest
- 调整 API 调用
// v5
editor.setValue('new content')
// v6
view.dispatch({
changes: {
from: 0,
to: view.state.doc.length,
insert: 'new content'
}
})
- 更新主题系统
// v5: 使用 CSS 类
editor.setOption('theme', 'my-theme')
// v6: 使用扩展
extensions.push(EditorView.theme({ /* ... */ }))
参考资料
官方文档
社区资源
相关包
- @codemirror/language-data - 文件类型检测
- @uiw/react-codemirror - React 封装
论坛讨论
维护日志
2026-02-05
- ✅ 修复亮色主题语法高亮问题
- ✅ 添加自定义亮色主题支持
- ✅ 创建完整的技术文档
未来计划
- 升级到最新版本(6.39.12)
- 添加更多主题选项
- 支持自定义快捷键
- 添加代码折叠功能
- 集成 LSP(语言服务器协议)
- 性能优化(大文件处理)
相关文件
frontend/src/
├── components/
│ └── CodeEditor.vue # 主编辑器组件
├── utils/
│ └── codeMirrorLoader.js # 语言包动态加载
└── stores/
└── theme.js # 主题状态管理
文档维护: 开发团队 最后更新: 2026-02-05 版本: 1.0.0