- 开发工具
- 前端构建
【免费下载链接】craco
Create React App Configuration Override, an easy and comprehensible configuration layer for Create React App.
CRACO(Create React App Configuration Override)为 Create React App 提供了一层统一、易理解的配置覆盖能力。本篇指南聚焦于其中用于定制 Jest 测试环境的jest配置段,完整讲解jest.babel与jest.configure两大配置项的用法、函数形式的 context 对象能力,并结合仓库源码(如 merge-jest-config.ts、create-jest-babel-transform.ts)与单元测试,深入剖析其底层合并原理与调用链。读完本文,你将能够熟练地在craco.config.js中定制 Jest 的 Babel 转换行为、任意覆盖 Jest 配置项,并理解对象字面量与函数两种配置模式的取舍。
一、配置总览
在craco.config.js中,Jest 相关的配置全部收拢在jest键下,结构如下:
module.exports = { // ... jest: { babel: { addPresets: true /* (default value) */, addPlugins: true /* (default value) */, }, configure: { /* ... */ }, configure: (jestConfig, { env, paths, resolve, rootDir }) => { /* ... */ return jestConfig; }, }, };:::tip
上面大纲中重复出现的属性(例如configure)既可以赋值为对象字面量,也可以赋值为函数。两者合并语义不同,详见 配置技巧(对象字面量与函数)。
:::
从配置结构可以清晰看到两个核心维度:
jest.babel:控制 CRACO 是否把你在babel配置段中声明的 presets / plugins 注入到 Jest 的 Babel 转换器里;jest.configure:允许你以"对象合并"或"函数接管"两种方式修改 Jest 最终配置。
CRACO 对jest段的所有处理都发生在 merge-jest-config.ts 的mergeJestConfig中,其处理顺序为:先加载 CRA 提供的原始 Jest 配置 → 处理jest.babel的 Babel 转换器覆盖 → 应用jest.configure(对象合并或函数接管)→ 最后执行通过craco:jest插件注册的配置钩子。
二、jest.babel:控制 Babel 预设与插件的注入
jest.babel只包含两个布尔选项,用于决定 CRACO 是否把babel配置段中定义的 Babel presets 和 plugins 补充到 Jest 的转换流程中。其官方文档指向了 jestjs.io 的 Code Transformation 说明,核心概念即babel-jest转换器。
2.1 jest.babel.addPresets
- 类型:
boolean - 默认值:
true
是否将babel.presets注入到 Jest 使用的 Babel 配置中。置为false时,CRACO 将只保留 CRA 默认的 Babel 预设(即babel-preset-react-app),忽略你在babel段额外声明的 presets。
2.2 jest.babel.addPlugins
- 类型:
boolean - 默认值:
true
是否将babel.plugins注入到 Jest 使用的 Babel 配置中。置为false时,额外声明的 Babel plugins 不会参与测试环境的编译。
2.3 底层实现:Babel 转换器是如何被覆盖的
这两个选项在 merge-jest-config.ts 的configureBabel函数中被消费。源码逻辑如下:
const BABEL_TRANSFORM_ENTRY_KEY = '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$'; function configureBabel(jestConfig, cracoConfig) { const { addPresets, addPlugins } = cracoConfig.jest?.babel ?? {}; if (addPresets || addPlugins) { if (cracoConfig.babel) { const { presets, plugins } = cracoConfig.babel; if (isArray(presets) || isArray(plugins)) { if (!jestConfig.transform) { jestConfig.transform = {}; } if (jestConfig.transform[BABEL_TRANSFORM_ENTRY_KEY]) { overrideBabelTransform(jestConfig, cracoConfig, BABEL_TRANSFORM_ENTRY_KEY); } else { throw new Error( `craco: Cannot find Jest transform entry for Babel ${BABEL_TRANSFORM_ENTRY_KEY}.` ); } } } } }几个值得注意的实现细节:
- 触发条件:只有当
addPresets或addPlugins为真,且cracoConfig.babel中确实存在 presets 或 plugins 数组时,才会去覆盖 Jest 的 transform 配置;否则完全不干预。 - 依赖默认 transform 键:CRACO 会查找
^.+\\.(js|jsx|mjs|cjs|ts|tsx)$这个 CRA 默认的 Jest transform 条目。如果找不到(例如你的 Jest 配置把该键删掉了),会直接抛出craco: Cannot find Jest transform entry for Babel ...错误。 - 通过 globals 传递配置:
overrideBabelTransform会把整个cracoConfig挂到jestConfig.globals._cracoConfig上(实现思路参考了 facebook/jest 的 issue #1468),随后将 transform 指向 CRACO 自带的./jest-babel-transform,从而保证 Jest 工作线程能拿到 craco 配置:jestConfig.globals = jestConfig.globals || {}; jestConfig.globals._cracoConfig = cracoConfig; jestConfig.transform[transformKey] = require.resolve('./jest-babel-transform');
2.4 真正的转换器:create-jest-babel-transform
被替换后的转换器由 create-jest-babel-transform.ts 生成。它基于babel-jest的createTransformer构建,默认配置为:
const craBabelTransformer = { presets: [ [ 'babel-preset-react-app', { runtime: hasJsxRuntime ? 'automatic' : 'classic', }, ], ], babelrc: false, configFile: false, };- 默认保留
babel-preset-react-app,且会根据react/jsx-runtime是否可解析自动选择 JSXautomatic或classic运行时;设置环境变量DISABLE_NEW_JSX_TRANSFORM=true会强制回退到classic。 babelrc: false与configFile: false表明该转换器不读取项目的.babelrc/babel.config.js,一切以 CRACO 组合出的配置为准。- 当
addPresets为真时,会把babel.presets追加到上述 presets 之后;当addPlugins为真时,会把babel.plugins设为转换器的 plugins。
而 jest-babel-transform.ts 则是运行时的入口:第一次处理文件时,通过loadCracoConfigAsync异步加载 craco 配置并构建真正的babel-jest转换器,之后所有文件都用这份缓存的转换器处理,既解决了"转换器需要在运行前拿到配置"的矛盾,又避免了重复构建的开销。
三、jest.configure:覆盖任意 Jest 配置
jest.configure是定制 Jest 行为的主入口,支持两种赋值方式,对应两种截然不同的合并策略。
- 类型:
JestConfig或(config: JestConfig, { env, paths, resolve, rootDir }) => JestConfig - 可选值范围:任意 Jest 配置项(如
moduleNameMapper、transform、setupFiles、collectCoverageFrom、testPathIgnorePatterns等)。
3.1 对象字面量模式:深合并
当configure被赋值为普通对象时,CRACO 使用deepMergeWithArray将你的配置与 CRA 原始 Jest 配置进行深合并:
jestConfig = deepMergeWithArray({}, jestConfig, configureJest);- 深合并意味着嵌套对象(如
transform、moduleNameMapper)不会整块覆盖,而是逐键合并; - 数组采用"合并"策略,不会直接替换原有数组;
- 你只声明需要改动的键即可,CRA 的其余 Jest 配置原样保留。单元测试 jest.test.js 中专门验证了这一点:"does not remove existing Jest configurations"——合并后的配置键数量大于等于 CRA 原始配置。
下面是一个典型的对象字面量示例,它保留 CRA 的 Babel 转换、同时追加一个路径别名映射:
module.exports = { jest: { configure: { transform: { '^.+\\.[t|j]sx?$': 'babel-jest', }, moduleNameMapper: { '^@components/(.*)$': '<rootDir>/src/components/$1', }, }, }, };3.2 函数模式:完全接管
当configure被赋值为函数时,CRACO 不再做深合并,而是把 CRA 的原始 Jest 配置作为第一个参数交给你的函数,由你全权决定返回什么:
jestConfig = configureJest(jestConfig, context); if (!jestConfig) { throw new Error("craco: 'jest.configure' function didn't returned a Jest config object."); }- 函数必须返回一个 Jest 配置对象,否则 CRACO 会抛出错误;
- 这是"总控制权"模式(源码中该函数恰如其名地叫作
giveTotalControl):你可以自由改写、删除、替换任何配置项,而不受深合并策略约束; - 该函数签名同样可以在 getting-started.md 的配置技巧 中找到通用约定。
module.exports = { jest: { configure: (jestConfig, { env, paths, resolve, rootDir }) => { // 基于当前环境做差异化处理 jestConfig.setupFiles = jestConfig.setupFiles || []; if (env === 'test') { jestConfig.setupFiles.push('<rootDir>/src/setup-tests.js'); } return jestConfig; }, }, };3.3 configure 的 context 对象:env、paths、resolve、rootDir
函数版本的configure会收到第二个参数——context 对象。除了与其他配置段共享的通用属性外,Jest 段还额外提供两个由 CRA 提供的属性:
| 属性 | 来源 | 说明 |
|---|---|---|
env | CRACO | 当前NODE_ENV(development、production、test等) |
paths | CRACO | 一个包含 CRA 全部路径的对象(appSrc、appPublic、appBuild、appHtml、appIndexJs、testsSetup等),完整字段可参考 context.ts 中的CraPaths |
resolve | CRA | 由 CRACO 包装的require.resolve,用于在react-scripts(或reactScriptsVersion指定的包)内解析模块路径 |
rootDir | CRA | 项目根目录 |
在 merge-jest-config.ts 中,resolve与rootDir是这样构建的:
const customResolve = (relativePath: string) => require.resolve( path.join(cracoConfig.reactScriptsVersion ?? 'react-scripts', relativePath), { paths: [projectRoot] } ); const jestContext = { ...context, resolve: customResolve, rootDir: projectRoot, };resolve让你可以按相对路径解析 CRA 内部模块(例如resolve('config/jest/cssTransform.js')),这在引用 CRA 自带的 Jest 工具模块时非常有用;rootDir固定为项目根目录,可用它拼出指向项目内文件的绝对路径;reactScriptsVersion未配置时默认解析react-scripts包;若你使用了 CRA 的 fork 包,可以在配置中通过reactScriptsVersion指定(参见 getting-started.md)。
对应的类型定义见 context.ts:
export interface JestContext extends BaseContext { resolve?: (id: string) => string; rootDir?: string; }3.4 一条链路贯穿到底:craco test 是如何工作的
jest配置段不仅在 Jest 配置生成时生效,CRACO 的craco test命令也走同一套覆盖逻辑。scripts/test.ts 的启动流程为:
- 设置
NODE_ENV为test(若未设置); - 加载 craco 配置,校验 CRA 版本;
- 获取并覆盖 CRA paths;
- 调用
overrideJest(cracoConfig, context)把 Jest 配置提供者替换为 CRACO 合并后的结果(见 override.ts,其内部同样调用mergeJestConfig); - 最后执行 CRA 的
test脚本。
另外,如果你需要以编程方式生成 Jest 配置(例如在自定义 CI 脚本中),可以使用 api.ts 导出的createJestConfig(cracoConfig, callerContext, options)。它会完成NODE_ENV兜底、craco 配置处理、路径获取,并最终调用mergeJestConfig返回一份完整的 JestInitialOptions。
四、实践建议与常见问题
4.1 推荐用法
- 只需增补配置项(别名、setup 文件、覆盖率配置):优先使用对象字面量形式的
configure,借助深合并天然继承 CRA 默认值,且与单元测试中"不删除已有配置"的行为保持一致; - 需要条件化改写、删除配置或依赖 context:使用函数形式,配合
env、resolve、rootDir做差异化处理; - 想让 craco 的 Babel presets/plugins 同时作用于测试环境:保持
jest.babel.addPresets/addPlugins为默认的true;若测试环境需要纯净的babel-preset-react-app,则显式置为false。
4.2 常见报错与排查
craco: Cannot find Jest transform entry for Babel ^.+\.(js|jsx|mjs|cjs|ts|tsx)$:说明你的jest.configure移除了 CRA 默认的 transform 键。若同时启用了jest.babel,CRACO 将无法挂载自定义 Babel 转换器。此时应保留该 transform 键,或改用函数形式自行接管 transform。craco: 'jest.configure' function didn't returned a Jest config object.:函数形式的configure忘记返回jestConfig。CRACO 对未返回配置的情况做了显式校验(见giveTotalControl)。craco: 'cracoConfig' is required./'cracoConfig' should be an object.:调用createJestConfig时未传配置或传入了函数,均会被 api.ts 拒绝。
4.3 验证路径
- 单元测试:jest.test.js 验证了对象字面量合并行为与"不删除既有配置"的语义;
- 配套配置示例:craco.config.js;
- 核心实现:merge-jest-config.ts、create-jest-babel-transform.ts、jest-babel-transform.ts;
- 类型定义:context.ts。
五、小结
CRACO 的jest配置段虽然只有两个键,却覆盖了测试环境定制的完整闭环:jest.babel控制 Babel 预设/插件是否注入(默认均开启),jest.configure则以"深合并对象"或"函数总接管"两种模式让你触碰任意 Jest 配置项,并通过resolve、rootDir等扩展 context 属性获得与 CRA 内部模块协作的能力。理解 merge-jest-config.ts 中"先 Babel、再 configure、最后插件钩子"的处理顺序,将帮助你在排查问题时快速定位行为来源。
- 开发工具
- 前端构建
【免费下载链接】craco
Create React App Configuration Override, an easy and comprehensible configuration layer for Create React App.
相关推荐
在 CRACO 中为 Jest 配置 Webpack 别名(moduleNameMapper 实战指南)
在 CRACO 中为 Jest 配置 Webpack 别名(moduleNameMapper 实战指南) 本篇技术指南面向使用 Create React App
开发工具前端构建Laravel CORS深度解析:从原理到实战的完整配置指南
Laravel CORS深度解析:从原理到实战的完整配置指南 跨域资源共享(CORS)是现代Web开发中不可或缺的安全机制,而Laravel CORS扩展包则为
后端在 webpack 项目中使用 Jest:从 webpack 配置到 Jest 配置的完整迁移指南
在 webpack 项目中使用 Jest:从 webpack 配置到 Jest 配置的完整迁移指南 本指南聚焦于如何在基于 webpack 构建的前端项目(尤其
测试质量保障代码覆盖率开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考