Skip to content

Commit c709a37

Browse files
author
Angular Builds
committed
421b6e7 refactor(@angular/build): provide structured application builder result types
1 parent f563f5e commit c709a37

File tree

9 files changed

+79
-98
lines changed

9 files changed

+79
-98
lines changed

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
{
22
"name": "@angular-devkit/build-angular",
3-
"version": "18.2.0-next.1+sha-37a2138",
3+
"version": "18.2.0-next.1+sha-421b6e7",
44
"description": "Angular Webpack Build Facade",
55
"main": "src/index.js",
66
"typings": "src/index.d.ts",
77
"builders": "builders.json",
88
"dependencies": {
99
"@ampproject/remapping": "2.3.0",
10-
"@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#37a2138",
11-
"@angular-devkit/build-webpack": "github:angular/angular-devkit-build-webpack-builds#37a2138",
12-
"@angular-devkit/core": "github:angular/angular-devkit-core-builds#37a2138",
13-
"@angular/build": "github:angular/angular-build-builds#37a2138",
10+
"@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#421b6e7",
11+
"@angular-devkit/build-webpack": "github:angular/angular-devkit-build-webpack-builds#421b6e7",
12+
"@angular-devkit/core": "github:angular/angular-devkit-core-builds#421b6e7",
13+
"@angular/build": "github:angular/angular-build-builds#421b6e7",
1414
"@babel/core": "7.24.9",
1515
"@babel/generator": "7.24.10",
1616
"@babel/helper-annotate-as-pure": "7.24.7",
@@ -21,7 +21,7 @@
2121
"@babel/preset-env": "7.24.8",
2222
"@babel/runtime": "7.24.8",
2323
"@discoveryjs/json-ext": "0.6.0",
24-
"@ngtools/webpack": "github:angular/ngtools-webpack-builds#37a2138",
24+
"@ngtools/webpack": "github:angular/ngtools-webpack-builds#421b6e7",
2525
"@vitejs/plugin-basic-ssl": "1.1.0",
2626
"ansi-colors": "4.1.3",
2727
"autoprefixer": "10.4.19",

src/builders/browser-esbuild/index.d.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.dev/license
77
*/
8-
import type { BuildOutputFile } from '@angular/build';
9-
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
8+
import { Result } from '@angular/build/private';
9+
import { BuilderContext } from '@angular-devkit/architect';
1010
import type { Plugin } from 'esbuild';
1111
import type { Schema as BrowserBuilderOptions } from './schema';
1212
/**
@@ -18,12 +18,9 @@ import type { Schema as BrowserBuilderOptions } from './schema';
1818
*/
1919
export declare function buildEsbuildBrowser(userOptions: BrowserBuilderOptions, context: BuilderContext, infrastructureSettings?: {
2020
write?: boolean;
21-
}, plugins?: Plugin[]): AsyncIterable<BuilderOutput & {
22-
outputFiles?: BuildOutputFile[];
23-
assetFiles?: {
24-
source: string;
25-
destination: string;
26-
}[];
27-
}>;
21+
}, plugins?: Plugin[]): AsyncIterable<Result>;
22+
export declare function buildEsbuildBrowserArchitect(options: BrowserBuilderOptions, context: BuilderContext): AsyncGenerator<{
23+
success: boolean;
24+
}, void, unknown>;
2825
declare const _default: import("../../../../architect/src/internal").Builder<BrowserBuilderOptions & import("../../../../core/src").JsonObject>;
2926
export default _default;

