← Home

The Two ESM/CommonJS Errors Everyone Quotes Are Gone. One Nobody Mentions Is Not.

require() of an ESM module works now, and TypeScript no longer complains either. What still breaks is the dual package hazard — and it fails as instanceof returning false, not as an error.

Two thick pipes meeting seamlessly in the centre, one warm cream and one cool blue, with two identically shaped blocks in those two colours fallen out below.

Search for an ESM/CommonJS error and you get thousands of confident answers. Most of them are correct — about a version of Node from three years ago. The specific advice they give you now fixes a problem you do not have.

The famous error does not happen any more

Measured on Node v25.9.0, with real packages and real package.json files: require() of an ESM module succeeds. No ERR_REQUIRE_ESM.

What you get back is the module namespace, not the default export:

keys: Token, __esModule, default, name
m[Symbol.toStringTag] === 'Module'

That distinction matters more than the fix everyone remembers. Code carried over from the old world reaches for .default, and .default is now the default export sitting as one property among the named ones — usually not the thing it wanted.

Neither does the TypeScript one

TS1479 — the current file is a CommonJS module whose imports will produce ‘require’ calls; however, the referenced file is an ECMAScript module — is the compile-time half of the same folklore.

On TypeScript 6.0.3 with module: nodenext, a .cts file importing a .mjs file produces no diagnostic at all. I checked that the file was actually being type-checked by putting a deliberate type error in it first:

main.cts(2,14): error TS2322: Type 'number' is not assignable to type 'string'.

The control fires, the interop error does not.

What actually breaks now

What Node actually does
The side doing the loading

What came back

keys: Token, default, name  ·  m.default === 'i-am-a-property-called-default'

Error, verbatim

No error. This one succeeds.

CJS globals inside ESM

  • ReferenceError: __dirname is not defined → import.meta.dirname
  • ReferenceError: __filename is not defined → import.meta.filename
  • ReferenceError: require is not defined → module.createRequire(import.meta.url)
  • ReferenceError: module is not defined → no direct replacement
  • ReferenceError: exports is not defined → no direct replacement

Every cell measured on Node v25.9.0 on 2026-08-07, with real packages.

Every cell above was run, not looked up. Three of them are worth reading closely.

Top-level await is the new failure

require() an ESM module whose graph contains a top-level await and you get:

require() cannot be used on an ESM graph with top-level await. Use import() instead.
To see where the top-level await comes from, use --experimental-print-required-tla.

That is a good error. It names the cause, gives the fix, and hands you a flag that finds the offending module. It is also the message you will actually meet in 2026, which is why it is reproduced here verbatim rather than paraphrased.

Named imports from CommonJS depend on how the source is written

Node finds a CommonJS module’s named exports by analysing the source statically. An export whose key is computed at runtime is invisible:

const key = ['com', 'puted'].join('');
module.exports[key] = '…';

The namespace then contains only default and a literal module.exports key, and the named import fails:

SyntaxError: Named export 'computed' not found. The requested module 'cjs-dynamic' is a
CommonJS module, which may not support all module.exports as named exports.

Note may not. This is not a rule about CommonJS; it is a statement about what a static analyser could see in that particular file. That is why the same import style works against one package and fails against another, which feels arbitrary until you know what it is looking at.

The dual package hazard is still here

A package that ships both entry points can end up loaded twice in one process:

require(pkg).Token === (await import(pkg)).Token   →  false
instance instanceof otherToken                     →  false

Two classes, same source, different identities. Nothing throws. Your instanceof check simply returns false, your error-type matching stops matching, and the symptom appears somewhere far away from the cause.

This is the one that survived. The two errors everyone quotes got fixed because they were loud and blocked people immediately. This one is quiet, so it is still here.

The globals, for completeness

Inside ESM, all five CommonJS globals throw the same plain error:

ReferenceError: __dirname is not defined

…and likewise for __filename, require, module and exports. The replacements are import.meta.dirname and import.meta.filename, both of which are strings, and module.createRequire(import.meta.url) when you genuinely need require.

What to take from this

The useful habit is not memorising which combination works. It is noticing that this topic rots faster than almost anything else you will search for, and that a highly upvoted answer is evidence about the past.

Before you restructure a package because of something you read: run the two lines that reproduce it on the Node you actually ship. Half the time in 2026 the problem is already gone, and the change you were about to make would only add a layer of compatibility shim protecting you from nothing.

Common follow-ups

Is ERR_REQUIRE_ESM really gone?

For a plain ESM module, yes — measured on Node v25.9.0, require() of an ESM package succeeds. You get the module namespace, so the named exports are on the object you receive rather than under a default property. If the module graph contains top-level await it still fails, with a different error.

What does require() return for an ESM module?

The namespace object, with Symbol.toStringTag set to "Module" and an __esModule marker added. Code written for the old world that reaches for .default gets the default export as a property, which is usually not what it wanted.

Why do named imports from a CommonJS package sometimes fail?

Node detects CommonJS named exports by statically analysing the source, so an export whose key is computed at runtime is invisible to it. The error says the module "may not support all module.exports as named exports" — may not, because whether it works depends on how that module's source is written.

What is the dual package hazard?

A package that ships separate CommonJS and ESM entry points can be loaded twice in one process, once through each. The two copies do not share identity, so a class from one is not the same class as from the other and instanceof against it returns false. Nothing throws — the check just quietly says no.

What replaces __dirname in ESM?

import.meta.dirname, and import.meta.filename for __filename. Referencing __dirname, __filename, require, module or exports inside ESM throws a plain ReferenceError saying the name is not defined.