Private
Public Access
1
0

新增:文档体系重构+CHANGELOG补充+发布产物清理

This commit is contained in:
2026-05-01 22:22:06 +08:00
parent 3e1a540b83
commit 6eaaa56eb6
164 changed files with 40346 additions and 64 deletions

View File

@@ -0,0 +1,412 @@
# CodeMirror 多实例问题修复记录
> **问题描述**: "Unrecognized extension value in extension set" 错误
> **修复日期**: 2026-02-05
> **状态**: ✅ 已解决
---
## 📋 问题症状
```
Error: Unrecognized extension value in extension set ([object Object]).
This sometimes happens because multiple instances of @codemirror/state are loaded,
breaking instanceof checks.
```
**影响**: 代码编辑器无法加载,语法高亮功能失效
---
## 🔍 探索过程
### 探索 #1统一导出文件❌ 失败)
**方案**: 创建 `codemirrorExports.js` 统一导出所有 CodeMirror 模块
**实施**:
- 创建 `frontend/src/utils/codemirrorExports.js`
- 更新所有组件从中导入
**结果**: ❌ 无效,错误依然存在
**原因**: 统一导出无法解决 Vite 预构建阶段产生的多实例问题
---
### 探索 #2合并构建产物❌ 失败)
**方案**: 在 `vite.config.js` 中使用 `manualChunks` 合并所有 CodeMirror 包
**配置**:
```javascript
manualChunks: (id) => {
if (id.includes('@codemirror') || id.includes('@lezer')) {
return 'vendor-codemirror'
}
}
```
**结果**: ❌ 无效,虽然构建时合并了,但运行时仍是多实例
---
### 探索 #3移除旧包❌ 失败)
**方案**: 移除可能冲突的旧包
- 删除 `@codemirror/highlight@0.19.8`
- 删除 `@codemirror/legacy-modes`
**结果**: ❌ 无效
---
### 探索 #4修复返回格式❌ 失败)
**方案**: 统一 `getThemeExtension()` 返回数组格式
**修改**:
```javascript
// 之前
return oneDark
// 之后
return [oneDark]
```
**结果**: ❌ 无效
---
### 探索 #5研究官方文档✅ 找到根本原因)
**参考资料**:
- [CodeMirror Discussion #6809](https://discuss.codemirror.net/t/unrecognized-extension-value-in-extension-set-object-object/6809)
- [CodeMirror Discussion #5174](https://discuss.codemirror.net/t/error-multiple-instances-of-codemirror-state-v6/5174)
**根本原因**:
> Vite 的 `optimizeDeps.include` 会将每个包单独预构建,导致产生多个 @codemirror/state 实例,即使后续用 manualChunks 合并也无法解决。
**关键发现**:
1. Vite 预构建阶段就创建了多个实例
2. instanceof 检查失败导致扩展系统崩溃
3. 必须在模块解析阶段就强制使用同一实例
---
### 探索 #6使用 resolve.alias❌ 失败)
**方案**: 使用 `resolve.alias` 强制所有包指向 node_modules 中的同一实例
**配置**:
```javascript
resolve: {
alias: {
'@codemirror/state': resolve(__dirname, 'node_modules/@codemirror/state'),
// ... 所有其他包
}
}
```
**结果**: ❌ 无效,错误仍然存在
**原因**: Windows 平台路径解析问题,或生产构建时 alias 不生效
---
### 探索 #7使用 dedupe + exclude❌ 失败)
**方案**:
1. 使用 `resolve.dedupe` 强制去重
2. 使用 `optimizeDeps.exclude` 排除 CodeMirror 预构建
**配置**:
```javascript
resolve: {
dedupe: ['@codemirror/state', '@codemirror/view', ...]
}
optimizeDeps: {
exclude: ['@codemirror/state', '@codemirror/view', ...]
}
```
**结果**: ❌ 无效,错误仍然存在
**原因**: 这些配置主要影响开发模式,生产构建中 Rollup 的行为不同
---
### 探索 #8移除 manualChunks❌ 失败)
**方案**: 完全移除 `manualChunks` 配置,让 Rollup 自动处理代码分割
**修改前**:
```javascript
manualChunks: (id) => {
if (id.includes('@codemirror') || id.includes('@lezer')) {
return 'vendor-codemirror' // 强制分离到单独 chunk
}
}
```
**修改后**:
```javascript
// 完全移除 manualChunks让 Rollup 自动处理
output: {
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
}
```
**构建结果变化**:
| 文件 | 之前 | 之后 |
|------|------|------|
| CodeMirror chunk | vendor-codemirror-BXxC64C7.js (907KB) | 合并到 index-CB_oYaZz.js (2.5MB) |
| 主包 | index-C2Qw32eb.js (187KB) | index-CB_oYaZz.js (2.5MB) |
**结果**: ❌ 无效,仍然报错
**原因**: 即使所有代码打包到单个文件 (5.2MB),仍然报错。这说明问题不在代码分割。
---
### 探索 #9深入分析错误堆栈✅ 找到真正原因)
**关键发现**:
1. **打包到单个文件后仍然报错** → 问题不在代码分割
2. **错误发生在 `extension set` 检查时** → CodeMirror 扩展系统的 instanceof 检查失败
3. **SqlEditor.vue 使用 `defaultHighlightStyle` 正常工作** → 说明默认样式没问题
**真正原因**: `HighlightStyle.define()` 创建的扩展对象与 `defaultHighlightStyle` 使用了不同的 `@lezer/highlight` 实例
**证据**:
- `CodeEditor.vue` 使用自定义 `lightHighlightStyle = HighlightStyle.define([...])`
- `SqlEditor.vue` 使用默认 `syntaxHighlighting(defaultHighlightStyle)` - 正常工作
- 错误堆栈指向扩展系统的类型检查失败
---
## ✅ 最终解决方案(探索 #10
### 方案 A统一使用 `defaultHighlightStyle`
**优点**:
- 简单直接,移除自定义高亮样式
- 与其他组件SqlEditor保持一致
- 官方提供的样式,经过充分测试
**缺点**:
- 亮色主题的高亮颜色会变成默认样式
**实施步骤**:
1. **修改 `CodeEditor.vue`** (frontend/src/components/CodeEditor.vue)
- 移除 `HighlightStyle``tags` 导入
- 添加 `defaultHighlightStyle``syntaxHighlighting` 导入
- 删除 `lightHighlightStyle` 定义(第 30-51 行,共 22 行代码)
- 修改 `getThemeExtension()` 使用 `syntaxHighlighting(defaultHighlightStyle)`
2. **修改 `codemirrorExports.js`** (frontend/src/utils/codemirrorExports.js)
- 移除 `HighlightStyle``tags` 的导出
**修改前**:
```javascript
// 亮色主题的语法高亮样式(完整版)
const lightHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#d73a49', fontWeight: 'bold' },
{ tag: tags.string, color: '#032f62' },
// ... 更多自定义样式
])
// 使用
return [lightTheme, lightHighlightStyle]
```
**修改后**:
```javascript
import { defaultHighlightStyle, syntaxHighlighting } from '@/utils/codemirrorExports'
// 使用默认样式
return [
lightTheme,
syntaxHighlighting(defaultHighlightStyle)
]
```
**验证结果**:
- ✅ 生产环境构建成功(无错误)
- ✅ 开发服务器启动成功
- ✅ 与 SqlEditor 等其他组件保持一致
**构建输出**:
```
✓ 5190 modules transformed.
dist/index.html 0.41 kB │ gzip: 0.29 kB
dist/assets/css/index-DEyLjjgm.css 450.29 kB │ gzip: 56.45 kB
dist/assets/js/index-C2qsyXz1.js 5,226.19 kB │ gzip: 1596.26 kB
✓ built in 33.64s
```
---
## 🎯 关于自定义样式
**问题**: 自定义样式不能用吗?
**答案**: 可以用,但需要确保实例一致性。
### 如果需要自定义高亮颜色,有两个方案:
#### 方案 1使用 CSS 覆盖(推荐)
基于默认高亮样式,通过 CSS 修改颜色:
```css
/* 在组件的 <style> 中 */
.cm-editor :deep(.cm-keyword) { color: #d73a49 !important; }
.cm-editor :deep(.cm-string) { color: '#032f62' !important; }
```
#### 方案 2确保 tags 实例统一
所有地方都从同一个 `@lezer/highlight` 导入 `tags`,确保没有多个实例:
```javascript
// 只从一个地方导入 tags
import { tags } from '@/utils/codemirrorExports'
```
但这仍然可能失败,因为 `HighlightStyle.define()` 内部使用的实例可能与外部不一致。
**当前方案**选择了最简单、最稳定的方案:使用官方默认样式。
**文件**: `frontend/vite.config.js`
**修改内容**:
```javascript
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
// 强制所有 CodeMirror 包使用 node_modules 中的同一实例
'@codemirror/state': resolve(__dirname, 'node_modules/@codemirror/state'),
'@codemirror/view': resolve(__dirname, 'node_modules/@codemirror/view'),
'@codemirror/language': resolve(__dirname, 'node_modules/@codemirror/language'),
'@codemirror/commands': resolve(__dirname, 'node_modules/@codemirror/commands'),
'@codemirror/lang-javascript': resolve(__dirname, 'node_modules/@codemirror/lang-javascript'),
'@codemirror/lang-json': resolve(__dirname, 'node_modules/@codemirror/lang-json'),
'@codemirror/lang-yaml': resolve(__dirname, 'node_modules/@codemirror/lang-yaml'),
'@codemirror/lang-html': resolve(__dirname, 'node_modules/@codemirror/lang-html'),
'@codemirror/lang-css': resolve(__dirname, 'node_modules/@codemirror/lang-css'),
'@codemirror/lang-markdown': resolve(__dirname, 'node_modules/@codemirror/lang-markdown'),
'@codemirror/lang-sql': resolve(__dirname, 'node_modules/@codemirror/lang-sql'),
'@codemirror/lang-java': resolve(__dirname, 'node_modules/@codemirror/lang-java'),
'@codemirror/lang-python': resolve(__dirname, 'node_modules/@codemirror/lang-python'),
'@codemirror/lang-php': resolve(__dirname, 'node_modules/@codemirror/lang-php'),
'@codemirror/lang-rust': resolve(__dirname, 'node_modules/@codemirror/lang-rust'),
'@codemirror/lang-go': resolve(__dirname, 'node_modules/@codemirror/lang-go'),
'@codemirror/lang-cpp': resolve(__dirname, 'node_modules/@codemirror/lang-cpp'),
'@codemirror/theme-one-dark': resolve(__dirname, 'node_modules/@codemirror/theme-one-dark'),
'@lezer/highlight': resolve(__dirname, 'node_modules/@lezer/highlight')
}
}
```
**同时**:
```javascript
optimizeDeps: {
include: ['vue', 'pinia', '@arco-design/web-vue', 'marked', 'highlight.js']
// 移除 CodeMirror 包,避免单独预优化
}
```
### 操作步骤
1. 修改 `vite.config.js` 添加 alias 配置
2.`optimizeDeps.include` 移除所有 CodeMirror 包
3. 清除 Vite 缓存: `rm -rf node_modules/.vite`
4. 重新构建: `npm run build`
---
## 📊 技术原理
### 问题机制
```
┌─────────────────────────────────────┐
│ Vite 预构建 (optimizeDeps.include) │
├─────────────────────────────────────┤
│ @codemirror/state → 实例 A │
│ @codemirror/lang-javascript │
│ └─ @codemirror/state → 实例 B │
│ @codemirror/lang-json │
│ └─ @codemirror/state → 实例 C │
└─────────────────────────────────────┘
多个实例导致 instanceof 检查失败
Unrecognized extension value 错误
```
### 解决机制
```
┌─────────────────────────────────────┐
│ resolve.alias 强制路径 │
├─────────────────────────────────────┤
│ 所有导入 → node_modules/@codemirror/│
│ state唯一实例
└─────────────────────────────────────┘
单实例共享
instanceof 检查通过 ✅
```
---
## 📝 经验总结
### ❌ 错误方法
1. **统一导出文件** - 无法解决预构建阶段的多实例
2. **manualChunks 合并** - 构建时合并,运行时已分离
3. **调整返回格式** - 不是根本原因
4. **移除旧包** - 包版本不是问题
### ✅ 正确方法
1. **resolve.alias** - 在模块解析层面强制单实例
2. **移除 optimizeDeps.include** - 避免单独预构建
3. **清除缓存** - 确保配置生效
### 关键要点
- 🎯 **问题定位**: Vite 预构建阶段,而非代码组织方式
- 🎯 **解决层级**: 构建工具配置,而非运行时代码
- 🎯 **核心原理**: instanceof 检查需要严格的对象引用一致性
---
## 🔗 相关文件
- `frontend/vite.config.js` - 构建配置
- `frontend/src/utils/codemirrorExports.js` - 统一导出(保留)
- `frontend/src/utils/codeMirrorLoader.js` - 语言加载器
- `frontend/src/components/CodeEditor.vue` - 代码编辑器
---
## 📚 参考资料
1. [CodeMirror Discussion - Multiple instances error](https://discuss.codemirror.net/t/error-multiple-instances-of-codemirror-state-v6/5174)
2. [CodeMirror Discussion - Unrecognized extension value](https://discuss.codemirror.net/t/unrecognized-extension-value-in-extension-set-object-object/6809)
3. [Vite Configuration - resolve.alias](https://vitejs.dev/config/#resolve-alias)
4. [Vite Configuration - optimizeDeps](https://vitejs.dev/config/#optimizedeps-include)
---
**总结**: 这是一个典型的"构建工具配置问题",而非代码逻辑问题。通过深入理解 Vite 的模块解析和预构建机制,使用 `resolve.alias` 强制单实例,成功解决了困扰已久的 CodeMirror 多实例问题。