webpack.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const util = require("util");
  7. const webpackOptionsSchemaCheck = require("../schemas/WebpackOptions.check");
  8. const webpackOptionsSchema =
  9. /** @type {EXPECTED_ANY} */
  10. (require("../schemas/WebpackOptions.json"));
  11. const Compiler = require("./Compiler");
  12. const WebpackOptionsApply = require("./WebpackOptionsApply");
  13. const {
  14. applyWebpackOptionsBaseDefaults,
  15. applyWebpackOptionsDefaults
  16. } = require("./config/defaults");
  17. const {
  18. applyWebpackOptionsInterception,
  19. getNormalizedWebpackOptions
  20. } = require("./config/normalization");
  21. const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin");
  22. const memoize = require("./util/memoize");
  23. const getValidateSchema = memoize(() => require("./validateSchema"));
  24. const getMultiCompiler = memoize(() => require("./MultiCompiler"));
  25. const getProgressPlugin = memoize(() => require("./ProgressPlugin"));
  26. /**
  27. * @import {
  28. * WebpackOptions,
  29. * WatchOptions
  30. * } from "../declarations/WebpackOptions"
  31. */
  32. /** @import { WebpackOptionsNormalizedWithDefaults } from "./config/defaults" */
  33. /** @import { WebpackOptionsInterception } from "./config/normalization" */
  34. /**
  35. * @import MultiCompiler, {
  36. * MultiCompilerOptions,
  37. * MultiWebpackOptions
  38. * } from "./MultiCompiler"
  39. */
  40. /** @import MultiStats from "./MultiStats" */
  41. /** @import Stats from "./Stats" */
  42. /** @typedef {(this: Compiler, compiler: Compiler) => void} WebpackPluginFunction */
  43. /** @typedef {(compiler: Compiler) => void} WebpackPluginInstanceApplyFunction */
  44. // only an array config reaches the multi compiler
  45. /**
  46. * Whether core should auto-apply progress, from `infrastructureLogging.progress`.
  47. * Like other bundlers, `"auto"` shows it in a TTY and stays silent in CI; `true`
  48. * forces it on (unless logging is off), `false` disables it.
  49. * @param {WebpackOptionsNormalizedWithDefaults} options resolved options
  50. * @returns {boolean} true when progress should be applied by default
  51. */
  52. const isDefaultProgressEnabled = (options) => {
  53. const infrastructureLogging = options.infrastructureLogging;
  54. if (!infrastructureLogging) return false;
  55. const progress = infrastructureLogging.progress;
  56. if (!progress) return false;
  57. if (
  58. infrastructureLogging.level === "none" ||
  59. /** @type {EXPECTED_ANY} */ (infrastructureLogging.level) === false
  60. ) {
  61. return false;
  62. }
  63. return progress === "auto" ? !infrastructureLogging.appendOnly : true;
  64. };
  65. /**
  66. * @param {WebpackOptionsNormalizedWithDefaults} options resolved options
  67. * @returns {boolean} true when the user already added a ProgressPlugin
  68. */
  69. const hasUserProgressPlugin = (options) =>
  70. Array.isArray(options.plugins) &&
  71. options.plugins.some((p) => p instanceof getProgressPlugin());
  72. /**
  73. * Auto-applies a default `ProgressPlugin` driven by `infrastructureLogging.progress`,
  74. * unless the user already added one. `hasUserPlugin` is lazy so the plugin scan
  75. * only runs when progress is actually enabled.
  76. * @param {Compiler | MultiCompiler} compiler compiler to apply to
  77. * @param {WebpackOptionsNormalizedWithDefaults} options options deciding whether progress is on
  78. * @param {() => boolean} hasUserPlugin whether a ProgressPlugin is already present
  79. * @returns {void}
  80. */
  81. const applyDefaultProgressPlugin = (compiler, options, hasUserPlugin) => {
  82. if (!isDefaultProgressEnabled(options) || hasUserPlugin()) return;
  83. const ProgressPlugin = getProgressPlugin();
  84. new ProgressPlugin({ progressBar: "auto" }).apply(compiler);
  85. };
  86. /**
  87. * Defines the callback callback.
  88. * @template T
  89. * @template [R=void]
  90. * @callback Callback
  91. * @param {Error | null} err
  92. * @param {T=} result
  93. * @returns {R}
  94. */
  95. /** @typedef {Callback<void>} ErrorCallback */
  96. /**
  97. * Creates a multi compiler.
  98. * @param {ReadonlyArray<WebpackOptions>} childOptions options array
  99. * @param {MultiCompilerOptions} options options
  100. * @returns {MultiCompiler} a multi-compiler
  101. */
  102. const createMultiCompiler = (childOptions, options) => {
  103. const MultiCompiler = getMultiCompiler();
  104. const compilers = childOptions.map((options, index) =>
  105. createCompiler(options, index)
  106. );
  107. const compiler = new MultiCompiler(compilers, options);
  108. for (const childCompiler of compilers) {
  109. if (childCompiler.options.dependencies) {
  110. compiler.setDependencies(
  111. childCompiler,
  112. childCompiler.options.dependencies
  113. );
  114. }
  115. }
  116. const firstOptions =
  117. compilers.length > 0 &&
  118. /** @type {WebpackOptionsNormalizedWithDefaults} */ (compilers[0].options);
  119. if (firstOptions) {
  120. applyDefaultProgressPlugin(compiler, firstOptions, () =>
  121. compilers.some((c) =>
  122. hasUserProgressPlugin(
  123. /** @type {WebpackOptionsNormalizedWithDefaults} */ (c.options)
  124. )
  125. )
  126. );
  127. }
  128. return compiler;
  129. };
  130. /**
  131. * Creates a compiler.
  132. * @param {WebpackOptions} rawOptions options object
  133. * @param {number=} compilerIndex index of compiler
  134. * @returns {Compiler} a compiler
  135. */
  136. const createCompiler = (rawOptions, compilerIndex) => {
  137. let options = getNormalizedWebpackOptions(rawOptions);
  138. applyWebpackOptionsBaseDefaults(options);
  139. /** @type {WebpackOptionsInterception=} */
  140. let interception;
  141. ({ options, interception } = applyWebpackOptionsInterception(options));
  142. const compiler = new Compiler(
  143. /** @type {string} */ (options.context),
  144. options
  145. );
  146. new NodeEnvironmentPlugin({
  147. infrastructureLogging: options.infrastructureLogging
  148. }).apply(compiler);
  149. if (Array.isArray(options.plugins)) {
  150. for (const plugin of options.plugins) {
  151. if (typeof plugin === "function") {
  152. /** @type {WebpackPluginFunction} */
  153. (plugin).call(compiler, compiler);
  154. } else if (plugin) {
  155. plugin.apply(compiler);
  156. }
  157. }
  158. }
  159. const resolvedDefaultOptions = applyWebpackOptionsDefaults(
  160. options,
  161. compilerIndex
  162. );
  163. if (resolvedDefaultOptions.platform) {
  164. compiler.platform = resolvedDefaultOptions.platform;
  165. }
  166. // Child compilers skip this; the MultiCompiler applies one aggregated reporter.
  167. if (compilerIndex === undefined) {
  168. const resolvedOptions =
  169. /** @type {WebpackOptionsNormalizedWithDefaults} */ (options);
  170. applyDefaultProgressPlugin(compiler, resolvedOptions, () =>
  171. hasUserProgressPlugin(resolvedOptions)
  172. );
  173. }
  174. if (options.validate) {
  175. compiler.hooks.validate.call();
  176. }
  177. compiler.hooks.environment.call();
  178. compiler.hooks.afterEnvironment.call();
  179. new WebpackOptionsApply().process(
  180. /** @type {WebpackOptionsNormalizedWithDefaults} */
  181. (options),
  182. compiler,
  183. interception
  184. );
  185. compiler.hooks.initialize.call();
  186. return compiler;
  187. };
  188. /**
  189. * Returns array of options.
  190. * @template T
  191. * @param {T[] | T} options options
  192. * @returns {T[]} array of options
  193. */
  194. const asArray = (options) =>
  195. Array.isArray(options) ? [...options] : [options];
  196. /**
  197. * Checks whether it needs validate.
  198. * @param {WebpackOptions | null | undefined} options options
  199. * @returns {boolean} true when need to validate, otherwise false
  200. */
  201. const needValidate = (options) => {
  202. if (
  203. options &&
  204. (options.validate === false ||
  205. (options.experiments &&
  206. options.experiments.futureDefaults === true &&
  207. (options.mode === "production" || !options.mode)))
  208. ) {
  209. return false;
  210. }
  211. return true;
  212. };
  213. /**
  214. * Returns the compiler object.
  215. * @overload
  216. * @param {WebpackOptions} options options object
  217. * @param {Callback<Stats>} callback callback
  218. * @returns {Compiler | null} the compiler object
  219. */
  220. /**
  221. * Returns the compiler object.
  222. * @overload
  223. * @param {WebpackOptions} options options object
  224. * @returns {Compiler} the compiler object
  225. */
  226. /**
  227. * Returns the multi compiler object.
  228. * @overload
  229. * @param {MultiWebpackOptions} options options objects
  230. * @param {Callback<MultiStats>} callback callback
  231. * @returns {MultiCompiler | null} the multi compiler object
  232. */
  233. /**
  234. * Returns the multi compiler object.
  235. * @overload
  236. * @param {MultiWebpackOptions} options options objects
  237. * @returns {MultiCompiler} the multi compiler object
  238. */
  239. /**
  240. * Returns compiler or MultiCompiler.
  241. * @param {WebpackOptions | MultiWebpackOptions} options options
  242. * @param {Callback<Stats> & Callback<MultiStats>=} callback callback
  243. * @returns {Compiler | MultiCompiler | null} Compiler or MultiCompiler
  244. */
  245. const webpack = (options, callback) => {
  246. const create = () => {
  247. const isMultiCompiler = Array.isArray(options);
  248. if (
  249. !asArray(/** @type {WebpackOptions} */ (options)).every((options) =>
  250. needValidate(options) ? webpackOptionsSchemaCheck(options) : true
  251. )
  252. ) {
  253. getValidateSchema()(
  254. webpackOptionsSchema,
  255. isMultiCompiler
  256. ? options.map((options) => (needValidate(options) ? options : {}))
  257. : needValidate(options)
  258. ? options
  259. : {}
  260. );
  261. util.deprecate(
  262. () => {},
  263. "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
  264. "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
  265. )();
  266. }
  267. /** @type {MultiCompiler | Compiler} */
  268. let compiler;
  269. /** @type {boolean | undefined} */
  270. let watch = false;
  271. /** @type {WatchOptions | WatchOptions[]} */
  272. let watchOptions;
  273. if (isMultiCompiler) {
  274. /** @type {MultiCompiler} */
  275. compiler = createMultiCompiler(
  276. options,
  277. /** @type {MultiCompilerOptions} */
  278. (options)
  279. );
  280. watch = options.some((options) => options.watch);
  281. watchOptions = options.map((options) => options.watchOptions || {});
  282. } else {
  283. const webpackOptions = /** @type {WebpackOptions} */ (options);
  284. /** @type {Compiler} */
  285. compiler = createCompiler(webpackOptions);
  286. watch = webpackOptions.watch;
  287. watchOptions = webpackOptions.watchOptions || {};
  288. }
  289. return { compiler, watch, watchOptions };
  290. };
  291. if (callback) {
  292. try {
  293. const { compiler, watch, watchOptions } = create();
  294. if (watch) {
  295. compiler.watch(watchOptions, callback);
  296. } else {
  297. compiler.run((err, stats) => {
  298. compiler.close((err2) => {
  299. callback(
  300. err || err2,
  301. /** @type {options extends WebpackOptions ? Stats : MultiStats} */
  302. (stats)
  303. );
  304. });
  305. });
  306. }
  307. return compiler;
  308. } catch (err) {
  309. process.nextTick(() => callback(/** @type {Error} */ (err)));
  310. return null;
  311. }
  312. } else {
  313. const { compiler, watch } = create();
  314. if (watch) {
  315. util.deprecate(
  316. () => {},
  317. "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
  318. "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
  319. )();
  320. }
  321. return compiler;
  322. }
  323. };
  324. module.exports = webpack;