在TypeScript項目中使用Jest進行測試時,ES模塊(ESM)與CommonJS模塊的互操作性常常帶來挑戰。本文將系統介紹如何使用ts-jest實現ES模塊與CommonJS依賴的無縫協作,解決常見的模塊解析問題和兼容性錯誤。
當TypeScript項目配置為生成ES模塊(如設置"module": "ES2022"或"NodeNext")時,導入CommonJS依賴可能導致多種錯誤,例如SyntaxError: Cannot use import statement outside a module或require is not defined。這是因為:
ES模塊使用import/export語法,而CommonJS使用require/module.exports
Node.js對兩種模塊系統的處理方式存在根本差異
TypeScript的模塊解析邏輯與Jest的轉換流程可能衝突
官方文檔詳細説明了這些兼容性問題:ESM Support。
配置基礎:tsconfig與Jest的協同設置
正確配置TypeScript編譯器選項是解決模塊互操作問題的第一步。根據項目需求選擇以下兩種配置策略之一:
策略1:使用純ES模塊模式
{
"compilerOptions": {
"module": "ES2022",
"target": "ESNext",
"esModuleInterop": true,
"moduleResolution": "NodeNext"
}
}
策略2:使用混合模塊模式(Node16/NodeNext)
{
"compilerOptions": {
"module": "NodeNext",
"target": "ESNext",
"esModuleInterop": true,
"isolatedModules": true
}
}
混合模塊模式要求在package.json中設置"type": "module",並對.mts和.cts文件使用不同的處理策略。詳細説明見Hybrid Node module。
Jest配置的關鍵調整
使用ESM預設快速配置
ts-jest提供了預設函數簡化ESM配置,推薦在jest.config.ts中使用:
import type { Config } from 'jest'
import { createDefaultEsmPreset } from 'ts-jest'
export default {
displayName: 'ts-only',
...createDefaultEsmPreset({
tsconfig: 'tsconfig-esm.json',
}),
} satisfies Config
這個預設會自動配置必要的轉換規則和文件擴展名處理。預設實現代碼可查看create-jest-preset.ts。
手動配置轉換規則
如果需要更精細的控制,可以手動配置Jest轉換選項:
import type { Config } from 'jest'
import { TS_EXT_TO_TREAT_AS_ESM, ESM_TS_TRANSFORM_PATTERN } from 'ts-jest'
export default {
extensionsToTreatAsEsm: [...TS_EXT_TO_TREAT_AS_ESM],
transform: {
[ESM_TS_TRANSFORM_PATTERN]: [
'ts-jest',
{
useESM: true,
tsconfig: 'tsconfig-esm.json'
},
],
},
} satisfies Config
處理CommonJS依賴的實戰技巧
1. 使用動態import()加載CommonJS模塊
對於僅提供CommonJS版本的依賴,使用動態import替代靜態import:
// 替代 import { someFunction } from 'commonjs-package'
const { someFunction } = await import('commonjs-package')
2. 配置moduleNameMapper解決別名問題
當第三方庫使用非標準模塊路徑時,在Jest配置中添加映射規則:
export default {
// ...其他配置
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
'^commonjs-package$': '<rootDir>/node_modules/commonjs-package/dist/index.cjs'
}
}
3. 創建自定義解析器處理複雜場景
對於複雜的模塊解析需求,可以實現自定義Jest解析器:
import type { SyncResolver } from 'jest-resolve'
const resolver: SyncResolver = (path, options) => {
// 處理.mjs/.cjs文件的解析邏輯
if (path.endsWith('.mjs')) {
return options.defaultResolver(path.replace('.mjs', '.mts'), options)
}
return options.defaultResolver(path, options)
}
export default resolver
然後在Jest配置中引用:
export default {
// ...其他配置
resolver: '<rootDir>/jest-resolver.ts'
}
運行與調試
使用以下命令啓動Jest測試,確保啓用Node.js的ESM支持:
node --experimental-vm-modules node_modules/jest/bin/jest.js
對於Yarn用户,可以使用:
yarn node --experimental-vm-modules $(yarn bin jest)
注意:Jest的ESM支持仍在完善中,某些高級功能如自動模擬可能受限。跟蹤最新進展可關注Jest ESM支持進度。
常見問題解決方案
|
錯誤場景
|
解決方案
|
參考文檔
|
|
|
確保 |
ESM Support |
|
|
將 |
動態import() |
|
|
檢查 |
模塊解析 |
項目示例參考
ts-jest倉庫提供了多個ESM配置示例,可直接參考或複用:
- ts-only示例:純TypeScript項目的ESM配置
- esm-features示例:展示各種ESM特性的測試用例
- react-app示例:React+TypeScript項目的ESM配置
這些示例包含完整的package.json、tsconfig.json和jest.config.ts配置文件,可作為實際項目的模板。
通過以上策略,大多數ts-jest與ES模塊的互操作問題都能得到有效解決。關鍵是理解TypeScript、Jest和Node.js在模塊處理上的差異,並正確配置轉換流程。對於複雜場景,可結合自定義解析器和動態導入實現靈活的模塊處理邏輯。