Angular CDK 与 Angular Material 的 ng-update 迁移原理与实战指南 📅 发布时间:2026/9/12 12:19:08 👁 浏览次数: Angular CDK 与 Angular Material 的 ng-update 迁移原理与实战指南【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/componentsng-update schematic 是 Angular CDK以及以其为基础的 Angular Material提供的自动升级工具当开发者运行ng update升级到新的主版本时它能自动改写受破坏性变更breaking changes影响的 TypeScript、HTML 模板与样式文件并对无法自动迁移的变更给出明确提示。本文以本仓库 src/cdk/schematics/ng-update/update-schematic.md 为主干结合仓库源码深入讲解其多版本入口设计、update-tool迁移框架、升级数据upgrade data的组织方式以及如何为新的破坏性变更添加迁移数据与测试用例。一、ng-update 的整体架构1.1 多个迁移入口点Migration Entry-Pointsng-updateschematic 由多个迁移入口点构成每个入口点针对一个具体的 Angular CDK / Angular Material 主版本。根据原文档当前仓库维护着如下目标版本入口目标版本说明V6从任意版本升级到 v6.0.0V7从任意版本升级到 v7.0.0V8从任意版本升级到 v8.0.0V9从任意版本升级到 v9.0.0原文档写作时以 V6V9 为例本仓库当前代码中TargetVersion 枚举 已演进为只包含最新版本如V22而 Angular Material 侧对应提供了 updateToV22 入口并由createMigrationSchematicRule组装。历史的迁移规则仍按需保留以保证“从任意旧版本升级”的链路完整。1.2 迁移按顺序执行关键设计点是如果一次升级隐式跨越多个主版本迁移会按版本顺序依次运行。文档给出的典型场景假设应用正在使用 Angular Material v5.0.0开发者执行ng updateAngular CLI只安装最新的 V7然后会按顺序运行 V6 与 V7 的迁移。这意味着仓库中必须保留过去所有主版本的迁移代码CLI 通常只安装最新版包却期望旧版本对应的迁移全部在场。因此“所有历史迁移都保留在代码库中”是 ng-update 的硬性约束。顺序执行还有一个重要原因——升级数据的隔离。文档举了一个非常典型的例子在 V6 中onChange被重命名为changed在 V7 中changed被重命名为onValueChange如果迁移不按顺序执行、或升级数据不按版本隔离那么一个从 5.0.0 只想升到 6.0.0 的用户会错误地得到onValueChange因为非隔离的数据里只记录了onChange onValueChange这一条映射。按版本隔离数据 按顺序执行迁移才能让每个版本段的破坏性变更被正确处理。1.3 升级概念Update Conceptng-update 的目标是自动迁移受目标版本破坏性变更影响的代码。大多数破坏性变更可以通过升级数据自动改写但也存在少量无法自动迁移的变更——此时的目标是明确通知开发者该破坏性变更需要人工关注。这在源码中体现为MigrationFailure机制基类 Migration 中维护了failures: MigrationFailure[]并通过createFailureAtNode(node, message)在指定节点位置记录失败信息包含文件路径、行/列位置与消息供上层汇总并报告给开发者。二、update-toolTypeScript 文件的转换框架2.1 为什么放弃 tslint 自研 update-tool为了自动迁移 TypeScript 源文件项目利用了 TypeScript Compiler API 解析并操作项目源文件的 AST并在此之上构建了一个名为update-tool的小型框架。之所以必须自研是因为最初的ng update实现基于tslint存在一系列严重问题不支持 HTML 模板与样式表只能靠 workaround 处理文件更新后会重跑全部升级 lint 规则对文件众多的项目性能损耗显著每次源文件更新都会重建 TypeScript program对大型 TypeScript 项目造成严重内存压力甚至引发 OOMtslint 会为每条升级规则递归访问所有源文件的节点性能差CLI 项目中不保证安装 tslint对应 angular-cli issue #14555tslint 的 replacement 因保留 TypeScript 节点导致内存泄漏lint 规则只能逐个访问源文件无法进行全局分析global analysis阶段灵活性不足例如无法保证源文件只被分析一次、无法实现进度条、难以扩展对 HTML 模板和样式表的支持。2.2 update-tool 相比 tslint 的关键改进文档列出了update-tool相比 tslint 的差异结合源码可以逐条印证文件系统抽象化迁移可编程化运行迁移既能在 CLI 中运行也能在 google3 内运行还能脱离ng update独立运行——对应 file-system.ts 与 devkit 适配层devkit-file-system.ts、devkit-migration-rule.ts原生支持 HTML 模板与样式表Migration基类提供了 visitTemplate / visitStylesheet 回调配合 component-resource-collector.ts 收集组件资源每个源文件只迁移一次即使该文件属于多个 TypeScript 项目也不会重复处理每个 TypeScript 项目只创建一次 programtype checker 也只获取一次迁移失败不会保留ts.Node实例避免 tslint 常见的内存泄漏替换操作在虚拟文件系统中进行schematics 的最佳实践通过 update-recorder.ts 实现TypeScript program 只被递归访问一次完全灵活例如可以实现进度条支持全局分析阶段Migration基类中的 init() 用于对 program 做全局分析postAnalysis() 在所有节点、模板和样式表访问完毕后调用——这是 tslint 无法实现的。2.3 备选方案及其评估文档也讨论了其他 TypeScript 转换思路及其结论方案评估结论正则表达式Regular Expressions过于脆弱无法做类型检查只能在配合真实 AST 遍历时局部使用TypeScript transforms不 emit思路不错但缺少把转换后的 AST 序列化回源码的 APIts.Printer虽可序列化却会破坏格式与代码风格对迁移而言不可接受因此update-tool采用“遍历 AST 在虚拟文件系统上做替换replacement”的方案兼顾了正确性与对原文件格式的保持。三、升级数据Upgrade Data按版本与代码类型组织3.1 双重隔离按目标版本 按受影响的代码类型升级数据首先按目标版本隔离这是顺序迁移的前提见 1.2 的onChange/changed/onValueChange例子。其次数据还按受影响的代码类型拆分。文档给出的参考是src/material/schematics/ng-update/material/data目录在原文档中为 GitHub 链接本仓库内对应 src/material/schematics/ng-update/data。本仓库中 CDK 侧的升级数据类型定义在 src/cdk/schematics/ng-update/data包含attribute-selectorsHTML 属性选择器重命名class-namesCSS 类名重命名constructor-checks构造函数签名变更检查css-selectorsCSS 选择器重命名css-tokensCSS token 变更element-selectors元素选择器重命名input-names输入属性重命名method-call-checks方法调用变更检查output-names输出事件重命名property-names属性重命名symbol-removal符号移除每种数据都对应 ng-update/migrations 下同名的一个迁移实现如input-names.ts、output-names.ts、property-names.ts等以及 typescript 下的辅助工具如imports.ts、literal.ts、module-specifiers.ts。Angular Material 侧通过 materialUpgradeData 把这些数据聚合为一个UpgradeData对象统一注入迁移规则。3.2 数据类型与版本的数据结构升级数据的数据结构定义在 version-changes.tsexport type VersionChangesT { [target in TargetVersion]?: ReadableChangeT[]; }; export type ReadableChangeT { pr: string; // 关联的 Pull Request 链接便于追溯破坏性变更 changes: T[]; // 该 PR 涉及的具体变更列表 };配套提供了两个读取辅助函数getChangesForTarget(target, data)取指定目标版本的变更并剔除pr字段、展平为易迭代的数组——文档注释说明pr链接是为了可读性和破坏性变更总览升级执行时并不需要getAllChanges(data)取所有版本的变更用于不区分目标版本的迁移规则但数据仍按版本分隔以保持可读性。3.3 从数据到迁移以属性重命名为例以 PropertyNamesMigration 为例看升级数据如何驱动迁移export class PropertyNamesMigration extends MigrationUpgradeData { data: PropertyNameUpgradeData[] getVersionUpgradeData(this, propertyNames); enabled this.data.length ! 0; // 没有数据时自动禁用该迁移 override visitNode(node: ts.Node): void { if (ts.isPropertyAccessExpression(node)) { this._visitPropertyAccessExpression(node); } } // ... }其核心逻辑是对每个属性访问表达式用 TypeScripttypeChecker解析宿主类型若为交叉类型则展开所有成员类型当属性名匹配data.replace且满足limitedTo.classes的类型限制时就在虚拟文件系统中删除旧名并插入新名。enabled this.data.length ! 0说明迁移是否启用取决于目标版本是否存在对应升级数据——这正是文档“迁移规则与升级数据解耦、按数据驱动”的体现。四、为破坏性变更添加升级数据实战文档强调在把破坏性变更合并进上游upstream之前添加升级数据是强制步骤。对于简单常见的破坏性变更通常已有对应的升级数据文件只需插入新条目即可如果某个破坏性变更没有现成数据则需要评估是写一个与该变更绑定的misc迁移还是新建一个接受升级数据的可配置迁移。4.1 属性重命名的完整示例文档场景在 Angular Material V7.0.0 中将MatRipple#color重命名为MatRipple#newColor。第一步找到现有的同类升级数据文件。属性重命名对应property-names数据文件在VersionTarget即TargetVersion.V7下插入新变更// src/material/schematics/ng-update/material/data/property-names.ts export const propertyNames: VersionChangesMaterialPropertyNameData { [TargetVersion.V7]: [ { pr: {PULL_REQUEST_LINK_FOR_BREAKING_CHANGE}, changes: [ { replace: color, replaceWith: newColor, limitedTo: { classes: [MatRipple] } } ] } ], // ... };各字段含义字段说明pr该破坏性变更对应的 Pull Request 链接占位符便于追溯replace需要被替换的旧名称如colorreplaceWith替换后的新名称如newColorlimitedTo.classes限定作用范围仅当宿主类型为MatRipple时才改写避免误伤同名属性数据插入后开发者升级到 Angular Material V7.0.0 时MatRipple#color就会被自动迁移为MatRipple#newColor。本仓库当前版本中该数据文件为 src/material/schematics/ng-update/data/property-names.ts结构上与示例一致。4.2 向已有测试用例添加破坏性变更为新增迁移数据补充测试用例是强烈推荐的。属性重命名场景已有property-names迁移的测试用例因此只需把新变更加入既有测试文件无需新建。输入文件property-names_input.ts会被 V7 迁移转换/** * Mock definitions. This test case does not have access to angular/material. */ class MatRipple { color: string; } class A implements OnInit { constructor(private a: MatRipple) {} ngOnInit() { this.a.color primary; } }期望输出文件property-names_expected_output.ts迁移后应与之一致/** * Mock definitions. This test case does not have access to angular/material. */ class MatRipple { color: string; } class A implements OnInit { constructor(private a: MatRipple) {} ngOnInit() { this.a.newColor primary; } }注意事项_input.ts只会被 V7 迁移转换然后与_expected_output.ts逐字比对。因此仍然有效的 mock 声明也必须保留在期望输出文件中——即 mock 的MatRipple类定义本身不会被改写它只是测试用的占位所以color: string会原样出现在期望输出里而使用处this.a.color则改写为this.a.newColor。文档中给出的测试用例位于src/material/schematics/ng-update/test-cases/v7/目录本仓库中测试的入口与数据对应关系见 src/material/schematics/ng-update/test-cases/index.spec.ts。五、总结ng-update 的设计要点多入口、按版本顺序迁移每个主版本一个迁移入口升级时按旧版本 → 新版本的顺序依次执行全部历史迁移必须保留在仓库中因为 CLI 通常只安装最新版本包。升级数据按目标版本 代码类型双重隔离这是顺序迁移正确性的前提也让每个版本段的破坏性变更互不干扰。update-tool 框架替代 tslint借助 TypeScript Compiler API实现“program 只创建一次、源码只遍历一次、替换在虚拟文件系统中完成、支持模板/样式表与全局分析阶段”的高性能迁移管线。数据驱动、可配置的迁移规则迁移是否启用由升级数据决定enabled data.length ! 0新增破坏性变更的标准流程是“插入升级数据 → 补充测试用例”。无法自动迁移的变更明确上报通过MigrationFailure机制把文件、位置与消息反馈给开发者确保破坏性变更不被静默遗漏。Angular CDK 的 ng-update 是 Angular Material ng-update 的基础这套“升级数据 迁移框架”的设计同样适用于任何希望为 Angular 生态提供可复用自动迁移能力的组件库或应用。【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考