webpack 5.108.4 → 5.109.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/bin/webpack.js +12 -0
  2. package/lib/APIPlugin.js +48 -29
  3. package/lib/CacheFacade.js +8 -0
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/ChunkGroup.js +1 -0
  6. package/lib/CompatibilityPlugin.js +62 -29
  7. package/lib/Compilation.js +58 -6
  8. package/lib/ConstPlugin.js +11 -11
  9. package/lib/ContextModule.js +239 -6
  10. package/lib/ContextModuleFactory.js +139 -14
  11. package/lib/DefinePlugin.js +433 -112
  12. package/lib/Entrypoint.js +1 -1
  13. package/lib/ExportsInfo.js +22 -5
  14. package/lib/ExportsInfoApiPlugin.js +2 -2
  15. package/lib/ExternalModule.js +107 -5
  16. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  17. package/lib/FileSystemInfo.js +256 -100
  18. package/lib/FlagDependencyUsagePlugin.js +33 -12
  19. package/lib/HotModuleReplacementPlugin.js +18 -15
  20. package/lib/InitFragment.js +20 -32
  21. package/lib/Module.js +3 -0
  22. package/lib/ModuleFilenameHelpers.js +2 -2
  23. package/lib/ModuleTypeConstants.js +8 -1
  24. package/lib/MultiCompiler.js +13 -2
  25. package/lib/MultiWatching.js +1 -0
  26. package/lib/NodeStuffPlugin.js +51 -33
  27. package/lib/NormalModule.js +175 -401
  28. package/lib/NormalModuleFactory.js +76 -2
  29. package/lib/ProgressPlugin.js +211 -119
  30. package/lib/ProvidePlugin.js +2 -2
  31. package/lib/RuntimeGlobals.js +47 -0
  32. package/lib/RuntimePlugin.js +67 -107
  33. package/lib/RuntimeTemplate.js +304 -16
  34. package/lib/Template.js +23 -3
  35. package/lib/UseStrictPlugin.js +1 -1
  36. package/lib/WebpackIsIncludedPlugin.js +1 -1
  37. package/lib/WebpackOptionsApply.js +24 -19
  38. package/lib/asset/AssetGenerator.js +110 -28
  39. package/lib/asset/AssetModulesPlugin.js +92 -2
  40. package/lib/asset/WebManifestGenerator.js +164 -0
  41. package/lib/asset/WebManifestParser.js +130 -0
  42. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  43. package/lib/async-modules/isGeneratorLowered.js +26 -0
  44. package/lib/cache/PackFileCacheStrategy.js +9 -3
  45. package/lib/config/browserslistTargetHandler.js +40 -0
  46. package/lib/config/defaults.js +219 -29
  47. package/lib/config/normalization.js +26 -0
  48. package/lib/config/target.js +9 -1
  49. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  50. package/lib/css/CssGenerator.js +40 -31
  51. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  52. package/lib/css/CssModulesPlugin.js +13 -0
  53. package/lib/css/CssParser.js +729 -313
  54. package/lib/css/syntax.js +1371 -861
  55. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  56. package/lib/dependencies/AMDPlugin.js +2 -2
  57. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  58. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  59. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  60. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  61. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  62. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  63. package/lib/dependencies/CommonJsPlugin.js +2 -2
  64. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  65. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  66. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  67. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  68. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  69. package/lib/dependencies/CssUrlDependency.js +35 -4
  70. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  71. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  72. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  73. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  74. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  75. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  76. package/lib/dependencies/HarmonyImportDependency.js +13 -2
  77. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  78. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  79. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  80. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  81. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  82. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  83. package/lib/dependencies/ImportDependency.js +43 -1
  84. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  85. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  86. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  87. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  88. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  89. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  90. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  91. package/lib/dependencies/ImportParserPlugin.js +80 -17
  92. package/lib/dependencies/ImportPhase.js +10 -8
  93. package/lib/dependencies/LoaderPlugin.js +5 -1
  94. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  95. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  96. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  97. package/lib/dependencies/SystemPlugin.js +2 -4
  98. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  99. package/lib/dependencies/URLDependency.js +141 -21
  100. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  101. package/lib/dependencies/WorkerDependency.js +86 -18
  102. package/lib/dependencies/WorkletDependency.js +278 -0
  103. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  104. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  105. package/lib/errors/LoaderLoadingError.js +20 -0
  106. package/lib/errors/ModuleParseError.js +39 -22
  107. package/lib/errors/NodeStuffInWebError.js +1 -1
  108. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  109. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  110. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  111. package/lib/hmr/lazyCompilationBackend.js +51 -12
  112. package/lib/html/HtmlGenerator.js +907 -15
  113. package/lib/html/HtmlModule.js +2 -2
  114. package/lib/html/HtmlModulesPlugin.js +610 -166
  115. package/lib/html/HtmlParser.js +603 -247
  116. package/lib/html/favicon.svg +1 -0
  117. package/lib/html/syntax.js +3451 -2762
  118. package/lib/ids/IdHelpers.js +25 -11
  119. package/lib/index.js +6 -0
  120. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  121. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  122. package/lib/javascript/JavascriptGenerator.js +197 -49
  123. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  124. package/lib/javascript/JavascriptParser.js +704 -355
  125. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  126. package/lib/javascript/syntax.js +5037 -0
  127. package/lib/library/ModuleLibraryPlugin.js +38 -1
  128. package/lib/loaders/LoaderRunner.js +714 -0
  129. package/lib/loaders/loadLoader.js +100 -0
  130. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
  131. package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
  132. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  133. package/lib/optimize/ConcatenatedModule.js +139 -31
  134. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  135. package/lib/optimize/InnerGraphPlugin.js +50 -30
  136. package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
  137. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  138. package/lib/optimize/SplitChunksPlugin.js +146 -44
  139. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  140. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  141. package/lib/prefetch/ResourceHintPlugin.js +687 -0
  142. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  143. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  144. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  145. package/lib/rules/RuleSetCompiler.js +114 -0
  146. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  147. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  148. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  149. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  150. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  151. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  152. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  153. package/lib/schemes/HttpUriPlugin.js +40 -1
  154. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  155. package/lib/serialization/BinaryMiddleware.js +270 -938
  156. package/lib/serialization/FileMiddleware.js +29 -3
  157. package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
  158. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  159. package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
  160. package/lib/stats/StatsFactory.js +46 -17
  161. package/lib/url/URLParserPlugin.js +66 -13
  162. package/lib/util/AsyncQueue.js +122 -72
  163. package/lib/util/SetHelpers.js +1 -1
  164. package/lib/util/StackedMap.js +122 -48
  165. package/lib/util/binarySearchBounds.js +2 -2
  166. package/lib/util/concatenate.js +40 -14
  167. package/lib/util/createHooksRegistry.js +13 -5
  168. package/lib/util/dataURL.js +2 -2
  169. package/lib/util/deterministicGrouping.js +7 -5
  170. package/lib/util/formatSize.js +6 -2
  171. package/lib/util/globUtils.js +710 -0
  172. package/lib/util/identifier.js +23 -6
  173. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  174. package/lib/util/internalSerializables.js +88 -71
  175. package/lib/util/magicComment.js +121 -25
  176. package/lib/util/numberHash.js +2 -2
  177. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  178. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  179. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  180. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  181. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  182. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  183. package/lib/webpack.js +65 -0
  184. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  185. package/module.d.ts +21 -0
  186. package/package.json +12 -11
  187. package/schemas/WebpackOptions.check.js +1 -1
  188. package/schemas/WebpackOptions.json +759 -36
  189. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  190. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  191. package/schemas/plugins/ProgressPlugin.json +17 -1
  192. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  193. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  194. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  195. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  196. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  197. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  198. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  199. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  200. package/types.d.ts +1212 -99
