TypeScript动态导入在测试与Webpack运行时无法同时正常工作
这个问题我之前也碰到过,本质是Webpack和ts-jest对CommonJS模块的处理逻辑不一致导致的——zxcvbn是CommonJS格式的模块,两边对它的导出解析规则不一样,才会出现“测试和生产二选一”的尴尬情况。下面给你几个实用的解决办法:
方案1:代码层面做兼容判断(最直接)
不用改任何配置,只需要在动态导入后,判断模块是否有default属性,有的话取default,否则直接用模块本身。这样不管是Webpack处理后的生产环境,还是ts-jest的测试环境,都能正确拿到zxcvbn函数:
const result = import('zxcvbn').then(zxcvbnModule => { // 兼容两种导出格式 const zxcvbn = zxcvbnModule.default || zxcvbnModule; return zxcvbn('foo'); });
这种方法的好处是零配置改动,代码逻辑清晰,而且对任何第三方CommonJS模块都适用。
方案2:统一ts-jest和Webpack的模块解析规则(更优雅)
问题的根源是ts-jest默认对CommonJS模块的处理和Webpack不同,我们可以通过配置让ts-jest和Webpack保持一致:
- 在你的
tsconfig.test.json里,开启esModuleInterop: true(这个配置会自动开启allowSyntheticDefaultImports,而且会调整模块导入的逻辑,让ts-jest正确识别CommonJS模块的“合成默认导出”):
{ "extends": "./tsconfig.json", "compilerOptions": { "esModuleInterop": true, "allowSyntheticDefaultImports": true } }
- 确保你的
jest.config.js里指定了这个测试专用的tsconfig:
module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', globals: { 'ts-jest': { tsconfig: 'tsconfig.test.json' } } };
这样配置后,你就可以统一使用({default: zxcvbn})的解构方式,不管是测试还是生产打包都能正常运行了。之前你设置allowSyntheticDefaultImports: false导致编译错误,是因为没有开启esModuleInterop,单独开allowSyntheticDefaultImports不足以让ts-jest正确处理CommonJS模块的默认导出。
方案3:临时类型声明hack(不推荐,仅作备选)
如果上面两种方案都不适用,你可以手动给zxcvbn添加一个默认导出的类型声明,强制TypeScript识别它的default属性:
在项目根目录创建src/types/zxcvbn.d.ts(路径根据你的项目结构调整),内容如下:
declare module 'zxcvbn' { import type { ZXCVBNResult } from 'zxcvbn'; const zxcvbn: (password: string, userInputs?: string[]) => ZXCVBNResult; export default zxcvbn; }
这种方法属于类型层面的补丁,可能会和官方的@types/zxcvbn冲突,所以只建议在紧急情况下临时使用。
内容的提问来源于stack exchange,提问作者harryg

