The long-standing divide between CommonJS (CJS) and ECMAScript Modules (ESM) in the Node.js ecosystem just took a major step toward resolution. Thanks to the incredible efforts of Joyee Cheung, require(esm)
has been backported to Node.js 20.
This change means that when Node.js 18 reaches end-of-life (EOL) in April 2025, library maintainers can finally drop their CJS builds and ship ESM-only packages with confidence.
A Brief History of require(esm)
: From Experimental to Mainstream#
For years, the Node.js ecosystem has been split between those who prefer the simplicity of require()
in CommonJS and those pushing for the future of ESM with import. While Node.js introduced native support for ESM in 2019, the lack of require(esm)
made full adoption challenging.
That changed on December 5, 2024, when Node.js 22.12.0 (‘Jod’) shipped as the first LTS version with require(esm) enabled by default—removing the need for the --experimental-require-module
flag.
Joyee Cheung (Node.js TSC delegate) explained why this was a critical step:
It helps accelerate ESM adoption as package authors can start shipping native ESM with less breakage to their CJS users; it also helps frameworks and tools that take plugins to support native ESM in user/plugin code whilst they are still navigating their own migration to ESM.
Despite this progress, many developers remained locked into Node.js 20 for production stability. The recent backport of require(esm)
to Node.js 20 now ensures that long-term users can also benefit from this change while maintaining compatibility. However, developers are still encouraged to upgrade to newer versions, like Node.js 22, for improved security and stability.
Backporting require(esm)
: A Technical Feat#
The backporting process to Node.js 20 was not trivial. According to the tracking issue, the work involved:
- Refactoring ESM detection to eliminate performance costs when loading modules.
- Fixing CommonJS-ESM interop by improving how default exports are handled.
- Ensuring stable behavior for
require(esm)
across different module-loading conditions.
A total of 119+ patches were reviewed and triaged to ensure compatibility with Node.js 20.
Joyee Cheung @joyeecheung:
"I worked through the list with a new strategy (backport as few as possible) and came up with a v20.x backport branch. My assessment is that backporting it to v18.x is probably going to be an order of magnitude harder and not really worth it..."
As a result, Node.js 18 will not receive this backport, meaning developers must upgrade to Node.js 20 or later to take advantage of require(esm)
.
The backport of require(esm)
to Node.js 20 has been met with enthusiasm across the developer community. Many see this as a long-awaited resolution to the CJS-ESM incompatibilities that have complicated package maintenance for years.
“Our community has been split into camps. I for one, a guy with too much CJS to jump ship, have felt bitter about it since import() meant converting to apps that don't need it to be asynchronous,” JavaScript developer TQ White said. “That's over now. Let the healing begin!!”
For many library maintainers, this change means a significant reduction in complexity.
“If it is backported to Node 20, as it seems now, Vite 7 will stop shipping the thin CJS layer that was still needed so far,” Vite core team member Matias Capeletto (@Patak) said.
Another developer expressed similar excitement about the backport, noting that it removes a major roadblock to simplifying module bundling in modern JavaScript applications.
“The library I work on outputs 8+ bundles in a relatively convoluted way,” @ianb.dev commented on Bluesky. “With the Vite 6 Environment API and this, we'll be able to simplify it so much, love it!”
The shift toward an ESM-first future in Node.js has now reached a major milestone, and the broader JavaScript ecosystem is beginning to embrace the benefits of a unified module system.
A Defining Moment for ESM Adoption#
With Node.js 18's EOL in April, any project supporting Node.js 20+ can confidently ship only ESM. This is a major turning point—tools like Vite, libraries with complex bundling setups, and package maintainers maintaining legacy CJS builds now have a clear path forward.
This shift wouldn’t have been possible without the dedication of Joyee Cheung spearheading the initiative, whose work on require(esm)
has made life easier for thousands of developers.
If you’ve been holding off on ESM due to compatibility concerns, now might be the time to embrace the future. With this change, the CJS vs. ESM debate may finally start to fade into history.