Fix: Jest Cannot Transform ES Modules — SyntaxError: Cannot use import statement
Quick Answer
How to fix Jest failing with 'Cannot use import statement outside a module' — configuring Babel transforms, using experimental VM modules, migrating to Vitest, and handling ESM-only packages.
The Error
Jest fails when running tests that use ES module syntax:
SyntaxError: Cannot use import statement outside a module
> 1 | import { render } from '@testing-library/react';
| ^
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:...)Or when importing a package that ships as ESM:
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.
/node_modules/your-package/index.js:1
export default function ...
^^^^^^
SyntaxError: Unexpected token 'export'Or with TypeScript:
● Test suite failed to run
SyntaxError: /app/src/__tests__/utils.test.ts: Missing semicolon. (1:7)
> 1 | import { parseDate } from '../utils';
| ^Why This Happens
Jest runs in Node.js, and by default uses CommonJS (require/module.exports). When your code or a dependency uses ES module syntax (import/export), Jest can’t parse it without transformation:
- No Babel transform configured — Jest needs
babel-jestwith@babel/preset-env(or@babel/preset-typescript) to transform modern JS/TS to CommonJS before running tests. - ESM-only npm package — some packages (e.g.,
node-fetchv3,chalkv5,nanoidv4,uuidv9) ship only as ES modules. Jest’s default CommonJS runner can’t import them without special handling. - TypeScript without
ts-jest— TypeScript files need transformation. Withoutts-jestor Babel’s TypeScript preset, Jest can’t process.tsfiles. "type": "module"inpackage.json— this makes all.jsfiles in the project ES modules. Jest’s default runner doesn’t support this without extra configuration.- Missing
transformIgnorePatternsoverride — Jest ignoresnode_modulesby default. ESM-only packages innode_modulesneed to be transformed but aren’t.
Fix 1: Add Babel Transform (Most Common Fix)
For JavaScript projects, configure Jest with Babel to transform ESM to CommonJS:
Install dependencies:
npm install -D babel-jest @babel/core @babel/preset-envCreate babel.config.js (not .babelrc — Jest requires a root-level Babel config):
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: { node: 'current' }, // Transform for the current Node.js version
}],
],
};For React + JSX:
npm install -D @babel/preset-react// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
['@babel/preset-react', { runtime: 'automatic' }],
],
};Jest configuration:
// jest.config.js
module.exports = {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
};babel-jest is installed automatically with Jest — you don’t need to install it separately.
Fix 2: Configure TypeScript with ts-jest or Babel
Option A: ts-jest (recommended for TypeScript projects — preserves type checking):
npm install -D ts-jest @types/jest// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node', // or 'jsdom' for browser-like tests
};// tsconfig.json — ensure CommonJS output for Jest
{
"compilerOptions": {
"module": "CommonJS", // Jest needs CommonJS
"target": "ES2020"
}
}Option B: Babel with TypeScript preset (faster, no type checking during tests):
npm install -D @babel/preset-typescript// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};// jest.config.js
module.exports = {
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
};ts-jest vs Babel:
ts-jestis slower (runs TypeScript type checking) but catches type errors in tests. Babel is faster but silently ignores type errors. For CI that already runstsc --noEmit, Babel is usually fine.
Fix 3: Fix ESM-Only Packages in node_modules
Jest ignores node_modules by default (transformIgnorePatterns). When an ESM-only package is in node_modules, Jest can’t process it:
// jest.config.js
module.exports = {
// Override transformIgnorePatterns to transform specific ESM packages
transformIgnorePatterns: [
// Transform everything EXCEPT true CJS packages
'node_modules/(?!(node-fetch|nanoid|uuid|chalk|your-esm-package)/)',
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// List ESM-only packages here (pipe-separated)
],
};Finding which packages are ESM-only:
# Check if a package uses "exports" with ESM
cat node_modules/node-fetch/package.json | grep -A 5 '"exports"'
# If you see "import" in exports, it's ESM
# Or check for "type": "module"
cat node_modules/nanoid/package.json | grep '"type"'
# "type": "module" → ESM onlyAlternative: use a CommonJS-compatible alternative:
| ESM-only package | CommonJS alternative |
|---|---|
node-fetch v3 | node-fetch v2 or built-in fetch (Node 18+) |
nanoid v4 | nanoid v3 or uuid v8 |
chalk v5 | chalk v4 |
npm install node-fetch@2 # Install the CommonJS versionFix 4: Use Jest with Native ESM Support (Experimental)
For projects that are fully ESM ("type": "module" in package.json), use Jest’s experimental ESM support:
// package.json
{
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest"
}
}// jest.config.js (must also be ESM — use .js with type:module or rename to jest.config.mjs)
export default {
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1', // Strip .js extension for imports
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};Caveats of experimental ESM mode:
- Requires
--experimental-vm-modulesflag - Some Jest features (mocking,
jest.mock) work differently - Performance is generally worse than CJS mode
- Many examples and guides assume CJS mode
Fix 5: Migrate to Vitest (Recommended for Vite Projects)
If you’re using Vite (or any bundler with native ESM support), migrating to Vitest eliminates all Jest ESM configuration pain. Vitest uses Vite’s transform pipeline and supports ESM natively:
npm install -D vitest @vitest/ui// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom', // For browser-like tests
globals: true, // No need to import describe/it/expect
setupFiles: './src/test/setup.ts',
},
});Vitest is API-compatible with Jest — most Jest tests work without changes:
// Jest test
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from '@jest/globals';
// Vitest test — same code, works out of the box
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
// Or with globals: true — no imports needed at allMigration checklist:
- Replace
jest.fn()→vi.fn() - Replace
jest.mock()→vi.mock() - Replace
jest.spyOn()→vi.spyOn() - Replace
jest.useFakeTimers()→vi.useFakeTimers() - Update
jest.config.js→vitest.config.ts
Fix 6: Fix moduleNameMapper for ESM Imports
TypeScript projects often use path aliases or .js extensions in imports (required by the ESM spec but weird for TypeScript). Map these to actual files:
// TypeScript source with .js extension (ESM requirement)
import { formatDate } from './utils.js';
// But utils.js doesn't exist — utils.ts does// jest.config.js — map .js imports to actual .ts files
module.exports = {
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1', // Remove .js extension
'^@/(.*)$': '<rootDir>/src/$1', // Path alias @/ → src/
'^~/(.*)$': '<rootDir>/src/$1', // Path alias ~/ → src/
},
};For CSS/image imports in component tests:
module.exports = {
moduleNameMapper: {
'\\.(css|less|scss|sass)$': '<rootDir>/__mocks__/styleMock.js',
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/__mocks__/fileMock.js',
},
};// __mocks__/styleMock.js
module.exports = {};
// __mocks__/fileMock.js
module.exports = 'test-file-stub';Still Not Working?
Check package.json for "type": "module":
// If this exists and you haven't configured experimental ESM:
{ "type": "module" }Remove it or set it to "commonjs" if you don’t need full ESM mode. Jest’s CommonJS mode is more stable and widely supported.
Verify Babel config is at the project root. Jest requires babel.config.js (not .babelrc) for transformation of files outside the test directory:
ls babel.config.*
# Must be at the same level as package.jsonClear Jest’s cache — after changing transform config, old cached transforms may still run:
jest --clearCache
# Then run tests again
jestRun Jest with verbose output to see which transform is applied:
jest --verbose --showConfig 2>&1 | grep transformFor related testing issues, see Fix: Vitest Setup Not Working and Fix: Jest Cannot Find Module.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied
How to fix Jest setup file issues — setupFilesAfterFramework vs setupFiles, global mocks not applying, @testing-library/jest-dom matchers, module mocking in setup, and TypeScript setup files.
Fix: Jest Async Test Timeout — Exceeded 5000ms or Test Never Resolves
How to fix Jest async test timeouts — missing await, unresolved Promises, done callback misuse, global timeout configuration, fake timers, and async setup/teardown issues.
Fix: Jest Coverage Not Collected — Files Missing from Coverage Report
How to fix Jest coverage not collecting all files — collectCoverageFrom config, coverage thresholds, Istanbul ignore comments, ts-jest setup, and Babel transform issues.
Fix: Jest Fake Timers Not Working — setTimeout and setInterval Not Advancing
How to fix Jest fake timers not working — useFakeTimers setup, runAllTimers vs advanceTimersByTime, async timers, React testing with act(), and common timer test mistakes.