Skip to content

Commit 9d04d0e

Browse files
committed
fix(@angular-devkit/build-angular): display accurate sizes for downlevelled files
Fixes angular#15425
1 parent 0a286a2 commit 9d04d0e

File tree

7 files changed

+151
-31
lines changed

7 files changed

+151
-31
lines changed

packages/angular_devkit/build_angular/src/angular-cli-files/utilities/package-chunk-sort.ts

+2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ export function generateEntryPoints(appConfig: {
2626
};
2727

2828
const entryPoints = [
29+
'runtime',
2930
'polyfills-nomodule-es5',
3031
'polyfills-es5',
3132
'polyfills',
3233
'sw-register',
3334
...extraEntryPoints(appConfig.styles, 'styles'),
3435
...extraEntryPoints(appConfig.scripts, 'scripts'),
36+
'vendor',
3537
'main',
3638
];
3739

packages/angular_devkit/build_angular/src/angular-cli-files/utilities/stats.ts

+32-11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// tslint:disable
99
// TODO: cleanup this file, it's copied as is from Angular CLI.
1010
import { tags, terminal } from '@angular-devkit/core';
11+
import * as path from 'path';
1112

1213

1314
const { bold, green, red, reset, white, yellow } = terminal;
@@ -23,27 +24,47 @@ export function formatSize(size: number): string {
2324
return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${abbreviations[index]}`;
2425
}
2526

27+
export function generateBundleStats(
28+
info: {
29+
id: string | number;
30+
size?: number;
31+
files: string[];
32+
names?: string[];
33+
entry: boolean;
34+
initial: boolean;
35+
rendered?: boolean;
36+
},
37+
colors: boolean,
38+
): string {
39+
const g = (x: string) => (colors ? bold(green(x)) : x);
40+
const y = (x: string) => (colors ? bold(yellow(x)) : x);
41+
42+
const size = typeof info.size === 'number' ? ` ${formatSize(info.size)}` : '';
43+
const files = info.files.map(f => path.basename(f)).join(', ');
44+
const names = info.names ? ` (${info.names.join(', ')})` : '';
45+
const initial = y(info.entry ? '[entry]' : info.initial ? '[initial]' : '');
46+
const flags = ['rendered', 'recorded']
47+
.map(f => (f && (info as any)[f] ? g(` [${f}]`) : ''))
48+
.join('');
49+
50+
return `chunk {${y(info.id.toString())}} ${g(files)}${names}${size} ${initial}${flags}`;
51+
}
52+
53+
export function generateBuildStats(hash: string, time: number, colors: boolean): string {
54+
const w = (x: string) => colors ? bold(white(x)) : x;
55+
return `Date: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`
56+
}
2657

2758
export function statsToString(json: any, statsConfig: any) {
2859
const colors = statsConfig.colors;
2960
const rs = (x: string) => colors ? reset(x) : x;
3061
const w = (x: string) => colors ? bold(white(x)) : x;
31-
const g = (x: string) => colors ? bold(green(x)) : x;
32-
const y = (x: string) => colors ? bold(yellow(x)) : x;
3362

3463
const changedChunksStats = json.chunks
3564
.filter((chunk: any) => chunk.rendered)
3665
.map((chunk: any) => {
3766
const asset = json.assets.filter((x: any) => x.name == chunk.files[0])[0];
38-
const size = asset ? ` ${formatSize(asset.size)}` : '';
39-
const files = chunk.files.join(', ');
40-
const names = chunk.names ? ` (${chunk.names.join(', ')})` : '';
41-
const initial = y(chunk.entry ? '[entry]' : chunk.initial ? '[initial]' : '');
42-
const flags = ['rendered', 'recorded']
43-
.map(f => f && chunk[f] ? g(` [${f}]`) : '')
44-
.join('');
45-
46-
return `chunk {${y(chunk.id)}} ${g(files)}${names}${size} ${initial}${flags}`;
67+
return generateBundleStats({ ...chunk, size: asset && asset.size }, colors);
4768
});
4869

4970
const unchangedChunkNumber = json.chunks.length - changedChunksStats.length;

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

+106-9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import {
5151
import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig';
5252
import { augmentAppWithServiceWorker } from '../angular-cli-files/utilities/service-worker';
5353
import {
54+
generateBuildStats,
55+
generateBundleStats,
5456
statsErrorsToString,
5557
statsToString,
5658
statsWarningsToString,
@@ -64,7 +66,12 @@ import {
6466
normalizeSourceMaps,
6567
} from '../utils';
6668
import { manglingDisabled } from '../utils/mangle-options';
67-
import { CacheKey, ProcessBundleOptions, ProcessBundleResult } from '../utils/process-bundle';
69+
import {
70+
CacheKey,
71+
ProcessBundleFile,
72+
ProcessBundleOptions,
73+
ProcessBundleResult,
74+
} from '../utils/process-bundle';
6875
import { assertCompatibleAngularVersion } from '../utils/version';
6976
import {
7077
generateBrowserWebpackConfigFromContext,
@@ -202,9 +209,6 @@ export function buildWebpackBrowser(
202209
// Check Angular version.
203210
assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
204211

205-
const loggingFn =
206-
transforms.logging || createBrowserLoggingCallback(!!options.verbose, context.logger);
207-
208212
return from(initialize(options, context, host, transforms.webpackConfiguration)).pipe(
209213
// tslint:disable-next-line: no-big-function
210214
switchMap(({ config: configs, projectRoot }) => {
@@ -222,14 +226,24 @@ export function buildWebpackBrowser(
222226
`);
223227
}
224228

