diff --git a/src/cli.ts b/src/cli.ts index 302859a..0568206 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -48,7 +48,7 @@ prog.command('build [dest]') console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`node ${dest}`)} to run the app.`); } catch (err) { - console.error(err ? err.details || err.stack || err.message || err : 'Unknown error'); + console.log(`${colors.bold.red(`> ${err.message}`)}`); process.exit(1); } }); diff --git a/src/cli/build.ts b/src/cli/build.ts index f00dafb..6b291b6 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -2,6 +2,7 @@ import { build as _build } from '../api/build'; import colors from 'kleur'; import { locations } from '../config'; import validate_bundler from './utils/validate_bundler'; +import { repeat } from '../utils'; export function build(opts: { bundler?: string }) { const bundler = validate_bundler(opts.bundler); @@ -18,7 +19,20 @@ export function build(opts: { bundler?: string }) { }); emitter.on('build', event => { - console.log(colors.inverse(`\nbuilt ${event.type}`)); + let banner = `built ${event.type}`; + let c = colors.cyan; + + const { warnings } = event.result; + if (warnings.length > 0) { + banner += ` with ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`; + c = colors.yellow; + } + + console.log(); + console.log(c(`┌─${repeat('─', banner.length)}─┐`)); + console.log(c(`│ ${colors.bold(banner) } │`)); + console.log(c(`└─${repeat('─', banner.length)}─┘`)); + console.log(event.result.print()); }); @@ -30,8 +44,7 @@ export function build(opts: { bundler?: string }) { fulfil(); }); } catch (err) { - console.log(`${colors.bold.red(`> ${err.message}`)}`); - process.exit(1); + reject(err); } }); } \ No newline at end of file diff --git a/src/cli/export.ts b/src/cli/export.ts index 04aa0f9..00dd1d5 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -1,12 +1,8 @@ import { exporter as _exporter } from '../api/export'; import colors from 'kleur'; -import prettyBytes from 'pretty-bytes'; +import pb from 'pretty-bytes'; import { locations } from '../config'; - -function left_pad(str: string, len: number) { - while (str.length < len) str = ` ${str}`; - return str; -} +import { right_pad } from '../utils'; export function exporter(export_dir: string, { basepath = '', @@ -25,9 +21,8 @@ export function exporter(export_dir: string, { }); emitter.on('file', event => { - const pb = prettyBytes(event.size); const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray; - const size_label = size_color(left_pad(prettyBytes(event.size), 10)); + const size_label = size_color(right_pad(pb(event.size), 10)); const file_label = event.status === 200 ? event.file diff --git a/src/core/create_compilers.ts b/src/core/create_compilers.ts index 7909944..7d97b10 100644 --- a/src/core/create_compilers.ts +++ b/src/core/create_compilers.ts @@ -1,7 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; +import colors from 'kleur'; +import pb from 'pretty-bytes'; import { locations } from '../config'; import relative from 'require-relative'; +import { left_pad, right_pad } from '../utils'; let r: any; let wp: any; @@ -20,6 +23,8 @@ export class CompileResult { } class RollupResult extends CompileResult { + summary: string; + constructor(duration: number, compiler: RollupCompiler) { super(); @@ -39,10 +44,51 @@ class RollupResult extends CompileResult { this.assetsByChunkName.main = chunk.fileName; } }); + + this.summary = compiler.chunks.map(chunk => { + const size_color = chunk.code.length > 150000 ? colors.bold.red : chunk.code.length > 50000 ? colors.bold.yellow : colors.bold.white; + const size_label = left_pad(pb(chunk.code.length), 10); + + const lines = [size_color(`${size_label} ${chunk.fileName}`)]; + + const deps = Object.keys(chunk.modules) + .map(file => { + return { + file: path.relative(process.cwd(), file), + size: chunk.modules[file].renderedLength + }; + }) + .filter(dep => dep.size > 0) + .sort((a, b) => b.size - a.size); + + const total_unminified = deps.reduce((t, d) => t + d.size, 0); + + deps.forEach((dep, i) => { + const c = i === deps.length - 1 ? '└' : '│'; + let line = ` ${c} ${dep.file}`; + + if (deps.length > 1) { + const p = (100 * dep.size / total_unminified).toFixed(1); + line += ` (${p}%)`; + } + + lines.push(colors.gray(line)); + }); + + return lines.join('\n'); + }).join('\n'); } print() { - return 'TODO summarise build'; + const blocks: string[] = this.warnings.map(warning => { + return warning.file + ? `> ${colors.bold(warning.file)}\n${warning.message}` + : `> ${warning.message}`; + }); + + blocks.push(this.summary); + + return blocks.join('\n\n'); } } @@ -148,10 +194,23 @@ export class RollupCompiler { const start = Date.now(); - const bundle = await r.rollup(config); - await bundle.write(config.output); + try { + const bundle = await r.rollup(config); + await bundle.write(config.output); - return new RollupResult(Date.now() - start, bundle); + return new RollupResult(Date.now() - start, this); + } catch (err) { + if (err.filename) { + // TODO this is a bit messy. Also, can + // Rollup emit other kinds of error? + err.message = [ + `Failed to build — error in ${err.filename}: ${err.message}`, + err.frame + ].filter(Boolean).join('\n'); + } + + throw err; + } } async watch(cb: (err?: Error, stats?: any) => void) { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..ea70779 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,15 @@ +export function left_pad(str: string, len: number) { + while (str.length < len) str = ` ${str}`; + return str; +} + +export function right_pad(str: string, len: number) { + while (str.length < len) str += ' '; + return str; +} + +export function repeat(str: string, i: number) { + let result = ''; + while (i--) result += str; + return result; +} \ No newline at end of file