Skip to content

Version 22.20.0 tightens Module Loader ? #60290

Description

Version

22.20.0 LTS

Platform

Darwin M-ldkfj2929292 24.6.0 Darwin Kernel Version 24.6.0: Mon Aug 11 21:16:34 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T6020 arm64

Subsystem

No response

What steps will reproduce the bug?

before 22.20.0 upgrade, we had 22.17.1 and our script configuration was working correctly.

The line of code (script block, and the affected line respecr) - with 22.17.1

"test": "cd ../../ && jest --verbose --testPathPatterns=packages/platform-tooling"

moduleNameMapper: { '^uuid$': require.resolve('uuid'), ... },

After upgrading to node 22.20.0 - we are seeing the following error stack when that uuid module is being loaded(uuid package version hasn't changed)

`Error: Jest: Failed to parse the TypeScript config file /Users/dumb-user/mockingbird/jest.config.ts

ReferenceError: require is not defined in ES module scope, you can use import instead

at readConfigFileAndSetRootDir (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:2258:13)

at async readInitialOptions (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:1147:13)

at async readConfig (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:918:7)

at async readConfigs (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:1168:26)

at async runCLI (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/@jest/core/build/index.js:1393:7)

at async Object.run (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-cli/build/index.js:656:9)`

🤔 Why does this happen? And here is my stupid theory (please rebut and let me know the root cause)

  1. Node v22.20.0 tightened its module loader so a require() on a .ts file now throws ERR_REQUIRE_ESM whenever the file is treated as an ES module. That change comes from the work introducing the native TypeScript loader (--experimental-strip-types) described in the v22.6.0+ release notes on nodejs.org; the loader defaults .ts to ESM, so plain require() is rejected (?)

  2. When Jest tries to load jest.config.ts, it calls require(configPath). Since Node 22.20 reports ERR_REQUIRE_ESM, the code in node_modules/jest-config/build/readConfigFileAndSetRootDir.js falls back to await import(configPath). Inside that ESM evaluation require is not defined, so the line ^uuid$': require.resolve('uuid') immediately throws the “ReferenceError: require is not defined in ES module scope” you’re seeing(?)

Hope this makes sense. And thanks in advance for looking into it.

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

Immediately after moving from 22.17.1 to 22.20.0

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

No change in behaviour in any minor/patch version released. Not at least for tightening ESM vs. CJS expectations.

What do you see instead?

As reported above in stack trace.

Additional information

No response

Activity

  1. joyeecheung commented on Oct 17, 2025

    @joyeecheung
    Member

    Can you provide a minimal reproduction? It would be difficult to do anything about it with the information given, as it seems to be specific to the exact graph and the loader customizations involved.

    On a side note this sounds similar to #59963 which was fixed by #60072 but not yet backported to v22. If you can make it go away by testing it in v24.10.0 and above then it's likely it.

  2. added
    loadersIssues and PRs related to ES module loaders.
    strip-typesIssues and PRs related to TypeScript type stripping.
    esmIssues and PRs related to the ECMAScript Modules implementation.
    on Oct 17, 2025
  3. mohammedmanna-skyscanner commented on Oct 17, 2025

    @mohammedmanna-skyscanner
    Author
  4. HayderAlshammari commented on Oct 17, 2025

    @HayderAlshammari

    at readConfigFileAndSetRootDir (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:2258:13)

    at async readInitialOptions (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:1147:13)

    at async readConfig (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:918:7)

    at async readConfigs (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-config/build/index.js:1168:26)

    at async runCLI (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/@jest/core/build/index.js:1393:7)

    at async Object.run (/Users/dumb-user/mockingbird/packages/platform-tooling/node_modules/jest-cli/build/index.js:656:9)`

  5. marco-ippolito commented on Oct 17, 2025

    @marco-ippolito
    Member

    I think the issue is that previously it was using an external TS loader to run typescript (ts-node), while now it's using type stripping and therefore you cannot use require in a ES module.
    If you use Node 22.17.0 (before native ts) it throws this error:

    Error: Jest: Failed to parse the TypeScript config file /Users/marcoippolito/Desktop/jest-bug-reproduction/jest.config.ts
      Error: Jest: 'ts-node' is required for the TypeScript configuration files. Make sure it is installed
    

    You repo does not have ts-node installed.
    And then once you install it, test pass.
    If you have ts-node installed it also works with Node v22.18.0, 22.20.0 and latest 24.
    So the only difference is that Jest: 'ts-node' is required for the TypeScript is no longer emitted if you dont have ts-node installed because it will use the native one (you can test it by adding a random enum in the file and see the error)
    The error is Jest does not check for ts-node being installed correctly and assumes that if you can run a ts file its ts-node.
    So there is nothing much to do on Node.js side

  6. mohammedmanna-skyscanner commented on Oct 17, 2025

    @mohammedmanna-skyscanner
    Author

    I think the issue is that previously it was using an external TS loader to run typescript (ts-node), while now it's using type stripping and therefore you cannot use require in a ES module. If you use Node 22.17.0 (before native ts) it throws this error:

    Error: Jest: Failed to parse the TypeScript config file /Users/marcoippolito/Desktop/jest-bug-reproduction/jest.config.ts
      Error: Jest: 'ts-node' is required for the TypeScript configuration files. Make sure it is installed
    

    You repo does not have ts-node installed. And then once you install it, test pass. If you have ts-node installed it also works with Node v22.18.0, 22.20.0 and latest 24. So the only difference is that Jest: 'ts-node' is required for the TypeScript is no longer emitted if you dont have ts-node installed because it will use the native one (you can test it by adding a random enum in the file and see the error) The error is Jest does not check for ts-node being installed correctly and assumes that if you can run a ts file its ts-node

    I am unsure what you are suggesting, sorry :) .
    Also, the issue was never there for node 22.17.1 for me. As soon as I moved to 22.20.0 - it started after 22.20.0. So does it mean the expectation from that version has changed around require() ?

  7. marco-ippolito commented on Oct 17, 2025

    @marco-ippolito
    Member

    no what changed is that now that Node.js can execute typescript natively, Jest does not correctly detect if ts-node is installed, and uses Node.js native type stripping. It's a Jest bug not a node bug.

  8. mohammedmanna-skyscanner commented on Oct 17, 2025

    @mohammedmanna-skyscanner
    Author

    no what changed is that now that Node.js can execute typescript natively, Jest does not correctly detect if ts-node is installed, and uses Node.js native type stripping. It's a Jest bug not a node bug.

    ☝️ Got it - makes sense. So would this bug be more appropriate to raise for the Jest package.

  9. marco-ippolito commented on Oct 17, 2025

    @marco-ippolito
    Member

    no what changed is that now that Node.js can execute typescript natively, Jest does not correctly detect if ts-node is installed, and uses Node.js native type stripping. It's a Jest bug not a node bug.

    ☝️ Got it - makes sense. So would this bug be more appropriate to raise for the Jest package.

    However if you install ts-node (npm i ts-node) in your repo, the problem goes away

  10. mohammedmanna-skyscanner commented on Oct 17, 2025

    @mohammedmanna-skyscanner
    Author

    However if you install ts-node (npm i ts-node) in your repo, the problem goes away

    Ok - but this is where my confusion is, or rather, I wanted to make sure I understood the root cause.

    1. Before 22.20.0 upgrade, everything was working fine for me. and to be clear, the quickest reproducible example is a package within my project. The top-level package.json has ts-node already.
    top-level
       - package.json.  // this has ts-node
       - packages/platform-tools
         - package.json. // this didn't and it didn't matter
     
    
    1. After 22.20.0 upgrade, it stopped working. So, if I have to now also install ts-node in my monorepo package, that's a change. And I am wondering where this bug is e.g. Jest or Node.
    top-level
       - package.json.  // this has ts-node
       - packages/platform-tools
         - package.json. // You are proposing to install here now too...(which is what I don't get why)
    

    Because installing ts-node in nested package (along with top-level package.json) - is not a solution if it worked before 22.20.0 - unless this is what's warranted by 22.20.0.

    May be The issue is that Jest 30+ treats .ts config files differently in certain contexts (like when it's run from workspaces), and require.resolve() isn't available in ES module scope. But even then, why would it start complaining after a minor node version upgrade.

  11. mohammedmanna-skyscanner commented on Oct 17, 2025

    @mohammedmanna-skyscanner
    Author

    OK - based on the conversation and some further research... I managed to recover the build. Below is the analysis (some common parts from previous comments)

    Root Cause
    Node.js 22.18.0 enabled TypeScript type stripping by default. This feature made Node.js treat .ts files especially, running them through a TypeScript parser/transformer even when loaded by tools like Jest.

    When jest.config.ts is loaded:

    Before Node 22.18.0: The config was treated as a regular module, and require.resolve() worked fine
    After Node 22.18.0: Node's built-in TypeScript support intercepts the loading, treating it as an ES module with type stripping, which breaks require.resolve() calls

    The issue is NOT:

    ❌ Jest version mismatch
    ❌ Node.js version in general (22.17.1 vs 22.20.0)
    ❌ Missing ts-node or other tooling

    The issue IS:

    ✅ Node 22.18.0's new default TypeScript type stripping feature interfering with Jest's config loading>

    AND then....

    The require.resolve('uuid') was a workaround NO LONGER NECESSARY 🤦‍♀️

    Because....

    The issue was fixed in Jest 28+: According to the GitHub issue, Jest 28 added proper support for the uuid package's exports field.

    GitHub Issue: [https://github.com/uuidjs/uuid/issues/451]

    SimenB's comment: [https://github.com/uuidjs/uuid/issues/451#issuecomment-1112030160]

    I am using Jest 29.7.0: And that workaround interfered with Node 22.18+ native type-stripping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    esmIssues and PRs related to the ECMAScript Modules implementation.loadersIssues and PRs related to ES module loaders.strip-typesIssues and PRs related to TypeScript type stripping.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions