WebpackOptionsDefaulter.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const path = require("path");
  7. const OptionsDefaulter = require("./OptionsDefaulter");
  8. const Template = require("./Template");
  9. const isProductionLikeMode = options => {
  10. return options.mode === "production" || !options.mode;
  11. };
  12. const isWebLikeTarget = options => {
  13. return options.target === "web" || options.target === "webworker";
  14. };
  15. const getDevtoolNamespace = library => {
  16. // if options.output.library is a string
  17. if (Array.isArray(library)) {
  18. return library.join(".");
  19. } else if (typeof library === "object") {
  20. return getDevtoolNamespace(library.root);
  21. }
  22. return library || "";
  23. };
  24. class WebpackOptionsDefaulter extends OptionsDefaulter {
  25. constructor() {
  26. super();
  27. this.set("entry", "./src");
  28. this.set(
  29. "devtool",
  30. "make",
  31. options => (options.mode === "development" ? "eval" : false)
  32. );
  33. this.set("cache", "make", options => options.mode === "development");
  34. this.set("context", process.cwd());
  35. this.set("target", "web");
  36. this.set("module", "call", value => Object.assign({}, value));
  37. this.set("module.unknownContextRequest", ".");
  38. this.set("module.unknownContextRegExp", false);
  39. this.set("module.unknownContextRecursive", true);
  40. this.set("module.unknownContextCritical", true);
  41. this.set("module.exprContextRequest", ".");
  42. this.set("module.exprContextRegExp", false);
  43. this.set("module.exprContextRecursive", true);
  44. this.set("module.exprContextCritical", true);
  45. this.set("module.wrappedContextRegExp", /.*/);
  46. this.set("module.wrappedContextRecursive", true);
  47. this.set("module.wrappedContextCritical", false);
  48. this.set("module.strictExportPresence", false);
  49. this.set("module.strictThisContextOnImports", false);
  50. this.set("module.unsafeCache", "make", options => !!options.cache);
  51. this.set("module.rules", []);
  52. this.set("module.defaultRules", "make", options => [
  53. {
  54. type: "javascript/auto",
  55. resolve: {}
  56. },
  57. {
  58. test: /\.mjs$/i,
  59. type: "javascript/esm",
  60. resolve: {
  61. mainFields:
  62. options.target === "web" ||
  63. options.target === "webworker" ||
  64. options.target === "electron-renderer"
  65. ? ["browser", "main"]
  66. : ["main"]
  67. }
  68. },
  69. {
  70. test: /\.json$/i,
  71. type: "json"
  72. },
  73. {
  74. test: /\.wasm$/i,
  75. type: "webassembly/experimental"
  76. }
  77. ]);
  78. this.set("output", "call", (value, options) => {
  79. if (typeof value === "string") {
  80. return {
  81. filename: value
  82. };
  83. } else if (typeof value !== "object") {
  84. return {};
  85. } else {
  86. return Object.assign({}, value);
  87. }
  88. });
  89. this.set("output.filename", "[name].js");
  90. this.set("output.chunkFilename", "make", options => {
  91. const filename = options.output.filename;
  92. if (typeof filename !== "function") {
  93. const hasName = filename.includes("[name]");
  94. const hasId = filename.includes("[id]");
  95. const hasChunkHash = filename.includes("[chunkhash]");
  96. // Anything changing depending on chunk is fine
  97. if (hasChunkHash || hasName || hasId) return filename;
  98. // Elsewise prefix "[id]." in front of the basename to make it changing
  99. return filename.replace(/(^|\/)([^/]*(?:\?|$))/, "$1[id].$2");
  100. }
  101. return "[id].js";
  102. });
  103. this.set("output.webassemblyModuleFilename", "[modulehash].module.wasm");
  104. this.set("output.library", "");
  105. this.set("output.hotUpdateFunction", "make", options => {
  106. return Template.toIdentifier(
  107. "webpackHotUpdate" + Template.toIdentifier(options.output.library)
  108. );
  109. });
  110. this.set("output.jsonpFunction", "make", options => {
  111. return Template.toIdentifier(
  112. "webpackJsonp" + Template.toIdentifier(options.output.library)
  113. );
  114. });
  115. this.set("output.chunkCallbackName", "make", options => {
  116. return Template.toIdentifier(
  117. "webpackChunk" + Template.toIdentifier(options.output.library)
  118. );
  119. });
  120. this.set("output.globalObject", "make", options => {
  121. switch (options.target) {
  122. case "web":
  123. case "electron-renderer":
  124. case "node-webkit":
  125. return "window";
  126. case "webworker":
  127. return "self";
  128. case "node":
  129. case "async-node":
  130. case "electron-main":
  131. return "global";
  132. default:
  133. return "self";
  134. }
  135. });
  136. this.set("output.devtoolNamespace", "make", options => {
  137. return getDevtoolNamespace(options.output.library);
  138. });
  139. this.set("output.libraryTarget", "var");
  140. this.set("output.path", path.join(process.cwd(), "dist"));
  141. this.set(
  142. "output.pathinfo",
  143. "make",
  144. options => options.mode === "development"
  145. );
  146. this.set("output.sourceMapFilename", "[file].map[query]");
  147. this.set("output.hotUpdateChunkFilename", "[id].[hash].hot-update.js");
  148. this.set("output.hotUpdateMainFilename", "[hash].hot-update.json");
  149. this.set("output.crossOriginLoading", false);
  150. this.set("output.jsonpScriptType", false);
  151. this.set("output.chunkLoadTimeout", 120000);
  152. this.set("output.hashFunction", "md4");
  153. this.set("output.hashDigest", "hex");
  154. this.set("output.hashDigestLength", 20);
  155. this.set("output.devtoolLineToLine", false);
  156. this.set("output.strictModuleExceptionHandling", false);
  157. this.set("node", "call", value => {
  158. if (typeof value === "boolean") {
  159. return value;
  160. } else {
  161. return Object.assign({}, value);
  162. }
  163. });
  164. this.set("node.console", false);
  165. this.set("node.process", true);
  166. this.set("node.global", true);
  167. this.set("node.Buffer", true);
  168. this.set("node.setImmediate", true);
  169. this.set("node.__filename", "mock");
  170. this.set("node.__dirname", "mock");
  171. this.set("performance", "call", (value, options) => {
  172. if (value === false) return false;
  173. if (
  174. value === undefined &&
  175. (!isProductionLikeMode(options) || !isWebLikeTarget(options))
  176. )
  177. return false;
  178. return Object.assign({}, value);
  179. });
  180. this.set("performance.maxAssetSize", 250000);
  181. this.set("performance.maxEntrypointSize", 250000);
  182. this.set(
  183. "performance.hints",
  184. "make",
  185. options => (isProductionLikeMode(options) ? "warning" : false)
  186. );
  187. this.set("optimization", "call", value => Object.assign({}, value));
  188. this.set("optimization.removeAvailableModules", true);
  189. this.set("optimization.removeEmptyChunks", true);
  190. this.set("optimization.mergeDuplicateChunks", true);
  191. this.set("optimization.flagIncludedChunks", "make", options =>
  192. isProductionLikeMode(options)
  193. );
  194. // TODO webpack 5 add `moduleIds: "named"` default for development
  195. // TODO webpack 5 add `moduleIds: "size"` default for production
  196. // TODO webpack 5 remove optimization.occurrenceOrder
  197. this.set("optimization.occurrenceOrder", "make", options =>
  198. isProductionLikeMode(options)
  199. );
  200. this.set("optimization.sideEffects", "make", options =>
  201. isProductionLikeMode(options)
  202. );
  203. this.set("optimization.providedExports", true);
  204. this.set("optimization.usedExports", "make", options =>
  205. isProductionLikeMode(options)
  206. );
  207. this.set("optimization.concatenateModules", "make", options =>
  208. isProductionLikeMode(options)
  209. );
  210. this.set("optimization.splitChunks", {});
  211. this.set("optimization.splitChunks.hidePathInfo", "make", options => {
  212. return isProductionLikeMode(options);
  213. });
  214. this.set("optimization.splitChunks.chunks", "async");
  215. this.set("optimization.splitChunks.minSize", "make", options => {
  216. return isProductionLikeMode(options) ? 30000 : 10000;
  217. });
  218. this.set("optimization.splitChunks.minChunks", 1);
  219. this.set("optimization.splitChunks.maxAsyncRequests", "make", options => {
  220. return isProductionLikeMode(options) ? 5 : Infinity;
  221. });
  222. this.set("optimization.splitChunks.automaticNameDelimiter", "~");
  223. this.set("optimization.splitChunks.maxInitialRequests", "make", options => {
  224. return isProductionLikeMode(options) ? 3 : Infinity;
  225. });
  226. this.set("optimization.splitChunks.name", true);
  227. this.set("optimization.splitChunks.cacheGroups", {});
  228. this.set("optimization.splitChunks.cacheGroups.default", {
  229. automaticNamePrefix: "",
  230. reuseExistingChunk: true,
  231. minChunks: 2,
  232. priority: -20
  233. });
  234. this.set("optimization.splitChunks.cacheGroups.vendors", {
  235. automaticNamePrefix: "vendors",
  236. test: /[\\/]node_modules[\\/]/,
  237. priority: -10
  238. });
  239. this.set("optimization.runtimeChunk", "call", value => {
  240. if (value === "single") {
  241. return {
  242. name: "runtime"
  243. };
  244. }
  245. if (value === true || value === "multiple") {
  246. return {
  247. name: entrypoint => `runtime~${entrypoint.name}`
  248. };
  249. }
  250. return value;
  251. });
  252. this.set("optimization.noEmitOnErrors", "make", options =>
  253. isProductionLikeMode(options)
  254. );
  255. this.set("optimization.checkWasmTypes", "make", options =>
  256. isProductionLikeMode(options)
  257. );
  258. this.set("optimization.mangleWasmImports", false);
  259. // TODO webpack 5 remove optimization.namedModules
  260. this.set(
  261. "optimization.namedModules",
  262. "make",
  263. options => options.mode === "development"
  264. );
  265. this.set("optimization.hashedModuleIds", false);
  266. // TODO webpack 5 add `chunkIds: "named"` default for development
  267. // TODO webpack 5 add `chunkIds: "size"` default for production
  268. // TODO webpack 5 remove optimization.namedChunks
  269. this.set(
  270. "optimization.namedChunks",
  271. "make",
  272. options => options.mode === "development"
  273. );
  274. this.set(
  275. "optimization.portableRecords",
  276. "make",
  277. options =>
  278. !!(
  279. options.recordsInputPath ||
  280. options.recordsOutputPath ||
  281. options.recordsPath
  282. )
  283. );
  284. this.set("optimization.minimize", "make", options =>
  285. isProductionLikeMode(options)
  286. );
  287. this.set("optimization.minimizer", "make", options => [
  288. {
  289. apply: compiler => {
  290. // Lazy load the Terser plugin
  291. const TerserPlugin = require("terser-webpack-plugin");
  292. const SourceMapDevToolPlugin = require("./SourceMapDevToolPlugin");
  293. new TerserPlugin({
  294. cache: true,
  295. parallel: true,
  296. sourceMap:
  297. (options.devtool && /source-?map/.test(options.devtool)) ||
  298. (options.plugins &&
  299. options.plugins.some(p => p instanceof SourceMapDevToolPlugin))
  300. }).apply(compiler);
  301. }
  302. }
  303. ]);
  304. this.set("optimization.nodeEnv", "make", options => {
  305. // TODO: In webpack 5, it should return `false` when mode is `none`
  306. return options.mode || "production";
  307. });
  308. this.set("resolve", "call", value => Object.assign({}, value));
  309. this.set("resolve.unsafeCache", true);
  310. this.set("resolve.modules", ["node_modules"]);
  311. this.set("resolve.extensions", [".wasm", ".mjs", ".js", ".json"]);
  312. this.set("resolve.mainFiles", ["index"]);
  313. this.set("resolve.aliasFields", "make", options => {
  314. if (
  315. options.target === "web" ||
  316. options.target === "webworker" ||
  317. options.target === "electron-renderer"
  318. ) {
  319. return ["browser"];
  320. } else {
  321. return [];
  322. }
  323. });
  324. this.set("resolve.mainFields", "make", options => {
  325. if (
  326. options.target === "web" ||
  327. options.target === "webworker" ||
  328. options.target === "electron-renderer"
  329. ) {
  330. return ["browser", "module", "main"];
  331. } else {
  332. return ["module", "main"];
  333. }
  334. });
  335. this.set("resolve.cacheWithContext", "make", options => {
  336. return (
  337. Array.isArray(options.resolve.plugins) &&
  338. options.resolve.plugins.length > 0
  339. );
  340. });
  341. this.set("resolveLoader", "call", value => Object.assign({}, value));
  342. this.set("resolveLoader.unsafeCache", true);
  343. this.set("resolveLoader.mainFields", ["loader", "main"]);
  344. this.set("resolveLoader.extensions", [".js", ".json"]);
  345. this.set("resolveLoader.mainFiles", ["index"]);
  346. this.set("resolveLoader.cacheWithContext", "make", options => {
  347. return (
  348. Array.isArray(options.resolveLoader.plugins) &&
  349. options.resolveLoader.plugins.length > 0
  350. );
  351. });
  352. }
  353. }
  354. module.exports = WebpackOptionsDefaulter;