news 2026/9/28 6:52:57

深入理解 CRACO 的 Jest 配置:从 jest.babel 到 jest.configure 的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 CRACO 的 Jest 配置:从 jest.babel 到 jest.configure 的完整实战指南
  • 开发工具
  • 前端构建

【免费下载链接】craco

Create React App Configuration Override, an easy and comprehensible configuration layer for Create React App.

项目地址:https://gitcode.com/gh_mirrors/cr/craco
点击查看免费下载

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}.` ); } } } } }

几个值得注意的实现细节:

  1. 触发条件:只有当addPresets或addPlugins为真,且cracoConfig.babel中确实存在 presets 或 plugins 数组时,才会去覆盖 Jest 的 transform 配置;否则完全不干预。
  2. 依赖默认 transform 键:CRACO 会查找^.+\\.(js|jsx|mjs|cjs|ts|tsx)$这个 CRA 默认的 Jest transform 条目。如果找不到(例如你的 Jest 配置把该键删掉了),会直接抛出craco: Cannot find Jest transform entry for Babel ...错误。
  3. 通过 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 提供的属性:

属性来源说明
envCRACO当前NODE_ENV(development、production、test等)
pathsCRACO一个包含 CRA 全部路径的对象(appSrc、appPublic、appBuild、appHtml、appIndexJs、testsSetup等),完整字段可参考 context.ts 中的CraPaths
resolveCRA由 CRACO 包装的require.resolve,用于在react-scripts(或reactScriptsVersion指定的包)内解析模块路径
rootDirCRA项目根目录

在 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 的启动流程为:

  1. 设置NODE_ENV为test(若未设置);
  2. 加载 craco 配置,校验 CRA 版本;
  3. 获取并覆盖 CRA paths;
  4. 调用overrideJest(cracoConfig, context)把 Jest 配置提供者替换为 CRACO 合并后的结果(见 override.ts,其内部同样调用mergeJestConfig);
  5. 最后执行 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.

项目地址:https://gitcode.com/gh_mirrors/cr/craco
点击查看免费下载
上一篇:QMUI_Android中的矢量图标使用:减小APK体积的有效方法
下一篇:最完整的Flet入门指南:从安装到部署,30分钟打造你的第一个跨平台应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 6:50:10

AI工程从零实战:手写反向传播到全链路部署实践

最近后台收到不少私信&#xff0c;都在问同一个事儿&#xff1a;非科班、零基础&#xff0c;到底能不能啃下 AI 工程这块硬骨头&#xff1f;刚好我手头就在做一个小项目&#xff0c;代号就叫ai-engineering-from-scratch&#xff0c;意思很直白&#xff0c;就是完全从零开始&am…

作者头像 李华
网站建设 2026/9/28 6:50:08

超轻量AI助手nanobot Docker部署指南:本地模型与WebUI实战

1. 为什么我最终选了 nanobot 而不是其他 AI 助手方案1.1 从一次折腾了三天的部署说起前阵子我想给自己搭一个能长期跑在 NAS 上的个人 AI 助手&#xff0c;需求其实很朴素&#xff1a;能对话、能记住上下文、能挂本地模型、最好有个网页界面&#xff0c;别太吃资源。一开始我试…

作者头像 李华
网站建设 2026/9/28 6:50:08

本地部署AI编程智能体:Ollama与PI-Desktop实操指南

做编程智能体&#xff0c;最麻烦的往往不是模型本身&#xff0c;而是运行环境。把代码交给云端对话窗口跑&#xff0c;每一次生成都在烧 token&#xff0c;代码文件还会留在别人的服务器上。我的思路是把整套链路搬到本地&#xff1a;用 PI-Desktop 这个开源桌面端当智能体运行…

作者头像 李华
网站建设 2026/9/28 6:49:37

8300张YOLO头盔检测数据集实战:从训练到落地的智慧交通方案

1. 为什么我盯上了这个8300张的头盔检测数据集智慧交通这个方向&#xff0c;目标检测能落地且真正产生社会价值的场景其实不多&#xff0c;头盔佩戴检测算一个。我最早接触这类需求是在一个园区出入口的项目里&#xff0c;当时甲方要求对骑电动车进出的人员做头盔佩戴识别&…

作者头像 李华
网站建设 2026/9/28 6:49:33

x86工作站交叉编译Qt到龙芯LoongArch的完整实战指南

1. 动手前先讲清楚&#xff1a;交叉编译到底在折腾什么如果你手里有一台龙芯 3A5000 或者 LoongArch 架构的开发板&#xff0c;接到任务时第一反应多半是“直接在板子上装 Qt、写代码、编译不就行了”。但真把机器跑起来就发现&#xff0c;龙芯设备往往配的是精简桌面、内存和 …

作者头像 李华