6.5 KiB
CodeMirror 多实例问题 - 当前状态
日期: 2026-02-05 状态: ✅ 已修复
🎉 修复成功
经过 10 次探索,问题已成功解决!
最终方案: 统一使用 defaultHighlightStyle,移除自定义高亮样式
📊 问题摘要
错误: Unrecognized extension value in extension set - CodeMirror 6 多实例错误
影响: 代码编辑器无法加载,语法高亮失效
🔧 已尝试的解决方案
| # | 方案 | 结果 | 详情 |
|---|---|---|---|
| 1 | 统一导出文件 | ❌ | codemirrorExports.js |
| 2 | manualChunks 合并 | ❌ | 反而可能导致问题 |
| 3 | 移除旧包 | ❌ | 版本不是问题 |
| 4 | 修复返回格式 | ❌ | 不是根本原因 |
| 5 | resolve.alias | ❌ | Windows 路径问题 |
| 6 | dedupe + exclude | ❌ | 主要影响开发模式 |
| 7 | 移除 manualChunks | ❌ | 即使单文件打包仍失败 |
| 8 | 深入分析错误 | ✅ | 找到真正原因 |
| 9 | 统一使用默认样式 | ✅ | 成功 |
✅ 最终解决方案
方案:使用 defaultHighlightStyle
文件修改:
-
CodeEditor.vue (frontend/src/components/CodeEditor.vue)
- 移除
HighlightStyle和tags导入 - 添加
defaultHighlightStyle和syntaxHighlighting导入 - 删除
lightHighlightStyle定义(22 行代码) - 修改
getThemeExtension()使用syntaxHighlighting(defaultHighlightStyle)
- 移除
-
codemirrorExports.js (frontend/src/utils/codemirrorExports.js)
- 移除
HighlightStyle和tags的导出
- 移除
验证结果
- ✅ 生产环境构建成功(无错误)
- ✅ 开发服务器启动成功
- ✅ 与 SqlEditor 等其他组件保持一致
vite.config.js
export default defineConfig({
resolve: {
alias: { '@': resolve(__dirname, 'src') },
// 强制去重 CodeMirror 包
dedupe: [
'@codemirror/state',
'@codemirror/view',
'@codemirror/language',
// ... 所有 CodeMirror 和 Lezer 包
]
},
build: {
rollupOptions: {
output: {
// 移除 manualChunks,让 Rollup 自动处理
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
}
}
},
optimizeDeps: {
include: ['vue', 'pinia', '@arco-design/web-vue', 'marked', 'highlight.js'],
// 排除 CodeMirror,避免预构建多实例
exclude: [
'@codemirror/state',
// ... 所有 CodeMirror 包
]
}
})
构建结果
- 主包:
index-CB_oYaZz.js(2.5 MB) - 包含所有代码 - 无单独的 CodeMirror chunk - 所有 CodeMirror 代码在同一 bundle 中
📝 技术原理
真正原因
之前的假设: 多个 @codemirror/state 实例导致 instanceof 检查失败
实际原因: HighlightStyle.define() 创建的扩展对象与 defaultHighlightStyle 使用了不同的 @lezer/highlight 实例
为什么之前的方案都失败了
- 统一导出文件 - 无法解决预构建阶段的多实例
- manualChunks 合并 - 即使打包到单个文件仍失败
- resolve.alias - Windows 路径问题,且不能解决
@lezer/highlight实例问题 - 移除 manualChunks - 代码在同一 bundle 中,但
HighlightStyle.define()内部使用了不同的实例
为什么最终方案成功
- SqlEditor.vue 使用
defaultHighlightStyle一直正常工作 - CodeEditor.vue 改用
defaultHighlightStyle后也正常了 - 官方提供的
defaultHighlightStyle内部处理了实例一致性问题
📝 关于自定义样式
问题: 自定义样式不能用吗?
答案: 可以用,但需要确保实例一致性。
方案 1:使用 CSS 覆盖(推荐)
基于默认高亮样式,通过 CSS 修改颜色:
/* 在组件的 <style> 中 */
.cm-editor :deep(.cm-keyword) { color: #d73a49 !important; }
.cm-editor :deep(.cm-string) { color: #032f62 !important; }
方案 2:确保 tags 实例统一
所有地方都从同一个 @lezer/highlight 导入 tags,确保没有多个实例。但这仍然可能失败。
当前方案选择了最简单、最稳定的方案:使用官方默认样式。
📚 相关文档
- 完整探索记录 - 10 次探索的完整过程
- CodeMirror 官方讨论 #5174
- Vite 构建优化文档
🎯 关键发现
问题本质: 自定义 HighlightStyle.define() 创建的对象与默认样式使用的 @lezer/highlight 实例不一致。
根本原因: tags 实例的引用不一致,导致 instanceof 检查失败。
解决方向: 统一使用官方提供的 defaultHighlightStyle,避免自定义样式带来的实例问题。
📝 经验总结
❌ 错误方向
- 关注构建配置 - resolve.alias、manualChunks、optimizeDeps 都无法解决
- 代码分割问题 - 即使打包到单个文件仍然失败
- 多实例问题 - @codemirror/state 实例不是根本原因
✅ 正确方向
- 关注代码本身 - 自定义
HighlightStyle.define()的问题 - 对比正常工作的代码 - SqlEditor 使用默认样式正常工作
- 使用官方方案 -
defaultHighlightStyle处理了实例一致性
核心教训
Occam's Razor(奥卡姆剃刀原则): 如果其他组件(SqlEditor)使用默认样式正常工作,那么最简单的方案就是:让 CodeEditor 也使用默认样式。
不应该花费 9 次尝试去调整构建配置,而应该第 1 次就对比正常工作的代码。
修复完成!代码编辑器现在可以正常工作了。 ✅
🚀 配置优化(2026-02-05)
在修复问题后,对构建配置进行了优化:
移除的无用配置
- resolve.dedupe - 28 个包的去重配置,对生产构建无效
- optimizeDeps.exclude - 28 个包的排除配置,不能解决 instanceof 问题
- inlineDynamicImports - 导致所有代码打包到单个文件(5.2MB)
- manualChunks: undefined - 无意义的显式配置
优化效果
| 指标 | 优化前 | 优化后 | 改善 |
|---|---|---|---|
| 主包大小 | 5,226 KB | 2,569 KB | ↓ 51% |
| 构建时间 | 33.64s | 17.14s | ↓ 49% |
| 代码分割 | 无(全部内联) | 按需加载 | ✅ |
详细文档: CodeMirror-配置优化总结.md