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
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type { CompilerOptions } from '@angular/compiler-cli';
import type { PartialMessage } from 'esbuild';
import { createRequire } from 'node:module';
import { MessageChannel } from 'node:worker_threads';
import Piscina from 'piscina';
import type { SourceFile } from 'typescript';
import { WorkerPool } from '../../../utils/worker-pool';
import type { AngularHostOptions } from '../angular-host';
import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-compilation';

Expand All @@ -24,23 +24,18 @@ import { AngularCompilation, DiagnosticModes, EmitFileResult } from './angular-c
* main Node.js CLI process memory settings with large application code sizes.
*/
export class ParallelCompilation extends AngularCompilation {
readonly #worker: Piscina;
readonly #worker: WorkerPool;

constructor(readonly jit: boolean) {
super();

// TODO: Convert to import.meta usage during ESM transition
const localRequire = createRequire(__filename);

this.#worker = new Piscina({
minThreads: 1,
this.#worker = new WorkerPool({
maxThreads: 1,
idleTimeout: Infinity,
// Web containers do not support transferable objects with receiveOnMessagePort which
// is used when the Atomics based wait loop is enable.
useAtomics: !process.versions.webcontainer,
filename: localRequire.resolve('./parallel-worker'),
recordTiming: false,
});
}

Expand Down
7 changes: 3 additions & 4 deletions packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import assert from 'node:assert';
import Piscina from 'piscina';
import { WorkerPool } from '../../utils/worker-pool';
import { BuildOutputFile, BuildOutputFileType } from './bundler-context';
import { createOutputFile } from './utils';

