June 20th, 2024

NPM and NodeJS should do more to make ES Modules easy to use

Boris Cherny discusses challenges with ES Modules in NodeJS and NPM, proposing solutions like simplifying file extensions, upgrading libraries, and phasing out CommonJS support in NodeJS to boost adoption rates.

Read original articleLink Icon
NPM and NodeJS should do more to make ES Modules easy to use

Boris Cherny discusses the challenges faced when using ES Modules in NodeJS and NPM, highlighting errors encountered and the evolution of module systems in JavaScript and TypeScript. Despite the benefits of ES Modules, adoption rates remain low. Cherny suggests simplifying the ecosystem by eliminating new file extensions like .mjs and making type=module the default in package.json files. He also proposes upgrading common libraries to ES Modules and requiring an explicit module field in the NPM registry for packages using CommonJS. Additionally, he suggests NodeJS could phase out support for require and module.exports to encourage migration. Cherny invites feedback on the difficulties of interoperating ES Modules and CommonJS, encouraging discussion on platforms like HackerNews and Threads.

Link Icon 36 comments
By @bastawhiz - 4 months
The simplest solution is to stop requiring the top level package to be a module. Allow es modules to be require()-ed. It's be synchronous and slow and ideologically impure, but it'll make it so that millions of projects (mostly private!) will be able to start adopting es modules without a massive refactor. Anyone with a project of significant age or size looks at ES modules now and thinks "fuck it". There's no return on investment to convert (other than less pain while trying to upgrade or adopt new libraries). It's a big undertaking with loads of risk (modifying import order isn't safe!) and the payoff is "it's the shiny new thing".

I had been using adminjs at work. Their new major version was ESM-only, and it was easier to _write a new admin panel from scratch_ than it was to refactor our entire codebase to be ESM just so we could upgrade one library. I expect that's the situation at hundreds of thousands of other companies.

Like for all the belly aching that happened over async functions (and the whole function color rant), synchronous and asynchronous functions work together just fine through plain old promises. You can easily use async functions alongside functions that use old fashioned callbacks. ESM vs CJS is a file coloring (versus function coloring) problem, but there's no interoperability. There's no escape hatch when you just need one file to use another file but their colors are incompatible.

By @knallfrosch - 4 months
I don't know which is which. I don't care. I don't understand the benefit of a top-level await if I can simply await in a different file. I use Typescript which adds a layer in-between anyway. At work, we use Angular, which (I think) uses both Typescript and maybe esbuild. Or Webpack. Does it compile to ESM, or CJS? Who knows, and it will change in 2 years again anyway.

All of that is something that I consider to be platform-level. It's insane that millions of feature-writing devs are expected to know all these arcana.

Then again, it might be fixed™ soon Ⓡ

https://joyeecheung.github.io/blog/2024/03/18/require-esm-in...

By @jakubmazanec - 4 months
I followed advice of Sindre Sorhus [1] and moved all my packages and apps to ESM year ago and couldn't be happier. Only Jest and ts-jest were problematic, so I replaced them with Vitest. I also never encountered problems because of the so-called dual-package hazard [2]; but IMO this isn't that much different than when you have two copies of React in node_modules - it's simply an npm/dependencies problem, not ESM problem.

[1] https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3...

[2] https://nodejs.org/api/packages.html#packages_dual_package_h...

By @jeswin - 4 months
ES Modules are better in every way.

But I do believe they got the syntax wrong - should have been "from fs import { readFile }" so that auto-complete works. Python got that right, but that's the only thing Python got right ;)

By @gbuk2013 - 4 months
Can someone explain to me what the advantages of ESM actually are to me as a backend dev who uses import / export syntax in TS already?

Parent article mentions static analysis and synchronous loading on startup but that has never been an issue for me despite building some large and complex Node apps over the years.

I’ve looked into this in the past but all I could find are strong opinions without solid technical reasoning.

By @can3p - 4 months
I think the module imports apis are a python2/3 moment for node.js ecosystem. There is no clearly superior way and as a consequence not too many people care, however it hurts for real.

The proposal to disable node.js style imports will just split ecosystem and make a large part of industry stick to ancient version / make a fork. Is that really worth the gain? Just check how long it took some bigger projects to migrate from python2 to python3

By @apitman - 4 months
My only complaint about the current state of ESM is that I can't figure out how to resolve the following:

1. I'm developing a library that depends on d3js

2. I want the library to be usable without any build tools. Just clone my repo and host the files from a static server. Or just import directly from jsdelivr

3. I also want people to be able to use NPM to install my library if they so choose

The problem is if I vendor d3js, then developers who consume my library via NPM might end up with 2 copies of d3js in their app, if their app also uses d3 directly. But if I don't vendor it, then my ESM-only users have to use an import map to resolve the bare specifier in the browser, which is kind of ugly and confusing.

More details: https://stackoverflow.com/q/78645299/943814

By @righthand - 4 months
Why does it matter that people use ES modules instead of requires? They’re compatible enough. Javascript is weird because it has this directive for browsers to keep compatibility, but then has proponents for language changes that people try to force on everyone through framework/library use and design. All to the benefit of someone reading code the way they want to read code.

It hasn’t changed because it’s not a real problem. This is like forcing main as the default branch in git.

By @Aeolun - 4 months
Or you can use Bun and have it handle all the nonsense. Or esbuild, but then you get a big blob, which isn’t really usable for a lot of things.

The extensions were always silly to me. Who changes only a few files to esmodules? You either change your whole project or not at all.

By @catapart - 4 months
Just use JSR[0] and only deal with npm when a project forces you to do things backwards. Since JSR packages are available on npm, there's nothing lost.

[0]https://jsr.io/

By @TheRealPomax - 4 months
At the very least, they finally should switch over to ESM-by-default and announce that 2 or 3 major versions in advance. "From Node 25 all code is assumed ESM unless you have `"type": "commonjs"` in your package.json" is not a particularly difficult message to send out and would stop people from writing new projects using the now legacy CJS (super great that it existed back when JS had nothing even close to a dependency model, but it should have been retired once ESM went from stage 4 to "this is literally and officially how JavaScript works")
By @h1fra - 4 months
The fact that `npm init` still not default to `type: module` is baffling
By @ramesh31 - 4 months
>NodeJS can officially drop support for require and module.exports in a future version, creating a bit more pressure to migrate.

This will never, ever happen. Too much of the foundational ecosystem relies on it.

By @PaulHoule - 4 months
This is timely to me because I am in the middle of modernizing a project that was ejected from CRA years ago and now won’t build in Node 18.

I’ve worked on a few big React projects but haven’t really looked much into how the build works, I found out upgrading one thing forced me to upgrade other things and I wound up making a lot of changes by hand to the build scripts and figured I’d probably screw something up. Dependencies changing from CommonJS to ESM was probably the most common problem that can frequently solved by version bumps (at risks of adopting changes you don’t want)

At some point I decided to try the alternate path of switching to Vite for a build system since I’ve had good luck working with it for some VR side projects.

It’s funny how you can code on front-end Javascript and not need to learn about CommonJS until something like this hits.

By @lenerdenator - 4 months
Surely, this will be the thing tacked onto JavaScript that will make it easy to scale and reduce toolchain complexity......
By @fwlr - 4 months
Bun put a lot of work into making both “import” and “require” always work regardless of whether it’s given a commonjs or an ESM target. I’d say that’s half of the right idea: make only import work with anything.

Another angle that might be effective: take the most popular aspect of commonjs - `require(‘extensionless-string’)` - and tie it to the least popular aspect, .cjs extensions.

By @o11c - 4 months
As someone who only dabbles in JS, one problem is that ESM makes polyfills impossible. And polyfills are mandatory in the JS ecosystem.

It's quite meaningful for dependencies to be fetched asynchronously, but sometimes you really need something to be executed in the order it's written.

By @preya2k - 4 months
And then here is one of the biggest backend JS frameworks (NestJS) clinging to CJS/holding off on migrating to ES modules. https://github.com/nestjs/nest/issues/13319
By @ulrischa - 4 months
The whole node npm ecosystemand tooling is so bad and broken. No other language is so frustrating. JavaScript was bad in the early years took a good way but is now stuck again. I tried webcomponents with lit. All very easy in the beginning. But when you try to use an external stylesheet you hit the wall of the modern js world: css can not be imported in js without a massive tool stack. I whish tue main focus would be tool- and buildless. This is really what is lacking
By @999900000999 - 4 months
I'm tempted to say we need a complete reset of the NodeJS ecosystem.

It will never happen because it would require coordination and money, but instead of having millions upon millions of different NPM packages, many of which are downright harmful, we should have a careful selection of maybe the top 20,000 or so.

And then maybe call the next generation of node something else, maybe ProJS.

By @pictur - 4 months
if you look at most npm packages, you can see that versions that do not support es modules are downloaded more. and it's been like this for years. an example package: https://www.npmjs.com/package/p-queue?activeTab=versions
By @meego - 4 months
Barely a third of "high-impact" packages on npm are ESM. And that's with a generous definition of what an ESM package is. [1]

[1]: https://github.com/wooorm/npm-esm-vs-cjs

By @montroser - 4 months
Node should just do like bun and support intermixing both. It was a mistake to force this schism -- untold hours of frustration and busywork for maintainers and developers with no hope of actually "completing" a mythical full transition to the new world.

And for what? In Node specifically, it's not as if esm actually solves any real problem! In the greater ecosystem, sure it has some benefits, but Node doesn't even have to choose. Just support both at once, like bun and build systems have for a while, and let's move on from this nonsense.

By @byt3h3ad - 4 months
totally unrelated, but for the first time did i see a discuss here to threads. i had to double check if it was really the threads by instagram.
By @plopz - 4 months
not being able to mock es modules in tests is a real pain
By @zazaulola - 4 months
Jesus, what the hell?!

Why do you dislike `require()` so much?

Just imagine a person who doesn't write modules in Node.JS, but he would like his small scripts to be placed in one JS-file - without any additional directories and `package.json`. His script, for example, updates data for a desktop widget, is easily bypassed by standard nodejs modules and he has hundreds of such scripts in his folder. Why does he need all this `import` overhead?

By @pcloadletter_ - 4 months
How are the deer in Nara?
By @tobyhinloopen - 4 months
It’s such a waste of time to have this change so often. Can we just stop changing things? Require and module.exports was fine.
By @replete - 4 months
Completely disagree on getting rid of .mjs, .mts, .cjs, etc. This has actually solved a lot of the problems we had of mixed module loading IMO
By @mirekrusin - 4 months
And yet approximately 100% of js/ts devs are using ES module syntax and don’t write blog posts about it. Magic.
By @rezokun - 4 months
Or JS ecosystem should drop ES modules since they have only brought pain and unnecessary complexity without real benefits for years.