You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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模式下的兼容性问题

原因:

  1. 测试文件中导入了不存在的concat方法(实际导出的是foo);
  2. 启用--experimental-vm-modules后,Jest以原生ESM模式运行,需要严格遵循Node.js ESM规范(如导入路径需带.js后缀),同时CommonJS格式的配置文件存在兼容性冲突。

解决步骤:

  1. 修正测试文件的导入与逻辑:
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')
  })
})
  1. 将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)/)'
  ]
};
  1. 保持tsconfig.json中的moduleResolution和module为NodeNext,确保TypeScript生成符合Node.js ESM规范的代码。

内容的提问来源于stack exchange,提问作者Daniel Gruszczyk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 09:58:09