Skip to content

Commit c74e618

Browse files
clydinalan-agius4
authored andcommitted
refactor(@angular-devkit/build-angular): export application builder for experimental programmatic usage
Similar to the existing Webpack-based `browser` builder, the new `application` builder is also exported from the `@angular-devkit/build-angular` package for use programmatically. As is the case for the existing builder JavaScript exports from the package, the new export (`buildApplication`) is also considered experimental and does not provide the support nor semver guarantees that the builders have when used via `angular.json` configuration. The usage of the `plugins` parameter of the `buildApplication` allows adding esbuild compatible plugins to the end of the plugin list for the main application code bundling. However, usage of the parameter may result in unexpected application output and/or build failures. Stable and supported methods for build process extension are being evaluated for a future release.
1 parent f1cf952 commit c74e618

File tree

8 files changed

+142
-27
lines changed

8 files changed

+142
-27
lines changed

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

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,54 @@ import type { ConfigOptions } from 'karma';
1010
import { Configuration } from 'webpack';
1111
import { DevServerBuildOutput } from '@angular-devkit/build-webpack';
1212
import { Observable } from 'rxjs';
13+
import { OutputFile } from 'esbuild';
14+
import type { Plugin as Plugin_2 } from 'esbuild';
1315
import webpack from 'webpack';
1416
import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
1517

18+
// @public
19+
export interface ApplicationBuilderOptions {
20+
allowedCommonJsDependencies?: string[];
21+
aot?: boolean;
22+
appShell?: boolean;
23+
assets?: AssetPattern_2[];
24+
baseHref?: string;
25+
browser: string;
26+
budgets?: Budget_2[];
27+
crossOrigin?: CrossOrigin_2;
28+
deleteOutputPath?: boolean;
29+
externalDependencies?: string[];
30+
extractLicenses?: boolean;
31+
fileReplacements?: FileReplacement_2[];
32+
i18nDuplicateTranslation?: I18NTranslation_2;
33+
i18nMissingTranslation?: I18NTranslation_2;
34+
index: IndexUnion_2;
35+
inlineStyleLanguage?: InlineStyleLanguage_2;
36+
localize?: Localize_2;
37+
namedChunks?: boolean;
38+
optimization?: OptimizationUnion_2;
39+
outputHashing?: OutputHashing_2;
40+
outputPath: string;
41+
poll?: number;
42+
polyfills?: string[];
43+
prerender?: PrerenderUnion;
44+
preserveSymlinks?: boolean;
45+
progress?: boolean;
46+
scripts?: ScriptElement_2[];
47+
server?: string;
48+
serviceWorker?: ServiceWorker_2;
49+
sourceMap?: SourceMapUnion_2;
50+
ssr?: ServiceWorker_2;
51+
statsJson?: boolean;
52+
stylePreprocessorOptions?: StylePreprocessorOptions_2;
53+
styles?: StyleElement_2[];
54+
subresourceIntegrity?: boolean;
55+
tsConfig: string;
56+
verbose?: boolean;
57+
watch?: boolean;
58+
webWorkerTsConfig?: string;
59+
}
60+
1661
// @public (undocumented)
1762
export type AssetPattern = AssetPatternObject | string;
1863

@@ -94,6 +139,15 @@ export interface Budget {
94139
warning?: string;
95140
}
96141

142+
// @public
143+
export function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin_2[]): AsyncIterable<BuilderOutput & {
144+
outputFiles?: BuildOutputFile[];
145+
assetFiles?: {
146+
source: string;
147+
destination: string;
148+
}[];
149+
}>;
150+
97151
// @public
98152
export enum CrossOrigin {
99153
// (undocumented)
@@ -149,7 +203,7 @@ export function executeDevServerBuilder(options: DevServerBuilderOptions, contex
149203
webpackConfiguration?: ExecutionTransformer<Configuration>;
150204
logging?: WebpackLoggingCallback;
151205
indexHtml?: IndexHtmlTransform;
152-
}): Observable<DevServerBuilderOutput>;
206+
}, plugins?: Plugin_2[]): Observable<DevServerBuilderOutput>;
153207

