Node 22 – which will eventually be a LTS (Long-term support) version – is out with new features that include experimental support for the require statement targeting ECMAScript modules (ESM) as well as CommonJS (CJS) modules.
This is the latest effort to mitigate a long-running compatibility problem, caused when the official ECMAScript technical committee adopted a module system different from the one already used by Node.js, the most widely used runtime for JavaScript outside the browser. ECMAScript is the formal name of the JavaScript standard.
The outcome was that browsers support ESM but not CJS – yet CJS modules remain more widely used. According to data on the most popular packages in the NPM (Node Package Manager) registry, adoption of ESM is painfully slow, with just 21 percent of modules with ESM or dual in February 2024, up from 7.8 percent in August 2021. More than 67 percent are CJS.
The subject is a matter of debate among those working on competing JavaScript runtimes, with the Deno team arguing that JavaScript is “sabotaged by its own baggage from the past … CommonJS”, while Bun creator Jarred Sumner is focused on native support for CJS alongside ESM, stating that “we will never hit a point where all packages can be expected to use ES modules.”
A core compatibility issue is that ES modules load asynchronously, whereas CJS modules are synchronous. This means that until now the require() statement used to import a module only supports CJS, while ESM modules can be imported using import(). “Due to the synchronous nature of require(), it is not possible to use it to load ECMAScript module files,” state the docs for Node.js 21, the previous version. “Use import() instead.”
Node.js 22 now supports loading ES modules with require() provided they do not have or import a top-level await statement, indicating that they are asyncronous. If a top-level await is discovered, an exception is thrown. The support is subject to a flag –experimental-require-module. Node.js TSC (Technical Steering Committee) member Joyee Cheung describes the motivation for the change in the pull request which implements it, stating that “in 2024 I believe we can agree that not supporting require(esm) is creating enough pain for our users that we should really deprioritize the drawbacks of it. A non-perfect solution is still better than having nothing at all.”
A use case which concerns the Node.js team is what happens when a module goes ESM-only. The change to require() means that this is “less likely to be a breaking change for users,” Cheung wrote.
Another key feature in Node.js 22 is that the native WebSocket client is now enabled by default. WebSockets are ideal for real-time communications. In addition, the V8 JavaScript engine has been updated to version 12.4.
The Watch feature, whereby a process is restarted when an imported file is changed, is now marked stable. This was introduced as an experimental feature in Node.js 18.
TSC member Rafael Gonzaga told DevClass that the arrival of strong competing runtimes, Deno and Bun, has been beneficial to Node.js. Diverse perspectives lead to better solutions, he said. “The success of a feature in another runtime is a testament to its value, and we are keen on considering such factors.”
Security is also top of mind, with the sophistication of supply chain attacks increasing. Gonzaga highlighted the Permission Model, “the experimental feature originally released with Node.js 20, which is actively being developed and researched.” The team is considering integrating this with package managers. He added that “the Node.js codebase is under constant scrutiny through initiatives like OpenSSF Scorecard and CII-Best Practices.”
The Node.js release schedule means that a major version appears every six months, but only even-numbered releases become LTS. These releases only achieve LTS status when the next version appears, so in this case October 2024. In the meantime, Node.js 22 is intended more for test and evaluation than production use.