Skip to content

require.extensions is undefined on synthetic require for CJS loaded through ESM loader (regression in v24.15.0) #62786

@kabo

Description

@kabo

Version

v24.15.0 (fails). Works on v24.14.1 and earlier. Platform: Linux x64 (also reproduced inside node:24.15.0-slim Docker image).

Platform

Linux x64

Subsystem

loaders, module, specifically the CJS-from-ESM translator (node:internal/modules/esm/translators).

What steps will reproduce the bug?

Reproducer repo: https://github.com/kabo/node-24.15-require-extensions-repro

git clone https://github.com/kabo/node-24.15-require-extensions-repro
cd node-24.15-require-extensions-repro/variant-b
touch yarn.lock
corepack yarn install
corepack yarn node run.mjs   # crashes on v24.15.0; succeeds on v24.14.1

The reproducer is ~10 lines of code split across 4 files: a Yarn 4.14.1 PnP project (package.json), an ESM entry (run.mjs) that does await import('dummy-cjs'), and a trivial local CJS dependency (dummy-cjs/index.cjs) whose only relevant line is:

const oldJSHook = require.extensions['.js']  // line that throws on v24.15.0

See the repo's README.md for full context, and output-variant-b-24.14.0.txt / output-variant-b-24.15.0.txt for the side-by-side outputs that establish the regression.

How often does it reproduce? Is there a required condition?

Every time on v24.15.0 when a CJS file is loaded via the ESM loader through a custom resolver/loader that produces a non-file URL (Yarn PnP loads from file://.../cache/....zip/node_modules/...).

What is the expected behavior? Why is that the expected behavior?

require.extensions should be defined on the require function passed into the CJS module wrapper. It is deprecated (DEP0007) but has been part of the public require surface since 0.12. Multiple libraries still in active use (Next.js next-config-ts/require-hook.js, ts-node, esbuild-register, pirates, etc.) read it at CJS module top level.

Confirmed behavior on v24.14.1:

node: v24.14.1
typeof require.extensions: object
.js hook: function

What do you see instead?

On v24.15.0:

[dummy-cjs] node: v24.15.0
[dummy-cjs] typeof require: function
[dummy-cjs] typeof require.extensions: undefined
file:///.../.yarn/berry/cache/dummy-cjs-file-6872e80d06-10.zip/node_modules/dummy-cjs/index.cjs:6
const oldJSHook = require.extensions['.js']
                                    ^

TypeError: Cannot read properties of undefined (reading '.js')
    at Object.<anonymous> (file:///.../.yarn/berry/cache/dummy-cjs-file-6872e80d06-10.zip/node_modules/dummy-cjs/index.cjs:6:37)
    at loadCJSModule (node:internal/modules/esm/translators:185:3)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:231:7)
    at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async node:internal/modules/esm/loader:639:26
    at async file:///.../run.mjs:1:1

Node.js v24.15.0

Note typeof require is function — a require is provided — but require.extensions is undefined on it, which differs from what CJS modules (and the Module.prototype.require used in filesystem-loaded CJS) see.

Additional information

  • Suspected cause: nodejs/node#61769 ("lib: reduce cycles in esm loader and load it in snapshot"), which rewrote the ESM loader initialization and the synthesised require construction for CJS-from-ESM. That PR landed in v24.x for the first time in v24.15.0.
  • Related bug in same subsystem: nodejs/node#62012 — EBADF fstat on zip fds in the same createCJSModuleWrap path. Same Node version, same Yarn PnP setup, different symptom in the same synthetic-CJS-wrap code.
  • Real-world impact: next build on Next.js 15+ fails for all users on Yarn PnP + Node 24.15 because Next's next/dist/build/next-config-ts/require-hook.js reads require.extensions['.js'] at module top level.
  • Not reproducible when the CJS file is loaded from a filesystem path (without a custom PnP-like loader in front), which suggests the bug is specific to how the ESM→CJS wrap constructs require for modules whose source URL is not a plain file:///.../something.cjs.
  • Likely also affects v25.7+ since PR lib: include ESM loader in the built-in snapshot #61769 landed on v25 first.

Workaround

  • Downgrade to Node v24.14.1.
  • Or, for library authors reading require.extensions: use optional chaining (require.extensions?.['.js']) so the call at module load doesn't throw.

Metadata

Metadata

Assignees

No one assigned

    Labels

    duplicateIssues and PRs that are duplicates of other issues or PRs.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions