- 数据可视化
- 机器学习
- 前端
- 后端
【免费下载链接】tensorboard
TensorFlow's Visualization Toolkit
导读
本文围绕 TensorBoard 仓库中 patches/README.md 所描述的核心主题展开:TensorBoard 如何利用patch-package对其 npm/yarn 依赖(如@bazel/concatjs、@angular/build-tooling、protobuf 与 rules_closure 等 Bazel 依赖)制作项目专属补丁,并在 Bazel 构建期通过yarn_install(post_install_patches = ...)直接应用这些补丁文件。读完本文,你将掌握 TensorBoard 的补丁制作流程、五个补丁各自解决的问题与修改的源文件、重新生成补丁的完整命令序列,以及补丁文件的格式规范(如行尾空白检查与--exclude参数陷阱),能够在自己的 Bazel + rules_nodejs 项目中复刻这套可维护的依赖补丁体系。
一、背景:为什么 TensorBoard 需要给第三方依赖打补丁
TensorBoard 是一个依赖链条极深的大型前端 + 后端项目:Angular 前端通过 Bazel + rules_nodejs 构建,同时大量使用 protobuf、Closure Templates(Soy)等工具链。当上游依赖的行为与 TensorBoard 的构建方式不兼容时,TensorBoard 不能等待上游修复,而是通过patch-package对node_modules中的依赖源码直接打补丁,并把补丁文件作为一等公民提交进仓库。
patch-package是一款 npm 生态工具,允许开发者在
node_modules中直接修改第三方包源码,然后生成统一的.patch文件;后续每次执行yarn install/npm install时,它会自动把补丁重新应用到新安装的依赖上。
在 TensorBoard 中,这些补丁被统一存放在仓库根目录的 patches/ 目录下,目前包含五个补丁文件:
| 补丁文件 | 针对的依赖 | 解决的问题领域 |
|---|---|---|
@bazel+concatjs+5.8.1.patch | @bazel/concatjs(rules_nodejs 生态的 TS/Angular 编译规则) | Angular 21 的.d.ts解析、Ivy 产物声明、TypeScript 沙箱缺失 |
@angular+build-tooling+0.0.0-98b30ab5fdeeb1df3278f5257b9a8f07abb76941.patch | @angular/build-tooling | 禁用markTopLevelPure优化插件,修复打包后空白页 |
protobuf_6_31_1_java_export.patch | protobuf 6.31.1 的 Java 导出规则 | 适配新版 rules_java/protobuf 栈的 javadocopts 与路径规范化检查 |
rules_cc_protobuf.patch | rules_cc 的cc/defs.bzl | 重新导出cc_proto_library符号 |
rules_closure_soy_cli.patch | rules_closure 的 Soy 调用 | 适配当前编译器版本的 CLI 参数 |
二、构建期集成:post_install_patches与 WORKSPACE 配置
一个值得注意的设计决策是:TensorBoard 并不在yarn install时通过patch-package自动应用补丁,而是在 Bazel 的WORKSPACE文件中通过yarn_install(post_install_patches = ...)直接应用已生成的补丁文件。
查看仓库根目录的 WORKSPACE 可以看到关键配置:
yarn_install( name = "npm", exports_directories_only = False, # ts_library 等规则需要访问包内部嵌套 label package_json = "//:package.json", package_json_remove = ["scripts.postinstall"], # 移除包自身的 postinstall 脚本 patch_args = ["-p1"], # 仍用 patch-package 撰写补丁,但在仓库规则内直接应用生成的补丁工件, # 而不是在安装时调用 patch-package post_install_patches = [ "//patches:@angular+build-tooling+0.0.0-98b30ab5fdeeb1df3278f5257b9a8f07abb76941.patch", "//patches:@bazel+concatjs+5.8.1.patch", ], yarn_lock = "//:yarn.lock", )README 明确指出这种设计的原因:在当前 Bazel/CI 环境下,在安装期调用 patch-package 的可靠性不如直接应用生成的补丁文件。这正是把"撰写补丁"与"应用补丁"解耦的工程实践——补丁的撰写仍用 patch-package 完成(保证 diff 格式正确、路径规范),而补丁的应用交给 Bazel 仓库规则完成(保证构建可复现、可缓存)。
post_install_patches列表中只出现两个 npm 依赖补丁(@angular/build-tooling与@bazel/concatjs),其余三个补丁(protobuf、rules_cc、rules_closure)则通过http_archive等仓库规则的patches参数应用到 Bazel 外部仓库,例如 WORKSPACE 中对io_bazel_rules_closure的声明:
http_archive( name = "io_bazel_rules_closure", patch_args = ["-p1"], patches = ["//patches:rules_closure_soy_cli.patch"], ... )三、补丁文件的格式规范与维护红线
3.1 行尾空白检查
补丁文件是 diff 格式的文本,其格式与普通源码不同。TensorBoard 的 CI 会运行 tensorboard/tools/whitespace_hygiene_test.py 检查所有文件的行尾空白,但补丁文件有特殊豁免——该脚本的exceptions集合中列出了两个补丁文件:
exceptions = frozenset( [ "patches/protobuf_6_31_1_java_export.patch", "patches/@bazel+concatjs+5.8.1.patch", ] )脚本注释解释了原因:补丁文件中的空行使用尾随空格标记上下文行,这是 patch 格式的硬性要求,不能移除(见 whitespace_hygiene_test.py)。同时,脚本还会检查exceptions列表中是否存在"过期豁免"(即不再有行尾空白问题的文件),提示维护者清理。
不过 README 也强调:创建或更新补丁后,要确保除这些格式必需的尾随空格外,任何行都没有多余的尾随空白。如果手头补丁带有非格式要求的尾随空白,可以用以下命令清洗(macOS 下sed -i需要空参数):
sed -i '' 's/[[:space:]]*$//' patches/<patch-file>.patch3.2--exclude陷阱:不要漏掉 package.json 的改动
这是 README 标注为Important的一条关键注意事项:
patch-package默认使用--exclude '/package\.json$/',因此直接执行yarn patch-package "<pkg>"会静默丢弃对包package.json的任何修改。
也就是说,如果补丁需要修改依赖的package.json(例如@bazel/concatjs补丁就在 package.json 中新增了typescript直接依赖),必须显式传入:
yarn patch-package "<pkg>" --exclude '^$'--exclude '^$'将排除模式设为"不匹配任何路径",从而允许 package.json 进入补丁。此外,每次重新生成补丁后都要检查git diff patches/,确认没有 hunk 被静默丢弃。
四、补丁详解一:@bazel+concatjs+5.8.1.patch
这是 TensorBoard 前端构建链中最核心的补丁,包含三个相互独立的修改,对应@bazel/concatjs的多个文件(见 补丁原文)。
4.1 修改文件清单
node_modules/@bazel/concatjs/internal/common/compilation.bzlnode_modules/@bazel/concatjs/internal/common/tsconfig.bzlnode_modules/@bazel/concatjs/package.json
4.2 修改 1:停止声明*.ngfactory.*/*.ngsummary.*产物
在compilation.bzl的_outputs函数中,原逻辑在use_angular_plugin = True时额外声明*.ngfactory.mjs、*.ngsummary.mjs等产物。但Ivy(Angular 的新编译管线)已不再生成这些文件,导致 Bazel 报错declared output was not created(声明的输出未被创建)。
补丁将这些声明代码整体删除,使 Bazel 的产物声明与实际编译行为一致。从 补丁的 diff 可以看到,被删除的代码块带有TODO(alexeagle): clean up after Ivy launch注释——这是上游留下的清理标记,TensorBoard 通过补丁提前完成了这项工作。
4.3 修改 2:为 Angular/Material/CDK/NgRx 入口点补充module_roots映射
这是补丁中最大的一块改动。从Angular 21 开始,APF(Angular Package Format)打包方式发生变化:各包的.d.ts类型定义文件被集中到每个包的types/<name>.d.ts目录下,且只通过package.json的"exports"字段对外暴露。而 Bazel 的node_modules路径映射无法解析"exports",导致 TypeScript 编译找不到类型定义。
补丁在tsconfig.bzl的create_tsconfig中为以下主包添加了module_roots映射:
@angular/cdk、@angular/common、@angular/core、@angular/material、@angular/platform-browser、@angular/platform-browser-dynamic
每个主包映射到types/*.d.ts通配符路径;同时为一批测试入口点做了精确映射(见 补丁的映射表):
@angular/cdk/testing/testbed→testing-testbed@angular/common/http/testing→http-testing@angular/material/checkbox/testing→checkbox-testing@angular/material/chips/testing→chips-testing@angular/material/core/testing→core-testing@angular/material/dialog/testing→dialog-testing@angular/material/form-field/testing/control→form-field-testing-control@angular/material/icon/testing→icon-testing@angular/material/menu/testing→menu-testing@angular/material/select/testing→select-testing@ngrx/store/testing→ngrx-store-testing(注意:NgRx 使用包名前缀的类型文件名,与 Angular 的types/<entry-point>.d.ts命名不同)@ngrx/effects/testing→ngrx-effects-testing
README 还专门解释了为什么不能把这些映射放进工作区根目录的tsconfig.json:Bazel 生成的 tsconfig 虽然会extends工作区 tsconfig,但它会写入自己的compilerOptions.paths,而TypeScript 对paths是整体替换而不是合并,因此工作区配置中的映射会被覆盖。映射必须存在于 Bazel 生成的 tsconfig 中,这正是需要 patchtsconfig.bzl的原因。
4.4 修改 3:为package.json增加typescript直接依赖
@bazel/concatjs原先把typescript放在peerDependencies中。在 Bazel 沙箱环境下,typescript无法被自动找到,因此补丁将其提升为dependencies中的直接依赖:
"dependencies": { "protobufjs": "6.8.8", "source-map-support": "0.5.9", "tsutils": "3.21.0", "typescript": "5.9.3" }(见 补丁的 package.json hunk)
4.5 为什么是 5.8.1 而不是 6.x?
README 给出了明确解释:rules_nodejs 6.x 移除了 TensorBoard 依赖的大部分构建规则(concatjs、esbuild、typescript 等),将它们迁移到了独立的 rules_js 项目中。这是一项需要专门升级投入的工作,因此 TensorBoard 停留在 rules_nodejs 5.8.1,并将完整迁移留待未来版本。
4.6 移除计划
@bazel/concatjs5.8.1 是它的最后发布版本,且 rules_nodejs 已被归档,上游不会再有修复。README 披露的近期计划是:把tsconfig.bzl的映射和compilation.bzl的产物覆盖逻辑迁移到 TensorBoard 自有的ts_library规则(位于 tensorboard/defs 下),复用 concatjs 的compile_ts而不再打补丁(见 tsconfig.bzl hunk 中的 TODO)。
五、补丁详解二:@angular+build-tooling+*.patch
5.1 修改文件与作用
该补丁只修改一个文件:node_modules/@angular/build-tooling/shared-scripts/angular-optimization/esbuild-plugin.mjs。其作用是禁用markTopLevelPure优化插件。
markTopLevelPure插件会剔除(cull)被判定为"纯副作用无关"的顶层函数调用。但 TensorBoard 的部分代码在运行时依赖这些顶层调用,被剔除后应用打包结果表现为空白页面且控制台无任何报错——这是极难排查的静默故障。补丁将该插件的挂载代码整体注释掉(见 补丁原文),代价是最终产物体积变大(优化机会减少)。
5.2 文件名追踪固定 commit 的注意事项
补丁文件名中的0.0.0-98b30ab5fdeeb1df3278f5257b9a8f07abb76941是@angular/build-tooling被钉住的commit 哈希。因此,每当该依赖升级时,补丁文件必须重命名(并在 WORKSPACE 中同步更新引用)。这是依赖补丁维护中的一个隐含约定:文件名本身即版本标识。
5.3 移除计划
与 concatjs 补丁一样,该补丁的移除被提上日程。@angular/build-tooling上游已冻结,两个补丁只有在 TensorBoard 前端构建完全迁移出 rules_nodejs 之后才会一起消失。
六、补丁详解三:protobuf、rules_cc 与 rules_closure 补丁
这三个补丁面向 Bazel 外部仓库(通过http_archive的patches参数应用),属于 protobuf 6.31.1 升级后的兼容性修复。
6.1protobuf_6_31_1_java_export.patch
修改两个文件(见 补丁原文):
build_defs/java_opts.bzl:删除protobuf_java_export中旧的javadocopts变通方案(原先针对rules_jvm_external的 issue #1245)。在新版 rules_java/protobuf 栈上不再需要。bazel/private/proto_library_rule.bzl:新增_is_normalized辅助函数,放宽import_prefix/strip_import_prefix的路径规范化检查——允许空字符串作为已规范化值继续工作,以适应新版路径处理逻辑:
def _is_normalized(path): return path == "" or paths.normalize(path) == path6.2rules_cc_protobuf.patch
只修改cc/defs.bzl一个文件(见 补丁原文):从 protobuf 的 Bazel 定义中重新导出cc_proto_library:
load("@com_google_protobuf//bazel:cc_proto_library.bzl", _protobuf_cc_proto_library = "cc_proto_library") cc_proto_library = _protobuf_cc_proto_library这样,本仓库的调用方可以继续通过rules_cc加载cc_proto_library符号,同时底层实际使用 protobuf 6.31.1 的仓库布局,保持源码兼容。
6.3rules_closure_soy_cli.patch
只修改closure/templates/closure_java_template_library.bzl一个文件(见 补丁原文),适配当前仓库使用的编译器/Java 组合:
- 依赖列表 flag 从
--deps改为--depHeaders(当前 Soy 编译器期望的参数名); - 删除旧的
--allowExternalCallsflag(当前编译器不接受该参数)。
同时,WORKSPACE 中通过 local_repository 引入了一个本地修改版的safe_html_types(位于 third_party/safe_html_types),因为 rules_closure 的 Soy 工具链仍期望与 protobuf-java 6.x 兼容的类,TensorBoard 需要的是经过调整的版本而非任意上游 release——这是与 Soy 工具链兼容的配套措施。
七、补丁的重新生成工作流
7.1@bazel/concatjs补丁的重生成
README 给出的完整操作序列(其中--exclude '^$'是必需的,否则 package.json 的 hunk 会被静默丢弃):
# 1. 编辑依赖源码 vi node_modules/@bazel/concatjs/internal/common/compilation.bzl vi node_modules/@bazel/concatjs/internal/common/tsconfig.bzl vi node_modules/@bazel/concatjs/package.json # 2. 做出修改后重新生成补丁(注意 --exclude 参数) yarn patch-package "@bazel/concatjs" --exclude '^$' # 3. 用新补丁文件名更新 WORKSPACE 中的引用7.2@angular/build-tooling补丁的重生成
# 1. 编辑依赖源码 vi node_modules/@angular/build-tooling/shared-scripts/angular-optimization/esbuild-plugin.mjs # 2. 生成补丁 yarn patch-package "@angular/build-tooling" # 3. 用新补丁文件名更新 WORKSPACE 引用7.3 每次重生成后的统一检查清单
- 检查
git diff patches/,确认没有任何 hunk 消失(特别是 package.json 的改动); - 检查补丁行尾是否有多余的空白(可通过 whitespace_hygiene_test.py 或
sed命令处理); - 若依赖版本号变化导致补丁文件名含版本/commit 标识,同步重命名文件并更新 WORKSPACE 中的引用。
八、从源码结构看补丁体系的工程启示
TensorBoard 的补丁体系体现了三个值得借鉴的工程决策:
- 撰写与应用解耦:补丁仍用 patch-package 生态撰写(格式标准、路径规范),但应用时机从
yarn install的 postinstall 移到 Bazel 仓库规则(post_install_patches/patches参数),换来构建期的确定性与 CI 可靠性; - 每个补丁都要有"移除计划":README 为每个补丁都标注了上游状态(已归档、已冻结、不再修复)与迁移路径(如迁往 tensorboard/defs 的
ts_library规则、整体迁出 rules_nodejs),避免补丁永久化; - 文件名承载版本信息:含 commit 哈希的补丁名让版本升级必须显式重命名,防止静默失配。
对于同样基于 Bazel + rules_nodejs 构建 Angular 应用的团队,这套patches/ + WORKSPACE的补丁管理方式可以直接迁移复用:把补丁作为仓库资产提交、在yarn_install中声明post_install_patches、并为每个补丁保留一份如上所述的"修改文件 / 作用 / 重生成步骤 / 移除计划"文档,可以让依赖适配工作变得透明、可审计、可回归。
- 数据可视化
- 机器学习
- 前端
- 后端
【免费下载链接】tensorboard
TensorFlow's Visualization Toolkit
相关推荐
patch-package与npm scripts:集成自定义补丁生命周期钩子
patch package与npm scripts:集成自定义补丁生命周期钩子 你是否曾在项目中遇到依赖包的紧急bug,却因等待官方修复而停滞开发?是否想过在依
开发工具patch-package与npm 7+工作区:多包项目中的依赖补丁隔离策略
patch package与npm 7+工作区:多包项目中的依赖补丁隔离策略 在现代前端工程化体系中,多包项目(Monorepo)已成为复杂应用的主流组织方式。
开发工具深入理解patch-package工作原理:如何优雅管理npm依赖补丁
深入理解patch package工作原理:如何优雅管理npm依赖补丁 patch package是一个强大的npm包管理工具,专门用于修复node_modul
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考