UNPKG

4.33 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 }
33
34 resolve(result);
35 };
36}
37
38/**
39 * Attempts to require a specified reporter. Tries the local built-ins first,
40 * and if that fails, attempts to load an npm module that exports a reporter
41 * handler function, and if that fails, attempts to load the reporter relative
42 * to the current working directory.
43 */
44function requireReporter(name) {
45 const prefix = capitalize(camelcase(name));
46 const locations = [`./reporters/${prefix}Reporter`, name, path.resolve(name)];
47 let result = null;
48
49 for (const location of locations) {
50 try {
51 // eslint-disable-next-line import/no-dynamic-require, global-require
52 result = require(location);
53 } catch (e) {
54 // noop
55 }
56
57 if (result) {
58 break;
59 }
60 }
61
62 return result;
63}
64
65/**
66 * Removes invalid webpack config properties leftover from applying flags
67 */
68function sanitize(config) {
69 const configs = [].concat(config).map((conf) => {
70 const result = merge({}, conf);
71 delete result.progress;
72 delete result.reporter;
73 delete result.watchStdin;
74
75 // TODO: remove the need for this
76 for (const property of ['entry', 'output']) {
77 const target = result[property];
78 if (target && Object.keys(target).length === 0) {
79 delete result[property];
80 }
81 }
82
83 return result;
84 });
85
86 // if we always return an array, every compilation will be a MultiCompiler
87 return configs.length > 1 ? configs : configs[0];
88}
89
90module.exports = (config) => {
91 const log = weblog({ name: 'webpack', id: 'webpack-command' });
92 const target = sanitize(config);
93 const compiler = webpack(target);
94 const { done, run } = compiler.hooks;
95 const configs = [].concat(config);
96 const [first] = configs;
97 const { reporter: reporterName, watchOptions } = first;
98 let ReporterClass = requireReporter(reporterName || 'stylish');
99
100 if (!ReporterClass) {
101 log.error(`The reporter specified (${reporterName}) could not be located`);
102 ReporterClass = StylishReporter;
103 }
104
105 const reporter = new ReporterClass({ compiler, config });
106
107 if (first.progress) {
108 progress.apply(first, compiler, reporter);
109 }
110
111 run.tap('WebpackCommand', () => {
112 log.info('Starting Build');
113 });
114
115 done.tap('WebpackCommand', () => {
116 log.info('Build Finished');
117 });
118
119 return {
120 run() {
121 return new Promise((resolve, reject) => {
122 const { stdin } = watchOptions || {};
123 const callback = makeCallback({
124 compiler,
125 config: first,
126 reporter,
127 resolve,
128 reject,
129 });
130
131 if (first.watch) {
132 log.info('Watching enabled');
133 for (const conf of configs) {
134 if (conf.bail) {
135 const configName =
136 configs.length > 1
137 ? `config[${conf.name || configs.indexOf(conf)}]`
138 : 'the config';
139 log.warn(
140 chalk`The {bold \`bail\`} option in ${configName} will force webpack to exit on the first error`
141 );
142 break;
143 }
144 }
145 compiler.watch(watchOptions, callback);
146 } else {
147 compiler.run(callback);
148 }
149
150 /* istanbul ignore if */
151 if (stdin) {
152 process.stdin.on('end', () => {
153 process.exit();
154 });
155
156 process.stdin.resume();
157 }
158 });
159 },
160 };
161};