Skip to content

Commit bdedff8

Browse files
clydinvikerman
authored andcommitted
refactor(@angular-devkit/build-angular): improve performance of parallel bundle processing
1 parent 459aff9 commit bdedff8

File tree

5 files changed

+89
-40
lines changed

5 files changed

+89
-40
lines changed

packages/angular_devkit/build_angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"find-cache-dir": "3.0.0",
2828
"glob": "7.1.4",
2929
"istanbul-instrumenter-loader": "3.0.1",
30+
"jest-worker": "24.9.0",
3031
"karma-source-map-support": "1.4.0",
3132
"less": "3.9.0",
3233
"less-loader": "5.0.0",
@@ -61,7 +62,6 @@
6162
"webpack-merge": "4.2.1",
6263
"webpack-sources": "1.4.3",
6364
"webpack-subresource-integrity": "1.1.0-rc.6",
64-
"worker-farm": "1.7.0",
6565
"worker-plugin": "3.2.0"
6666
},
6767
"devDependencies": {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import JestWorker from 'jest-worker';
9+
import * as os from 'os';
10+
11+
export class ActionExecutor<Input extends { size: number }, Output> {
12+
private largeWorker: JestWorker;
13+
private smallWorker: JestWorker;
14+
15+
private smallThreshold = 32 * 1024;
16+
17+
constructor(actionFile: string, private readonly actionName: string) {
18+
// larger files are processed in a separate process to limit memory usage in the main process
19+
this.largeWorker = new JestWorker(actionFile, {
20+
exposedMethods: [actionName],
21+
});
22+
23+
// small files are processed in a limited number of threads to improve speed
24+
// The limited number also prevents a large increase in memory usage for an otherwise short operation
25+
this.smallWorker = new JestWorker(actionFile, {
26+
exposedMethods: [actionName],
27+
numWorkers: os.cpus().length < 2 ? 1 : 2,
28+
// Will automatically fallback to processes if not supported
29+
enableWorkerThreads: true,
30+
});
31+
}
32+
33+
execute(options: Input): Promise<Output> {
34+
if (options.size > this.smallThreshold) {
35+
return ((this.largeWorker as unknown) as Record<string, (options: Input) => Promise<Output>>)[
36+
this.actionName
37+
](options);
38+
} else {
39+
return ((this.smallWorker as unknown) as Record<string, (options: Input) => Promise<Output>>)[
40+
this.actionName
41+
](options);
42+
}
43+
}
44+
45+
executeAll(options: Input[]): Promise<Output[]> {
46+
return Promise.all(options.map(o => this.execute(o)));
47+
}
48+
49+
stop() {
50+
this.largeWorker.end();
51+
this.smallWorker.end();
52+
}
53+
}

packages/angular_devkit/build_angular/src/browser/index.ts

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { from, of } from 'rxjs';
3333
import { bufferCount, catchError, concatMap, map, mergeScan, switchMap } from 'rxjs/operators';
3434
import { ScriptTarget } from 'typescript';
3535
import * as webpack from 'webpack';
36-
import * as workerFarm from 'worker-farm';
3736
import { NgBuildAnalyticsPlugin } from '../../plugins/webpack/analytics';
3837
import { WebpackConfigOptions } from '../angular-cli-files/models/build-options';
3938
import {
@@ -80,6 +79,7 @@ import {
8079
getIndexInputFile,
8180
getIndexOutputFile,
8281
} from '../utils/webpack-browser-config';
82+
import { ActionExecutor } from './action-executor';
8383
import { Schema as BrowserBuilderSchema } from './schema';
8484

8585
const cacache = require('cacache');
@@ -598,35 +598,25 @@ export function buildWebpackBrowser(
598598
}
599599

600600
if (processActions.length > 0) {
601-
await new Promise<void>((resolve, reject) => {
602-
const workerFile = require.resolve('../utils/process-bundle');
603-
const workers = workerFarm(
604-
{
605-
maxRetries: 1,
606-
},
607-
path.extname(workerFile) !== '.ts'
608-
? workerFile
609-
: require.resolve('../utils/process-bundle-bootstrap'),
610-
['process'],
611-
);
612-
let completed = 0;
613-
const workCallback = (error: Error | null, result: ProcessBundleResult) => {
614-
if (error) {
615-
workerFarm.end(workers);
616-
reject(error);
617-
618-
return;
619-
}
620-
621-
processResults.push(result);
622-
if (++completed === processActions.length) {
623-
workerFarm.end(workers);
624-
resolve();
625-
}
626-
};
601+
const workerFile = require.resolve('../utils/process-bundle');
602+
const executor = new ActionExecutor<
603+
ProcessBundleOptions & { size: number },
604+
ProcessBundleResult
605+
>(
606+
path.extname(workerFile) !== '.ts'
607+
? workerFile
608+
: require.resolve('../utils/process-bundle-bootstrap'),
609+
'process',
610+
);
627611

628-
processActions.forEach(action => workers['process'](action, workCallback));
629-
});
612+
try {
613+
const results = await executor.executeAll(
614+
processActions.map(a => ({ ...a, size: a.code.length })),
615+
);
616+
results.forEach(result => processResults.push(result));
617+
} finally {
618+
executor.stop();
619+
}
630620
}
631621

632622
// Runtime must be processed after all other files
@@ -636,7 +626,7 @@ export function buildWebpackBrowser(
636626
runtimeData: processResults,
637627
};
638628
processResults.push(
639-
await import('../utils/process-bundle').then(m => m.processAsync(runtimeOptions)),
629+
await import('../utils/process-bundle').then(m => m.process(runtimeOptions)),
640630
);
641631
}
642632

packages/angular_devkit/build_angular/src/utils/process-bundle.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,7 @@ export const enum CacheKey {
5757
DownlevelMap = 3,
5858
}
5959

60-
export function process(
61-
options: ProcessBundleOptions,
62-
callback: (error: Error | null, result?: ProcessBundleResult) => void,
63-
): void {
64-
processAsync(options).then(result => callback(null, result), error => callback(error));
65-
}
66-
67-
export async function processAsync(options: ProcessBundleOptions): Promise<ProcessBundleResult> {
60+
export async function process(options: ProcessBundleOptions): Promise<ProcessBundleResult> {
6861
if (!options.cacheKeys) {
6962
options.cacheKeys = [];
7063
}

yarn.lock

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6531,6 +6531,14 @@ jasminewd2@^2.1.0:
65316531
resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.2.0.tgz#e37cf0b17f199cce23bea71b2039395246b4ec4e"
65326532
integrity sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=
65336533

6534+
6535+
version "24.9.0"
6536+
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
6537+
integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
6538+
dependencies:
6539+
merge-stream "^2.0.0"
6540+
supports-color "^6.1.0"
6541+
65346542
jquery@^3.3.1:
65356543
version "3.3.1"
65366544
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
@@ -7515,6 +7523,11 @@ [email protected]:
75157523
dependencies:
75167524
source-map "^0.5.6"
75177525

7526+
merge-stream@^2.0.0:
7527+
version "2.0.0"
7528+
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
7529+
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
7530+
75187531
methods@~1.1.2:
75197532
version "1.1.2"
75207533
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@@ -12177,7 +12190,7 @@ wordwrap@~0.0.2:
1217712190
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
1217812191
integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
1217912192

12180-
worker-farm@1.7.0, worker-farm@^1.7.0:
12193+
worker-farm@^1.7.0:
1218112194
version "1.7.0"
1218212195
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
1218312196
integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==

0 commit comments

Comments
 (0)