UNPKG

4.51 kBJavaScriptView Raw
1const path = require('path');
2
3const camelcase = require('camelcase');
4const capitalize = require('titleize');
5const chalk = require('chalk');
6const merge = require('merge-options');
7const webpack = require('webpack');
8const weblog = require('webpack-log');
9
10const progress = require('./progress');
11const StylishReporter = require('./reporters/StylishReporter');
12
13function makeCallback(options) {
14 const { compiler, config, reporter, resolve, reject } = options;
15 const { watch } = config;
16
17 return (error, stats) => {
18 const log = weblog({ name: 'webpack', id: 'webpack-command' });
19
20 if (error || !watch) {
21 // for some reason webpack-cli invalidates the cache here. there's no
22 // documentation as to why, so we're going to assume there's a reason.
23 compiler.purgeInputFileSystem();
24 log.debug('Input File System Purged');
25 }
26
27 const result = reporter.render(error, stats);
28
29 if (error) {
30 reject(error);
31 return;
32 } else if (stats && stats.hasErrors() && !watch) {
33 process.exitCode = 1;
34 // a little extra sugar to make running in npm scripts a bit nicer
35 process.stdout.write('\n');
36 }
37
38 resolve(result);
39 };
40}
41
42/**
43 * Attempts to require a specified reporter. Tries the local built-ins first,
44 * and if that fails, attempts to load an npm module that exports a reporter
45 * handler function, and if that fails, attempts to load the reporter relative
46 * to the current working directory.
47 */
48function requireReporter(name) {
49 const prefix = capitalize(camelcase(name));
50 const locations = [`./reporters/${prefix}Reporter`, name, path.resolve(name)];
51 let result = null;
52
53 for (const location of locations) {
54 try {
55 // eslint-disable-next-line import/no-dynamic-require, global-require
56 result = require(location);
57 } catch (e) {
58 // noop
59 }
60
61 if (result) {
62 break;
63 }
64 }
65
66 return result;
67}
68
69/**
70 * Removes invalid webpack config properties leftover from applying flags
71 */
72function sanitize(config) {
73 const configs = [].concat(config).map((conf) => {
74 const result = merge({}, conf);
75 delete result.progress;
76 delete result.reporter;
77 delete result.watchStdin;
78
79 // TODO: remove the need for this
80 for (const property of ['entry', 'output']) {
81 const target = result[property];
82 if (target && Object.keys(target).length === 0) {
83 delete result[property];
84 }
85 }
86
87 return result;
88 });
89
90 // if we always return an array, every compilation will be a MultiCompiler
91 return configs.length > 1 ? configs : configs[0];
92}
93
94module.exports = (config) => {
95 const log = weblog({ name: 'webpack', id: 'webpack-command' });
96 const target = sanitize(config);
97 const compiler = webpack(target);
98 const { done, run } = compiler.hooks;
99 const configs = [].concat(config);
100 const [first] = configs;
101 const { reporter: reporterName, watchOptions } = first;
102 let ReporterClass = requireReporter(reporterName || 'stylish');
103
104 if (!ReporterClass) {
105 log.error(`The reporter specified (${reporterName}) could not be located`);
106 ReporterClass = StylishReporter;
107 }
108
109 const reporter = new ReporterClass({ compiler, config });
110
111 if (first.progress) {
112 progress.apply(first, compiler, reporter);
113 }
114
115 run.tap('WebpackCommand', () => {
116 log.info('Starting Build');
117 });
118
119 done.tap('WebpackCommand', () => {
120 log.info('Build Finished');
121 });
122
123 return {
124 run() {
125 return new Promise((resolve, reject) => {
126 const { stdin } = watchOptions || {};
127 const callback = makeCallback({
128 compiler,
129 config: first,
130 reporter,
131 resolve,
132 reject,
133 });
134
135 if (first.watch) {
136 log.info('Watching enabled');
137 for (const conf of configs) {
138 if (conf.bail) {
139 const configName =
140 configs.length > 1
141 ? `config[${conf.name || configs.indexOf(conf)}]`
142 : 'the config';
143 log.warn(
144 chalk`The {bold \`bail\`} option in ${configName} will force webpack to exit on the first error`
145 );
146 break;
147 }
148 }
149 compiler.watch(watchOptions, callback);
150 } else {
151 compiler.run(callback);
152 }
153
154 /* istanbul ignore if */
155 if (stdin) {
156 process.stdin.on('end', () => {
157 process.exit();
158 });
159
160 process.stdin.resume();
161 }
162 });
163 },
164 };
165};