Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/angular/cli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ ts_library(

# @external_begin
CLI_SCHEMA_DATA = [
"//packages/angular_devkit/build_angular:src/builders/application/schema.json",
"//packages/angular_devkit/build_angular:src/builders/app-shell/schema.json",
"//packages/angular_devkit/build_angular:src/builders/browser/schema.json",
"//packages/angular_devkit/build_angular:src/builders/browser-esbuild/schema.json",
Expand Down
23 changes: 23 additions & 0 deletions packages/angular/cli/lib/config/workspace-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@
"description": "The builder used for this package.",
"not": {
"enum": [
"@angular-devkit/build-angular:application",
"@angular-devkit/build-angular:app-shell",
"@angular-devkit/build-angular:browser",
"@angular-devkit/build-angular:browser-esbuild",
Expand Down Expand Up @@ -385,6 +386,28 @@
"additionalProperties": false,
"required": ["builder"]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"builder": {
"const": "@angular-devkit/build-angular:application"
},
"defaultConfiguration": {
"type": "string",
"description": "A default named configuration to use when a target configuration is not provided."
},
"options": {
"$ref": "../../../../angular_devkit/build_angular/src/builders/application/schema.json"
},
"configurations": {
"type": "object",
"additionalProperties": {
"$ref": "../../../../angular_devkit/build_angular/src/builders/application/schema.json"
}
}
}
},
{
"type": "object",
"additionalProperties": false,
Expand Down
16 changes: 12 additions & 4 deletions packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ licenses(["notice"])

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

ts_json_schema(
name = "application_schema",
src = "src/builders/application/schema.json",
)

ts_json_schema(
name = "app_shell_schema",
src = "src/builders/app-shell/schema.json",
Expand Down Expand Up @@ -80,6 +85,7 @@ ts_library(
],
) + [
"//packages/angular_devkit/build_angular:src/builders/app-shell/schema.ts",
"//packages/angular_devkit/build_angular:src/builders/application/schema.ts",
"//packages/angular_devkit/build_angular:src/builders/browser-esbuild/schema.ts",
"//packages/angular_devkit/build_angular:src/builders/browser/schema.ts",
"//packages/angular_devkit/build_angular:src/builders/dev-server/schema.ts",
Expand Down Expand Up @@ -290,6 +296,12 @@ ts_library(
)

LARGE_SPECS = {
"application": {
"shards": 10,
"extra_deps": [
"@npm//buffer",
],
},
"app-shell": {
},
"dev-server": {
Expand Down Expand Up @@ -347,10 +359,6 @@ LARGE_SPECS = {
],
},
"browser-esbuild": {
"shards": 10,
"extra_deps": [
"@npm//buffer",
],
},
"jest": {
"extra_deps": [
Expand Down
5 changes: 5 additions & 0 deletions packages/angular_devkit/build_angular/builders.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "../architect/src/builders-schema.json",
"builders": {
"application": {
"implementation": "./src/builders/application",
"schema": "./src/builders/application/schema.json",
"description": "Build an application."
},
"app-shell": {
"implementation": "./src/builders/app-shell",
"schema": "./src/builders/app-shell/schema.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { BuilderOutput } from '@angular-devkit/architect';
import type { logging } from '@angular-devkit/core';
import fs from 'node:fs/promises';
import path from 'node:path';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language';
import { withNoProgress, withSpinner, writeResultFiles } from '../../tools/esbuild/utils';
import { assertIsError } from '../../utils/error';
import { NormalizedCachedOptions } from '../../utils/normalize-cache';

export async function* runEsBuildBuildAction(
action: (rebuildState?: RebuildState) => ExecutionResult | Promise<ExecutionResult>,
options: {
workspaceRoot: string;
projectRoot: string;
outputPath: string;
logger: logging.LoggerApi;
cacheOptions: NormalizedCachedOptions;
writeToFileSystem?: boolean;
watch?: boolean;
verbose?: boolean;
progress?: boolean;
deleteOutputPath?: boolean;
poll?: number;
},
): AsyncIterable<(ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & BuilderOutput> {
const {
writeToFileSystem = true,
watch,
poll,
logger,
deleteOutputPath,
cacheOptions,
outputPath,
verbose,
projectRoot,
workspaceRoot,
progress,
} = options;

if (writeToFileSystem) {
// Clean output path if enabled
if (deleteOutputPath) {
if (outputPath === workspaceRoot) {
logger.error('Output path MUST not be workspace root directory!');

return;
}

await fs.rm(outputPath, { force: true, recursive: true, maxRetries: 3 });
}

// Create output directory if needed
try {
await fs.mkdir(outputPath, { recursive: true });
} catch (e) {
assertIsError(e);
logger.error('Unable to create output directory: ' + e.message);

return;
}
}

const withProgress: typeof withSpinner = progress ? withSpinner : withNoProgress;

// Initial build
let result: ExecutionResult;
try {
result = await withProgress('Building...', () => action());

if (writeToFileSystem) {
// Write output files
await writeResultFiles(result.outputFiles, result.assetFiles, outputPath);

yield result.output;
} else {
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
yield result.outputWithFiles as any;
}

// Finish if watch mode is not enabled
if (!watch) {
return;
}
} finally {
// Ensure Sass workers are shutdown if not watching
if (!watch) {
shutdownSassWorkerPool();
}
}

if (progress) {
logger.info('Watch mode enabled. Watching for file changes...');
}

// Setup a watcher
const { createWatcher } = await import('../../tools/esbuild/watcher');
const watcher = createWatcher({
polling: typeof poll === 'number',
interval: poll,
ignored: [
// Ignore the output and cache paths to avoid infinite rebuild cycles
outputPath,
cacheOptions.basePath,
// Ignore all node modules directories to avoid excessive file watchers.
// Package changes are handled below by watching manifest and lock files.
'**/node_modules/**',
'**/.*/**',
],
});

// Temporarily watch the entire project
watcher.add(projectRoot);

// Watch workspace for package manager changes
const packageWatchFiles = [
// manifest can affect module resolution
'package.json',
// npm lock file
'package-lock.json',
// pnpm lock file
'pnpm-lock.yaml',
// yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
'yarn.lock',
'.pnp.cjs',
'.pnp.data.json',
];

watcher.add(packageWatchFiles.map((file) => path.join(workspaceRoot, file)));

// Watch locations provided by the initial build result
let previousWatchFiles = new Set(result.watchFiles);
watcher.add(result.watchFiles);

// Wait for changes and rebuild as needed
try {
for await (const changes of watcher) {
if (verbose) {
logger.info(changes.toDebugString());
}

result = await withProgress('Changes detected. Rebuilding...', () =>
action(result.createRebuildState(changes)),
);

// Update watched locations provided by the new build result.
// Add any new locations
watcher.add(result.watchFiles.filter((watchFile) => !previousWatchFiles.has(watchFile)));
const newWatchFiles = new Set(result.watchFiles);
// Remove any old locations
watcher.remove([...previousWatchFiles].filter((watchFile) => !newWatchFiles.has(watchFile)));
previousWatchFiles = newWatchFiles;

if (writeToFileSystem) {
// Write output files
await writeResultFiles(result.outputFiles, result.assetFiles, outputPath);

yield result.output;
} else {
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
yield result.outputWithFiles as any;
}
}
} finally {
// Stop the watcher and cleanup incremental rebuild state
await Promise.allSettled([watcher.close(), result.dispose()]);

shutdownSassWorkerPool();
}
}
Loading