diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts index 6e4debf62501..60945a758081 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation.ts @@ -67,23 +67,28 @@ export class ParallelCompilation extends AngularCompilation { ); this.#webWorkerChannel?.port1.close(); + this.#webWorkerChannel = undefined; - // The web worker processing is a synchronous operation and uses shared memory combined with - // the Atomics API to block execution here until a response is received. - const webWorkerChannel = new MessageChannel(); - this.#webWorkerChannel = webWorkerChannel; - const webWorkerSignal = new Int32Array(new SharedArrayBuffer(4)); - webWorkerChannel.port1.on('message', ({ workerFile, containingFile }) => { - try { - const workerCodeFile = hostOptions.processWebWorker(workerFile, containingFile); - webWorkerChannel.port1.postMessage({ workerCodeFile }); - } catch (error) { - webWorkerChannel.port1.postMessage({ error }); - } finally { - Atomics.store(webWorkerSignal, 0, 1); - Atomics.notify(webWorkerSignal, 0); - } - }); + let webWorkerSignal: Int32Array | undefined; + if (buildType !== 'library') { + // The web worker processing is a synchronous operation and uses shared memory combined with + // the Atomics API to block execution here until a response is received. + const webWorkerChannel = new MessageChannel(); + this.#webWorkerChannel = webWorkerChannel; + const signal = new Int32Array(new SharedArrayBuffer(4)); + webWorkerSignal = signal; + webWorkerChannel.port1.on('message', ({ workerFile, containingFile }) => { + try { + const workerCodeFile = hostOptions.processWebWorker(workerFile, containingFile); + webWorkerChannel.port1.postMessage({ workerCodeFile }); + } catch (error) { + webWorkerChannel.port1.postMessage({ error }); + } finally { + Atomics.store(signal, 0, 1); + Atomics.notify(signal, 0); + } + }); + } let success = false; try { @@ -97,12 +102,14 @@ export class ParallelCompilation extends AngularCompilation { compilerOptionOverrides, buildType, stylesheetPort: stylesheetChannel.port2, - webWorkerPort: webWorkerChannel.port2, + webWorkerPort: this.#webWorkerChannel?.port2, webWorkerSignal, }, { name: 'initialize', - transferList: [stylesheetChannel.port2, webWorkerChannel.port2], + transferList: this.#webWorkerChannel + ? [stylesheetChannel.port2, this.#webWorkerChannel.port2] + : [stylesheetChannel.port2], }, ); success = true; diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-compilation_spec.ts b/packages/angular/build/src/tools/angular/compilation/parallel-compilation_spec.ts new file mode 100644 index 000000000000..7157251e1ab9 --- /dev/null +++ b/packages/angular/build/src/tools/angular/compilation/parallel-compilation_spec.ts @@ -0,0 +1,59 @@ +/** + * @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 { MessageChannel } from 'node:worker_threads'; +import { AotCompilation } from './aot-compilation'; +import { initialize } from './parallel-worker'; + +describe('parallel-worker', () => { + it('uses incrementing numeric request IDs for transformStylesheet and handles omitted webWorkerPort in library builds', async () => { + const receivedRequestIds: unknown[] = []; + let workerResult: string | undefined; + + spyOn(AotCompilation.prototype, 'initialize').and.callFake(async (_tsconfig, hostOptions) => { + const [style1, style2] = await Promise.all([ + hostOptions.transformStylesheet('body {}', '/src/a.ts', '/src/a.css', 0, 'CompA'), + hostOptions.transformStylesheet('div {}', '/src/b.ts', '/src/b.css', 1, 'CompB'), + ]); + expect(style1).toBe('transformed:body {}'); + expect(style2).toBe('transformed:div {}'); + + workerResult = hostOptions.processWebWorker('./my.worker', '/src/a.ts'); + + return { + compilerOptions: {}, + referencedFiles: ['/src/a.ts'], + }; + }); + + const stylesheetChannel = new MessageChannel(); + stylesheetChannel.port1.on('message', ({ requestId, data }) => { + receivedRequestIds.push(requestId); + stylesheetChannel.port1.postMessage({ + requestId, + value: `transformed:${data}`, + }); + }); + + try { + const result = await initialize({ + jit: false, + browserOnlyBuild: false, + tsconfig: '/src/tsconfig.lib.json', + buildType: 'library', + stylesheetPort: stylesheetChannel.port2, + }); + + expect(receivedRequestIds).toEqual([1, 2]); + expect(workerResult).toBe('./my.worker'); + expect(result.referencedFiles).toEqual(['/src/a.ts']); + } finally { + stylesheetChannel.port1.close(); + } + }); +}); diff --git a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts index 475592bd478b..dde3df4c0c20 100644 --- a/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts +++ b/packages/angular/build/src/tools/angular/compilation/parallel-worker.ts @@ -8,7 +8,6 @@ import type { PartialMessage } from 'esbuild'; import assert from 'node:assert'; -import { randomUUID } from 'node:crypto'; import { type MessagePort, receiveMessageOnPort } from 'node:worker_threads'; import { initializeHash } from '../../../utils/hash'; import { getAndClearCumulativeDurations } from '../../../utils/profiling'; @@ -29,8 +28,8 @@ export interface InitRequest { compilerOptionOverrides?: CompilerOptionOverrides; buildType?: 'application' | 'library'; stylesheetPort: MessagePort; - webWorkerPort: MessagePort; - webWorkerSignal: Int32Array; + webWorkerPort?: MessagePort; + webWorkerSignal?: Int32Array; } let compilation: AngularCompilation | undefined; @@ -52,8 +51,9 @@ export async function initialize(request: InitRequest): Promise void, (reason: Error) => void] >(); request.stylesheetPort.on('message', ({ requestId, value, error }) => { @@ -81,7 +81,7 @@ export async function initialize(request: InitRequest): Promise((resolve, reject) => stylesheetRequests.set(requestId, [resolve, reject]), ); @@ -98,6 +98,10 @@ export async function initialize(request: InitRequest): Promise