Angular 仓库 Public API 契约全解析:受支持 npm 包、兼容边界与 Golden 文件守护机制
本指南以 angular/angular 仓库的公共 API 面(Public API Surface)约定为主体,系统梳理哪些 npm 包与导出符号被纳入 SemVer 版本语义与弃用(deprecation)策略、哪些实现细节被明确排除在兼容承诺之外,并深入讲解仓库如何通过 Golden 文件与 Public API Guard 在 CI 中强制约束任何破坏性变更。阅读本文后,你将能准确判断「什么可以安全 import、什么绝不能依赖」,并掌握向该仓库贡献 API 变更时正确接受 Golden 文件的标准流程。
为什么需要一份「公共 API 面」清单
angular/angular 是一个长期演进、拥有数亿应用依赖的框架仓库。在持续的 minor / patch 发布中,如果某个看似无害的内部符号被应用直接 import,一旦重构就可能造成无法预料的破坏。因此,该仓库通过一份明确清单回答「哪些内容受到 SemVer 承诺保护」,凡是未列入承诺范围的实现细节,都允许在任何时刻被调整或删除,而无需等待 major 版本。
这份清单即本指南所依据的仓库文档 contributing-docs/public-api-surface.md,它与仓库实际构建配置、Golden 测试目标一一对应,可以直接在源码中验证。
受 SemVer 承诺保护的 npm 包
Angular 的语义化版本(SemVer)、发布节奏与弃用策略,仅适用于下列 npm 包:
| 包名 | 仓库目录 | 用途 |
|---|---|---|
@angular/animations |
packages/animations | 动画 DSL 与浏览器实现 |
@angular/common |
packages/common | 通用指令、管道与 HTTP 客户端 |
@angular/core |
packages/core | 框架核心:DI、变更检测、信号、渲染 |
@angular/elements |
packages/elements | 将 Angular 组件封装为 Web Components |
@angular/forms |
packages/forms | 模板驱动与响应式表单 |
@angular/platform-browser-dynamic |
packages/platform-browser-dynamic | 基于 JIT 编译器的浏览器引导 |
@angular/platform-browser |
packages/platform-browser | 浏览器平台实现 |
@angular/platform-server |
packages/platform-server | 服务端渲染(SSR) |
@angular/router |
packages/router | 路由与导航 |
@angular/service-worker |
packages/service-worker | PWA 与离线缓存 |
@angular/upgrade |
packages/upgrade | AngularJS 迁移桥接 |
以上每个包在仓库内都对应一个独立的 Bazel 包与 ng_package 产物,其中多数同时部署了对应的 API Golden 测试,这一点在下文会结合具体 BUILD 文件印证。
被明确排除的 @angular/compiler 与受限的 @angular/compiler-cli
@angular/compiler被显式排除在承诺列表之外,整体视为私有 / 内部 API,可能随时变化。仅极少数特殊场景(如 linter 或 IDE 集成)需要直接访问编译器 API;若确实在做此类集成,官方要求先联系 Angular 团队再使用,避免把你的工具建立在随时可能变更的实现上。@angular/compiler-cli本身虽不在表格中,但它的命令行用法(command line usage)受到覆盖,而其 API 的直接调用方式不在覆盖范围内。也就是说,通过 Angular CLI /ngc命令行执行编译是被支持的,绕过命令行直接编程调用其内部接口则不受兼容性保护。
从源码结构看,packages/compiler-cli 同时存在 index.ts 与庞大的 src/、test/ 目录,属于典型的工具链内部实现;goldens/public-api/compiler-cli 目录中确实保留有 golden 产物,用于守护 CLI 层对外暴露的类型。
稳定 API 的判定边界:入口点与全局命名空间
在上述受支持包内部,Angular 保持稳定的内容分为两类:
- 主入口点导出的符号,例如
@angular/core;以及 testing 入口点导出的符号,例如@angular/core/testing。该承诺同时适用于运行时 / JavaScript 值与 TypeScript 类型。 - 通过全局命名空间
ng导出的符号(如ng.core),这类全局符号主要服务于非打包的脚本加载场景。
值得注意的是,goldens/public-api/core/index.api.md 的 Golden 文件头部明确标注「Do not edit this file. It is a report generated by API Extractor」,而 goldens/public-api/core/global_utils.api.md 则守护全局工具函数,可见这两类稳定面在 CI 中都有独立的持久化产物与之对应。
边界规则背后的可执行载体
在仓库中,「入口点」概念并不是抽象口号,而是直接体现在 packages/core/BUILD.bazel 等文件的 api_golden_test_npm_package 目标上。例如 core 包的目标定义中声明了 golden_dir = "goldens/public-api/core" 与 npm_package = "packages/core/npm_package",表示该测试直接以打包后的 npm 产物的 .d.ts 声明文件为输入来生成 API 报告并与 Golden 比对。
明确排除在 Public API 之外的内容
仓库文档逐条列举了「不要当作稳定 API 使用」的清单,任何依赖下列内容的代码都可能在 minor 版本中无声破裂:
- 除
/、/testing、/bundles/*及其他有文档记录的入口点之外的任何文件 / import 路径。换言之,直接import到@angular/core/src/...这类内部深路径不受任何保护。 - 可注入类(服务与指令)的构造函数。获取这类实例必须通过依赖注入(Dependency Injection),不要自行
new。 - 标记为
private、或以下划线(_)、单重拉丁 o(ɵ)、双重拉丁 o(ɵɵ)前缀的类成员与符号。前缀ɵ与ɵɵ正是 Angular 压缩产物中用于标记私有内部符号的专用字符。Golden 生成过程中亦有对应体现,例如 packages/core/BUILD.bazel 中的strip_export_pattern = "^ɵ(?!.*ApiGuard)",表示导出名以ɵ开头的符号通常会被从 API 报告中剥离(仅对ApiGuard相关私有导出做白名单豁免)。 - 继承(extend)任何 Angular 类,除非 API 参考文档明确声明支持该继承——详见下文「Final 类」约定。
- Angular 编译器生成代码的内容与 API 面(框架输出到应用产物中的那部分代码不属于公共契约)。
@angular/core/primitives包及其全部后代入口点。这对应仓库内 packages/core/primitives 目录下按域拆分的多组实现(如 signals、di、dom-navigation 等),它们是内部原语,仅通过 core 聚合后再对外暴露稳定接口。
同行依赖:不在 API 面内,但在 SemVer 策略内
TypeScript、Zone.js、RxJS 等 peer dependencies(同行依赖) 不被视为 Angular API 面的一部分,但它们被纳入 SemVer 版本策略进行管理:
- 如果升级某个 peer dependency 的必需版本不会对 Angular 应用造成破坏性变更,则允许放在 minor 发布中一并推进;
- 如果升级会引入非平凡的破坏性变更,则必须推迟到 major 版本再执行。
这一条对框架使用者的实际意义在于:你应该把 Angular 版本与关键同行依赖的版本约束看作一套需要联动评估的组合,而不是各自独立的版本号。
Final 类约定:Angular 类的可扩展性边界
文档中单列一节强调:Angular 公共 API 中的所有类都被视为 final,除非 API 文档中显式声明,否则不应被继承。
原因很直接:这些类的 protected 成员与内部实现可能在任何非 major 版本中调整。一旦你的业务类继承了某个 Angular 类,而 Angular 在下个 minor 版本中重排了内部字段布局,你的派生类就会在不被通知的情况下编译失败或运行时行为异常。
判断某个类是否允许继承的唯一依据是 API 参考文档中的显式说明,而不是「看起来可以 extends」。这也是为什么 API Extractor 生成的 Golden 报告(如 goldens/public-api/core/index.api.md 中每个 @public 声明)会成为此类约定的仲裁证据。
Golden 文件:用 diff 锁定每一个公开变更
Golden 文件的含义
Angular 使用 Golden 文件 追踪受支持包的公共 API 现状,并由一个名为 Public API Guard 的工具来维护。每份 Golden 都是对包内 .d.ts 声明文件的文本化快照(典型如 core.d.ts),经工具规范化后落盘到 goldens/public-api 目录。
仓库的 Golden 目录结构与受支持包一一对应,读者可自行核对:
- core、common、router、forms、animations、elements、platform-browser、platform-browser-dynamic、platform-server、service-worker、upgrade、localize 各自拥有目录;
- 顶层 goldens/public-api/manage.js 提供跨目录管理的辅助逻辑;
- 部分包目录内含
errors.api.md(错误类型面)与testing/、http/、upgrade/等子入口点的独立报告。
CI 中的拦截机制
一旦你在某个受支持公共包中改动任何一部分公共 API,Pull Request 就会在 CI 中失败一项测试,失败信息会提示你接受(accept)新的 Golden 文件。从 packages/core/BUILD.bazel 可以看到,每个核心包都声明了多组守护目标:
core_api:使用api_golden_test_npm_package守护主 npm 包的完整导出面;ng_global_utils_api:守护全局ng工具函数的单独报告;core_errors:守护错误类型的公共面。
这些目标的公共宏定义源自 tools/defaults.bzl,其中 api_golden_test 与 api_golden_test_npm_package 均从 @devinfra//bazel/api-golden 规则集再导出,是仓库统一接入 Golden 测试的入口。
接受新 Golden 的命令
Public API Guard 为每个包提供了 Bazel 目标用于更新该包当前 API 状态。修改或新增任何公共 API 之后,必须在终端(建议使用较新版本的 bash)中通过 pnpm 执行相应的 Bazel 目标:
pnpm bazel run //packages/<modified_package>:<modified_package>_api.accept
例如修改的是 @angular/core 的公共导出,则应执行:
pnpm bazel run //packages/core:core_api.accept
该命令会把当前生成的 API 报告覆盖到对应的 Golden 文件,使后续 CI 中的 Golden 对比测试转绿。
一次真实的 CI 失败剖析
文档中给出了一次由「向 core.d.ts 中某个公共属性的合法类型集合新增类型」引发的 CI 失败样例。API Guard 的错误信息使用 git-diff 的组合 diff 格式输出,关键段落解读如下:
--- goldens/public-api/core/core.d.ts Golden file
+++ goldens/public-api/core/core.d.ts Generated API
@@ -563,9 +563,9 @@
ngModule: Type<T>;
providers?: Provider[];
}
-export declare type NgIterable<T> = Array<T> | Iterable<T>;
+export declare type NgIterable<T> = Iterable<T>;
export declare interface NgModule {
-行表示 Golden 中记录的既有状态;+行表示 本次改动后实际生成的 API 状态;- 中间的
@@头部指明变更发生的声明位置。
这类输出把「契约与现状的偏差」精确到符号级,既可用于 code review,也可作为回归测试的机器可读输入。修复方式即执行上文给出的 accept 目标,让 Golden 反映有意为之的新 API,随后将更新后的 Golden 文件随 PR 一并提交。
对框架使用者的实践建议
基于以上契约,给直接使用 Angular 的开发者几条可操作的原则:
- 只从受文档保护的入口点导入。
@angular/*根入口、/testing后缀入口以及有文档记录的公共入口之外的深路径都视为私有;@angular/core/primitives及其子路径尤其不要直接引用。 - 绝不手写
new服务或指令,一律通过构造器参数注入或inject()获取实例;不要在类外部调用被注入类的构造器。 - 远离
ɵ与ɵɵ前缀符号。它们在产物中出现时是框架内部的正常压缩产物,应用代码不应感知或依赖它们。 - 除非 API 参考文档白纸黑字允许,否则不要 extends 任何 Angular 类;尽量采用组合、接口与 DI 机制实现扩展需求。
- 把版本升级当作组合评估:Angular minor 版本可能顺带提升 TypeScript / Zone.js / RxJS 的下限版本,升级 Angular 时应同步阅读其 peer dependency 约束。
- 若你是库作者或贡献者:修改公共 API 后必须运行
pnpm bazel run //packages/<modified_package>:<modified_package>_api.accept接受新 Golden,并让更新后的 goldens/public-api 文件随 PR 提交,CI 才会放行。
结语
「Supported public API surface」本质上是 Angular 对自己社区的一份兼容性宪法:它在 contributing-docs/public-api-surface.md 中写明承诺范围,又用 goldens/public-api 目录下的持久化快照与散落在各包 BUILD.bazel 中的 Guard 目标把它变成每次 CI 都会执行的硬约束。理解这份契约,既能帮助普通开发者写出经得起版本升级的应用代码,也能帮助贡献者以正确姿势向框架提交 API 演进。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00