webpack 5.8.0 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webpack might be problematic. Click here for more details.

@@ -7,9 +7,7 @@
7
7
 
8
8
  const WebpackError = require("./WebpackError");
9
9
 
10
- module.exports = class ConcurrentCompilationError extends (
11
- WebpackError
12
- ) {
10
+ module.exports = class ConcurrentCompilationError extends WebpackError {
13
11
  constructor() {
14
12
  super();
15
13
 
@@ -26,9 +26,7 @@ const { join } = require("./util/fs");
26
26
 
27
27
  const EMPTY_RESOLVE_OPTIONS = {};
28
28
 
29
- module.exports = class ContextModuleFactory extends (
30
- ModuleFactory
31
- ) {
29
+ module.exports = class ContextModuleFactory extends ModuleFactory {
32
30
  /**
33
31
  * @param {ResolverFactory} resolverFactory resolverFactory
34
32
  */
@@ -6,9 +6,7 @@
6
6
 
7
7
  const WebpackError = require("./WebpackError");
8
8
 
9
- module.exports = class HarmonyLinkingError extends (
10
- WebpackError
11
- ) {
9
+ module.exports = class HarmonyLinkingError extends WebpackError {
12
10
  /** @param {string} message Error message */
13
11
  constructor(message) {
14
12
  super(message);
@@ -12,6 +12,7 @@ const Compilation = require("./Compilation");
12
12
  const HotUpdateChunk = require("./HotUpdateChunk");
13
13
  const NormalModule = require("./NormalModule");
14
14
  const RuntimeGlobals = require("./RuntimeGlobals");
15
+ const WebpackError = require("./WebpackError");
15
16
  const ConstDependency = require("./dependencies/ConstDependency");
16
17
  const ImportMetaHotAcceptDependency = require("./dependencies/ImportMetaHotAcceptDependency");
17
18
  const ImportMetaHotDeclineDependency = require("./dependencies/ImportMetaHotDeclineDependency");
@@ -22,15 +23,22 @@ const JavascriptParser = require("./javascript/JavascriptParser");
22
23
  const {
23
24
  evaluateToIdentifier
24
25
  } = require("./javascript/JavascriptParserHelpers");
25
- const { find } = require("./util/SetHelpers");
26
+ const { find, isSubset } = require("./util/SetHelpers");
26
27
  const TupleSet = require("./util/TupleSet");
27
28
  const { compareModulesById } = require("./util/comparators");
28
- const { getRuntimeKey, keyToRuntime } = require("./util/runtime");
29
+ const {
30
+ getRuntimeKey,
31
+ keyToRuntime,
32
+ forEachRuntime,
33
+ mergeRuntimeOwned,
34
+ subtractRuntime
35
+ } = require("./util/runtime");
29
36
 
30
37
  /** @typedef {import("./Chunk")} Chunk */
31
38
  /** @typedef {import("./Compilation").AssetInfo} AssetInfo */
32
39
  /** @typedef {import("./Compiler")} Compiler */
33
40
  /** @typedef {import("./Module")} Module */
41
+ /** @typedef {import("./RuntimeModule")} RuntimeModule */
34
42
 
35
43
  /**
36
44
  * @typedef {Object} HMRJavascriptParserHooks
@@ -388,27 +396,58 @@ class HotModuleReplacementPlugin {
388
396
  }
389
397
  chunkModuleHashes[key] = hash;
390
398
  }
391
- const hotUpdateMainContent = {
392
- c: [],
393
- r: [],
394
- m: undefined
395
- };
399
+
400
+ /** @type {Map<string, { updatedChunkIds: Set<string|number>, removedChunkIds: Set<string|number>, removedModules: Set<Module>, filename: string, assetInfo: AssetInfo }>} */
401
+ const hotUpdateMainContentByRuntime = new Map();
402
+ let allOldRuntime;
403
+ for (const key of Object.keys(records.chunkRuntime)) {
404
+ const runtime = keyToRuntime(records.chunkRuntime[key]);
405
+ allOldRuntime = mergeRuntimeOwned(allOldRuntime, runtime);
406
+ }
407
+ forEachRuntime(allOldRuntime, runtime => {
408
+ const {
409
+ path: filename,
410
+ info: assetInfo
411
+ } = compilation.getPathWithInfo(
412
+ compilation.outputOptions.hotUpdateMainFilename,
413
+ {
414
+ hash: records.hash,
415
+ runtime
416
+ }
417
+ );
418
+ hotUpdateMainContentByRuntime.set(runtime, {
419
+ updatedChunkIds: new Set(),
420
+ removedChunkIds: new Set(),
421
+ removedModules: new Set(),
422
+ filename,
423
+ assetInfo
424
+ });
425
+ });
426
+ if (hotUpdateMainContentByRuntime.size === 0) return;
396
427
 
397
428
  // Create a list of all active modules to verify which modules are removed completely
398
429
  /** @type {Map<number|string, Module>} */
399
430
  const allModules = new Map();
400
431
  for (const module of compilation.modules) {
401
- allModules.set(chunkGraph.getModuleId(module), module);
432
+ const id = chunkGraph.getModuleId(module);
433
+ allModules.set(id, module);
402
434
  }
403
435
 
404
436
  // List of completely removed modules
405
- const allRemovedModules = new Set();
437
+ /** @type {Set<string | number>} */
438
+ const completelyRemovedModules = new Set();
406
439
 
407
440
  for (const key of Object.keys(records.chunkHashs)) {
408
- // Check which modules are completely removed
441
+ const oldRuntime = keyToRuntime(records.chunkRuntime[key]);
442
+ /** @type {Module[]} */
443
+ const remainingModules = [];
444
+ // Check which modules are removed
409
445
  for (const id of records.chunkModuleIds[key]) {
410
- if (!allModules.has(id)) {
411
- allRemovedModules.add(id);
446
+ const module = allModules.get(id);
447
+ if (module === undefined) {
448
+ completelyRemovedModules.add(id);
449
+ } else {
450
+ remainingModules.push(module);
412
451
  }
413
452
  }
414
453
 
@@ -417,6 +456,7 @@ class HotModuleReplacementPlugin {
417
456
  let newRuntimeModules;
418
457
  let newFullHashModules;
419
458
  let newRuntime;
459
+ let removedFromRuntime;
420
460
  const currentChunk = find(
421
461
  compilation.chunks,
422
462
  chunk => `${chunk.id}` === key
@@ -438,18 +478,61 @@ class HotModuleReplacementPlugin {
438
478
  Array.from(fullHashModules).filter(module =>
439
479
  updatedModules.has(module, currentChunk)
440
480
  );
481
+ removedFromRuntime = subtractRuntime(oldRuntime, newRuntime);
441
482
  } else {
483
+ // chunk has completely removed
442
484
  chunkId = `${+key}` === key ? +key : key;
443
- hotUpdateMainContent.r.push(chunkId);
444
- const runtime = keyToRuntime(records.chunkRuntime[key]);
445
- for (const id of records.chunkModuleIds[key]) {
446
- const module = allModules.get(id);
447
- if (!module) continue;
448
- const hash = chunkGraph.getModuleHash(module, runtime);
485
+ removedFromRuntime = oldRuntime;
486
+ newRuntime = oldRuntime;
487
+ }
488
+ if (removedFromRuntime) {
489
+ // chunk was removed from some runtimes
490
+ forEachRuntime(removedFromRuntime, runtime => {
491
+ hotUpdateMainContentByRuntime
492
+ .get(runtime)
493
+ .removedChunkIds.add(chunkId);
494
+ });
495
+ // dispose modules from the chunk in these runtimes
496
+ // where they are no longer in this runtime
497
+ for (const module of remainingModules) {
449
498
  const moduleKey = `${key}|${module.identifier()}`;
450
- if (hash !== records.chunkModuleHashes[moduleKey]) {
451
- newModules = newModules || [];
452
- newModules.push(module);
499
+ const oldHash = records.chunkModuleHashes[moduleKey];
500
+ const runtimes = chunkGraph.getModuleRuntimes(module);
501
+ if (oldRuntime === newRuntime && runtimes.has(newRuntime)) {
502
+ // Module is still in the same runtime combination
503
+ const hash = chunkGraph.getModuleHash(module, newRuntime);
504
+ if (hash !== oldHash) {
505
+ if (module.type === "runtime") {
506
+ newRuntimeModules = newRuntimeModules || [];
507
+ newRuntimeModules.push(
508
+ /** @type {RuntimeModule} */ (module)
509
+ );
510
+ } else {
511
+ newModules = newModules || [];
512
+ newModules.push(module);
513
+ }
514
+ }
515
+ } else {
516
+ // module is no longer in this runtime combination
517
+ // We (incorrectly) assume that it's not in an overlapping runtime combination
518
+ // and dispose it from the main runtimes the chunk was removed from
519
+ forEachRuntime(removedFromRuntime, runtime => {
520
+ // If the module is still used in this runtime, do not dispose it
521
+ // This could create a bad runtime state where the module is still loaded,
522
+ // but no chunk which contains it. This means we don't receive further HMR updates
523
+ // to this module and that's bad.
524
+ // TODO force load one of the chunks which contains the module
525
+ for (const moduleRuntime of runtimes) {
526
+ if (typeof moduleRuntime === "string") {
527
+ if (moduleRuntime === runtime) return;
528
+ } else if (moduleRuntime !== undefined) {
529
+ if (moduleRuntime.has(runtime)) return;
530
+ }
531
+ }
532
+ hotUpdateMainContentByRuntime
533
+ .get(runtime)
534
+ .removedModules.add(module);
535
+ });
453
536
  }
454
537
  }
455
538
  }
@@ -516,24 +599,75 @@ class HotModuleReplacementPlugin {
516
599
  compilation.hooks.chunkAsset.call(currentChunk, filename);
517
600
  }
518
601
  }
519
- hotUpdateMainContent.c.push(chunkId);
602
+ forEachRuntime(newRuntime, runtime => {
603
+ hotUpdateMainContentByRuntime
604
+ .get(runtime)
605
+ .updatedChunkIds.add(chunkId);
606
+ });
520
607
  }
521
608
  }
522
- hotUpdateMainContent.m = Array.from(allRemovedModules);
523
- const source = new RawSource(JSON.stringify(hotUpdateMainContent));
524
- const {
525
- path: filename,
526
- info: assetInfo
527
- } = compilation.getPathWithInfo(
528
- compilation.outputOptions.hotUpdateMainFilename,
529
- {
530
- hash: records.hash
531
- }
609
+ const completelyRemovedModulesArray = Array.from(
610
+ completelyRemovedModules
532
611
  );
533
- compilation.emitAsset(filename, source, {
534
- hotModuleReplacement: true,
535
- ...assetInfo
536
- });
612
+ const hotUpdateMainContentByFilename = new Map();
613
+ for (const {
614
+ removedChunkIds,
615
+ removedModules,
616
+ updatedChunkIds,
617
+ filename,
618
+ assetInfo
619
+ } of hotUpdateMainContentByRuntime.values()) {
620
+ const old = hotUpdateMainContentByFilename.get(filename);
621
+ if (
622
+ old &&
623
+ (!isSubset(old.removedChunkIds, removedChunkIds) ||
624
+ !isSubset(old.removedModules, removedModules) ||
625
+ !isSubset(old.updatedChunkIds, updatedChunkIds))
626
+ ) {
627
+ compilation.warnings.push(
628
+ new WebpackError(`HotModuleReplacementPlugin
629
+ The configured output.hotUpdateMainFilename doesn't lead to unique filenames per runtime and HMR update differs between runtimes.
630
+ This might lead to incorrect runtime behavior of the applied update.
631
+ To fix this, make sure to include [runtime] in the output.hotUpdateMainFilename option, or use the default config.`)
632
+ );
633
+ for (const chunkId of removedChunkIds)
634
+ old.removedChunkIds.add(chunkId);
635
+ for (const chunkId of removedModules)
636
+ old.removedModules.add(chunkId);
637
+ for (const chunkId of updatedChunkIds)
638
+ old.updatedChunkIds.add(chunkId);
639
+ continue;
640
+ }
641
+ hotUpdateMainContentByFilename.set(filename, {
642
+ removedChunkIds,
643
+ removedModules,
644
+ updatedChunkIds,
645
+ assetInfo
646
+ });
647
+ }
648
+ for (const [
649
+ filename,
650
+ { removedChunkIds, removedModules, updatedChunkIds, assetInfo }
651
+ ] of hotUpdateMainContentByFilename) {
652
+ const hotUpdateMainJson = {
653
+ c: Array.from(updatedChunkIds),
654
+ r: Array.from(removedChunkIds),
655
+ m:
656
+ removedModules.size === 0
657
+ ? completelyRemovedModulesArray
658
+ : completelyRemovedModulesArray.concat(
659
+ Array.from(removedModules, m =>
660
+ chunkGraph.getModuleId(m)
661
+ )
662
+ )
663
+ };
664
+
665
+ const source = new RawSource(JSON.stringify(hotUpdateMainJson));
666
+ compilation.emitAsset(filename, source, {
667
+ hotModuleReplacement: true,
668
+ ...assetInfo
669
+ });
670
+ }
537
671
  }
538
672
  );
539
673
 
@@ -10,9 +10,7 @@ const WebpackError = require("./WebpackError");
10
10
  /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
11
11
  /** @typedef {import("./Module")} Module */
12
12
 
13
- module.exports = class ModuleDependencyWarning extends (
14
- WebpackError
15
- ) {
13
+ module.exports = class ModuleDependencyWarning extends WebpackError {
16
14
  /**
17
15
  * @param {Module} module module tied to dependency
18
16
  * @param {Error} err error thrown
@@ -7,9 +7,7 @@
7
7
 
8
8
  const WebpackError = require("./WebpackError");
9
9
 
10
- module.exports = class NoModeWarning extends (
11
- WebpackError
12
- ) {
10
+ module.exports = class NoModeWarning extends WebpackError {
13
11
  constructor(modules) {
14
12
  super();
15
13
 
@@ -26,6 +26,7 @@ const ModuleWarning = require("./ModuleWarning");
26
26
  const RuntimeGlobals = require("./RuntimeGlobals");
27
27
  const UnhandledSchemeError = require("./UnhandledSchemeError");
28
28
  const WebpackError = require("./WebpackError");
29
+ const formatLocation = require("./formatLocation");
29
30
  const LazySet = require("./util/LazySet");
30
31
  const { getScheme } = require("./util/URLAbsoluteSpecifier");
31
32
  const {
@@ -248,6 +249,7 @@ class NormalModule extends Module {
248
249
  this._lastSuccessfulBuildMeta = {};
249
250
  this._forceBuild = true;
250
251
  this._isEvaluatingSideEffects = false;
252
+ this._addedSideEffectsBailout = new WeakSet();
251
253
  }
252
254
 
253
255
  /**
@@ -877,6 +879,17 @@ class NormalModule extends Module {
877
879
  for (const dep of this.dependencies) {
878
880
  const state = dep.getModuleEvaluationSideEffectsState(moduleGraph);
879
881
  if (state === true) {
882
+ if (!this._addedSideEffectsBailout.has(moduleGraph)) {
883
+ this._addedSideEffectsBailout.add(moduleGraph);
884
+ moduleGraph
885
+ .getOptimizationBailout(this)
886
+ .push(
887
+ () =>
888
+ `Dependency (${
889
+ dep.type
890
+ }) with side effects at ${formatLocation(dep.loc)}`
891
+ );
892
+ }
880
893
  this._isEvaluatingSideEffects = false;
881
894
  return true;
882
895
  } else if (state !== ModuleGraphConnection.CIRCULAR_CONNECTION) {
@@ -274,6 +274,14 @@ const replacePathVariables = (path, data, assetInfo) => {
274
274
  if (data.url) {
275
275
  replacements.set("url", replacer(data.url));
276
276
  }
277
+ if (typeof data.runtime === "string") {
278
+ replacements.set(
279
+ "runtime",
280
+ replacer(() => prepareId(data.runtime))
281
+ );
282
+ } else {
283
+ replacements.set("runtime", replacer("_"));
284
+ }
277
285
 
278
286
  if (typeof path === "function") {
279
287
  path = path(data, assetInfo);
@@ -642,7 +642,7 @@ const applyOutputDefaults = (
642
642
  F(output, "pathinfo", () => development);
643
643
  D(output, "sourceMapFilename", "[file].map[query]");
644
644
  D(output, "hotUpdateChunkFilename", "[id].[fullhash].hot-update.js");
645
- D(output, "hotUpdateMainFilename", "[fullhash].hot-update.json");
645
+ D(output, "hotUpdateMainFilename", "[runtime].[fullhash].hot-update.json");
646
646
  D(output, "crossOriginLoading", false);
647
647
  F(output, "scriptType", () => (output.module ? "module" : false));
648
648
  D(
@@ -12,9 +12,7 @@ const ContainerEntryModule = require("./ContainerEntryModule");
12
12
  /** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
13
13
  /** @typedef {import("./ContainerEntryDependency")} ContainerEntryDependency */
14
14
 
15
- module.exports = class ContainerEntryModuleFactory extends (
16
- ModuleFactory
17
- ) {
15
+ module.exports = class ContainerEntryModuleFactory extends ModuleFactory {
18
16
  /**
19
17
  * @param {ModuleFactoryCreateData} data data object
20
18
  * @param {function(Error=, ModuleFactoryResult=): void} callback callback
@@ -12,9 +12,7 @@ const FallbackModule = require("./FallbackModule");
12
12
  /** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
13
13
  /** @typedef {import("./FallbackDependency")} FallbackDependency */
14
14
 
15
- module.exports = class FallbackModuleFactory extends (
16
- ModuleFactory
17
- ) {
15
+ module.exports = class FallbackModuleFactory extends ModuleFactory {
18
16
  /**
19
17
  * @param {ModuleFactoryCreateData} data data object
20
18
  * @param {function(Error=, ModuleFactoryResult=): void} callback callback
@@ -370,6 +370,14 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
370
370
  };
371
371
  }
372
372
 
373
+ /**
374
+ * @param {ModuleGraph} moduleGraph the module graph
375
+ * @returns {ConnectionState} how this dependency connects the module to referencing modules
376
+ */
377
+ getModuleEvaluationSideEffectsState(moduleGraph) {
378
+ return false;
379
+ }
380
+
373
381
  /**
374
382
  * Returns list of exports referenced by this dependency
375
383
  * @param {ModuleGraph} moduleGraph module graph
@@ -88,6 +88,14 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
88
88
  this.checkUsedByExports(moduleGraph, runtime);
89
89
  }
90
90
 
91
+ /**
92
+ * @param {ModuleGraph} moduleGraph the module graph
93
+ * @returns {ConnectionState} how this dependency connects the module to referencing modules
94
+ */
95
+ getModuleEvaluationSideEffectsState(moduleGraph) {
96
+ return false;
97
+ }
98
+
91
99
  checkUsedByExports(moduleGraph, runtime) {
92
100
  if (this.usedByExports === false) return false;
93
101
  if (this.usedByExports !== true && this.usedByExports !== undefined) {
@@ -9,6 +9,7 @@ const glob2regexp = require("glob-to-regexp");
9
9
  const { STAGE_DEFAULT } = require("../OptimizationStages");
10
10
  const HarmonyExportImportedSpecifierDependency = require("../dependencies/HarmonyExportImportedSpecifierDependency");
11
11
  const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
12
+ const formatLocation = require("../formatLocation");
12
13
 
13
14
  /** @typedef {import("../Compiler")} Compiler */
14
15
  /** @typedef {import("../Dependency")} Dependency */
@@ -67,223 +68,242 @@ class SideEffectsFlagPlugin {
67
68
  cache = new Map();
68
69
  globToRegexpCache.set(compiler.root, cache);
69
70
  }
70
- compiler.hooks.normalModuleFactory.tap("SideEffectsFlagPlugin", nmf => {
71
- nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => {
72
- const resolveData = data.resourceResolveData;
73
- if (
74
- resolveData &&
75
- resolveData.descriptionFileData &&
76
- resolveData.relativePath
77
- ) {
78
- const sideEffects = resolveData.descriptionFileData.sideEffects;
79
- const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
80
- resolveData.relativePath,
81
- sideEffects,
82
- cache
83
- );
84
- if (!hasSideEffects) {
85
- if (module.factoryMeta === undefined) {
86
- module.factoryMeta = {};
71
+ compiler.hooks.compilation.tap(
72
+ "SideEffectsFlagPlugin",
73
+ (compilation, { normalModuleFactory }) => {
74
+ const moduleGraph = compilation.moduleGraph;
75
+ normalModuleFactory.hooks.module.tap(
76
+ "SideEffectsFlagPlugin",
77
+ (module, data) => {
78
+ const resolveData = data.resourceResolveData;
79
+ if (
80
+ resolveData &&
81
+ resolveData.descriptionFileData &&
82
+ resolveData.relativePath
83
+ ) {
84
+ const sideEffects = resolveData.descriptionFileData.sideEffects;
85
+ const hasSideEffects = SideEffectsFlagPlugin.moduleHasSideEffects(
86
+ resolveData.relativePath,
87
+ sideEffects,
88
+ cache
89
+ );
90
+ if (!hasSideEffects) {
91
+ if (module.factoryMeta === undefined) {
92
+ module.factoryMeta = {};
93
+ }
94
+ module.factoryMeta.sideEffectFree = true;
95
+ }
87
96
  }
88
- module.factoryMeta.sideEffectFree = true;
89
- }
90
- }
91
97
 
92
- return module;
93
- });
94
- nmf.hooks.module.tap("SideEffectsFlagPlugin", (module, data) => {
95
- if (data.settings.sideEffects === false) {
96
- if (module.factoryMeta === undefined) {
97
- module.factoryMeta = {};
98
- }
99
- module.factoryMeta.sideEffectFree = true;
100
- } else if (data.settings.sideEffects === true) {
101
- if (module.factoryMeta !== undefined) {
102
- module.factoryMeta.sideEffectFree = false;
98
+ return module;
103
99
  }
104
- }
105
- return module;
106
- });
107
- if (this._analyseSource) {
108
- /**
109
- * @param {JavascriptParser} parser the parser
110
- * @returns {void}
111
- */
112
- const parserHandler = parser => {
113
- let hasSideEffects = false;
114
- parser.hooks.program.tap("SideEffectsFlagPlugin", () => {
115
- hasSideEffects = false;
116
- });
117
- parser.hooks.statement.tap(
118
- { name: "SideEffectsFlagPlugin", stage: -100 },
119
- statement => {
120
- if (hasSideEffects) return;
121
- if (parser.scope.topLevelScope !== true) return;
122
- switch (statement.type) {
123
- case "ExpressionStatement":
124
- if (
125
- !parser.isPure(statement.expression, statement.range[0])
126
- ) {
127
- hasSideEffects = true;
128
- }
129
- break;
130
- case "IfStatement":
131
- case "WhileStatement":
132
- case "DoWhileStatement":
133
- if (!parser.isPure(statement.test, statement.range[0])) {
134
- hasSideEffects = true;
135
- }
136
- // statement hook will be called for child statements too
137
- break;
138
- case "ForStatement":
139
- if (
140
- !parser.isPure(statement.init, statement.range[0]) ||
141
- !parser.isPure(
142
- statement.test,
143
- statement.init
144
- ? statement.init.range[1]
145
- : statement.range[0]
146
- ) ||
147
- !parser.isPure(
148
- statement.update,
149
- statement.test
150
- ? statement.test.range[1]
151
- : statement.init
152
- ? statement.init.range[1]
153
- : statement.range[0]
154
- )
155
- ) {
156
- hasSideEffects = true;
157
- }
158
- // statement hook will be called for child statements too
159
- break;
160
- case "SwitchStatement":
161
- if (
162
- !parser.isPure(statement.discriminant, statement.range[0])
163
- ) {
164
- hasSideEffects = true;
165
- }
166
- // statement hook will be called for child statements too
167
- break;
168
- case "VariableDeclaration":
169
- case "ClassDeclaration":
170
- case "FunctionDeclaration":
171
- if (!parser.isPure(statement, statement.range[0])) {
172
- hasSideEffects = true;
173
- }
174
- break;
175
- case "ExportDefaultDeclaration":
176
- if (
177
- !parser.isPure(statement.declaration, statement.range[0])
178
- ) {
179
- hasSideEffects = true;
180
- }
181
- break;
182
- case "ExportNamedDeclaration":
183
- if (statement.source) {
184
- hasSideEffects = true;
185
- }
186
- break;
187
- case "LabeledStatement":
188
- case "BlockStatement":
189
- // statement hook will be called for child statements too
190
- break;
191
- case "EmptyStatement":
192
- break;
193
- case "ImportDeclaration":
194
- // imports will be handled by the dependencies
195
- break;
196
- default:
197
- hasSideEffects = true;
198
- break;
100
+ );
101
+ normalModuleFactory.hooks.module.tap(
102
+ "SideEffectsFlagPlugin",
103
+ (module, data) => {
104
+ if (data.settings.sideEffects === false) {
105
+ if (module.factoryMeta === undefined) {
106
+ module.factoryMeta = {};
107
+ }
108
+ module.factoryMeta.sideEffectFree = true;
109
+ } else if (data.settings.sideEffects === true) {
110
+ if (module.factoryMeta !== undefined) {
111
+ module.factoryMeta.sideEffectFree = false;
199
112
  }
200
113
  }
201
- );
202
- parser.hooks.finish.tap("SideEffectsFlagPlugin", () => {
203
- if (!hasSideEffects) {
204
- parser.state.module.buildMeta.sideEffectFree = true;
205
- }
206
- });
207
- };
208
- for (const key of [
209
- "javascript/auto",
210
- "javascript/esm",
211
- "javascript/dynamic"
212
- ]) {
213
- nmf.hooks.parser.for(key).tap("SideEffectsFlagPlugin", parserHandler);
114
+ return module;
115
+ }
116
+ );
117
+ if (this._analyseSource) {
118
+ /**
119
+ * @param {JavascriptParser} parser the parser
120
+ * @returns {void}
121
+ */
122
+ const parserHandler = parser => {
123
+ let sideEffectsStatement;
124
+ parser.hooks.program.tap("SideEffectsFlagPlugin", () => {
125
+ sideEffectsStatement = undefined;
126
+ });
127
+ parser.hooks.statement.tap(
128
+ { name: "SideEffectsFlagPlugin", stage: -100 },
129
+ statement => {
130
+ if (sideEffectsStatement) return;
131
+ if (parser.scope.topLevelScope !== true) return;
132
+ switch (statement.type) {
133
+ case "ExpressionStatement":
134
+ if (
135
+ !parser.isPure(statement.expression, statement.range[0])
136
+ ) {
137
+ sideEffectsStatement = statement;
138
+ }
139
+ break;
140
+ case "IfStatement":
141
+ case "WhileStatement":
142
+ case "DoWhileStatement":
143
+ if (!parser.isPure(statement.test, statement.range[0])) {
144
+ sideEffectsStatement = statement;
145
+ }
146
+ // statement hook will be called for child statements too
147
+ break;
148
+ case "ForStatement":
149
+ if (
150
+ !parser.isPure(statement.init, statement.range[0]) ||
151
+ !parser.isPure(
152
+ statement.test,
153
+ statement.init
154
+ ? statement.init.range[1]
155
+ : statement.range[0]
156
+ ) ||
157
+ !parser.isPure(
158
+ statement.update,
159
+ statement.test
160
+ ? statement.test.range[1]
161
+ : statement.init
162
+ ? statement.init.range[1]
163
+ : statement.range[0]
164
+ )
165
+ ) {
166
+ sideEffectsStatement = statement;
167
+ }
168
+ // statement hook will be called for child statements too
169
+ break;
170
+ case "SwitchStatement":
171
+ if (
172
+ !parser.isPure(statement.discriminant, statement.range[0])
173
+ ) {
174
+ sideEffectsStatement = statement;
175
+ }
176
+ // statement hook will be called for child statements too
177
+ break;
178
+ case "VariableDeclaration":
179
+ case "ClassDeclaration":
180
+ case "FunctionDeclaration":
181
+ if (!parser.isPure(statement, statement.range[0])) {
182
+ sideEffectsStatement = statement;
183
+ }
184
+ break;
185
+ case "ExportNamedDeclaration":
186
+ case "ExportDefaultDeclaration":
187
+ if (
188
+ !parser.isPure(statement.declaration, statement.range[0])
189
+ ) {
190
+ sideEffectsStatement = statement;
191
+ }
192
+ break;
193
+ case "LabeledStatement":
194
+ case "BlockStatement":
195
+ // statement hook will be called for child statements too
196
+ break;
197
+ case "EmptyStatement":
198
+ break;
199
+ case "ExportAllDeclaration":
200
+ case "ImportDeclaration":
201
+ // imports will be handled by the dependencies
202
+ break;
203
+ default:
204
+ sideEffectsStatement = statement;
205
+ break;
206
+ }
207
+ }
208
+ );
209
+ parser.hooks.finish.tap("SideEffectsFlagPlugin", () => {
210
+ if (sideEffectsStatement === undefined) {
211
+ parser.state.module.buildMeta.sideEffectFree = true;
212
+ } else {
213
+ const { loc, type } = sideEffectsStatement;
214
+ moduleGraph
215
+ .getOptimizationBailout(parser.state.module)
216
+ .push(
217
+ () =>
218
+ `Statement (${type}) with side effects in source code at ${formatLocation(
219
+ loc
220
+ )}`
221
+ );
222
+ }
223
+ });
224
+ };
225
+ for (const key of [
226
+ "javascript/auto",
227
+ "javascript/esm",
228
+ "javascript/dynamic"
229
+ ]) {
230
+ normalModuleFactory.hooks.parser
231
+ .for(key)
232
+ .tap("SideEffectsFlagPlugin", parserHandler);
233
+ }
214
234
  }
215
- }
216
- });
217
- compiler.hooks.compilation.tap("SideEffectsFlagPlugin", compilation => {
218
- const moduleGraph = compilation.moduleGraph;
219
- compilation.hooks.optimizeDependencies.tap(
220
- {
221
- name: "SideEffectsFlagPlugin",
222
- stage: STAGE_DEFAULT
223
- },
224
- modules => {
225
- const logger = compilation.getLogger("webpack.SideEffectsFlagPlugin");
235
+ compilation.hooks.optimizeDependencies.tap(
236
+ {
237
+ name: "SideEffectsFlagPlugin",
238
+ stage: STAGE_DEFAULT
239
+ },
240
+ modules => {
241
+ const logger = compilation.getLogger(
242
+ "webpack.SideEffectsFlagPlugin"
243
+ );
226
244
 
227
- logger.time("update dependencies");
228
- for (const module of modules) {
229
- if (module.getSideEffectsConnectionState(moduleGraph) === false) {
230
- const exportsInfo = moduleGraph.getExportsInfo(module);
231
- for (const connection of moduleGraph.getIncomingConnections(
232
- module
233
- )) {
234
- const dep = connection.dependency;
235
- let isReexport;
236
- if (
237
- (isReexport =
238
- dep instanceof HarmonyExportImportedSpecifierDependency) ||
239
- (dep instanceof HarmonyImportSpecifierDependency &&
240
- !dep.namespaceObjectAsContext)
241
- ) {
242
- // TODO improve for export *
243
- if (isReexport && dep.name) {
244
- const exportInfo = moduleGraph.getExportInfo(
245
- connection.originModule,
246
- dep.name
247
- );
248
- exportInfo.moveTarget(
249
- moduleGraph,
250
- ({ module }) =>
251
- module.getSideEffectsConnectionState(moduleGraph) ===
252
- false
253
- );
254
- }
255
- // TODO improve for nested imports
256
- const ids = dep.getIds(moduleGraph);
257
- if (ids.length > 0) {
258
- const exportInfo = exportsInfo.getExportInfo(ids[0]);
259
- const target = exportInfo.moveTarget(
260
- moduleGraph,
261
- ({ module }) =>
262
- module.getSideEffectsConnectionState(moduleGraph) ===
263
- false
264
- );
265
- if (!target) continue;
245
+ logger.time("update dependencies");
246
+ for (const module of modules) {
247
+ if (module.getSideEffectsConnectionState(moduleGraph) === false) {
248
+ const exportsInfo = moduleGraph.getExportsInfo(module);
249
+ for (const connection of moduleGraph.getIncomingConnections(
250
+ module
251
+ )) {
252
+ const dep = connection.dependency;
253
+ let isReexport;
254
+ if (
255
+ (isReexport =
256
+ dep instanceof
257
+ HarmonyExportImportedSpecifierDependency) ||
258
+ (dep instanceof HarmonyImportSpecifierDependency &&
259
+ !dep.namespaceObjectAsContext)
260
+ ) {
261
+ // TODO improve for export *
262
+ if (isReexport && dep.name) {
263
+ const exportInfo = moduleGraph.getExportInfo(
264
+ connection.originModule,
265
+ dep.name
266
+ );
267
+ exportInfo.moveTarget(
268
+ moduleGraph,
269
+ ({ module }) =>
270
+ module.getSideEffectsConnectionState(moduleGraph) ===
271
+ false
272
+ );
273
+ }
274
+ // TODO improve for nested imports
275
+ const ids = dep.getIds(moduleGraph);
276
+ if (ids.length > 0) {
277
+ const exportInfo = exportsInfo.getExportInfo(ids[0]);
278
+ const target = exportInfo.moveTarget(
279
+ moduleGraph,
280
+ ({ module }) =>
281
+ module.getSideEffectsConnectionState(moduleGraph) ===
282
+ false
283
+ );
284
+ if (!target) continue;
266
285
 
267
- moduleGraph.updateModule(dep, target.module);
268
- moduleGraph.addExplanation(
269
- dep,
270
- "(skipped side-effect-free modules)"
271
- );
272
- dep.setIds(
273
- moduleGraph,
274
- target.export
275
- ? [...target.export, ...ids.slice(1)]
276
- : ids.slice(1)
277
- );
286
+ moduleGraph.updateModule(dep, target.module);
287
+ moduleGraph.addExplanation(
288
+ dep,
289
+ "(skipped side-effect-free modules)"
290
+ );
291
+ dep.setIds(
292
+ moduleGraph,
293
+ target.export
294
+ ? [...target.export, ...ids.slice(1)]
295
+ : ids.slice(1)
296
+ );
297
+ }
278
298
  }
279
299
  }
280
300
  }
281
301
  }
302
+ logger.timeEnd("update dependencies");
282
303
  }
283
- logger.timeEnd("update dependencies");
284
- }
285
- );
286
- });
304
+ );
305
+ }
306
+ );
287
307
  }
288
308
 
289
309
  static moduleHasSideEffects(moduleName, flagValue, cache) {
@@ -10,9 +10,7 @@ const WebpackError = require("../WebpackError");
10
10
 
11
11
  /** @typedef {import("./SizeLimitsPlugin").AssetDetails} AssetDetails */
12
12
 
13
- module.exports = class AssetsOverSizeLimitWarning extends (
14
- WebpackError
15
- ) {
13
+ module.exports = class AssetsOverSizeLimitWarning extends WebpackError {
16
14
  /**
17
15
  * @param {AssetDetails[]} assetsOverSizeLimit the assets
18
16
  * @param {number} assetLimit the size limit
@@ -10,9 +10,7 @@ const WebpackError = require("../WebpackError");
10
10
 
11
11
  /** @typedef {import("./SizeLimitsPlugin").EntrypointDetails} EntrypointDetails */
12
12
 
13
- module.exports = class EntrypointsOverSizeLimitWarning extends (
14
- WebpackError
15
- ) {
13
+ module.exports = class EntrypointsOverSizeLimitWarning extends WebpackError {
16
14
  /**
17
15
  * @param {EntrypointDetails[]} entrypoints the entrypoints
18
16
  * @param {number} entrypointLimit the size limit
@@ -7,9 +7,7 @@
7
7
 
8
8
  const WebpackError = require("../WebpackError");
9
9
 
10
- module.exports = class NoAsyncChunksWarning extends (
11
- WebpackError
12
- ) {
10
+ module.exports = class NoAsyncChunksWarning extends WebpackError {
13
11
  constructor() {
14
12
  super(
15
13
  "webpack performance recommendations: \n" +
@@ -26,12 +26,14 @@ class GetMainFilenameRuntimeModule extends RuntimeModule {
26
26
  * @returns {string} runtime code
27
27
  */
28
28
  generate() {
29
- const { global, filename, compilation } = this;
29
+ const { global, filename, compilation, chunk } = this;
30
30
  const { runtimeTemplate } = compilation;
31
31
  const url = compilation.getPath(JSON.stringify(filename), {
32
32
  hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
33
33
  hashWithLength: length =>
34
- `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`
34
+ `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`,
35
+ chunk,
36
+ runtime: chunk.runtime
35
37
  });
36
38
  return Template.asString([
37
39
  `${global} = ${runtimeTemplate.returningFunction(url)};`
@@ -485,6 +485,10 @@ class RuntimeSpecSet {
485
485
  this._map.set(getRuntimeKey(runtime), runtime);
486
486
  }
487
487
 
488
+ has(runtime) {
489
+ return this._map.has(getRuntimeKey(runtime));
490
+ }
491
+
488
492
  [Symbol.iterator]() {
489
493
  return this._map.values();
490
494
  }
@@ -6,9 +6,7 @@
6
6
 
7
7
  const WebpackError = require("../WebpackError");
8
8
 
9
- module.exports = class UnsupportedWebAssemblyFeatureError extends (
10
- WebpackError
11
- ) {
9
+ module.exports = class UnsupportedWebAssemblyFeatureError extends WebpackError {
12
10
  /** @param {string} message Error message */
13
11
  constructor(message) {
14
12
  super(message);
@@ -79,9 +79,7 @@ const getInitialModuleChains = (
79
79
  return Array.from(results);
80
80
  };
81
81
 
82
- module.exports = class WebAssemblyInInitialChunkError extends (
83
- WebpackError
84
- ) {
82
+ module.exports = class WebAssemblyInInitialChunkError extends WebpackError {
85
83
  /**
86
84
  * @param {Module} module WASM module
87
85
  * @param {ModuleGraph} moduleGraph the module graph
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack",
3
- "version": "5.8.0",
3
+ "version": "5.9.0",
4
4
  "author": "Tobias Koppers @sokra",
5
5
  "description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.",
6
6
  "license": "MIT",
package/types.d.ts CHANGED
@@ -8060,6 +8060,7 @@ declare abstract class RuntimeSpecMap<T> {
8060
8060
  }
8061
8061
  declare abstract class RuntimeSpecSet {
8062
8062
  add(runtime?: any): void;
8063
+ has(runtime?: any): boolean;
8063
8064
  [Symbol.iterator](): IterableIterator<string | SortableSet<string>>;
8064
8065
  readonly size: number;
8065
8066
  }