Hello,
I am new to npms and javascript. Such, I am having a lot of issues with it.
Currently, I’m trying to integrate CodeMirror 6 (including the merge
addon and lang-markdown
) into my NiceGUI (frontend python-based framework) application, following the node module integration example in the documentation link. However, the page fails to render, and I consistently get the following error in the browser’s developer console:
Uncaught SyntaxError: The requested module ‘/_nicegui/2.12.1/components/2bc0ddd3fb955118d45797c0bcfed968/codemirrorbundle.js’ does not provide an export named ‘default’ (at (index):56:16)
I’ve tried simplifying my code to a minimal example, but the error persists. I suspect there might be an issue with how webpack is bundling everything, especially my links that just won’t work.
Currently, I am on NICEGUI v2.0.0, python 3.12.7, node v22.13.1, and npm v11.0.0
This is how my directory looks like:
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
this is my package.json:
{
"scripts": {
"build": "webpack --config webpack.config.js"
},
"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"
}
}
this is my webpack.config.js:
const path = require("path");
module.exports = {
entry: "./codemirror_editor.js",
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "codemirrorbundle.js",
library: "CodeMirrorEditor",
libraryTarget: "window",
globalObject: 'this',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
};
this is my 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("@codemirror/merge");
const { EditorView } = await import("@codemirror/view");
const { basicSetup } = await import("codemirror");
const { EditorState } = await import("@codemirror/state");
const { markdown } = await import("@codemirror/lang-markdown");
const { keymap, defaultKeymap } = await import("@codemirror/view");
const { defaultKeymap: commandsDefaultKeymap } = await import("@codemirror/commands");
mergeViewInstance = new MergeView({
a: {
doc: initialValueA,
extensions: [
basicSetup,
markdown(),
EditorView.editable.of(true),
keymap.of(defaultKeymap),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
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) {
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(); }
};
}
this is my codemirror_editor.py:
from nicegui import ui
from typing import Callable, Optional
class CodeMirrorEditor(ui.element, component='dist/codemirrorbundle.js', dependencies=['dist/codemirrorbundle.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()
this is my main.py:
from nicegui import ui
from codemirror_editor import CodeMirrorEditor
@ui.page('/')
async def page():
editor = CodeMirrorEditor(value_a="# Initial A", value_b="Initial B")
ui.run()