Skip to content

Commit

Permalink
Log bundle metrics (#733)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jasper De Moor authored and devongovett committed Feb 14, 2018
1 parent d3ae5f6 commit 6deec80
Show file tree
Hide file tree
Showing 12 changed files with 332 additions and 19 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
"cross-spawn": "^6.0.4",
"cssnano": "^3.10.0",
"dotenv": "^5.0.0",
"filesize": "^3.6.0",
"get-port": "^3.2.0",
"glob": "^7.1.2",
"grapheme-breaker": "^0.3.2",
"htmlnano": "^0.1.6",
"is-url": "^1.2.2",
"js-yaml": "^3.10.0",
Expand All @@ -52,6 +54,7 @@
"serialize-to-js": "^1.1.1",
"serve-static": "^1.12.4",
"source-map": "0.6.1",
"strip-ansi": "^4.0.0",
"toml": "^2.3.3",
"tomlify-j0.4": "^3.0.0",
"uglify-es": "^3.2.1",
Expand Down
2 changes: 2 additions & 0 deletions src/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class Asset {
this.parentBundle = null;
this.bundles = new Set();
this.cacheData = {};
this.buildTime = 0;
this.bundledSize = 0;
}

shouldInvalidate() {
Expand Down
14 changes: 14 additions & 0 deletions src/Bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Bundle {
this.siblingBundles = new Set();
this.siblingBundlesMap = new Map();
this.offsets = new Map();
this.totalSize = 0;
this.bundleTime = 0;
}

static createWithAsset(asset, parentBundle) {
Expand Down Expand Up @@ -122,6 +124,7 @@ class Bundle {
let Packager = bundler.packagers.get(this.type);
let packager = new Packager(this, bundler);

let startTime = Date.now();
await packager.start();

let included = new Set();
Expand All @@ -130,6 +133,11 @@ class Bundle {
}

await packager.end();

this.bundleTime = Date.now() - startTime;
for (let asset of this.assets) {
this.bundleTime += asset.buildTime;
}
}

async _addDeps(asset, packager, included) {
Expand All @@ -144,6 +152,12 @@ class Bundle {
}

await packager.addAsset(asset);
this.addAssetSize(asset, packager.getSize() - this.totalSize);
}

addAssetSize(asset, size) {
asset.bundledSize = size;
this.totalSize += size;
}

getParents() {
Expand Down
15 changes: 10 additions & 5 deletions src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const config = require('./utils/config');
const emoji = require('./utils/emoji');
const loadEnv = require('./utils/env');
const PromiseQueue = require('./utils/PromiseQueue');
const bundleReport = require('./utils/bundleReport');
const prettifyTime = require('./utils/prettifyTime');

/**
* The Bundler is the main entry point. It resolves and loads assets,
Expand Down Expand Up @@ -93,7 +95,8 @@ class Bundler extends EventEmitter {
typeof options.sourceMaps === 'boolean'
? options.sourceMaps
: !isProduction,
hmrHostname: options.hmrHostname || ''
hmrHostname: options.hmrHostname || '',
detailedReport: options.detailedReport || false
};
}

Expand Down Expand Up @@ -200,11 +203,11 @@ class Bundler extends EventEmitter {
this.unloadOrphanedAssets();

let buildTime = Date.now() - startTime;
let time =
buildTime < 1000
? `${buildTime}ms`
: `${(buildTime / 1000).toFixed(2)}s`;
let time = prettifyTime(buildTime);
logger.status(emoji.success, `Built in ${time}.`, 'green');
if (!this.watcher) {
bundleReport(bundle, this.options.detailedReport);
}

this.emit('bundled', bundle);
return bundle;
Expand Down Expand Up @@ -378,6 +381,7 @@ class Bundler extends EventEmitter {
asset.processed = true;

// First try the cache, otherwise load and compile in the background
let startTime = Date.now();
let processed = this.cache && (await this.cache.read(asset.name));
if (!processed || asset.shouldInvalidate(processed.cacheData)) {
processed = await this.farm.run(asset.name, asset.package, this.options);
Expand All @@ -386,6 +390,7 @@ class Bundler extends EventEmitter {
}
}

asset.buildTime = Date.now() - startTime;
asset.generated = processed.generated;
asset.hash = processed.hash;

Expand Down
42 changes: 42 additions & 0 deletions src/Logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const chalk = require('chalk');
const readline = require('readline');
const prettyError = require('./utils/prettyError');
const emoji = require('./utils/emoji');
const {countBreaks} = require('grapheme-breaker');
const stripAnsi = require('strip-ansi');

class Logger {
constructor(options) {
Expand Down Expand Up @@ -127,6 +129,46 @@ class Logger {
_log(message) {
console.log(message);
}

table(columns, table) {
// Measure column widths
let colWidths = [];
for (let row of table) {
let i = 0;
for (let item of row) {
colWidths[i] = Math.max(colWidths[i] || 0, stringWidth(item));
i++;
}
}

// Render rows
for (let row of table) {
let items = row.map((item, i) => {
// Add padding between columns unless the alignment is the opposite to the
// next column and pad to the column width.
let padding =
!columns[i + 1] || columns[i + 1].align === columns[i].align ? 4 : 0;
return pad(item, colWidths[i] + padding, columns[i].align);
});

this.log(items.join(''));
}
}
}

// Pad a string with spaces on either side
function pad(text, length, align = 'left') {
let pad = ' '.repeat(length - stringWidth(text));
if (align === 'right') {
return pad + text;
}

return text + pad;
}

// Count visible characters in a string
function stringWidth(string) {
return countBreaks(stripAnsi('' + string));
}

// If we are in a worker, make a proxy class which will
Expand Down
4 changes: 4 additions & 0 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ program
'set the runtime environment, either "node", "browser" or "electron". defaults to "browser"',
/^(node|browser|electron)$/
)
.option(
'--detailed-report',
'print a detailed build report after a completed build'
)
.action(bundle);

program
Expand Down
4 changes: 4 additions & 0 deletions src/packagers/Packager.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class Packager {
throw new Error('Must be implemented by subclasses');
}

getSize() {
return this.dest.bytesWritten;
}

async end() {
await this.dest.end();
}
Expand Down
5 changes: 5 additions & 0 deletions src/packagers/RawPackager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ class RawPackager extends Packager {
contents = await fs.readFile(contents ? contents.path : asset.name);
}

this.size = contents.length;
await fs.writeFile(name, contents);
}

getSize() {
return this.size || 0;
}

end() {}
}

Expand Down
8 changes: 0 additions & 8 deletions src/transforms/uglify.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const {minify} = require('uglify-es');
const logger = require('../Logger');

module.exports = async function(asset) {
await asset.parseIfNeeded();
Expand All @@ -24,13 +23,6 @@ module.exports = async function(asset) {
throw result.error;
}

// Log all warnings
if (result.warnings) {
result.warnings.forEach(warning => {
logger.warn('[uglify] ' + warning);
});
}

// babel-generator did our code generation for us, so remove the old AST
asset.ast = null;
asset.outputCode = result.code;
Expand Down
97 changes: 97 additions & 0 deletions src/utils/bundleReport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const path = require('path');
const prettifyTime = require('./prettifyTime');
const logger = require('../Logger');
const emoji = require('./emoji');
const filesize = require('filesize');

const LARGE_BUNDLE_SIZE = 1024 * 1024;
const NUM_LARGE_ASSETS = 10;
const COLUMNS = [
{align: 'left'}, // name
{align: 'right'}, // size
{align: 'right'} // time
];

function bundleReport(mainBundle, detailed = false) {
// Get a list of bundles sorted by size
let bundles = Array.from(iterateBundles(mainBundle)).sort(
(a, b) => b.totalSize - a.totalSize
);
let rows = [];

for (let bundle of bundles) {
// Add a row for the bundle
rows.push([
formatFilename(bundle.name, logger.chalk.cyan.bold),
logger.chalk.bold(
prettifySize(bundle.totalSize, bundle.totalSize > LARGE_BUNDLE_SIZE)
),
logger.chalk.green.bold(prettifyTime(bundle.bundleTime))
]);

// If detailed, generate a list of the top 10 largest assets in the bundle
if (detailed && bundle.assets.size > 1) {
let assets = Array.from(bundle.assets)
.filter(a => a.type === bundle.type)
.sort((a, b) => b.bundledSize - a.bundledSize);

let largestAssets = assets.slice(0, NUM_LARGE_ASSETS);
for (let asset of largestAssets) {
// Add a row for the asset.
rows.push([
(asset == assets[assets.length - 1] ? '└── ' : '├── ') +
formatFilename(asset.name, logger.chalk.reset),
logger.chalk.dim(prettifySize(asset.bundledSize)),
logger.chalk.dim(logger.chalk.green(prettifyTime(asset.buildTime)))
]);
}

// Show how many more assets there are
if (assets.length > largestAssets.length) {
rows.push([
'└── ' +
logger.chalk.dim(
`+ ${assets.length - largestAssets.length} more assets`
)
]);
}

// If this isn't the last bundle, add an empty row before the next one
if (bundle !== bundles[bundles.length - 1]) {
rows.push([]);
}
}
}

// Render table
logger.log('');
logger.table(COLUMNS, rows);
}

module.exports = bundleReport;

function* iterateBundles(bundle) {
yield bundle;
for (let child of bundle.childBundles) {
yield* iterateBundles(child);
}
}

function prettifySize(size, isLarge) {
let res = filesize(size);
if (isLarge) {
res = logger.chalk.yellow(emoji.warning + ' ' + res);
} else {
res = logger.chalk.magenta(res);
}

return res;
}

function formatFilename(filename, color = logger.chalk.reset) {
let dir = path.relative(process.cwd(), path.dirname(filename));
return (
logger.chalk.dim(dir + (dir ? path.sep : '')) +
color(path.basename(filename))
);
}
3 changes: 3 additions & 0 deletions src/utils/prettifyTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function(time) {
return time < 1000 ? `${time}ms` : `${(time / 1000).toFixed(2)}s`;
};

0 comments on commit 6deec80

Please sign in to comment.