Repository navigation
Meta: Native support for ES Modules #9430
Description
EDIT: quick guide for getting started: https://jestjs.io/docs/ecmascript-modules
ESM support will be unflagged in a future release of Node 12 (maybe not before April nodejs/node#29866 (comment)) and it is already unflagged in Node 13.2, so I think it's time to evaluate how we can add native support in Jest. I'll try to list which features Jest currently provides that are impacted by ESM support, and how we can solve/investigate them.
There is issue #4842, but I think that's more of a discussion issue, while this issue will be geared towards actually implementing support and more suitable to track for those who just want to get the current implementation status. Any comments added to this issue not related to how we can implement support for the below enumerated features will be marked as spam - please direct any workarounds/discussions to separate issues. Also feel free to tell us if anything related to ESM features is missing from the list!
Please note that Jest will use the vm API (https://nodejs.org/api/vm.html) and as of writing (node v13.6 v16.10) the ESM parts of this API is still flagged (--experimental-vm-modules). So saying ESM is unflagged is a bit of a misnomer at the moment. But I think we should start experimenting and potentially provide feedback to the Modules WG.
EDIT: Tracking issue for stabilization in Node: nodejs/node#37648
Lastly, I'm writing this issue mostly for people who will implement support, so it'll be somewhat low-level and specific to how Jest works. For people who just want to know whether support has landed or not, I recommend using GH's wonderful "custom notification" and only subscribe to notifications on closing/reopening.
- Running the module in the correct context
We achieve sandboxes by running a script within a given vm.Context (either provided by JSDOM or node core APIs). We need to do the same for ESM, but we'll need access to the context during construction of the module, not just when executing the module. I've opened up #9428 which adds the necessary APIs to JestEnvironment.
- Globals
expect, test, beforeEach etc will still be added as globals, nothing should change here. jasmine global will also still be here.
-
jest"global" property
This is not really a global - it's injected into the module scope. Since the module scope is gone in ESM, we need to move it somewhere. Adding it to import.meta seems natural - there's an option called initializeImportMeta which we can use.
EDIT: Solution here is to fetch it via import {jest} from '@jest/globals'. We might still add it via import.meta in the future, but this should be enough for now.
-
jest.(do|un)mock
Since ESM has different "stages" when evaluating a module, jest.mock will not work for static imports. It can work for dynamic imports though, so I think we just have to be clear in the docs about what it supports and what it doesn't.
jest.mock calls are hoisted, but that doesn't help in ESM. We might consider transforming import 'thing' to import('thing') which should allow hoisting to work, but then it's async. Using top-level await is probably a necessity for such an approach. I also think it's invasive enough to warrant a separate option. Something to discuss - we don't need to support everything jest.mock can for for an initial release.
PR: #10976
-
jest.requireActual
Not sure if how it should behave in ESM. Should we provide a jest.importActual and let requireActual evaluate in CJS always?
-
import.meta
Node has url as its only property (for now, at least). We need to make sure it's populated in Jest as well. We provide identifier instead of filename when constructing the module so I don't think it'll happen automatically, but url is essentially filename passed though pathToFileURL.
There's also an open PR for import.meta.resolve: nodejs/node#31032
-
import thing from 'thing'
This should actually be fairly straightforward, we just need to implement a linker where we can also transform the source before returning it, meaning we don't need the loader API (which doesn't exist yet). This allows us to return mocks as well (albeit they'll have to come from a __mocks__ directory).
-
import('thing')
Essentially the same as above, but passed as importModuleDynamically when constructing the module. Will also support jest.mock, jest.resetModules etc more cleanly, so likely to be used quite a bit.
This can also be done for vm.Script via the same option.
- Handling errors during evaluation
Right now it's a runtime error (e.g. module not found), but that's not necessarily true with ESM. Does it matter for us? We should verify errors still look nice.
-
module.createRequire
We need to deal with this for people wanting to use CJS from ESM. I've opened up #9426 to track this separately as implementing it is not really related to ESM support.
EDIT: Implemented in #9469
-
module.syncBuiltinESMExports
https://nodejs.org/api/modules.html#modules_module_syncbuiltinesmexports. Do we care about it, or is just making it a no-op enough? Not sure what the use case in Jest would be. Messing with the builtins is already breaking the sandbox and I don't think this should matter.
EDIT: #9469 made this into a no-op. I think that's fine?
- Detect if a file is supposed to be ESM or CJS mode
Inspecting type field in a module's package.json seems reasonable: https://nodejs.org/api/esm.html#esm_enabling. Should we also have our own config flag? Also needs to respect file endings.
-
moduleNameMapper
Not sure if this impacts anything. I think not since we'll be linking the modules together ourselves. Needs investigation, though.
EDIT: This is all resolution logic, which we control. So no changes here.
-
jest.config.mjs
Through #9291 we support jest.config.cjs - do we need to do anything special for .mjs? Probably use import('path/to/configFile.mjs') which means it'll have to be async. Is this an issue? Might be worth making config resolution async in Jest 25 so it's not a blocker for incremental support of ESM in Jest 25.
EDIT: #9431
- Package Exports
Node supports package exports, which sorta maps to Jest's moduleNameMapper, but also provides encapsulation features. Hopefully resolve will implement this, but if they do not we'll need to do something. Might be enough to use the pathFilter option? Unsure.
EDIT: #9771
- JSON/WASM module
https://nodejs.org/api/esm.html#esm_experimental_json_modules. Do we need to care? Probably, especially for json. It's trivial for us to support import thing from './package.json' since we control the linking phase, but we probably shouldn't do it by default as it'll differ from default node. Should we force people to define a transform for it?
WASM: #13505
- Code coverage
Does it matter? I don't think it's affected as we can still transform the source with babel (maybe it'll be confused by import statements, probably not) and V8 coverage definitely shouldn't care. We should verify though.
- Async code resolution
This is absolutely no blocker as sync resolution will work just fine. But we can use async resolution now, which is great. I wonder if we should look into just using the resolve module off of npm again, as it already supports async. See #9505.
- Async code transformation
Similar to above, not blocking, but would be nice to support it. Might make @jest/transformer more usable in other environments as well. See #9504.
- Bad performance when accessing globals
Due to #5163 we have the extraGlobals option as a workaround - that workaround is no longer viable in ESM. I've opened up and issue with node here: nodejs/node#31658
- Import assertions
Activity
SimenB commentedon Apr 16, 2020
I've landed very basic support with #9772. I've only tested the simplest cases, and there are many known limitations (most notably no
jestobject support and broken semantics when mixing CJS and ESM), but at least it's something. It'll go out in the next release of Jest (hopefully soon, only blocked by #9806)SimenB commentedon Apr 19, 2020
25.4.0 has been released with the first pieces of support. In addition to #9772 mentioned above, I've also included #9842. In theory mixing CJS and ESM should work correctly now (🤞).
The one main missing feature is supporting the
jestobject. I haven't decided if we should stick it toimport.metaor require people to import it throughimport {jest} from '@jest/globals'. Feedback appreciated!I haven't written docs for this yet, but to activate it you need to do 3 things
importstatements (settransform: {}in config or otherwise ensurebabeldoesn't transform the file to CJS, such as avoiding themodulesoption to preset-env)node@^12.16.0 || >=13.2.0with--experimental-vm-modulesflagjest-environment-nodeorjest-environment-jsdom-sixteenPlease try it out and provide feedback! If reporting bugs, it'd be wonderful if you can also include how running the same code (minus any test specific code) runs in Node. I've read https://nodejs.org/api/esm.html a lot over the last few weeks, but I've probably missed something.
just-boris commentedon Apr 19, 2020
For the typescript use-case it is better to have an explicit import.
SimenB commentedon Apr 19, 2020
Yup, I've added (and the temporarily reverted) a
@jest/globalspackage that supports this, so that will be available regardless. I'm wondering if it makes sense to also expose it onimport.meta. Currently leaning towards not doing so, mainly since it's easier to add than remove later (and I'm personally no fan of globals)IlCallo commentedon Apr 20, 2020
+1 for the explicit import, it's a bit more verbose but simpler to understand
zandaqo commentedon Apr 20, 2020
SimenB commentedon Apr 20, 2020
zandaqo commentedon Apr 20, 2020
ReferenceError: jest is not definedI'm guessing this is due to the missing@jest/globalsSimenB commentedon Apr 20, 2020
Yes, as mentioned this will only work if you don't use the
jestobject.Mocks are also probably broken, haven't tested them 😃
SimenB commentedon Apr 20, 2020
SimenB commentedon Apr 20, 2020
aldeed commentedon Apr 20, 2020
361 remaining items
chore(apps/app): Setup SWC tests compilation
chore(apps/app): Setup SWC tests compilation
test: Migrate all packages from `jest` to `vitest` (#13)
ahnpnl commentedon Aug 12, 2024
Hi, I have a question: Is it possible to let Jest run in ESM mode when detecting
type: moduleinpackage.jsonwithout the need of providing Node ESM flag?Reference https://nodejs.org/docs/latest-v16.x/api/esm.html#enabling
koshic commentedon Aug 12, 2024
No, 'node esm flag' required to experimental (still...) modules VM API. Problem is not in detection, problem is how to execute module in VM context.
Add --experimental-vm-modules flag required by Jest to support ES Module
SkReD commentedon Aug 11, 2025
What is the status of support
requireActualandcreateMockFromModulefor esm? Or what is the correct way to create manual mock of esm module? For example in such casefix(tests): skip ES module dynamic import tests in Jest
fix(tests): skip ES module dynamic import tests in Jest (#54)
relu91 commentedon Mar 31, 2026
Hey everyone, it seems that node now supports
requirefor ESM even from.jsfiles. I noticed that this use case is not listed in @SimenB comment. Currently, jest simply refuses to require the module, suggesting usingimport. However, this creates a disalignment between the code I can deploy (will use require) and the code I test (I need to dynamically import). Is there any roadmap or workaround (aside to "babelize" the module)?joshkel commentedon Mar 31, 2026
@relu91 It's been requested, with some possible workarounds, at #15275, but there's no concrete roadmap.
SimenB commentedon May 7, 2026
Hello folks! Big release today for native ESM support.
https://github.com/jestjs/jest/releases/tag/v30.4.0
The entire implementation of native ESM in Jest has been rebuilt from the ground up.
It has support for
require(esm)and many correctness fixes in the "regular" ESM (as long as you are on Node 24.9+).A new design is being discussed in Node upstream at nodejs/node#62720 which is the path for stabilization 🙂 So things are happening! 🎉
Pomax commentedon May 9, 2026
Is the idea to eventually fold this into
node --test, given Node v22 added native testing with full ESM support? Or will this stay its own separate test runner?joshkel commentedon May 9, 2026
@Pomax I expect it to remain its own separate test runner. Jest still has quite a bit of functionality beyond Node's native test suite: a richer set of assertions, more snapshot functionality, more flexible configuration, the option to transform inputs via Babel or similar, etc.
Particularly relevant here (and the reason why native support for ES modules in Jest is more complex): Node.js's own test suite has to run with either no test isolation (one test can change globals in a way that's visible to other tests) or by isolating each test file in its own Node.js process (which is a lot heavier).
Jest instead uses Node.js's vm API to fully isolate tests within a single process. It also has support for using vm to set up test environments: for example, jest-environment-jsdom can hide Node.js globals and use jsdom to create a browser-compatible environment, to test browser-side JS. It's my understanding that the complexities of working with vm are a major reason why full ESM support for Jest has taken so long; that's what makes the work at nodejs/node#62720 so exciting.
SimenB commentedon Aug 28, 2026
Many gaps closed in today's release: https://github.com/jestjs/jest/releases/tag/v30.5.0
Still missing a good story for mocking - might revisit that soon. But feature parity with Node is now much better 👍