Skip to content

Commit 7718efe

Browse files
clydinalan-agius4
authored andcommitted
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. (cherry picked from commit 438c6d9)
1 parent c0694ca commit 7718efe

File tree

1 file changed

+29
-6
lines changed

1 file changed

+29
-6
lines changed

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

+29-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
import { Importer, ImporterReturnType, Options, Result, SassException } from 'sass';
1010
import { MessageChannel, Worker } from 'worker_threads';
11+
import { maxWorkers } from '../utils/environment-options';
12+
13+
/**
14+
* The maximum number of Workers that will be created to execute render requests.
15+
*/
16+
const MAX_RENDER_WORKERS = maxWorkers;
1117

1218
/**
1319
* The callback type for the `dart-sass` asynchronous render function.
@@ -19,6 +25,7 @@ type RenderCallback = (error?: SassException, result?: Result) => void;
1925
*/
2026
interface RenderRequest {
2127
id: number;
28+
workerIndex: number;
2229
callback: RenderCallback;
2330
importers?: Importer[];
2431
}
@@ -39,9 +46,11 @@ interface RenderResponseMessage {
3946
* the worker which can be up to two times faster than the asynchronous variant.
4047
*/
4148
export class SassWorkerImplementation {
42-
private worker?: Worker;
49+
private readonly workers: Worker[] = [];
50+
private readonly availableWorkers: number[] = [];
4351
private readonly requests = new Map<number, RenderRequest>();
4452
private idCounter = 1;
53+
private nextWorkerIndex = 0;
4554

4655
/**
4756
* Provides information about the Sass implementation.
@@ -74,14 +83,23 @@ export class SassWorkerImplementation {
7483
throw new Error('Sass custom functions are not supported.');
7584
}
7685

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

81-
const request = this.createRequest(callback, importer);
99+
const request = this.createRequest(workerIndex, callback, importer);
82100
this.requests.set(request.id, request);
83101

84-
this.worker.postMessage({
102+
this.workers[workerIndex].postMessage({
85103
id: request.id,
86104
hasImporter: !!importer,
87105
options: serializableOptions,
@@ -96,7 +114,9 @@ export class SassWorkerImplementation {
96114
* is only needed if early cleanup is needed.
97115
*/
98116
close(): void {
99-
this.worker?.terminate();
117+
for (const worker of this.workers) {
118+
void worker.terminate();
119+
}
100120
this.requests.clear();
101121
}
102122

@@ -117,6 +137,7 @@ export class SassWorkerImplementation {
117137
}
118138

119139
this.requests.delete(response.id);
140+
this.availableWorkers.push(request.workerIndex);
120141

121142
if (response.result) {
122143
// The results are expected to be Node.js `Buffer` objects but will each be transferred as
@@ -193,11 +214,13 @@ export class SassWorkerImplementation {
193214
}
194215

195216
private createRequest(
217+
workerIndex: number,
196218
callback: RenderCallback,
197219
importer: Importer | Importer[] | undefined,
198220
): RenderRequest {
199221
return {
200222
id: this.idCounter++,
223+
workerIndex,
201224
callback,
202225
importers: !importer || Array.isArray(importer) ? importer : [importer],
203226
};

0 commit comments

Comments
 (0)