Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): improve build stats output format
Browse files Browse the repository at this point in the history
With this change we also remove sourcemaps from build info to align with Webpack 5 output.
  • Loading branch information
alan-agius4 authored and filipesilva committed Sep 18, 2020
1 parent 9025637 commit 5996896
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 46 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"@types/resolve": "^1.17.1",
"@types/rimraf": "^3.0.0",
"@types/semver": "^7.0.0",
"@types/text-table": "^0.2.1",
"@types/universal-analytics": "^0.4.2",
"@types/uuid": "^8.0.0",
"@types/webpack": "^4.41.22",
Expand Down Expand Up @@ -215,6 +216,7 @@
"temp": "^0.9.0",
"terser": "5.3.1",
"terser-webpack-plugin": "4.2.1",
"text-table": "0.2.0",
"through2": "^4.0.0",
"tree-kill": "1.2.2",
"ts-api-guardian": "0.5.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ ts_library(
"@npm//@types/node",
"@npm//@types/rimraf",
"@npm//@types/semver",
"@npm//@types/text-table",
"@npm//@types/webpack",
"@npm//@types/webpack-dev-server",
"@npm//@types/webpack-sources",
Expand Down Expand Up @@ -182,6 +183,7 @@ ts_library(
"@npm//stylus-loader",
"@npm//terser",
"@npm//terser-webpack-plugin",
"@npm//text-table",
"@npm//tree-kill",
"@npm//tslint",
"@npm//typescript",
Expand Down
1 change: 1 addition & 0 deletions packages/angular_devkit/build_angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"stylus-loader": "3.0.2",
"terser": "5.3.1",
"terser-webpack-plugin": "4.2.1",
"text-table": "0.2.0",
"tree-kill": "1.2.2",
"webpack": "4.44.1",
"webpack-dev-middleware": "3.7.2",
Expand Down
25 changes: 12 additions & 13 deletions packages/angular_devkit/build_angular/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ import {
import { NgBuildAnalyticsPlugin } from '../webpack/plugins/analytics';
import { markAsyncChunksNonInitial } from '../webpack/utils/async-chunks';
import {
BundleStats,
createWebpackLoggingCallback,
generateBuildStats,
generateBuildStatsTable,
generateBundleStats,
statsErrorsToString,
statsHasErrors,
Expand Down Expand Up @@ -579,13 +581,11 @@ export function buildWebpackBrowser(

type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
function generateBundleInfoStats(
id: string | number,
bundle: ProcessBundleFile,
chunk: ArrayElement<webpack.Stats.ToJsonOutput['chunks']> | undefined,
): string {
): BundleStats {
return generateBundleStats(
{
id,
size: bundle.size,
files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename],
names: chunk && chunk.names,
Expand All @@ -597,19 +597,17 @@ export function buildWebpackBrowser(
);
}

let bundleInfoText = '';
const bundleInfoStats: BundleStats[] = [];
for (const result of processResults) {
const chunk = webpackStats.chunks
&& webpackStats.chunks.find((chunk) => chunk.id.toString() === result.name);

if (result.original) {
bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.original, chunk);
bundleInfoStats.push(generateBundleInfoStats(result.original, chunk));
}

if (result.downlevel) {
bundleInfoText +=
'\n' + generateBundleInfoStats(result.name, result.downlevel, chunk);
bundleInfoStats.push(generateBundleInfoStats(result.downlevel, chunk));
}
}

Expand All @@ -620,18 +618,19 @@ export function buildWebpackBrowser(
for (const chunk of unprocessedChunks) {
const asset =
webpackStats.assets && webpackStats.assets.find(a => a.name === chunk.files[0]);
bundleInfoText +=
'\n' + generateBundleStats({ ...chunk, size: asset && asset.size }, true);
bundleInfoStats.push(generateBundleStats({ ...chunk, size: asset && asset.size }, true));
}

bundleInfoText +=
context.logger.info(
'\n' +
generateBuildStatsTable(bundleInfoStats, colors.enabled) +
'\n\n' +
generateBuildStats(
(webpackStats && webpackStats.hash) || '<unknown>',
Date.now() - startTime,
true,
);
context.logger.info(bundleInfoText);
),
);

// Check for budget errors and display them to the user.
const budgets = options.budgets || [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,11 @@ describe('Browser Builder scripts array', () => {
{ logger },
);

expect(logs.join('\n')).toMatch(/\(lazy-script\) 69 bytes.*\[entry].*\[rendered]/);
expect(logs.join('\n')).toMatch(/\(renamed-script\) 78 bytes.*\[entry].*\[rendered]/);
expect(logs.join('\n')).toMatch(/\(renamed-lazy-script\) 88 bytes.*\[entry].*\[rendered]/);
const joinedLogs = logs.join('\n');
expect(joinedLogs).toMatch(/lazy-script.+69 bytes/);
expect(joinedLogs).toMatch(/renamed-script.+78 bytes/);
expect(joinedLogs).toMatch(/renamed-lazy-script.+88 bytes/);
expect(joinedLogs).not.toContain('Lazy Chunks');
});

it(`should error when a script doesn't exist`, async () => {
Expand Down
114 changes: 84 additions & 30 deletions packages/angular_devkit/build_angular/src/webpack/utils/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
import { logging, tags } from '@angular-devkit/core';
import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
import * as path from 'path';
import { colors as ansiColors } from '../../utils/color';
import * as textTable from 'text-table';
import { colors as ansiColors, removeColor } from '../../utils/color';

export function formatSize(size: number): string {
if (size <= 0) {
Expand All @@ -23,9 +24,15 @@ export function formatSize(size: number): string {
return `${+(size / Math.pow(1024, index)).toPrecision(3)} ${abbreviations[index]}`;
}

export type BundleStatsData = [files: string, names: string, size: string];

export interface BundleStats {
initial: boolean;
stats: BundleStatsData;
};

export function generateBundleStats(
info: {
id: string | number;
size?: number;
files: string[];
names?: string[];
Expand All @@ -34,53 +41,100 @@ export function generateBundleStats(
rendered?: boolean;
},
colors: boolean,
): string {
const g = (x: string) => (colors ? ansiColors.bold.green(x) : x);
const y = (x: string) => (colors ? ansiColors.bold.yellow(x) : x);

const id = info.id ? y(info.id.toString()) : '';
const size = typeof info.size === 'number' ? ` ${formatSize(info.size)}` : '';
const files = info.files.map(f => path.basename(f)).join(', ');
const names = info.names ? ` (${info.names.join(', ')})` : '';
const initial = y(info.entry ? '[entry]' : info.initial ? '[initial]' : '');
const flags = ['rendered', 'recorded']
.map(f => (f && (info as any)[f] ? g(` [${f}]`) : ''))
.join('');

return `chunk {${id}} ${g(files)}${names}${size} ${initial}${flags}`;
): BundleStats {
const g = (x: string) => (colors ? ansiColors.greenBright(x) : x);
const c = (x: string) => (colors ? ansiColors.cyanBright(x) : x);

const size = typeof info.size === 'number' ? formatSize(info.size) : '-';
const files = info.files.filter(f => !f.endsWith('.map')).map(f => path.basename(f)).join(', ');
const names = info.names?.length ? info.names.join(', ') : '-';
const initial = !!(info.entry || info.initial);

return {
initial,
stats: [g(files), names, c(size)],
}
}

export function generateBuildStatsTable(data: BundleStats[], colors: boolean): string {
const changedEntryChunksStats: BundleStatsData[] = [];
const changedLazyChunksStats: BundleStatsData[] = [];
for (const {initial, stats} of data) {
if (initial) {
changedEntryChunksStats.push(stats);
} else {
changedLazyChunksStats.push(stats);
}
}

const bundleInfo: string[][] = [];

const bold = (x: string) => colors ? ansiColors.bold(x) : x;
const dim = (x: string) => colors ? ansiColors.dim(x) : x;

// Entry chunks
if (changedEntryChunksStats.length) {
bundleInfo.push(
['Initial Chunk Files', 'Names', 'Size'].map(bold),
...changedEntryChunksStats,
);
}

// Seperator
if (changedEntryChunksStats.length && changedLazyChunksStats.length) {
bundleInfo.push([]);
}

// Lazy chunks
if (changedLazyChunksStats.length) {
bundleInfo.push(
['Lazy Chunk Files', 'Names', 'Size'].map(bold),
...changedLazyChunksStats,
);
}

return textTable(bundleInfo, {
hsep: dim(' | '),
stringLength: s => removeColor(s).length,
});
}

export function generateBuildStats(hash: string, time: number, colors: boolean): string {
const w = (x: string) => colors ? ansiColors.bold.white(x) : x;
return `Date: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`
return `Build at: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`;
}

export function statsToString(json: any, statsConfig: any) {
const colors = statsConfig.colors;
const rs = (x: string) => colors ? ansiColors.reset(x) : x;
const w = (x: string) => colors ? ansiColors.bold.white(x) : x;

const changedChunksStats = json.chunks
.filter((chunk: any) => chunk.rendered)
.map((chunk: any) => {
const assets = json.assets.filter((asset: any) => chunk.files.indexOf(asset.name) != -1);
const summedSize = assets.filter((asset: any) => !asset.name.endsWith(".map")).reduce((total: number, asset: any) => { return total + asset.size }, 0);
return generateBundleStats({ ...chunk, size: summedSize }, colors);
});
const changedChunksStats: BundleStats[] = [];
for (const chunk of json.chunks) {
if (!chunk.rendered) {
continue;
}

const assets = json.assets.filter((asset: any) => chunk.files.includes(asset.name));
const summedSize = assets.filter((asset: any) => !asset.name.endsWith(".map")).reduce((total: number, asset: any) => { return total + asset.size }, 0);
changedChunksStats.push(generateBundleStats({ ...chunk, size: summedSize }, colors));
}

const unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
const statsTable = generateBuildStatsTable(changedChunksStats, colors);

if (unchangedChunkNumber > 0) {
return '\n' + rs(tags.stripIndents`
Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)}
${statsTable}
${unchangedChunkNumber} unchanged chunks
${changedChunksStats.join('\n')}
Time: ${w('' + json.time)}ms
${generateBuildStats(json.hash, json.time, colors)}
`);
} else {
return '\n' + rs(tags.stripIndents`
${changedChunksStats.join('\n')}
Date: ${w(new Date().toISOString())} - Hash: ${w(json.hash)} - Time: ${w('' + json.time)}ms
${statsTable}
${generateBuildStats(json.hash, json.time, colors)}
`);
}
}
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,11 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==

"@types/text-table@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.1.tgz#39c4d4a058a82f677392dfd09976e83d9b4c9264"
integrity sha512-dchbFCWfVgUSWEvhOkXGS7zjm+K7jCUvGrQkAHPk2Fmslfofp4HQTH2pqnQ3Pw5GPYv0zWa2AQjKtsfZThuemQ==

"@types/through@*":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895"
Expand Down Expand Up @@ -11550,6 +11555,11 @@ text-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"
integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==

[email protected]:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=

through2@^2.0.0, through2@^2.0.2, through2@~2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
Expand Down

0 comments on commit 5996896

Please sign in to comment.