Angular CLI 仓库的 Bazel 构建实践Workspaces 依赖同步、Windows 兼容与 jasmine_node_test 调试指南【免费下载链接】angular-cliCLI tool for Angular项目地址: https://gitcode.com/gh_mirrors/an/angular-cli本文是 angular-cli 仓库 docs/process/bazel.md 的深度解读与实践指南核心主题是在基于 pnpm/Yarn Workspaces 的 monorepo 中引入 Bazel 构建系统后如何正确同步 NPM 依赖、如何在 Windows 上规避 runfiles 解析陷阱以及如何高效调试jasmine_node_test测试目标。读完本文你将掌握该仓库依赖声明的双份同步规则、require.resolve的跨平台意义以及一套可直接复制的远程断点调试命令。一、背景Workspaces 与 Bazel 的“混搭”架构1.1 包管理基础Yarn Workspacesangular-cli仓库最初采用 Yarn workspaces 组织多包架构仓库中各个package.json的依赖被统一链接并安装在一起形成一个共享的node_modules。这意味着像packages/angular/cli、packages/angular_devkit/core这样的子包虽然各自声明依赖但实际安装时共享同一棵依赖树。在本文仓库的当前版本中包管理已迁移为 pnpm但 Workspaces 的理念被完整继承见根目录 pnpm-workspace.yaml它以packages:字段显式列出全部工作区成员packages/angular/cli、packages/angular_devkit/*、packages/schematics/angular、modules/testing/builder、tests等并设置hoist: false注释明确说明这是为了让 pnpm 在 Bazel 之外铺出的node_modules与rules_js在 Bazel 内部铺出的结构保持一致不产生隐藏的node_modules/.pnpm/node_modules。此外还设置了autoInstallPeers: false避免 pnpm 自动安装 peer 依赖确保版本完全显式可控。1.2 Bazel 的引入与约束后来仓库引入 Bazel 来管理部分构建依赖。但Bazel 并不支持 Yarn Workspaces因为 Bazel 需要铺设多个node_modules目录每个 Bazel 包在自己的执行环境中解析依赖这与 Workspaces 的单一共享node_modules模型冲突。因此仓库长期处于“混合模式”mixed modepnpm 负责工作区安装Bazel 负责构建与测试。在这种模式下开发者必须格外小心地同步依赖声明。从仓库的 Bazel 配置可以印证这一点MODULE.bazel 声明了rules_nodejs6.7.5、aspect_rules_js3.4.1、aspect_rules_ts3.10.1、aspect_rules_jasmine2.0.4、aspect_rules_esbuild等规则集并通过aspect_rules_js//npm:extensions.bzl的npm_translate_lock读取根目录pnpm-lock.yaml生成 Bazel 侧的 NPM 依赖仓库。.bazelrc 中设置了build --symlink_prefixdist/将 Bazel 输出目录bazel-out、dist/bin等统一符号链接到仓库根目录的dist/前缀下避免污染源码树。根目录 package.json 提供bazel: bazelisk脚本因此仓库内统一用pnpm bazel来调用 Bazelbazelisk 负责按.bazelversion下载匹配的 Bazel 版本。二、依赖同步规则根 package.json 与子包 package.json 的“双份维护”这是混合模式下最容易踩坑、也最需要遵守的规则NPM 注册表依赖必须在根package.json声明。仓库中的yarn_install现为npm_translate_lock规则只读取根目录的package.json以及pnpm-lock.yaml。因此任何 Bazel 目标如果需要依赖从 NPM 下载的包该依赖就必须出现在根package.json中否则 Bazel 解析//:node_modules/pkg时会失败。运行时依赖还需同步更新到对应子包的package.json。如果某个依赖在运行时非 devDependencies也被使用那么所在包的package.json也必须同步声明。原因很实际当用户从 NPM 下载发布版本时他们不会使用 Bazel只能靠子包自身的package.json来安装依赖。保持两个package.json同步是开发者的责任。这条规则的底层原因可以从 MODULE.bazel 的npm_translate_lock调用看出它的data参数不仅包含//:package.json和//:pnpm-workspace.yaml还显式列出了//modules/testing/builder:package.json、//packages/angular/cli:package.json等全部子包。也就是说Bazel 侧依赖解析完全以这些package.json的锁定结果为输入根与子包的声明缺一不可。从源码看依赖的 Bazel 声明方式以核心包为例packages/angular/cli/BUILD.bazel 中ts_project目标的deps形如deps [ :node_modules/angular-devkit/architect, :node_modules/angular-devkit/core, :node_modules/inquirer/prompts, //:node_modules/types/node, //:node_modules/typescript, ... ]其中:node_modules/...来自该 BUILD 文件顶部的npm_link_all_packages()由npm//:defs.bzl生成链接本包package.json中声明的依赖而//:node_modules/...则链接根package.json中声明的依赖。这种区分正是上述“双份同步”规则在 BUILD 文件中的直观体现本包声明的走:node_modules根声明的走//:node_modules。发布时的包依赖治理发布侧同样有保障机制tools/bazel/npm_package.bzl 与 packages/angular/cli/BUILD.bazel 中的npm_package(name pkg)通过pkg_deps声明该包对angular-devkit/*、schematics/angular等兄弟包的依赖关系scripts/release-checks/dependency-ranges/ 目录下的发布检查脚本peer-deps-check.mts、latest-versions-check.mts会在发布前校验依赖范围防止发布出去的包出现依赖缺失或版本漂移。三、Windows 支持为什么必须使用require.resolve3.1 runfiles 机制与require.resolve在 Bazel 中任何形式的 Node 文件查找都应该使用require.resolve。这是因为rules_nodejs借助 Bazel 的runfiles机制来解析路径一个给定的 Bazel 目标只能访问其依赖产生的输出而不是整个仓库任意路径下的文件。3.2 Linux 与 Windows 的差异LinuxBazel 会在目标实际运行的位置铺设一层symlink forest符号链接树把依赖文件链接到位。由于文件确实“在那里”无论是否使用require.resolve大部分路径都能被正确解析。因此漏写require.resolve在 Linux 上几乎不会被察觉。WindowsBazel 默认不铺设 symlink forest。部分文件如其他规则产出的构建产物即使不用require.resolve也能找到但node_modules 依赖和数据文件必须通过 runfiles 目录查找此时就只能依赖require.resolve。3.3 后果问题延迟到 Windows 才爆发由于 Linux 上要求宽松、Windows 上要求严格实际发生的情况是代码中缺少require.resolve的问题在 Linux 上长期潜伏直到有人尝试在 Windows 上运行时才突然崩溃。这是本仓库开发流程中反复强调“一律使用require.resolve”的根本原因。仓库源码可以佐证这一约定已成为实践例如 packages/angular/build/src/tools/esbuild/javascript-transformer.ts 使用require.resolve(./javascript-transformer-worker)定位 worker 文件packages/angular/build/src/builders/dev-server/tests/setup.ts 通过require.resolve(angular/build/package.json)反查包根目录。这些用法正是为了保证在 Bazel 的 runfiles 布局尤其是 Windows 下中也能稳定解析。补充关于 Windows 开发环境docs/DEVELOPER.md 还额外建议在 Windows 上贡献代码时使用 WSLWindows Linux Subsystem并在 WSL 内按 Linux 流程操作但ngCLI 对终端用户的 Windows 支持仍然完整并持续经过测试。四、调试 jasmine_node_test从沙箱到断点4.1 关闭沙箱、找到中间产物在 Linux 上Bazel 测试默认在沙箱sandbox中运行以实现隔离。调试时需要关闭沙箱在规则上添加local True属性或在命令行强制本地执行并流式输出--test_outputstreamed。关闭沙箱后中间测试文件位于bazel-out/k8-fastbuild/bin目录下紧接着是测试目标路径即workspace_test/包路径/目标名结构具体以--symlink_prefixdist/配置下dist/bin的实际布局为准。4.2 分片sharding与 flaky 重试的坑shard_count分片如果测试被分片例如shard_count 4而你在本地减少或聚焦了测试用例如用fit/fdescribe聚焦部分分片可能被执行 0 个测试导致测试进程以错误码退出、整条命令失败。仓库中确实大量使用分片例如 packages/angular/build/BUILD.bazel 的shard_count 4以及 packages/angular_devkit/build_angular/BUILD.bazel 中根据LARGE_SPECS配置为不同规格测试分配shards数量。flaky True重试标记为 flaky 的测试失败后会自动重跑。由于聚焦测试focused tests会让 jasmine 以非零码退出被标记 flaky 的测试会被反复重跑造成大量无意义输出。仓库中也有多处flaky True的真实用例见 packages/angular/build/BUILD.bazel 与 packages/angular/ssr/test/BUILD.bazel。调试期间的两种对策在 BUILD 文件中临时移除shard_count取消分片与flaky取消重试属性或完全通过命令行参数解决--test_outputstreamed会禁用分片--flaky_test_attempts1会禁用 flaky 测试的重跑。命令行方案的好处是不需要改动 BUILD 文件也便于与--configdebug组合使用见下节。此外仓库根目录 .bazelrc 中已经预置了test:no-sharding配置--flaky_test_attempts1 --test_sharding_strategydisabled其注释明确指出这是为配合fit/fdescribe聚焦调试而准备的。4.3 远程调试配置--configdebug仓库在 .bazelrc 中预置了完整的调试配置段test:debug --test_arg--node_options--inspect-brk --test_outputstreamed --test_strategyexclusive --test_timeout9999 --nocache_test_results test:no-sharding --flaky_test_attempts1 --test_sharding_strategydisabledtest:debug的含义逐项拆解参数作用--test_arg--node_options--inspect-brk让测试进程以--inspect-brk启动 Node等待调试器附加--test_outputstreamed流式输出测试日志同时禁用分片--test_strategyexclusive独占执行避免并行干扰断点--test_timeout9999测试不会因超时被杀死--nocache_test_results强制真实重跑不命中缓存结果于是针对 CLI 包测试目标的调试命令为# 使用 --configdebug 启动远程调试会暂停等待调试器附加 pnpm bazel test --configdebug //packages/angular/cli:angular-cli_test # 同时禁用被标记为 flaky 的测试的重跑 pnpm bazel test --configdebug --configno-sharding //packages/angular/cli:angular-cli_test第二条命令组合了两个配置--configdebug负责断点与流式输出--configno-sharding通过--flaky_test_attempts1关闭 flaky 重试此时 sharding 已被 streamed 关闭no-sharding 主要起兜底作用。命令中的目标名//packages/angular/cli:angular-cli_test对应 packages/angular/cli/BUILD.bazel 中的jasmine_test(name test, ...)——注意实际目标名是test仓库文档与命令示例中的angular-cli_test为历史/示例命名以你仓库中pnpm bazel query tests(//packages/...)的输出为准。4.4 jasmine_test 封装与 Chrome 断点体验仓库通过 tools/defaults.bzl 中的jasmine_test封装了aspect_rules_jasmine的规则它会自动注入CHROME_BIN/CHROME_PATH/CHROMEDRIVER_BIN环境变量与 Chromium 工具链供涉及浏览器的测试使用并固定传入--require../node_modules/source-map-support/register.js和**/*spec.{js,mjs,cjs}的测试匹配参数。调试时启动--configdebug后Node 会暂停在--inspect-brk断点你可以在 IDE 的 Node 调试配置中附加到chrome://inspect列出的测试进程或使用debugger;语句在感兴趣的源码处设置硬断点CLI 会动态require()文件预先设断点不一定可靠这是 docs/DEVELOPER.md 推荐的兜底方案。五、快速自查清单在向仓库提交涉及 Bazel 的改动前对照 docs/process/bazel.md 的要求检查新增 NPM 依赖是否同时写入了根package.jsonBazel 解析用若是运行时依赖子包package.json是否也同步声明NPM 发布用Windows 兼容Bazel 目标内所有 Node 文件查找是否都走了require.resolve不要依赖 Linux 上符号链接带来的“恰好能用”。调试本地聚焦测试是否记得用--configno-sharding或--flaky_test_attempts1关闭 flaky 重跑避免聚焦测试被反复执行是否用--test_outputstreamed关闭分片修改 BUILD 属性不要忘记local True只应在调试时临时使用shard_count/flaky的移除应在上交前还原。六、相关阅读构建与测试总览docs/DEVELOPER.md发布流程中的 Bazel 打标签stamp配置docs/process/release.mdBazel 全局构建/测试配置.bazelrc依赖锁定与规则声明MODULE.bazel、pnpm-workspace.yaml、package.json测试规则封装tools/defaults.bzl包 BUILD 示例packages/angular/cli/BUILD.bazel【免费下载链接】angular-cliCLI tool for Angular项目地址: https://gitcode.com/gh_mirrors/an/angular-cli创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考