在NiceGUI中集成CodeMirror 6遇模块解析及404错误求助
问题描述
尝试按照NiceGUI文档的Node模块集成示例,将CodeMirror 6(含merge插件和lang-markdown)集成到NiceGUI应用中,但页面无法渲染,浏览器控制台报错:
Uncaught TypeError: Failed to resolve module specifier "codemirror". Relative references must start with either "/", "./", or "../".
环境信息:
- NiceGUI v2.0.0
- Python 3.12.7
- Node v22.13.1
- npm v11.0.0
目录结构
nicegui-codemirror-minimal/ ├── dist/ │ └── codemirror-bundle.js ├── node_modules/ │ └── ... ├── codemirror_editor.js ├── codemirror_editor.py ├── main.py ├── package.json ├── package-lock.json └── webpack.config.js
相关文件内容
package.json
{ "name": "nicegui-codemirror-minimal-test", "version": "1.0.0", "description": "", "main": "codemirror_editor.js", "scripts": { "build": "webpack --config webpack.config.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@codemirror/commands": "^6.4.0", "@codemirror/lang-markdown": "^6.2.4", "@codemirror/merge": "^6.0.1", "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.25.4", "codemirror": "^6.0.1" }, "devDependencies": { "@babel/core": "^7.24.5", "@babel/preset-env": "^7.24.5", "babel-loader": "^9.1.3", "webpack": "^5.91.0", "webpack-cli": "^5.1.4", "vue": "^3.4.21" } }
webpack.config.js
const path = require("path"); module.exports = { entry: "./codemirror_editor.js", mode: "development", output: { path: path.resolve(__dirname, "dist"), filename: "codemirror-bundle.js", library: "CodeMirrorEditor", libraryTarget: "window", globalObject: 'this', }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, }, ], }, };
codemirror_editor.js
import { EditorView, basicSetup } from "codemirror"; import { EditorState } from "@codemirror/state"; import { markdown } from "@codemirror/lang-markdown"; import { MergeView } from "@codemirror/merge"; import { keymap } from "@codemirror/view"; import { defaultKeymap } from "@codemirror/commands"; import { nextTick } from 'vue'; export default { template: ` <div style="display: flex; gap: 1em;"><div ref="editorA" style="flex: 1; border: 1px solid #ddd;"></div> <div ref="editorB" style="flex: 1; border: 1px solid #ddd;"></div> </div> `, mounted() { nextTick(() => { setTimeout(() => { const initialValueA = this.value_a || ""; const initialValueB = this.value_b || ""; this.mergeView = new MergeView({ a: { doc: initialValueA, extensions: [ basicSetup, markdown(), EditorView.editable.of(true), keymap.of(defaultKeymap), EditorView.updateListener.of((update) => { if (update.docChanged) { this.sendUpdate('a', update.state.doc.toString()); } }) ], }, b: { doc: initialValueB, extensions: [ basicSetup, markdown(), EditorView.editable.of(true), keymap.of(defaultKeymap), EditorView.updateListener.of((update) => { if (update.docChanged) { this.sendUpdate('b', update.state.doc.toString()); } }), ], }, parent: this.$refs.editorA.parentElement, root: document, }); }, 0); }); }, methods: { sendUpdate(side, value) { this.$emit('update-value', { side, value }); }, setValue(side, value) { const editor = side === 'a' ? this.mergeView.a : this.mergeView.b; const tr = editor.state.update({ changes: { from: 0, to: editor.state.doc.length, insert: value } }); editor.dispatch(tr); }, }, props: { value_a: String, value_b: String, }, beforeDestroy() { this.mergeView.destroy(); } };
codemirror_editor.py
from nicegui import ui from typing import Callable, Optional class CodeMirrorEditor(ui.element, component='codemirror_editor.js', dependencies=['dist/codemirror-bundle.js']): def __init__(self, value_a: str = "", value_b: str = "", on_change_a: Optional[Callable] = None, on_change_b: Optional[Callable] = None ) -> None: super().__init__() self._props['value_a'] = value_a self._props['value_b'] = value_b self.on_change_a = on_change_a self.on_change_b = on_change_b self.on('update-value', self.handle_change) def handle_change(self, event): side = event.args['side'] value = event.args['value'] if side == 'a': self._props['value_a'] = value if self.on_change_a: self.on_change_a(value) elif side == 'b': self._props['value_b'] = value if self.on_change_b: self.on_change_b(value) self.update() def set_value(self, side: str, value: str): if side not in ('a', 'b'): raise ValueError("side must be 'a' or 'b'") self.run_method('setValue', side, value) @property def value_a(self) -> str: return self._props['value_a'] @value_a.setter def value_a(self, value: str) -> None: self._props['value_a'] = value self.set_value('a', value) self.update() @property def value_b(self) -> str: return self._props['value_b'] @value_b.setter def value_b(self, value: str) -> None: self._props['value_b'] = value self.set_value('b', value) self.update()
main.py
from nicegui import ui from codemirror_editor import CodeMirrorEditor @ui.page('/') async def page(): # Must be async editor = CodeMirrorEditor(value_a="# Initial A", value_b="Initial B") # That's it! No other UI elements or callbacks. ui.run()
更新内容
调整后解决了模块路径问题,但出现404错误:
GET http://127.0.0.1:8080/dist/merge.js net::ERR_ABORTED 404 (Not Found)
调整后的目录结构
nicegui-codemirror-minimal/ ├── dist/ │ ├── merge.js │ ├── state.js │ └── view.js ├── node_modules/ │ └── ... ├── codemirror_editor.js ├── codemirror_editor.py ├── main.py ├── package.json ├── package-lock.json └── webpack.config.js
调整后的相关文件
package.json
{ "scripts": { "build": "webpack --config webpack.config.js" }, "dependencies": { "@codemirror/merge": "^6.0.1" }, "devDependencies": { "@babel/core": "^7.24.5", "@babel/preset-env": "^7.24.5", "babel-loader": "^9.1.3", "webpack": "^5.91.0", "webpack-cli": "^5.1.4" } }
codemirror_editor.js
let mergeViewInstance; export default async function createCodeMirrorEditor(element, props) { const initialValueA = props.value_a || ""; const initialValueB = props.value_b || ""; // Dynamically import CodeMirror modules const { MergeView } = await import("/dist/merge.js"); const { EditorView } = await import("/dist/view.js"); const { EditorState } = await import("/dist/state.js"); mergeViewInstance = new MergeView({ a: { doc: initialValueA, extensions: [ EditorView.editable.of(true), // Removed other extensions for simplicity - you can add them back if needed EditorView.updateListener.of((update) => { if (update.docChanged) { sendUpdate('a', update.state.doc.toString()); } }), ], }, b: { doc: initialValueB, extensions: [ EditorView.editable.of(true), // Removed other extensions for simplicity - you can add them back if needed EditorView.updateListener.of((update) => { if (update.docChanged) { sendUpdate('b', update.state.doc.toString()); } }), ], }, parent: element, root: document, }); function sendUpdate(side, value) { element.dispatchEvent(new CustomEvent('update-value', { detail: { side, value } })); } function setValue(side, value) { const editor = side === 'a' ? mergeViewInstance.a : mergeViewInstance.b; const transaction = editor.state.update({ changes: { from: 0, to: editor.state.doc.length, insert: value } }); editor.dispatch(transaction); } return { setValue: setValue, destroy: () => { mergeViewInstance.destroy(); } }; }
codemirror_editor.py
from nicegui import ui from typing import Callable, Optional class CodeMirrorEditor(ui.element, component='codemirror_editor.js', dependencies=[ 'dist/merge.js', 'dist/view.js', 'dist/state.js', ]): def __init__(self, value_a: str = "", value_b: str = "", on_change_a: Optional[Callable] = None, on_change_b: Optional[Callable] = None ) -> None: super().__init__() self._props['value_a'] = value_a self._props['value_b'] = value_b self.on_change_a = on_change_a self.on_change_b = on_change_b self.on('update-value', self.handle_change) def handle_change(self, event): side = event.args['detail']['side'] value = event.args['detail']['value'] if side == 'a': self._props['value_a'] = value if self.on_change_a: self.on_change_a(value) elif side == 'b': self._props['value_b'] = value if self.on_change_b: self.on_change_b(value) self.update() def set_value(self, side: str, value: str): if side not in ('a', 'b'): raise ValueError("side must be 'a' or 'b'") self.run_method('setValue', side, value) @property def value_a(self) -> str: return self._props['value_a'] @value_a.setter def value_a(self, value: str) -> None: self._props['value_a'] = value self.set_value('a', value) self.update() @property def value_b(self) -> str: return self._props['value_b'] @value_b.setter def value_b(self, value: str) -> None: self.props['value_b'] = value self.set_value('b', value) self.update()
webpack.config.js
const path = require("path"); module.exports = { entry: { merge: "@codemirror/merge", view: "@codemirror/view", state: "@codemirror/state", }, mode: "development", output: { path: path.resolve(__dirname, "dist"), filename: "[name].js", library: "CodeMirrorMerge", libraryTarget: "umd", }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env"], }, }, }, ], }, };
需分析错误原因并提供解决方案,可从webpack配置、模块导入方式等方面给出建议。
内容的提问来源于stack exchange,提问作者ayejays
相关产品推荐
相关产品推荐

