Skip to content

Commit 8d98874

Browse files
committed
perf(@angular-devkit/build-angular): render Sass using a pool of workers
A pool of Workers is now used to process Sass render requests. This change allows multiple synchronous render operations to occur at the same time. Sass synchronous render operations can be up to two times faster than the asynchronous variant. The benefit will be most pronounced in applications with large amounts of Sass stylesheets.
1 parent 80a08b5 commit 8d98874

File tree

1 file changed

+28
-6
lines changed

1 file changed

+28
-6
lines changed

packages/angular_devkit/build_angular/src/sass/sass-service.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import { Importer, ImporterReturnType, Options, Result, SassException } from 'sass';
1010
import { MessageChannel, Worker } from 'worker_threads';
1111

12+
/**
13+
* The maximum number of Workers that will be created to execute render requests.
14+
*/
15+
const MAX_RENDER_WORKERS = 4;
16+
1217
/**
1318
* The callback type for the `dart-sass` asynchronous render function.
1419
*/
@@ -19,6 +24,7 @@ type RenderCallback = (error?: SassException, result?: Result) => void;
1924
*/
2025
interface RenderRequest {
2126
id: number;
27+
workerIndex: number;
2228
callback: RenderCallback;
2329
importers?: Importer[];
2430
}
@@ -39,9 +45,11 @@ interface RenderResponseMessage {
3945
* the worker which can be up to two times faster than the asynchronous variant.
4046
*/
4147
export class SassWorkerImplementation {
42-
private worker?: Worker;
48+
private readonly workers: Worker[] = [];
49+
private readonly availableWorkers: number[] = [];
4350
private readonly requests = new Map<number, RenderRequest>();
4451
private idCounter = 1;
52+
private nextWorkerIndex = 0;
4553

4654
/**
4755
* Provides information about the Sass implementation.
@@ -74,14 +82,23 @@ export class SassWorkerImplementation {
7482
throw new Error('Sass custom functions are not supported.');
7583
}
7684

77-
if (!this.worker) {
78-
this.worker = this.createWorker();
85+
let workerIndex = this.availableWorkers.pop();
86+
if (workerIndex === undefined) {
87+
if (this.workers.length < MAX_RENDER_WORKERS) {
88+
workerIndex = this.workers.length;
89+
this.workers.push(this.createWorker());
90+
} else {
91+
workerIndex = this.nextWorkerIndex++;
92+
if (this.nextWorkerIndex >= this.workers.length) {
93+
this.nextWorkerIndex = 0;
94+
}
95+
}
7996
}
8097

81-
const request = this.createRequest(callback, importer);
98+
const request = this.createRequest(workerIndex, callback, importer);
8299
this.requests.set(request.id, request);
83100

84-
this.worker.postMessage({
101+
this.workers[workerIndex].postMessage({
85102
id: request.id,
86103
hasImporter: !!importer,
87104
options: serializableOptions,
@@ -96,7 +113,9 @@ export class SassWorkerImplementation {
96113
* is only needed if early cleanup is needed.
97114
*/
98115
close(): void {
99-
this.worker?.terminate();
116+
for (const worker of this.workers) {
117+
void worker.terminate();
118+
}
100119
this.requests.clear();
101120
}
102121

@@ -117,6 +136,7 @@ export class SassWorkerImplementation {
117136
}
118137

119138
this.requests.delete(response.id);
139+
this.availableWorkers.push(request.workerIndex);
120140

121141
if (response.result) {
122142
// The results are expected to be Node.js `Buffer` objects but will each be transferred as
@@ -193,11 +213,13 @@ export class SassWorkerImplementation {
193213
}
194214

195215
private createRequest(
216+
workerIndex: number,
196217
callback: RenderCallback,
197218
importer: Importer | Importer[] | undefined,
198219
): RenderRequest {
199220
return {
200221
id: this.idCounter++,
222+
workerIndex,
201223
callback,
202224
importers: !importer || Array.isArray(importer) ? importer : [importer],
203225
};

0 commit comments

Comments
 (0)