src/builders/browser-esbuild/index.js

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1111
};
1212
Object.defineProperty(exports, "__esModule", { value: true });
1313
exports.buildEsbuildBrowser = buildEsbuildBrowser;
14+
exports.buildEsbuildBrowserArchitect = buildEsbuildBrowserArchitect;
1415
const private_1 = require("@angular/build/private");
1516
const architect_1 = require("@angular-devkit/architect");
1617
const promises_1 = __importDefault(require("node:fs/promises"));
@@ -35,19 +36,30 @@ async function* buildEsbuildBrowser(userOptions, context, infrastructureSettings
3536
for await (const result of (0, private_1.buildApplicationInternal)(normalizedOptions, context, {
3637
write: false,
3738
}, plugins && { codePlugins: plugins })) {
38-
if (infrastructureSettings?.write !== false && result.outputFiles) {
39-
// Write output files
40-
await writeResultFiles(result.outputFiles, result.assetFiles, fullOutputPath);
39+
// Write the file directly from this builder to maintain webpack output compatibility
40+
// and not output browser files into '/browser'.
41+
if (infrastructureSettings?.write !== false &&
42+
(result.kind === private_1.ResultKind.Full || result.kind === private_1.ResultKind.Incremental)) {
43+
const directoryExists = new Set();
44+
// Writes the output file to disk and ensures the containing directories are present
45+
await (0, private_1.emitFilesToDisk)(Object.entries(result.files), async ([filePath, file]) => {
46+
// Ensure output subdirectories exist
47+
const basePath = node_path_1.default.dirname(filePath);
48+
if (basePath && !directoryExists.has(basePath)) {
49+
await promises_1.default.mkdir(node_path_1.default.join(fullOutputPath, basePath), { recursive: true });
50+
directoryExists.add(basePath);
51+
}
52+
if (file.origin === 'memory') {
53+
// Write file contents
54+
await promises_1.default.writeFile(node_path_1.default.join(fullOutputPath, filePath), file.contents);
55+
}
56+
else {
57+
// Copy file contents
58+
await promises_1.default.copyFile(file.inputPath, node_path_1.default.join(fullOutputPath, filePath), promises_1.default.constants.COPYFILE_FICLONE);
59+
}
60+
});
4161
}
42-
// The builder system (architect) currently attempts to treat all results as JSON and
43-
// attempts to validate the object with a JSON schema validator. This can lead to slow
44-
// build completion (even after the actual build is fully complete) or crashes if the
45-
// size and/or quantity of output files is large. Architect only requires a `success`
46-
// property so that is all that will be passed here if the infrastructure settings have
47-
// not been explicitly set to avoid writes. Writing is only disabled when used directly
48-
// by the dev server which bypasses the architect behavior.
49-
const builderResult = infrastructureSettings?.write === false ? result : { success: result.success };
50-
yield builderResult;
62+
yield result;
5163
}
5264
}
5365
function normalizeOptions(options) {
@@ -63,32 +75,9 @@ function normalizeOptions(options) {
6375
...otherOptions,
6476
};
6577
}
66-
// We write the file directly from this builder to maintain webpack output compatibility
67-
// and not output browser files into '/browser'.
68-
async function writeResultFiles(outputFiles, assetFiles, outputPath) {
69-
const directoryExists = new Set();
70-
const ensureDirectoryExists = async (basePath) => {
71-
if (basePath && !directoryExists.has(basePath)) {
72-
await promises_1.default.mkdir(node_path_1.default.join(outputPath, basePath), { recursive: true });
73-
directoryExists.add(basePath);
74-
}
75-
};
76-
// Writes the output file to disk and ensures the containing directories are present
77-
await (0, private_1.emitFilesToDisk)(outputFiles, async (file) => {
78-
// Ensure output subdirectories exist
79-
const basePath = node_path_1.default.dirname(file.path);
80-
await ensureDirectoryExists(basePath);
81-
// Write file contents
82-
await promises_1.default.writeFile(node_path_1.default.join(outputPath, file.path), file.contents);
83-
});
84-
if (assetFiles?.length) {
85-
await (0, private_1.emitFilesToDisk)(assetFiles, async ({ source, destination }) => {
86-
const basePath = node_path_1.default.dirname(destination);
87-
// Ensure output subdirectories exist
88-
await ensureDirectoryExists(basePath);
89-
// Copy file contents
90-
await promises_1.default.copyFile(source, node_path_1.default.join(outputPath, destination), promises_1.default.constants.COPYFILE_FICLONE);
91-
});
78+
async function* buildEsbuildBrowserArchitect(options, context) {
79+
for await (const result of buildEsbuildBrowser(options, context)) {
80+
yield { success: result.kind !== private_1.ResultKind.Failure };
9281
}
9382
}
94-
exports.default = (0, architect_1.createBuilder)(buildEsbuildBrowser);
83+
exports.default = (0, architect_1.createBuilder)(buildEsbuildBrowserArchitect);

src/builders/extract-i18n/application-extraction.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
*/
88
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
99
import type { MessageExtractor } from '@angular/localize/tools';
10-
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
10+
import type { BuilderContext } from '@angular-devkit/architect';
1111
import type { NormalizedExtractI18nOptions } from './options';
1212
export declare function extractMessages(options: NormalizedExtractI18nOptions, builderName: string, context: BuilderContext, extractorConstructor: typeof MessageExtractor): Promise<{
13-
builderResult: BuilderOutput;
13+
success: boolean;
1414
basePath: string;
1515
messages: LocalizeMessage[];
1616
useLegacyIds: boolean;

src/builders/extract-i18n/application-extraction.js

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
1212
Object.defineProperty(exports, "__esModule", { value: true });
1313
exports.extractMessages = extractMessages;
1414
const private_1 = require("@angular/build/private");
15-
const node_assert_1 = __importDefault(require("node:assert"));
1615
const node_path_1 = __importDefault(require("node:path"));
1716
const browser_esbuild_1 = require("../browser-esbuild");
1817
async function extractMessages(options, builderName, context, extractorConstructor) {
@@ -35,63 +34,51 @@ async function extractMessages(options, builderName, context, extractorConstruct
3534
else {
3635
build = browser_esbuild_1.buildEsbuildBrowser;
3736
}
38-
// Build the application with the build options
39-
let builderResult;
40-
try {
41-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
42-
for await (const result of build(buildOptions, context, { write: false })) {
43-
builderResult = result;
44-
break;
45-
}
46-
(0, node_assert_1.default)(builderResult !== undefined, 'Application builder did not provide a result.');
37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
const builderResult = await first(build(buildOptions, context, { write: false }));
39+
let success = false;
40+
if (!builderResult || builderResult.kind === private_1.ResultKind.Failure) {
41+
context.logger.error('Application build failed.');
4742
}
48-
catch (err) {
49-
builderResult = {
50-
success: false,
51-
error: err.message,
52-
};
43+
else if (builderResult.kind !== private_1.ResultKind.Full) {
44+
context.logger.error('Application build did not provide a full output.');
5345
}
54-
// Extract messages from each output JavaScript file.
55-
// Output files are only present on a successful build.
56-
if (builderResult.outputFiles) {
57-
// Store the JS and JS map files for lookup during extraction
58-
const files = new Map();
59-
for (const outputFile of builderResult.outputFiles) {
60-
if (outputFile.path.endsWith('.js')) {
61-
files.set(outputFile.path, outputFile.text);
62-
}
63-
else if (outputFile.path.endsWith('.js.map')) {
64-
files.set(outputFile.path, outputFile.text);
65-
}
66-
}
46+
else {
6747
// Setup the localize message extractor based on the in-memory files
68-
const extractor = setupLocalizeExtractor(extractorConstructor, files, context);
69-
// Attempt extraction of all output JS files
70-
for (const filePath of files.keys()) {
48+
const extractor = setupLocalizeExtractor(extractorConstructor, builderResult.files, context);
49+
// Extract messages from each output JavaScript file.
50+
// Output files are only present on a successful build.
51+
for (const filePath of Object.keys(builderResult.files)) {
7152
if (!filePath.endsWith('.js')) {
7253
continue;
7354
}
7455
const fileMessages = extractor.extractMessages(filePath);
7556
messages.push(...fileMessages);
7657
}
58+
success = true;
7759
}
7860
return {
79-
builderResult,
61+
success,
8062
basePath: context.workspaceRoot,
8163
messages,
8264
// Legacy i18n identifiers are not supported with the new application builder
8365
useLegacyIds: false,
8466
};
8567
}
8668
function setupLocalizeExtractor(extractorConstructor, files, context) {
69+
const textDecoder = new TextDecoder();
8770
// Setup a virtual file system instance for the extractor
8871
// * MessageExtractor itself uses readFile, relative and resolve
8972
// * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve
9073
const filesystem = {
9174
readFile(path) {
9275
// Output files are stored as relative to the workspace root
9376
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
94-
const content = files.get(requestedPath);
77+
const file = files[requestedPath];
78+
let content;
79+
if (file?.origin === 'memory') {
80+
content = textDecoder.decode(file.contents);
81+
}
9582
if (content === undefined) {
9683
throw new Error('Unknown file requested: ' + requestedPath);
9784
}
@@ -106,7 +93,7 @@ function setupLocalizeExtractor(extractorConstructor, files, context) {
10693
exists(path) {
10794
// Output files are stored as relative to the workspace root
10895
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
109-
return files.has(requestedPath);
96+
return files[requestedPath] !== undefined;
11097
},
11198
dirname(path) {
11299
return node_path_1.default.dirname(path);
@@ -137,3 +124,8 @@ function setupLocalizeExtractor(extractorConstructor, files, context) {
137124
});
138125
return extractor;
139126
}
127+
async function first(iterable) {
128+
for await (const value of iterable) {
129+
return value;
130+
}
131+
}

src/builders/extract-i18n/builder.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,20 @@ async function execute(options, context, transforms) {
7575
builderName === '@angular-devkit/build-angular:browser-esbuild') {
7676
const { extractMessages } = await Promise.resolve().then(() => __importStar(require('./application-extraction')));
7777
extractionResult = await extractMessages(normalizedOptions, builderName, context, localizeToolsModule.MessageExtractor);
78+
if (!extractionResult.success) {
79+
return { success: false };
80+
}
7881
}
7982
else {
8083
// Purge old build disk cache.
8184
// Other build systems handle stale cache purging directly.
8285
await (0, private_1.purgeStaleBuildCache)(context);
8386
const { extractMessages } = await Promise.resolve().then(() => __importStar(require('./webpack-extraction')));
8487
extractionResult = await extractMessages(normalizedOptions, builderName, context, transforms);
85-
}
86-
// Return the builder result if it failed
87-
if (!extractionResult.builderResult.success) {
88-
return extractionResult.builderResult;
88+
// Return the builder result if it failed
89+
if (!extractionResult.builderResult.success) {
90+
return extractionResult.builderResult;
91+
}
8992
}
9093
// Perform duplicate message checks
9194
const { checkDuplicateMessages } = localizeToolsModule;

src/builders/web-test-runner/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ exports.default = (0, architect_1.createBuilder)(async (schema, ctx) => {
4747
]);
4848
// Build the tests and abort on any build failure.
4949
const buildOutput = await buildTests(testFiles, testDir, options, ctx);
50-
if (!buildOutput.success) {
51-
return buildOutput;
50+
if (buildOutput.kind === private_1.ResultKind.Failure) {
51+
return { success: false };
5252
}
5353
// Run the built tests.
5454
return await runTests(wtr, `${testDir}/browser`, options);

src/utils/normalize-cache.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
1010
exports.normalizeCacheOptions = normalizeCacheOptions;
1111
const node_path_1 = require("node:path");
1212
/** Version placeholder is replaced during the build process with actual package version */
13-
const VERSION = '18.2.0-next.1+sha-37a2138';
13+
const VERSION = '18.2.0-next.1+sha-421b6e7';
1414
function hasCacheMetadata(value) {
1515
return (!!value &&
1616
typeof value === 'object' &&

uniqueId

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Fri Jul 19 2024 13:33:16 GMT+0000 (Coordinated Universal Time)
1+
Fri Jul 19 2024 14:12:37 GMT+0000 (Coordinated Universal Time)

0 commit comments

Comments
 (0)