@depup/webpack 5.110.1-depup.0 → 5.110.3-depup.0

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 (70) hide show
  1. package/README.md +4 -4
  2. package/changes.json +3 -3
  3. package/lib/ChunkGraph.js +20 -18
  4. package/lib/Compilation.js +1 -1
  5. package/lib/ConcatenationScope.js +25 -35
  6. package/lib/DotenvPlugin.js +41 -13
  7. package/lib/ExportsInfo.js +10 -0
  8. package/lib/FlagDependencyUsagePlugin.js +8 -2
  9. package/lib/NormalModule.js +13 -20
  10. package/lib/RuntimePlugin.js +3 -0
  11. package/lib/RuntimeTemplate.js +437 -432
  12. package/lib/async-modules/AsyncModuleHelpers.js +10 -4
  13. package/lib/config/defaults.js +26 -16
  14. package/lib/config/normalization.js +18 -8
  15. package/lib/css/CssLoadingRuntimeModule.js +32 -13
  16. package/lib/css/CssModulesPlugin.js +9 -17
  17. package/lib/css/data.js +13 -3
  18. package/lib/css/syntax.js +78 -10
  19. package/lib/dependencies/CommonJsRequireDependency.js +18 -4
  20. package/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +20 -7
  21. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  22. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
  23. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +143 -17
  24. package/lib/dependencies/HarmonyImportDependency.js +94 -41
  25. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +93 -14
  26. package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
  27. package/lib/dependencies/HtmlEntryDependency.js +12 -2
  28. package/lib/dependencies/ImportContextDependency.js +43 -3
  29. package/lib/dependencies/ImportDependency.js +8 -29
  30. package/lib/dependencies/ImportParserPlugin.js +24 -14
  31. package/lib/dependencies/RequireHeaderDependency.js +20 -21
  32. package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
  33. package/lib/dependencies/WorkerDependency.js +6 -12
  34. package/lib/dependencies/WorkletDependency.js +7 -11
  35. package/lib/dependencies/importOptionsCheck.js +47 -0
  36. package/lib/errors/DotenvFileError.js +29 -0
  37. package/lib/errors/ImportedBindingAssignmentError.js +30 -0
  38. package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
  39. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
  40. package/lib/html/HtmlModulesPlugin.js +84 -38
  41. package/lib/html/syntax.js +417 -169
  42. package/lib/javascript/JavascriptModulesPlugin.js +5 -10
  43. package/lib/loaders/LoaderRunner.js +33 -5
  44. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  45. package/lib/optimize/ConcatenatedModule.js +46 -7
  46. package/lib/optimize/ConstExportsPlugin.js +5 -0
  47. package/lib/optimize/InlineExports.js +121 -0
  48. package/lib/optimize/RealContentHashPlugin.js +111 -35
  49. package/lib/prefetch/ResourceHintPlugin.js +12 -13
  50. package/lib/runtime/AsyncModuleRuntimeModule.js +5 -1
  51. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +11 -1
  52. package/lib/schemes/HttpUriPlugin.js +22 -5
  53. package/lib/serialization/FileMiddleware.js +15 -2
  54. package/lib/typescript/TypeScriptPlugin.js +8 -10
  55. package/lib/util/ArrayQueue.js +3 -3
  56. package/lib/util/SourceProcessor.js +24 -8
  57. package/lib/util/URLAbsoluteSpecifier.js +3 -1
  58. package/lib/util/conventions.js +5 -1
  59. package/lib/util/createMappings.js +25 -3
  60. package/lib/util/internalSerializables.js +2 -0
  61. package/lib/util/removeBOM.js +82 -1
  62. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
  63. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
  64. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  65. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  66. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  67. package/package.json +7 -7
  68. package/schemas/WebpackOptions.check.js +1 -1
  69. package/schemas/WebpackOptions.json +9 -10
  70. package/types.d.ts +95 -27
@@ -130,6 +130,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
130
130
  /** @type {HarmonySettings | undefined} */
131
131
  (parser.getTagData(name, harmonySpecifierTag));
132
132
  if (!settings || settings.ids.length !== 0) return;
133
+ settings.used = true;
133
134
  if (ImportPhaseUtils.isDefer(settings.phase)) return;
