Skip to content

Commit 7af63b4

Browse files
committed
refactor(@angular-devkit/build-angular): reduce custom code in browser-esbuild implementation
The implementation of the `browser-esbuild` builder is now a small wrapper around the `application` builder. The custom file writing code is no longer required with the availability of the additional output path options for `application` builder. This also allows the internal `browser-esbuild` programmatic interface to retain its architect-based signature.
1 parent 8d650d3 commit 7af63b4

File tree

7 files changed

+46
-101
lines changed

7 files changed

+46
-101
lines changed

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

+12-67
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,14 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import type { ApplicationBuilderOptions } from '@angular/build';
10-
import {
11-
Result,
12-
ResultKind,
13-
buildApplicationInternal,
14-
deleteOutputDir,
15-
emitFilesToDisk,
16-
} from '@angular/build/private';
17-
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
9+
import { type ApplicationBuilderOptions, buildApplication } from '@angular/build';
10+
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
1811
import type { Plugin } from 'esbuild';
19-
import fs from 'node:fs/promises';
20-
import path from 'node:path';
2112
import { logBuilderStatusWarnings } from './builder-status-warnings';
2213
import type { Schema as BrowserBuilderOptions } from './schema';
2314

15+
export type { BrowserBuilderOptions };
16+
2417
type OutputPathClass = Exclude<ApplicationBuilderOptions['outputPath'], string | undefined>;
2518

