Skip to content

Commit c05c83b

Browse files
alan-agius4clydin
authored andcommitted
feat(@angular-devkit/build-angular): add initial application builder implementation
This commits add the initial application builder schema and build configuration and refactors several files.
1 parent 4d87b7d commit c05c83b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1294
-688
lines changed

packages/angular/cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ ts_library(
7979

8080
# @external_begin
8181
CLI_SCHEMA_DATA = [
82+
"//packages/angular_devkit/build_angular:src/builders/application/schema.json",
8283
"//packages/angular_devkit/build_angular:src/builders/app-shell/schema.json",
8384
"//packages/angular_devkit/build_angular:src/builders/browser/schema.json",
8485
"//packages/angular_devkit/build_angular:src/builders/browser-esbuild/schema.json",

packages/angular/cli/lib/config/workspace-schema.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@
354354
"description": "The builder used for this package.",
355355
"not": {
356356
"enum": [
357+
"@angular-devkit/build-angular:application",
357358
"@angular-devkit/build-angular:app-shell",
358359
"@angular-devkit/build-angular:browser",
359360
"@angular-devkit/build-angular:browser-esbuild",
@@ -385,6 +386,28 @@
385386
"additionalProperties": false,
386387
"required": ["builder"]
387388
},
389+
{
390+
"type": "object",
391+
"additionalProperties": false,
392+
"properties": {
393+
"builder": {
394+
"const": "@angular-devkit/build-angular:application"
395+
},
396+
"defaultConfiguration": {
397+
"type": "string",
398+
"description": "A default named configuration to use when a target configuration is not provided."
399+
},
400+
"options": {
401+
"$ref": "../../../../angular_devkit/build_angular/src/builders/application/schema.json"
402+
},
403+
"configurations": {
404+
"type": "object",
405+
"additionalProperties": {
406+
"$ref": "../../../../angular_devkit/build_angular/src/builders/application/schema.json"
407+
}
408+
}
409+
}
410+
},
388411
{
389412
"type": "object",
390413
"additionalProperties": false,

packages/angular_devkit/build_angular/BUILD.bazel

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ licenses(["notice"])
1313

1414
package(default_visibility = ["//visibility:public"])
1515

16+
ts_json_schema(
17+
name = "application_schema",
18+
src = "src/builders/application/schema.json",
19+
)
20+
1621
ts_json_schema(
1722
name = "app_shell_schema",
1823
src = "src/builders/app-shell/schema.json",
@@ -80,6 +85,7 @@ ts_library(
8085
],
8186
) + [
8287
"//packages/angular_devkit/build_angular:src/builders/app-shell/schema.ts",
88+
"//packages/angular_devkit/build_angular:src/builders/application/schema.ts",
8389
"//packages/angular_devkit/build_angular:src/builders/browser-esbuild/schema.ts",
8490
"//packages/angular_devkit/build_angular:src/builders/browser/schema.ts",
8591
"//packages/angular_devkit/build_angular:src/builders/dev-server/schema.ts",
@@ -290,6 +296,12 @@ ts_library(
290296
)
291297

292298
LARGE_SPECS = {
299+
"application": {
300+
"shards": 10,
301+
"extra_deps": [
302+
"@npm//buffer",
303+
],
304+
},
293305
"app-shell": {
294306
},
295307
"dev-server": {
@@ -347,10 +359,6 @@ LARGE_SPECS = {
347359
],
348360
},
349361
"browser-esbuild": {
350-
"shards": 10,
351-
"extra_deps": [
352-
"@npm//buffer",
353-
],
354362
},
355363
"jest": {
356364
"extra_deps": [

packages/angular_devkit/build_angular/builders.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"$schema": "../architect/src/builders-schema.json",
33
"builders": {
4+
"application": {
5+
"implementation": "./src/builders/application",
6+
"schema": "./src/builders/application/schema.json",
7+
"description": "Build an application."
8+
},
49
"app-shell": {
510
"implementation": "./src/builders/app-shell",
611
"schema": "./src/builders/app-shell/schema.json",
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { BuilderOutput } from '@angular-devkit/architect';
10+
import type { logging } from '@angular-devkit/core';
11+
import fs from 'node:fs/promises';
12+
import path from 'node:path';
13+
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
14+
import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
15+
import { withNoProgress, withSpinner, writeResultFiles } from '../../tools/esbuild/utils';
16+
import { assertIsError } from '../../utils/error';
17+
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
18+
19+
export async function* runEsBuildBuildAction(
20+
action: (rebuildState?: RebuildState) => ExecutionResult | Promise<ExecutionResult>,
21+
options: {
22+
workspaceRoot: string;
23+
projectRoot: string;
24+
outputPath: string;
25+
logger: logging.LoggerApi;
26+
cacheOptions: NormalizedCachedOptions;
27+
writeToFileSystem?: boolean;
28+
watch?: boolean;
29+
verbose?: boolean;
30+
progress?: boolean;
31+
deleteOutputPath?: boolean;
32+
poll?: number;
33+
},
34+
): AsyncIterable<(ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & BuilderOutput> {
35+
const {
36+
writeToFileSystem = true,
37+
watch,
38+
poll,
39+
logger,
40+
deleteOutputPath,
41+
cacheOptions,
42+
outputPath,
43+
verbose,
44+
projectRoot,
45+
workspaceRoot,
46+
progress,
47+
} = options;
48+
49+
if (writeToFileSystem) {
50+
// Clean output path if enabled
51+
if (deleteOutputPath) {
52+
if (outputPath === workspaceRoot) {
53+
logger.error('Output path MUST not be workspace root directory!');
54+
55+
return;
56+
}
57+
58+
await fs.rm(outputPath, { force: true, recursive: true, maxRetries: 3 });
59+
}
60+
61+
// Create output directory if needed
62+
try {
63+
await fs.mkdir(outputPath, { recursive: true });
64+
} catch (e) {
65+
assertIsError(e);
66+
logger.error('Unable to create output directory: ' + e.message);
67+
68+
return;
69+
}
70+
}
71+
72+
const withProgress: typeof withSpinner = progress ? withSpinner : withNoProgress;
73+
74+
// Initial build
75+
let result: ExecutionResult;
76+
try {
77+
result = await withProgress('Building...', () => action());
78+
79+
if (writeToFileSystem) {
80+
// Write output files
81+
await writeResultFiles(result.outputFiles, result.assetFiles, outputPath);
82+
83+
yield result.output;
84+
} else {
85+
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
86+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
87+
yield result.outputWithFiles as any;
88+
}
89+
90+
// Finish if watch mode is not enabled
91+
if (!watch) {
92+
return;
93+
}
94+
} finally {
95+
// Ensure Sass workers are shutdown if not watching
96+
if (!watch) {
97+
shutdownSassWorkerPool();
98+
}
99+
}
100+
101+
if (progress) {
102+
logger.info('Watch mode enabled. Watching for file changes...');
103+
}
104+
105+
// Setup a watcher
106+
const { createWatcher } = await import('../../tools/esbuild/watcher');
107+
const watcher = createWatcher({
108+
polling: typeof poll === 'number',
109+
interval: poll,
110+
ignored: [
111+
// Ignore the output and cache paths to avoid infinite rebuild cycles
112+
outputPath,
113+
cacheOptions.basePath,
114+
// Ignore all node modules directories to avoid excessive file watchers.
115+
// Package changes are handled below by watching manifest and lock files.
116+
'**/node_modules/**',
117+
'**/.*/**',
118+
],
119+
});
120+
121+
// Temporarily watch the entire project
122+
watcher.add(projectRoot);
123+
124+
// Watch workspace for package manager changes
125+
const packageWatchFiles = [
126+
// manifest can affect module resolution
127+
'package.json',
128+
// npm lock file
129+
'package-lock.json',
130+
// pnpm lock file
131+
'pnpm-lock.yaml',
132+
// yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
133+
'yarn.lock',
134+
'.pnp.cjs',
135+
'.pnp.data.json',
136+
];
137+
138+
watcher.add(packageWatchFiles.map((file) => path.join(workspaceRoot, file)));
139+
140+
// Watch locations provided by the initial build result
141+
let previousWatchFiles = new Set(result.watchFiles);
142+
watcher.add(result.watchFiles);
143+
144+
// Wait for changes and rebuild as needed
145+
try {
146+
for await (const changes of watcher) {
147+
if (verbose) {
148+
logger.info(changes.toDebugString());
149+
}
150+
151+
result = await withProgress('Changes detected. Rebuilding...', () =>
152+
action(result.createRebuildState(changes)),
153+
);
154+
155+
// Update watched locations provided by the new build result.
156+
// Add any new locations
157+
watcher.add(result.watchFiles.filter((watchFile) => !previousWatchFiles.has(watchFile)));
158+
const newWatchFiles = new Set(result.watchFiles);
159+
// Remove any old locations
160+
watcher.remove([...previousWatchFiles].filter((watchFile) => !newWatchFiles.has(watchFile)));
161+
previousWatchFiles = newWatchFiles;
162+
163+
if (writeToFileSystem) {
164+
// Write output files
165+
await writeResultFiles(result.outputFiles, result.assetFiles, outputPath);
166+
167+
yield result.output;
168+
} else {
169+
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
170+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171+
yield result.outputWithFiles as any;
172+
}
173+
}
174+
} finally {
175+
// Stop the watcher and cleanup incremental rebuild state
176+
await Promise.allSettled([watcher.close(), result.dispose()]);
177+
178+
shutdownSassWorkerPool();
179+
}
180+
}

0 commit comments

Comments
 (0)