webpack 5.110.0 → 5.110.2

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 (52) hide show
  1. package/README.md +10 -17
  2. package/lib/ExportsInfo.js +10 -0
  3. package/lib/FileSystemInfo.js +17 -2
  4. package/lib/Generator.js +17 -0
  5. package/lib/NormalModule.js +3 -3
  6. package/lib/RuntimeTemplate.js +423 -430
  7. package/lib/asset/AssetBytesGenerator.js +1 -3
  8. package/lib/asset/AssetGenerator.js +1 -3
  9. package/lib/asset/AssetSourceGenerator.js +1 -3
  10. package/lib/config/defaults.js +6 -0
  11. package/lib/css/CssGenerator.js +1 -3
  12. package/lib/css/CssLoadingRuntimeModule.js +32 -13
  13. package/lib/css/CssModulesPlugin.js +9 -17
  14. package/lib/css/data.js +13 -3
  15. package/lib/css/syntax.js +78 -10
  16. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
  17. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
  18. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +48 -3
  19. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +83 -14
  20. package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
  21. package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
  22. package/lib/dependencies/WorkerDependency.js +6 -12
  23. package/lib/dependencies/WorkletDependency.js +7 -11
  24. package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
  25. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
  26. package/lib/html/HtmlGenerator.js +1 -1
  27. package/lib/html/HtmlModulesPlugin.js +55 -16
  28. package/lib/html/syntax.js +113 -26
  29. package/lib/javascript/JavascriptGenerator.js +1 -1
  30. package/lib/javascript/JavascriptModulesPlugin.js +3 -10
  31. package/lib/json/JsonGenerator.js +1 -1
  32. package/lib/loaders/LoaderRunner.js +33 -5
  33. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
  34. package/lib/optimize/ConcatenatedModule.js +8 -0
  35. package/lib/optimize/ConstExportsPlugin.js +5 -0
  36. package/lib/optimize/InlineExports.js +121 -0
  37. package/lib/optimize/RealContentHashPlugin.js +111 -35
  38. package/lib/prefetch/ResourceHintPlugin.js +6 -12
  39. package/lib/serialization/ObjectMiddleware.js +128 -108
  40. package/lib/typescript/TypeScriptPlugin.js +8 -10
  41. package/lib/util/SourceProcessor.js +24 -8
  42. package/lib/util/createMappings.js +25 -3
  43. package/lib/util/removeBOM.js +82 -1
  44. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
  45. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
  46. package/lib/wasm-async/AsyncWebAssemblyJavascriptGenerator.js +3 -1
  47. package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
  48. package/lib/wasm-sync/WebAssemblyJavascriptGenerator.js +3 -1
  49. package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
  50. package/lib/web/FetchCompileWasmPlugin.js +4 -2
  51. package/package.json +1 -1
  52. package/types.d.ts +77 -10
@@ -12,7 +12,7 @@ const {
12
12
  } = require("../ModuleSourceTypeConstants");
13
13
  const GenericSourceProcessor = require("../util/SourceProcessor");
14
14
 
15
- const { deferredWrite } = GenericSourceProcessor;
15
+ const { deferredWrite, declineDeferredWrites } = GenericSourceProcessor;
16
16
 
17
17
  /** @import { CssEnvironment, CssProcessOptions, CssTransformOptions } from "../css/syntax" */
18
18
  /** @import { OutputHtmlOptions } from "../../declarations/WebpackOptions" */
@@ -11508,11 +11508,19 @@ const _settleFosteredRuns = (path) => {
11508
11508
  _fosterHolders = holders;
11509
11509
  };
11510
11510
 
11511
+ // Stands in for what the print chooses the bytes of: a raw-text body it
11512
+ // rewrites whole, or an attribute value it respells.
11513
+ const _DIGEST_OPAQUE = "\u0003";
11514
+
11511
11515
  /**
11512
11516
  * The tree as a string, for the one check the printer cannot make from the tree
11513
- * alone: whether what it printed parses back to what it printed from. Values are
11514
- * compared as the DOM holds them, so the same attribute written with a different
11515
- * quote or escape is the same attribute.
11517
+ * alone: whether what it printed parses back to what it printed from. Read in
11518
+ * the form the print is licensed to leave — comments gone, a run of text
11519
+ * collapsed and joined, the elements and attributes the options drop dropped,
11520
+ * every value the printer respells standing in for itself — so an equal digest
11521
+ * says the print moved nothing, not that it changed nothing. What a repair does
11522
+ * move (a node under another parent, an attribute a bad quote swallowed)
11523
+ * reshapes the tree, and that is what parts the two.
11516
11524
  * @param {HtmlNodeRef} root the node whose children are read
11517
11525
  * @returns {string} a rendering equal for equal trees
11518
11526
  */