229+
const useBundleDownleveling =
230+
isDifferentialLoadingNeeded && !(fullDifferential || options.watch);
231+
const startTime = Date.now();
232+
225233
return from(configs).pipe(
226234
// the concurrency parameter (3rd parameter of mergeScan) is deliberately
227235
// set to 1 to make sure the build steps are executed in sequence.
228236
mergeScan(
229237
(lastResult, config) => {
230238
// Make sure to only run the 2nd build step, if 1st one succeeded
231239
if (lastResult.success) {
232-
return runWebpack(config, context, { logging: loggingFn });
240+
return runWebpack(config, context, {
241+
logging:
242+
transforms.logging ||
243+
(useBundleDownleveling
244+
? () => {}
245+
: createBrowserLoggingCallback(!!options.verbose, context.logger)),
246+
});
233247
} else {
234248
return of();
235249
}
@@ -242,7 +256,19 @@ export function buildWebpackBrowser(
242256
switchMap(async buildEvents => {
243257
configs.length = 0;
244258
const success = buildEvents.every(r => r.success);
245-
if (success) {
259+
if (!success && useBundleDownleveling) {
260+
// If using bundle downleveling then there is only one build
261+
// If it fails show any diagnostic messages and bail
262+
const webpackStats = buildEvents[0].webpackStats;
263+
if (webpackStats && webpackStats.warnings.length > 0) {
264+
context.logger.warn(statsWarningsToString(webpackStats, { colors: true }));
265+
}
266+
if (webpackStats && webpackStats.errors.length > 0) {
267+
context.logger.error(statsErrorsToString(webpackStats, { colors: true }));
268+
}
269+
270+
return { success };
271+
} else if (success) {
246272
let noModuleFiles: EmittedFiles[] | undefined;
247273
let moduleFiles: EmittedFiles[] | undefined;
248274
let files: EmittedFiles[] | undefined;
@@ -263,7 +289,7 @@ export function buildWebpackBrowser(
263289
noModuleFiles = secondBuild.emittedFiles;
264290
}
265291
} else if (isDifferentialLoadingNeeded && !fullDifferential) {
266-
const { emittedFiles = [] } = firstBuild;
292+
const { emittedFiles = [], webpackStats } = firstBuild;
267293
moduleFiles = [];
268294
noModuleFiles = [];
269295

@@ -342,7 +368,9 @@ export function buildWebpackBrowser(
342368
filename,
343369
code,
344370
map,
345-
name: file.name,
371+
// id is always present for non-assets
372+
// tslint:disable-next-line: no-non-null-assertion
373+
name: file.id!,
346374
optimizeOnly: true,
347375
});
348376

@@ -356,7 +384,9 @@ export function buildWebpackBrowser(
356384
filename,
357385
code,
358386
map,
359-
name: file.name,
387+
// id is always present for non-assets
388+
// tslint:disable-next-line: no-non-null-assertion
389+
name: file.id!,
360390
runtime: file.file.startsWith('runtime'),
361391
ignoreOriginal: es5Polyfills,
362392
});
@@ -600,6 +630,73 @@ export function buildWebpackBrowser(
600630
}
601631

602632
context.logger.info('ES5 bundle generation complete.');
633+
634+
type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
635+
function generateBundleInfoStats(
636+
id: string | number,
637+
bundle: ProcessBundleFile,
638+
chunk: ArrayElement<webpack.Stats.ToJsonOutput['chunks']> | undefined,
639+
): string {
640+
return generateBundleStats(
641+
{
642+
id,
643+
size: bundle.size,
644+
files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename],
645+
names: chunk && chunk.names,
646+
entry: !!chunk && chunk.names.includes('runtime'),
647+
initial: !!chunk && chunk.initial,
648+
rendered: true,
649+
},
650+
true,
651+
);
652+
}
653+
654+
let bundleInfoText = '';
655+
const processedNames = new Set<string>();
656+
for (const result of processResults) {
657+
processedNames.add(result.name);
658+
659+
const chunk =
660+
webpackStats &&
661+
webpackStats.chunks &&
662+
webpackStats.chunks.find(c => result.name === c.id.toString());
663+
if (result.original) {
664+
bundleInfoText +=
665+
'\n' + generateBundleInfoStats(result.name, result.original, chunk);
666+
}
667+
if (result.downlevel) {
668+
bundleInfoText +=
669+
'\n' + generateBundleInfoStats(result.name, result.downlevel, chunk);
670+
}
671+
}
672+
673+
if (webpackStats && webpackStats.chunks) {
674+
for (const chunk of webpackStats.chunks) {
675+
if (processedNames.has(chunk.id.toString())) {
676+
continue;
677+
}
678+
679+
const asset =
680+
webpackStats.assets && webpackStats.assets.find(a => a.name === chunk.files[0]);
681+
bundleInfoText +=
682+
'\n' + generateBundleStats({ ...chunk, size: asset && asset.size }, true);
683+
}
684+
}
685+
686+
bundleInfoText +=
687+
'\n' +
688+
generateBuildStats(
689+
(webpackStats && webpackStats.hash) || '<unknown>',
690+
Date.now() - startTime,
691+
true,
692+
);
693+
context.logger.info(bundleInfoText);
694+
if (webpackStats && webpackStats.warnings.length > 0) {
695+
context.logger.warn(statsWarningsToString(webpackStats, { colors: true }));
696+
}
697+
if (webpackStats && webpackStats.errors.length > 0) {
698+
context.logger.error(statsErrorsToString(webpackStats, { colors: true }));
699+
}
603700
} else {
604701
const { emittedFiles = [] } = firstBuild;
605702
files = emittedFiles.filter(x => x.name !== 'polyfills-es5');

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface ProcessBundleOptions {
1919
filename: string;
2020
code: string;
2121
map?: string;
22-
name?: string;
22+
name: string;
2323
sourceMaps?: boolean;
2424
hiddenSourceMaps?: boolean;
2525
vendorSourceMaps?: boolean;
@@ -34,7 +34,7 @@ export interface ProcessBundleOptions {
3434
}
3535

3636
export interface ProcessBundleResult {
37-
name?: string;
37+
name: string;
3838
integrity?: string;
3939
original?: ProcessBundleFile;
4040
downlevel?: ProcessBundleFile;

tests/legacy-cli/e2e/tests/basic/scripts-array.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ export default async function () {
7373
await expectFileToMatch(
7474
'dist/test-project/index.html',
7575
oneLineTrim`
76+
<script src="runtime-es2015.js" type="module"></script>
77+
<script src="runtime-es5.js" nomodule defer></script>
7678
<script src="polyfills-es5.js" nomodule defer></script>
7779
<script src="polyfills-es2015.js" type="module"></script>
7880
<script src="scripts.js" defer></script>
7981
<script src="renamed-script.js" defer></script>
80-
<script src="runtime-es2015.js" type="module"></script>
8182
<script src="vendor-es2015.js" type="module"></script>
82-
<script src="main-es2015.js" type="module"></script>
83-
<script src="runtime-es5.js" nomodule defer></script>
8483
<script src="vendor-es5.js" nomodule defer></script>
84+
<script src="main-es2015.js" type="module"></script>
8585
<script src="main-es5.js" nomodule defer></script>
8686
`,
8787
);

tests/legacy-cli/e2e/tests/basic/styles-array.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ export default async function() {
6161
await expectFileToMatch(
6262
'dist/test-project/index.html',
6363
oneLineTrim`
64+
<script src="runtime-es2015.js" type="module"></script>
65+
<script src="runtime-es5.js" nomodule defer></script>
6466
<script src="polyfills-es5.js" nomodule defer></script>
6567
<script src="polyfills-es2015.js" type="module"></script>
66-
<script src="runtime-es2015.js" type="module"></script>
6768
<script src="vendor-es2015.js" type="module"></script>
68-
<script src="main-es2015.js" type="module"></script>
69-
<script src="runtime-es5.js" nomodule defer></script>
7069
<script src="vendor-es5.js" nomodule defer></script>
70+
<script src="main-es2015.js" type="module"></script>
7171
<script src="main-es5.js" nomodule defer></script>
7272
`,
7373
);

tests/legacy-cli/e2e/tests/misc/support-safari-10.1.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ export default async function () {
4343
`);
4444
} else {
4545
await expectFileToMatch('dist/test-project/index.html', oneLineTrim`
46+
<script src="runtime-es2015.js" type="module"></script>
47+
<script src="runtime-es5.js" nomodule defer></script>
4648
<script src="polyfills-nomodule-es5.js" nomodule></script>
4749
<script src="polyfills-es5.js" nomodule defer></script>
4850
<script src="polyfills-es2015.js" type="module"></script>
4951
<script src="styles-es2015.js" type="module"></script>
5052
<script src="styles-es5.js" nomodule defer></script>
51-
<script src="runtime-es2015.js" type="module"></script>
5253
<script src="vendor-es2015.js" type="module"></script>
53-
<script src="main-es2015.js" type="module"></script>
54-
<script src="runtime-es5.js" nomodule defer></script>
5554
<script src="vendor-es5.js" nomodule defer></script>
55+
<script src="main-es2015.js" type="module"></script>
5656
<script src="main-es5.js" nomodule defer></script>
5757
`);
5858
}

0 commit comments

Comments
 (0)