Just sharing something I found. Note that I have no clue at the moment whether this works for all libraries. Don't have enough free time to figure out stuff like that. So if anybody else knows more about the topic or knows a better solution, please share your knowledge.
I needed to use the svglint npm package in the backend js file of one of my custom nodes. However that library is an ES (EcmaScript) module and no classic CommonJs module.
When I import the ES module in my js file:
import SVGLint from "svglint"
Then that fails:
SyntaxError: Cannot use import statement outside a module
When I load it like a classic CommonJs module:
const SVGLint = require('svglint')
Then that also fails:
Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/svglint/node_modules/chalk/source/index.js from .../node_modules/svglint/src/svglint.cjs not supported.
Instead change the require of index.js in .../node_modules/svglint/src/svglint.cjs to a dynamic import() which is available in all CommonJS modules. (line:8)
So after some trial and error I managed to do a dynamic import, like suggested in the previous error message:
Note that I used module.default after I had uncommented the console.log statement above, which revealed this information to me in the Node-RED console log:
Thanks Bart, not sure if you saw my Lounge post the other day about this.
Node-RED actually already uses this approach in core.
The big issue is that dynamic imports are async. That can often mean that you need to reorganise your code to be async back up a chain of function calls. That can require very extensive changes to existing code.
But yes, when you can cope with that issue, it absolutely works.
For the future, there is now an experimental feature in the new Node.js v22 that will allow synchronous requires of ESM's - long overdue.
There are some serious gotya's when working with CJS modules in ESM's as well - mostly around how different import is from require().
Safe to say that the move from CJS to ESM (which does need to happen since ESM is now the JavaScript standard and works in the browser as well) is a MESS in Node.js.
What I'd like to eventually do is create a test Node that uses ESM for libraries and dependencies to see what impact that might have on the top-level code.
I've already a lot of async code now in my nodes due to the handling of Typed Inputs so possibly now less of an issue at least for new nodes. Certainly I don't seem to have any issues making the input code async.
Not really... Do you mean the Discourse Lounge? Long time ago that I received that. Must have been kicked out of it in that case...
Ah I wasn't aware of that. Didn't have a look in the core for this one...
I know that this has been discussed it a number of times in the past, but wasn't clear anymore to me if someone had shared a solution/workaround already.
Mind blowing that they didn't provide something like that from day one.
Like you say it has become a complete mess.
When a lib switches from CommonJs to ES, I see advices all over the place to keep using the last unmaintained CommonJs version. Not really a solution for the problem...
Indeed. I had/have a couple of those in uibuilder dependencies. Though now I'm preparing v7, I've already managed to get rid of them.
The longer I deliver in this space, the more and more determined I get to avoid dependencies and frameworks. I'm down to just 7 dependencies now (and no frameworks of course!) of which one is my module anyway. fs-extra is my next target and I'm moving all filing system access into a single library and eliminating fs-extra calls along the way.
Ran into "only ESM forward" with chai for testing and a few libraries I looked at for new nodes.
I naively tried type=module in package.json, swapped require for import, but then NodeRED wouldn't load the node.
Without knowing the code, hard to say. I do know that Node-RED's module loaded does allow for ESM but it has to load modules asynchronously and I don't know if that will cause any timing edge-cases (I assume we are talking about the runtime code here, in the Editor code, use of a module is probably a bit simpler assuming you limit yourself to browsers that support ESM, which shouldn't be hard now).
There are also a few things in Node.js that work slightly differently when switching to ESM from CommonJS - the Node.js docs have a decent description of what doesn't work.