@@ -11525,6 +11533,15 @@ const _digest = (root) => {
11525
11533
  const stack = [];
11526
11534
  const first = A.firstChild(root);
11527
11535
  if (first !== 0) stack.push(first, 0);
11536
+ // A run of text siblings reads as one string: dropping the comment or the
11537
+ // empty element between two of them is what joins them in the output.
11538
+ let pendingText = "";
11539
+ let pendingDepth = 0;
11540
+ const flushText = () => {
11541
+ const text = _asciiTrim(pendingText);
11542
+ pendingText = "";
11543
+ if (text !== "") out.push(`${pendingDepth}\u0001t\u0001${text}`);
11544
+ };
11528
11545
  while (stack.length !== 0) {
11529
11546
  const depth = /** @type {number} */ (stack.pop());
11530
11547
  const node = /** @type {HtmlNodeRef} */ (stack.pop());
@@ -11532,27 +11549,87 @@ const _digest = (root) => {
11532
11549
  const next = A.nextSibling(node);
11533
11550
  if (next !== 0) stack.push(next, depth);
11534
11551
  const type = A.type(node);
11535
- if (type !== NodeType.Element) {
11536
- out.push(`${depth}\u0001${type}\u0001${A.data(node)}`);
11552
+ // Not flushed: a comment minifying drops leaves the text around it one run.
11553
+ if (type === NodeType.Comment) continue;
11554
+ if (type === NodeType.Text) {
11555
+ if (depth !== pendingDepth) flushText();
11556
+ pendingDepth = depth;
11557
+ const parent = A.parentOf(node);
11558
+ pendingText +=
11559
+ parent !== 0 && LITERAL_TEXT_PARENTS.has(A.tagName(parent))
11560
+ ? _DIGEST_OPAQUE
11561
+ : collapseWhitespaceRuns(A.data(node));
11537
11562
  continue;
11538
11563
  }
11539
- out.push(
11540
- `${depth}\u0001e\u0001${A.namespace(node)}\u0001${A.tagName(node)}`
11541
- );
11542
- const attributes = [];
11543
- for (const a of A.attributes(node)) {
11544
- // The value as the DOM holds it: the same value can be written with a
11545
- // different quote and a different escape and still be the same attribute.
11546
- attributes.push(
11547
- `${a.serializedName || a.name}=${decodeEntities(a.value, true)}`
11548
- );
11564
+ if (type === NodeType.Element) {
11565
+ // Same as a comment: an element the print drops joins what surrounds it.
11566
+ if (
11567
+ _isRemovableEmptyElement(A, node) ||
11568
+ (_absorbedStyles !== null && _absorbedStyles.has(node))
11569
+ ) {
11570
+ continue;
11571
+ }
11572
+ flushText();
11573
+ const elementName = A.tagName(node);
11574
+ const html = A.namespace(node) === NS_HTML;
11575
+ out.push(`${depth}\u0001e\u0001${A.namespace(node)}\u0001${elementName}`);
11576
+ // Per element, as in `_minifyOpenTag`: every attribute of one tag gets the
11577
+ // same answer out of these tables.
11578
+ const markers =
11579
+ html && _removeRedundantAttributes !== "none"
11580
+ ? REDUNDANT_TYPE_ATTRIBUTES[elementName]
11581
+ : undefined;
11582
+ const defaults =
11583
+ html && _removeRedundantAttributes === "all"
11584
+ ? REDUNDANT_DEFAULT_ATTRIBUTES[elementName]
11585
+ : undefined;
11586
+ const attributes = [];
11587
+ for (const a of A.attributes(node)) {
11588
+ if (
11589
+ html &&
11590
+ (((markers !== undefined || defaults !== undefined) &&
11591
+ _isRedundantAttribute(
11592
+ markers,
11593
+ defaults,
11594
+ elementName,
11595
+ a.name,
11596
+ a.value
11597
+ )) ||
11598
+ _isRemovableEmptyAttribute(elementName, a.name, a.value))
11599
+ ) {
11600
+ continue;
11601
+ }
11602
+ const name = a.serializedName || a.name;
11603
+ if (REWRITABLE_ATTRIBUTES.has(a.name) || a.name === "srcdoc") {
11604
+ attributes.push(`${name}=${_DIGEST_OPAQUE}`);
11605
+ continue;
11606
+ }
11607
+ // The value as the DOM holds it — the same value can be written with a
11608
+ // different quote and a different escape and still be the same
11609
+ // attribute — with a boolean spelled by its own name as the bare
11610
+ // presence the printer writes it back as.
11611
+ attributes.push(
11612
+ `${name}=${
11613
+ html &&
11614
+ a.value.length === a.name.length &&
11615
+ _isBooleanAttribute(elementName, a.name) &&
11616
+ _asciiLowerCase(a.value) === a.name
11617
+ ? ""
11618
+ : decodeEntities(a.value, true)
11619
+ }`
11620
+ );
11621
+ }
11622
+ attributes.sort();
11623
+ out.push(attributes.join("\u0002"));
11624
+ const content = A.templateContent(node);
11625
+ const inner = content !== 0 ? A.firstChild(content) : A.firstChild(node);
11626
+ if (inner !== 0) stack.push(inner, depth + 1);
11627
+ continue;
11549
11628
  }
11550
- attributes.sort();
11551
- out.push(attributes.join("\u0002"));
11552
- const content = A.templateContent(node);
11553
- const inner = content !== 0 ? A.firstChild(content) : A.firstChild(node);
11554
- if (inner !== 0) stack.push(inner, depth + 1);
11629
+ flushText();
11630
+ out.push(`${depth}\u0001${type}\u0001${A.data(node)}`);
11555
11631
  }
11632
+ flushText();
11556
11633
  return out.join("\n");
11557
11634
  };
11558
11635
 
@@ -12549,12 +12626,11 @@ const grammar = (input, visitors, writer, options) => {
12549
12626
  _printDepth = 0;
12550
12627
  try {
12551
12628
  const root = parseHtml(input, 0, options);
12552
- let digest = "";
12553
12629
  if (writer !== undefined) {
12554
12630
  if (_fosteredLog !== null) _settleFosteredRuns(A);
12555
12631
  // A shape no arrangement of tags reaches, or foster parenting — whose repair
12556
12632
  // is checked, not trusted. No real document reaches either.
12557
- if (_printFromSource || _fosteredLog !== null) digest = _digest(root);
12633
+ const verify = _printFromSource || _fosteredLog !== null;
12558
12634
  _walkRun(_nodeParents[root], root, _nodeNextSiblings[root]);
12559
12635
  // A single root: take its text as the output (the printer read every node's
12560
12636
  // source into the store during the walk, so this needs no source). A root
@@ -12562,9 +12638,20 @@ const grammar = (input, visitors, writer, options) => {
12562
12638
  if (_printOpen[0] === 1) writer.dropStore();
12563
12639
  else writer.take(root);
12564
12640
  // Print, then read the output back: the source always parses to its own
12565
- // tree, so it stands in when the printed form does not.
12566
- if (digest !== "") {
12567
- const printed = writer.result();
12641
+ // tree, so it stands in when the printed form does not. Digested after the
12642
+ // walk, which is what settles the elements the print left out.
12643
+ if (verify) {
12644
+ const digest = _digest(root);
12645
+ // A deferred body prints as a marker, which reads back as text where
12646
+ // its node stood — put it back, or the whole document is handed back.
12647
+ const printed =
12648
+ _deferEmbeddedSource === undefined ||
12649
+ _deferEmbeddedSource.length === 0
12650
+ ? writer.result()
12651
+ : declineDeferredWrites(writer.result(), _deferEmbeddedSource);
12652
+ // Both are keyed by node id, which the next parse hands out afresh.
12653
+ _emptyElements.clear();
12654
+ _absorbedStyles = null;
12568
12655
  if (_digest(parseHtml(printed, 0, options)) !== digest) {
12569
12656
  writer.replaceAll(input);
12570
12657
  }
@@ -332,7 +332,7 @@ class JavascriptGenerator extends Generator {
332
332
  * @returns {Source | null} generated code
333
333
  */
334
334
  generateError(error, module, generateContext) {
335
- return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
335
+ return new RawSource(Generator.throwBuildErrorCode(error));
336
336
  }
337
337
  }
338
338
 
@@ -50,7 +50,7 @@ const {
50
50
  const createHash = require("../util/createHash");
51
51
  const createHooksRegistry = require("../util/createHooksRegistry");
52
52
  const { digestNonNumericOnlyWithFull } = require("../util/nonNumericOnlyHash");
53
- const removeBOM = require("../util/removeBOM");
53
+ const { removeBOMFromResult } = require("../util/removeBOM");
54
54
  const { intersectRuntime } = require("../util/runtime");
55
55
  const { getAllChunks } = require("./ChunkHelpers");
56
56
  const JavascriptGenerator = require("./JavascriptGenerator");
@@ -528,15 +528,8 @@ class JavascriptModulesPlugin {
528
528
 
529
529
  NormalModule.getCompilationHooks(compilation).processResult.tap(
530
530
  PLUGIN_NAME,
531
- (result, module) => {
532
- if (module.type === type) {
533
- const [source, ...rest] = result;
534
-
535
- return [removeBOM(source), ...rest];
536
- }
537
-
538
- return result;
539
- }
531
+ (result, module) =>
532
+ module.type === type ? removeBOMFromResult(result) : result
540
533
  );
541
534
  }
542
535
 
@@ -259,7 +259,7 @@ class JsonGenerator extends Generator {
259
259
  * @returns {Source | null} generated code
260
260
  */
261
261
  generateError(error, module, generateContext) {
262
- return new RawSource(`throw new Error(${JSON.stringify(error.message)});`);
262
+ return new RawSource(Generator.throwBuildErrorCode(error));
263
263
  }
264
264
 
265
265
  /**
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { readFile } = require("fs");
9
9
  const { parseResource } = require("../util/identifier");
10
+ const { adjustSourceMapForRemovedBOM } = require("../util/removeBOM");
10
11
  const loadLoader = require("./loadLoader");
11
12
 
12
13
  // Set on a loader context to have each loader's own run measured. Absent
@@ -59,6 +60,8 @@ const HASH_ESCAPE_REGEXP = /#/g;
59
60
  const UTF8_BOM_0 = 0xef;
60
61
  const UTF8_BOM_1 = 0xbb;
61
62
  const UTF8_BOM_2 = 0xbf;
63
+ // U+FEFF, the BOM once decoded
64
+ const BOM_CHAR_CODE = 0xfeff;
62
65
 
63
66
  /**
64
67
  * @param {Buffer} buf buffer
@@ -76,6 +79,14 @@ function utf8BufferToString(buf) {
76
79
  return buf.toString("utf8");
77
80
  }
78
81
 
82
+ /**
83
+ * @param {string} str string
84
+ * @returns {string} string, with a leading BOM removed
85
+ */
86
+ function stringWithoutBOM(str) {
87
+ return str.charCodeAt(0) === BOM_CHAR_CODE ? str.slice(1) : str;
88
+ }
89
+
79
90
  /**
80
91
  * Escape `#` with a preceding `\0` byte; short-circuits when there is no `#`.
81
92
  * @param {string} str input string
@@ -292,9 +303,19 @@ function runSyncOrAsync(fn, context, args, callback) {
292
303
  /**
293
304
  * @param {EXPECTED_ANY[]} args arguments
294
305
  * @param {boolean | null=} raw whether the loader wants a Buffer
306
+ * @param {boolean=} fromLoader whether args came from a loader rather than the resource
295
307
  * @returns {void}
296
308
  */
297
- function convertArgs(args, raw) {
309
+ function convertArgs(args, raw, fromLoader) {
310
+ // A BOM only means anything at the start of a file, so one a loader produced
311
+ // is content the next loader must not see. The resource keeps its own.
312
+ if (fromLoader && typeof args[0] === "string") {
313
+ const withoutBOM = stringWithoutBOM(args[0]);
314
+ if (withoutBOM !== args[0]) {
315
+ args[0] = withoutBOM;
316
+ if (args[1]) args[1] = adjustSourceMapForRemovedBOM(args[1]);
317
+ }
318
+ }
298
319
  if (!raw && Buffer.isBuffer(args[0])) {
299
320
  args[0] = utf8BufferToString(args[0]);
300
321
  } else if (raw && typeof args[0] === "string") {
@@ -307,9 +328,16 @@ function convertArgs(args, raw) {
307
328
  * @param {LoaderContext} loaderContext the loader context
308
329
  * @param {EXPECTED_ANY[]} args arguments
309
330
  * @param {(err: Error | null, args?: EXPECTED_ANY[]) => void} callback callback
331
+ * @param {boolean=} fromLoader whether args came from a loader rather than the resource
310
332
  * @returns {void}
311
333
  */
312
- function iterateNormalLoaders(options, loaderContext, args, callback) {
334
+ function iterateNormalLoaders(
335
+ options,
336
+ loaderContext,
337
+ args,
338
+ callback,
339
+ fromLoader
340
+ ) {
313
341
  while (loaderContext.loaderIndex >= 0) {
314
342
  const currentLoaderObject =
315
343
  loaderContext.loaders[loaderContext.loaderIndex];
@@ -327,11 +355,11 @@ function iterateNormalLoaders(options, loaderContext, args, callback) {
327
355
  continue;
328
356
  }
329
357
 
330
- convertArgs(args, currentLoaderObject.raw);
358
+ convertArgs(args, currentLoaderObject.raw, fromLoader);
331
359
 
332
360
  return runSyncOrAsync(fn, loaderContext, args, (err, ...nextArgs) => {
333
361
  if (err) return callback(err);
334
- iterateNormalLoaders(options, loaderContext, nextArgs, callback);
362
+ iterateNormalLoaders(options, loaderContext, nextArgs, callback, true);
335
363
  });
336
364
  }
337
365
 
@@ -410,7 +438,7 @@ function iteratePitchingLoaders(options, loaderContext, callback) {
410
438
  }
411
439
  if (hasArg) {
412
440
  loaderContext.loaderIndex--;
413
- iterateNormalLoaders(options, loaderContext, args, callback);
441
+ iterateNormalLoaders(options, loaderContext, args, callback, true);
414
442
  } else {
415
443
  iteratePitchingLoaders(options, loaderContext, callback);
416
444
  }
@@ -62,27 +62,20 @@ class ReadFileCompileAsyncWasmPlugin {
62
62
  // settled while the plugin is being applied.
63
63
  /**
64
64
  * @param {string} path rendered path to the wasm file
65
- * @param {RuntimeSpec} runtime the runtime this loader serves
65
+ * @param {boolean} analyzable whether the call site bakes a complete URL
66
66
  * @returns {string} the argument to hand the reader
67
67
  */
68
- const wasmUrlArg = (path, runtime) =>
69
- compilation.runtimeTemplate.supportsAnalyzable(
70
- "wasm",
71
- undefined,
72
- undefined,
73
- runtime
74
- )
75
- ? path
76
- : compilation.runtimeTemplate.importMetaUrl(path);
68
+ const wasmUrlArg = (path, analyzable) =>
69
+ analyzable ? path : compilation.runtimeTemplate.importMetaUrl(path);
77
70
  /**
78
- * @type {(path: string, runtime: RuntimeSpec) => string} callback to generate code to load the wasm file
71
+ * @type {(path: string, runtime: RuntimeSpec, analyzable: boolean) => string} callback to generate code to load the wasm file
79
72
  */
80
73
  const generateLoadBinaryCode = this._import
81
- ? (path, runtime) =>
74
+ ? (path, runtime, analyzable) =>
82
75
  Template.asString([
83
76
  "Promise.all([import('fs'), import('url')]).then(([{ readFile }, { URL }]) => new Promise((resolve, reject) => {",
84
77
  Template.indent([
85
- `readFile(${wasmUrlArg(path, runtime)}, (err, buffer) => {`,
78
+ `readFile(${wasmUrlArg(path, analyzable)}, (err, buffer) => {`,
86
79
  Template.indent([
87
80
  "if (err) return reject(err);",
88
81
  "",
@@ -154,7 +147,7 @@ class ReadFileCompileAsyncWasmPlugin {
154
147
  if (
155
148
  !compilation.runtimeTemplate.supportsAnalyzable(
156
149
  "wasm",
157
- undefined,
150
+ chunkGraph,
158
151
  undefined,
159
152
  chunk.runtime
160
153
  ) &&
@@ -196,7 +189,7 @@ class ReadFileCompileAsyncWasmPlugin {
196
189
  if (
197
190
  !compilation.runtimeTemplate.supportsAnalyzable(
198
191
  "wasm",
199
- undefined,
192
+ chunkGraph,
200
193
  undefined,
201
194
  chunk.runtime
202
195
  ) &&
@@ -1415,10 +1415,18 @@ class ConcatenatedModule extends Module {
1415
1415
  runtimeCondition,
1416
1416
  runtime
1417
1417
  );
1418
+ const wasDeferredOnly = !entry.nonDeferAccess;
1418
1419
  entry.nonDeferAccess = mergeNonDeferAccess(
1419
1420
  entry.nonDeferAccess,
1420
1421
  nonDeferAccess
1421
1422
  );
1423
+ // A deferred import does not evaluate, so the first eager reference
1424
+ // fixes the position; insertion order is emit order.
1425
+ if (wasDeferredOnly && nonDeferAccess) {
1426
+ entry.connection = connection;
1427
+ referencesMap.delete(module);
1428
+ referencesMap.set(module, entry);
1429
+ }
1422
1430
  }
1423
1431
  if (targets !== undefined) importConnections.set(module, targets);
1424
1432
  return referencesMap.values();
@@ -15,6 +15,7 @@ const { VariableInfoFlags } = require("../javascript/JavascriptParser");
15
15
  const {
16
16
  InlinedUsedName,
17
17
  enableInlineExports,
18
+ findModulesInCycles,
18
19
  toInlinedValue
19
20
  } = require("./InlineExports");
20
21
 
@@ -162,11 +163,15 @@ class ConstExportsPlugin {
162
163
 
163
164
  const moduleGraph = compilation.moduleGraph;
164
165
  compilation.hooks.optimizeDependencies.tap(PLUGIN_NAME, (modules) => {
166
+ // A cycle peer may read an export during its temporal dead zone,
167
+ // where the binding must throw and a literal would answer.
168
+ const inCycle = findModulesInCycles(moduleGraph, modules);
165
169
  /** @type {Set<ExportsInfo>} */
166
170
  const visited = new Set();
167
171
  /** @type {ExportsInfo[]} */
168
172
  let queue = [];
169
173
  for (const module of modules) {
174
+ if (inCycle.has(module)) continue;
170
175
  queue.push(moduleGraph.getExportsInfo(module));
171
176
  }
172
177
  while (queue.length > 0) {
@@ -172,10 +172,131 @@ const isExportInlined = (moduleGraph, module, ids, runtime) => {
172
172
  return moduleGraph.getExportsInfo(module).hasInlinedUsedName(ids, runtime);
173
173
  };
174
174
 
175
+ /**
176
+ * Collects, by an iterative Tarjan pass, the modules a cycle peer may read in
177
+ * their dead zone. A cycle of bare side-effect imports still inlines.
178
+ * @param {ModuleGraph} moduleGraph module graph
179
+ * @param {Iterable<Module>} modules all modules of the compilation
180
+ * @returns {Set<Module>} modules whose exports must not be inlined
181
+ */
182
+ const findModulesInCycles = (moduleGraph, modules) => {
183
+ const HarmonyImportDependency = require("../dependencies/HarmonyImportDependency");
184
+ const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency");
185
+
186
+ /** @type {Set<Module>} */
187
+ const inCycle = new Set();
188
+ /** @type {[Module, Module][]} */
189
+ const bindingReads = [];
190
+ /** @type {Map<Module, number>} */
191
+ const componentOf = new Map();
192
+ let nextComponent = 0;
193
+ /** @type {Map<Module, number>} */
194
+ const index = new Map();
195
+ /** @type {Map<Module, number>} */
196
+ const low = new Map();
197
+ /** @type {Set<Module>} */
198
+ const onStack = new Set();
199
+ /** @type {Module[]} */
200
+ const componentStack = [];
201
+ let counter = 0;
202
+ /**
203
+ * @param {Module} module the module
204
+ * @returns {Module[]} modules it imports, self included when self-importing
205
+ */
206
+ const targetsOf = (module) => {
207
+ const connections = moduleGraph.getOutgoingConnectionsByModule(module);
208
+ if (!connections) return [];
209
+ /** @type {Module[]} */
210
+ const targets = [];
211
+ for (const [target, moduleConnections] of connections) {
212
+ if (
213
+ !target ||
214
+ !moduleConnections.some(
215
+ (c) => c.dependency instanceof HarmonyImportDependency
216
+ )
217
+ ) {
218
+ continue;
219
+ }
220
+ // Anything but a bare side-effect import may observe the binding.
221
+ if (
222
+ moduleConnections.some(
223
+ (c) =>
224
+ c.dependency instanceof HarmonyImportDependency &&
225
+ !(c.dependency instanceof HarmonyImportSideEffectDependency)
226
+ )
227
+ ) {
228
+ bindingReads.push([module, target]);
229
+ }
230
+ targets.push(target);
231
+ }
232
+ return targets;
233
+ };
234
+ /** @type {{ node: Module, targets: Module[], i: number }[]} */
235
+ const work = [];
236
+ /**
237
+ * @param {Module} node node to open
238
+ */
239
+ const open = (node) => {
240
+ index.set(node, counter);
241
+ low.set(node, counter);
242
+ counter++;
243
+ componentStack.push(node);
244
+ onStack.add(node);
245
+ work.push({ node, targets: targetsOf(node), i: 0 });
246
+ };
247
+ for (const seed of modules) {
248
+ if (index.has(seed)) continue;
249
+ open(seed);
250
+ while (work.length > 0) {
251
+ const frame = work[work.length - 1];
252
+ if (frame.i < frame.targets.length) {
253
+ const next = frame.targets[frame.i++];
254
+ if (!index.has(next)) {
255
+ open(next);
256
+ } else if (onStack.has(next)) {
257
+ const nodeLow = /** @type {number} */ (low.get(frame.node));
258
+ const nextIndex = /** @type {number} */ (index.get(next));
259
+ if (nextIndex < nodeLow) low.set(frame.node, nextIndex);
260
+ }
261
+ continue;
262
+ }
263
+ work.pop();
264
+ const node = frame.node;
265
+ const nodeLow = /** @type {number} */ (low.get(node));
266
+ if (work.length > 0) {
267
+ const parent = work[work.length - 1].node;
268
+ if (nodeLow < /** @type {number} */ (low.get(parent))) {
269
+ low.set(parent, nodeLow);
270
+ }
271
+ }
272
+ if (nodeLow === index.get(node)) {
273
+ const id = nextComponent++;
274
+ let member;
275
+ do {
276
+ member = /** @type {Module} */ (componentStack.pop());
277
+ onStack.delete(member);
278
+ componentOf.set(member, id);
279
+ } while (member !== node);
280
+ }
281
+ }
282
+ }
283
+ // Reader and target evaluate together exactly when they share a component.
284
+ for (const [origin, target] of bindingReads) {
285
+ if (
286
+ origin === target ||
287
+ componentOf.get(origin) === componentOf.get(target)
288
+ ) {
289
+ inCycle.add(target);
290
+ }
291
+ }
292
+ return inCycle;
293
+ };
294
+
175
295
  module.exports.InlinedUsedName = InlinedUsedName;
176
296
  module.exports.InlinedValue = InlinedValue;
177
297
  module.exports.MAX_INLINE_BYTES = MAX_INLINE_BYTES;
178
298
  module.exports.enableInlineExports = enableInlineExports;
299
+ module.exports.findModulesInCycles = findModulesInCycles;
179
300
  module.exports.isExportInlined = isExportInlined;
180
301
  module.exports.isInlineEnabled = isInlineEnabled;
181
302
  module.exports.isInlineExportsEnabled = isInlineExportsEnabled;