Skip to content
This repository has been archived by the owner on Sep 11, 2018. It is now read-only.

Commit

Permalink
feat: add support for --progress. fixes #7 (#15)
Browse files Browse the repository at this point in the history
* refactor: moving around reporter

* feat: add support for --progress

* chore: update dependencies

* test: update test snapshots

* fix: fix typo in progress flag

* docs: add documentation for reporter progress

* feat: add progress to BasicReporter

* test: update test snapshot

* chore: add nsprc to bypass https://nodesecurity.io/advisories/612

* test: add Reporter class tests

* test: add distill plugin tests

* test: add more reporter tests

* test: add progress util tests

* test: temporarily ignore basic, stylish progress methods

* test: update progress and reporter tests

* test: improve command tests

* fix: change grey to dim for chalk
  • Loading branch information
shellscape committed May 9, 2018
1 parent 5c579ec commit ca36c75
Show file tree
Hide file tree
Showing 27 changed files with 940 additions and 126 deletions.
3 changes: 3 additions & 0 deletions .nsprc
@@ -0,0 +1,3 @@
{
"exceptions": ["https://nodesecurity.io/advisories/612"]
}
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -78,6 +78,7 @@ $ webpack --help
--log-level Limit all process console messages to a specific level and above
Levels: trace, debug, info, warn, error, silent
--log-time Instruct the logger for webpack-serve and dependencies to display a timestamp
--progress Instructs webpack to track and display build progress
--reporter Specifies the reporter to use for generating console output for a build
--require Preload one or more modules before loading the webpack configuration
Typically used for language-specific require hooks
Expand Down Expand Up @@ -223,7 +224,9 @@ Resolve aliases passed by flag in `webpack-cli` using

## Reporters

`webpack-command` ships with two available reporters:
`webpack-command` supports custom, user-defined reporters which allow users
full control over how build data is presented. By default, it ships with two
available reporters:

#### `basic`

