Skip to content

Commit b387963

Browse files
committed
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 c48982d commit b387963

File tree

7 files changed

+70
-9
lines changed

7 files changed

+70
-9
lines changed

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)