154208
// @public
155209
export function executeExtractI18nBuilder(options: ExtractI18nBuilderOptions, context: BuilderContext, transforms?: {
@@ -201,25 +255,25 @@ export interface FileReplacement {
201255

202256
// @public
203257
export interface KarmaBuilderOptions {
204-
assets?: AssetPattern_2[];
258+
assets?: AssetPattern_3[];
205259
browsers?: string;
206260
codeCoverage?: boolean;
207261
codeCoverageExclude?: string[];
208262
exclude?: string[];
209-
fileReplacements?: FileReplacement_2[];
263+
fileReplacements?: FileReplacement_3[];
210264
include?: string[];
211-
inlineStyleLanguage?: InlineStyleLanguage_2;
265+
inlineStyleLanguage?: InlineStyleLanguage_3;
212266
karmaConfig?: string;
213267
main?: string;
214268
poll?: number;
215269
polyfills?: Polyfills_2;
216270
preserveSymlinks?: boolean;
217271
progress?: boolean;
218272
reporters?: string[];
219-
scripts?: ScriptElement_2[];
220-
sourceMap?: SourceMapUnion_2;
221-
stylePreprocessorOptions?: StylePreprocessorOptions_2;
222-
styles?: StyleElement_2[];
273+
scripts?: ScriptElement_3[];
274+
sourceMap?: SourceMapUnion_3;
275+
stylePreprocessorOptions?: StylePreprocessorOptions_3;
276+
styles?: StyleElement_3[];
223277
tsConfig: string;
224278
watch?: boolean;
225279
webWorkerTsConfig?: string;
@@ -276,30 +330,30 @@ export interface ProtractorBuilderOptions {
276330

277331
// @public (undocumented)
278332
export interface ServerBuilderOptions {
279-
assets?: AssetPattern_3[];
333+
assets?: AssetPattern_4[];
280334
buildOptimizer?: boolean;
281335
deleteOutputPath?: boolean;
282336
// @deprecated
283337
deployUrl?: string;
284338
externalDependencies?: string[];
285339
extractLicenses?: boolean;
286-
fileReplacements?: FileReplacement_3[];
287-
i18nDuplicateTranslation?: I18NTranslation_2;
288-
i18nMissingTranslation?: I18NTranslation_2;
289-
inlineStyleLanguage?: InlineStyleLanguage_3;
290-
localize?: Localize_2;
340+
fileReplacements?: FileReplacement_4[];
341+
i18nDuplicateTranslation?: I18NTranslation_3;
342+
i18nMissingTranslation?: I18NTranslation_3;
343+
inlineStyleLanguage?: InlineStyleLanguage_4;
344+
localize?: Localize_3;
291345
main: string;
292346
namedChunks?: boolean;
293-
optimization?: OptimizationUnion_2;
294-
outputHashing?: OutputHashing_2;
347+
optimization?: OptimizationUnion_3;
348+
outputHashing?: OutputHashing_3;
295349
outputPath: string;
296350
poll?: number;
297351
preserveSymlinks?: boolean;
298352
progress?: boolean;
299353
resourcesOutputPath?: string;
300-
sourceMap?: SourceMapUnion_3;
354+
sourceMap?: SourceMapUnion_4;
301355
statsJson?: boolean;
302-
stylePreprocessorOptions?: StylePreprocessorOptions_3;
356+
stylePreprocessorOptions?: StylePreprocessorOptions_4;
303357
tsConfig: string;
304358
vendorChunk?: boolean;
305359
verbose?: boolean;

packages/angular_devkit/build_angular/src/builders/application/index.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
10+
import type { Plugin } from 'esbuild';
1011
import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
1112
import { purgeStaleBuildCache } from '../../utils/purge-cache';
1213
import { assertCompatibleAngularVersion } from '../../utils/version';
@@ -15,13 +16,16 @@ import { executeBuild } from './execute-build';
1516
import { ApplicationBuilderInternalOptions, normalizeOptions } from './options';
1617
import { Schema as ApplicationBuilderOptions } from './schema';
1718

19+
export { ApplicationBuilderOptions };
20+
1821
export async function* buildApplicationInternal(
1922
options: ApplicationBuilderInternalOptions,
2023
// TODO: Integrate abort signal support into builder system
2124
context: BuilderContext & { signal?: AbortSignal },
2225
infrastructureSettings?: {
2326
write?: boolean;
2427
},
28+
plugins?: Plugin[],
2529
): AsyncIterable<
2630
BuilderOutput & {
2731
outputFiles?: BuildOutputFile[];
@@ -42,7 +46,7 @@ export async function* buildApplicationInternal(
4246
return;
4347
}
4448

45-
const normalizedOptions = await normalizeOptions(context, projectName, options);
49+
const normalizedOptions = await normalizeOptions(context, projectName, options, plugins);
4650

4751
yield* runEsBuildBuildAction(
4852
(rebuildState) => executeBuild(normalizedOptions, context, rebuildState),
@@ -69,16 +73,31 @@ export async function* buildApplicationInternal(
6973
);
7074
}
7175

76+
/**
77+
* Builds an application using the `application` builder with the provided
78+
* options.
79+
*
80+
* Usage of the `plugins` parameter is NOT supported and may cause unexpected
81+
* build output or build failures.
82+
*
83+
* @experimental Direct usage of this function is considered experimental.
84+
*
85+
* @param options The options defined by the builder's schema to use.
86+
* @param context An Architect builder context instance.
87+
* @param plugins An array of plugins to apply to the main code bundling.
88+
* @returns The build output results of the build.
89+
*/
7290
export function buildApplication(
7391
options: ApplicationBuilderOptions,
7492
context: BuilderContext,
93+
plugins?: Plugin[],
7594
): AsyncIterable<
7695
BuilderOutput & {
7796
outputFiles?: BuildOutputFile[];
7897
assetFiles?: { source: string; destination: string }[];
7998
}
8099
> {
81-
return buildApplicationInternal(options, context);
100+
return buildApplicationInternal(options, context, undefined, plugins);
82101
}
83102

84103
export default createBuilder(buildApplication);

packages/angular_devkit/build_angular/src/builders/application/options.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { BuilderContext } from '@angular-devkit/architect';
10+
import type { Plugin } from 'esbuild';
1011
import { createRequire } from 'node:module';
1112
import path from 'node:path';
1213
import {
@@ -71,13 +72,15 @@ export type ApplicationBuilderInternalOptions = Omit<
7172
* @param context The context for current builder execution.
7273
* @param projectName The name of the project for the current execution.
7374
* @param options An object containing the options to use for the build.
75+
* @param plugins An optional array of programmatically supplied build plugins.
7476
* @returns An object containing normalized options required to perform the build.
7577
*/
7678
// eslint-disable-next-line max-lines-per-function
7779
export async function normalizeOptions(
7880
context: BuilderContext,
7981
projectName: string,
8082
options: ApplicationBuilderInternalOptions,
83+
plugins?: Plugin[],
8184
) {
8285
const workspaceRoot = context.workspaceRoot;
8386
const projectMetadata = await context.getProjectMetadata(projectName);
@@ -295,6 +298,7 @@ export async function normalizeOptions(
295298
namedChunks,
296299
budgets: budgets?.length ? budgets : undefined,
297300
publicPath: deployUrl ? deployUrl : undefined,
301+
plugins: plugins?.length ? plugins : undefined,
298302
};
299303
}
300304

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
10+
import type { Plugin } from 'esbuild';
1011
import { constants as fsConstants } from 'node:fs';
1112
import fs from 'node:fs/promises';
1213
import path from 'node:path';
@@ -29,6 +30,7 @@ export async function* buildEsbuildBrowser(
2930
infrastructureSettings?: {
3031
write?: boolean;
3132
},
33+
plugins?: Plugin[],
3234
): AsyncIterable<
3335
BuilderOutput & {
3436
outputFiles?: BuildOutputFile[];
@@ -40,9 +42,14 @@ export async function* buildEsbuildBrowser(
4042
const normalizedOptions = normalizeOptions(userOptions);
4143
const fullOutputPath = path.join(context.workspaceRoot, normalizedOptions.outputPath);
4244

43-
for await (const result of buildApplicationInternal(normalizedOptions, context, {
44-
write: false,
45-
})) {
45+
for await (const result of buildApplicationInternal(
46+
normalizedOptions,
47+
context,
48+
{
49+
write: false,
50+
},
51+
plugins,
52+
)) {
4653
if (infrastructureSettings?.write !== false && result.outputFiles) {
4754
// Write output files
4855
await writeResultFiles(result.outputFiles, result.assetFiles, fullOutputPath);

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import type { BuilderContext } from '@angular-devkit/architect';
10+
import type { Plugin } from 'esbuild';
1011
import { EMPTY, Observable, defer, switchMap } from 'rxjs';
1112
import type { ExecutionTransformer } from '../../transforms';
1213
import { checkPort } from '../../utils/check-port';
@@ -33,6 +34,7 @@ export function execute(
3334
logging?: import('@angular-devkit/build-webpack').WebpackLoggingCallback;
3435
indexHtml?: IndexHtmlTransform;
3536
} = {},
37+
plugins?: Plugin[],
3638
): Observable<DevServerBuilderOutput> {
3739
// Determine project name from builder context target
3840
const projectName = context.target?.project;
@@ -50,11 +52,23 @@ export function execute(
5052
builderName === '@angular-devkit/build-angular:browser-esbuild' ||
5153
normalizedOptions.forceEsbuild
5254
) {
55+
if (Object.keys(transforms).length > 0) {
56+
throw new Error(
57+
'The `application` and `browser-esbuild` builders do not support Webpack transforms.',
58+
);
59+
}
60+
5361
return defer(() => import('./vite-server')).pipe(
54-
switchMap(({ serveWithVite }) => serveWithVite(normalizedOptions, builderName, context)),
62+
switchMap(({ serveWithVite }) =>
63+
serveWithVite(normalizedOptions, builderName, context, plugins),
64+
),
5565
);
5666
}
5767

68+
if (plugins?.length) {
69+
throw new Error('Only the `application` and `browser-esbuild` builders support plugins.');
70+
}
71+
5872
// Use Webpack for all other browser targets
5973
return defer(() => import('./webpack-server')).pipe(
6074
switchMap(({ serveWebpackBrowser }) =>

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import type { BuilderContext } from '@angular-devkit/architect';
1010
import type { json, logging } from '@angular-devkit/core';
11+
import type { Plugin } from 'esbuild';
1112
import { lookup as lookupMimeType } from 'mrmime';
1213
import assert from 'node:assert';
1314
import { BinaryLike, createHash } from 'node:crypto';
@@ -48,6 +49,7 @@ export async function* serveWithVite(
4849
serverOptions: NormalizedDevServerOptions,
4950
builderName: string,
5051
context: BuilderContext,
52+
plugins?: Plugin[],
5153
): AsyncIterableIterator<DevServerBuilderOutput> {
5254
// Get the browser configuration from the target name.
5355
const rawBrowserOptions = (await context.getTargetOptions(
@@ -115,9 +117,14 @@ export async function* serveWithVite(
115117
const generatedFiles = new Map<string, OutputFileRecord>();
116118
const assetFiles = new Map<string, string>();
117119
// TODO: Switch this to an architect schedule call when infrastructure settings are supported
118-
for await (const result of buildEsbuildBrowser(browserOptions, context, {
119-
write: false,
120-
})) {
120+
for await (const result of buildEsbuildBrowser(
121+
browserOptions,
122+
context,
123+
{
124+
write: false,
125+
},
126+
plugins,
127+
)) {
121128
assert(result.outputFiles, 'Builder did not provide result files.');
122129

123130
// Analyze result files for changes

packages/angular_devkit/build_angular/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export {
2929
BrowserBuilderOutput,
3030
} from './builders/browser';
3131

32+
export { buildApplication, ApplicationBuilderOptions } from './builders/application';
33+
3234
export {
3335
executeDevServerBuilder,
3436
DevServerBuilderOptions,

packages/angular_devkit/build_angular/src/tools/esbuild/application-code-bundle.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ export function createBrowserCodeBundleOptions(
165165
);
166166
}
167167

168+
if (options.plugins) {
169+
buildOptions.plugins?.push(...options.plugins);
170+
}
171+
168172
return buildOptions;
169173
}
170174

@@ -343,6 +347,10 @@ export function createServerCodeBundleOptions(
343347
);
344348
}
345349

350+
if (options.plugins) {
351+
buildOptions.plugins.push(...options.plugins);
352+
}
353+
346354
return buildOptions;
347355
}
348356

0 commit comments

Comments
 (0)