Expand All @@ -235,6 +238,9 @@ using `webpack-cli`.
The **default** reporter and displays beautiful output using the same code
that drives [`webpack-stylish`](https://www.npmjs.com/package/webpack-stylish).

Building your own reporter is as easy as inheriting from the `Reporter` class
located at `lib/reporters/Reporter.js`.

## Configuration Languages and Compilers

`webpack-command` allows users to leverage any language that provides a require
Expand Down
24 changes: 16 additions & 8 deletions lib/compiler.js
Expand Up @@ -4,14 +4,14 @@ const merge = require('merge-options');
const webpack = require('webpack');
const weblog = require('webpack-log');

const progress = require('./progress');

function makeCallback(options) {
const { compiler, config, resolve, reject } = options;
const { reporter, watch } = config;
const { compiler, config, reporter, resolve, reject } = options;
const { watch } = config;

return (error, stats) => {
const log = weblog({ name: 'webpack', id: 'webpack-command' });
const Reporter = requireReporter(reporter || 'stylish');
const actualReporter = new Reporter({ compiler, config });

if (error || !watch) {
// for some reason webpack-cli invalidates the cache here. there's no
Expand All @@ -25,7 +25,7 @@ function makeCallback(options) {
return;
}

const result = actualReporter.render(error, stats);
const result = reporter.render(error, stats);

resolve(result);
};
Expand Down Expand Up @@ -59,6 +59,7 @@ function requireReporter(name, local = true) {
function sanitize(config) {
const configs = [].concat(config).map((conf) => {
const result = merge({}, conf);
delete result.progress;
delete result.reporter;
delete result.watchStdin;

Expand All @@ -82,6 +83,15 @@ module.exports = (config) => {
const target = sanitize(config);
const compiler = webpack(target);
const { done, run } = compiler.hooks;
const configs = [].concat(config);
const [first] = configs;
const { reporter: reporterName, watchOptions } = first;
const ReporterClass = requireReporter(reporterName || 'stylish');
const reporter = new ReporterClass({ compiler, config });

if (first.progress) {
progress.apply(first, compiler, reporter);
}

run.tap('WebpackCommand', () => {
log.info('Starting Build');
Expand All @@ -94,13 +104,11 @@ module.exports = (config) => {
return {
run() {
return new Promise((resolve, reject) => {
const configs = [].concat(config);
const [first] = configs;
const { watchOptions } = first;
const { stdin } = watchOptions || {};
const callback = makeCallback({
compiler,
config: first,
reporter,
resolve,
reject,
});
Expand Down
26 changes: 25 additions & 1 deletion lib/config.js
@@ -1,3 +1,5 @@
const { isAbsolute, resolve } = require('path');

const loader = require('@webpack-contrib/config-loader');
const merge = require('merge-options');

Expand All @@ -8,9 +10,23 @@ module.exports = {
let result;

if (Array.isArray(config)) {
result = config.map((conf) => merge(conf, options));
result = config.map((conf) => {
const res = merge(conf, options);
const { plugins } = conf;

if (plugins && Array.isArray(plugins) && options.plugins) {
res.plugins = plugins.concat(options.plugins);
}

return res;
});
} else {
result = merge(options, config);
const { plugins } = result;

if (plugins && Array.isArray(plugins) && options.plugins) {
result.plugins = plugins.concat(options.plugins);
}
}

if (argv.configName) {
Expand Down Expand Up @@ -41,6 +57,14 @@ module.exports = {
require: argv.require,
};

if (argv.config) {
if (isAbsolute(argv.config)) {
loaderOptions.configPath = argv.config;
} else {
loaderOptions.configPath = resolve(process.cwd(), argv.config);
}
}

return loader(loaderOptions).then((result) => {
const { config } = result;
const { distill } = module.exports;
Expand Down
17 changes: 14 additions & 3 deletions lib/flags/general.js
Expand Up @@ -6,7 +6,6 @@ const webpack = require('webpack');

module.exports = {
apply(argv, options) {
// let entry = {};
let plugins = [];
const result = {};

Expand All @@ -30,6 +29,14 @@ module.exports = {
result.devtool = argv.devtool;
}

if (argv.profile) {
result.profile = argv.profile;
}

if (argv.progress) {
result.progress = argv.progress;
}

if (argv.reporter) {
result.reporter = argv.reporter;
}
Expand Down Expand Up @@ -93,8 +100,12 @@ module.exports = {
type: 'string',
},
'log-time': {
desc:
'Instruct the logger for webpack-serve and dependencies to display a timestamp',
desc: 'Instruct the logger and dependencies to display a timestamp',
},
progress: {
desc: chalk`Instructs webpack to track and display build progress
{dim This is often used with --profile}`,
type: 'boolean',
},
reporter: {
desc:
Expand Down
2 changes: 1 addition & 1 deletion lib/flags/util.js
Expand Up @@ -62,7 +62,7 @@ module.exports = {
const PluginClass = require(pluginPath);
return new PluginClass(args);
} catch (e) {
log.error(chalk`Cannot load plugin ${name} {grey from ${pluginPath}}`);
log.error(chalk`Cannot load plugin ${name} {dim from ${pluginPath}}`);
throw e;
}
},
Expand Down
61 changes: 61 additions & 0 deletions lib/progress.js
@@ -0,0 +1,61 @@
const webpack = require('webpack');

const { ProgressPlugin } = webpack;

function parseArgs(profile, ...args) {
// args is an unorganized set of parameters.
// e.g. building modules|5/6 modules|1 active|/node_modules/lodash/lodash.js
// building modules|14/14 modules|0 active|
// finish module graph
// finish module graph|FlagDependencyExportsPlugin
const [value, , counts, index, fileName] = args;
const [, modulePos, totalModules] = (
(counts || '').match(/(\d+)\/(\d+)/) || []
).map((match) => parseInt(match, 10));
const [, indexNumber, indexState] = (index || '').match(/(\d+)\s(.+)/) || [];
const percentage = parseFloat(value);
const [, stepName] = args;
let scope;
let empty;

// we've got a step with a scope on our hands
// e.g. finish module graph|FlagDependencyExportsPlugin
if (args.length === 3) {
scope = counts;
}

const result = {
profile,
fileName,
scope,
step: {
index: parseInt(indexNumber, 10) || empty,
modulePosition: modulePos || empty,
name: stepName,
percentage: percentage || empty,
state: indexState,
totalModules: totalModules || empty,
},
};

return result;
}

module.exports = {
apply(config, compiler, reporter) {
const { profile } = config;
const opts = { profile };

if (reporter.progress) {
opts.handler = (...args) => {
const data = parseArgs(profile, ...args);
reporter.progress(data);
};
}

const plugin = new ProgressPlugin(opts);
plugin.apply(compiler);
},

parseArgs,
};
31 changes: 31 additions & 0 deletions lib/reporters/BasicReporter.js
@@ -1,6 +1,37 @@
const ora = require('ora');

const Reporter = require('./Reporter');

module.exports = class BasicReporter extends Reporter {
constructor(...args) {
super(...args);

this.spinner = ora({ spinner: 'toggle6' });
}

// TODO: create proper testing for this with a large build an stdout hooks.
/* istanbul ignore next */
progress(data) {
if (!this.spinner.isSpinning && data.step.percentage < 1) {
this.spinner.start(data.step.name);
}

const { name, modulePosition, totalModules } = data.step;
const percent = Math.floor(data.step.percentage * 100);

if (modulePosition) {
this.spinner.text = `${percent}% ${modulePosition}/${totalModules} ${name} ${
data.fileName
}`;
} else {
this.spinner.text = `${percent} ${name} ${data.scope}`;
}

if (data.step.percentage >= 1) {
this.spinner.stop();
}
}

render(error, stats) {
const { log } = console;
const compilers = this.compiler.compilers || [this.compiler];
Expand Down
34 changes: 29 additions & 5 deletions lib/reporters/Reporter.js
@@ -1,7 +1,7 @@
module.exports = class Reporter {
/**
* @constructor
* @param compiler {Object} A webpack Compiler instance https://webpack.js.org/api/node/#compiler-instance
@constructor
@param compiler {Object} A webpack Compiler instance https://webpack.js.org/api/node/#compiler-instance
*/
// eslint-disable-next-line no-unused-vars
constructor(options) {
Expand All @@ -10,9 +10,33 @@ module.exports = class Reporter {
}

/**
* @method render
* @param error {Error} An Error object
* @param stats {Object} A webpack stats object https://webpack.js.org/api/node/#stats-object
NOTE: Reporters also support a progress handler, which is called when webpack
reports progress on a build, typically in tandem with the --progress CLI flag.
@method progress
@param data {Object} An object containing data on the current progress state of a build.
const data = {
profile: Boolean,
fileName: String,
scope: String,
step: {
index: Number,
modulePosition: Number,
name: String,
percentage: Number,
state: String,
totalModules: Number,
},
};
*/
// eslint-disable-next-line no-unused-vars
progress(data) {}

/**
@method render
@param error {Error} An Error object
@param stats {Object} A webpack stats object https://webpack.js.org/api/node/#stats-object
*/
// eslint-disable-next-line no-unused-vars
render(error, stats) {}
Expand Down
21 changes: 21 additions & 0 deletions lib/reporters/StylishReporter.js
@@ -1,4 +1,6 @@
const capitalize = require('titleize');
const chalk = require('chalk');
const ora = require('ora');

const parse = require('./parse');
const Reporter = require('./Reporter');
Expand All @@ -14,6 +16,8 @@ module.exports = class StylishReporter extends Reporter {
header: false,
};

this.spinner = ora();

this.state = {
active: 0,
hashes: [],
Expand All @@ -27,6 +31,23 @@ module.exports = class StylishReporter extends Reporter {
};
}

// TODO: create proper testing for this with a large build an stdout hooks.
/* istanbul ignore next */
progress(stage) {
if (!this.spinner.isSpinning && stage.step.percentage < 1) {
this.spinner.start(stage.step.name);
}

const name = capitalize(stage.step.name);
const percent = Math.floor(stage.step.percentage * 100);

this.spinner.text = chalk`${name} {dim (${percent}%)}`;

if (stage.step.percentage >= 1) {
this.spinner.stop();
}
}

render(error, resultStats) {
// handles both Stats and MultiStats
const allStats = resultStats.stats || [resultStats];
Expand Down

0 comments on commit ca36c75

Please sign in to comment.