@@ -5,7 +5,6 @@
5
5
 
6
6
  "use strict";
7
7
 
8
- const { getContext } = require("loader-runner");
9
8
  const asyncLib = require("neo-async");
10
9
  const {
11
10
  AsyncSeriesBailHook,
@@ -21,6 +20,7 @@ const ModuleGraph = require("./ModuleGraph");
21
20
  const { JAVASCRIPT_MODULE_TYPE_AUTO } = require("./ModuleTypeConstants");
22
21
  const NormalModule = require("./NormalModule");
23
22
  const { ImportPhaseUtils } = require("./dependencies/ImportPhase");
23
+ const { getContext } = require("./loaders/LoaderRunner");
24
24
  const BasicEffectRulePlugin = require("./rules/BasicEffectRulePlugin");
25
25
  const BasicMatcherRulePlugin = require("./rules/BasicMatcherRulePlugin");
26
26
  const ObjectMatcherRulePlugin = require("./rules/ObjectMatcherRulePlugin");
@@ -118,6 +118,19 @@ const {
118
118
  * @property {string | undefined} options options
119
119
  */
120
120
 
121
+ /**
122
+ * Dependencies captured while resolving a loader.
123
+ * @typedef {object} ResolveDependencies
124
+ * @property {LazySet<string>} fileDependencies file dependencies of the resolve
125
+ * @property {LazySet<string>} contextDependencies context dependencies of the resolve
126
+ * @property {LazySet<string>} missingDependencies missing dependencies of the resolve
127
+ */
128
+
129
+ /**
130
+ * Memoized result of resolving a loader for a given (context, request).
131
+ * @typedef {ResolveDependencies & { loader: string, query: string | undefined, type: string | undefined }} LoaderResolveCacheEntry
132
+ */
133
+
121
134
  /** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_AUTO} JAVASCRIPT_MODULE_TYPE_AUTO */
122
135
  /** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_DYNAMIC} JAVASCRIPT_MODULE_TYPE_DYNAMIC */
123
136
  /** @typedef {import("./ModuleTypeConstants").JAVASCRIPT_MODULE_TYPE_ESM} JAVASCRIPT_MODULE_TYPE_ESM */
@@ -149,6 +162,25 @@ const EMPTY_ELEMENTS = [];
149
162
  const MATCH_RESOURCE_REGEX = /^([^!]+)!=!/;
150
163
  const LEADING_DOT_EXTENSION_REGEX = /^[^.]/;
151
164
 
165
+ /**
166
+ * Replays the file/context/missing dependencies captured by a loader resolve
167
+ * into a resolve context, so memoized loaders still record everything needed
168
+ * for watch invalidation.
169
+ * @param {ResolveContext} resolveContext resolve context to add dependencies to
170
+ * @param {ResolveDependencies} source captured resolve dependencies
171
+ * @returns {void}
172
+ */
173
+ const replayResolveDependencies = (resolveContext, source) => {
174
+ // On this path resolveContext always carries the three LazySet dependency
175
+ // sets built in the `resolve` hook, so they are safe to treat as LazySet.
176
+ /** @type {LazySet<string>} */
177
+ (resolveContext.fileDependencies).addAll(source.fileDependencies);
178
+ /** @type {LazySet<string>} */
179
+ (resolveContext.contextDependencies).addAll(source.contextDependencies);
180
+ /** @type {LazySet<string>} */
181
+ (resolveContext.missingDependencies).addAll(source.missingDependencies);
182
+ };
183
+
152
184
  /**
153
185
  * Returns ident.
154
186
  * @param {LoaderItem} data data
@@ -457,6 +489,8 @@ class NormalModuleFactory extends ModuleFactory {
457
489
  this.generatorCache = new Map();
458
490
  /** @type {Set<Module>} */
459
491
  this._restoredUnsafeCacheEntries = new Set();
492
+ /** @type {Map<string, LoaderResolveCacheEntry>} */
493
+ this._loaderResolveCache = new Map();
460
494
 
461
495
  /** @type {(resource: string) => import("./util/identifier").ParsedResource} */
462
496
  const cacheParseResource = parseResource.bindCache(
@@ -1336,14 +1370,44 @@ If changing the source code is not an option there is also a resolve options cal
1336
1370
  * Handles the callback logic for this hook.
1337
1371
  * @param {LoaderItem} item item
1338
1372
  * @param {Callback<LoaderItem>} callback callback
1373
+ * @returns {void}
1339
1374
  */
1340
1375
  (item, callback) => {
1376
+ // Loaders resolve identically for a given (context, request), so memoize
1377
+ // the result and the resolve's dependencies to skip repeated resolves.
1378
+ const cacheKey = `${context}\n${item.loader}`;
1379
+ const cached = this._loaderResolveCache.get(cacheKey);
1380
+ if (cached !== undefined) {
1381
+ replayResolveDependencies(resolveContext, cached);
1382
+ return callback(null, {
1383
+ loader: cached.loader,
1384
+ type: cached.type,
1385
+ options:
1386
+ item.options === undefined
1387
+ ? cached.query
1388
+ ? cached.query.slice(1)
1389
+ : undefined
1390
+ : item.options,
1391
+ ident: item.options === undefined ? undefined : item.ident
1392
+ });
1393
+ }
1394
+ // Capture this resolve's dependencies in dedicated sets so a future hit
1395
+ // can replay them; also feed them back into the caller's resolveContext.
1396
+ const captureContext = {
1397
+ ...resolveContext,
1398
+ fileDependencies: new LazySet(),
1399
+ contextDependencies: new LazySet(),
1400
+ missingDependencies: new LazySet()
1401
+ };
1341
1402
  resolver.resolve(
1342
1403
  contextInfo,
1343
1404
  context,
1344
1405
  item.loader,
1345
- resolveContext,
1406
+ captureContext,
1346
1407
  (err, result, resolveRequest) => {
1408
+ // Merge the captured dependencies back so a failed or successful
1409
+ // resolve records them for watch invalidation just like before.
1410
+ replayResolveDependencies(resolveContext, captureContext);
1347
1411
  if (
1348
1412
  err &&
1349
1413
  /^[^/]*$/.test(item.loader) &&
@@ -1385,6 +1449,16 @@ If changing the source code is not an option there is also a resolve options cal
1385
1449
  /** @type {ResolveRequest} */
1386
1450
  (resolveRequest).descriptionFileData.type
1387
1451
  );
1452
+
1453
+ this._loaderResolveCache.set(cacheKey, {
1454
+ loader: parsedResult.path,
1455
+ query: parsedResult.query,
1456
+ type,
1457
+ fileDependencies: captureContext.fileDependencies,
1458
+ contextDependencies: captureContext.contextDependencies,
1459
+ missingDependencies: captureContext.missingDependencies
1460
+ });
1461
+
1388
1462
  /** @type {LoaderItem} */
1389
1463
  const resolved = {
1390
1464
  loader: parsedResult.path,
@@ -29,27 +29,12 @@ const BULLET_ICON = "●";
29
29
  */
30
30
  /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
31
31
  /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
32
- /** @typedef {import("./Compilation").FactorizeModuleOptions} FactorizeModuleOptions */
33
32
  /** @typedef {import("./Dependency")} Dependency */
34
33
  /** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
35
34
  /** @typedef {import("./Module")} Module */
36
- /** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
37
35
  /** @typedef {import("./logging/Logger").Logger} Logger */
38
36
  /** @typedef {import("./cli").Colors} Colors */
39
37
 
40
- /**
41
- * Defines the async queue type used by this module.
42
- * @template T, K, R
43
- * @typedef {import("./util/AsyncQueue")<T, K, R>} AsyncQueue
44
- */
45
-
46
- /**
47
- * Defines the counts data type used by this module.
48
- * @typedef {object} CountsData
49
- * @property {number} modulesCount modules count
50
- * @property {number} dependenciesCount dependencies count
51
- */
52
-
53
38
  /**
54
39
  * Returns median.
55
40
  * @param {number} a a
@@ -125,37 +110,110 @@ const reportProfile = (logger, lastStateInfo, percentage, msg, args) => {
125
110
  }
126
111
  };
127
112
 
113
+ /**
114
+ * @param {number} ms milliseconds
115
+ * @returns {string} human readable duration
116
+ */
117
+ const formatTime = (ms) => {
118
+ if (ms < 1000) return `${Math.round(ms)}ms`;
119
+ const seconds = Math.floor(ms / 1000);
120
+ if (seconds < 60) return `${seconds}s`;
121
+ const minutes = Math.floor(seconds / 60);
122
+ if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
123
+ const hours = Math.floor(minutes / 60);
124
+ return `${hours}h ${minutes % 60}m`;
125
+ };
126
+
128
127
  /**
129
128
  * @param {string} name progress bar name
130
129
  * @param {string} color progress bar color
130
+ * @param {number} width progress bar width in characters
131
131
  * @returns {(percentage: number) => string} bar renderer
132
132
  */
133
- const createReportBar = (name, color) => {
133
+ const createReportBar = (name, color, width) => {
134
134
  const c = getColors();
135
+ const colorFn = color in c ? c[/** @type {keyof Colors} */ (color)] : c.green;
136
+ // The bar only changes when the filled width does, so cache it between steps.
137
+ let lastFilled = -1;
138
+ let lastBar = "";
135
139
 
136
140
  return (percentage) => {
137
- const w = Math.round(percentage * BAR_LENGTH);
138
- const filled = BLOCK_CHAR.repeat(w);
139
- const empty = BLOCK_CHAR.repeat(BAR_LENGTH - w);
140
- const colorFn =
141
- color in c ? c[/** @type {keyof Colors} */ (color)] : c.green;
142
-
143
- return `${[BULLET_ICON, name, filled].map(colorFn).join(" ")}${c.white(empty)}`;
141
+ const filled = Math.round(percentage * width);
142
+ if (filled === lastFilled) return lastBar;
143
+ lastFilled = filled;
144
+ const filledStr = BLOCK_CHAR.repeat(filled);
145
+ const emptyStr = BLOCK_CHAR.repeat(width - filled);
146
+ lastBar = `${[BULLET_ICON, name, filledStr].map(colorFn).join(" ")}${c.white(
147
+ emptyStr
148
+ )}`;
149
+ return lastBar;
144
150
  };
145
151
  };
146
152
 
147
- /** @typedef {Required<Exclude<NonNullable<ProgressPluginOptions["progressBar"]>, boolean>>} ProgressBarOptions */
153
+ /** @typedef {Required<Exclude<NonNullable<ProgressPluginOptions["progressBar"]>, boolean | "auto">>} ProgressBarOptions */
154
+
155
+ /** @type {ProgressBarOptions} */
156
+ const DEFAULT_PROGRESS_BAR = {
157
+ name: "Build",
158
+ color: "green",
159
+ width: BAR_LENGTH
160
+ };
161
+
162
+ /** @typedef {{ progressBar?: ProgressBarOptions | false, estimatedTime?: boolean, phaseTimings?: boolean }} DefaultHandlerOptions */
148
163
 
149
164
  /**
150
165
  * Creates a default handler.
151
166
  * @param {boolean | null | undefined} profile need profile
152
167
  * @param {Logger} logger logger
153
- * @param {ProgressBarOptions | false=} progressBar render bar
168
+ * @param {DefaultHandlerOptions=} options display options
154
169
  * @returns {HandlerFn} default handler
155
170
  */
156
- const createDefaultHandler = (profile, logger, progressBar) => {
171
+ const createDefaultHandler = (profile, logger, options = {}) => {
172
+ const {
173
+ progressBar = false,
174
+ estimatedTime = false,
175
+ phaseTimings = false
176
+ } = options;
177
+ const trackTime = estimatedTime || phaseTimings;
178
+
157
179
  /** @type {{ value: string | undefined, time: number }[]} */
158
180
  const lastStateInfo = [];
181
+ let buildStart = 0;
182
+ // Exponential moving average of the estimated total build time (0 = unset).
183
+ let smoothedTotal = 0;
184
+ /** @type {Map<string, number>} accumulated ms per phase (msg) */
185
+ const phaseTimes = new Map();
186
+ /** @type {string | undefined} */
187
+ let currentPhase;
188
+ let currentPhaseStart = 0;
189
+ let summaryReported = false;
190
+ /** @type {((percentage: number) => string) | undefined} */
191
+ let reportBar;
192
+
193
+ /**
194
+ * @param {number} now current timestamp
195
+ * @returns {void}
196
+ */
197
+ const reportPhaseSummary = (now) => {
198
+ if (!phaseTimings || summaryReported || phaseTimes.size === 0) return;
199
+ summaryReported = true;
200
+ // Flush the phase still running at completion.
201
+ if (currentPhase !== undefined) {
202
+ phaseTimes.set(
203
+ currentPhase,
204
+ (phaseTimes.get(currentPhase) || 0) + (now - currentPhaseStart)
205
+ );
206
+ }
207
+ const total = now - buildStart;
208
+ logger.info(`Build completed in ${formatTime(total)}`);
209
+ logger.info("Phase breakdown:");
210
+ for (const [phase, duration] of phaseTimes) {
211
+ if (duration > 0) {
212
+ const percent = total > 0 ? Math.round((duration / total) * 100) : 0;
213
+ logger.info(` ${phase}: ${formatTime(duration)} (${percent}%)`);
214
+ }
215
+ }
216
+ };
159
217
 
160
218
  /** @type {HandlerFn} */
161
219
  const defaultHandler = (percentage, msg, ...args) => {
@@ -163,34 +221,84 @@ const createDefaultHandler = (profile, logger, progressBar) => {
163
221
  reportProfile(logger, lastStateInfo, percentage, msg, args);
164
222
  }
165
223
 
224
+ const now = trackTime ? Date.now() : 0;
225
+
226
+ if (trackTime && percentage === 0) {
227
+ buildStart = now;
228
+ smoothedTotal = 0;
229
+ summaryReported = false;
230
+ phaseTimes.clear();
231
+ currentPhase = undefined;
232
+ currentPhaseStart = now;
233
+ }
234
+
235
+ // Accumulate time under the previous phase whenever the top-level message changes.
236
+ if (phaseTimings && msg && msg !== currentPhase) {
237
+ if (currentPhase !== undefined) {
238
+ phaseTimes.set(
239
+ currentPhase,
240
+ (phaseTimes.get(currentPhase) || 0) + (now - currentPhaseStart)
241
+ );
242
+ }
243
+ currentPhase = msg;
244
+ currentPhaseStart = now;
245
+ }
246
+
247
+ /** @type {string | undefined} */
248
+ let eta;
249
+ if (estimatedTime && percentage > 0.05 && percentage < 1) {
250
+ const elapsed = now - buildStart;
251
+ const rawTotal = elapsed / percentage;
252
+ smoothedTotal =
253
+ smoothedTotal > 0 ? smoothedTotal * 0.7 + rawTotal * 0.3 : rawTotal;
254
+ const remaining = smoothedTotal - elapsed;
255
+ if (remaining > 0) eta = `ETA: ${formatTime(remaining)}`;
256
+ }
257
+
166
258
  if (progressBar) {
167
- const reportBar = createReportBar(progressBar.name, progressBar.color);
259
+ // Build the bar renderer once, then reuse it across every step.
260
+ if (reportBar === undefined) {
261
+ reportBar = createReportBar(
262
+ progressBar.name,
263
+ progressBar.color,
264
+ progressBar.width
265
+ );
266
+ }
168
267
  const c = getColors();
169
268
  /** @type {string} */
170
269
  const currentBar = reportBar(percentage);
270
+ const suffix = eta ? ` ${eta}` : "";
171
271
 
172
272
  if (percentage === 1) {
173
273
  logger.status();
274
+ reportPhaseSummary(now);
174
275
  } else if (msg) {
175
276
  logger.status(
176
- `${currentBar} (${Math.floor(percentage * 100)}%)`,
277
+ `${currentBar} (${Math.floor(percentage * 100)}%)${suffix}`,
177
278
  `\n${[msg, ...args].map(c.gray).join(" ")}`
178
279
  );
179
280
  } else {
180
- logger.status(`${currentBar} (${Math.floor(percentage * 100)}%)`);
281
+ logger.status(
282
+ `${currentBar} (${Math.floor(percentage * 100)}%)${suffix}`
283
+ );
181
284
  }
182
285
  return;
183
286
  }
184
287
 
185
- logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
186
- if (percentage === 1 || (!msg && args.length === 0)) logger.status();
288
+ if (eta) {
289
+ logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args, eta);
290
+ } else {
291
+ logger.status(`${Math.floor(percentage * 100)}%`, msg, ...args);
292
+ }
293
+ if (percentage === 1 || (!msg && args.length === 0)) {
294
+ if (percentage === 1) reportPhaseSummary(now);
295
+ logger.status();
296
+ }
187
297
  };
188
298
 
189
299
  return defaultHandler;
190
300
  };
191
301
 
192
- const SKIPPED_QUEUE_CONTEXTS = ["import-module", "load-module"];
193
-
194
302
  /**
195
303
  * Defines the report progress callback.
196
304
  * @callback ReportProgress
@@ -214,7 +322,34 @@ const DEFAULT_OPTIONS = {
214
322
  activeModules: false,
215
323
  entries: true,
216
324
  percentBy: null,
217
- progressBar: false
325
+ progressBar: false,
326
+ estimatedTime: false,
327
+ phaseTimings: false
328
+ };
329
+
330
+ /**
331
+ * Whether progress can be rendered in place (interactive TTY). The bar is only
332
+ * useful here; in append-only output every update becomes a new line.
333
+ * @param {Compiler | MultiCompiler} compiler compiler
334
+ * @returns {boolean} true when output is an interactive terminal
335
+ */
336
+ const isInteractive = (compiler) => {
337
+ const c =
338
+ compiler instanceof MultiCompiler ? compiler.compilers[0] : compiler;
339
+ const infrastructureLogging =
340
+ c && c.options && c.options.infrastructureLogging;
341
+ if (
342
+ infrastructureLogging &&
343
+ typeof infrastructureLogging.appendOnly === "boolean"
344
+ ) {
345
+ return !infrastructureLogging.appendOnly;
346
+ }
347
+ const stream =
348
+ (infrastructureLogging && infrastructureLogging.stream) || process.stderr;
349
+ return (
350
+ Boolean(/** @type {NodeJS.WriteStream} */ (stream).isTTY) &&
351
+ process.env.TERM !== "dumb"
352
+ );
218
353
  };
219
354
 
220
355
  class ProgressPlugin {
@@ -260,11 +395,21 @@ class ProgressPlugin {
260
395
  this.showActiveModules = merged.activeModules;
261
396
  this.percentBy = merged.percentBy;
262
397
 
263
- const progressBar = merged.progressBar === true ? {} : merged.progressBar;
264
- /** @type {ProgressBarOptions | false} */
265
- this.progressBar = progressBar
266
- ? { name: "Build", color: "green", ...progressBar }
267
- : false;
398
+ const progressBar = merged.progressBar;
399
+ /** @type {ProgressBarOptions | false | "auto"} */
400
+ this.progressBar =
401
+ progressBar === "auto"
402
+ ? "auto"
403
+ : progressBar
404
+ ? {
405
+ ...DEFAULT_PROGRESS_BAR,
406
+ ...(progressBar === true ? {} : progressBar)
407
+ }
408
+ : false;
409
+ /** @type {boolean} */
410
+ this.estimatedTime = merged.estimatedTime;
411
+ /** @type {boolean} */
412
+ this.phaseTimings = merged.phaseTimings;
268
413
  }
269
414
 
270
415
  /**
@@ -273,12 +418,31 @@ class ProgressPlugin {
273
418
  * @returns {void}
274
419
  */
275
420
  apply(compiler) {
421
+ let progressBar = this.progressBar;
422
+ // webpack@6 (opt-in today via `experiments.futureDefaults`) turns an unset
423
+ // progressBar into the auto bar; an explicit `false` is still respected.
424
+ if (progressBar === false && this.options.progressBar === undefined) {
425
+ const c =
426
+ compiler instanceof MultiCompiler ? compiler.compilers[0] : compiler;
427
+ if (c && c.options && c.options.experiments.futureDefaults) {
428
+ progressBar = "auto";
429
+ }
430
+ }
431
+ if (progressBar === "auto") {
432
+ progressBar = isInteractive(compiler)
433
+ ? { ...DEFAULT_PROGRESS_BAR }
434
+ : false;
435
+ }
276
436
  const handler =
277
437
  this.handler ||
278
438
  createDefaultHandler(
279
439
  this.profile,
280
440
  compiler.getInfrastructureLogger("webpack.Progress"),
281
- this.progressBar
441
+ {
442
+ progressBar,
443
+ estimatedTime: this.estimatedTime,
444
+ phaseTimings: this.phaseTimings
445
+ }
282
446
  );
283
447
  if (compiler instanceof MultiCompiler) {
284
448
  this._applyOnMultiCompiler(compiler, handler);
@@ -336,9 +500,7 @@ class ProgressPlugin {
336
500
  let lastDependenciesCount = 0;
337
501
  let lastEntriesCount = 0;
338
502
  let modulesCount = 0;
339
- let skippedModulesCount = 0;
340
503
  let dependenciesCount = 0;
341
- let skippedDependenciesCount = 0;
342
504
  let entriesCount = 1;
343
505
  let doneModules = 0;
344
506
  let doneDependencies = 0;
@@ -422,16 +584,7 @@ class ProgressPlugin {
422
584
  lastUpdate = Date.now();
423
585
  };
424
586
 
425
- /**
426
- * Processes the provided factorize queue.
427
- * @template T
428
- * @param {AsyncQueue<FactorizeModuleOptions, string, Module | ModuleFactoryResult>} factorizeQueue async queue
429
- * @param {T} _item item
430
- */
431
- const factorizeAdd = (factorizeQueue, _item) => {
432
- if (SKIPPED_QUEUE_CONTEXTS.includes(factorizeQueue.getContext())) {
433
- skippedDependenciesCount++;
434
- }
587
+ const factorizeAdd = () => {
435
588
  dependenciesCount++;
436
589
  if (dependenciesCount < 50 || dependenciesCount % 100 === 0) {
437
590
  updateThrottled();
@@ -445,16 +598,7 @@ class ProgressPlugin {
445
598
  }
446
599
  };
447
600
 
448
- /**
449
- * Processes the provided add module queue.
450
- * @template T
451
- * @param {AsyncQueue<Module, string, Module>} addModuleQueue async queue
452
- * @param {T} _item item
453
- */
454
- const moduleAdd = (addModuleQueue, _item) => {
455
- if (SKIPPED_QUEUE_CONTEXTS.includes(addModuleQueue.getContext())) {
456
- skippedModulesCount++;
457
- }
601
+ const moduleAdd = () => {
458
602
  modulesCount++;
459
603
  if (modulesCount < 50 || modulesCount % 100 === 0) updateThrottled();
460
604
  };
@@ -516,72 +660,20 @@ class ProgressPlugin {
516
660
  update();
517
661
  };
518
662
 
519
- const cache = compiler.getCache(PLUGIN_NAME).getItemCache("counts", null);
520
-
521
- /** @type {Promise<CountsData> | undefined} */
522
- let cacheGetPromise;
523
-
524
- compiler.hooks.beforeCompile.tap(PLUGIN_NAME, () => {
525
- if (!cacheGetPromise) {
526
- cacheGetPromise = cache.getPromise().then(
527
- (data) => {
528
- if (data) {
529
- lastModulesCount = lastModulesCount || data.modulesCount;
530
- lastDependenciesCount =
531
- lastDependenciesCount || data.dependenciesCount;
532
- }
533
- return data;
534
- },
535
- (_err) => {
536
- // Ignore error
537
- }
538
- );
539
- }
540
- });
541
-
542
- compiler.hooks.afterCompile.tapPromise(PLUGIN_NAME, (compilation) => {
543
- if (compilation.compiler.isChild()) return Promise.resolve();
544
- return /** @type {Promise<CountsData>} */ (cacheGetPromise).then(
545
- async (oldData) => {
546
- const realModulesCount = modulesCount - skippedModulesCount;
547
- const realDependenciesCount =
548
- dependenciesCount - skippedDependenciesCount;
549
-
550
- if (
551
- !oldData ||
552
- oldData.modulesCount !== realModulesCount ||
553
- oldData.dependenciesCount !== realDependenciesCount
554
- ) {
555
- await cache.storePromise({
556
- modulesCount: realModulesCount,
557
- dependenciesCount: realDependenciesCount
558
- });
559
- }
560
- }
561
- );
562
- });
563
-
564
663
  compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
565
664
  if (compilation.compiler.isChild()) return;
665
+ // Carry the previous compilation's counts into the next as an estimate
666
+ // (helps watch rebuilds); not persisted, to avoid cache invalidation.
566
667
  lastModulesCount = modulesCount;
567
668
  lastEntriesCount = entriesCount;
568
669
  lastDependenciesCount = dependenciesCount;
569
- modulesCount =
570
- skippedModulesCount =
571
- dependenciesCount =
572
- skippedDependenciesCount =
573
- entriesCount =
574
- 0;
670
+ modulesCount = dependenciesCount = entriesCount = 0;
575
671
  doneModules = doneDependencies = doneEntries = 0;
576
672
 
577
- compilation.factorizeQueue.hooks.added.tap(PLUGIN_NAME, (item) =>
578
- factorizeAdd(compilation.factorizeQueue, item)
579
- );
673
+ compilation.factorizeQueue.hooks.added.tap(PLUGIN_NAME, factorizeAdd);
580
674
  compilation.factorizeQueue.hooks.result.tap(PLUGIN_NAME, factorizeDone);
581
675
 
582
- compilation.addModuleQueue.hooks.added.tap(PLUGIN_NAME, (item) =>
583
- moduleAdd(compilation.addModuleQueue, item)
584
- );
676
+ compilation.addModuleQueue.hooks.added.tap(PLUGIN_NAME, moduleAdd);
585
677
  compilation.processDependenciesQueue.hooks.result.tap(
586
678
  PLUGIN_NAME,
587
679
  moduleDone
@@ -85,7 +85,7 @@ class ProvidePlugin {
85
85
  request.slice(1),
86
86
  /** @type {Range} */ (expr.range)
87
87
  );
88
- dep.loc = /** @type {DependencyLocation} */ (expr.loc);
88
+ dep.loc = parser.getLocation(expr);
89
89
  parser.state.module.addDependency(dep);
90
90
  return true;
91
91
  });
@@ -100,7 +100,7 @@ class ProvidePlugin {
100
100
  request.slice(1),
101
101
  /** @type {Range} */ (expr.callee.range)
102
102
  );
103
- dep.loc = /** @type {DependencyLocation} */ (expr.callee.loc);
103
+ dep.loc = parser.getLocation(expr.callee);
104
104
  parser.state.module.addDependency(dep);
105
105
  parser.walkExpressions(expr.arguments);
106
106
  return true;