jest: [Bug]: Unable to use ESM-only modules with TypeScript and babel-jest

Version

29.4.2

Steps to reproduce

Run yarn install and yarn test with the following package setup.

Note that react-dnd-html5-backend is used as an example of an ESM-only package.

package.json:

{
  "name": "jest-test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "type": "module",
  "scripts": {
    "test": "NODE_OPTIONS=--experimental-vm-modules jest"
  },
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-typescript": "^7.18.6",
    "babel-jest": "^29.4.2",
    "jest": "^29.4.2",
    "react-dnd-html5-backend": "^16.0.1",
    "uuid": "^9.0.0"
  }
}

babel.config.json:

{
  "presets": [
    ["@babel/preset-env", { "targets": { "node": "current" }, "modules": false }],
    "@babel/preset-typescript"
  ]
}

index.test.ts:

import {HTML5Backend} from 'react-dnd-html5-backend'

describe("example", ()=> {
    it("works", ()=> {
        console.log('backend', HTML5Backend)
        expect.assertions(0);
    })
})

Expected behavior

yarn test should work.

Actual behavior

WITHOUT "modules": false in @babel/preset-env options, an error occurs because react-dnd-html5-backend is an ESM-only package:

$ yarn test                
yarn run v1.22.15
$ NODE_OPTIONS=--experimental-vm-modules jest
 FAIL  ./index.test.ts
  ● Test suite failed to run

    Must use import to load ES Module: /Users/jacob/Desktop/jest-test/node_modules/react-dnd-html5-backend/dist/index.js

    > 1 | import {HTML5Backend} from 'react-dnd-html5-backend'
        | ^
      2 |
      3 | describe("example", ()=> {
      4 |     it("works", ()=> {

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:817:21)
      at Object.<anonymous> (index.test.ts:1:1)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.294 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

WITH "modules": false in @babel/preset-env options (to leave the ES module syntax alone, because we are using --experimental-vm-modules), an error also occurs:

$ yarn test                                                                             [1]
yarn run v1.22.15
$ NODE_OPTIONS=--experimental-vm-modules jest
 FAIL  ./index.test.ts
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the docs:
    https://jestjs.io/docs/configuration
    For information about custom transformations, see:
    https://jestjs.io/docs/code-transformation

    Details:

    /Users/jacob/Desktop/jest-test/index.test.ts:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { HTML5Backend } from 'react-dnd-html5-backend';
                                                                                      ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1449:14)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.289 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Additional context

No response

Environment

  System:
    OS: macOS 13.2
    CPU: (10) arm64 Apple M1 Pro
  Binaries:
    Node: 16.15.1 - ~/.nvm/versions/node/v16.15.1/bin/node
    Yarn: 1.22.15 - ~/.nvm/versions/node/v16.15.1/bin/yarn
    npm: 8.11.0 - ~/.nvm/versions/node/v16.15.1/bin/npm
  npmPackages:
    jest: ^29.4.2 => 29.4.2

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 13
  • Comments: 33 (5 by maintainers)

Commits related to this issue

Most upvoted comments

I’ve had similar problem and looked into it, and there are multiple points that can cause similar error message. It seems like the solution is different by whether if you use --experimental-vm-modules. (https://jestjs.io/docs/ecmascript-modules)


If you use ‘–experimental-vm-modules’, it seems like Jest needs to decide each file’s module type (between ESM modules and CJS, just like Node.js does). For example, if Jest misdetects the module type, you’ll encounter “Must use import to load ES Module”, or “Cannot use import statement outside a module” error.

Furthermore, it seems like Jest sends the current module type to Babel, and Babel transpiles the code accordingly to fit the module type. For example, if Jest detects that the file is CJS, then Babel will use module.exports. But if it’s detected as ESM, Babel will keep import and export. For this reason, you don’t need to put modules: false in .babelrc or babel.config.js if Jest is configured correctly.

Therefore, It’d be crucial to make sure if the module type is detected correctly.

  • You need to make sure the project itself is declared as "type": "module" in package.json, as Node.js and Jest discerns the file type using it. Node.js Module System
  • If you use TypeScript and experimental ESM support, you need to make sure that Jest treats .ts, .tsx file as ESM module. This can be achieved by setting extensionsToTreatAsEsm: ['.ts', '.tsx'] in jest.config.js. #
  • In babel.config.js, make sure that there is no "modules" setting in "@babel/preset-env". It should still work if it’s set to false, but you won’t really need it. #
  • Of course, you need to make sure ‘–experimental-vm-modules’ flag is passed to Jest as well.

So, @jtbandes, can you check that extensionsToTreatAsEsm fixes your problem? @hubertlepicki, can you make sure that "type": "module" is set in your package.json?


If you don’t use ‘–experimental-vm-modules’, you have to make sure that babel can transpile all the files, including files in node_modules into CommonJS format. There are some points to check as well:

  • You need to use babel.config.js (or babel.config.json), NOT .babelrc! Since Jest expects that everything is CommonJS, you need to transpile pure ESM packages as well.
  • After doing this, you need to make sure that Jest transpiles all the files inside node_modules as well. This can be achieved by setting transformIgnorePatterns: [] inside jest.config.js. #
  • In babel.config.js, make sure that there is no "modules" setting in "@babel/preset-env". #

If you use .babelrc, Babel simply ignores the .babelrc file when it’s inside node_modules directory. It’s because that Babel simply stops seeking the .babelrc once it finds package.json. In other words, .babelrc will keep import and export, which is unusable in Jest. Babel Config Files

Hope I covered everything!

Sounds like this is still an issue.

Still happening with 29.4.2.

having the same problem, can’t seem to use the ESM module support. I get the same error whether or not I run using node --experimental-vm-modules node_modules/jest/bin/jest.js or just yarn jest

@lazarljubenovic Thanks for reproduction. In this case ts-jest is misconfigured. See ESM Support page in their docs. There is the useESM option and three ESM presets. All works if I use configuration from the page (did try the presets, not sure what are the differences between them).

Also it is enough adding extensionsToTreatAsEsm: ['.ts'], removing transform from Jest config. And adding configuration for Babel (follow Getting Started guide to install required Babel dependencies):

// babel.config.cjs

module.exports = {
  presets: [
    ["@babel/preset-env", { targets: { node: "current" }, modules: true }],
    "@babel/preset-typescript",
  ],
};

All works as documented, just read the docs (;

I have my package set to "type": "module" and everything but jest works, including webpack. For me removing "modules": false from my babel config is not an option because it would essentially remove all the benefits of using using ES modules.

However, because jest is using VM and ES modules are experimental with that feature, and jest has some outstanding issues to resolve (such as jest.requireActual -> jest.importActual), it’s OK with me just to disable modules just for jest and keep it enabled for everything else.

This is how you do it. Use this transform:

import babelConfig from "./babel.config.json" assert {
  type: "json",
}
// OR:
// import babelConfig from "./babel.config.js"

{
	// ...
	transform: {
		"\\.[jt]sx?$": [
			"babel-jest",
			{
				presets: babelConfig.presets.map(preset => {
					if (Array.isArray(preset) && preset[0] === "@babel/preset-env") {
						return [
							"@babel/preset-env",
							{
								...preset[1],
								modules: "commonjs",
							}
						]
					}
					return preset
				})
			}
		],
	},
}

This will pull your existing babel config and override "modules". Once jest resolves the issue you should be able to remove that from your jest config, and update mocks that likely can’t use jest.requireActual anymore (as they probably will need to make an async jest.importActual).

This is happening for me as well. The stack is lacking TypeScript, but we have React, Babel and Jest.

When installing an ESM-only package to my project, like p-throttle version 5.0.0, and then runnig the tests with enabled experimental ESM modules support I get the following:

 FAIL  src/js/foo/bar.test.js
  ● Test suite failed to run

    Must use import to load ES Module: /home/hubert/[redacted]/node_modules/p-throttle/index.js

    > 20 | import pThrottle from "p-throttle";
         | ^
      21 |
      22 | const throttle = pThrottle({
      23 |   limit: 4, // Max. concurrent Requests

      at Runtime.requireModule (node_modules/jest-runtime/build/index.js:841:21)
      at Object.<anonymous> (src/js/graphql/client.js:20:1)
      at Object.<anonymous> (src/js/foo/modal.js:13:1)
      at Object.<anonymous> (src/js/foo/bar.test.js:1:1)

I believe this is the same problem @jtbandes is reporting.

I don’t quite understand the whole babel/jest build process, but it’s probably worth noting that the package builds and runs fine with ESM only package, the problem is only present when running tests.

I tried various combinations of settings mentioned here but nothing seems to have an effect. For example:

diff --git a/.babelrc b/.babelrc
index c9909be..a134f6a 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,8 +1,8 @@
 {
     "presets": [
-        "@babel/preset-env", "@babel/preset-react"
+      ["@babel/preset-env", {"modules": false}], "@babel/preset-react"
     ],
     "plugins": [
-      ["@babel/plugin-transform-runtime"], ["graphql-tag"]
+      ["@babel/plugin-transform-modules-commonjs"], ["@babel/plugin-transform-runtime"], ["graphql-tag"]
     ]
 }
diff --git a/jest.config.js b/jest.config.js
index 0557dcb..7cc54bb 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -144,7 +144,7 @@ module.exports = {
+  transformIgnorePatterns: [],

@SimenB This is still an issue. The problem is related to importing dependencies that are now ESM only and how jest handles those dependencies out of the box. It is solvable by telling babel-jest to transpile those dependencies (as shown above).

This is also somewhat related to https://github.com/jestjs/jest/issues/9430 which is still being worked on. Meaning one way to resolve this is by making your own package/app an ESM module. Depending on how you use jest, you may not be able to do this until that issue is solved. For my case I need importActual for my test setup to replace requireActual.

If you don’t use ‘–experimental-vm-modules’, you have to make sure that babel can transpile all the files, including files in node_modules into CommonJS format. There are some points to check as well:

You need to use babel.config.js (or babel.config.json), NOT .babelrc! Since Jest expects that everything is CommonJS, you need to transpile pure ESM packages as well. After doing this, you need to make sure that Jest transpiles all the files inside node_modules as well. This can be achieved by setting transformIgnorePatterns: [] inside jest.config.js. # In babel.config.js, make sure that there is no “modules” setting in “@babel/preset-env”. # If you use .babelrc, Babel simply ignores the .babelrc file when it’s inside node_modules directory. It’s because that Babel simply stops seeking the .babelrc once it finds package.json. In other words, .babelrc will keep import and export, which is unusable in Jest. Babel Config Files

@yoo2001818 I followed these directions and got everything working perfectly. Thank you!

The reproduction you provided is very good example how to get the problem solved. Thanks for that once again!

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.