Jest 单体仓库开发实战:从环境搭建、测试体系到源码架构的完整工作手册
发布时间:2026/9/18 3:22:59 锦皓数字建站

Jest 单体仓库开发实战从环境搭建、测试体系到源码架构的完整工作手册【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest在 Jest 官方仓库中CLAUDE.md 是写给 AI 编码代理的入口导航它通过.github/copilot-instructions.md导入方式指向一份 17KB 的详细工作指令文档 .github/copilot-instructions.md涵盖环境搭建、构建体系、测试命令、Lint 硬规则、Mock 类型化模式、代码规范以及如何定位每个功能在源码中的位置等全部内容。读完本篇你将掌握在 Jest 仓库中完成一次完整开发闭环安装依赖 → 构建 → 跑单测/e2e → 类型检查 → Lint → 提交前校验清单所需的全部实操细节并理解其 55 个包组成的单体仓库内部各组件的协作关系。CLAUDE.md 的作用一个导入式的代理指令文件仓库根目录的 CLAUDE.md 本体只有两行# CLAUDE.md .github/copilot-instructions.md这是 Claude Code 类工具支持的file导入语法代理加载CLAUDE.md时会自动展开被引用的文件内容。真正承载全部技术内容的就是 .github/copilot-instructions.md标题为 Jest Repository — Coding Agent Instructions。值得注意的是仓库并非只有一份这类文档——根文档在 When in doubt 一节中列出expect、jest-circus、jest-config、jest-environment-node、jest-fake-timers、jest-haste-map、jest-mock、jest-reporters、jest-resolve、jest-runtime、jest-snapshot、jest-source-map、jest-transform、jest-worker这 14 个关键包各自还维护了一份包级CLAUDE.md例如 packages/jest-runtime/CLAUDE.md用于沉淀包内特有的坑与约定。这份文档的定位是可被机器执行的操作手册因此它的每一节都对应仓库中真实存在的脚本、配置或 CI 行为本文按原文骨架逐节展开并结合源码做纵深验证。仓库形态与环境前提文档开篇给出的仓库画像可以直接从仓库文件得到印证大型单体仓库55 个包、200 余个 e2e fixture位于 packages/ 与 e2e/ 目录下工具链Lerna-litelerna.json 中version: 30.4.2根 package.json 依赖lerna-lite/cli、lerna-lite/publish Yarn 4Berrynode-moduleslinker不是 PnP根 package.json 中packageManager: yarn4.18.0语言全 TypeScript每个包用 Webpack 单独编译Node 引擎^18.14.0 || ^20.0.0 || ^22.0.0 || 24.0.0与根 package.json 的engines字段完全一致并被 yarn.config.cjs 的约束规则同步强制到所有公开发布的工作区上。环境搭建install 之后必须先 build:js文档给出的 Setup 步骤只有三条命令但背后藏着这个仓库最容易踩的坑——源码与构建产物的双轨加载机制corepack enable yarn install # 约 45 秒需要 Pythonnode-gyp 用 yarn build:js # 约 5 秒必须执行yarn build:js之所以是强制步骤源于文档明确解释的双轨机制单元测试从源码跑各包__tests__/里的import {x} from ../解析到该包的src/由babel-jest即时转译jest.config.mjs 中transform正是{\\.[jt]sx?$: require.resolve(babel-jest)}e2e 测试跑构建产物e2e fixture 直接调用packages/jest-cli/bin/jest.js该入口从build/加载所有包同时跨 workspace 包的 import 也经由main字段指向build/。因此每次 checkout 后、每次修改了其他包或 e2e fixture 会消费的包之后都必须重新yarn build:js。完整的yarn buildbuild:js build:ts bundle:ts耗时 3–5 分钟涉及 API Extractor 类型声明产物仅在修改类型声明或 API 输出、以及运行yarn typecheck:tests之前才需要后者从build/*.d.ts解析跨包类型缺少构建产物会报幻影错误。迭代与清理命令yarn watchWebpack 热编译、yarn watch:ts声明文件 watch即yarn build:ts --watch、yarn build-cleanrimraf 掉所有packages/*/build、dist、tsconfig.tsbuildinfo、yarn clean-all再清 e2e 临时产物与 node_modules。这些脚本均定义在根 package.json 的scripts字段中可直接查证。测试体系命令、配置与 CI 行为常用测试命令文档列出的命令全部来自根 package.json 的 scripts可原样复制使用yarn jest path # 跑指定文件或目录 yarn jest-runtime-vm-modules # 以 --experimental-vm-modules 跑 jest-runtime 的 ESM 测试 yarn workspace name test # 只跑单个包 yarn jest-coverage # 带覆盖率 yarn jest-jasmine-ci # CI 模式 jasmine2 runner yarn test-leak # 对 jest-mock/jest-diff/pretty-format 做 detectLeaks yarn test-types # tstyche 类型级测试__typetests__/ 目录 yarn test-ts # TypeScript 配置集成测试独立配置 yarn test-ci-partial:parallel --max-workers N --shardM/N # CI 分片几个值得展开的实现细节yarn jest并非调用 npm 上的 jest而是node ./packages/jest-cli/bin/jest.js——用自己仓库的 CLI 测自己yarn jest-runtime-vm-modules的真实定义是NODE_OPTIONS--experimental-vm-modules --no-warnings yarn jest packages/jest-runtime对应 package.json 中的 scriptyarn test-leak实际为yarn jest -i --detectLeaks --color jest-mock jest-diff jest-source-map pretty-format。三份 Jest 配置与默认运行参数配置用途jest.config.mjs主配置jest.config.ci.mjsCI 专用叠加github-actions、jest-junit、jest-silent-reporter、summary四组 reporter覆盖率输出 jsonjest.config.ts.mjstest-ts集成测试专用从 jest.config.mjs 可以读出文档所述默认值的出处testTimeout: 70_000默认超时 70 秒、projects: [rootDir, rootDir/examples/*/]examples 目录也是测试工程、snapshotSerializers使用jest-serializer-ansi-escapes这正是后文快照含 ANSI 序列陷阱的根源。默认 runner 是jest-circus设置环境变量JEST_JASMINE1可切换到兼容用的jest-jasmine2对应yarn jest-jasminescript。新测试文件一律使用.ts后缀仓库中仍有少量遗留.js。关于类型检查的一个硬约束每个被yarn typecheck:tests覆盖的包内__tests__/目录都有各自的tsconfig.jsonextends根 tsconfig.test.json其中noEmit: true、types: [jest/test-globals]。当测试用到Console/Stats/__dirname等 Node 全局时需要向该目录 tsconfig 的types数组中追加node。yarn typecheck:tests的 glob 列表tsc -b packages/{...}/**/__tests__写在根 package.json 中新增包必须把该包追加进这个 glob——这一条在 CI 中是门控项必须 exit 0。类型测试的归属规则matcher 的类型测试放 jest-types文档特别强调了一条容易被忽略的约定expectmatcher 的类型测试属于 packages/jest-types/typetests/expect/该目录下已有toHaveBeenCalledWith.test.ts、toHaveBeenLastCalledWith.test.ts等文件不放packages/expect/__typetests__/原因jest-types测的是用户视角的公开jest/globals面用户实际 import 的类型而packages/expect/__typetests__/只覆盖expect包内部关切如MatcherFunction、JestExpect形状。e2e 测试的约束与手动运行e2e 测试e2e/tests/禁止使用jest.mock/jest.fn——ESLint 规则强制执行原因是 e2e 进程本身是独立启动的 jest 实例mock 无法按单测语义工作正确做法是编写 fixture 文件部分 e2e 测试依赖 Mercurial需要brew install hg手动跑单个 e2e fixturecd e2e/test-directory node ../../packages/jest-cli/bin/jest.js --no-cacheDocblock pragma单文件级环境覆盖文档说明测试文件头部的 docblock 注释可覆盖测试环境jest-environment name覆盖该文件的 test environmentjest-environment-options {key: value}合并进testEnvironmentOptions。从源码可确认其实现位置packages/jest-runner/src/runTest.ts 中docblock.parse(docblock.extract(testSource))解析 pragma读取jest-environment字段且报错提示只允许定义单一环境随后在 L151-L155 解析jest-environment-options的 JSON 字符串并合并——两个 pragma 都只对该文件生效。CI 行为解读为什么本地红、CI 绿CI 矩阵定义在 .github/workflows/test.ymlUbuntu/macOS/Windows × Node 18/20/22/24/25/26测试步骤通过nick-fields/retryaction 包裹配置为timeout_minutes: 10, max_attempts: 3, retry_on: error见 test.yml L41-L46并以--shardM/N切分。文档由此给出一条排障经验若某测试本地稳定失败而 CI 常绿怀疑是被重试掩盖的 flaky。另一个反直觉点在覆盖率codecov/patch与codecov/project在四个Node LTS on Ubuntu with coverage (N/4)分片全部上传前数字没有意义且两者本身是参考性的require_ci_to_pass: false, target: auto。覆盖率的统计口径也有限制——只统计运行套件所在进程内被执行的代码因此只能经 e2e fixture 触达的行永远显示为未覆盖。四条值得背下来的测试陷阱文档 Test gotchas worth memorizing 一节给出四条高频坑均已在仓库中可验证ANSI 颜色快照很多快照包含 chalk 渲染的 ANSI 转义序列jest.config.mjs 注册了jest-serializer-ansi-escapes。更新快照必须FORCE_COLOR1 yarn jest path -u否则颜色序列被剥掉、生成错误快照Windows CI 上的路径断言期望值由path.join/path.dirname/path.basename构造时断言侧也要用path.join构造硬编码/path/to/x这类 POSIX 字符串在 Windows 上必挂扫描 globalThis 时防抛异常的 getter遍历Object.keys(scope)再读scope[key]会在用户安装了 throwing getter 时崩溃应以key in scopehas陷阱而非get作门控ESM 条件测试辅助函数jest/test-utils提供三个 ESM 条件用例辅助源码见 packages/test-utils/src/ConditionalTest.tstestWithVmEsmNode 18 需--experimental-vm-modulestestWithLinkedSyntheticModuleNode 22.21/24.8以linkRequests能力门控testWithSyncEsmNode 24.9以hasAsyncGraph门控与 packages/jest-runtime/src/internals/nodeCapabilities.ts 中supportsSyncEvaluate探测SourceTextModule.prototype.hasAsyncGraph的实现相呼应特别注意yarn jest packages/jest-runtime不包含ESM 套件必须用yarn jest-runtime-vm-modules。Lint硬规则与格式化文档要求每次编辑后立即 lint 变更文件yarn eslint --cache --fix files # 开发中 yarn lint # 推送前全量eslint . --cache --ext js,jsx,cjs,mjs,ts,tsx,mdLint 栈为 ESLint 9.x flat configeslint.config.mjs并在 .eslintplugin/index.mjs 定义了三个渐进式迁移本地规则local/no-restricted-types-eventually基于no-restricted-types、local/prefer-rest-params-eventually、local/prefer-spread-eventually均直接复用内置规则实现。Markdown 代码块也参与 lint。CI 会失败的硬规则Hard rules规则说明用graceful-fs禁止fs/node:fs由no-restricted-imports封禁用globalThis禁止global由no-restricted-globals封禁源码中 Node 内置模块必须走node:协议如import * as path from node:path例外expect、expect-utils、jest-matcher-utils、jest-message-util、jest-pattern、jest-regex-util、jest-util会被 webpack/浏览器端打包消费不得使用node:前缀由no-restricted-syntax强制见 CHANGELOG #16167sort-keys源码中键必须字母序测试中关闭import-x/order组内字母序builtin → external → internal → parent → sibling → indexnewlines-between: never可 autofix禁止Function类型与Boolean/Number/Object/String/Symbol包装类型只用原始类型local/no-restricted-types-eventually告警版权头每个.js/.ts/.tsx/.mjs/.cjs必须有如下头部由yarn check-copyright-headers强制版权头标准格式/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */Prettier 规则直接写在根 package.json 的prettier字段bracketSpacing: false、singleQuote: true、trailingComma: all、arrowParens: avoid。文档的建议是让yarn lint:prettier重写永远不要手排。Mock 类型化模式Always typed / Never文档给出了 mock 函数的类型化正反例这是该仓库 TypeScript 实践中最具引用价值的一节始终类型化推荐jest.fntypeof someFn(); // 显式 jest.fn((arg: T) result); // 从实现推断 const Mocked X as jest.MockedClasstypeof X; // 模块 mock 的类 jest.mocked(obj.method).mockReturnValue(x); // 对已 mock 对象的类型化 cast绝不要jest.fn().mockReturnValue(x); // 会退化为 UnknownFunction (x as jest.Mock).mockReturnValue(y); // cast 汤 ({foo: jest.fn()}) as unknown as Real; // 应改为构造类型化对象另外两个容易忽略的点beforeEach(() jest.clearAllMocks())会令typecheck:tests失败返回类型被拓宽必须改为块体beforeEach(() { jest.clearAllMocks(); })三个 reset 语义的区别mockClear()只清调用记录mockReset()再清实现与返回值mockRestore()进一步恢复原始实现仅对spyOn有意义。对应配置项clearMocks/resetMocks/restoreMocks会在每个测试前自动执行相应操作。参考实现见 packages/jest-mock/typetests/mock-functions.test.ts 与 packages/jest-runtime/src/internals/tests/。代码模式DI、封装、注释、错误处理这一节是文档对贡献者代码风格的强约束五条规则均可在现有源码中对照类抽取与依赖注入类超过 3 个依赖时用*Options命名的构造函数选项袋不叫*Deps每个依赖对应一个private readonly x: T字段不要塞成一个deps: Options字段方法内直接读字段this.resolution.resolveCjs(...)而非先解构。若字段初始化器闭包了this或另一字段必须在构造函数体内初始化声明期初始化器先于函数体执行会捕获undefined。不要把不相关依赖归入嵌套袋子来减少参数个数——袋子过长说明类职责过多应拆分封装边界顶层消费者看到什么才是真正重要的边界内部状态持有类可以保留较松的文件内 API。passthrough 包装看起来多余时要找语义替代不能靠暴露它本想隐藏的私有状态来修命名与注释禁止缩写名requireFn而非req默认不写注释只为非显性的 WHY隐藏约束、微妙不变量、特定 bug 的 workaround、反直觉行为写注释不解释 WHAT不在注释里引用当前 PR/调用方会腐烂在新声明上方插入代码时检查上一行是否会变成绑定到你声明上的 doc 注释错误处理用jest-util的isError收窄抛出的值不要e as Error不用异常做控制流优先显式能力谓词而非 try/catch 探针在系统边界用户输入、外部 API校验内部代码可信重构 PR不强制严格保持行为不变——暴露潜在 bug 本身就是价值发现的问题就地修复并补回归测试review 期间不改写amend提交后续工作以新提交叠加跨多个逻辑分组的任务一组一个提交。How the pieces fit一次测试运行的完整数据流文档给出了一段被广泛引用的自上而下流程也是理解 Jest 55 包架构的最快地图jest-cli — CLI 参数 jest-config — 加载并归一化用户配置jest-validate 对照 jest-schemas 校验 jest-core — 编排haste map、排序、worker 派生、输出 jest-runner — 单 worker 内执行测试 jest-runtime — 模块加载、mock、ESM/CJS 互操作、运行用户代码 jest-circus — 默认测试框架jest-jasmine2 为遗留替代 expect — 断言失败信息由 jest-matcher-utils、jest-diff、pretty-format 格式化 jest-reporters — 最终输出default、GitHub、junit 等横向贯穿多阶段的基建包jest-haste-map文件爬取模块映射Watchman 或 Node fs 两种后端、jest-resolve模块解析、jest/transform转译管线babel-jest为默认、jest-workerworker 池、jest-environment-node/jest-environment-jsdomVM 上下文全局、jest-mockjest.fn/jest.spyOn实现、jest-fake-timers、jest-snapshot/jest/snapshot-utils、jest-watcher--watch交互、jest-changed-files/jest-resolve-dependencies--onlyChanged/--onlyFailures、jest-message-util堆栈错误格式化、jest-types/jest-util共享类型与小工具如isError、invariant、deepCyclicCopy。按目标定位修改点文档提供的一张目标 → 入口文件映射表是所有修改的导航中枢下表中的入口文件均已确认存在于仓库目标从哪开始可能连带触及增改 CLI flagjest-cli/src/args.tspackages/jest-cli/src/args.tsjest-config、jest-types增改配置项jest-schemas/src/raw-types.tsjest-config/src/Descriptions.tsjest-validate、jest-types、docs/Configuration.md修改 matcherexpect/src/matchers.ts或asymmetricMatchers.ts、spyMatchers.tsexpect-utils、jest-matcher-utils修改快照行为jest-snapshot/src/*.tsjest/snapshot-utils、pretty-format修改 reporter 输出jest-reporters/src/ReporterReporter.tsjest-message-util、pretty-format修改模块加载/mockjest-runtime/src/index.tsinternals/jest-resolve、jest-mock、jest/transform修改模块解析jest-resolve/src/resolver.tsdefaultResolver.tsjest-runtime、jest-haste-map修改文件爬取/监听jest-haste-map/src/crawlers/或watchers/jest-worker修改转译管线jest/transform/src/ScriptTransformer.ts各 transformer如babel-jest修改测试环境全局jest-environment-node/jest-environment-jsdomjest-runtime消费方修改 worker 调度jest-runner/src/runTest.tstestWorker.tsjest-worker、jest/test-sequencer修改定时器 mockjest-fake-timers/src/*.tsjest-runtime接线修改 mock 函数行为jest-mock/src/index.tsjest-runtime接线三条追踪经验一次公开 API 行为变更通常需要五处落点实现包代码、jest-typesjest-schemas的类型、jest-config的归一化、e2e/下验证用户可见行为的 fixture、docs/文档Runtime类packages/jest-runtime/src/index.tsexport default class Runtime被文档标注为可子类化其覆盖缝override seams包括requireModule、requireModuleOrMock、requireMock、requireActual、requireInternalModule、unstable_importModule源码中这些方法分别在 L376-L425 一带定义且构造函数内大量以回调形式this.requireModule(...)外发——任何内部加载模块的回调都必须经由这些缝分发不得直接调用兄弟内部方法当node:vm语义同步 vs 异步 ESM成为问题时查 packages/jest-runtime/src/internals/nodeCapabilities.ts 中的能力门capability gate并且代码移动时必须把门控条件原样带走。提交前校验清单与 Yarn 约束文档给出的推送前完整清单yarn build:js yarn eslint --cache --fix files # 开发中每次编辑后 yarn lint # 最终检查 yarn jest affected # 或 yarn jest --config jest.config.ci.mjs yarn typecheck:tests # 必须 exit 0 yarn check-changelog yarn check-copyright-headers yarn constraints yarn dedupe --check yarn verify-pnp其中yarn constraints的判定逻辑实现在 yarn.config.cjs规则包括同一依赖在所有 workspace 中版本一致types/node通过 resolutions 钉在18.x是唯一例外同一依赖不得同时出现在dependencies与devDependencies公开包必须有license/repository/publishConfig/engines且main/types以./开头私有包则反向清掉这些字段。yarn constraints --fix可自动修复yarn dedupe --check查重复版本。新增依赖必须走yarn workspace pkg add dep——约束会拒绝跨仓库版本不一致。常见坑速查文档 Common pitfalls 一节的五条速查第一条与 Setup 一节的双轨机制直接呼应仓库包内部报Module not found忘了yarn build:js——跨包 import 经main指向build/yarn install后 lockfile 变动必须提交CI 使用--immutable可在 test.yml 的yarn --immutable步骤中印证typecheck:tests报Console/Stats/__dirname错误向该测试目录 tsconfig 的types数组加nodeexecFilemock 类型化其重载极多内部实现无法直接满足typeof execFile在实现侧 cast 为((_file, _args, cb) cb(null, ...)) as unknown as typeof execFilepeer-dep 警告存量警告属预期但新增的不行——合并前必须解决。CHANGELOG 规范用户可见变更必须在 CHANGELOG.md 的## main小节下新增条目分Features/Fixes/Chore Maintenance三节条目格式为- [package-name] 描述 (#PR_NUMBER) - [jest-core, jest-cli] 多包条目以逗号分隔包名 (#16100)要求同一节内按首个包名字母序排列yarn check-changelog会校验链接格式见 scripts/checkChangelog.mjs一个提交只承载一个逻辑变更不要 squash 无关工作。当前 CHANGELOG 的## main条目如mockFn.whenCalledWith特性、describe级重试等正是这一格式的实例。遇到不确定时文档的最后两行是全篇的行动准则相关包各有CLAUDE.md包内特有的坑以包级文档为准以及——信任代码胜过这份文件Trust the code over this file一旦文档与源码观察冲突应把修正这份文档作为变更的一部分。这条自我修正条款正是这类代理指令文档能够长期保持准确的关键设计。【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。