2619
/**
@@ -37,57 +30,15 @@ export async function* buildEsbuildBrowser(
3730
write?: boolean;
3831
},
3932
plugins?: Plugin[],
40-
): AsyncIterable<Result> {
33+
): AsyncIterable<BuilderOutput> {
4134
// Inform user of status of builder and options
4235
logBuilderStatusWarnings(userOptions, context);
43-
const normalizedOptions = normalizeOptions(userOptions);
44-
const { deleteOutputPath, outputPath } = normalizedOptions;
45-
const fullOutputPath = path.join(context.workspaceRoot, outputPath.base);
46-
47-
if (deleteOutputPath && infrastructureSettings?.write !== false) {
48-
await deleteOutputDir(context.workspaceRoot, outputPath.base);
49-
}
50-
51-
for await (const result of buildApplicationInternal(
52-
normalizedOptions,
53-
context,
54-
plugins && { codePlugins: plugins },
55-
)) {
56-
// Write the file directly from this builder to maintain webpack output compatibility
57-
// and not output browser files into '/browser'.
58-
if (
59-
infrastructureSettings?.write !== false &&
60-
(result.kind === ResultKind.Full || result.kind === ResultKind.Incremental)
61-
) {
62-
const directoryExists = new Set<string>();
63-
// Writes the output file to disk and ensures the containing directories are present
64-
await emitFilesToDisk(Object.entries(result.files), async ([filePath, file]) => {
65-
// Ensure output subdirectories exist
66-
const basePath = path.dirname(filePath);
67-
if (basePath && !directoryExists.has(basePath)) {
68-
await fs.mkdir(path.join(fullOutputPath, basePath), { recursive: true });
69-
directoryExists.add(basePath);
70-
}
71-
72-
if (file.origin === 'memory') {
73-
// Write file contents
74-
await fs.writeFile(path.join(fullOutputPath, filePath), file.contents);
75-
} else {
76-
// Copy file contents
77-
await fs.copyFile(
78-
file.inputPath,
79-
path.join(fullOutputPath, filePath),
80-
fs.constants.COPYFILE_FICLONE,
81-
);
82-
}
83-
});
84-
}
8536

86-
yield result;
87-
}
37+
const normalizedOptions = convertBrowserOptions(userOptions);
38+
yield* buildApplication(normalizedOptions, context, { codePlugins: plugins });
8839
}
8940

90-
function normalizeOptions(
41+
export function convertBrowserOptions(
9142
options: BrowserBuilderOptions,
9243
): Omit<ApplicationBuilderOptions, 'outputPath'> & { outputPath: OutputPathClass } {
9344
const {
@@ -96,6 +47,7 @@ function normalizeOptions(
9647
ngswConfigPath,
9748
serviceWorker,
9849
polyfills,
50+
resourcesOutputPath,
9951
...otherOptions
10052
} = options;
10153

@@ -106,18 +58,11 @@ function normalizeOptions(
10658
outputPath: {
10759
base: outputPath,
10860
browser: '',
61+
server: '',
62+
media: resourcesOutputPath ?? 'media',
10963
},
11064
...otherOptions,
11165
};
11266
}
11367

114-
export async function* buildEsbuildBrowserArchitect(
115-
options: BrowserBuilderOptions,
116-
context: BuilderContext,
117-
) {
118-
for await (const result of buildEsbuildBrowser(options, context)) {
119-
yield { success: result.kind !== ResultKind.Failure };
120-
}
121-
}
122-
123-
export default createBuilder<BrowserBuilderOptions>(buildEsbuildBrowserArchitect);
68+
export default createBuilder<BrowserBuilderOptions>(buildEsbuildBrowser);

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/assets_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { buildEsbuildBrowserArchitect } from '../../index';
9+
import { buildEsbuildBrowser } from '../../index';
1010
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
1111

12-
describeBuilder(buildEsbuildBrowserArchitect, BROWSER_BUILDER_INFO, (harness) => {
12+
describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
1313
describe('Option: "assets"', () => {
1414
beforeEach(async () => {
1515
// Application code is not needed for asset tests

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/delete-output-path_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { buildEsbuildBrowserArchitect } from '../../index';
9+
import { buildEsbuildBrowser } from '../../index';
1010
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
1111

12-
describeBuilder(buildEsbuildBrowserArchitect, BROWSER_BUILDER_INFO, (harness) => {
12+
describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
1313
describe('Option: "deleteOutputPath"', () => {
1414
beforeEach(async () => {
1515
// Application code is not needed for asset tests

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/deploy-url_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { buildEsbuildBrowserArchitect } from '../../index';
9+
import { buildEsbuildBrowser } from '../../index';
1010
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
1111

12-
describeBuilder(buildEsbuildBrowserArchitect, BROWSER_BUILDER_INFO, (harness) => {
12+
describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
1313
describe('Option: "deployUrl"', () => {
1414
beforeEach(async () => {
1515
// Add a global stylesheet to test link elements

packages/angular_devkit/build_angular/src/builders/browser-esbuild/tests/options/main_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { buildEsbuildBrowserArchitect } from '../../index';
9+
import { buildEsbuildBrowser } from '../../index';
1010
import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup';
1111

12-
describeBuilder(buildEsbuildBrowserArchitect, BROWSER_BUILDER_INFO, (harness) => {
12+
describeBuilder(buildEsbuildBrowser, BROWSER_BUILDER_INFO, (harness) => {
1313
describe('Option: "main"', () => {
1414
it('uses a provided TypeScript file', async () => {
1515
harness.useTarget('build', {

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,16 @@ export function execute(
8888
return defer(() =>
8989
Promise.all([import('@angular/build/private'), import('../browser-esbuild')]),
9090
).pipe(
91-
switchMap(([{ serveWithVite, buildApplicationInternal }, { buildEsbuildBrowser }]) =>
91+
switchMap(([{ serveWithVite, buildApplicationInternal }, { convertBrowserOptions }]) =>
9292
serveWithVite(
9393
normalizedOptions,
9494
builderName,
9595
(options, context, codePlugins) => {
9696
return builderName === '@angular-devkit/build-angular:browser-esbuild'
9797
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
98-
buildEsbuildBrowser(options as any, context, { write: false }, codePlugins)
98+
buildApplicationInternal(convertBrowserOptions(options as any), context, {
99+
codePlugins,
100+
})
99101
: buildApplicationInternal(options, context, { codePlugins });
100102
},
101103
context,

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

+22-24
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {
10-
type ApplicationBuilderInternalOptions,
11-
ResultFile,
12-
ResultKind,
13-
buildApplicationInternal,
14-
} from '@angular/build/private';
9+
import type { ApplicationBuilderOptions } from '@angular/build';
10+
import { ResultFile, ResultKind, buildApplicationInternal } from '@angular/build/private';
1511
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
1612
import type { MessageExtractor } from '@angular/localize/tools';
1713
import type { BuilderContext } from '@angular-devkit/architect';
1814
import nodePath from 'node:path';
19-
import { buildEsbuildBrowser } from '../browser-esbuild';
15+
import { BrowserBuilderOptions, convertBrowserOptions } from '../browser-esbuild';
2016
import type { NormalizedExtractI18nOptions } from './options';
2117

2218
export async function extractMessages(
@@ -33,30 +29,32 @@ export async function extractMessages(
3329
const messages: LocalizeMessage[] = [];
3430

3531
// Setup the build options for the application based on the buildTarget option
36-
const buildOptions = (await context.validateOptions(
37-
await context.getTargetOptions(options.buildTarget),
38-
builderName,
39-
)) as unknown as ApplicationBuilderInternalOptions;
32+
let buildOptions;
33+
if (builderName === '@angular-devkit/build-angular:application') {
34+
buildOptions = (await context.validateOptions(
35+
await context.getTargetOptions(options.buildTarget),
36+
builderName,
37+
)) as unknown as ApplicationBuilderOptions;
38+
} else {
39+
buildOptions = convertBrowserOptions(
40+
(await context.validateOptions(
41+
await context.getTargetOptions(options.buildTarget),
42+
builderName,
43+
)) as unknown as BrowserBuilderOptions,
44+
);
45+
}
46+
4047
buildOptions.optimization = false;
4148
buildOptions.sourceMap = { scripts: true, vendor: true, styles: false };
4249
buildOptions.localize = false;
4350
buildOptions.budgets = undefined;
4451
buildOptions.index = false;
4552
buildOptions.serviceWorker = false;
53+
buildOptions.ssr = false;
54+
buildOptions.appShell = false;
55+
buildOptions.prerender = false;
4656

47-
let build;
48-
if (builderName === '@angular-devkit/build-angular:application') {
49-
build = buildApplicationInternal;
50-
51-
buildOptions.ssr = false;
52-
buildOptions.appShell = false;
53-
buildOptions.prerender = false;
54-
} else {
55-
build = buildEsbuildBrowser;
56-
}
57-
58-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
59-
const builderResult = await first(build(buildOptions as any, context, { write: false }));
57+
const builderResult = await first(buildApplicationInternal(buildOptions, context));
6058

6159
let success = false;
6260
if (!builderResult || builderResult.kind === ResultKind.Failure) {

0 commit comments

Comments
 (0)