Hacker News new | past | comments | ask | show | jobs | submit login

They key thing is that require(esm) shipped in node 22, and is being back ported to node 20.

Since Node 18 maintenance ends in less than a month, this means all Node versions will have good esm support, including for consuming esm-only libraries (which until recently did not work!)

This is noted somewhat in the article, but basically is the whole story to me. Its now possible to stop doing CJS libraries entirely. And with that, I don't see why we would do CJS at all.




Annoyingly you have to use an experimental flag. That just adds too much friction.

Latest Node version added Node options as config feature. I wish that was ported to every version of Node.

Otherwise you have to set NODE_OPTIONS which can often be overwritten by some scripts in the execution chain.


require(esm) is no longer behind a flag in v22.12.0+ and v20.19.0+.


Funnily my conclusion was, on the contrary, that it's now possible to ignore ESM entirely when using/targeting nodejs ;)


(sadly at work, at this point we already have done the useless work of migrateming to ESM for no good reason other than having libs that were ESM only)


ESM has heaps of benefits: being able to do static analysis, or limiting the exposed modules in a package, for example.


Also not having to debug two separate shipped codebases. “The CommonJS version does X” is an annoying github issue to fix.


Tree-shaking also.


Static analysis and tree shaking have been done with CJS for about a decade.

https://gist.github.com/joepie91/bca2fda868c1e8b2c2caf76af7d...


The main argument in that post doesn't even hold; using ESM, you can always using dynamic imports:

  await import( someExpression )
Besides, static analysis and tree shaking completely break down if modules impose side effects from being required, which is one of the main gripes of Python as well. ESM completely alleviates that.


That forces dynamic imports (and is horrible ergonomics as discussed in the post). CJS requires can be statically analyzed to figure out whether a require can be static, as is done e.g. in all bundlers.

ESM import runs side effects as well.


We have had top-level await for a while now. I don’t know what’s so horrible about this:

  const app = express()
  app.use("/users", await import("./routers/users"))
…if you’re so inclined to do things exactly as you did in the past. I’m pretty sure bundlers will even transform that into an ordinary module import.

The cases where you actually need dynamic imports are few and far between. Are we actually talking about engine limitations here, or is it just a few snowflakes that insist on creating loggers like this?

  require("debug")("acme")
What is so particularly pretty about that is beyond me.

> ESM import runs side effects as well.

At runtime, yes. Not during static analysis.


In a limited way due to side effects from the require function.

Reading that rant, the author did not seem to take much time to understand the rationale behind ES modules: “ yet for some completely unclear reason, ESM proponents decided to remove that property” is just pure ignorance.


> In a limited way due to side effects from the require function.

In a limited way that covers something like 99.9% of CJS usage. Bundling is based on static analysis.

If by side effects you mean running code, not just declaring exports, ES6 import does these side effects too.

Thinking that CJS can't be used for static analysis or tree-shaking is the widest spread pure ignorance.


I'm doing server code (which I guess is the main use case for nodejs), I just COPY the project folder in the docker and ship that. No need for bundling, shaking and other annoyance.


Tree-shaking also happens in GC heap space (both in the server and in the browser): V8 and SpiderMonkey and JavaScriptCore will all eventually treeshake unused code out of memory entirely. The ESM format was designed to allow that. Modules actually only reference each other through weak references and things like import * build proxy objects of weak references designed for tree-shaking.

Depending on how your application is structured, an ESM version of a server-only Node application may still benefit from the performance optimizations of the server being able to do dead code elimination at runtime.


You'd likely still benefit from using TypeScript and transpiling that to plain JavaScript (preventing a lot of subtle bugs), and tree shaking to minimize your Docker image size (yielding faster deployments).


We do use TS already. And yeah sure, we could save a second or two on our deployment, but I'm not sure that's worth the mess


In case you ever get to that point; have a look at Unbuild (https://github.com/unjs/unbuild). It's what Vite uses for their own builds, and really painless to set up.


Haha I'm probably getting too old or something but I really cannot see what this tool does exactly and why I'd need it. I have been working on what is now a fairly large typescript/nodejs monorepo (over 1M LoC) for the past ~13 years, and we can simply build it all with tsc -b, and COPY it in the docker image as I said, it's nice and simple and works great.


Yeah, there is no point in tree shaking and bundling for server code so your comment makes little sense.


There are still maintained and supported older versions of node.js by the likes of Debian, Ubuntu, Enterprise Linuxes. They backport security fixes and such during a longer extended window but are unlikely to port ESM-require support. It may not be relevant to you and obv nobody's obliging you to also support those users, just saying it's not that binary.


If you are pinning to unsupported node you should not expect new npm packages to work, ESM or not.

However given my NPM experiences in the past, I would not be surprised that someone updated to ESM in a revision bump.


Is that really "support those users"?

If they need to use a new library version, they can install their own version of node instead of relying on the OS supplied one.

All the OS version is doing is supplying the convenience of not having to install it.

Or have I misunderstood how those versions of node are used?


Sometimes the entire reason why they are still on that older LTS dist is because they still need (e.g.) node 12 for some reason.


That really becomes a "not your problem" as a library author.

If a user wants to use an old version of node, they'll have to use an old version of the library that still supports cjs.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: