Private
Public Access
1
0

重构:CodeMirror 架构优化

核心优化:
- 新增统一导出避免多实例问题
- 语言加载器从动态改为静态导入
- 使用 Compartment 实现主题/语言动态切换

依赖清理:
- 移除废弃的 @codemirror/highlight
- 移除不再使用的 @codemirror/legacy-modes

组件优化:
- CodeEditor 添加内容更新防抖
- 改进亮色主题样式
- 移除不必要的编辑器重建逻辑

构建配置:
- 简化 Vite manualChunks 配置
- 优化依赖预加载列表

文档清理:
- 删除过期的代码审查文档
- 更新版本号 0.3.0 → 0.3.2
This commit is contained in:
2026-02-06 11:32:27 +08:00
parent 9eb39fbb8f
commit 0229cab550
30 changed files with 592 additions and 3971 deletions

View File

@@ -4,14 +4,30 @@
<script setup>
import { ref, onMounted, watch, onBeforeUnmount, computed, nextTick } from 'vue'
import { EditorView, lineNumbers, highlightActiveLineGutter, keymap } from '@codemirror/view'
import { EditorState } from '@codemirror/state'
import { defaultKeymap, history } from '@codemirror/commands'
import { bracketMatching } from '@codemirror/language'
import { oneDark } from '@codemirror/theme-one-dark'
import {
EditorView, lineNumbers, highlightActiveLineGutter, keymap,
EditorState, Compartment,
defaultKeymap, history,
bracketMatching, defaultHighlightStyle, syntaxHighlighting,
oneDark
} from '@/utils/codemirrorExports'
import { useThemeStore } from '@/stores/theme'
import { loadLanguageExtension, getLanguageFromExtension } from '@/utils/codeMirrorLoader'
// ==================== 主题定义 ====================
// 亮色主题的基础样式
const lightTheme = EditorView.theme({
'&': { backgroundColor: '#ffffff' },
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
'.cm-line': { caretColor: '#000' },
'.cm-selection': { backgroundColor: '#d9d9d9' },
'.cm-cursor': { borderLeftColor: '#000' }
})
// ==================== Props & Emits ====================
const props = defineProps({
modelValue: { type: String, required: true },
fileExtension: { type: String, default: '' }
@@ -19,69 +35,144 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue'])
// ==================== 状态管理 ====================
const themeStore = useThemeStore()
const editorContainer = ref(null)
let view = null
const createExtensions = async () => {
// 使用 Compartment 实现动态切换,避免重建编辑器
const themeCompartment = new Compartment()
const languageCompartment = new Compartment()
// ==================== 防抖处理 ====================
let emitTimeout = null
const debouncedEmit = (value) => {
if (emitTimeout) {
clearTimeout(emitTimeout)
}
emitTimeout = setTimeout(() => {
emit('update:modelValue', value)
}, 150)
}
// 获取当前主题扩展
const getThemeExtension = () => {
if (themeStore.isDark) {
return [oneDark]
} else {
// 亮色主题:使用默认语法高亮样式
return [
EditorView.theme({
'&': { backgroundColor: '#ffffff' },
'.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
'.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
'.cm-line': { caretColor: '#000' },
'.cm-selection': { backgroundColor: '#d9d9d9' },
'.cm-cursor': { borderLeftColor: '#000' }
}),
syntaxHighlighting(defaultHighlightStyle)
]
}
}
// ==================== 扩展配置 ====================
const createExtensions = () => {
const extensions = [
lineNumbers(),
highlightActiveLineGutter(),
history(),
keymap.of(defaultKeymap),
bracketMatching(),
// 内容更新监听(带防抖)
EditorView.updateListener.of((update) => {
if (update.docChanged) {
emit('update:modelValue', update.state.doc.toString())
debouncedEmit(update.state.doc.toString())
}
}),
// 基础样式
EditorView.theme({
'&': { height: '100%', fontSize: '13px' },
'.cm-scroller': { overflow: 'auto', fontFamily: 'Consolas, Monaco, Courier New, monospace' },
'.cm-content': { padding: '8px', minHeight: '100%' },
'.cm-line': { padding: '0 0' },
'&.cm-focused': { outline: 'none' }
})
}),
// 使用 Compartment 支持动态切换主题
themeCompartment.of(getThemeExtension()),
// 使用 Compartment 支持动态切换语言
languageCompartment.of([])
]
if (themeStore.isDark) {
extensions.push(oneDark)
}
const language = getLanguageFromExtension(props.fileExtension)
if (language !== 'text') {
const langExtension = await loadLanguageExtension(language)
if (langExtension) {
extensions.push(langExtension)
}
}
return extensions
}
const createEditor = async (docContent = '') => {
// ==================== 语言管理 ====================
const initLanguage = async () => {
const language = getLanguageFromExtension(props.fileExtension)
if (language === 'text') return
try {
const langExtension = await loadLanguageExtension(language)
if (langExtension && view) {
view.dispatch({
effects: languageCompartment.reconfigure(langExtension)
})
}
} catch (error) {
console.warn(`[CodeEditor] 加载语言包失败: ${language}`, error)
}
}
// ==================== 编辑器创建 ====================
const createEditor = (docContent = '') => {
if (!editorContainer.value) return
const extensions = await createExtensions()
const state = EditorState.create({ doc: docContent, extensions })
const state = EditorState.create({
doc: docContent,
extensions: createExtensions()
})
view = new EditorView({ state, parent: editorContainer.value })
// 初始化语言
initLanguage()
}
const recreateEditor = async () => {
if (!view) return
const currentDoc = view.state.doc.toString()
view.destroy()
await createEditor(currentDoc)
}
// ==================== 生命周期 ====================
onMounted(async () => {
await createEditor(props.modelValue || '')
onMounted(() => {
createEditor(props.modelValue || '')
// 确保主题正确应用(在下一 tick
nextTick(() => {
if (view) {
view.dispatch({
effects: themeCompartment.reconfigure(getThemeExtension())
})
}
})
})
onBeforeUnmount(() => {
if (emitTimeout) {
clearTimeout(emitTimeout)
}
view?.destroy()
view = null
})
// ==================== 监听器 ====================
// 监听外部内容变化
watch(() => props.modelValue, (newValue) => {
if (view && newValue !== view.state.doc.toString()) {
view.dispatch({
@@ -90,24 +181,39 @@ watch(() => props.modelValue, (newValue) => {
}
})
const isDark = computed(() => themeStore.isDark)
watch([isDark, () => props.fileExtension], async () => {
await nextTick()
await recreateEditor()
// 监听主题变化(使用 Compartment 重建,不丢失状态)
watch(() => themeStore.isDark, () => {
if (view) {
view.dispatch({
effects: themeCompartment.reconfigure(getThemeExtension())
})
}
})
// 监听文件扩展名变化(重新加载语言)
watch(() => props.fileExtension, () => {
initLanguage()
})
</script>
<style scoped>
.codemirror-editor {
height: 100%;
width: 100%;
overflow: hidden;
}
.codemirror-editor :deep(.cm-editor) {
height: 100%;
width: 100%;
}
.codemirror-editor :deep(.cm-scroller) {
overflow: auto;
height: 100%;
}
.codemirror-editor :deep(.cm-content) {
height: 100%;
}
</style>