You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Electron配置CodeMirror时出现@codemirror/state多实例错误

React + Electron项目中CodeMirror多版本依赖冲突报错解决方案

错误信息

Uncaught Error: Unrecognized extension value in extension set ([object Object]). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.

问题背景

开发React + Electron的Markdown编辑器时,配置CodeMirror扩展出现上述错误,注释部分扩展后错误暂时消失。相关代码、依赖配置及依赖树如下:

自定义CodeMirror Hook代码

import { useEffect, useState, useRef } from "react"
import { EditorState } from '@codemirror/state'
import { EditorView, keymap, highlightActiveLine } from '@codemirror/view'
import { defaultKeymap } from '@codemirror/commands'
import { history, historyKeymap } from '@codemirror/history'
import { indentOnInput } from "@codemirror/language"
import { bracketMatching } from "@codemirror/matchbrackets"
import { lineNumbers, highlightActiveLineGutter } from "@codemirror/gutter"
import { defaultHighlightStyle } from "@codemirror/highlight"
import type React from "react"

interface Props {
  initialDoc: string,
  onChange?: (state: EditorState) => void
}

const useCodeMirror = <T extends Element>(
  props: Props
): [React.MutableRefObject<T | null>, EditorView?] => {
  const refContainer = useRef<T>(null)
  const [editorView, setEditorView] = useState<EditorView>()
  const { onChange } = props

  useEffect(() => {
    if (!refContainer.current) return

    const startState = EditorState.create({
      doc: props.initialDoc,
      extensions: [
        keymap.of([...defaultKeymap, ...historyKeymap]),
      //   lineNumbers(),
      //   highlightActiveLineGutter(),
      //   history(),
      //   indentOnInput(),
      // bracketMatching(),
      // defaultHighlightStyle.fallback,
        highlightActiveLine(),
        EditorView.lineWrapping,
        EditorView.updateListener.of(update => {
          if (update.changes) {
            onChange && onChange(update.state)
          }
        })
      ]
    })

    const view = new EditorView({
      state: startState,
      parent: refContainer.current
    })
    setEditorView(view)
  }, [refContainer])

  return [refContainer, editorView]
}

export default useCodeMirror

package.json配置

