Angular CLI 构建器(Builder)
Angular CLI builders
很多 Angular CLI 命令都要在你的代码上执行一些复杂的处理,比如风格检查(lint)构建或测试。这些命令会通过一个叫做建筑师(Architect)的内部工具来运行 CLI 构建器,而这些构建器会运用一些第三方工具来完成目标任务。
A number of Angular CLI commands run a complex process on your code, such as linting, building, or testing. The commands use an internal tool called Architect to run CLI builders, which apply another tool to accomplish the desired task.
在 Angular 的版本 8 中,CLI 构建器的 API 是稳定的,想要通过添加或修改命令来自定义 Angular CLI 的开发人员可以使用它。例如,你可以提供一个构建器来执行全新的任务,或者更改一个现有命令所使用的第三方工具。
With Angular version 8, the CLI Builder API is stable and available to developers who want to customize the Angular CLI by adding or modifying commands. For example, you could supply a builder to perform an entirely new task, or to change which third-party tool is used by an existing command.
本文档介绍了 CLI 构建器是如何与工作区配置文件集成的,还展示了如何创建你自己的构建器。
This document explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder.
你可以在这个 GitHub 仓库中的例子中找到代码。
You can find the code from the examples used here in this GitHub repository.
CLI 构建器
CLI builders
内部建筑师工具会把工作委托给名叫构建器的处理器函数。处理器函数接收两个参数:一组 options
输入(JSON 对象)和一个 context
(BuilderContext
对象)。
The internal Architect tool delegates work to handler functions called builders. A builder handler function receives two arguments; a set of input options
(a JSON object), and a context
(a BuilderContext
object).
这里对关注点的分离和原理图中是一样的,它也适用于其它要接触(touch)代码的 CLI 命令(例如 ng generate
)。
The separation of concerns here is the same as with schematics, which are used for other CLI commands that touch your code (such as ng generate
).
选项由 CLI 用户提供,上下文由 CLI 构建器提供,并提供对 CLI 构建器 API 的访问,而开发人员提供了处理函数的行为。
Options are given by the CLI user, context is provided by and provides access to the CLI Builder API, and the developer provides the behavior.
BuilderContext
对象提供了访问调度方法BuilderContext.scheduleTarget()
的途径。调度器会用指定的目标配置来执行构建器处理函数。The
BuilderContext
object provides access to the scheduling method,BuilderContext.scheduleTarget()
. The scheduler executes the builder handler function with a given target configuration.
这个构建器处理函数可以是同步的(返回一个值)或异步的(返回一个 Promise),也可以监视并返回多个值(返回一个 Observable)。最终返回的值全都是 BuilderOutput
类型的。该对象包含一个逻辑字段 success
和一个可以包含错误信息的可选字段 error
。
The builder handler function can be synchronous (return a value) or asynchronous (return a Promise), or it can watch and return multiple values (return an Observable). The return value or values must always be of type BuilderOutput
. This object contains a Boolean success
field and an optional error
field that can contain an error message.
Angular 提供了一些构建器,供 CLI 命令使用,如 ng build
、ng test
和 ng lint
等。这些内置 CLI 构建器的默认目标配置可以在工作空间配置文件 angular.json
的 architect
部分找到(并进行自定义)。可以通过创建自己的构建器来扩展和自定义 Angular,你可以使用 ng run
CLI 命令来运行你自己的构建器。
Angular provides some builders that are used by the CLI for commands such as ng build
, ng test
, and ng lint
. Default target configurations for these and other built-in CLI builders can be found (and customized) in the "architect" section of the workspace configuration file, angular.json
. You can also extend and customize Angular by creating your own builders, which you can run using the ng run
CLI command.
构建器的项目结构
Builder project structure
构建器位于一个 project
文件夹中,该文件夹的结构类似于 Angular 工作区,包括位于顶层的全局配置文件,以及位于工作代码所在源文件夹中的更具体的配置。例如,myBuilder
文件夹中可能包含如下文件。
A builder resides in a "project" folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior. For example, your myBuilder
folder could contain the following files.
文件 FILES | 目的 PURPOSE |
---|---|
src/my-builder.ts | 这个构建器定义的主要源码。 Main source file for the builder definition. |
src/my-builder.spec.ts | 测试的源码。 Source file for tests. |
src/schema.json | 构建器输入选项的定义。 Definition of builder input options. |
builders.json | 测试配置。 Testing configuration. |
package.json | 依赖包。参见https://docs.npmjs.com/files/package.json 。 Dependencies. See https://docs.npmjs.com/files/package.json. |
tsconfig.json |
你可以把构建器发布到 npm
(请参阅发布你的库)。如果把它发布成了 @example/my-builder
,就可以使用下面的命令来安装它。
You can publish the builder to npm
(see Publishing your Library). If you publish it as @example/my-builder
, you can install it using the following command.
npm install @example/my-builder
创建构建器
Creating a builder
举个例子,让我们创建一个用来执行 shell 命令的构建器。要创建构建器,请使用 CLI 构建器函数 createBuilder()
,并返回一个 Promise<BuilderOutput>
对象。
As an example, let's create a builder that executes a shell command. To create a builder, use the createBuilder()
CLI Builder function, and return a Promise<BuilderOutput>
object.
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
interface Options extends JsonObject {
command: string;
args: string[];
}
export default createBuilder(commandBuilder);
function commandBuilder(
options: Options,
context: BuilderContext,
): Promise<BuilderOutput> {
}
现在,让我们为它添加一些逻辑。下列代码会从用户选项中检索命令和参数、生成新进程,并等待该进程完成。如果进程成功(返回代码为 0),就会解析成返回的值。
Now let’s add some logic to it. The following code retrieves the command and arguments from the user options, spawns the new process, and waits for the process to finish. If the process is successful (returns a code of 0), it resolves the return value.
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import * as childProcess from 'child_process';
interface Options extends JsonObject {
command: string;
args: string[];
}
export default createBuilder(commandBuilder);
function commandBuilder(
options: Options,
context: BuilderContext,
): Promise<BuilderOutput> {
const child = childProcess.spawn(options.command, options.args);
return new Promise(resolve => {
child.on('close', code => {
resolve({ success: code === 0 });
});
});
}
处理输出
Handling output
默认情况下,spawn()
方法会把所有内容输出到进程标准输出(stdout)和标准错误(stderr)中。为了便于测试和调试,我们可以把输出转发给 CLI 构建器的 Logger。这样还能让构建器本身可以在一个单独的进程中执行,即使其标准输出和标准错误被停用了也无所谓(就像在 Electron 应用中一样)。
By default, the spawn()
method outputs everything to the process standard output and error. To make it easier to test and debug, we can forward the output to the CLI Builder logger instead. This also allows the builder itself to be executed in a separate process, even if the standard output and error are deactivated (as in an Electron app).
我们可以从上下文中检索一个 Logger 实例。
We can retrieve a Logger instance from the context.
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import * as childProcess from 'child_process';
interface Options extends JsonObject {
command: string;
args: string[];
}
export default createBuilder(commandBuilder);
function commandBuilder(
options: Options,
context: BuilderContext,
): Promise<BuilderOutput> {
const child = childProcess.spawn(options.command, options.args);
child.stdout.on('data', data => {
context.logger.info(data.toString());
});
child.stderr.on('data', data => {
context.logger.error(data.toString());
});
return new Promise(resolve => {
child.on('close', code => {
resolve({ success: code === 0 });
});
});
}
进度和状态报告
Progress and status reporting
CLI 构建器 API 包含一些进度报告和状态报告工具,可以为某些函数和接口提供提示信息。
The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces.
要报告进度,请使用 BuilderContext.reportProgress()
方法,它接受一个当前值(value)、一个(可选的)总值(total)和状态(status)字符串作为参数。总值可以是任意数字,例如,如果你知道有多少个文件需要处理,那么总值可能是这些文件的数量,而当前值是已处理过的数量。除非传入了新的字符串,否则这个状态字符串不会改变。
To report progress, use the BuilderContext.reportProgress()
method, which takes a current value, (optional) total, and status string as arguments. The total can be any number; for example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far. The status string is unmodified unless you pass in a new string value.
你可以看看 tslint
构建器如何报告进度的例子 。
You can see an example of how the tslint
builder reports progress.
在我们的例子中,shell 命令或者已完成或者正在执行,所以不需要进度报告,但是可以报告状态,以便调用此构建器的父构建器知道发生了什么。可以用 BuilderContext.reportStatus()
方法生成一个任意长度的状态字符串。(注意,无法保证长字符串会完全显示出来,可以裁剪它以适应界面显示。)传入一个空字符串可以移除状态。
In our example, the shell command either finishes or is still executing, so there’s no need for a progress report, but we can report status so that a parent builder that called our builder would know what’s going on. Use the BuilderContext.reportStatus()
method to generate a status string of any length. (Note that there’s no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.) Pass an empty string to remove the status.
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import * as childProcess from 'child_process';
interface Options extends JsonObject {
command: string;
args: string[];
}
export default createBuilder(commandBuilder);
function commandBuilder(
options: Options,
context: BuilderContext,
): Promise<BuilderOutput> {
context.reportStatus(`Executing "${options.command}"...`);
const child = childProcess.spawn(options.command, options.args);
child.stdout.on('data', data => {
context.logger.info(data.toString());
});
child.stderr.on('data', data => {
context.logger.error(data.toString());
});
return new Promise(resolve => {
context.reportStatus(`Done.`);
child.on('close', code => {
resolve({ success: code === 0 });
});
});
}
构建器的输入
Builder input
你可以通过 CLI 命令间接调用一个构建器,也可以直接用 Angular CLI 的 ng run
命令来调用它。无论哪种情况,你都必须提供所需的输入,但是可以用特定目标中预配置的值作为其默认值,然后指定一个预定义的、指定的配置进行覆盖,最后在命令行中进一步覆盖这些选项的值。
You can invoke a builder indirectly through a CLI command, or directly with the Angular CLI ng run
command. In either case, you must provide required inputs, but can allow other inputs to default to values that are pre-configured for a specific target, provide a pre-defined, named override configuration, and provide further override option values on the command line.
对输入的验证
Input validation
你可以在该构建器的相关 JSON 模式中定义构建器都有哪些输入。 建筑师工具会把解析后的输入值收集到一个 options
对象中,并在将其传给构建器函数之前先根据这个模式验证它们的类型。(Schematics 库也对用户输入做了同样的验证)。
You define builder inputs in a JSON schema associated with that builder. The Architect tool collects the resolved input values into an options
object, and validates their types against the schema before passing them to the builder function. (The Schematics library does the same kind of validation of user input).
对于这个示例构建器,我们希望 options
值是带有两个键的 JsonObject
:一个是字符串型的 command
,一个是字符串数组型的 args
。
For our example builder, we expect the options
value to be a JsonObject
with two keys: a command
that is a string, and an args
array of string values.
我们可以提供如下模式来对这些值的类型进行验证。
We can provide the following schema for type validation of these values.
{
"$schema": "http://json-schema.org/schema",
"type": "object",
"properties": {
"command": {
"type": "string"
},
"args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
这是一个非常简单的例子,但这种模式验证也可以非常强大。要了解更多信息,请参阅 JSON 模式网站 。
This is a very simple example, but the use of a schema for validation can be very powerful. For more information, see the JSON schemas website.
要把构建器的实现与它的模式和名称关联起来,我们需要创建一个构建器定义文件,可以在 package.json
中指向该文件。
To link our builder implementation with its schema and name, we need to create a builder definition file, which we can point to in package.json
.
创建一个名为 builders.json
文件,它看起来像这样。
Create a file named builders.json
file that looks like this.
{
"builders": {
"command": {
"implementation": "./command",
"schema": "./command/schema.json",
"description": "Runs any command line in the operating system."
}
}
}
在 package.json
文件中,添加一个 builders
键,告诉建筑师工具可以在哪里找到这个构建器定义文件。
In the package.json
file, add a builders
key that tells the Architect tool where to find our builder definition file.
{
"name": "@example/command-runner",
"version": "1.0.0",
"description": "Builder for Command Runner",
"builders": "builders.json",
"devDependencies": {
"@angular-devkit/architect": "^1.0.0"
}
}
现在,这个构建器的正式名字是 @example/command-runner:command
。第一部分是包名(使用 node 方案进行解析),第二部分是构建器名称(使用 builders.json
文件进行解析)。
The official name of our builder is now @example/command-runner:command
. The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the builders.json
file).
使用某个 options
是非常简单的。在上一节,我们就曾访问过 options.command
。
Using one of our options
is very straightforward, we did this in the previous section when we accessed options.command
.
context.reportStatus(`Executing "${options.command}"...`);
const child = childProcess.spawn(options.command, options.args);
目标配置
Target configuration
构建器必须有一个已定义的目标,此目标会把构建器与特定的输入配置和项目关联起来。
A builder must have a defined target that associates it with a specific input configuration and project.
目标是在 CLI 配置文件 angular.json
中定义的。目标用于指定要使用的构建器、默认的选项配置,以及指定的备用配置。建筑师工具使用目标定义来为一次特定的执行解析输入选项。
Targets are defined in the angular.json
CLI configuration file. A target specifies the builder to use, its default options configuration, and named alternative configurations. The Architect tool uses the target definition to resolve input options for a given run.
angular.json
文件中为每个项目都有一节配置,每个项目的 architect
部分都会为 CLI 命令(例如 build
、test
和 lint
)配置构建器目标。默认情况下,build
命令会运行 @angular-devkit/build-angular:browser
构建器来执行 build
任务,并传入 angular.json
中为 build
目标指定的默认选项值。
The angular.json
file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'lint'. By default, for example, the build
command runs the builder @angular-devkit/build-angular:browser
to perform the build task, and passes in default option values as specified for the build
target in angular.json
.
{
"myApp": {
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"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
节中指定的一组默认选项。如果你传入了 --configuration=production
标志,它就会使用 production
备用配置中指定的值进行覆盖。你可以在命令行中单独指定其它选项进行覆盖,还可以为 build
目标添加更多备用配置,以定义其它环境,比如 stage
或 qa
。
The command passes the builder the set of default options specified in the "options" section. If you pass the --configuration=production
flag, it uses the override values specified in the production
alternative configuration. You can specify further option overrides individually on the command line. You might also add more alternative configurations to the build
target, to define other environments such as stage
or qa
.
目标字符串
Target strings
通用的 ng run
CLI 命令的第一个参数是形如 project:target[:configuration] 的目标字符串。
The generic ng run
CLI command takes as its first argument a target string of the form project:target[:configuration].
project:与此目标关联的 Angular CLI 项目的名称。
project: The name of the Angular CLI project that the target is associated with.
target:
angular.json
文件architect
下的指定构建器配置。target: A named builder configuration from the
architect
section of theangular.json
file.configuration:(可选)用于覆盖指定目标的具体配置名称,如
angular.json
文件中的定义。configuration: (optional) The name of a specific configuration override for the given target, as defined in the
angular.json
file.
如果你的构建器调用另一个构建器,它可能需要读取一个传入的目标字符串。你可以使用 @angular-devkit/architect
中的工具函数 targetFromTargetString()
把这个字符串解析成一个对象。
If your builder calls another builder, it may need to read a passed target string. You can parse this string into an object by using the targetFromTargetString()
utility function from @angular-devkit/architect
.
调度并运行
Schedule and run
建筑师会异步运行构建器。要调用某个构建器,就要在所有配置解析完成之后安排一个要运行的任务。
Architect runs builders asynchronously. To invoke a builder, you schedule a task to be run when all configuration resolution is complete.
在调度器返回 BuilderRun
控件对象之前,不会执行该构建器函数。 CLI 通常会通过调用 BuilderContext.scheduleTarget()
函数来调度任务,然后使用 angular.json
文件中的目标定义来解析输入选项。
The builder function is not executed until the scheduler returns a BuilderRun
control object. The CLI typically schedules tasks by calling the BuilderContext.scheduleTarget()
function, and then resolves input options using the target definition in the angular.json
file.
建筑师会接受默认的选项对象来解析指定目标的输入选项,然后覆盖所用配置中的值(如果有的话),然后再从传给 BuilderContext.scheduleTarget()
的覆盖对象中覆盖这些值。对于 Angular CLI,覆盖对象是从命令行参数中构建的。
Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration used (if any), then further overwriting values from the overrides object passed to BuilderContext.scheduleTarget()
. For the Angular CLI, the overrides object is built from command line arguments.
建筑师会根据构建器的模式对生成的选项值进行验证。如果输入有效,建筑师会创建上下文并执行该构建器。
Architect validates the resulting options values against the schema of the builder. If inputs are valid, Architect creates the context and executes the builder.
欲知详情,请参阅工作空间配置。
For more information see Workspace Configuration.
你还可以通过调用 BuilderContext.scheduleBuilder()
从另一个构建器或测试中调用某个构建器。你可以直接把 options
对象传给该方法,并且这些选项值会根据这个构建器的模式进行验证,而无需进一步调整。
You can also invoke a builder directly from another builder or test by calling BuilderContext.scheduleBuilder()
. You pass an options
object directly to the method, and those option values are validated against the schema of the builder without further adjustment.
只有 BuilderContext.scheduleTarget()
方法来解析这些配置和并通过 angular.json
文件进行覆盖。
Only the BuilderContext.scheduleTarget()
method resolves the configuration and overrides through the angular.json
file.
默认建筑师配置
Default architect configuration
让我们创建一个简单的 angular.json
文件,它会把目标配置放到上下文中。
Let’s create a simple angular.json
file that puts target configurations into context.
我们可以把这个构建器发布到 npm(请参阅发布你的库),并使用如下命令来安装它:
We can publish the builder to npm (see Publishing your Library), and install it using the following command:
npm install @example/command-runner
如果我们使用 ng new builder-test
创建一个新项目,那么生成的 angular.json
文件就是这样的,它只有默认的构建器参数。
If we create a new project with ng new builder-test
, the generated angular.json
file looks something like this, with only default builder configurations.
{
// ...
"projects": {
// ...
"builder-test": {
// ...
"architect": {
// ...
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
// ... more options...
"outputPath": "dist/builder-test",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json"
},
"configurations": {
"production": {
// ... more options...
"optimization": true,
"aot": true,
"buildOptimizer": true
}
}
}
}
}
}
// ...
}
添加一个目标
Adding a target
让我们添加一个新的目标来运行我们的构建器执行一个特定的命令。这个目标会告诉构建器在文件上运行 touch
,以便更新修改过的日期。
Let's add a new target that will run our builder to execute a particular command. This target will tell the builder to run touch
on a file, in order to update its modified date.
我们需要更新 angular.json
文件,把这个构建器的目标添加到新项目的 architect
部分。
We need to update the angular.json
file to add a target for this builder to the "architect" section of our new project.
我们会为项目的
architect
对象添加一个新的目标小节。We'll add a new target section to the "architect" object for our project.
名为
touch
的目标使用了我们的构建器,它发布到了@example/command-runner
。 (参见发布你的库 )The target named "touch" uses our builder, which we published to
@example/command-runner
. (See Publishing your Library)这个配置对象为我们定义的两个输入提供了默认值:
command
(要执行的 Unix 命令)和args
(包含要操作的文件的数组)。The options object provides default values for the two inputs that we defined;
command
, which is the Unix command to execute, andargs
, an array that contains the file to operate on.这些配置键都是可选的,但我们先不展开。
The configurations key is optional, we'll leave it out for now.
{
"projects": {
"builder-test": {
"architect": {
"touch": {
"builder": "@example/command-runner:command",
"options": {
"command": "touch",
"args": [
"src/main.ts"
]
}
},
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/builder-test",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json"
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"aot": true,
"buildOptimizer": true
}
}
}
}
}
}
}
运行这个构建器
Running the builder
要想使用这个新目标的默认配置运行我们的构建器,请在 Linux shell 中使用以下 CLI 命令。
To run our builder with the new target's default configuration, use the following CLI command in a Linux shell.
ng run builder-test:touch
这将在 src/main.ts
文件上运行 touch
命令。
This will run the touch
command on the src/main.ts
file.
你可以使用命令行参数来覆盖已配置的默认值。例如,要改用其它 command
值运行,请使用以下 CLI 命令。
You can use command-line arguments to override the configured defaults. For example, to run with a different command
value, use the following CLI command.
ng run builder-test:touch --command=ls
这将调用 ls
命令而不是 touch
命令。因为我们没有覆盖 args 选项,所以它会列出 src/main.ts
文件的信息(提供给该目标的默认值)。
This will call the ls
command instead of the touch
command. Because we did not override the args option, it will list information about the src/main.ts
file (the default value provided for the target).
测试一个构建器
Testing a builder
对构建器进行集成测试,以便你可以使用建筑师的调度器来创建一个上下文,就像这个例子中一样。
Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this example.
在构建器的源码目录下,我们创建了一个新的测试文件
index.spec.ts
。该代码创建了JsonSchemaRegistry
(用于模式验证)、TestingArchitectHost
(对ArchitectHost
的内存实现)和Architect
的新实例。In the builder source directory, we have created a new test file
my-builder.spec.ts
. The code creates new instances ofJsonSchemaRegistry
(for schema validation),TestingArchitectHost
(an in-memory implementation ofArchitectHost
), andArchitect
.我们紧挨着这个构建器的
package.json
文件添加了一个builders.json
文件,并修改了package.json
文件以指向它。We've added a
builders.json
file next to the builder'spackage.json
file, and modified the package file to point to it.
下面是运行该命令构建器的测试范例。该测试使用该构建器来运行 node --print 'foo'
命令,然后验证 logger
中是否包含一条 foo
记录。
Here’s an example of a test that runs the command builder. The test uses the builder to run the node --print 'foo'
command, then validates that the logger
contains an entry for foo
.
import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { logging, schema } from '@angular-devkit/core';
describe('Command Runner 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 don't 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('..');
});
it('can run node', async () => {
// Create a logger that keeps an array of all messages that were logged.
const logger = new logging.Logger('');
const logs = [];
logger.subscribe(ev => logs.push(ev.message));
// A "run" can have multiple outputs, and contains progress information.
const run = await architect.scheduleBuilder('@example/command-runner:command', {
command: 'node',
args: ['--print', '\'foo\''],
}, { logger }); // We pass the logger for checking later.
// 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 foo was logged
expect(logs).toContain('foo');
});
});
在你的仓库中运行这个测试时,需要使用 ts-node
包。你可以把 index.spec.ts
重命名为 index.spec.js
来回避它。
When running this test in your repo, you need the ts-node
package. You can avoid this by renaming my-builder.spec.ts
to my-builder.spec.js
.
监视(watch)模式
Watch mode
建筑师希望构建器运行一次(默认情况下)并返回。这种行为与那些需要监视文件更改的构建器(例如 Webpack)并不完全兼容。 建筑师可以支持监视模式,但要注意一些问题。
Architect expects builders to run once (by default) and return. This behavior is not entirely compatible with a builder that watches for changes (like Webpack, for example). Architect can support watch mode, but there are some things to look out for.
要在监视模式下使用,构建器处理函数应返回一个 Observable。建筑师会订阅这个 Observable,直到这个 Observable 完成(complete)为止。此外,如果使用相同的参数再次调度这个构建器,建筑师还能重用这个 Observable。
To be used with watch mode, a builder handler function should return an Observable. Architect subscribes to the Observable until it completes and might reuse it if the builder is scheduled again with the same arguments.
这个构建器应该总是在每次执行后发出一个
BuilderOutput
对象。一旦它被执行,就会进入一个由外部事件触发的监视模式。如果一个事件导致它重启,那么此构建器应该执行BuilderContext.reportRunning()
函数来告诉建筑师再次运行它。如果调度器还计划了另一次运行,就会阻止建筑师停掉这个构建器。The builder should always emit a
BuilderOutput
object after each execution. Once it’s been executed, it can enter a watch mode, to be triggered by an external event. If an event triggers it to restart, the builder should execute theBuilderContext.reportRunning()
function to tell Architect that it is running again. This prevents Architect from stopping the builder if another run is scheduled.
当你的构建器通过调用 BuilderRun.stop()
来退出监视模式时,建筑师会从构建器的 Observable 中取消订阅,并调用构建器的退出逻辑进行清理。(这种行为也允许停止和清理运行时间过长的构建。)
When your builder calls BuilderRun.stop()
to exit watch mode, Architect unsubscribes from the builder’s Observable and calls the builder’s teardown logic to clean up. (This behavior also allows for long running builds to be stopped and cleaned up.)
一般来说,如果你的构建器正在监视一个外部事件,你应该把你的运行分成三个阶段。
In general, if your builder is watching an external event, you should separate your run into three phases.
运行,例如 webpack 编译。这会在 webpack 完成并且你的构建器发出
BuilderOutput
对象时结束。Running For example, webpack compiles. This ends when webpack finishes and your builder emits a
BuilderOutput
object.监视,在两次运行之间监视外部事件流。例如,webpack 会监视文件系统是否发生了任何变化。这会在 webpack 重启构建时结束,并调用
BuilderContext.reportRunning()
。这样就会再回到第 1 步。Watching Between two runs, watch an external event stream. For example, webpack watches the file system for any changes. This ends when webpack restarts building, and
BuilderContext.reportRunning()
is called. This goes back to step 1.完成,任务完全完成(例如,webpack 应运行多次),或者构建器停止运行(使用
BuilderRun.stop()
)。你的退出逻辑被调用了,建筑师也从你的构建器的 Observable 中取消了订阅。Completion Either the task is fully completed (for example, webpack was supposed to run a number of times), or the builder run was stopped (using
BuilderRun.stop()
). Your teardown logic is executed, and Architect unsubscribes from your builder’s Observable.
总结
Summary
CLI 构建器 API 提供了一种通过构建器执行自定义逻辑,以改变 Angular CLI 行为的新方式。
The CLI Builder API provides a new way of changing the behavior of the Angular CLI by using builders to execute custom logic.
构建器既可以是同步的,也可以是异步的,它可以只执行一次也可以监视外部事件,还可以调度其它构建器或目标。
Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets.
构建器在
angular.json
配置文件中指定了选项的默认值,它可以被目标的备用配置覆盖,还可以进一步被命令行标志所覆盖。Builders have option defaults specified in the
angular.json
configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags.我们建议你使用集成测试来测试建筑师的构建器。你还可以使用单元测试来验证这个构建器的执行逻辑。
We recommend that you use integration tests to test Architect builders. You can use unit tests to validate the logic that the builder executes.
如果你的构建器返回一个 Observable,你应该在那个 Observable 的退出逻辑中进行清理。
If your builder returns an Observable, it should clean up in the teardown logic of that Observable.