webpack 5.110.2 → 5.110.3

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 (42) hide show
  1. package/README.md +0 -2
  2. package/lib/ChunkGraph.js +20 -18
  3. package/lib/Compilation.js +1 -1
  4. package/lib/ConcatenationScope.js +25 -35
  5. package/lib/DotenvPlugin.js +41 -13
  6. package/lib/FlagDependencyUsagePlugin.js +8 -2
  7. package/lib/NormalModule.js +13 -20
  8. package/lib/RuntimePlugin.js +3 -0
  9. package/lib/RuntimeTemplate.js +14 -2
  10. package/lib/async-modules/AsyncModuleHelpers.js +10 -4
  11. package/lib/config/defaults.js +26 -16
  12. package/lib/config/normalization.js +18 -8
  13. package/lib/dependencies/CommonJsRequireDependency.js +18 -4
  14. package/lib/dependencies/ContextDependencyTemplateAsRequireCall.js +20 -7
  15. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +100 -19
  16. package/lib/dependencies/HarmonyImportDependency.js +94 -41
  17. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +12 -2
  18. package/lib/dependencies/HtmlEntryDependency.js +12 -2
  19. package/lib/dependencies/ImportContextDependency.js +43 -3
  20. package/lib/dependencies/ImportDependency.js +8 -29
  21. package/lib/dependencies/ImportParserPlugin.js +24 -14
  22. package/lib/dependencies/RequireHeaderDependency.js +20 -21
  23. package/lib/dependencies/importOptionsCheck.js +47 -0
  24. package/lib/errors/DotenvFileError.js +29 -0
  25. package/lib/errors/ImportedBindingAssignmentError.js +30 -0
  26. package/lib/html/HtmlModulesPlugin.js +29 -22
  27. package/lib/html/syntax.js +304 -143
  28. package/lib/javascript/JavascriptModulesPlugin.js +2 -0
  29. package/lib/optimize/ConcatenatedModule.js +38 -7
  30. package/lib/prefetch/ResourceHintPlugin.js +6 -1
  31. package/lib/runtime/AsyncModuleRuntimeModule.js +5 -1
  32. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +11 -1
  33. package/lib/schemes/HttpUriPlugin.js +22 -5
  34. package/lib/serialization/FileMiddleware.js +15 -2
  35. package/lib/util/ArrayQueue.js +3 -3
  36. package/lib/util/URLAbsoluteSpecifier.js +3 -1
  37. package/lib/util/conventions.js +5 -1
  38. package/lib/util/internalSerializables.js +2 -0
  39. package/package.json +1 -1
  40. package/schemas/WebpackOptions.check.js +1 -1
  41. package/schemas/WebpackOptions.json +9 -10
  42. package/types.d.ts +26 -17
@@ -22,11 +22,9 @@ class ContextDependencyTemplateAsRequireCall
22
22
  * @param {DependencyTemplateContext} templateContext the context object
23
23
  * @returns {void}
24
24
  */
25
- apply(
26
- dependency,
27
- source,
28
- { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements }
29
- ) {
25
+ apply(dependency, source, templateContext) {
26
+ const { runtimeTemplate, moduleGraph, chunkGraph, runtimeRequirements } =
27
+ templateContext;
30
28
  const dep = /** @type {ContextDependency} */ (dependency);
31
29
  let moduleExports = runtimeTemplate.moduleExports({
32
30
  module: moduleGraph.getModule(dep),
@@ -49,8 +47,7 @@ class ContextDependencyTemplateAsRequireCall
49
47
  source.replace(rep.range[0], rep.range[1] - 1, rep.value);
50
48
  }
51
49
  }
52
- source.replace(dep.valueRange[1], range[1] - 1, ")");
53
- source.replace(range[0], dep.valueRange[0] - 1, `${moduleExports}(`);
50
+ this.replaceCall(dep, source, moduleExports, templateContext);
54
51
  } else {
55
52
  source.replace(range[0], range[1] - 1, moduleExports);
56
53
  }
@@ -58,6 +55,22 @@ class ContextDependencyTemplateAsRequireCall
58
55
  source.replace(range[0], range[1] - 1, moduleExports);
59
56
  }
60
57
  }
