webpack 5.110.1 → 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.
- package/README.md +10 -17
- package/lib/ExportsInfo.js +10 -0
- package/lib/RuntimeTemplate.js +423 -430
- package/lib/css/CssLoadingRuntimeModule.js +32 -13
- package/lib/css/CssModulesPlugin.js +9 -17
- package/lib/css/data.js +13 -3
- package/lib/css/syntax.js +78 -10
- package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +10 -4
- package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +2 -0
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +48 -3
- package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +83 -14
- package/lib/dependencies/HarmonyImportSideEffectDependency.js +114 -0
- package/lib/dependencies/WorkerAndWorkletPlugin.js +1 -0
- package/lib/dependencies/WorkerDependency.js +6 -12
- package/lib/dependencies/WorkletDependency.js +7 -11
- package/lib/esm/ModuleChunkLoadingPlugin.js +6 -9
- package/lib/esm/ModuleChunkLoadingRuntimeModule.js +7 -1
- package/lib/html/HtmlModulesPlugin.js +55 -16
- package/lib/html/syntax.js +113 -26
- package/lib/javascript/JavascriptModulesPlugin.js +3 -10
- package/lib/loaders/LoaderRunner.js +33 -5
- package/lib/node/ReadFileCompileAsyncWasmPlugin.js +8 -15
- package/lib/optimize/ConcatenatedModule.js +8 -0
- package/lib/optimize/ConstExportsPlugin.js +5 -0
- package/lib/optimize/InlineExports.js +121 -0
- package/lib/optimize/RealContentHashPlugin.js +111 -35
- package/lib/prefetch/ResourceHintPlugin.js +6 -12
- package/lib/typescript/TypeScriptPlugin.js +8 -10
- package/lib/util/SourceProcessor.js +24 -8
- package/lib/util/createMappings.js +25 -3
- package/lib/util/removeBOM.js +82 -1
- package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +5 -3
- package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +5 -3
- package/lib/wasm-async/UniversalCompileAsyncWasmPlugin.js +7 -12
- package/lib/web/FetchCompileAsyncWasmPlugin.js +10 -6
- package/lib/web/FetchCompileWasmPlugin.js +4 -2
- package/package.json +1 -1
- package/types.d.ts +69 -10
package/lib/html/syntax.js
CHANGED
|
@@ -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.
|
|
11514
|
-
*
|
|
11515
|
-
*
|
|
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
|
-
|
|
11536
|
-
|
|
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
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
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
|
-
|
|
11551
|
-
out.push(
|
|
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
|
-
|
|
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
|
-
|
|
12567
|
-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
|
@@ -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(
|
|
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 {
|
|
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,
|
|
69
|
-
compilation.runtimeTemplate.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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;
|