Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@angular-devkit/build-angular): add buildTarget option to dev-server and extract-i18n builders #25999

Merged
merged 1 commit into from Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions goldens/public-api/angular_devkit/build_angular/index.md
Expand Up @@ -107,7 +107,9 @@ export enum CrossOrigin {
// @public
export interface DevServerBuilderOptions {
allowedHosts?: string[];
browserTarget: string;
// @deprecated
browserTarget?: string;
buildTarget?: string;
disableHostCheck?: boolean;
forceEsbuild?: boolean;
headers?: {
Expand Down Expand Up @@ -176,7 +178,9 @@ export type ExecutionTransformer<T> = (input: T) => T | Promise<T>;

// @public
export interface ExtractI18nBuilderOptions {
browserTarget: string;
// @deprecated
browserTarget?: string;
buildTarget?: string;
format?: Format;
outFile?: string;
outputPath?: string;
Expand Down
Expand Up @@ -44,7 +44,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => {

const builderAbort = new AbortController();
const buildCount = await firstValueFrom(
harness.execute({ outputLogsOnFailure: true, signal: builderAbort.signal }).pipe(
harness.execute({ outputLogsOnFailure: false, signal: builderAbort.signal }).pipe(
timeout(20_000),
concatMap(async ({ result, logs }, index) => {
switch (index) {
Expand Down
Expand Up @@ -74,7 +74,7 @@ async function initialize(
await purgeStaleBuildCache(context);

const normalizedOptions = await normalizeOptions(context, projectName, initialOptions);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.browserTarget);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);

if (
!normalizedOptions.disableHostCheck &&
Expand Down
Expand Up @@ -34,7 +34,8 @@ export async function normalizeOptions(

const cacheOptions = normalizeCacheOptions(projectMetadata, workspaceRoot);

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

// Initial options to keep
const {
Expand All @@ -60,7 +61,7 @@ export async function normalizeOptions(

// Return all the normalized options
return {
browserTarget,
buildTarget,
host: host ?? 'localhost',
port: port ?? 4200,
poll,
Expand Down
Expand Up @@ -7,6 +7,12 @@
"browserTarget": {
"type": "string",
"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`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
"x-deprecated": "Use 'buildTarget' instead."
},
"buildTarget": {
"type": "string",
"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`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
Expand Down Expand Up @@ -103,5 +109,5 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"]
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
}
Expand Up @@ -30,7 +30,7 @@ export const DEV_SERVER_BUILDER_INFO = Object.freeze({
* supports parallel test execution.
*/
export const BASE_OPTIONS = Object.freeze<Schema>({
browserTarget: 'test:build',
buildTarget: 'test:build',
port: 0,
});

Expand Down
Expand Up @@ -51,7 +51,7 @@ export async function* serveWithVite(
): AsyncIterableIterator<DevServerBuilderOutput> {
// Get the browser configuration from the target name.
const rawBrowserOptions = (await context.getTargetOptions(
serverOptions.browserTarget,
serverOptions.buildTarget,
)) as json.JsonObject & BrowserBuilderOptions;

const browserOptions = (await context.validateOptions(
Expand Down
Expand Up @@ -86,7 +86,7 @@ export function serveWebpackBrowser(

// Get the browser configuration from the target name.
const rawBrowserOptions = (await context.getTargetOptions(
options.browserTarget,
options.buildTarget,
)) as json.JsonObject & BrowserBuilderSchema;

if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== OutputHashing.None) {
Expand Down
Expand Up @@ -29,9 +29,9 @@ export async function extractMessages(
}> {
const messages: LocalizeMessage[] = [];

// Setup the build options for the application based on the browserTarget option
// Setup the build options for the application based on the buildTarget option
const buildOptions = (await context.validateOptions(
await context.getTargetOptions(options.browserTarget),
await context.getTargetOptions(options.buildTarget),
builderName,
)) as unknown as ApplicationBuilderInternalOptions;
buildOptions.optimization = false;
Expand Down
Expand Up @@ -43,9 +43,8 @@ export async function execute(
// The package is a peer dependency and might not be present
let localizeToolsModule;
try {
localizeToolsModule = await loadEsmModule<typeof import('@angular/localize/tools')>(
'@angular/localize/tools',
);
localizeToolsModule =
await loadEsmModule<typeof import('@angular/localize/tools')>('@angular/localize/tools');
} catch {
return {
success: false,
Expand All @@ -57,7 +56,7 @@ export async function execute(

// Normalize options
const normalizedOptions = await normalizeOptions(context, projectName, options);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.browserTarget);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);

// Extract messages based on configured builder
let extractionResult;
Expand Down
Expand Up @@ -33,7 +33,8 @@ export async function normalizeOptions(
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = path.join(workspaceRoot, (projectMetadata.root as string | undefined) ?? '');

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

const i18nOptions = createI18nOptions(projectMetadata);

Expand Down Expand Up @@ -62,7 +63,7 @@ export async function normalizeOptions(
return {
workspaceRoot,
projectRoot,
browserTarget,
buildTarget,
i18nOptions,
format,
outFile,
Expand Down
Expand Up @@ -7,6 +7,12 @@
"browserTarget": {
"type": "string",
"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`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
"x-deprecated": "Use 'buildTarget' instead."
},
"buildTarget": {
"type": "string",
"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`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"format": {
Expand All @@ -30,5 +36,5 @@
}
},
"additionalProperties": false,
"required": ["browserTarget"]
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
}
Expand Up @@ -41,7 +41,7 @@ export async function extractMessages(
let useLegacyIds = true;

const browserOptions = await context.validateOptions(
await context.getTargetOptions(options.browserTarget),
await context.getTargetOptions(options.buildTarget),
builderName,
);

Expand Down
Expand Up @@ -76,7 +76,7 @@ export interface BuildOptions {

export interface WebpackDevServerOptions
extends BuildOptions,
Omit<DevServerSchema, 'optimization' | 'sourceMap' | 'browserTarget'> {}
Omit<DevServerSchema, 'optimization' | 'sourceMap' | 'buildTarget' | 'browserTarget'> {}

export interface WebpackConfigOptions<T = BuildOptions> {
root: string;
Expand Down
6 changes: 3 additions & 3 deletions packages/schematics/angular/application/index.ts
Expand Up @@ -263,17 +263,17 @@ function addAppToWorkspaceFile(
options: {},
configurations: {
production: {
browserTarget: `${options.name}:build:production`,
buildTarget: `${options.name}:build:production`,
},
development: {
browserTarget: `${options.name}:build:development`,
buildTarget: `${options.name}:build:development`,
},
},
},
'extract-i18n': {
builder: Builders.ExtractI18n,
options: {
browserTarget: `${options.name}:build`,
buildTarget: `${options.name}:build`,
},
},
test: options.minimal
Expand Down
Expand Up @@ -4,6 +4,11 @@
"version": "17.0.0",
"factory": "./update-17/replace-nguniversal-builders",
"description": "Replace usages of '@nguniversal/builders' with '@angular-devkit/build-angular'."
},
"update-workspace-config": {
"version": "17.0.0",
"factory": "./update-17/update-workspace-config",
"description": "Replace deprecated options in 'angular.json'."
}
}
}
@@ -0,0 +1,32 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { Rule, chain } from '@angular-devkit/schematics';
import { removePackageJsonDependency } from '../../utility/dependencies';
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
import { Builders, ProjectType } from '../../utility/workspace-models';

export default function (): Rule {
return updateWorkspace((workspace) => {
for (const [, project] of workspace.projects) {
if (project.extensions.projectType !== ProjectType.Application) {
// Only interested in application projects since these changes only effects application builders
continue;
}

for (const [, target] of project.targets) {
if (target.builder === Builders.ExtractI18n || target.builder === Builders.DevServer) {
for (const [, options] of allTargetOptions(target, false)) {
options['buildTarget'] = options['browserTarget'];
delete options['browserTarget'];
}
}
}
}
});
}
@@ -0,0 +1,112 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';

function createWorkSpaceConfig(tree: UnitTestTree) {
const angularConfig: WorkspaceSchema = {
version: 1,
projects: {
app: {
root: '/project/lib',
sourceRoot: '/project/app/src',
projectType: ProjectType.Application,
prefix: 'app',
architect: {
'app-shell': {
builder: Builders.AppShell,
options: {
browserTarget: 'app:build',
serverTarget: 'app:server',
route: '',
},
configurations: {
production: {
browserTarget: 'app:build:production',
serverTarget: 'app:server:production',
},
},
},
serve: {
builder: Builders.DevServer,
options: {
browserTarget: 'app:build:development',
},
configurations: {
production: {
browserTarget: 'app:build:production',
},
},
},
i18n: {
builder: Builders.ExtractI18n,
options: {
browserTarget: 'app:build:production',
},
},
},
},
},
};

tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
}

describe(`Migration to update 'angular.json'.`, () => {
const schematicName = 'update-workspace-config';
const schematicRunner = new SchematicTestRunner(
'migrations',
require.resolve('../migration-collection.json'),
);

let tree: UnitTestTree;
beforeEach(() => {
tree = new UnitTestTree(new EmptyTree());
createWorkSpaceConfig(tree);
});

it(`should replace 'browserTarget' when using '@angular-devkit/build-angular:dev-server'`, async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const {
projects: { app },
} = JSON.parse(newTree.readContent('/angular.json'));

const { browserTarget, buildTarget } = app.architect['serve'].options;
expect(browserTarget).toBeUndefined();
expect(buildTarget).toBe('app:build:development');

const { browserTarget: browserTargetProd, buildTarget: buildTargetProd } =
app.architect['serve'].configurations['production'];
expect(browserTargetProd).toBeUndefined();
expect(buildTargetProd).toBe('app:build:production');
});

it(`should replace 'browserTarget' when using '@angular-devkit/build-angular:extract-i18n'`, async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const {
projects: { app },
} = JSON.parse(newTree.readContent('/angular.json'));

const { browserTarget, buildTarget } = app.architect['i18n'].options;
expect(browserTarget).toBeUndefined();
expect(buildTarget).toBe('app:build:production');
});

it(`should not replace 'browserTarget' when using other builders`, async () => {
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
const {
projects: { app },
} = JSON.parse(newTree.readContent('/angular.json'));

const { browserTarget, buildTarget } = app.architect['app-shell'].options;
expect(browserTarget).toBe('app:build');
expect(buildTarget).toBeUndefined();
});
});