58
+
59
+ /**
60
+ * Rewrites the call around its request, dropping anything the original one
61
+ * carried after it. Overridden where a later argument must be kept.
62
+ * @param {ContextDependency} dep the dependency
63
+ * @param {ReplaceSource} source the current replace source which can be modified
64
+ * @param {string} moduleExports the expression the request is passed to
65
+ * @param {DependencyTemplateContext} templateContext the context object
66
+ * @returns {void}
67
+ */
68
+ replaceCall(dep, source, moduleExports, templateContext) {
69
+ const range = /** @type {Range} */ (dep.range);
70
+ const valueRange = /** @type {Range} */ (dep.valueRange);
71
+ source.replace(valueRange[1], range[1] - 1, ")");
72
+ source.replace(range[0], valueRange[0] - 1, `${moduleExports}(`);
73
+ }
61
74
  }
62
75
 
63
76
  module.exports = ContextDependencyTemplateAsRequireCall;
@@ -237,12 +237,28 @@ const isStarReexportBackToParent = (moduleGraph, exportInfo, parentModule) => {
237
237
  }
238
238
  };
239
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
+
240
256
  /**
241
257
  * Determine export assignments.
242
258
  * @param {ModuleGraph} moduleGraph module graph
243
259
  * @param {HarmonyExportImportedSpecifierDependency[]} dependencies dependencies
244
260
  * @param {HarmonyExportImportedSpecifierDependency=} additionalDependency additional dependency
245
- * @returns {{ names: ExportInfoName[], dependencyIndices: DependencyIndices }} result
261
+ * @returns {{ names: ExportInfoName[], dependencyIndices: DependencyIndices, conflicting: Set<ExportInfoName> | undefined }} result
246
262
  */
247
263
  const determineExportAssignments = (
248
264
  moduleGraph,
@@ -253,6 +269,10 @@ const determineExportAssignments = (
253
269
  const names = new Set();
254
270
  /** @type {DependencyIndices} */
255
271
  const dependencyIndices = [];
272
+ /** @type {Map<ExportInfoName, InstanceType<ExportInfo>>} */
273
+ const firstProvider = new Map();
274
+ /** @type {Set<ExportInfoName> | undefined} */
275
+ let conflicting;
256
276
 
257
277
  if (additionalDependency) {
258
278
  dependencies = [...dependencies, additionalDependency];
@@ -270,24 +290,42 @@ const determineExportAssignments = (
270
290
  if (otherImportedModule) {
271
291
  const exportsInfo = moduleGraph.getExportsInfo(otherImportedModule);
272
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
+ }
273
314
  if (
274
- exportInfo.provided === true &&
275
- exportInfo.name !== "default" &&
276
- !names.has(exportInfo.name) &&
277
- !(
278
- parentModule &&
279
- isStarReexportBackToParent(moduleGraph, exportInfo, parentModule)
280
- )
315
+ parentModule &&
316
+ isStarReexportBackToParent(moduleGraph, exportInfo, parentModule)
281
317
  ) {
282
- names.add(exportInfo.name);
283
- dependencyIndices[i] = names.size;
318
+ continue;
284
319
  }
320
+ names.add(exportInfo.name);
321
+ firstProvider.set(exportInfo.name, exportInfo);
322
+ dependencyIndices[i] = names.size;
285
323
  }
286
324
  }
287
325
  }
288
326
  dependencyIndices.push(names.size);
289
327
 
290
- return { names: [...names], dependencyIndices };
328
+ return { names: [...names], dependencyIndices, conflicting };
291
329
  };
292
330
 
293
331
  /**
@@ -666,6 +704,12 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
666
704
  /** @type {IgnoredExports} */
667
705
  const ignoredExports = new Set(this.activeExports);
668
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
+ }
669
713
 
670
714
  /** @type {Hidden | undefined} */
671
715
  let hiddenExports;
@@ -871,6 +915,21 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
871
915
  }
872
916
  }
873
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
+
874
933
  /**
875
934
  * Discover active exports from other star exports.
876
935
  * @param {ModuleGraph} moduleGraph the module graph
@@ -1072,7 +1131,10 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
1072
1131
  */