134
135
  const harmonyNamedExports = (parser.state.harmonyNamedExports =
135
136
  parser.state.harmonyNamedExports || new Set());
@@ -255,6 +256,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
255
256
  const settings =
256
257
  /** @type {HarmonySettings} */
257
258
  (parser.getTagData(id, harmonySpecifierTag));
259
+ if (settings) settings.used = true;
258
260
  const harmonyNamedExports = (parser.state.harmonyNamedExports =
259
261
  parser.state.harmonyNamedExports || new Set());
260
262
  harmonyNamedExports.add(name);
@@ -135,6 +135,11 @@ class ExportMode {
135
135
  /** @type {InstanceType<ExportInfo> | null} */
136
136
  this.partialNamespaceExportInfo = null;
137
137
 
138
+ // for "reexport-undefined": the name resolves through a circular
139
+ // reexport chain rather than being absent from a non-harmony module
140
+ /** @type {boolean} */
141
+ this.circular = false;
142
+
138
143
  // for "dynamic-reexport":
139
144
  /** @type {ExportModeIgnored | null} */
140
145
  this.ignored = null;
@@ -232,12 +237,28 @@ const isStarReexportBackToParent = (moduleGraph, exportInfo, parentModule) => {
232
237
  }
233
238
  };
234
239
 
240
+ /**
241
+ * Two `export *` sources are ambiguous for a name when they resolve to
242
+ * different bindings; a diamond re-export of the same binding is not.
243
+ * @param {ModuleGraph} moduleGraph module graph
244
+ * @param {InstanceType<ExportInfo>} previous export info of the star source that first provided the name
245
+ * @param {InstanceType<ExportInfo>} exportInfo export info of a later star source providing the same name
246
+ * @returns {boolean} true when the two sources provide different bindings
247
+ */
248
+ const isAmbiguousWith = (moduleGraph, previous, exportInfo) => {
249
+ const previousBinding = previous.getTerminalBinding(moduleGraph);
250
+ if (!previousBinding) return false;
251
+ const binding = exportInfo.getTerminalBinding(moduleGraph);
252
+ if (!binding) return false;
253
+ return previousBinding !== binding;
254
+ };
255
+
235
256
  /**
236
257
  * Determine export assignments.
237
258
  * @param {ModuleGraph} moduleGraph module graph
238
259
  * @param {HarmonyExportImportedSpecifierDependency[]} dependencies dependencies
239
260
  * @param {HarmonyExportImportedSpecifierDependency=} additionalDependency additional dependency
240
- * @returns {{ names: ExportInfoName[], dependencyIndices: DependencyIndices }} result
261
+ * @returns {{ names: ExportInfoName[], dependencyIndices: DependencyIndices, conflicting: Set<ExportInfoName> | undefined }} result
241
262
  */
242
263
  const determineExportAssignments = (
243
264
  moduleGraph,
@@ -248,6 +269,10 @@ const determineExportAssignments = (
248
269
  const names = new Set();
249
270
  /** @type {DependencyIndices} */
250
271
  const dependencyIndices = [];
272
+ /** @type {Map<ExportInfoName, InstanceType<ExportInfo>>} */
273
+ const firstProvider = new Map();
274
+ /** @type {Set<ExportInfoName> | undefined} */
275
+ let conflicting;
251
276
 
252
277
  if (additionalDependency) {
253
278
  dependencies = [...dependencies, additionalDependency];
@@ -265,24 +290,42 @@ const determineExportAssignments = (
265
290
  if (otherImportedModule) {
266
291
  const exportsInfo = moduleGraph.getExportsInfo(otherImportedModule);
267
292
  for (const exportInfo of exportsInfo.exports) {
293
+ if (exportInfo.provided !== true || exportInfo.name === "default") {
294
+ continue;
295
+ }
296
+ if (names.has(exportInfo.name)) {
297
+ const previous =
298
+ /** @type {InstanceType<ExportInfo>} */
299
+ (firstProvider.get(exportInfo.name));
300
+ if (
301
+ isAmbiguousWith(moduleGraph, previous, exportInfo) &&
302
+ // A cyclic contribution resolves to nothing, so it never competes
303
+ // with the binding a sibling `export *` provides.
304
+ !(
305
+ parentModule &&
306
+ isStarReexportBackToParent(moduleGraph, exportInfo, parentModule)
307
+ )
308
+ ) {
309
+ if (conflicting === undefined) conflicting = new Set();
310
+ conflicting.add(exportInfo.name);
311
+ }
312
+ continue;
313
+ }
268
314
  if (
269
- exportInfo.provided === true &&
270
- exportInfo.name !== "default" &&
271
- !names.has(exportInfo.name) &&
272
- !(
273
- parentModule &&
274
- isStarReexportBackToParent(moduleGraph, exportInfo, parentModule)
275
- )
315
+ parentModule &&
316
+ isStarReexportBackToParent(moduleGraph, exportInfo, parentModule)
276
317
  ) {
277
- names.add(exportInfo.name);
278
- dependencyIndices[i] = names.size;
318
+ continue;
279
319
  }
320
+ names.add(exportInfo.name);
321
+ firstProvider.set(exportInfo.name, exportInfo);
322
+ dependencyIndices[i] = names.size;
280
323
  }
281
324
  }
282
325
  }
283
326
  dependencyIndices.push(names.size);
284
327
 
285
- return { names: [...names], dependencyIndices };
328
+ return { names: [...names], dependencyIndices, conflicting };
286
329
  };
287
330
 
288
331
  /**
@@ -396,6 +439,14 @@ const getMode = (moduleGraph, dep, runtimeKey) => {
396
439
  mode.name = name;
397
440
  break;
398
441
  default:
442
+ // forwarding to a circular chain would recurse until the stack
443
+ // blows, so the name resolves to undefined like a missing one
444
+ if (exportInfo.hasCircularTarget(moduleGraph)) {
445
+ mode = new ExportMode("reexport-undefined");
446
+ mode.name = name;
447
+ mode.circular = true;
448
+ break;
449
+ }
399
450
  mode = new ExportMode("normal-reexport");
400
451
  mode.items = [
401
452
  new NormalReexportItem(name, ids, exportInfo, false, false)
@@ -653,6 +704,12 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
653
704
  /** @type {IgnoredExports} */
654
705
  const ignoredExports = new Set(this.activeExports);
655
706
  ignoredExports.add("default");
707
+ // An ambiguous name resolves to no binding at all, so no `export *` in this
708
+ // module may contribute it — not even the first one.
709
+ const conflicting = this._getConflictingStarExportNames(moduleGraph);
710
+ if (conflicting !== undefined) {
711
+ for (const name of conflicting) ignoredExports.add(name);
712
+ }
656
713
 
657
714
  /** @type {Hidden | undefined} */
658
715
  let hiddenExports;
@@ -858,6 +915,21 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
858
915
  }
859
916
  }
860
917
 
918
+ /**
919
+ * Names this module's `export *` sources provide with conflicting bindings.
920
+ * @param {ModuleGraph} moduleGraph the module graph
921
+ * @returns {Set<ExportInfoName> | undefined} ambiguous names, or nothing when there are none
922
+ */
923
+ _getConflictingStarExportNames(moduleGraph) {
924
+ // Every `export *` carries the module's full star list; a lone one has
925
+ // nothing to be ambiguous with, so skip the walk.
926
+ if (!this.allStarExports) return;
927
+ const { dependencies } = this.allStarExports;
928
+ if (dependencies.length < 2) return;
929
+ return moduleGraph.cached(determineExportAssignments, dependencies)
930
+ .conflicting;
931
+ }
932
+
861
933
  /**
862
934
  * Discover active exports from other star exports.
863
935
  * @param {ModuleGraph} moduleGraph the module graph
@@ -1059,10 +1131,54 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
1059
1131
  */
1060
1132
  getWarnings(moduleGraph) {
1061
1133
  const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
1134
+ const circular =
1135
+ this._getCircularReexportPresenceLevel() === ExportPresenceModes.WARN
1136
+ ? this._getCircularReexportErrors(moduleGraph)
1137
+ : undefined;
1062
1138
  if (exportsPresence === ExportPresenceModes.WARN) {
1063
- return this._getErrors(moduleGraph);
1139
+ const errors = this._getErrors(moduleGraph);
1140
+ if (!circular) return errors;
1141
+ return errors ? [...errors, ...circular] : circular;
1064
1142
  }
1065
- return null;
1143
+ return circular;
1144
+ }
1145
+
1146
+ /**
1147
+ * Reporting level for a circular reexport chain. It skips the promotion to
1148
+ * an error that AUTO gives a strict harmony module — a cycle warns by
1149
+ * default — but an explicit setting still says whether and how to report.
1150
+ * @returns {ExportPresenceMode} effective mode
1151
+ */
1152
+ _getCircularReexportPresenceLevel() {
1153
+ return this.exportPresenceMode === ExportPresenceModes.AUTO
1154
+ ? ExportPresenceModes.WARN
1155
+ : this.exportPresenceMode;
1156
+ }
1157
+
1158
+ /**
1159
+ * Returns errors for a name resolving through a circular reexport chain.
1160
+ * @param {ModuleGraph} moduleGraph module graph
1161
+ * @returns {WebpackError[] | undefined} errors
1162
+ */
1163
+ _getCircularReexportErrors(moduleGraph) {
1164
+ const ids = this.getIds(moduleGraph);
1165
+ if (ids.length === 0 || !this.name) return;
1166
+ const parentModule = /** @type {Module} */ (
1167
+ moduleGraph.getParentModule(this)
1168
+ );
1169
+ const exportInfo = moduleGraph
1170
+ .getExportsInfo(parentModule)
1171
+ .getReadOnlyExportInfo(this.name);
1172
+ if (!exportInfo.hasCircularTarget(moduleGraph)) return;
1173
+ return [
1174
+ new HarmonyLinkingError(
1175
+ `export '${ids.join(".")}' (reexported as '${
1176
+ this.name
1177
+ }') is part of a circular reexport chain in '${
1178
+ this.userRequest
1179
+ }' and is undefined`
1180
+ )
1181
+ ];
1066
1182
  }
1067
1183
 
1068
1184
  /**
@@ -1072,10 +1188,18 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
1072
1188
  */
1073
1189
  getErrors(moduleGraph) {
1074
1190
  const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
1075
- if (exportsPresence === ExportPresenceModes.ERROR) {
1076
- return this._getErrors(moduleGraph);
1191
+ const errors =
1192
+ exportsPresence === ExportPresenceModes.ERROR
1193
+ ? this._getErrors(moduleGraph)
1194
+ : null;
1195
+ if (
1196
+ this._getCircularReexportPresenceLevel() !== ExportPresenceModes.ERROR
1197
+ ) {
1198
+ return errors;
1077
1199
  }
1078
- return null;
1200
+ const circular = this._getCircularReexportErrors(moduleGraph);
1201
+ if (!circular) return errors;
1202
+ return errors ? [...errors, ...circular] : circular;
1079
1203
  }
1080
1204
 
1081
1205
  /**
@@ -1429,7 +1553,9 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS
1429
1553
  initFragments.push(
1430
1554
  this.getReexportFragment(
1431
1555
  module,
1432
- "reexport non-default export from non-harmony",
1556
+ mode.circular
1557
+ ? "reexport circular"
1558
+ : "reexport non-default export from non-harmony",
1433
1559
  moduleGraph
1434
1560
  .getExportsInfo(module)
1435
1561
  .getUsedName(/** @type {string} */ (mode.name), runtime),
@@ -11,11 +11,14 @@ const InitFragment = require("../InitFragment");
11
11
  const Template = require("../Template");
12
12
  const AwaitDependenciesInitFragment = require("../async-modules/AwaitDependenciesInitFragment");
13
13
  const isGeneratorLowered = require("../async-modules/isGeneratorLowered");
14
+ const memoize = require("../util/memoize");
14
15
  const { filterRuntime, mergeRuntime } = require("../util/runtime");
15
16
  const HarmonyLinkingError = require("./HarmonyLinkingError");
16
17
  const { ImportPhase, ImportPhaseUtils } = require("./ImportPhase");
17
18
  const ModuleDependency = require("./ModuleDependency");
18
19
 
20
+ const getExternalModule = memoize(() => require("../ExternalModule"));
21
+
19
22
  /** @import { ReplaceSource } from "webpack-sources" */
20
23
  /**
21
24
  * @import {
@@ -97,6 +100,31 @@ const getNonOptionalPart = (members, membersOptionals) => {
97
100
 
98
101
  /** @typedef {string[]} Ids */
99
102
 
103
+ /**
104
+ * Export names a lazy barrel still defers, so they are missing from its provided
105
+ * exports without being absent from the module.
106
+ * @param {Module} module the imported module
107
+ * @returns {Set<string> | true | undefined} deferred names, true when a deferred star re-export may provide any name, undefined when none
108
+ */
109
+ const getDeferredExports = (module) => {
110
+ const factoryMeta = module.factoryMeta;
111
+ if (factoryMeta === undefined || !factoryMeta.sideEffectFree) return;
112
+ /** @type {Set<string> | undefined} */
113
+ let names;
114
+ for (const dep of module.dependencies) {
115
+ // TODO remove in webpack 6
116
+ // These may be missing on custom dependency types not extending the base Dependency
117
+ if (!("getLazyUntil" in dep) || !("isLazy" in dep)) continue;
118
+ if (!dep.isLazy()) continue;
119
+ const until = dep.getLazyUntil();
120
+ if (until === Dependency.LAZY_UNTIL_FALLBACK) return true;
121
+ if (until !== Dependency.LAZY_UNTIL_ID) continue;
122
+ if (names === undefined) names = new Set();
123
+ names.add(/** @type {string} */ (dep.getLazyName()));
124
+ }
125
+ return names;
126
+ };
127
+
100
128
  class HarmonyImportDependency extends ModuleDependency {
101
129
  /**
102
130
  * Creates an instance of HarmonyImportDependency.
@@ -185,9 +213,14 @@ class HarmonyImportDependency extends ModuleDependency {
185
213
  const importedModule = /** @type {Module} */ (moduleGraph.getModule(this));
186
214
  const meta = moduleGraph.getMeta(module);
187
215
 
216
+ // An async external is a promise webpack awaits, not a module record with
217
+ // an evaluating state, so deferring its namespace would never throw.
188
218
  const isDeferred =
189
219
  ImportPhaseUtils.isDefer(this.phase) &&
190
- !(/** @type {BuildMeta} */ (importedModule.buildMeta).async);
220
+ !(
221
+ /** @type {BuildMeta} */ (importedModule.buildMeta).async &&
222
+ importedModule instanceof getExternalModule()
223
+ );
191
224
 
192
225
  const metaKey = isDeferred ? "deferredImportVarMap" : "importVarMap";
193
226
  let importVarMap = meta[metaKey];
@@ -283,48 +316,63 @@ class HarmonyImportDependency extends ModuleDependency {
283
316
  (exportsType !== "default-with-named" || ids[0] !== "default") &&
284
317
  moduleGraph.isExportProvided(importedModule, ids) === false
285
318
  ) {
286
- // We are sure that it's not provided
287
-
288
- // Try to provide detailed info in the error message
289
- let pos = 0;
290
- let exportsInfo = moduleGraph.getExportsInfo(importedModule);
291
- while (pos < ids.length && exportsInfo) {
292
- const id = ids[pos++];
293
- const exportInfo = exportsInfo.getReadOnlyExportInfo(id);
294
- if (exportInfo.provided === false) {
295
- // We are sure that it's not provided
296
- const providedExports = exportsInfo.getProvidedExports();
297
- const moreInfo = !Array.isArray(providedExports)
298
- ? " (possible exports unknown)"
299
- : providedExports.length === 0
300
- ? " (module has no exports)"
301
- : ` (possible exports: ${providedExports.join(", ")})`;
302
- return [
303
- new HarmonyLinkingError(
304
- `export ${ids
305
- .slice(0, pos)
306
- .map((id) => `'${id}'`)
307
- .join(".")} ${additionalMessage} was not found in '${
308
- this.userRequest
309
- }'${moreInfo}`
310
- )
311
- ];
319
+ const deferredExports = getDeferredExports(importedModule);
320
+
321
+ if (
322
+ deferredExports !== true &&
323
+ (deferredExports === undefined || !deferredExports.has(ids[0]))
324
+ ) {
325
+ // We are sure that it's not provided
326
+
327
+ // Try to provide detailed info in the error message
328
+ let pos = 0;
329
+ let exportsInfo = moduleGraph.getExportsInfo(importedModule);
330
+ while (pos < ids.length && exportsInfo) {
331
+ const id = ids[pos++];
332
+ const exportInfo = exportsInfo.getReadOnlyExportInfo(id);
333
+ if (exportInfo.provided === false) {
334
+ // We are sure that it's not provided
335
+ let providedExports = exportsInfo.getProvidedExports();
336
+ // only the first level names the imported module itself
337
+ if (
338
+ pos === 1 &&
339
+ deferredExports !== undefined &&
340
+ Array.isArray(providedExports)
341
+ ) {
342
+ providedExports = [...providedExports, ...deferredExports];
343
+ }
344
+ const moreInfo = !Array.isArray(providedExports)
345
+ ? " (possible exports unknown)"
346
+ : providedExports.length === 0
347
+ ? " (module has no exports)"
348
+ : ` (possible exports: ${providedExports.join(", ")})`;
349
+ return [
350
+ new HarmonyLinkingError(
351
+ `export ${ids
352
+ .slice(0, pos)
353
+ .map((id) => `'${id}'`)
354
+ .join(".")} ${additionalMessage} was not found in '${
355
+ this.userRequest
356
+ }'${moreInfo}`
357
+ )
358
+ ];
359
+ }
360
+ exportsInfo =
361
+ /** @type {ExportsInfo} */
362
+ (exportInfo.getNestedExportsInfo());
312
363
  }
313
- exportsInfo =
314
- /** @type {ExportsInfo} */
315
- (exportInfo.getNestedExportsInfo());
316
- }
317
364
 
318
- // General error message
319
- return [
320
- new HarmonyLinkingError(
321
- `export ${ids
322
- .map((id) => `'${id}'`)
323
- .join(".")} ${additionalMessage} was not found in '${
324
- this.userRequest
325
- }'`
326
- )
327
- ];
365
+ // General error message
366
+ return [
367
+ new HarmonyLinkingError(
368
+ `export ${ids
369
+ .map((id) => `'${id}'`)
370
+ .join(".")} ${additionalMessage} was not found in '${
371
+ this.userRequest
372
+ }'`
373
+ )
374
+ ];
375
+ }
328
376
  }
329
377
  }
330
378
  switch (exportsType) {
@@ -468,8 +516,13 @@ HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate extends
468
516
  }
469
517
 
470
518
  const importStatement = dep.getImportStatement(false, templateContext);
519
+ // A module deferring itself is already evaluating: awaiting it would probe
520
+ // the deferred namespace and force the evaluation it must not trigger.
521
+ const isSelfDeferred =
522
+ ImportPhaseUtils.isDefer(dep.phase) && referencedModule === module;
471
523
  if (
472
524
  referencedModule &&
525
+ !isSelfDeferred &&
473
526
  templateContext.moduleGraph.isAsync(referencedModule)
474
527
  ) {
475
528
  templateContext.initFragments.push(
@@ -5,6 +5,7 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ const ImportedBindingAssignmentError = require("../errors/ImportedBindingAssignmentError");
8
9
  const WebpackError = require("../errors/WebpackError");
9
10
  const getParserHooks = require("../hmr/parserHooks");
10
11
  const {
@@ -43,6 +44,7 @@ const { ImportPhaseUtils, createGetImportPhase } = require("./ImportPhase");
43
44
  * } from "../../declarations/WebpackOptions"
44
45
  */
45
46
  /** @import Module from "../Module" */
47
+ /** @import { JavascriptModuleBuildInfo } from "../javascript/JavascriptModule" */
46
48
  /**
47
49
  * @import JavascriptParser, {
48
50
  * ImportAttributes,
@@ -71,10 +73,24 @@ const EMPTY_ID_RANGES = [];
71
73
  * @property {boolean} await
72
74
  * @property {ImportAttributes=} attributes
73
75
  * @property {ImportPhaseType} phase
76
+ * @property {boolean} used whether the binding is referenced anywhere
77
+ * @property {HarmonyImportSideEffectDependency | undefined} dependency the statement's own dependency
74
78
  */
75
79
 
76
80
  const PLUGIN_NAME = "HarmonyImportDependencyParserPlugin";
77
81
 
82
+ /**
83
+ * Records that an import binding is referenced, so it is not reported as an
84
+ * unused specifier naming a missing export.
85
+ * @param {HarmonySettings | undefined} settings settings of the referenced tag
86
+ * @returns {boolean} true when the reference was a harmony import specifier
87
+ */
88
+ const markUsed = (settings) => {
89
+ if (!settings) return false;
90
+ settings.used = true;
91
+ return true;
92
+ };
93
+
78
94
  /**
79
95
  * Gets in operator harmony import info.
80
96
  * @param {JavascriptParser} parser the parser
@@ -107,6 +123,7 @@ const getInOperatorHarmonyImportInfo = (parser, left, right) => {
107
123
  if (!settings) {
108
124
  return;
109
125
  }
126
+ settings.used = true;
110
127
 
111
128
  return {
112
129
  leftPart,
@@ -125,7 +142,10 @@ const getInOperatorHarmonyImportInfo = (parser, left, right) => {
125
142
  const findImportSpecifier = (parser, node) => {
126
143
  switch (node.type) {
127
144
  case "Identifier":
128
- return Boolean(parser.getTagData(node.name, harmonySpecifierTag));
145
+ return markUsed(
146
+ /** @type {HarmonySettings | undefined} */
147
+ (parser.getTagData(node.name, harmonySpecifierTag))
148
+ );
129
149
  case "UnaryExpression":
130
150
  return findImportSpecifier(
131
151
  parser,
@@ -213,6 +233,35 @@ class HarmonyImportDependencyParserPlugin {
213
233
  this.options.sourceImport
214
234
  );
215
235
 
236
+ // Parsers are reused across modules, so both are reset per program.
237
+ /** @type {HarmonySettings[]} */
238
+ const declaredSpecifiers = [];
239
+ /** @type {HarmonyImportSideEffectDependency | undefined} */
240
+ let lastSideEffectDependency;
241
+
242
+ parser.hooks.program.tap(PLUGIN_NAME, () => {
243
+ declaredSpecifiers.length = 0;
244
+ lastSideEffectDependency = undefined;
245
+ });
246
+ parser.hooks.finish.tap(PLUGIN_NAME, () => {
247
+ for (const settings of declaredSpecifiers) {
248
+ const dependency = settings.dependency;
249
+ if (dependency === undefined || settings.used) continue;
250
+ if (dependency.unusedSpecifiers === undefined) {
251
+ dependency.unusedSpecifiers = {
252
+ exportPresenceMode: this.exportPresenceMode,
253
+ specifiers: []
254
+ };
255
+ }
256
+ dependency.unusedSpecifiers.specifiers.push([
257
+ settings.ids,
258
+ settings.name
259
+ ]);
260
+ }
261
+ declaredSpecifiers.length = 0;
262
+ lastSideEffectDependency = undefined;
263
+ });
264
+
216
265
  /**
217
266
  * Gets non optional member chain.
218
267
  * @param {MemberExpression} node member expression
@@ -228,7 +277,10 @@ class HarmonyImportDependencyParserPlugin {
228
277
  const expr = /** @type {Identifier} */ (expression);
229
278
  if (
230
279
  parser.isVariableDefined(expr.name) ||
231
- parser.getTagData(expr.name, harmonySpecifierTag)
280
+ markUsed(
281
+ /** @type {HarmonySettings | undefined} */
282
+ (parser.getTagData(expr.name, harmonySpecifierTag))
283
+ )
232
284
  ) {
233
285
  return true;
234
286
  }
@@ -267,6 +319,7 @@ class HarmonyImportDependencyParserPlugin {
267
319
  );
268
320
  sideEffectDep.loc = parser.getLocation(statement);
269
321
  parser.state.module.addDependency(sideEffectDep);
322
+ lastSideEffectDependency = sideEffectDep;
270
323
  return true;
271
324
  });
272
325
  parser.hooks.importSpecifier.tap(
@@ -274,18 +327,20 @@ class HarmonyImportDependencyParserPlugin {
274
327
  (statement, source, id, name) => {
275
328
  const ids = id === null ? [] : [id];
276
329
  const phase = getImportPhase(parser, statement);
277
- parser.tagVariable(
330
+ const settings = /** @type {HarmonySettings} */ ({
278
331
  name,
279
- harmonySpecifierTag,
280
- /** @type {HarmonySettings} */ ({
281
- name,
282
- source,
283
- ids,
284
- sourceOrder: parser.state.lastHarmonyImportOrder,
285
- attributes: getImportAttributes(statement),
286
- phase
287
- })
288
- );
332
+ source,
333
+ ids,
334
+ sourceOrder: parser.state.lastHarmonyImportOrder,
335
+ attributes: getImportAttributes(statement),
336
+ phase,
337
+ used: false,
338
+ dependency: lastSideEffectDependency
339
+ });
340
+ parser.tagVariable(name, harmonySpecifierTag, settings);
341
+ // The statement's dependency carries them, so a never-referenced
342
+ // one still resolves; a namespace import names no export.
343
+ if (ids.length !== 0) declaredSpecifiers.push(settings);
289
344
  return true;
290
345
  }
291
346
  );
@@ -328,16 +383,37 @@ class HarmonyImportDependencyParserPlugin {
328
383
  nameInfo &&
329
384
  nameInfo.rootInfo instanceof VariableInfo &&
330
385
  nameInfo.rootInfo.name &&
331
- parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag)
386
+ markUsed(
387
+ /** @type {HarmonySettings | undefined} */
388
+ (parser.getTagData(nameInfo.rootInfo.name, harmonySpecifierTag))
389
+ )
332
390
  ) {
333
391
  return true;
334
392
  }
335
393
  }
336
394
  );
395
+ // Concatenation would turn the binding into a plain local, losing the
396
+ // getter-only property whose write throws.
397
+ parser.hooks.assign.for(harmonySpecifierTag).tap(PLUGIN_NAME, (expr) => {
398
+ const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
399
+ markUsed(settings);
400
+ /** @type {JavascriptModuleBuildInfo} */
401
+ (parser.state.module.buildInfo).moduleConcatenationBailout =
402
+ "assignment to an imported binding";
403
+ // A named import is written through the namespace's getter-only
404
+ // property, which throws on its own. A namespace one is the binding
405
+ // itself, and nothing webpack can emit for it throws, so report it.
406
+ if (settings.ids.length === 0) {
407
+ const error = new ImportedBindingAssignmentError(settings.name);
408
+ error.loc = parser.getLocation(expr);
409
+ parser.state.module.addError(error);
410
+ }
411
+ });
337
412
  parser.hooks.expression
338
413
  .for(harmonySpecifierTag)
339
414
  .tap(PLUGIN_NAME, (expr) => {
340
415
  const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
416
+ settings.used = true;
341
417
 
342
418
  const dep = new HarmonyImportSpecifierDependency(
343
419
  settings.source,
@@ -376,6 +452,7 @@ class HarmonyImportDependencyParserPlugin {
376
452
  const settings =
377
453
  /** @type {HarmonySettings} */
378
454
  (parser.currentTagData);
455
+ settings.used = true;
379
456
  const nonOptionalMembers = getNonOptionalPart(
380
457
  members,
381
458
  membersOptionals
@@ -431,6 +508,7 @@ class HarmonyImportDependencyParserPlugin {
431
508
  const settings = /** @type {HarmonySettings} */ (
432
509
  parser.currentTagData
433
510
  );
511
+ settings.used = true;
434
512
  const nonOptionalMembers = getNonOptionalPart(
435
513
  members,
436
514
  membersOptionals
@@ -491,6 +569,7 @@ class HarmonyImportDependencyParserPlugin {
491
569
  .for(harmonySpecifierTag)
492
570
  .tap(PLUGIN_NAME, (expression, members) => {
493
571
  const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
572
+ settings.used = true;
494
573
  if (!ImportPhaseUtils.isDefer(settings.phase)) return;
495
574
  if (expression.operator !== "=") return;
496
575
  if (members.length !== 1) return;