Expand All @@ -33,7 +33,7 @@ export interface I18nInlinerOptions {
* localize function (`$localize`).
*/
export class I18nInliner {
#workerPool: Piscina;
#workerPool: WorkerPool;
readonly #localizeFiles: ReadonlyMap<string, Blob>;
readonly #unmodifiedFiles: Array<BuildOutputFile>;
readonly #fileToType = new Map<string, BuildOutputFileType>();
Expand Down Expand Up @@ -88,7 +88,7 @@ export class I18nInliner {

this.#localizeFiles = files;

this.#workerPool = new Piscina({
this.#workerPool = new WorkerPool({
filename: require.resolve('./i18n-inliner-worker'),
maxThreads,
// Extract options to ensure only the named options are serialized and sent to the worker
Expand All @@ -97,7 +97,6 @@ export class I18nInliner {
shouldOptimize: options.shouldOptimize,
files,
},
recordTiming: false,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import Piscina from 'piscina';
import { WorkerPool } from '../../utils/worker-pool';
import { Cache } from './cache';

/**
Expand All @@ -29,7 +29,7 @@ export interface JavaScriptTransformerOptions {
* and advanced optimizations.
*/
export class JavaScriptTransformer {
#workerPool: Piscina | undefined;
#workerPool: WorkerPool | undefined;
#commonOptions: Required<JavaScriptTransformerOptions>;
#fileCacheKeyBase: Uint8Array;

Expand All @@ -54,14 +54,10 @@ export class JavaScriptTransformer {
this.#fileCacheKeyBase = Buffer.from(JSON.stringify(this.#commonOptions), 'utf-8');
}

#ensureWorkerPool(): Piscina {
this.#workerPool ??= new Piscina({
#ensureWorkerPool(): WorkerPool {
this.#workerPool ??= new WorkerPool({
filename: require.resolve('./javascript-transformer-worker'),
minThreads: 1,
maxThreads: this.maxThreads,
// Shutdown idle threads after 1 second of inactivity
idleTimeout: 1000,
recordTiming: false,
});

return this.#workerPool;
Expand Down
15 changes: 4 additions & 11 deletions packages/angular/build/src/tools/sass/sass-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import assert from 'node:assert';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { MessageChannel } from 'node:worker_threads';
import { Piscina } from 'piscina';
import type {
CanonicalizeContext,
CompileResult,
Expand All @@ -22,6 +21,7 @@ import type {
StringOptions,
} from 'sass';
import { maxWorkers } from '../../utils/environment-options';
import { WorkerPool } from '../../utils/worker-pool';

// Polyfill Symbol.dispose if not present
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -84,24 +84,17 @@ interface RenderResponseMessage {
* the worker which can be up to two times faster than the asynchronous variant.
*/
export class SassWorkerImplementation {
#workerPool: Piscina | undefined;
#workerPool: WorkerPool | undefined;

constructor(
private readonly rebase = false,
readonly maxThreads = MAX_RENDER_WORKERS,
) {}

#ensureWorkerPool(): Piscina {
this.#workerPool ??= new Piscina({
#ensureWorkerPool(): WorkerPool {
this.#workerPool ??= new WorkerPool({
filename: require.resolve('./worker'),
minThreads: 1,
maxThreads: this.maxThreads,
// Web containers do not support transferable objects with receiveOnMessagePort which
// is used when the Atomics based wait loop is enable.
useAtomics: !process.versions.webcontainer,
// Shutdown idle threads after 1 second of inactivity
idleTimeout: 1000,
recordTiming: false,
});

return this.#workerPool;
Expand Down
7 changes: 7 additions & 0 deletions packages/angular/build/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@
declare module 'esbuild' {
export * from 'esbuild-wasm';
}

/**
* Augment the Node.js module builtin types to support the v22.8+ compile cache functions
*/
declare module 'node:module' {
function getCompileCacheDir(): string | undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import { readFile } from 'node:fs/promises';
import { extname, join, posix } from 'node:path';
import { pathToFileURL } from 'node:url';
import Piscina from 'piscina';
import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-context';
import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
import { urlJoin } from '../url';
import { WorkerPool } from '../worker-pool';
import type { RenderWorkerData } from './render-worker';
import type {
RoutersExtractorWorkerResult,
Expand Down Expand Up @@ -188,7 +188,7 @@ async function renderPages(
workerExecArgv.push('--enable-source-maps');
}

const renderWorker = new Piscina({
const renderWorker = new WorkerPool({
filename: require.resolve('./render-worker'),
maxThreads: Math.min(allRoutes.size, maxThreads),
workerData: {
Expand All @@ -197,7 +197,6 @@ async function renderPages(
assetFiles: assetFilesForWorker,
} as RenderWorkerData,
execArgv: workerExecArgv,
recordTiming: false,
});

try {
Expand Down Expand Up @@ -286,7 +285,7 @@ async function getAllRoutes(
workerExecArgv.push('--enable-source-maps');
}

const renderWorker = new Piscina({
const renderWorker = new WorkerPool({
filename: require.resolve('./routes-extractor-worker'),
maxThreads: 1,
workerData: {
Expand All @@ -295,7 +294,6 @@ async function getAllRoutes(
assetFiles: assetFilesForWorker,
} as RoutesExtractorWorkerData,
execArgv: workerExecArgv,
recordTiming: false,
});

const errors: string[] = [];
Expand Down
44 changes: 44 additions & 0 deletions packages/angular/build/src/utils/worker-pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @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.dev/license
*/

import { getCompileCacheDir } from 'node:module';
import { Piscina } from 'piscina';

export type WorkerPoolOptions = ConstructorParameters<typeof Piscina>[0];

export class WorkerPool extends Piscina {
constructor(options: WorkerPoolOptions) {
const piscinaOptions: WorkerPoolOptions = {
minThreads: 1,
idleTimeout: 1000,
// Web containers do not support transferable objects with receiveOnMessagePort which
// is used when the Atomics based wait loop is enable.
useAtomics: !process.versions.webcontainer,
recordTiming: false,
...options,
};

// Enable compile code caching if enabled for the main process (only exists on Node.js v22.8+).
// Skip if running inside Bazel via a RUNFILES environment variable check. The cache does not work
// well with Bazel's hermeticity requirements.
const compileCacheDirectory = process.env['RUNFILES'] ? undefined : getCompileCacheDir?.();
if (compileCacheDirectory) {
if (typeof piscinaOptions.env === 'object') {
piscinaOptions.env['NODE_COMPILE_CACHE'] = compileCacheDirectory;
} else {
// Default behavior of `env` option is to copy current process values
piscinaOptions.env = {
...process.env,
'NODE_COMPILE_CACHE': compileCacheDirectory,
};
}
}

super(piscinaOptions);
}
}
14 changes: 13 additions & 1 deletion packages/angular/cli/bin/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,16 @@
* range.
*/

import('../lib/init.js');
// Enable on-disk code caching if available (Node.js 22.8+)
// Skip if running inside Bazel via a RUNFILES environment variable check. The cache does not work
// well with Bazel's hermeticity requirements.
if (!process.env['RUNFILES']) {
try {
const { enableCompileCache } = require('node:module');

enableCompileCache?.();
} catch {}
}

// Initialize the Angular CLI
void import('../lib/init.js');