1073
1132
  getWarnings(moduleGraph) {
1074
1133
  const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
1075
- const circular = this._getCircularReexportWarnings(moduleGraph);
1134
+ const circular =
1135
+ this._getCircularReexportPresenceLevel() === ExportPresenceModes.WARN
1136
+ ? this._getCircularReexportErrors(moduleGraph)
1137
+ : undefined;
1076
1138
  if (exportsPresence === ExportPresenceModes.WARN) {
1077
1139
  const errors = this._getErrors(moduleGraph);
1078
1140
  if (!circular) return errors;
@@ -1082,12 +1144,23 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
1082
1144
  }
1083
1145
 
1084
1146
  /**
1085
- * Returns warnings for a name resolving through a circular reexport chain,
1086
- * off the export-presence path so a strict harmony module warns, not fails.
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.
1087
1160
  * @param {ModuleGraph} moduleGraph module graph
1088
- * @returns {WebpackError[] | undefined} warnings
1161
+ * @returns {WebpackError[] | undefined} errors
1089
1162
  */
1090
- _getCircularReexportWarnings(moduleGraph) {
1163
+ _getCircularReexportErrors(moduleGraph) {
1091
1164
  const ids = this.getIds(moduleGraph);
1092
1165
  if (ids.length === 0 || !this.name) return;
1093
1166
  const parentModule = /** @type {Module} */ (
@@ -1115,10 +1188,18 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
1115
1188
  */
1116
1189
  getErrors(moduleGraph) {
1117
1190
  const exportsPresence = this._getEffectiveExportPresenceLevel(moduleGraph);
1118
- if (exportsPresence === ExportPresenceModes.ERROR) {
1119
- 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;
1120
1199
  }
1121
- return null;
1200
+ const circular = this._getCircularReexportErrors(moduleGraph);
1201
+ if (!circular) return errors;
1202
+ return errors ? [...errors, ...circular] : circular;
1122
1203
  }
1123
1204
 
1124
1205
  /**
@@ -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 {
@@ -393,11 +394,20 @@ class HarmonyImportDependencyParserPlugin {
393
394
  );
394
395
  // Concatenation would turn the binding into a plain local, losing the
395
396
  // getter-only property whose write throws.
396
- parser.hooks.assign.for(harmonySpecifierTag).tap(PLUGIN_NAME, () => {
397
- markUsed(/** @type {HarmonySettings} */ (parser.currentTagData));
397
+ parser.hooks.assign.for(harmonySpecifierTag).tap(PLUGIN_NAME, (expr) => {
398
+ const settings = /** @type {HarmonySettings} */ (parser.currentTagData);
399
+ markUsed(settings);
398
400
  /** @type {JavascriptModuleBuildInfo} */
399
401
  (parser.state.module.buildInfo).moduleConcatenationBailout =
400
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
+ }
401
411
  });
402
412
  parser.hooks.expression
403
413
  .for(harmonySpecifierTag)
@@ -194,7 +194,12 @@ const getEntrypointChunksInLoadOrder = (entrypoint) => {
194
194
  const chunksLoadedByAncestorTags = new Set();
195
195
  /** @type {Set<import("../ChunkGroup")>} */
196
196
  const visitedGroups = new Set();
197
- const walk = (/** @type {import("../ChunkGroup")} */ group) => {
197
+ /**
198
+ * Collects the chunks an ancestor entry's own tags already load.
199
+ * @param {import("../ChunkGroup")} group chunk group to walk up from
200
+ * @returns {void}
201
+ */
202
+ const walk = (group) => {
198
203
  if (visitedGroups.has(group)) return;
199
204
  visitedGroups.add(group);
200
205
  for (const parent of group.parentsIterable) {
@@ -220,7 +225,12 @@ const getEntrypointChunksInLoadOrder = (entrypoint) => {
220
225
  const ordered = [];
221
226
  /** @type {Set<Chunk>} */
222
227
  const seen = new Set();
223
- const push = (/** @type {Chunk | null | undefined} */ chunk) => {
228
+ /**
229
+ * Appends a chunk once, skipping the ones an ancestor entry already loads.
230
+ * @param {Chunk | null | undefined} chunk chunk to append
231
+ * @returns {void}
232
+ */
233
+ const push = (chunk) => {
224
234
  if (!chunk || seen.has(chunk) || chunk === entryChunk) return;
225
235
  if (chunksLoadedByAncestorTags.has(chunk)) return;
226
236
  seen.add(chunk);
@@ -8,11 +8,13 @@
8
8
  const makeSerializable = require("../util/makeSerializable");
9
9
  const ContextDependency = require("./ContextDependency");
10
10
  const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall");
11
+ const importOptionsCheck = require("./importOptionsCheck");
11
12
 
12
13
  /** @import { Range } from "../javascript/JavascriptParser" */
13
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext<[Range]>} ObjectDeserializerContext */
14
- /** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext<[Range]>} ObjectSerializerContext */
14
+ /** @import { ObjectDeserializerContext, ObjectSerializerContext } from "../serialization/ObjectMiddleware" */
15
15
  /** @import { ContextDependencyOptions } from "./ContextDependency" */
16
+ /** @import { ReplaceSource } from "webpack-sources" */
17
+ /** @import { DependencyTemplateContext } from "../DependencyTemplate" */
16
18
 
17
19
  class ImportContextDependency extends ContextDependency {
18
20
  /**
@@ -26,6 +28,8 @@ class ImportContextDependency extends ContextDependency {
26
28
 
27
29
  this.range = range;
28
30
  this.valueRange = valueRange;
31
+ /** @type {Range | undefined} */
32
+ this.optionsRange = undefined;
29
33
  }
30
34
 
31
35
  get type() {
@@ -56,6 +60,7 @@ class ImportContextDependency extends ContextDependency {
56
60
  */
57
61
  serialize(context) {
58
62
  context.write(this.valueRange);
63
+ context.write(this.optionsRange);
59
64
 
60
65
  super.serialize(context);
61
66
  }
@@ -66,6 +71,7 @@ class ImportContextDependency extends ContextDependency {
66
71
  */
67
72
  deserialize(context) {
68
73
  this.valueRange = context.read();
74
+ this.optionsRange = context.read();
69
75
 
70
76
  super.deserialize(context.rest);
71
77
  }
@@ -76,6 +82,40 @@ makeSerializable(
76
82
  "webpack/lib/dependencies/ImportContextDependency"
77
83
  );
78
84
 
79
- ImportContextDependency.Template = ContextDependencyTemplateAsRequireCall;
85
+ ImportContextDependency.Template = class ImportContextDependencyTemplate extends (
86
+ ContextDependencyTemplateAsRequireCall
87
+ ) {
88
+ /**
89
+ * Rewrites the call around its request, dropping anything the original one
90
+ * carried after it. Overridden where a later argument must be kept.
91
+ * @param {ContextDependency} dep the dependency
92
+ * @param {ReplaceSource} source the current replace source which can be modified
93
+ * @param {string} moduleExports the expression the request is passed to
94
+ * @param {DependencyTemplateContext} templateContext the context object
95
+ * @returns {void}
96
+ */
97
+ replaceCall(dep, source, moduleExports, templateContext) {
98
+ const optionsRange = /** @type {ImportContextDependency} */ (dep)
99
+ .optionsRange;
100
+ if (optionsRange === undefined) {
101
+ super.replaceCall(dep, source, moduleExports, templateContext);
102
+ return;
103
+ }
104
+ // Request then options, in that order; their own source is kept so
105
+ // nested dependencies in them still get their replacements.
106
+ const range = /** @type {Range} */ (dep.range);
107
+ const valueRange = /** @type {Range} */ (dep.valueRange);
108
+ const check = importOptionsCheck(
109
+ templateContext.runtimeTemplate,
110
+ ["r", "o"],
111
+ `${moduleExports}(r)`
112
+ );
113
+ source.replace(range[0], valueRange[0] - 1, `(${check})(`);
114
+ // The options range excludes any parentheses the source had, so a comma
115
+ // expression would otherwise split into arguments of its own.
116
+ source.replace(valueRange[1], optionsRange[0] - 1, ", (");
117
+ source.replace(optionsRange[1], range[1] - 1, "))");
118
+ }
119
+ };
80
120
 
81
121
  module.exports = ImportContextDependency;
@@ -6,11 +6,11 @@
6
6
  "use strict";
7
7
 
8
8
  const Dependency = require("../Dependency");
9
- const Template = require("../Template");
10
9
  const makeSerializable = require("../util/makeSerializable");
11
10
  const HarmonyImportGuard = require("./HarmonyImportGuard");
12
11
  const { ImportPhaseUtils } = require("./ImportPhase");
13
12
  const ModuleDependency = require("./ModuleDependency");
13
+ const importOptionsCheck = require("./importOptionsCheck");
14
14
 
15
15
  /** @import { ReplaceSource } from "webpack-sources" */
16
16
  /** @import AsyncDependenciesBlock from "../AsyncDependenciesBlock" */
@@ -226,35 +226,14 @@ ImportDependency.Template = class ImportDependencyTemplate extends (
226
226
  }
227
227
 
228
228
  // A non-static second argument must still be evaluated (for its side
229
- // effects and evaluation order) and validated per spec. Keep it in place
230
- // and wrap it in an inline validator that mirrors the runtime checks of
231
- // the spec's `import(specifier, options)` evaluation.
229
+ // effects and evaluation order) and checked per spec, so keep it in place
230
+ // and wrap it.
232
231
  if (dep.optionsRange) {
233
- const validator = runtimeTemplate.basicFunction("o", [
234
- "try {",
235
- Template.indent([
236
- "if (o !== undefined) {",
237
- Template.indent([
238
- 'if ((typeof o !== "object" && typeof o !== "function") || o === null) throw new TypeError("The second argument to import() must be an object");',
239
- 'var a = o["with"];',
240
- "if (a !== undefined) {",
241
- Template.indent([
242
- 'if ((typeof a !== "object" && typeof a !== "function") || a === null) throw new TypeError("The \'with\' option must be an object");',
243
- "for (var k = Object.keys(a), i = 0; i < k.length; i++) {",
244
- Template.indent([
245
- 'if (typeof a[k[i]] !== "string") throw new TypeError("Import attribute values must be strings");'
246
- ]),
247
- "}"
248
- ]),
249
- "}"
250
- ]),
251
- "}"
252
- ]),
253
- "} catch (e) { return Promise.reject(e); }",
254
- `return ${content};`
255
- ]);
256
- source.replace(dep.range[0], dep.optionsRange[0] - 1, `(${validator})(`);
257
- source.replace(dep.optionsRange[1], dep.range[1] - 1, ")");
232
+ const check = importOptionsCheck(runtimeTemplate, ["o"], content);
233
+ // The options range excludes any parentheses the source had, so a
234
+ // comma expression would otherwise split into arguments of its own.
235
+ source.replace(dep.range[0], dep.optionsRange[0] - 1, `(${check})((`);
236
+ source.replace(dep.optionsRange[1], dep.range[1] - 1, "))");
258
237
  return;
259
238
  }
260
239
 
@@ -209,20 +209,18 @@ const isStaticStringAttributes = (node) => {
209
209
  : key.type === "Literal"
210
210
  ? key.value
211
211
  : undefined;
212
- if (keyName === "with" || keyName === "assert") {
213
- const value = prop.value;
214
- if (value.type !== "ObjectExpression") return false;
215
- for (const attr of value.properties) {
216
- if (attr.type !== "Property" || attr.kind !== "init" || attr.computed) {
217
- return false;
218
- }
219
- const attrValue = attr.value;
220
- if (
221
- attrValue.type !== "Literal" ||
222
- typeof attrValue.value !== "string"
223
- ) {
224
- return false;
225
- }
212
+ // Any other key is an expression webpack cannot account for, and its
213
+ // value still evaluates, so the argument has to stay.
214
+ if (keyName !== "with" && keyName !== "assert") return false;
215
+ const value = prop.value;
216
+ if (value.type !== "ObjectExpression") return false;
217
+ for (const attr of value.properties) {
218
+ if (attr.type !== "Property" || attr.kind !== "init" || attr.computed) {
219
+ return false;
220
+ }
221
+ const attrValue = attr.value;
222
+ if (attrValue.type !== "Literal" || typeof attrValue.value !== "string") {
223
+ return false;
226
224
  }
227
225
  }
228
226
  }
@@ -676,6 +674,18 @@ class ImportParserPlugin {
676
674
  parser
677
675
  );
678
676
  if (!dep) return;
677
+ // The context module ignores the options argument, but the spec
678
+ // evaluates it, so anything but a static attributes object stays.
679
+ const optionsNode = expr.options;
680
+ if (
681
+ optionsNode &&
682
+ optionsNode.range &&
683
+ !isStaticStringAttributes(optionsNode)
684
+ ) {
685
+ /** @type {ImportContextDependency} */ (dep).optionsRange =
686
+ /** @type {Range} */ (optionsNode.range);
687
+ parser.walkExpression(optionsNode);
688
+ }
679
689
  dep.loc = parser.getLocation(expr);
680
690
  dep.optional = Boolean(parser.scope.inTry);
681
691
  parser.state.current.addDependency(dep);