cli.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. #!/usr/bin/env node
  2. /*
  3. MIT License http://www.opensource.org/licenses/mit-license.php
  4. Author Tobias Koppers @sokra
  5. */
  6. (function() {
  7. // wrap in IIFE to be able to use return
  8. const importLocal = require("import-local");
  9. // Prefer the local installation of webpack-cli
  10. if (importLocal(__filename)) {
  11. return;
  12. }
  13. require("v8-compile-cache");
  14. const ErrorHelpers = require("./errorHelpers");
  15. const NON_COMPILATION_ARGS = [
  16. "init",
  17. "migrate",
  18. "add",
  19. "remove",
  20. "serve",
  21. "generate-loader",
  22. "generate-plugin",
  23. "info"
  24. ];
  25. const NON_COMPILATION_CMD = process.argv.find(arg => {
  26. if (arg === "serve") {
  27. global.process.argv = global.process.argv.filter(a => a !== "serve");
  28. process.argv = global.process.argv;
  29. }
  30. return NON_COMPILATION_ARGS.find(a => a === arg);
  31. });
  32. if (NON_COMPILATION_CMD) {
  33. return require("./prompt-command")(NON_COMPILATION_CMD, ...process.argv);
  34. }
  35. const yargs = require("yargs").usage(`webpack-cli ${require("../package.json").version}
  36. Usage: webpack-cli [options]
  37. webpack-cli [options] --entry <entry> --output <output>
  38. webpack-cli [options] <entries...> --output <output>
  39. webpack-cli <command> [options]
  40. For more information, see https://webpack.js.org/api/cli/.`);
  41. require("./config-yargs")(yargs);
  42. const DISPLAY_GROUP = "Stats options:";
  43. const BASIC_GROUP = "Basic options:";
  44. yargs.options({
  45. silent: {
  46. type: "boolean",
  47. describe: "Prevent output from being displayed in stdout"
  48. },
  49. json: {
  50. type: "boolean",
  51. alias: "j",
  52. describe: "Prints the result as JSON."
  53. },
  54. progress: {
  55. type: "boolean",
  56. describe: "Print compilation progress in percentage",
  57. group: BASIC_GROUP
  58. },
  59. color: {
  60. type: "boolean",
  61. alias: "colors",
  62. default: function supportsColor() {
  63. return require("supports-color").stdout;
  64. },
  65. group: DISPLAY_GROUP,
  66. describe: "Force colors on the console"
  67. },
  68. "no-color": {
  69. type: "boolean",
  70. alias: "no-colors",
  71. group: DISPLAY_GROUP,
  72. describe: "Force no colors on the console"
  73. },
  74. "sort-modules-by": {
  75. type: "string",
  76. group: DISPLAY_GROUP,
  77. describe: "Sorts the modules list by property in module"
  78. },
  79. "sort-chunks-by": {
  80. type: "string",
  81. group: DISPLAY_GROUP,
  82. describe: "Sorts the chunks list by property in chunk"
  83. },
  84. "sort-assets-by": {
  85. type: "string",
  86. group: DISPLAY_GROUP,
  87. describe: "Sorts the assets list by property in asset"
  88. },
  89. "hide-modules": {
  90. type: "boolean",
  91. group: DISPLAY_GROUP,
  92. describe: "Hides info about modules"
  93. },
  94. "display-exclude": {
  95. type: "string",
  96. group: DISPLAY_GROUP,
  97. describe: "Exclude modules in the output"
  98. },
  99. "display-modules": {
  100. type: "boolean",
  101. group: DISPLAY_GROUP,
  102. describe: "Display even excluded modules in the output"
  103. },
  104. "display-max-modules": {
  105. type: "number",
  106. group: DISPLAY_GROUP,
  107. describe: "Sets the maximum number of visible modules in output"
  108. },
  109. "display-chunks": {
  110. type: "boolean",
  111. group: DISPLAY_GROUP,
  112. describe: "Display chunks in the output"
  113. },
  114. "display-entrypoints": {
  115. type: "boolean",
  116. group: DISPLAY_GROUP,
  117. describe: "Display entry points in the output"
  118. },
  119. "display-origins": {
  120. type: "boolean",
  121. group: DISPLAY_GROUP,
  122. describe: "Display origins of chunks in the output"
  123. },
  124. "display-cached": {
  125. type: "boolean",
  126. group: DISPLAY_GROUP,
  127. describe: "Display also cached modules in the output"
  128. },
  129. "display-cached-assets": {
  130. type: "boolean",
  131. group: DISPLAY_GROUP,
  132. describe: "Display also cached assets in the output"
  133. },
  134. "display-reasons": {
  135. type: "boolean",
  136. group: DISPLAY_GROUP,
  137. describe: "Display reasons about module inclusion in the output"
  138. },
  139. "display-depth": {
  140. type: "boolean",
  141. group: DISPLAY_GROUP,
  142. describe: "Display distance from entry point for each module"
  143. },
  144. "display-used-exports": {
  145. type: "boolean",
  146. group: DISPLAY_GROUP,
  147. describe: "Display information about used exports in modules (Tree Shaking)"
  148. },
  149. "display-provided-exports": {
  150. type: "boolean",
  151. group: DISPLAY_GROUP,
  152. describe: "Display information about exports provided from modules"
  153. },
  154. "display-optimization-bailout": {
  155. type: "boolean",
  156. group: DISPLAY_GROUP,
  157. describe: "Display information about why optimization bailed out for modules"
  158. },
  159. "display-error-details": {
  160. type: "boolean",
  161. group: DISPLAY_GROUP,
  162. describe: "Display details about errors"
  163. },
  164. display: {
  165. type: "string",
  166. choices: ["", "verbose", "detailed", "normal", "minimal", "errors-only", "none"],
  167. group: DISPLAY_GROUP,
  168. describe: "Select display preset"
  169. },
  170. verbose: {
  171. type: "boolean",
  172. group: DISPLAY_GROUP,
  173. describe: "Show more details"
  174. },
  175. "info-verbosity": {
  176. type: "string",
  177. default: "info",
  178. choices: ["none", "info", "verbose"],
  179. group: DISPLAY_GROUP,
  180. describe: "Controls the output of lifecycle messaging e.g. Started watching files..."
  181. },
  182. "build-delimiter": {
  183. type: "string",
  184. group: DISPLAY_GROUP,
  185. describe: "Display custom text after build output"
  186. }
  187. });
  188. // yargs will terminate the process early when the user uses help or version.
  189. // This causes large help outputs to be cut short (https://github.com/nodejs/node/wiki/API-changes-between-v0.10-and-v4#process).
  190. // To prevent this we use the yargs.parse API and exit the process normally
  191. yargs.parse(process.argv.slice(2), (err, argv, output) => {
  192. Error.stackTraceLimit = 30;
  193. // arguments validation failed
  194. if (err && output) {
  195. console.error(output);
  196. process.exitCode = 1;
  197. return;
  198. }
  199. // help or version info
  200. if (output) {
  201. console.log(output);
  202. return;
  203. }
  204. if (argv.verbose) {
  205. argv["display"] = "verbose";
  206. }
  207. let options;
  208. try {
  209. options = require("./convert-argv")(argv);
  210. } catch (err) {
  211. if (err.name !== "ValidationError") {
  212. throw err;
  213. }
  214. const stack = ErrorHelpers.cleanUpWebpackOptions(err.stack, err.message);
  215. const message = err.message + "\n" + stack;
  216. if (argv.color) {
  217. console.error(`\u001b[1m\u001b[31m${message}\u001b[39m\u001b[22m`);
  218. } else {
  219. console.error(message);
  220. }
  221. process.exitCode = 1;
  222. return;
  223. }
  224. /**
  225. * When --silent flag is present, an object with a no-op write method is
  226. * used in place of process.stout
  227. */
  228. const stdout = argv.silent
  229. ? {
  230. write: () => {}
  231. } // eslint-disable-line
  232. : process.stdout;
  233. function ifArg(name, fn, init) {
  234. if (Array.isArray(argv[name])) {
  235. if (init) init();
  236. argv[name].forEach(fn);
  237. } else if (typeof argv[name] !== "undefined") {
  238. if (init) init();
  239. fn(argv[name], -1);
  240. }
  241. }
  242. function processOptions(options) {
  243. // process Promise
  244. if (typeof options.then === "function") {
  245. options.then(processOptions).catch(function(err) {
  246. console.error(err.stack || err);
  247. process.exit(1); // eslint-disable-line
  248. });
  249. return;
  250. }
  251. const firstOptions = [].concat(options)[0];
  252. const statsPresetToOptions = require("webpack").Stats.presetToOptions;
  253. let outputOptions = options.stats;
  254. if (typeof outputOptions === "boolean" || typeof outputOptions === "string") {
  255. outputOptions = statsPresetToOptions(outputOptions);
  256. } else if (!outputOptions) {
  257. outputOptions = {};
  258. }
  259. ifArg("display", function(preset) {
  260. outputOptions = statsPresetToOptions(preset);
  261. });
  262. outputOptions = Object.create(outputOptions);
  263. if (Array.isArray(options) && !outputOptions.children) {
  264. outputOptions.children = options.map(o => o.stats);
  265. }
  266. if (typeof outputOptions.context === "undefined") outputOptions.context = firstOptions.context;
  267. ifArg("env", function(value) {
  268. if (outputOptions.env) {
  269. outputOptions._env = value;
  270. }
  271. });
  272. ifArg("json", function(bool) {
  273. if (bool) {
  274. outputOptions.json = bool;
  275. outputOptions.modules = bool;
  276. }
  277. });
  278. if (typeof outputOptions.colors === "undefined") outputOptions.colors = require("supports-color").stdout;
  279. ifArg("sort-modules-by", function(value) {
  280. outputOptions.modulesSort = value;
  281. });
  282. ifArg("sort-chunks-by", function(value) {
  283. outputOptions.chunksSort = value;
  284. });
  285. ifArg("sort-assets-by", function(value) {
  286. outputOptions.assetsSort = value;
  287. });
  288. ifArg("display-exclude", function(value) {
  289. outputOptions.exclude = value;
  290. });
  291. if (!outputOptions.json) {
  292. if (typeof outputOptions.cached === "undefined") outputOptions.cached = false;
  293. if (typeof outputOptions.cachedAssets === "undefined") outputOptions.cachedAssets = false;
  294. ifArg("display-chunks", function(bool) {
  295. if (bool) {
  296. outputOptions.modules = false;
  297. outputOptions.chunks = true;
  298. outputOptions.chunkModules = true;
  299. }
  300. });
  301. ifArg("display-entrypoints", function(bool) {
  302. outputOptions.entrypoints = bool;
  303. });
  304. ifArg("display-reasons", function(bool) {
  305. if (bool) outputOptions.reasons = true;
  306. });
  307. ifArg("display-depth", function(bool) {
  308. if (bool) outputOptions.depth = true;
  309. });
  310. ifArg("display-used-exports", function(bool) {
  311. if (bool) outputOptions.usedExports = true;
  312. });
  313. ifArg("display-provided-exports", function(bool) {
  314. if (bool) outputOptions.providedExports = true;
  315. });
  316. ifArg("display-optimization-bailout", function(bool) {
  317. if (bool) outputOptions.optimizationBailout = bool;
  318. });
  319. ifArg("display-error-details", function(bool) {
  320. if (bool) outputOptions.errorDetails = true;
  321. });
  322. ifArg("display-origins", function(bool) {
  323. if (bool) outputOptions.chunkOrigins = true;
  324. });
  325. ifArg("display-max-modules", function(value) {
  326. outputOptions.maxModules = +value;
  327. });
  328. ifArg("display-cached", function(bool) {
  329. if (bool) outputOptions.cached = true;
  330. });
  331. ifArg("display-cached-assets", function(bool) {
  332. if (bool) outputOptions.cachedAssets = true;
  333. });
  334. if (!outputOptions.exclude) outputOptions.exclude = ["node_modules", "bower_components", "components"];
  335. if (argv["display-modules"]) {
  336. outputOptions.maxModules = Infinity;
  337. outputOptions.exclude = undefined;
  338. outputOptions.modules = true;
  339. }
  340. }
  341. ifArg("hide-modules", function(bool) {
  342. if (bool) {
  343. outputOptions.modules = false;
  344. outputOptions.chunkModules = false;
  345. }
  346. });
  347. ifArg("info-verbosity", function(value) {
  348. outputOptions.infoVerbosity = value;
  349. });
  350. ifArg("build-delimiter", function(value) {
  351. outputOptions.buildDelimiter = value;
  352. });
  353. const webpack = require("webpack");
  354. let lastHash = null;
  355. let compiler;
  356. try {
  357. compiler = webpack(options);
  358. } catch (err) {
  359. if (err.name === "WebpackOptionsValidationError") {
  360. if (argv.color) console.error(`\u001b[1m\u001b[31m${err.message}\u001b[39m\u001b[22m`);
  361. else console.error(err.message);
  362. // eslint-disable-next-line no-process-exit
  363. process.exit(1);
  364. }
  365. throw err;
  366. }
  367. if (argv.progress) {
  368. const ProgressPlugin = require("webpack").ProgressPlugin;
  369. new ProgressPlugin({
  370. profile: argv.profile
  371. }).apply(compiler);
  372. }
  373. if (outputOptions.infoVerbosity === "verbose") {
  374. if (argv.w) {
  375. compiler.hooks.watchRun.tap("WebpackInfo", compilation => {
  376. const compilationName = compilation.name ? compilation.name : "";
  377. console.error("\nCompilation " + compilationName + " starting…\n");
  378. });
  379. } else {
  380. compiler.hooks.beforeRun.tap("WebpackInfo", compilation => {
  381. const compilationName = compilation.name ? compilation.name : "";
  382. console.error("\nCompilation " + compilationName + " starting…\n");
  383. });
  384. }
  385. compiler.hooks.done.tap("WebpackInfo", compilation => {
  386. const compilationName = compilation.name ? compilation.name : "";
  387. console.error("\nCompilation " + compilationName + " finished\n");
  388. });
  389. }
  390. function compilerCallback(err, stats) {
  391. if (!options.watch || err) {
  392. // Do not keep cache anymore
  393. compiler.purgeInputFileSystem();
  394. }
  395. if (err) {
  396. lastHash = null;
  397. console.error(err.stack || err);
  398. if (err.details) console.error(err.details);
  399. process.exit(1); // eslint-disable-line
  400. }
  401. if (outputOptions.json) {
  402. stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + "\n");
  403. } else if (stats.hash !== lastHash) {
  404. lastHash = stats.hash;
  405. if (stats.compilation && stats.compilation.errors.length !== 0) {
  406. const errors = stats.compilation.errors;
  407. if (errors[0].name === "EntryModuleNotFoundError") {
  408. console.error("\n\u001b[1m\u001b[31mInsufficient number of arguments or no entry found.");
  409. console.error(
  410. "\u001b[1m\u001b[31mAlternatively, run 'webpack(-cli) --help' for usage info.\u001b[39m\u001b[22m\n"
  411. );
  412. }
  413. }
  414. const statsString = stats.toString(outputOptions);
  415. const delimiter = outputOptions.buildDelimiter ? `${outputOptions.buildDelimiter}\n` : "";
  416. if (statsString) stdout.write(`${statsString}\n${delimiter}`);
  417. }
  418. if (!options.watch && stats.hasErrors()) {
  419. process.exitCode = 2;
  420. }
  421. }
  422. if (firstOptions.watch || options.watch) {
  423. const watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {};
  424. if (watchOptions.stdin) {
  425. process.stdin.on("end", function(_) {
  426. process.exit(); // eslint-disable-line
  427. });
  428. process.stdin.resume();
  429. }
  430. compiler.watch(watchOptions, compilerCallback);
  431. if (outputOptions.infoVerbosity !== "none") console.error("\nwebpack is watching the files…\n");
  432. } else {
  433. compiler.run((err, stats) => {
  434. if (compiler.close) {
  435. compiler.close(err2 => {
  436. compilerCallback(err || err2, stats);
  437. });
  438. } else {
  439. compilerCallback(err, stats);
  440. }
  441. });
  442. }
  443. }
  444. processOptions(options);
  445. });
  446. })();