Skip to content

Fix: Jest Cannot Transform ES Modules — SyntaxError: Cannot use import statement

FixDevs ·

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-jest with @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-fetch v3, chalk v5, nanoid v4, uuid v9) 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. Without ts-jest or Babel’s TypeScript preset, Jest can’t process .ts files.
  • "type": "module" in package.json — this makes all .js files in the project ES modules. Jest’s default runner doesn’t support this without extra configuration.
  • Missing transformIgnorePatterns override — Jest ignores node_modules by default. ESM-only packages in node_modules need 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-env

Create 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-jest is slower (runs TypeScript type checking) but catches type errors in tests. Babel is faster but silently ignores type errors. For CI that already runs tsc --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 only

Alternative: use a CommonJS-compatible alternative:

ESM-only packageCommonJS alternative
node-fetch v3node-fetch v2 or built-in fetch (Node 18+)
nanoid v4nanoid v3 or uuid v8
chalk v5chalk v4
npm install node-fetch@2   # Install the CommonJS version

Fix 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-modules flag
  • Some Jest features (mocking, jest.mock) work differently
  • Performance is generally worse than CJS mode
  • Many examples and guides assume CJS mode

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 all

Migration checklist:

  • Replace jest.fn()vi.fn()
  • Replace jest.mock()vi.mock()
  • Replace jest.spyOn()vi.spyOn()
  • Replace jest.useFakeTimers()vi.useFakeTimers()
  • Update jest.config.jsvitest.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.json

Clear Jest’s cache — after changing transform config, old cached transforms may still run:

jest --clearCache
# Then run tests again
jest

Run Jest with verbose output to see which transform is applied:

jest --verbose --showConfig 2>&1 | grep transform

For related testing issues, see Fix: Vitest Setup Not Working and Fix: Jest Cannot Find Module.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles