webpack 5.107.1 → 5.107.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/lib/Compiler.js +27 -3
- package/lib/ContextModuleFactory.js +45 -38
- package/lib/MultiCompiler.js +14 -0
- package/lib/NormalModule.js +459 -41
- package/lib/ProgressPlugin.js +1 -1
- package/lib/SourceMapDevToolPlugin.js +102 -25
- package/lib/buildChunkGraph.js +24 -2
- package/lib/cache/getLazyHashedEtag.js +9 -2
- package/lib/dependencies/HarmonyExportInitFragment.js +8 -9
- package/lib/dependencies/HtmlInlineScriptDependency.js +3 -14
- package/lib/dependencies/HtmlInlineStyleDependency.js +17 -0
- package/lib/dependencies/HtmlScriptSrcDependency.js +6 -45
- package/lib/dependencies/HtmlSourceDependency.js +18 -0
- package/lib/dependencies/WorkerPlugin.js +18 -4
- package/lib/hmr/LazyCompilationPlugin.js +104 -0
- package/lib/html/HtmlGenerator.js +81 -33
- package/lib/html/HtmlModulesPlugin.js +86 -23
- package/lib/javascript/JavascriptParser.js +1 -1
- package/lib/library/ModuleLibraryPlugin.js +30 -24
- package/lib/node/NodeWatchFileSystem.js +37 -22
- package/lib/optimize/ConcatenatedModule.js +3 -2
- package/lib/util/fs.js +6 -1
- package/package.json +3 -3
- package/types.d.ts +117 -5
|
@@ -36,12 +36,12 @@ const { makePathsAbsolute } = require("./util/identifier");
|
|
|
36
36
|
/**
|
|
37
37
|
* Defines the source map task type used by this module.
|
|
38
38
|
* @typedef {object} SourceMapTask
|
|
39
|
-
* @property {Source} asset
|
|
40
39
|
* @property {AssetInfo} assetInfo
|
|
41
40
|
* @property {(string | Module)[]} modules
|
|
42
41
|
* @property {string} source
|
|
43
42
|
* @property {string} file
|
|
44
43
|
* @property {RawSourceMap} sourceMap
|
|
44
|
+
* @property {Source} mapSource the Source instance whose `sourceAndMap` we called (the current asset or, when its map was already stripped, the pinned original from `originalSources`) — what `clearCache` should target
|
|
45
45
|
* @property {ItemCacheFacade} cacheItem cache item
|
|
46
46
|
*/
|
|
47
47
|
|
|
@@ -111,12 +111,14 @@ const getOriginalSourceRegistry = (compilation) => {
|
|
|
111
111
|
* The returned source is read from the asset as it currently stands — that way
|
|
112
112
|
* any `sourceMappingURL` comments appended by earlier plugin instances survive
|
|
113
113
|
* — while the map is taken from the pinned original Source when the current
|
|
114
|
-
* one no longer carries it.
|
|
114
|
+
* one no longer carries it. `mapSource` identifies which Source instance was
|
|
115
|
+
* actually queried for the map (the current asset, or the pinned original);
|
|
116
|
+
* that's the one whose internal caches the caller should release.
|
|
115
117
|
* @param {string} file file name
|
|
116
118
|
* @param {Source} asset source object as currently held by the compilation
|
|
117
119
|
* @param {MapOptions} options map extraction options
|
|
118
120
|
* @param {Map<string, Source>} registry compilation-scoped original-source registry
|
|
119
|
-
* @returns {{ source: string, sourceMap: RawSourceMap } | undefined} extracted pair or `undefined` when no map is recoverable
|
|
121
|
+
* @returns {{ source: string, sourceMap: RawSourceMap, mapSource: Source } | undefined} extracted pair or `undefined` when no map is recoverable
|
|
120
122
|
*/
|
|
121
123
|
const extractSourceAndMap = (file, asset, options, registry) => {
|
|
122
124
|
/** @type {string | Buffer} */
|
|
@@ -139,20 +141,20 @@ const extractSourceAndMap = (file, asset, options, registry) => {
|
|
|
139
141
|
// that a later plugin instance (which will see a rewrapped asset
|
|
140
142
|
// without a map) can recover it on demand.
|
|
141
143
|
if (!registry.has(file)) registry.set(file, asset);
|
|
142
|
-
|
|
143
|
-
// The current asset (typically a `RawSource` left by an earlier
|
|
144
|
-
// SourceMapDevToolPlugin instance) has no internal map. Re-extract
|
|
145
|
-
// the map from the original Source we pinned earlier. We keep using
|
|
146
|
-
// `source` from the current asset so that any prior wrappers (e.g.
|
|
147
|
-
// appended sourceMappingURL comments) are preserved.
|
|
148
|
-
const original = registry.get(file);
|
|
149
|
-
if (!original) return;
|
|
150
|
-
sourceMap = original.sourceAndMap
|
|
151
|
-
? original.sourceAndMap(options).map
|
|
152
|
-
: original.map(options);
|
|
153
|
-
if (!sourceMap) return;
|
|
144
|
+
return { source, sourceMap, mapSource: asset };
|
|
154
145
|
}
|
|
155
|
-
|
|
146
|
+
// The current asset (typically a `RawSource` left by an earlier
|
|
147
|
+
// SourceMapDevToolPlugin instance) has no internal map. Re-extract
|
|
148
|
+
// the map from the original Source we pinned earlier. We keep using
|
|
149
|
+
// `source` from the current asset so that any prior wrappers (e.g.
|
|
150
|
+
// appended sourceMappingURL comments) are preserved.
|
|
151
|
+
const original = registry.get(file);
|
|
152
|
+
if (!original) return;
|
|
153
|
+
sourceMap = original.sourceAndMap
|
|
154
|
+
? original.sourceAndMap(options).map
|
|
155
|
+
: original.map(options);
|
|
156
|
+
if (!sourceMap) return;
|
|
157
|
+
return { source, sourceMap, mapSource: original };
|
|
156
158
|
};
|
|
157
159
|
|
|
158
160
|
/**
|
|
@@ -177,7 +179,7 @@ const getTaskForFile = (
|
|
|
177
179
|
) => {
|
|
178
180
|
const extracted = extractSourceAndMap(file, asset, options, registry);
|
|
179
181
|
if (!extracted) return;
|
|
180
|
-
const { source, sourceMap } = extracted;
|
|
182
|
+
const { source, sourceMap, mapSource } = extracted;
|
|
181
183
|
const context = compilation.options.context;
|
|
182
184
|
const root = compilation.compiler.root;
|
|
183
185
|
const cachedAbsolutify = makePathsAbsolute.bindContextCache(context, root);
|
|
@@ -190,10 +192,10 @@ const getTaskForFile = (
|
|
|
190
192
|
|
|
191
193
|
return {
|
|
192
194
|
file,
|
|
193
|
-
asset,
|
|
194
195
|
source: /** @type {string} */ (source),
|
|
195
196
|
assetInfo,
|
|
196
197
|
sourceMap,
|
|
198
|
+
mapSource,
|
|
197
199
|
modules,
|
|
198
200
|
cacheItem
|
|
199
201
|
};
|
|
@@ -360,6 +362,25 @@ class SourceMapDevToolPlugin {
|
|
|
360
362
|
const tasks = [];
|
|
361
363
|
let fileIndex = 0;
|
|
362
364
|
|
|
365
|
+
// Shared deduplication set for `Source#clearCache` calls below.
|
|
366
|
+
// Webpack chunks routinely share module-level `CachedSource`
|
|
367
|
+
// instances. A per-call WeakSet would re-walk those shared
|
|
368
|
+
// subtrees once per chunk — 50 chunks × thousands of shared
|
|
369
|
+
// modules in dev/non-minified setups — and worse, every
|
|
370
|
+
// chunk's `sourceAndMap` would have to recompute the cleared
|
|
371
|
+
// caches, churning allocations (measured: +700 MB peak RSS,
|
|
372
|
+
// +6 s wall time on a 50×1000 synthetic build).
|
|
373
|
+
//
|
|
374
|
+
// Sharing one set lets each shared subtree be walked exactly
|
|
375
|
+
// once. The trade-off is that subsequent chunks' `sourceAndMap`
|
|
376
|
+
// calls can repopulate a shared module's `_cachedMaps` after
|
|
377
|
+
// its own clear was skipped (because the module is already in
|
|
378
|
+
// the visited set), leaving at most one populated cache entry
|
|
379
|
+
// per shared module at the end of the run — bounded to a few
|
|
380
|
+
// MB even at the scale of #20961. That's strictly preferable
|
|
381
|
+
// to the alternative's hundreds of MB of transient peak RSS.
|
|
382
|
+
const clearCacheVisited = new WeakSet();
|
|
383
|
+
|
|
363
384
|
asyncLib.each(
|
|
364
385
|
files,
|
|
365
386
|
(file, callback) => {
|
|
@@ -458,6 +479,44 @@ class SourceMapDevToolPlugin {
|
|
|
458
479
|
originalSources
|
|
459
480
|
);
|
|
460
481
|
|
|
482
|
+
// Release the per-instance caches that `sourceAndMap`
|
|
483
|
+
// just populated. The composed map (and, for
|
|
484
|
+
// `SourceMapSource`, the parsed `_sourceMapAsObject` /
|
|
485
|
+
// `_innerSourceMapAsObject`) otherwise sit on the
|
|
486
|
+
// CachedSource — and every shared child — until phase
|
|
487
|
+
// 2 replaces the asset, which is what causes the OOM
|
|
488
|
+
// spike on builds with thousands of chunks
|
|
489
|
+
// (webpack#20961). Keep `source` since downstream
|
|
490
|
+
// consumers reading the original asset still need it;
|
|
491
|
+
// `hash`/`size` default to retained because they're
|
|
492
|
+
// cheap to keep but expensive to rebuild.
|
|
493
|
+
// `clearCacheVisited` is shared across every call (see
|
|
494
|
+
// its declaration above for the rationale).
|
|
495
|
+
//
|
|
496
|
+
// Target `task.mapSource` (not `asset.source`): when
|
|
497
|
+
// `extractSourceAndMap` falls back to the pinned
|
|
498
|
+
// original (the current asset is a `RawSource` left
|
|
499
|
+
// by an earlier plugin instance), the `sourceAndMap`
|
|
500
|
+
// call populated the original's caches, not the
|
|
501
|
+
// current asset's.
|
|
502
|
+
//
|
|
503
|
+
// Feature-detected: `clearCache` landed in
|
|
504
|
+
// `webpack-sources` 3.5, but `compilation.assets` may
|
|
505
|
+
// hold `Source`-like instances from a third-party
|
|
506
|
+
// plugin built against an older copy of
|
|
507
|
+
// `webpack-sources` (or a hand-rolled implementation).
|
|
508
|
+
// Calling it unconditionally would throw on those.
|
|
509
|
+
if (task && typeof task.mapSource.clearCache === "function") {
|
|
510
|
+
task.mapSource.clearCache(
|
|
511
|
+
{
|
|
512
|
+
maps: true,
|
|
513
|
+
source: false,
|
|
514
|
+
parsedMap: true
|
|
515
|
+
},
|
|
516
|
+
clearCacheVisited
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
461
520
|
if (task) {
|
|
462
521
|
const modules = task.modules;
|
|
463
522
|
|
|
@@ -698,12 +757,23 @@ class SourceMapDevToolPlugin {
|
|
|
698
757
|
outputSourceMap.debugId = debugIdValue;
|
|
699
758
|
}
|
|
700
759
|
|
|
701
|
-
const sourceMapString = JSON.stringify(outputSourceMap);
|
|
702
760
|
if (sourceMapFilename) {
|
|
761
|
+
// External `.map` file: hold the serialized map as a
|
|
762
|
+
// `Buffer` instead of a V8 string. `RawSource` accepts
|
|
763
|
+
// a buffer directly, and the emitted asset stays in
|
|
764
|
+
// `compilation.assets` until the build finishes — so
|
|
765
|
+
// storing the bytes off the V8 heap (where Buffers
|
|
766
|
+
// live, accounted as `external` memory) avoids keeping
|
|
767
|
+
// a large V8 string alive for the rest of the build
|
|
768
|
+
// and reduces heap pressure on `--max-old-space-size`.
|
|
769
|
+
const sourceMapBuffer = Buffer.from(
|
|
770
|
+
JSON.stringify(outputSourceMap),
|
|
771
|
+
"utf8"
|
|
772
|
+
);
|
|
703
773
|
const filename = file;
|
|
704
774
|
const sourceMapContentHash = usesContentHash
|
|
705
775
|
? createHash(compilation.outputOptions.hashFunction)
|
|
706
|
-
.update(
|
|
776
|
+
.update(sourceMapBuffer)
|
|
707
777
|
.digest("hex")
|
|
708
778
|
: undefined;
|
|
709
779
|
|
|
@@ -775,7 +845,7 @@ class SourceMapDevToolPlugin {
|
|
|
775
845
|
assetsInfo[file] = assetInfo;
|
|
776
846
|
compilation.updateAsset(file, asset, assetInfo);
|
|
777
847
|
// Add source map file to compilation assets and chunk files
|
|
778
|
-
const sourceMapAsset = new RawSource(
|
|
848
|
+
const sourceMapAsset = new RawSource(sourceMapBuffer);
|
|
779
849
|
const sourceMapAssetInfo = {
|
|
780
850
|
...sourceMapInfo,
|
|
781
851
|
development: true
|
|
@@ -801,6 +871,16 @@ class SourceMapDevToolPlugin {
|
|
|
801
871
|
`${PLUGIN_NAME}: append can't be a function when no filename is provided`
|
|
802
872
|
);
|
|
803
873
|
}
|
|
874
|
+
// Inline data-URL form: `[map]` gets the raw JSON, `[url]`
|
|
875
|
+
// gets the same JSON base64-encoded. `URL_COMMENT_REGEXP`
|
|
876
|
+
// is a `/g` regex, so a user `append` template with more
|
|
877
|
+
// than one `[url]` placeholder would otherwise re-encode
|
|
878
|
+
// the same JSON per match. Pre-compute both once.
|
|
879
|
+
const sourceMapString = JSON.stringify(outputSourceMap);
|
|
880
|
+
const sourceMapBase64 = Buffer.from(
|
|
881
|
+
sourceMapString,
|
|
882
|
+
"utf8"
|
|
883
|
+
).toString("base64");
|
|
804
884
|
/**
|
|
805
885
|
* Add source map as data url to asset
|
|
806
886
|
*/
|
|
@@ -811,10 +891,7 @@ class SourceMapDevToolPlugin {
|
|
|
811
891
|
.replace(
|
|
812
892
|
URL_COMMENT_REGEXP,
|
|
813
893
|
() =>
|
|
814
|
-
`data:application/json;charset=utf-8;base64,${
|
|
815
|
-
sourceMapString,
|
|
816
|
-
"utf8"
|
|
817
|
-
).toString("base64")}`
|
|
894
|
+
`data:application/json;charset=utf-8;base64,${sourceMapBase64}`
|
|
818
895
|
)
|
|
819
896
|
);
|
|
820
897
|
assets[file] = asset;
|
package/lib/buildChunkGraph.js
CHANGED
|
@@ -16,6 +16,7 @@ const { getEntryRuntime, mergeRuntime } = require("./util/runtime");
|
|
|
16
16
|
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
|
|
17
17
|
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
|
|
18
18
|
/** @typedef {import("./Entrypoint")} Entrypoint */
|
|
19
|
+
/** @typedef {import("./Entrypoint").EntryOptions} EntryOptions */
|
|
19
20
|
/** @typedef {import("./Module")} Module */
|
|
20
21
|
/** @typedef {import("./ModuleGraph")} ModuleGraph */
|
|
21
22
|
/** @typedef {import("./ModuleGraphConnection").ConnectionState} ConnectionState */
|
|
@@ -581,7 +582,27 @@ const visitModules = (
|
|
|
581
582
|
}
|
|
582
583
|
} else {
|
|
583
584
|
entrypoint = /** @type {Entrypoint} */ (cgi.chunkGroup);
|
|
584
|
-
//
|
|
585
|
+
// Fill in options the existing entrypoint hasn't set yet. We never
|
|
586
|
+
// overwrite: blocks that dedupe to one entrypoint (e.g. several
|
|
587
|
+
// workers pointing at the same module) legitimately carry distinct
|
|
588
|
+
// values such as `runtime`, so the first block to create the
|
|
589
|
+
// entrypoint wins and later ones only contribute missing keys.
|
|
590
|
+
// `name` is excluded: it is the entrypoint's identity, fixed at
|
|
591
|
+
// creation (and used to key namedChunkGroups), so back-filling it
|
|
592
|
+
// from a block that deduped in via its module would leave
|
|
593
|
+
// `entrypoint.name` out of sync and make module codegen
|
|
594
|
+
// order-dependent, which breaks persistent caching.
|
|
595
|
+
const existingOptions = entrypoint.options;
|
|
596
|
+
for (const key_ of Object.keys(entryOptions)) {
|
|
597
|
+
const key =
|
|
598
|
+
/** @type {keyof EntryOptions} */
|
|
599
|
+
(key_);
|
|
600
|
+
if (key === "name") continue;
|
|
601
|
+
if (entryOptions[key] === undefined) continue;
|
|
602
|
+
if (existingOptions[key] !== undefined) continue;
|
|
603
|
+
/** @type {EntryOptions[keyof EntryOptions]} */
|
|
604
|
+
(existingOptions[key]) = entryOptions[key];
|
|
605
|
+
}
|
|
585
606
|
entrypoint.addOrigin(
|
|
586
607
|
module,
|
|
587
608
|
/** @type {DependencyLocation} */ (b.loc),
|
|
@@ -1290,7 +1311,8 @@ const connectChunkGroups = (
|
|
|
1290
1311
|
// connections and modules can only create one version
|
|
1291
1312
|
// TODO maybe decide this per runtime
|
|
1292
1313
|
if (
|
|
1293
|
-
//
|
|
1314
|
+
// Blocks with nested blocks must stay connected — skipping orphans the
|
|
1315
|
+
// nested block's chunk group from this block's chunk group parent.
|
|
1294
1316
|
!blocksWithNestedBlocks.has(block) &&
|
|
1295
1317
|
connections.every(({ chunkGroup, originChunkGroupInfo }) =>
|
|
1296
1318
|
areModulesAvailable(
|
|
@@ -25,7 +25,7 @@ class LazyHashedEtag {
|
|
|
25
25
|
* @param {HashFunction} hashFunction the hash function to use
|
|
26
26
|
*/
|
|
27
27
|
constructor(obj, hashFunction = DEFAULTS.HASH_FUNCTION) {
|
|
28
|
-
/** @type {HashableObject} */
|
|
28
|
+
/** @type {HashableObject | undefined} */
|
|
29
29
|
this._obj = obj;
|
|
30
30
|
/** @type {undefined | string} */
|
|
31
31
|
this._hash = undefined;
|
|
@@ -40,8 +40,15 @@ class LazyHashedEtag {
|
|
|
40
40
|
toString() {
|
|
41
41
|
if (this._hash === undefined) {
|
|
42
42
|
const hash = createHash(this._hashFunction);
|
|
43
|
-
|
|
43
|
+
/** @type {HashableObject} */
|
|
44
|
+
(this._obj).updateHash(hash);
|
|
44
45
|
this._hash = hash.digest("base64");
|
|
46
|
+
// Drop the captured object once the hash is memoized. The hash is
|
|
47
|
+
// never reset, so we never need `_obj` again — and many callers
|
|
48
|
+
// (e.g. `SourceMapDevToolPlugin`, `RealContentHashPlugin`) capture
|
|
49
|
+
// a heavy `CachedSource` here that would otherwise stay reachable
|
|
50
|
+
// through this etag for the lifetime of the compilation cache.
|
|
51
|
+
this._obj = undefined;
|
|
45
52
|
}
|
|
46
53
|
return this._hash;
|
|
47
54
|
}
|
|
@@ -161,9 +161,6 @@ class HarmonyExportInitFragment extends InitFragment {
|
|
|
161
161
|
* @returns {string | Source | undefined} the source code that will be included as initialization code
|
|
162
162
|
*/
|
|
163
163
|
getContent({ runtimeTemplate, runtimeRequirements }) {
|
|
164
|
-
runtimeRequirements.add(RuntimeGlobals.exports);
|
|
165
|
-
runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
|
|
166
|
-
|
|
167
164
|
const unusedPart =
|
|
168
165
|
this.unusedExports.size > 1
|
|
169
166
|
? `/* unused harmony exports ${joinIterableWithComma(
|
|
@@ -184,12 +181,14 @@ class HarmonyExportInitFragment extends InitFragment {
|
|
|
184
181
|
)}: ${runtimeTemplate.returningFunction(value)}`
|
|
185
182
|
);
|
|
186
183
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
let definePart = "";
|
|
185
|
+
if (this.exportMap.size > 0) {
|
|
186
|
+
runtimeRequirements.add(RuntimeGlobals.exports);
|
|
187
|
+
runtimeRequirements.add(RuntimeGlobals.definePropertyGetters);
|
|
188
|
+
definePart = `/* harmony export */ ${RuntimeGlobals.definePropertyGetters}(${
|
|
189
|
+
this.exportsArgument
|
|
190
|
+
}, {${definitions.join(",")}\n/* harmony export */ });\n`;
|
|
191
|
+
}
|
|
193
192
|
return `${definePart}${unusedPart}`;
|
|
194
193
|
}
|
|
195
194
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
+
const HtmlGenerator = require("../html/HtmlGenerator");
|
|
7
8
|
const makeSerializable = require("../util/makeSerializable");
|
|
8
|
-
const CssUrlDependency = require("./CssUrlDependency");
|
|
9
9
|
const ModuleDependency = require("./ModuleDependency");
|
|
10
10
|
|
|
11
11
|
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
|
@@ -101,19 +101,8 @@ HtmlInlineScriptDependency.Template = class HtmlInlineScriptDependencyTemplate e
|
|
|
101
101
|
|
|
102
102
|
if (entrypoint) {
|
|
103
103
|
const chunk = /** @type {Chunk} */ (entrypoint.getEntrypointChunk());
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
chunk.filenameTemplate ||
|
|
107
|
-
(chunk.canBeInitial()
|
|
108
|
-
? outputOptions.filename
|
|
109
|
-
: outputOptions.chunkFilename);
|
|
110
|
-
|
|
111
|
-
const filename = compilation.getPath(filenameTemplate, {
|
|
112
|
-
chunk,
|
|
113
|
-
contentHashType: "javascript"
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
url = `${CssUrlDependency.PUBLIC_PATH_AUTO}${filename}`;
|
|
104
|
+
// Defer chunk-URL substitution to renderManifest — chunk hashes aren't ready yet.
|
|
105
|
+
url = HtmlGenerator.makeChunkUrlSentinel(chunk, "javascript");
|
|
117
106
|
}
|
|
118
107
|
|
|
119
108
|
// Insert ` src="…"` right after `<script` so the inline body is
|
|
@@ -10,12 +10,15 @@ const ModuleDependency = require("./ModuleDependency");
|
|
|
10
10
|
|
|
11
11
|
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
|
12
12
|
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
|
|
13
|
+
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
|
|
13
14
|
/** @typedef {import("../Dependency")} Dependency */
|
|
14
15
|
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
|
|
15
16
|
/** @typedef {import("../Module")} Module */
|
|
17
|
+
/** @typedef {import("../Module").BuildInfo} BuildInfo */
|
|
16
18
|
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|
17
19
|
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
|
|
18
20
|
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
|
|
21
|
+
/** @typedef {import("../util/Hash")} Hash */
|
|
19
22
|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -45,6 +48,20 @@ class HtmlInlineStyleDependency extends ModuleDependency {
|
|
|
45
48
|
return "html-style";
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Updates the hash with the data contributed by this instance.
|
|
53
|
+
* @param {Hash} hash hash to be updated
|
|
54
|
+
* @param {UpdateHashContext} context context
|
|
55
|
+
* @returns {void}
|
|
56
|
+
*/
|
|
57
|
+
updateHash(hash, context) {
|
|
58
|
+
// Recurse so the inline CSS's transitive deps (e.g. `url(asset)`) propagate up.
|
|
59
|
+
const { chunkGraph } = context;
|
|
60
|
+
const module = chunkGraph.moduleGraph.getModule(this);
|
|
61
|
+
if (!module) return;
|
|
62
|
+
module.updateHash(hash, context);
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
/**
|
|
49
66
|
* Serializes this instance into the provided serializer context.
|
|
50
67
|
* @param {ObjectSerializerContext} context context
|
|
@@ -9,8 +9,8 @@ const {
|
|
|
9
9
|
CSS_TYPE,
|
|
10
10
|
JAVASCRIPT_TYPE
|
|
11
11
|
} = require("../ModuleSourceTypeConstants");
|
|
12
|
+
const HtmlGenerator = require("../html/HtmlGenerator");
|
|
12
13
|
const makeSerializable = require("../util/makeSerializable");
|
|
13
|
-
const CssUrlDependency = require("./CssUrlDependency");
|
|
14
14
|
const ModuleDependency = require("./ModuleDependency");
|
|
15
15
|
|
|
16
16
|
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
|
@@ -95,39 +95,6 @@ class HtmlScriptSrcDependency extends ModuleDependency {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
/**
|
|
99
|
-
* @param {Chunk} chunk a chunk
|
|
100
|
-
* @param {import("../Compilation")} compilation compilation
|
|
101
|
-
* @param {"javascript" | "css"} contentHashType which content hash to plug into the filename template
|
|
102
|
-
* @returns {string} chunk filename path (no public-path prefix)
|
|
103
|
-
*/
|
|
104
|
-
const getChunkFilename = (chunk, compilation, contentHashType) => {
|
|
105
|
-
const outputOptions = compilation.outputOptions;
|
|
106
|
-
let filenameTemplate;
|
|
107
|
-
if (contentHashType === "css") {
|
|
108
|
-
// For a CSS-typed chunk, use the same template the CSS pipeline
|
|
109
|
-
// will use when it actually emits the `.css` file, so the `<link
|
|
110
|
-
// rel="stylesheet" href>` URL we write into the HTML matches the
|
|
111
|
-
// asset on disk.
|
|
112
|
-
filenameTemplate =
|
|
113
|
-
require("../css/CssModulesPlugin").getChunkFilenameTemplate(
|
|
114
|
-
chunk,
|
|
115
|
-
outputOptions
|
|
116
|
-
);
|
|
117
|
-
} else {
|
|
118
|
-
filenameTemplate =
|
|
119
|
-
chunk.filenameTemplate ||
|
|
120
|
-
(chunk.canBeInitial()
|
|
121
|
-
? outputOptions.filename
|
|
122
|
-
: outputOptions.chunkFilename);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return compilation.getPath(filenameTemplate, {
|
|
126
|
-
chunk,
|
|
127
|
-
contentHashType
|
|
128
|
-
});
|
|
129
|
-
};
|
|
130
|
-
|
|
131
98
|
/**
|
|
132
99
|
* @param {Entrypoint} entrypoint entrypoint
|
|
133
100
|
* @returns {Chunk[]} every chunk this entrypoint needs in load order: the
|
|
@@ -410,15 +377,13 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
|
|
|
410
377
|
const entryChunk = orderedChunks[orderedChunks.length - 1];
|
|
411
378
|
const isStylesheet = dep.elementKind === "stylesheet";
|
|
412
379
|
|
|
413
|
-
// Rewrite
|
|
414
|
-
//
|
|
415
|
-
// `<link rel="stylesheet">`, `.js` for everything else.
|
|
380
|
+
// Rewrite src/href to a chunk-URL sentinel (resolved by renderManifest):
|
|
381
|
+
// `.css` for `<link rel="stylesheet">`, `.js` for everything else.
|
|
416
382
|
const entryContentHashType = isStylesheet ? "css" : "javascript";
|
|
417
|
-
const entryUrl =
|
|
383
|
+
const entryUrl = HtmlGenerator.makeChunkUrlSentinel(
|
|
418
384
|
entryChunk,
|
|
419
|
-
compilation,
|
|
420
385
|
entryContentHashType
|
|
421
|
-
)
|
|
386
|
+
);
|
|
422
387
|
source.replace(dep.range[0], dep.range[1] - 1, entryUrl);
|
|
423
388
|
|
|
424
389
|
if (dep.tagStart < 0 || dep.tagOpenEnd <= dep.tagStart) {
|
|
@@ -446,11 +411,7 @@ HtmlScriptSrcDependency.Template = class HtmlScriptSrcDependencyTemplate extends
|
|
|
446
411
|
* @returns {string} a single sibling tag's HTML
|
|
447
412
|
*/
|
|
448
413
|
const buildSibling = (chunk, kind) => {
|
|
449
|
-
const url =
|
|
450
|
-
chunk,
|
|
451
|
-
compilation,
|
|
452
|
-
kind
|
|
453
|
-
)}`;
|
|
414
|
+
const url = HtmlGenerator.makeChunkUrlSentinel(chunk, kind);
|
|
454
415
|
if (kind === "css" && !isStylesheet) {
|
|
455
416
|
// Originating tag is `<script>` (or `<link rel=modulepreload>`)
|
|
456
417
|
// but this chunk is CSS — emit a fresh `<link>` rather than
|
|
@@ -13,14 +13,17 @@ const ModuleDependency = require("./ModuleDependency");
|
|
|
13
13
|
|
|
14
14
|
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
|
|
15
15
|
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
|
|
16
|
+
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
|
|
16
17
|
/** @typedef {import("../Dependency")} Dependency */
|
|
17
18
|
/** @typedef {import("../DependencyTemplate").CssDependencyTemplateContext} DependencyTemplateContext */
|
|
18
19
|
/** @typedef {import("../Module")} Module */
|
|
20
|
+
/** @typedef {import("../Module").BuildInfo} BuildInfo */
|
|
19
21
|
/** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
|
|
20
22
|
/** @typedef {import("../Module").CodeGenerationResultData} CodeGenerationResultData */
|
|
21
23
|
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|
22
24
|
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
|
|
23
25
|
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
|
|
26
|
+
/** @typedef {import("../util/Hash")} Hash */
|
|
24
27
|
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
|
|
25
28
|
|
|
26
29
|
const getIgnoredRawDataUrlModule = memoize(
|
|
@@ -55,6 +58,21 @@ class HtmlSourceDependency extends ModuleDependency {
|
|
|
55
58
|
return getIgnoredRawDataUrlModule();
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Updates the hash with the data contributed by this instance.
|
|
63
|
+
* @param {Hash} hash hash to be updated
|
|
64
|
+
* @param {UpdateHashContext} context context
|
|
65
|
+
* @returns {void}
|
|
66
|
+
*/
|
|
67
|
+
updateHash(hash, context) {
|
|
68
|
+
// Fold in the asset's hash so the HTML invalidates when the embedded URL changes.
|
|
69
|
+
const { chunkGraph } = context;
|
|
70
|
+
const module = chunkGraph.moduleGraph.getModule(this);
|
|
71
|
+
if (!module) return;
|
|
72
|
+
const { hash: buildHash } = /** @type {BuildInfo} */ (module.buildInfo);
|
|
73
|
+
if (buildHash) hash.update(buildHash);
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
/**
|
|
59
77
|
* Serializes this instance into the provided serializer context.
|
|
60
78
|
* @param {ObjectSerializerContext} context context
|
|
@@ -349,10 +349,24 @@ class WorkerPlugin {
|
|
|
349
349
|
)
|
|
350
350
|
);
|
|
351
351
|
} else {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
// `webpackEntryOptions` is user input from a magic
|
|
353
|
+
// comment, so copy only safe own keys to avoid
|
|
354
|
+
// prototype pollution via `__proto__`/`constructor`/
|
|
355
|
+
// `prototype`.
|
|
356
|
+
const userEntryOptions = importOptions.webpackEntryOptions;
|
|
357
|
+
for (const key of Object.keys(userEntryOptions)) {
|
|
358
|
+
if (
|
|
359
|
+
key === "__proto__" ||
|
|
360
|
+
key === "constructor" ||
|
|
361
|
+
key === "prototype"
|
|
362
|
+
) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
/** @type {EXPECTED_ANY} */
|
|
366
|
+
(entryOptions)[key] = /** @type {EXPECTED_ANY} */ (
|
|
367
|
+
userEntryOptions
|
|
368
|
+
)[key];
|
|
369
|
+
}
|
|
356
370
|
}
|
|
357
371
|
}
|
|
358
372
|
if (importOptions.webpackChunkName !== undefined) {
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
const RuntimeGlobals = require("../RuntimeGlobals");
|
|
19
19
|
const Template = require("../Template");
|
|
20
20
|
const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency");
|
|
21
|
+
const { resolveByProperty } = require("../util/cleverMerge");
|
|
21
22
|
const { registerNotSerializable } = require("../util/serialization");
|
|
22
23
|
|
|
23
24
|
/** @typedef {import("../config/defaults").WebpackOptionsNormalizedWithDefaults} WebpackOptions */
|
|
@@ -45,6 +46,96 @@ const { registerNotSerializable } = require("../util/serialization");
|
|
|
45
46
|
|
|
46
47
|
/** @typedef {{ client: string, data: string, active: boolean }} ModuleResult */
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Library wrappers of these types pass external modules as closure arguments
|
|
51
|
+
* (e.g. `__WEBPACK_EXTERNAL_MODULE_react__`) baked into the entry chunk at
|
|
52
|
+
* render time. When `lazyCompilation` activates a proxy for the first time,
|
|
53
|
+
* any external dependency the lazily-built module pulls in lands in a hot
|
|
54
|
+
* update chunk that lives outside the original wrapper closure, so the
|
|
55
|
+
* factory body can't resolve its closure identifier and throws at runtime.
|
|
56
|
+
* Reserving the externals up front (during the inactive build) folds them
|
|
57
|
+
* into the initial wrapper, so the closure identifiers are already defined
|
|
58
|
+
* when the activation update arrives.
|
|
59
|
+
*/
|
|
60
|
+
const CLOSURE_LIBRARY_TYPES = new Set([
|
|
61
|
+
"umd",
|
|
62
|
+
"umd2",
|
|
63
|
+
"amd",
|
|
64
|
+
"amd-require",
|
|
65
|
+
"system"
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `enabledLibraryTypes` covers both the global `output.library.type` and any
|
|
70
|
+
* per-entry `entry.<name>.library.type`, so a UMD/AMD/System wrapper attached
|
|
71
|
+
* to an individual entry is still detected.
|
|
72
|
+
* @param {import("../../declarations/WebpackOptions").OutputNormalized} output normalized output option
|
|
73
|
+
* @returns {boolean} true when at least one library wrapper passes externals as closure arguments
|
|
74
|
+
*/
|
|
75
|
+
const hasClosureLibrary = (output) => {
|
|
76
|
+
const enabled = output.enabledLibraryTypes;
|
|
77
|
+
if (enabled) {
|
|
78
|
+
for (const type of enabled) {
|
|
79
|
+
if (CLOSURE_LIBRARY_TYPES.has(type)) return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (output.library && output.library.type) {
|
|
83
|
+
return CLOSURE_LIBRARY_TYPES.has(output.library.type);
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Collects request strings from statically-enumerable externals (string,
|
|
90
|
+
* object, and arrays of those). Function and RegExp forms are skipped because
|
|
91
|
+
* their effective request set isn't knowable until something asks for it.
|
|
92
|
+
*
|
|
93
|
+
* Layer resolution mirrors `ExternalModuleFactoryPlugin.resolveLayer`: the
|
|
94
|
+
* effective map for the proxy's layer is computed via the same
|
|
95
|
+
* `resolveByProperty(..., "byLayer", layer)` helper that the externals system
|
|
96
|
+
* uses, so `byLayer.default` fallback and function-form `byLayer` entries are
|
|
97
|
+
* honored the same way.
|
|
98
|
+
*
|
|
99
|
+
* Entries whose effective value is `false` are skipped — `false` explicitly
|
|
100
|
+
* disables externalization for that request, and reserving it would force the
|
|
101
|
+
* real module into the entry chunk.
|
|
102
|
+
* @param {import("../../declarations/WebpackOptions").Externals | undefined} externals normalized externals option
|
|
103
|
+
* @param {string | null} layer issuer layer for which to resolve `byLayer`
|
|
104
|
+
* @returns {Set<string>} requests to reserve in the entry chunk
|
|
105
|
+
*/
|
|
106
|
+
const collectStaticExternalRequests = (externals, layer) => {
|
|
107
|
+
/** @type {Set<string>} */
|
|
108
|
+
const requests = new Set();
|
|
109
|
+
if (!externals) return requests;
|
|
110
|
+
/** @param {import("../../declarations/WebpackOptions").ExternalItem} item one item */
|
|
111
|
+
const visit = (item) => {
|
|
112
|
+
if (typeof item === "string") {
|
|
113
|
+
requests.add(item);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!item || typeof item !== "object" || item instanceof RegExp) return;
|
|
117
|
+
const resolved = /** @type {Record<string, unknown>} */ (
|
|
118
|
+
resolveByProperty(
|
|
119
|
+
/** @type {Record<string, unknown>} */ (item),
|
|
120
|
+
"byLayer",
|
|
121
|
+
layer
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
for (const [request, value] of Object.entries(resolved)) {
|
|
125
|
+
// `false` explicitly opts the request out of externalization; reserving
|
|
126
|
+
// it would pull the actual module into the entry chunk.
|
|
127
|
+
if (value === false) continue;
|
|
128
|
+
requests.add(request);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
if (Array.isArray(externals)) {
|
|
132
|
+
for (const item of externals) visit(item);
|
|
133
|
+
} else {
|
|
134
|
+
visit(externals);
|
|
135
|
+
}
|
|
136
|
+
return requests;
|
|
137
|
+
};
|
|
138
|
+
|
|
48
139
|
/**
|
|
49
140
|
* Defines the backend api type used by this module.
|
|
50
141
|
* @typedef {object} BackendApi
|
|
@@ -213,6 +304,19 @@ class LazyCompilationProxyModule extends Module {
|
|
|
213
304
|
const block = new AsyncDependenciesBlock({});
|
|
214
305
|
block.addDependency(dep);
|
|
215
306
|
this.addBlock(block);
|
|
307
|
+
} else if (hasClosureLibrary(compilation.options.output)) {
|
|
308
|
+
// Reserve statically-declared externals as dependencies of the inactive
|
|
309
|
+
// proxy so the initial entry chunk's library wrapper already exposes
|
|
310
|
+
// their closure identifiers (e.g. `__WEBPACK_EXTERNAL_MODULE_react__`).
|
|
311
|
+
// Once the proxy activates and the lazily-built module references those
|
|
312
|
+
// externals, the identifiers resolve normally instead of throwing.
|
|
313
|
+
const requests = collectStaticExternalRequests(
|
|
314
|
+
options.externals,
|
|
315
|
+
this.layer
|
|
316
|
+
);
|
|
317
|
+
for (const request of requests) {
|
|
318
|
+
this.addDependency(new CommonJsRequireDependency(request));
|
|
319
|
+
}
|
|
216
320
|
}
|
|
217
321
|
callback();
|
|
218
322
|
}
|