Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log bundle metrics #733

Merged
merged 17 commits into from
Feb 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,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 @@ -48,6 +50,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 @@ -92,7 +94,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 @@ -199,11 +202,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 @@ -377,6 +380,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 @@ -385,6 +389,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 @@ -82,6 +82,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`;
};
Loading