convert-argv.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. const path = require("path");
  2. const fs = require("fs");
  3. fs.existsSync = fs.existsSync || path.existsSync;
  4. const interpret = require("interpret");
  5. const prepareOptions = require("./prepareOptions");
  6. const webpackConfigurationSchema = require("../schemas/webpackConfigurationSchema.json");
  7. const validateSchema = require("webpack").validateSchema;
  8. const WebpackOptionsValidationError = require("webpack")
  9. .WebpackOptionsValidationError;
  10. module.exports = function(...args) {
  11. const argv = args[1] || args[0];
  12. const options = [];
  13. // Shortcuts
  14. if (argv.d) {
  15. argv.debug = true;
  16. argv["output-pathinfo"] = true;
  17. if (!argv.devtool) {
  18. argv.devtool = "eval-cheap-module-source-map";
  19. }
  20. if (!argv.mode) {
  21. argv.mode = "development";
  22. }
  23. }
  24. if (argv.p) {
  25. argv["optimize-minimize"] = true;
  26. argv["define"] = []
  27. .concat(argv["define"] || [])
  28. .concat("process.env.NODE_ENV=\"production\"");
  29. if (!argv.mode) {
  30. argv.mode = "production";
  31. }
  32. }
  33. if (argv.output) {
  34. let output = argv.output;
  35. if (!path.isAbsolute(argv.o)) {
  36. output = path.resolve(process.cwd(), output);
  37. }
  38. argv["output-filename"] = path.basename(output);
  39. argv["output-path"] = path.dirname(output);
  40. }
  41. let configFileLoaded = false;
  42. let configFiles = [];
  43. const extensions = Object.keys(interpret.extensions).sort(function(a, b) {
  44. return a === ".js" ? -1 : b === ".js" ? 1 : a.length - b.length;
  45. });
  46. const defaultConfigFiles = ["webpack.config", "webpackfile"]
  47. .map(function(filename) {
  48. return extensions.map(function(ext) {
  49. return {
  50. path: path.resolve(filename + ext),
  51. ext: ext
  52. };
  53. });
  54. })
  55. .reduce(function(a, i) {
  56. return a.concat(i);
  57. }, []);
  58. let i;
  59. if (argv.config) {
  60. const getConfigExtension = function getConfigExtension(configPath) {
  61. for (i = extensions.length - 1; i >= 0; i--) {
  62. const tmpExt = extensions[i];
  63. if (
  64. configPath.indexOf(tmpExt, configPath.length - tmpExt.length) > -1
  65. ) {
  66. return tmpExt;
  67. }
  68. }
  69. return path.extname(configPath);
  70. };
  71. const mapConfigArg = function mapConfigArg(configArg) {
  72. const resolvedPath = path.resolve(configArg);
  73. const extension = getConfigExtension(resolvedPath);
  74. return {
  75. path: resolvedPath,
  76. ext: extension
  77. };
  78. };
  79. const configArgList = Array.isArray(argv.config)
  80. ? argv.config
  81. : [argv.config];
  82. configFiles = configArgList.map(mapConfigArg);
  83. } else {
  84. for (i = 0; i < defaultConfigFiles.length; i++) {
  85. const webpackConfig = defaultConfigFiles[i].path;
  86. if (fs.existsSync(webpackConfig)) {
  87. configFiles.push({
  88. path: webpackConfig,
  89. ext: defaultConfigFiles[i].ext
  90. });
  91. break;
  92. }
  93. }
  94. }
  95. if (configFiles.length > 0) {
  96. const registerCompiler = function registerCompiler(moduleDescriptor) {
  97. if (moduleDescriptor) {
  98. if (typeof moduleDescriptor === "string") {
  99. require(moduleDescriptor);
  100. } else if (!Array.isArray(moduleDescriptor)) {
  101. moduleDescriptor.register(require(moduleDescriptor.module));
  102. } else {
  103. for (let i = 0; i < moduleDescriptor.length; i++) {
  104. try {
  105. registerCompiler(moduleDescriptor[i]);
  106. break;
  107. } catch (e) {
  108. // do nothing
  109. }
  110. }
  111. }
  112. }
  113. };
  114. const requireConfig = function requireConfig(configPath) {
  115. let options = (function WEBPACK_OPTIONS() {
  116. if (argv.configRegister && argv.configRegister.length) {
  117. module.paths.unshift(
  118. path.resolve(process.cwd(), "node_modules"),
  119. process.cwd()
  120. );
  121. argv.configRegister.forEach(dep => {
  122. require(dep);
  123. });
  124. return require(configPath);
  125. } else {
  126. return require(configPath);
  127. }
  128. })();
  129. options = prepareOptions(options, argv);
  130. return options;
  131. };
  132. configFiles.forEach(function(file) {
  133. registerCompiler(interpret.extensions[file.ext]);
  134. options.push(requireConfig(file.path));
  135. });
  136. configFileLoaded = true;
  137. }
  138. if (!configFileLoaded) {
  139. return processConfiguredOptions({});
  140. } else if (options.length === 1) {
  141. return processConfiguredOptions(options[0]);
  142. } else {
  143. return processConfiguredOptions(options);
  144. }
  145. function processConfiguredOptions(options) {
  146. const webpackConfigurationValidationErrors = validateSchema(
  147. webpackConfigurationSchema,
  148. options
  149. );
  150. if (webpackConfigurationValidationErrors.length) {
  151. const error = new WebpackOptionsValidationError(
  152. webpackConfigurationValidationErrors
  153. );
  154. console.error(
  155. error.message,
  156. `\nReceived: ${typeof options} : ${JSON.stringify(options, null, 2)}`
  157. );
  158. process.exit(-1); // eslint-disable-line
  159. }
  160. // process Promise
  161. if (typeof options.then === "function") {
  162. return options.then(processConfiguredOptions);
  163. }
  164. // process ES6 default
  165. if (typeof options === "object" && typeof options.default === "object") {
  166. return processConfiguredOptions(options.default);
  167. }
  168. // filter multi-config by name
  169. if (Array.isArray(options) && argv["config-name"]) {
  170. const namedOptions = options.filter(function(opt) {
  171. return opt.name === argv["config-name"];
  172. });
  173. if (namedOptions.length === 0) {
  174. console.error(
  175. "Configuration with name '" + argv["config-name"] + "' was not found."
  176. );
  177. process.exit(-1); // eslint-disable-line
  178. } else if (namedOptions.length === 1) {
  179. return processConfiguredOptions(namedOptions[0]);
  180. }
  181. options = namedOptions;
  182. }
  183. if (Array.isArray(options)) {
  184. options.forEach(processOptions);
  185. } else {
  186. processOptions(options);
  187. }
  188. if (argv.context) {
  189. options.context = path.resolve(argv.context);
  190. }
  191. if (!options.context) {
  192. options.context = process.cwd();
  193. }
  194. if (argv.watch) {
  195. options.watch = true;
  196. }
  197. if (argv["watch-aggregate-timeout"]) {
  198. options.watchOptions = options.watchOptions || {};
  199. options.watchOptions.aggregateTimeout = +argv["watch-aggregate-timeout"];
  200. }
  201. if (typeof argv["watch-poll"] !== "undefined") {
  202. options.watchOptions = options.watchOptions || {};
  203. if (argv["watch-poll"] === "true" || argv["watch-poll"] === "")
  204. options.watchOptions.poll = true;
  205. else if (!isNaN(argv["watch-poll"]))
  206. options.watchOptions.poll = +argv["watch-poll"];
  207. }
  208. if (argv["watch-stdin"]) {
  209. options.watchOptions = options.watchOptions || {};
  210. options.watchOptions.stdin = true;
  211. options.watch = true;
  212. }
  213. return options;
  214. }
  215. function processOptions(options) {
  216. function ifArg(name, fn, init, finalize) {
  217. const isArray = Array.isArray(argv[name]);
  218. const isSet = typeof argv[name] !== "undefined" && argv[name] !== null;
  219. if (!isArray && !isSet) return;
  220. init && init();
  221. if (isArray) argv[name].forEach(fn);
  222. else if (isSet) fn(argv[name], -1);
  223. finalize && finalize();
  224. }
  225. function ifArgPair(name, fn, init, finalize) {
  226. ifArg(
  227. name,
  228. function(content, idx) {
  229. const i = content.indexOf("=");
  230. if (i < 0) {
  231. return fn(null, content, idx);
  232. } else {
  233. return fn(content.substr(0, i), content.substr(i + 1), idx);
  234. }
  235. },
  236. init,
  237. finalize
  238. );
  239. }
  240. function ifBooleanArg(name, fn) {
  241. ifArg(name, function(bool) {
  242. if (bool) {
  243. fn();
  244. }
  245. });
  246. }
  247. function mapArgToBoolean(name, optionName) {
  248. ifArg(name, function(bool) {
  249. if (bool === true) options[optionName || name] = true;
  250. else if (bool === false) options[optionName || name] = false;
  251. });
  252. }
  253. function loadPlugin(name) {
  254. const loadUtils = require("loader-utils");
  255. let args;
  256. try {
  257. const p = name && name.indexOf("?");
  258. if (p > -1) {
  259. args = loadUtils.parseQuery(name.substring(p));
  260. name = name.substring(0, p);
  261. }
  262. } catch (e) {
  263. console.log("Invalid plugin arguments " + name + " (" + e + ").");
  264. process.exit(-1); // eslint-disable-line
  265. }
  266. let path;
  267. try {
  268. const resolve = require("enhanced-resolve");
  269. path = resolve.sync(process.cwd(), name);
  270. } catch (e) {
  271. console.log("Cannot resolve plugin " + name + ".");
  272. process.exit(-1); // eslint-disable-line
  273. }
  274. let Plugin;
  275. try {
  276. Plugin = require(path);
  277. } catch (e) {
  278. console.log("Cannot load plugin " + name + ". (" + path + ")");
  279. throw e;
  280. }
  281. try {
  282. return new Plugin(args);
  283. } catch (e) {
  284. console.log("Cannot instantiate plugin " + name + ". (" + path + ")");
  285. throw e;
  286. }
  287. }
  288. function ensureObject(parent, name, force) {
  289. if (force || typeof parent[name] !== "object" || parent[name] === null) {
  290. parent[name] = {};
  291. }
  292. }
  293. function ensureArray(parent, name) {
  294. if (!Array.isArray(parent[name])) {
  295. parent[name] = [];
  296. }
  297. }
  298. function addPlugin(options, plugin) {
  299. ensureArray(options, "plugins");
  300. options.plugins.unshift(plugin);
  301. }
  302. ifArg("mode", function(value) {
  303. options.mode = value;
  304. });
  305. ifArgPair(
  306. "entry",
  307. function(name, entry) {
  308. if (
  309. typeof options.entry[name] !== "undefined" &&
  310. options.entry[name] !== null
  311. ) {
  312. options.entry[name] = [].concat(options.entry[name]).concat(entry);
  313. } else {
  314. options.entry[name] = entry;
  315. }
  316. },
  317. function() {
  318. ensureObject(options, "entry", true);
  319. }
  320. );
  321. function bindRules(arg) {
  322. ifArgPair(
  323. arg,
  324. function(name, binding) {
  325. if (name === null) {
  326. name = binding;
  327. binding += "-loader";
  328. }
  329. const rule = {
  330. test: new RegExp(
  331. "\\." +
  332. name.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") +
  333. "$"
  334. ), // eslint-disable-line no-useless-escape
  335. loader: binding
  336. };
  337. if (arg === "module-bind-pre") {
  338. rule.enforce = "pre";
  339. } else if (arg === "module-bind-post") {
  340. rule.enforce = "post";
  341. }
  342. options.module.rules.push(rule);
  343. },
  344. function() {
  345. ensureObject(options, "module");
  346. ensureArray(options.module, "rules");
  347. }
  348. );
  349. }
  350. bindRules("module-bind");
  351. bindRules("module-bind-pre");
  352. bindRules("module-bind-post");
  353. let defineObject;
  354. ifArgPair(
  355. "define",
  356. function(name, value) {
  357. if (name === null) {
  358. name = value;
  359. value = true;
  360. }
  361. defineObject[name] = value;
  362. },
  363. function() {
  364. defineObject = {};
  365. },
  366. function() {
  367. const DefinePlugin = require("webpack").DefinePlugin;
  368. addPlugin(options, new DefinePlugin(defineObject));
  369. }
  370. );
  371. ifArg("output-path", function(value) {
  372. ensureObject(options, "output");
  373. options.output.path = path.resolve(value);
  374. });
  375. ifArg("output-filename", function(value) {
  376. ensureObject(options, "output");
  377. options.output.filename = value;
  378. });
  379. ifArg("output-chunk-filename", function(value) {
  380. ensureObject(options, "output");
  381. options.output.chunkFilename = value;
  382. });
  383. ifArg("output-source-map-filename", function(value) {
  384. ensureObject(options, "output");
  385. options.output.sourceMapFilename = value;
  386. });
  387. ifArg("output-public-path", function(value) {
  388. ensureObject(options, "output");
  389. options.output.publicPath = value;
  390. });
  391. ifArg("output-jsonp-function", function(value) {
  392. ensureObject(options, "output");
  393. options.output.jsonpFunction = value;
  394. });
  395. ifBooleanArg("output-pathinfo", function() {
  396. ensureObject(options, "output");
  397. options.output.pathinfo = true;
  398. });
  399. ifArg("output-library", function(value) {
  400. ensureObject(options, "output");
  401. options.output.library = value;
  402. });
  403. ifArg("output-library-target", function(value) {
  404. ensureObject(options, "output");
  405. options.output.libraryTarget = value;
  406. });
  407. ifArg("records-input-path", function(value) {
  408. options.recordsInputPath = path.resolve(value);
  409. });
  410. ifArg("records-output-path", function(value) {
  411. options.recordsOutputPath = path.resolve(value);
  412. });
  413. ifArg("records-path", function(value) {
  414. options.recordsPath = path.resolve(value);
  415. });
  416. ifArg("target", function(value) {
  417. options.target = value;
  418. });
  419. mapArgToBoolean("cache");
  420. ifBooleanArg("hot", function() {
  421. const HotModuleReplacementPlugin = require("webpack")
  422. .HotModuleReplacementPlugin;
  423. addPlugin(options, new HotModuleReplacementPlugin());
  424. });
  425. ifBooleanArg("debug", function() {
  426. const LoaderOptionsPlugin = require("webpack").LoaderOptionsPlugin;
  427. addPlugin(
  428. options,
  429. new LoaderOptionsPlugin({
  430. debug: true
  431. })
  432. );
  433. });
  434. ifArg("devtool", function(value) {
  435. options.devtool = value;
  436. });
  437. function processResolveAlias(arg, key) {
  438. ifArgPair(arg, function(name, value) {
  439. if (!name) {
  440. throw new Error("--" + arg + " <string>=<string>");
  441. }
  442. ensureObject(options, key);
  443. ensureObject(options[key], "alias");
  444. options[key].alias[name] = value;
  445. });
  446. }
  447. processResolveAlias("resolve-alias", "resolve");
  448. processResolveAlias("resolve-loader-alias", "resolveLoader");
  449. ifArg("resolve-extensions", function(value) {
  450. ensureObject(options, "resolve");
  451. if (Array.isArray(value)) {
  452. options.resolve.extensions = value;
  453. } else {
  454. options.resolve.extensions = value.split(/,\s*/);
  455. }
  456. });
  457. ifArg("optimize-max-chunks", function(value) {
  458. const LimitChunkCountPlugin = require("webpack").optimize
  459. .LimitChunkCountPlugin;
  460. addPlugin(
  461. options,
  462. new LimitChunkCountPlugin({
  463. maxChunks: parseInt(value, 10)
  464. })
  465. );
  466. });
  467. ifArg("optimize-min-chunk-size", function(value) {
  468. const MinChunkSizePlugin = require("webpack").optimize.MinChunkSizePlugin;
  469. addPlugin(
  470. options,
  471. new MinChunkSizePlugin({
  472. minChunkSize: parseInt(value, 10)
  473. })
  474. );
  475. });
  476. ifBooleanArg("optimize-minimize", function() {
  477. const LoaderOptionsPlugin = require("webpack").LoaderOptionsPlugin;
  478. addPlugin(
  479. options,
  480. new LoaderOptionsPlugin({
  481. minimize: true
  482. })
  483. );
  484. });
  485. ifArg("prefetch", function(request) {
  486. const PrefetchPlugin = require("webpack").PrefetchPlugin;
  487. addPlugin(options, new PrefetchPlugin(request));
  488. });
  489. ifArg("provide", function(value) {
  490. const idx = value.indexOf("=");
  491. let name;
  492. if (idx >= 0) {
  493. name = value.substr(0, idx);
  494. value = value.substr(idx + 1);
  495. } else {
  496. name = value;
  497. }
  498. const ProvidePlugin = require("webpack").ProvidePlugin;
  499. addPlugin(options, new ProvidePlugin(name, value));
  500. });
  501. ifArg("plugin", function(value) {
  502. addPlugin(options, loadPlugin(value));
  503. });
  504. mapArgToBoolean("bail");
  505. mapArgToBoolean("profile");
  506. if (argv._.length > 0) {
  507. ensureObject(options, "entry", true);
  508. const addTo = function addTo(name, entry) {
  509. if (options.entry[name]) {
  510. if (!Array.isArray(options.entry[name])) {
  511. options.entry[name] = [options.entry[name]];
  512. }
  513. options.entry[name].push(entry);
  514. } else {
  515. options.entry[name] = entry;
  516. }
  517. };
  518. argv._.forEach(function(content) {
  519. const i = content.indexOf("=");
  520. const j = content.indexOf("?");
  521. if (i < 0 || (j >= 0 && j < i)) {
  522. const resolved = path.resolve(content);
  523. if (fs.existsSync(resolved)) {
  524. addTo(
  525. "main",
  526. `${resolved}${
  527. fs.statSync(resolved).isDirectory() ? path.sep : ""
  528. }`
  529. );
  530. } else {
  531. addTo("main", content);
  532. }
  533. } else {
  534. addTo(content.substr(0, i), content.substr(i + 1));
  535. }
  536. });
  537. }
  538. }
  539. };