Skip to content

ts-esm

ts-esm runs TypeScript tests as native ES modules in the node environment, transformed by @swc/jest.

jest.config.mjs
export default {
preset: '@repobuddy/jest/presets/ts-esm',
}

Run Jest with ESM support:

Terminal window
NODE_OPTIONS=--experimental-vm-modules jest

None. A preset takes no options. Override its keys in your config (see customize a preset).

Printed from the built preset with Node.js 26, in a directory that has a src folder:

{
extensionsToTreatAsEsm: ['.ts', '.mts', '.tsx'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.(js|jsx|cjs|mjs|ts|tsx|cts|mts)$': '@swc/jest',
},
collectCoverageFrom: [
'<rootDir>/src/**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}',
'!<rootDir>/src/**/*.stories.*',
],
roots: ['<rootDir>/src'],
coveragePathIgnorePatterns: [
'\\.(spec|test|unit|accept|integrate|learning|system|perf|stress|load)(\\..*)?\\.(js|jsx|cjs|mjs|ts|tsx|cts|mts)$',
],
testEnvironment: 'node',
testRegex: [
'\\.(spec|test|unit|accept|integrate|learning|system|perf|stress)(\\.node)?\\.(js|jsx|cjs|mjs|ts|tsx|cts|mts)$',
'\\.(spec|test|unit|accept|integrate|learning|system|perf|stress)\\.node14\\.(js|jsx|cjs|mjs|ts|tsx|cts|mts)$',
// one entry per Node.js major, from 15 up to the running major (26 here)
'\\.(spec|test|unit|accept|integrate|learning|system|perf|stress)\\.node26\\.(js|jsx|cjs|mjs|ts|tsx|cts|mts)$',
],
}

It is built from configs.tsEsm, configs.configSource(), and configs.node.

extract.extractPackages() on the preset returns:

['@swc/jest', 'jest-environment-node']

Install @swc/jest and its peer @swc/core. jest-environment-node is a dependency of Jest.

  • The moduleNameMapper entry maps a relative import ending in .js to the same path without the extension. TypeScript source written as import { foo } from './foo.js' resolves to ./foo.ts.
  • .ts, .mts, and .tsx files load as ES modules. .cts files are transformed but load as CommonJS.
  • transform covers JavaScript files too, but Jest’s default transformIgnorePatterns still skips node_modules. Dependencies load as they are published.
  • @swc/jest gets no options from the preset.
  • Without --experimental-vm-modules, ESM test files fail with SyntaxError: Cannot use import statement outside a module.
  • Test file names: see test file names.

@repobuddy/jest/presets/ts-esm-watch is ts-esm plus the watch plugins of the watch preset. It also needs jest-watch-suspend, jest-watch-toggle-config, and jest-watch-typeahead.

The two-chalk fixture in this repository uses the preset as is:

jest.config.mjs
export default {
preset: '@repobuddy/jest/presets/ts-esm',
}

Add your own mapper. Jest merges it with the preset’s .js mapper:

jest.config.mjs
export default {
preset: '@repobuddy/jest/presets/ts-esm-watch',
moduleNameMapper: {
'^#utils$': '<rootDir>/src/utils.ts',
},
}