Skip to content

Commit c48982d

Browse files
committedOct 10, 2023
feat(@angular-devkit/build-angular): add buildTarget option to dev-server and extract-i18n builders
This is to better match the nature of the application builder where the target can be both browser and server. DEPRECATED: The `browserTarget` in the dev-server and extract-i18n builders have been deprecated in favor of `buildTarget`.
1 parent 258ccae commit c48982d

File tree

18 files changed

+190
-24
lines changed

18 files changed

+190
-24
lines changed
 

‎goldens/public-api/angular_devkit/build_angular/index.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ export enum CrossOrigin {
107107
// @public
108108
export interface DevServerBuilderOptions {
109109
allowedHosts?: string[];
110-
browserTarget: string;
110+
// @deprecated
111+
browserTarget?: string;
112+
buildTarget?: string;
111113
disableHostCheck?: boolean;
112114
forceEsbuild?: boolean;
113115
headers?: {
@@ -176,7 +178,9 @@ export type ExecutionTransformer<T> = (input: T) => T | Promise<T>;
176178

177179
// @public
178180
export interface ExtractI18nBuilderOptions {
179-
browserTarget: string;
181+
// @deprecated
182+
browserTarget?: string;
183+
buildTarget?: string;
180184
format?: Format;
181185
outFile?: string;
182186
outputPath?: string;

‎packages/angular_devkit/build_angular/src/builders/application/tests/behavior/typescript-rebuild-lazy_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {
4444

4545
const builderAbort = new AbortController();
4646
const buildCount = await firstValueFrom(
47-
harness.execute({ outputLogsOnFailure: true, signal: builderAbort.signal }).pipe(
47+
harness.execute({ outputLogsOnFailure: false, signal: builderAbort.signal }).pipe(
4848
timeout(20_000),
4949
concatMap(async ({ result, logs }, index) => {
5050
switch (index) {

‎packages/angular_devkit/build_angular/src/builders/dev-server/builder.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async function initialize(
7474
await purgeStaleBuildCache(context);
7575

7676
const normalizedOptions = await normalizeOptions(context, projectName, initialOptions);
77-
const builderName = await context.getBuilderNameForTarget(normalizedOptions.browserTarget);
77+
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
7878

7979
if (
8080
!normalizedOptions.disableHostCheck &&

‎packages/angular_devkit/build_angular/src/builders/dev-server/options.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ export async function normalizeOptions(
3434

3535
const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot);
3636

37-
const browserTarget = targetFromTargetString(options.browserTarget);
37+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
38+
const buildTarget = targetFromTargetString(options.buildTarget ?? options.browserTarget!);
3839

3940
// Initial options to keep
4041
const {
@@ -60,7 +61,7 @@ export async function normalizeOptions(
6061

6162
// Return all the normalized options
6263
return {
63-
browserTarget,
64+
buildTarget,
6465
host: host ?? 'localhost',
6566
port: port ?? 4200,
6667
poll,

‎packages/angular_devkit/build_angular/src/builders/dev-server/schema.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
"browserTarget": {
88
"type": "string",
99
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10+
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
11+
"x-deprecated": "Use 'buildTarget' instead."
12+
},
13+
"buildTarget": {
14+
"type": "string",
15+
"description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
1016
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
1117
},
1218
"port": {
@@ -103,5 +109,5 @@
103109
}
104110
},
105111
"additionalProperties": false,
106-
"required": ["browserTarget"]
112+
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
107113
}

‎packages/angular_devkit/build_angular/src/builders/dev-server/tests/setup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const DEV_SERVER_BUILDER_INFO = Object.freeze({
3030
* supports parallel test execution.
3131
*/
3232
export const BASE_OPTIONS = Object.freeze<Schema>({
33-
browserTarget: 'test:build',
33+
buildTarget: 'test:build',
3434
port: 0,
3535
});
3636

‎packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function* serveWithVite(
5151
): AsyncIterableIterator<DevServerBuilderOutput> {
5252
// Get the browser configuration from the target name.
5353
const rawBrowserOptions = (await context.getTargetOptions(
54-
serverOptions.browserTarget,
54+
serverOptions.buildTarget,
5555
)) as json.JsonObject & BrowserBuilderOptions;
5656

5757
const browserOptions = (await context.validateOptions(

‎packages/angular_devkit/build_angular/src/builders/dev-server/webpack-server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export function serveWebpackBrowser(
8686

8787
// Get the browser configuration from the target name.
8888
const rawBrowserOptions = (await context.getTargetOptions(
89-
options.browserTarget,
89+
options.buildTarget,
9090
)) as json.JsonObject & BrowserBuilderSchema;
9191

9292
if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== OutputHashing.None) {

‎packages/angular_devkit/build_angular/src/builders/extract-i18n/application-extraction.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ export async function extractMessages(
2929
}> {
3030
const messages: LocalizeMessage[] = [];
3131

32-
// Setup the build options for the application based on the browserTarget option
32+
// Setup the build options for the application based on the buildTarget option
3333
const buildOptions = (await context.validateOptions(
34-
await context.getTargetOptions(options.browserTarget),
34+
await context.getTargetOptions(options.buildTarget),
3535
builderName,
3636
)) as unknown as ApplicationBuilderInternalOptions;
3737
buildOptions.optimization = false;

‎packages/angular_devkit/build_angular/src/builders/extract-i18n/builder.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ export async function execute(
4343
// The package is a peer dependency and might not be present
4444
let localizeToolsModule;
4545
try {
46-
localizeToolsModule = await loadEsmModule<typeof import('@angular/localize/tools')>(
47-
'@angular/localize/tools',
48-
);
46+
localizeToolsModule =
47+
await loadEsmModule<typeof import('@angular/localize/tools')>('@angular/localize/tools');
4948
} catch {
5049
return {
5150
success: false,
@@ -57,7 +56,7 @@ export async function execute(
5756

5857
// Normalize options
5958
const normalizedOptions = await normalizeOptions(context, projectName, options);
60-
const builderName = await context.getBuilderNameForTarget(normalizedOptions.browserTarget);
59+
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
6160

6261
// Extract messages based on configured builder
6362
let extractionResult;

‎packages/angular_devkit/build_angular/src/builders/extract-i18n/options.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export async function normalizeOptions(
3333
const projectMetadata = await context.getProjectMetadata(projectName);
3434
const projectRoot = path.join(workspaceRoot, (projectMetadata.root as string | undefined) ?? '');
3535

36-
const browserTarget = targetFromTargetString(options.browserTarget);
36+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
37+
const buildTarget = targetFromTargetString(options.buildTarget ?? options.browserTarget!);
3738

3839
const i18nOptions = createI18nOptions(projectMetadata);
3940

@@ -62,7 +63,7 @@ export async function normalizeOptions(
6263
return {
6364
workspaceRoot,
6465
projectRoot,
65-
browserTarget,
66+
buildTarget,
6667
i18nOptions,
6768
format,
6869
outFile,

‎packages/angular_devkit/build_angular/src/builders/extract-i18n/schema.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
"browserTarget": {
88
"type": "string",
99
"description": "A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10+
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
11+
"x-deprecated": "Use 'buildTarget' instead."
12+
},
13+
"buildTarget": {
14+
"type": "string",
15+
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
1016
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
1117
},
1218
"format": {
@@ -30,5 +36,5 @@
3036
}
3137
},
3238
"additionalProperties": false,
33-
"required": ["browserTarget"]
39+
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
3440
}

‎packages/angular_devkit/build_angular/src/builders/extract-i18n/webpack-extraction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export async function extractMessages(
4141
let useLegacyIds = true;
4242

4343
const browserOptions = await context.validateOptions(
44-
await context.getTargetOptions(options.browserTarget),
44+
await context.getTargetOptions(options.buildTarget),
4545
builderName,
4646
);
4747

‎packages/angular_devkit/build_angular/src/utils/build-options.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export interface BuildOptions {
7676

7777
export interface WebpackDevServerOptions
7878
extends BuildOptions,
79-
Omit<DevServerSchema, 'optimization' | 'sourceMap' | 'browserTarget'> {}
79+
Omit<DevServerSchema, 'optimization' | 'sourceMap' | 'buildTarget' | 'browserTarget'> {}
8080

8181
export interface WebpackConfigOptions<T = BuildOptions> {
8282
root: string;

‎packages/schematics/angular/application/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,17 @@ function addAppToWorkspaceFile(
263263
options: {},
264264
configurations: {
265265
production: {
266-
browserTarget: `${options.name}:build:production`,
266+
buildTarget: `${options.name}:build:production`,
267267
},
268268
development: {
269-
browserTarget: `${options.name}:build:development`,
269+
buildTarget: `${options.name}:build:development`,
270270
},
271271
},
272272
},
273273
'extract-i18n': {
274274
builder: Builders.ExtractI18n,
275275
options: {
276-
browserTarget: `${options.name}:build`,
276+
buildTarget: `${options.name}:build`,
277277
},
278278
},
279279
test: options.minimal

‎packages/schematics/angular/migrations/migration-collection.json

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"version": "17.0.0",
55
"factory": "./update-17/replace-nguniversal-builders",
66
"description": "Replace usages of '@nguniversal/builders' with '@angular-devkit/build-angular'."
7+
},
8+
"update-workspace-config": {
9+
"version": "17.0.0",
10+
"factory": "./update-17/update-workspace-config",
11+
"description": "Replace deprecated options in 'angular.json'."
712
}
813
}
914
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Rule, chain } from '@angular-devkit/schematics';
10+
import { removePackageJsonDependency } from '../../utility/dependencies';
11+
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
12+
import { Builders, ProjectType } from '../../utility/workspace-models';
13+
14+
export default function (): Rule {
15+
return updateWorkspace((workspace) => {
16+
for (const [, project] of workspace.projects) {
17+
if (project.extensions.projectType !== ProjectType.Application) {
18+
// Only interested in application projects since these changes only effects application builders
19+
continue;
20+
}
21+
22+
for (const [, target] of project.targets) {
23+
if (target.builder === Builders.ExtractI18n || target.builder === Builders.DevServer) {
24+
for (const [, options] of allTargetOptions(target, false)) {
25+
options['buildTarget'] = options['browserTarget'];
26+
delete options['browserTarget'];
27+
}
28+
}
29+
}
30+
}
31+
});
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
12+
13+
function createWorkSpaceConfig(tree: UnitTestTree) {
14+
const angularConfig: WorkspaceSchema = {
15+
version: 1,
16+
projects: {
17+
app: {
18+
root: '/project/lib',
19+
sourceRoot: '/project/app/src',
20+
projectType: ProjectType.Application,
21+
prefix: 'app',
22+
architect: {
23+
'app-shell': {
24+
builder: Builders.AppShell,
25+
options: {
26+
browserTarget: 'app:build',
27+
serverTarget: 'app:server',
28+
route: '',
29+
},
30+
configurations: {
31+
production: {
32+
browserTarget: 'app:build:production',
33+
serverTarget: 'app:server:production',
34+
},
35+
},
36+
},
37+
serve: {
38+
builder: Builders.DevServer,
39+
options: {
40+
browserTarget: 'app:build:development',
41+
},
42+
configurations: {
43+
production: {
44+
browserTarget: 'app:build:production',
45+
},
46+
},
47+
},
48+
i18n: {
49+
builder: Builders.ExtractI18n,
50+
options: {
51+
browserTarget: 'app:build:production',
52+
},
53+
},
54+
},
55+
},
56+
},
57+
};
58+
59+
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
60+
}
61+
62+
describe(`Migration to update 'angular.json'.`, () => {
63+
const schematicName = 'update-workspace-config';
64+
const schematicRunner = new SchematicTestRunner(
65+
'migrations',
66+
require.resolve('../migration-collection.json'),
67+
);
68+
69+
let tree: UnitTestTree;
70+
beforeEach(() => {
71+
tree = new UnitTestTree(new EmptyTree());
72+
createWorkSpaceConfig(tree);
73+
});
74+
75+
it(`should replace 'browserTarget' when using '@angular-devkit/build-angular:dev-server'`, async () => {
76+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
77+
const {
78+
projects: { app },
79+
} = JSON.parse(newTree.readContent('/angular.json'));
80+
81+
const { browserTarget, buildTarget } = app.architect['serve'].options;
82+
expect(browserTarget).toBeUndefined();
83+
expect(buildTarget).toBe('app:build:development');
84+
85+
const { browserTarget: browserTargetProd, buildTarget: buildTargetProd } =
86+
app.architect['serve'].configurations['production'];
87+
expect(browserTargetProd).toBeUndefined();
88+
expect(buildTargetProd).toBe('app:build:production');
89+
});
90+
91+
it(`should replace 'browserTarget' when using '@angular-devkit/build-angular:extract-i18n'`, async () => {
92+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
93+
const {
94+
projects: { app },
95+
} = JSON.parse(newTree.readContent('/angular.json'));
96+
97+
const { browserTarget, buildTarget } = app.architect['i18n'].options;
98+
expect(browserTarget).toBeUndefined();
99+
expect(buildTarget).toBe('app:build:production');
100+
});
101+
102+
it(`should not replace 'browserTarget' when using other builders`, async () => {
103+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
104+
const {
105+
projects: { app },
106+
} = JSON.parse(newTree.readContent('/angular.json'));
107+
108+
const { browserTarget, buildTarget } = app.architect['app-shell'].options;
109+
expect(browserTarget).toBe('app:build');
110+
expect(buildTarget).toBeUndefined();
111+
});
112+
});

0 commit comments

Comments
 (0)
Please sign in to comment.