ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Angular CLI builders 全解析:Architect 架构、自定义 Builder 开发与测试实战

Angular CLI builders 全解析:Architect 架构、自定义 Builder 开发与测试实战 Angular CLI builders 全解析Architect 架构、自定义 Builder 开发与测试实战【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular导读ng build、ng test、ng serve等 Angular CLI 命令看似简单背后却由一套名为Architect的内部调度器驱动它把具体任务委托给一个个名为builder构建器的处理函数再由 builder 调用打包器、测试运行器、开发服务器等第三方工具完成工作。本文以 Angular 官方文档为骨架结合仓库内的完整示例工程系统讲解 CLI builders 与angular.json工作区配置的协作方式并手把手演示如何编写、注册、运行与测试一个属于自己的 builder——掌握后你既能扩展全新任务也能替换现有命令背后的第三方工具实现对 Angular CLI 行为的深度定制。CLI builders 是什么Angular 的核心机制是命令使用内部工具 Architect 来运行 CLI builders由 builder 再去调用第三方工具bundler、test runner、server完成任务。Architect 会把工作委托给称为builder的处理函数。一个 builder 处理函数接收两个参数参数类型optionsJSONObjectcontextBuilderContext这里的职责分离思路与 schematics 编写指南 一致schematics 服务于ng generate这类改动代码的命令区别只在于二者面向的 CLI 命令场景不同options对象由 CLI 用户传入的选项与配置提供context对象则由 CLI Builder API 自动注入。context除了携带各种上下文信息外还提供了一个关键调度方法context.scheduleTarget()——调度器会以指定的 target 配置执行 builder 处理函数。Builder 处理函数可以是同步的直接返回值异步的返回一个Promise监听式返回一个Observable可以连续产出多个值。无论哪种形式返回值类型必须始终是BuilderOutput。该对象包含一个布尔类型的success字段以及一个可选的error字段用于携带错误信息。Angular 为ng build、ng test等命令提供了若干内建 builder。这些内建 builder 及其默认 target 配置都可以在 workspace 配置文件angular.json的architect一节中查看与修改。同时你也可以通过编写自定义 builder 来扩展 Angular并用ng run命令 直接运行它。Builder 项目结构一个 builder 存放在与 Angular 工作区结构相似的“项目”文件夹中顶层是全局配置文件src源码目录里则是定义行为的具体代码。例如你的myBuilder文件夹可能包含以下文件文件用途src/my-builder.tsbuilder 定义的主源码文件src/my-builder.spec.ts测试源码文件src/schema.jsonbuilder 输入选项的定义JSON Schemabuilders.jsonbuilders 定义文件package.json依赖声明tsconfig.jsonTypeScript 配置Builder 可以发布到 npm具体参考 发布你的库。在本仓库中官方配套的完整示例就存放在adev/src/content/examples/cli-builder下其中src/my-builder.ts与src/my-builder.spec.ts正是上面两个“src”文件的落地版本下文所有代码均以此仓库文件为准。创建一个 builder复制文件的完整示例以“把文件复制到新位置”的 builder 为例。创建 builder 需要调用createBuilder()函数并返回一个PromiseBuilderOutput对象。以下是骨架代码来自仓库 src/my-builder.ts 的builder-skeleton区段import {BuilderContext, BuilderOutput, createBuilder} from angular-devkit/architect; import {JsonObject} from angular-devkit/core; import {promises as fs} from fs; interface Options extends JsonObject { source: string; destination: string; } export default createBuilder(copyFileBuilder); async function copyFileBuilder(options: Options, context: BuilderContext): PromiseBuilderOutput { }要点拆解类型Options extends JsonObject显式声明了source与destination两个字符串输入与后续 schema 中的字段一一对应export default createBuilder(copyFileBuilder)把处理函数包装成标准的 builder 导出形式这里使用了 Node.jsfs/promises的copyFile()方法Promise 版本便于在异步函数中await。接下来为骨架填充逻辑同一文件的builder区段L21-L38async function copyFileBuilder(options: Options, context: BuilderContext): PromiseBuilderOutput { try { await fs.copyFile(options.source, options.destination); } catch (err) { return { success: false, error: (err as Error).message, }; } return {success: true}; }该代码从用户 options 中取出源文件与目标文件路径执行复制一旦复制失败就返回success: false并携带底层错误信息让上层能定位真正原因。处理输出用 Logger 记录日志默认情况下copyFile()不会向进程的标准输出或错误输出打印任何内容。若出错你很难判断 builder 当时在做什么。此时应使用Logger API补充上下文信息——即便标准输出/错误被关闭builder 也能在独立进程中正常执行。可以从context获取Logger实例handling-output区段L21-L33async function copyFileBuilder(options: Options, context: BuilderContext): PromiseBuilderOutput { try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error(Failed to copy file.); return { success: false, error: (err as Error).message, }; } return {success: true}; }context.logger.error(...)会在失败时向日志流输出明确的原因提示而不是静默失败。实践中还可使用context.logger.info()、context.logger.warn()等其它级别按需组合成更可读的构建输出。进度与状态上报CLI Builder API 还内置了进度与状态上报工具可为特定函数与界面提供提示。进度上报使用context.reportProgress()参数为当前值、可选的总量、状态字符串。总量可以是任意数字若你知道要处理多少文件可把total设为文件数、current设为已处理数。状态字符串只有传入新值才会被更新。状态上报使用context.reportStatus()可生成任意长度的状态字符串。需要注意长字符串并不保证被完整展示——它可能被 UI 截断以适应显示区域传空字符串则可移除当前状态。在本例中复制操作要么完成、要么仍在执行没有分步进度可言所以无需reportProgress()但可以上报状态让父级 builder 知道当前发生着什么。仓库示例在复制前与完成后分别上报了两次状态progress-reporting区段L18-L39async function copyFileBuilder(options: Options, context: BuilderContext): PromiseBuilderOutput { context.reportStatus(Copying ${options.source} to ${options.destination}.); try { await fs.copyFile(options.source, options.destination); } catch (err) { context.logger.error(Failed to copy file.); return { success: false, error: (err as Error).message, }; } context.reportStatus(Done.); return {success: true}; }至此一个功能完整的 builder 就成型了context.reportStatus报告开始与结束、context.logger.error记录失败原因、返回值始终符合BuilderOutput契约。Builder 输入定义、校验与关联Builder 可以通过ng build这样的 CLI 命令被间接调用也可以用ng run直接运行。无论哪种方式你都必须提供必需的输入项其余输入可以回落到预配置的默认值、由某个 configuration 覆盖、或在命令行上显式指定。输入校验与 JSON SchemaBuilder 的输入定义在一个与之关联的 JSON Schema 中。与 schematics 类似Architect 会把解析后的输入值收集进options对象并在把值传给 builder 函数之前按 schema 校验其类型。对示例 builder 而言options应是一个含source与destination两个字符串键的JsonObject。可提供如下 schema 做类型校验{ $schema: https://json-schema.org/schema, type: object, properties: { source: { type: string }, destination: { type: string } } }这是最简示例但基于 schema 的校验能力非常强大——除了基础类型检查还可以声明必填字段、默认值、枚举取值等。schema 越严谨用户在配置错误或拼错参数时越早得到清晰报错。关联实现与名称builders.json 与 package.json要把 builder 实现与其 schema、名称关联起来需要创建一个builder 定义文件并在package.json中指向它。创建builders.json{ builders: { copy: { implementation: ./dist/my-builder.js, schema: ./src/schema.json, description: Copies a file. } } }在package.json中增加builders键告诉 Architect 工具去哪里找定义文件{ name: example/copy-file, version: 1.0.0, description: Builder for copying files, builders: builders.json, dependencies: { angular/build: ^21.2.0 } }至此 builder 的官方全名就确定为example/copy-file:copy第一部分example/copy-file是 npm 包名第二部分copy是builders.json中声明的 builder 名。运行期间options.source与options.destination分别读取到这两个输入值。注意上例implementation指向./dist/my-builder.js——说明发布前需先把 TypeScript 源码编译到dist目录。Target 配置把 builder 挂到 angular.json 上一个 builder 必须拥有与之关联的targettarget 把它与特定的输入配置和项目绑定。Target 定义在angular.jsonCLI 配置文件 中。一个 target 指定了要使用的 builder默认 options 配置若干个命名的备选配置named alternative configurations。Angular CLI 中的 Architect 正是依据 target 定义来解析某次运行的输入选项。angular.json中每个项目都有一个自己的配置区块其中architect区块为 CLI 命令如build、test、serve使用的 builder 配置 target。例如默认情况下ng build会运行angular/build:applicationbuilder 来完成构建并把angular.json中buildtarget 指定的默认选项值传入{ myApp: { ...: ..., architect: { build: { builder: angular/build:application, options: { outputPath: dist/myApp, index: src/index.html, ...: ... }, configurations: { production: { fileReplacements: [ { replace: src/environments/environment.ts, with: src/environments/environment.prod.ts } ], optimization: true, outputHashing: all, ...: ... } } }, ...: ... } } }运行机制如下命令把options段指定的默认选项传给 builder若传入--configurationproduction标志则改用production配置段里的覆盖值还可以在命令行上逐项指定其它选项覆盖。Target 字符串通用的ng runCLI 命令把第一个参数取作如下形式的 target 字符串project:target[:configuration]段说明projecttarget 所关联的 Angular CLI 项目名targetangular.json的architect区块中的一个命名 builder 配置configuration可选该 target 的某个具体配置覆盖名定义于angular.json如果你的 builder 需要调用另一个 builder就可能要读取传入的 target 字符串。此时可用angular-devkit/architect提供的targetFromTargetString()工具函数把它解析成对象。调度与运行scheduleTarget 与 scheduleBuilderArchitect异步地运行 builders。要调用一个 builder你需要调度一个“当所有配置解析完成后执行的任务”。关键机制builder 函数不会立即执行只有调度器返回BuilderRun控制对象后才会运行CLI 通常调用context.scheduleTarget()来调度任务随后依据angular.json中的 target 定义解析输入选项。选项解析遵循严格的覆盖顺序Architect 先取默认 options 对象再用来自 configuration 的值覆盖最后用传入context.scheduleTarget()的 overrides 对象进一步覆盖。对 Angular CLI 而言这个 overrides 对象来自命令行参数。解析完成后Architect 会按 builder 的 schema 校验最终 options 值——只有校验通过Architect 才创建 context 并执行 builder。两点补充你也可以从另一个 builder 或测试里调用context.scheduleBuilder()直接调用某个 builder。此时直接把options对象传给方法这些值只按该 builder 的 schema 校验、不做任何额外调整只有context.scheduleTarget()会经由angular.json解析 configuration 与 overrides。scheduleBuilder不会。默认 architect 配置ng new 生成了什么把 target 配置放回上下文先看一个最简angular.json。假设已把 builder 发布到 npm见 发布你的库并执行安装npm install example/copy-file若用ng new builder-test新建项目生成的angular.json大致如下只含默认 builder 配置{ projects: { builder-test: { architect: { build: { builder: angular/build:application, options: { outputPath: dist/builder-test, index: src/index.html, main: src/main.ts, polyfills: src/polyfills.ts, tsConfig: src/tsconfig.app.json }, configurations: { production: { optimization: true, aot: true } } } } } } }可以看到buildtarget 的默认 options 里outputPath、index、main、polyfills、tsConfig等键都已就位production配置则覆盖optimization与aot。添加一个 target接入自定义 builder现在为项目添加一个运行复制 builder 的新 target让它复制package.json文件。操作分两步在项目的architect对象上追加一个名为copy-package的新 target该 target 使用你发布的example/copy-filebuilder并在 options 中提供两个输入默认值source要复制的源文件、destination复制目标路径。{ projects: { builder-test: { architect: { copy-package: { builder: example/copy-file:copy, options: { source: package.json, destination: package-copy.json } } // 原有的 targets... } } } }运行 builderng run 与命令行覆盖使用新 target 的默认配置运行ng run builder-test:copy-package执行后package.json被复制为package-copy.json。也可以用命令行参数覆盖已配置的默认值。例如换一个目标文件名ng run builder-test:copy-package --destinationpackage-other.json这次文件会被复制到package-other.json而非package-copy.json由于没有覆盖source它仍从默认的package.json读取源文件——这正是“默认值 → configuration → 命令行覆盖”三级优先级在命令行层的直观体现。测试 builder集成测试的正确姿势为 builder 编写测试建议使用集成测试——借助 Architect 调度器创建 context。仓库 src/my-builder.spec.ts 给出了官方完整测试示例import {Architect} from angular-devkit/architect; import {TestingArchitectHost} from angular-devkit/architect/testing; import {schema} from angular-devkit/core; import {promises as fs} from fs; import {join} from path; describe(Copy File Builder, () { let architect: Architect; let architectHost: TestingArchitectHost; beforeEach(async () { const registry new schema.CoreSchemaRegistry(); registry.addPostTransform(schema.transforms.addUndefinedDefaults); // TestingArchitectHost() takes workspace and current directories. // Since we dont use those, both are the same in this case. architectHost new TestingArchitectHost(__dirname, __dirname); architect new Architect(architectHost, registry); // This will either take a Node package name, or a path to the directory // for the package.json file. await architectHost.addBuilderFromPackage(join(__dirname, ..)); }); it(can copy files, async () { // A run can have multiple outputs, and contains progress information. const run await architect.scheduleBuilder(example/copy-file:copy, { source: package.json, destination: package-copy.json, }); // The result member (of type BuilderOutput) is the next output. const output await run.result; // Stop the builder from running. This stops Architect from keeping // the builder-associated states in memory, since builders keep waiting // to be scheduled. await run.stop(); // Expect that the copied file is the same as its source. const sourceContent await fs.readFile(package.json, utf8); const destinationContent await fs.readFile(package-copy.json, utf8); expect(destinationContent).toBe(sourceContent); }); });测试中创建了三个关键对象对象职责JsonSchemaRegistry这里为schema.CoreSchemaRegistryschema 注册与校验示例还加了addUndefinedDefaults后置变换为未提供的字段补默认值TestingArchitectHostArchitectHost的内存版实现可避免真实文件系统依赖Architect调度器本身用上面两个对象构造测试流程是通过architectHost.addBuilderFromPackage()读取包含builders.json的包目录 → 用architect.scheduleBuilder()调度example/copy-file:copy→ 读取run.result获取本次BuilderOutput→run.stop()释放状态 → 断言复制出的文件与源文件字节一致。提示在你自己的仓库里运行此测试需要安装ts-node。若想省掉这一步可把my-builder.spec.ts重命名为my-builder.spec.js。Watch 模式构建长任务的三阶段模型大多数 builder 运行一次即返回。但这与“监听变化”的 builder例如 dev server并不完全兼容。Architect 支持 watch 模式不过有几个要点需要注意handler 应返回Observable。Architect 会订阅该Observable直到它完成若 builder 以相同参数再次被调度这个Observable可能会被复用每次执行后都应发出一个BuilderOutput对象。执行完毕即可进入由外部事件触发的 watch 阶段重启时调用context.reportRunning()。若外部事件触发了重启builder 应调用context.reportRunning()告知 Architect“我再次运行了”从而防止 Architect 在另一次运行被调度时停掉本 builder。当 builder 调用BuilderRun.stop()退出 watch 模式时Architect 会退订该 builder 的Observable并调用 teardown 逻辑做清理。这种设计也让长时运行的构建可以被安全停止和清理。因此若 builder 在监听某个外部事件一般把运行划分为三个阶段阶段说明Running运行正在执行的任务例如调用编译器。编译器结束、builder 发出一个BuilderOutput对象后该阶段结束Watching监听两次运行之间监听外部事件流例如监听文件系统变化。当编译器重启并调用context.reportRunning()时结束Completion完成任务彻底完成如编译器需多次运行或本次 builder run 被BuilderRun.stop()停止。Architect 执行 teardown 逻辑并从 builder 的Observable退订小结CLI Builder API 的能力边界CLI Builder API 通过 builder 执行自定义逻辑为改变 Angular CLI 行为提供了标准途径官方推荐按以下原则实践Builder 形态灵活可同步或异步、可执行一次或监听外部事件、还能调度其它 builder 或 target选项优先级明确builder 的选项默认值在angular.json中指定可被某 target 的备选配置覆盖还可再被命令行参数覆盖测试分层Angular 团队推荐用集成测试测试 Architect builders用单元测试验证 builder 所执行的业务逻辑注意资源清理若 builder 返回Observable应在该Observable的 teardown 逻辑中完成自身清理。想动手实践的读者可在本仓库 cli-builder 示例目录 中找到上述全部源码与测试用例需要进一步理解 target 与配置语义时可对照阅读 workspace-config 参考文档 与 环境配置指南。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表