{
  "name": "vite-electron-builder",
  "description": "Secure boilerplate for Electron app based on Vite",
  "private": true,
  "author": {
    "email": "kozackunisoft@gmail.com",
    "name": "Alex Kozack",
    "url": "https://kozack.me"
  },
  "main": "packages/main/dist/index.cjs",
  "scripts": {
    "build": "npm run build:main && npm run build:preload && npm run build:renderer",
    "build:main": "cd ./packages/main && vite build",
    "build:preload": "cd ./packages/preload && vite build",
    "build:renderer": "cd ./packages/renderer && vite build",
    "compile": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.js --dir --config.asar=false",
    "test": "npm run test:main && npm run test:preload && npm run test:renderer && npm run test:e2e",
    "test:e2e": "npm run build && vitest run",
    "test:main": "vitest run -r packages/main --passWithNoTests",
    "test:preload": "vitest run -r packages/preload --passWithNoTests",
    "test:renderer": "vitest run -r packages/renderer --passWithNoTests",
    "watch": "node scripts/watch.mjs",
    "lint": "eslint . --ext js,mjs,cjs,ts,mts,cts,vue",
    "typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
    "typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json",
    "typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer",
    "postinstall": "cross-env ELECTRON_RUN_AS_NODE=1 electron scripts/update-electron-vendors.mjs",
    "format": "npx prettier --write \"**/*.{js,mjs,cjs,ts,mts,cts,vue,json}\""
  },
  "devDependencies": {
    "@types/react": "^18.0.18",
    "@types/react-dom": "^18.0.6",
    "@typescript-eslint/eslint-plugin": "^5.36.2",
    "@typescript-eslint/parser": "^5.36.2",
    "@vue/test-utils": "2.0.2",
    "cross-env": "7.0.3",
    "electron": "20.1.1",
    "electron-builder": "23.3.3",
    "eslint": "^8.23.0",
    "eslint-config-prettier": "^8.5.0",
    "happy-dom": "6.0.4",
    "nano-staged": "0.8.0",
    "playwright": "1.25.1",
    "simple-git-hooks": "2.8.0",
    "typescript": "4.8.2",
    "unplugin-auto-expose": "0.0.3",
    "vite": "3.1.0",
    "vitest": "0.23.1"
  },
  "dependencies": {
    "@codemirror/commands": "^6.1.0",
    "@codemirror/gutter": "^0.19.9",
    "@codemirror/highlight": "^0.19.8",
    "@codemirror/history": "^0.19.2",
    "@codemirror/lang-javascript": "^6.0.2",
    "@codemirror/language": "^6.2.1",
    "@codemirror/matchbrackets": "^0.19.4",
    "@codemirror/state": "^6.1.1",
    "electron-updater": "5.2.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
    "resolutions": {
    "@codemirror/highlight": "0.19.7",
    "@codemirror/lang-javascript": "0.19.7",
    "@codemirror/state": "0.19.9",
    "@codemirror/view": "0.19.47"
  }
}

npm list @codemirror/state输出

vite-electron-builder@ /Users/scott/Desktop/Electron-editor
├─┬ @codemirror/commands@6.1.0
│ ├── @codemirror/state@6.1.1 deduped
│ └─┬ @codemirror/view@6.2.2
│   └── @codemirror/state@6.1.1 deduped
├─┬ @codemirror/gutter@0.19.9
│ ├─┬ @codemirror/rangeset@0.19.9
│ │ └── @codemirror/state@0.19.9
│ ├── @codemirror/state@0.19.9
│ └─┬ @codemirror/view@0.19.48
│   └── @codemirror/state@0.19.9 deduped
├─┬ @codemirror/highlight@0.19.8
│ ├─┬ @codemirror/language@0.19.10
│ │ └── @codemirror/state@0.19.9 deduped
│ ├── @codemirror/state@0.19.9
│ └─┬ @codemirror/view@0.19.48
│   └── @codemirror/state@0.19.9 deduped
├─┬ @codemirror/history@0.19.2
│ ├── @codemirror/state@0.19.9
│ └─┬ @codemirror/view@0.19.48
│   └── @codemirror/state@0.19.9 deduped
├─┬ @codemirror/lang-javascript@6.0.2
│ ├─┬ @codemirror/autocomplete@6.1.0
│ │ └── @codemirror/state@6.1.1 deduped
│ ├─┬ @codemirror/lint@6.0.0
│ │ └── @codemirror/state@6.1.1 deduped
│ └── @codemirror/state@6.1.1 deduped
├─┬ @codemirror/language@6.2.1
│ └── @codemirror/state@6.1.1 deduped
├─┬ @codemirror/matchbrackets@0.19.4
│ ├─┬ @codemirror/language@0.19.10
│ │ └── @codemirror/state@0.19.9 deduped
│ ├── @codemirror/state@0.19.9
│ └─┬ @codemirror/view@0.19.48
│   └── @codemirror/state@0.19.9 deduped
└── @codemirror/state@6.1.1

问题原因

从依赖树可见,项目中同时存在@codemirror/state的两个版本:6.1.1和0.19.9。这是因为CodeMirror 6的包有两种版本命名策略:早期版本使用0.19.x,后续统一升级为6.x,但部分旧包(如@codemirror/gutter@0.19.9、@codemirror/matchbrackets@0.19.4)依赖旧版本的@codemirror/state,而核心包(如@codemirror/commands@6.1.0)依赖新版本,导致重复安装,破坏了CodeMirror的instanceof检查,引发扩展识别错误。

另外,resolutions中强制指定旧版本@codemirror/state,但部分包未遵循该规则,仍引入新版本,加剧了版本冲突。

解决方案

1. 统一所有CodeMirror包到6.x版本

CodeMirror 6的所有包现已统一使用6.x版本号,将package.json中所有0.19.x版本的包替换为对应6.x版本:

"dependencies": {
  "@codemirror/commands": "^6.1.0",
  "@codemirror/gutter": "^6.0.1",
  "@codemirror/highlight": "^6.1.0",
  "@codemirror/history": "^6.0.1",
  "@codemirror/lang-javascript": "^6.0.2",
  "@codemirror/language": "^6.2.1",
  "@codemirror/matchbrackets": "^6.0.1",
  "@codemirror/state": "^6.1.1",
  "electron-updater": "5.2.1",
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

2. 移除冲突的resolutions配置

删除package.json中的resolutions字段,统一版本后无需强制指定旧版本:

// 移除以下内容
"resolutions": {
  "@codemirror/highlight": "0.19.7",
  "@codemirror/lang-javascript": "0.19.7",
  "@codemirror/state": "0.19.9",
  "@codemirror/view": "0.19.47"
}

3. 清理依赖并重新安装

执行命令清理旧依赖,重新安装统一版本的包:

rm -rf node_modules package-lock.json
npm install

4. 恢复注释的扩展配置

版本统一后,重新启用之前注释的扩展即可正常运行:

const startState = EditorState.create({
  doc: props.initialDoc,
  extensions: [
    keymap.of([...defaultKeymap, ...historyKeymap]),
    lineNumbers(),
    highlightActiveLineGutter(),
    history(),
    indentOnInput(),
    bracketMatching(),
    defaultHighlightStyle.fallback,
    highlightActiveLine(),
    EditorView.lineWrapping,
    EditorView.updateListener.of(update => {
      if (update.changes) {
        onChange && onChange(update.state)
      }
    })
  ]
})

验证

重新执行npm list @codemirror/state,确认仅存在一个版本的@codemirror/state,此时运行项目即可正常加载所有CodeMirror扩展。

内容的提问来源于stack exchange,提问作者leo0807

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 03:50:25