Node.js require(esm) Goes Stable: CommonJS and ESM Finally Reconcile

Node.js now marks require(esm) as stable in LTS releases, allowing CommonJS files to require synchronous ESM modules without extra flags, simplifying package.json configuration, eliminating the need for dual packages, and resolving the long‑standing CJS‑ESM split for the vast majority of projects.

Node.js Tech Stack
Node.js Tech Stack
Node.js Tech Stack
Node.js require(esm) Goes Stable: CommonJS and ESM Finally Reconcile

Node.js developers have long struggled with the ERR_REQUIRE_ESM error when trying to require() a pure ESM package from a CommonJS (CJS) project. The typical work‑arounds involved upgrading the whole project to ESM, adding complex exports fields in package.json, or even compiling ESM back to CJS.

require(esm) becomes stable

Core contributor Joyee Cheung announced that the require(esm) feature is now stable (no experimental flag) in all supported LTS versions (v20.19.0+ and v22.12.0+). This means a CJS file can directly require() a synchronous ESM module without any command‑line flags or build tools.

Why the split existed

CommonJS (CJS) : uses require(), loads modules synchronously.

ESM : the JavaScript standard module system, uses import, designed for asynchronous loading.

Because server‑side code still heavily relies on CJS, package authors faced a dilemma:

Compile ESM to CJS ("Faux ESM").

Publish dual packages, risking duplicate module instances.

Publish only ESM and force CJS users to abandon the package.

The introduction of require(esm) breaks this deadlock, letting CJS consume ESM seamlessly and removing the pressure to ship a CJS version.

Developer concerns

1. How does require() handle top‑level await ?

ESM permits top‑level await, making module loading asynchronous, while require() must return synchronously. The article confirms that require() still cannot load ESM modules that contain top‑level await; attempting to do so will raise an error.

However, an analysis of the 5,000 most popular npm packages shows that only about 0.02% actually use top‑level await. Of those, six packages use it, and three misuse asynchronous fs methods that could be synchronous. Most libraries do not need top‑level await, so the limitation is irrelevant for 99.98% of cases.

2. Does package.json become simpler?

Previously, supporting both CJS and ESM required a complex exports map and a build step that emitted a dist/ directory. Example of the old configuration:

{
  "scripts": {
    "build": "babel src -d dist" // maintain build script
  },
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js" // must provide CJS version
    }
  }
}

With stable require(esm) and targeting Node.js v20+ or v22+, the configuration can be reduced to:

{
  "type": "module", // declare ESM
  "exports": "./src/index.js", // point directly to source
  "engines": { "node": "^20.19.0 || >=22.12.0" }
}

There is no dist folder, no build step, and no conditional exports field—resulting in a clean, minimal package.json.

Why it took so long?

Technical side : Making a synchronous require load an inherently asynchronous ESM required substantial changes in the V8 engine and Node.js module loader.

Community side : Early opinions suggested “pain the CJS users” to force migration, but that strategy backfired, deepening the split instead of accelerating ESM adoption.

Joyee Cheung describes the effort as “it takes a village”, noting that Bloomberg sponsorship and contributions from many community members helped resolve edge cases and finally land the feature in LTS.

Conclusion: From division to convergence

The stabilization of require(esm) is more than a technical update; it signals that the JavaScript ecosystem can now acknowledge CommonJS’s existence without letting it hinder ESM’s spread. For application developers the change may be as simple as one fewer error, but for library authors it dramatically reduces maintenance burden and opens the path to a lighter, source‑only Node.js ecosystem.

Reference: "require(esm) in Node.js: from experiment to stability" – Joyee Cheung's Blog
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Node.jsmodule systempackage.jsonCommonJSESMLTSrequire(esm)
Node.js Tech Stack
Written by

Node.js Tech Stack

Focused on sharing AI, programming, and overseas expansion

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.