Jest + ts-jest 处理node_modules中ESM模块及实验模式报错排查
Jest + TypeScript + ESM 配置问题排查与解决
项目目录结构
project |-- package.json |-- tsconfig.json |-- jest.config.cjs |-- src | |-- index.ts | `-- foo.ts `-- test |-- index.test.ts `-- helpers.ts
相关配置文件内容
package.json
[...] "type": "module", "scripts": { "test": "jest", "testExp": "NODE_OPTIONS=--experimental-vm-modules jest" } [...] "devDependencies": { "@types/jest": "^29.4.0", "jest": "^28.1.3", "ts-jest": "^28.0.7", "typescript": "^4.3.0" }, "dependencies": { "image-type": "^5.2.0" }
tsconfig.json
{ "compilerOptions": { "target": "ESNext", "moduleResolution": "NodeNext", "module": "ESNext", "esModuleInterop": true, "strict": true, "rootDir": "./src", "baseUrl": "./src", "outDir": "./dist", "skipLibCheck": true }, "exclude": ["node_modules"] }
jest.config.cjs
module.exports = { extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, transform: { '^.+\\.ts$': [ 'ts-jest', { useESM: true, }, ], }, };
源码文件内容
src/foo.ts
// import imageType from 'image-type' export const foo = (word: string): string => { // imageType(Buffer.from('abcd', 'utf-8')) return `not ${word}` }
src/index.ts
// import imageType from 'image-type' export const foo = (word: string): string => { // try { // imageType(Buffer.from('abcd', 'utf-8')) // import/use it just to prove a point! // } catch {} return `not ${word}` }
test/helpers.ts
export const logStuff = (a: string): string => { console.log(a) return a }
test/index.test.ts
import { expect, jest, test, describe } from '@jest/globals' import { concat } from '../src/index' import { logStuff } from './helpers' describe('some test suite', () => { test('some test', () => { const res = logStuff(concat('some', 'stuff')) expect(res).toEqual('some not stuff') }) })
遇到的问题
问题1:启用image-type后npm run test报错
取消注释源码中导入image-type的代码后,运行npm run test出现语法错误:
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/danielgruszczyk/workspace/priv/jest-ts-test/node_modules/image-type/index.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import {fileTypeFromBuffer} from 'file-type'; ^^^^^^ SyntaxError: Cannot use import statement outside a module > 1 | import imageType from 'image-type'
问题2:运行npm run testExp报错
执行npm run testExp时直接抛出引用错误:
ReferenceError: exports is not defined 1 | import { expect, jest, test, describe } from '@jest/globals' > 2 | | ^ 3 | import { concat } from '../src/index.js' 4 | // import { logStuff } from './helpers' 5 | at test/index.test.ts:2:23
问题原因与解决方案
问题1:image-type模块无法被识别
原因:Jest默认跳过转译node_modules目录,但image-type是纯ESM模块,在CommonJS运行环境下无法解析其import语法。
解决:在jest.config.cjs中添加transformIgnorePatterns规则,让Jest转译image-type及其依赖file-type:
module.exports = { extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, transform: { '^.+\\.ts$': [ 'ts-jest', { useESM: true, }, ], }, // 新增配置:允许转译指定的node_modules模块 transformIgnorePatterns: [ 'node_modules/(?!(image-type|file-type)/)' ] };
问题2:原生ESM模式下的兼容性问题
原因:
- 测试文件中导入了不存在的
concat方法(实际导出的是foo); - 启用
--experimental-vm-modules后,Jest以原生ESM模式运行,需要严格遵循Node.js ESM规范(如导入路径需带.js后缀),同时CommonJS格式的配置文件存在兼容性冲突。
解决步骤:
- 修正测试文件的导入与逻辑:
import { expect, test, describe } from '@jest/globals' import { foo } from '../src/index.js' import { logStuff } from './helpers.js' describe('some test suite', () => { test('some test', () => { const res = logStuff(foo('stuff')) expect(res).toEqual('not stuff') }) })
- 将Jest配置文件改为ESM格式(重命名为
jest.config.mjs):
export default { extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, transform: { '^.+\\.ts$': [ 'ts-jest', { useESM: true, }, ], }, transformIgnorePatterns: [ 'node_modules/(?!(image-type|file-type)/)' ] };
- 保持
tsconfig.json中的moduleResolution和module为NodeNext,确保TypeScript生成符合Node.js ESM规范的代码。
内容的提问来源于stack exchange,提问作者Daniel Gruszczyk
相关产品推荐
相关产品推荐

