webpack 5.68.0 → 5.70.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.

Files changed (63) hide show
  1. package/lib/BannerPlugin.js +10 -4
  2. package/lib/ChunkGraph.js +1 -2
  3. package/lib/CleanPlugin.js +64 -18
  4. package/lib/Compilation.js +43 -17
  5. package/lib/ContextModule.js +90 -26
  6. package/lib/ContextModuleFactory.js +65 -21
  7. package/lib/EntryOptionPlugin.js +1 -0
  8. package/lib/ExportsInfo.js +4 -4
  9. package/lib/Generator.js +1 -0
  10. package/lib/ModuleHashingError.js +29 -0
  11. package/lib/NodeStuffPlugin.js +10 -0
  12. package/lib/NormalModule.js +21 -16
  13. package/lib/NormalModuleFactory.js +40 -35
  14. package/lib/ProgressPlugin.js +4 -5
  15. package/lib/RuntimeTemplate.js +1 -0
  16. package/lib/TemplatedPathPlugin.js +48 -23
  17. package/lib/WebpackOptionsApply.js +2 -0
  18. package/lib/asset/AssetGenerator.js +122 -33
  19. package/lib/buildChunkGraph.js +1 -1
  20. package/lib/cache/ResolverCachePlugin.js +89 -28
  21. package/lib/config/browserslistTargetHandler.js +3 -5
  22. package/lib/config/defaults.js +7 -2
  23. package/lib/config/normalization.js +1 -0
  24. package/lib/css/CssLoadingRuntimeModule.js +63 -70
  25. package/lib/css/CssModulesPlugin.js +2 -1
  26. package/lib/debug/ProfilingPlugin.js +3 -4
  27. package/lib/dependencies/ContextDependencyHelpers.js +1 -1
  28. package/lib/dependencies/ContextElementDependency.js +8 -2
  29. package/lib/dependencies/ExportsInfoDependency.js +6 -0
  30. package/lib/dependencies/HarmonyAcceptImportDependency.js +5 -3
  31. package/lib/dependencies/HarmonyExportInitFragment.js +4 -1
  32. package/lib/dependencies/ImportContextDependency.js +0 -2
  33. package/lib/dependencies/ImportMetaContextDependency.js +35 -0
  34. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +252 -0
  35. package/lib/dependencies/ImportMetaContextPlugin.js +59 -0
  36. package/lib/dependencies/LoaderPlugin.js +2 -0
  37. package/lib/dependencies/RequireContextDependency.js +0 -16
  38. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +24 -8
  39. package/lib/index.js +5 -0
  40. package/lib/javascript/JavascriptModulesPlugin.js +27 -2
  41. package/lib/javascript/StartupHelpers.js +3 -2
  42. package/lib/library/AssignLibraryPlugin.js +8 -2
  43. package/lib/node/NodeTargetPlugin.js +1 -0
  44. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +22 -7
  45. package/lib/node/RequireChunkLoadingRuntimeModule.js +22 -7
  46. package/lib/optimize/ConcatenatedModule.js +10 -4
  47. package/lib/schemes/HttpUriPlugin.js +68 -6
  48. package/lib/serialization/FileMiddleware.js +44 -9
  49. package/lib/util/compileBooleanMatcher.js +1 -1
  50. package/lib/util/deterministicGrouping.js +1 -1
  51. package/lib/util/identifier.js +65 -44
  52. package/lib/util/internalSerializables.js +2 -0
  53. package/lib/util/nonNumericOnlyHash.js +22 -0
  54. package/lib/util/semver.js +17 -10
  55. package/lib/web/JsonpChunkLoadingRuntimeModule.js +15 -5
  56. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +30 -20
  57. package/module.d.ts +15 -0
  58. package/package.json +13 -13
  59. package/schemas/WebpackOptions.check.js +1 -1
  60. package/schemas/WebpackOptions.json +17 -1
  61. package/schemas/plugins/schemes/HttpUriPlugin.check.js +1 -1
  62. package/schemas/plugins/schemes/HttpUriPlugin.json +4 -0
  63. package/types.d.ts +203 -91
@@ -77,6 +77,7 @@ class BannerPlugin {
77
77
  undefined,
78
78
  options
79
79
  );
80
+ const cache = new WeakMap();
80
81
 
81
82
  compiler.hooks.compilation.tap("BannerPlugin", compilation => {
82
83
  compilation.hooks.processAssets.tap(
@@ -102,10 +103,15 @@ class BannerPlugin {
102
103
 
103
104
  const comment = compilation.getPath(banner, data);
104
105
 
105
- compilation.updateAsset(
106
- file,
107
- old => new ConcatSource(comment, "\n", old)
108
- );
106
+ compilation.updateAsset(file, old => {
107
+ let cached = cache.get(old);
108
+ if (!cached || cached.comment !== comment) {
109
+ const source = new ConcatSource(comment, "\n", old);
110
+ cache.set(old, { source, comment });
111
+ return source;
112
+ }
113
+ return cached.source;
114
+ });
109
115
  }
110
116
  }
111
117
  }
package/lib/ChunkGraph.js CHANGED
@@ -46,6 +46,7 @@ const compareModuleIterables = compareIterables(compareModulesByIdentifier);
46
46
 
47
47
  /** @typedef {(c: Chunk, chunkGraph: ChunkGraph) => boolean} ChunkFilterPredicate */
48
48
  /** @typedef {(m: Module) => boolean} ModuleFilterPredicate */
49
+ /** @typedef {[Module, Entrypoint | undefined]} EntryModuleWithChunkGroup */
49
50
 
50
51
  /**
51
52
  * @typedef {Object} ChunkSizeOptions
@@ -1180,8 +1181,6 @@ class ChunkGraph {
1180
1181
  return cgc.dependentHashModules;
1181
1182
  }
1182
1183
 
1183
- /** @typedef {[Module, Entrypoint | undefined]} EntryModuleWithChunkGroup */
1184
-
1185
1184
  /**
1186
1185
  * @param {Chunk} chunk the chunk
1187
1186
  * @returns {Iterable<EntryModuleWithChunkGroup>} iterable of modules (do not modify)
@@ -19,6 +19,7 @@ const processAsyncTree = require("./util/processAsyncTree");
19
19
  /** @typedef {import("./util/fs").StatsCallback} StatsCallback */
20
20
 
21
21
  /** @typedef {(function(string):boolean)|RegExp} IgnoreItem */
22
+ /** @typedef {Map<string, number>} Assets */
22
23
  /** @typedef {function(IgnoreItem): void} AddToIgnoreCallback */
23
24
 
24
25
  /**
@@ -40,18 +41,32 @@ const validate = createSchemaValidation(
40
41
  baseDataPath: "options"
41
42
  }
42
43
  );
44
+ const _10sec = 10 * 1000;
45
+
46
+ /**
47
+ * marge assets map 2 into map 1
48
+ * @param {Assets} as1 assets
49
+ * @param {Assets} as2 assets
50
+ * @returns {void}
51
+ */
52
+ const mergeAssets = (as1, as2) => {
53
+ for (const [key, value1] of as2) {
54
+ const value2 = as1.get(key);
55
+ if (!value2 || value1 > value2) as1.set(key, value1);
56
+ }
57
+ };
43
58
 
44
59
  /**
45
60
  * @param {OutputFileSystem} fs filesystem
46
61
  * @param {string} outputPath output path
47
- * @param {Set<string>} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator)
62
+ * @param {Map<string, number>} currentAssets filename of the current assets (must not start with .. or ., must only use / as path separator)
48
63
  * @param {function((Error | null)=, Set<string>=): void} callback returns the filenames of the assets that shouldn't be there
49
64
  * @returns {void}
50
65
  */
51
66
  const getDiffToFs = (fs, outputPath, currentAssets, callback) => {
52
67
  const directories = new Set();
53
68
  // get directories of assets
54
- for (const asset of currentAssets) {
69
+ for (const [asset] of currentAssets) {
55
70
  directories.add(asset.replace(/(^|\/)[^/]*$/, ""));
56
71
  }
57
72
  // and all parent directories
@@ -91,13 +106,15 @@ const getDiffToFs = (fs, outputPath, currentAssets, callback) => {
91
106
  };
92
107
 
93
108
  /**
94
- * @param {Set<string>} currentAssets assets list
95
- * @param {Set<string>} oldAssets old assets list
109
+ * @param {Assets} currentAssets assets list
110
+ * @param {Assets} oldAssets old assets list
96
111
  * @returns {Set<string>} diff
97
112
  */
98
113
  const getDiffToOldAssets = (currentAssets, oldAssets) => {
99
114
  const diff = new Set();
100
- for (const asset of oldAssets) {
115
+ const now = Date.now();
116
+ for (const [asset, ts] of oldAssets) {
117
+ if (ts >= now) continue;
101
118
  if (!currentAssets.has(asset)) diff.add(asset);
102
119
  }
103
120
  return diff;
@@ -124,7 +141,7 @@ const doStat = (fs, filename, callback) => {
124
141
  * @param {Logger} logger logger
125
142
  * @param {Set<string>} diff filenames of the assets that shouldn't be there
126
143
  * @param {function(string): boolean} isKept check if the entry is ignored
127
- * @param {function(Error=): void} callback callback
144
+ * @param {function(Error=, Assets=): void} callback callback
128
145
  * @returns {void}
129
146
  */
130
147
  const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => {
@@ -137,11 +154,13 @@ const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => {
137
154
  };
138
155
  /** @typedef {{ type: "check" | "unlink" | "rmdir", filename: string, parent: { remaining: number, job: Job } | undefined }} Job */
139
156
  /** @type {Job[]} */
140
- const jobs = Array.from(diff, filename => ({
157
+ const jobs = Array.from(diff.keys(), filename => ({
141
158
  type: "check",
142
159
  filename,
143
160
  parent: undefined
144
161
  }));
162
+ /** @type {Assets} */
163
+ const keptAssets = new Map();
145
164
  processAsyncTree(
146
165
  jobs,
147
166
  10,
@@ -161,6 +180,7 @@ const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => {
161
180
  switch (type) {
162
181
  case "check":
163
182
  if (isKept(filename)) {
183
+ keptAssets.set(filename, 0);
164
184
  // do not decrement parent entry as we don't want to delete the parent
165
185
  log(`${filename} will be kept`);
166
186
  return process.nextTick(callback);
@@ -247,7 +267,10 @@ const applyDiff = (fs, outputPath, dry, logger, diff, isKept, callback) => {
247
267
  break;
248
268
  }
249
269
  },
250
- callback
270
+ err => {
271
+ if (err) return callback(err);
272
+ callback(undefined, keptAssets);
273
+ }
251
274
  );
252
275
  };
253
276
 
@@ -302,6 +325,7 @@ class CleanPlugin {
302
325
  // We assume that no external modification happens while the compiler is active
303
326
  // So we can store the old assets and only diff to them to avoid fs access on
304
327
  // incremental builds
328
+ /** @type {undefined|Assets} */
305
329
  let oldAssets;
306
330
 
307
331
  compiler.hooks.emit.tapAsync(
@@ -322,7 +346,9 @@ class CleanPlugin {
322
346
  );
323
347
  }
324
348
 
325
- const currentAssets = new Set();
349
+ /** @type {Assets} */
350
+ const currentAssets = new Map();
351
+ const now = Date.now();
326
352
  for (const asset of Object.keys(compilation.assets)) {
327
353
  if (/^[A-Za-z]:\\|^\/|^\\\\/.test(asset)) continue;
328
354
  let normalizedAsset;
@@ -335,7 +361,12 @@ class CleanPlugin {
335
361
  );
336
362
  } while (newNormalizedAsset !== normalizedAsset);
337
363
  if (normalizedAsset.startsWith("../")) continue;
338
- currentAssets.add(normalizedAsset);
364
+ const assetInfo = compilation.assetsInfo.get(asset);
365
+ if (assetInfo && assetInfo.hotModuleReplacement) {
366
+ currentAssets.set(normalizedAsset, now + _10sec);
367
+ } else {
368
+ currentAssets.set(normalizedAsset, 0);
369
+ }
339
370
  }
340
371
 
341
372
  const outputPath = compilation.getPath(compiler.outputPath, {});
@@ -346,19 +377,34 @@ class CleanPlugin {
346
377
  return keepFn(path);
347
378
  };
348
379
 
380
+ /**
381
+ * @param {Error=} err err
382
+ * @param {Set<string>=} diff diff
383
+ */
349
384
  const diffCallback = (err, diff) => {
350
385
  if (err) {
351
386
  oldAssets = undefined;
352
- return callback(err);
387
+ callback(err);
388
+ return;
353
389
  }
354
- applyDiff(fs, outputPath, dry, logger, diff, isKept, err => {
355
- if (err) {
356
- oldAssets = undefined;
357
- } else {
358
- oldAssets = currentAssets;
390
+ applyDiff(
391
+ fs,
392
+ outputPath,
393
+ dry,
394
+ logger,
395
+ diff,
396
+ isKept,
397
+ (err, keptAssets) => {
398
+ if (err) {
399
+ oldAssets = undefined;
400
+ } else {
401
+ if (oldAssets) mergeAssets(currentAssets, oldAssets);
402
+ oldAssets = currentAssets;
403
+ if (keptAssets) mergeAssets(oldAssets, keptAssets);
404
+ }
405
+ callback(err);
359
406
  }
360
- callback(err);
361
- });
407
+ );
362
408
  };
363
409
 
364
410
  if (oldAssets) {
@@ -43,6 +43,7 @@ const Module = require("./Module");
43
43
  const ModuleDependencyError = require("./ModuleDependencyError");
44
44
  const ModuleDependencyWarning = require("./ModuleDependencyWarning");
45
45
  const ModuleGraph = require("./ModuleGraph");
46
+ const ModuleHashingError = require("./ModuleHashingError");
46
47
  const ModuleNotFoundError = require("./ModuleNotFoundError");
47
48
  const ModuleProfile = require("./ModuleProfile");
48
49
  const ModuleRestoreError = require("./ModuleRestoreError");
@@ -182,6 +183,7 @@ const { isSourceEqual } = require("./util/source");
182
183
 
183
184
  /**
184
185
  * @typedef {Object} ChunkHashContext
186
+ * @property {CodeGenerationResults} codeGenerationResults results of code generation
185
187
  * @property {RuntimeTemplate} runtimeTemplate the runtime template
186
188
  * @property {ModuleGraph} moduleGraph the module graph
187
189
  * @property {ChunkGraph} chunkGraph the chunk graph
@@ -3882,6 +3884,7 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
3882
3884
  let statModulesFromCache = 0;
3883
3885
  const { chunkGraph, runtimeTemplate, moduleMemCaches2 } = this;
3884
3886
  const { hashFunction, hashDigest, hashDigestLength } = this.outputOptions;
3887
+ const errors = [];
3885
3888
  for (const module of this.modules) {
3886
3889
  const memCache = moduleMemCaches2 && moduleMemCaches2.get(module);
3887
3890
  for (const runtime of chunkGraph.getModuleRuntimes(module)) {
@@ -3906,13 +3909,20 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
3906
3909
  hashFunction,
3907
3910
  runtimeTemplate,
3908
3911
  hashDigest,
3909
- hashDigestLength
3912
+ hashDigestLength,
3913
+ errors
3910
3914
  );
3911
3915
  if (memCache) {
3912
3916
  memCache.set(`moduleHash-${getRuntimeKey(runtime)}`, digest);
3913
3917
  }
3914
3918
  }
3915
3919
  }
3920
+ if (errors.length > 0) {
3921
+ errors.sort(compareSelect(err => err.module, compareModulesByIdentifier));
3922
+ for (const error of errors) {
3923
+ this.errors.push(error);
3924
+ }
3925
+ }
3916
3926
  this.logger.log(
3917
3927
  `${statModulesHashed} modules hashed, ${statModulesFromCache} from cache (${
3918
3928
  Math.round(
@@ -3929,17 +3939,22 @@ Or do you want to use the entrypoints '${name}' and '${runtime}' independently o
3929
3939
  hashFunction,
3930
3940
  runtimeTemplate,
3931
3941
  hashDigest,
3932
- hashDigestLength
3942
+ hashDigestLength,
3943
+ errors
3933
3944
  ) {
3934
- const moduleHash = createHash(hashFunction);
3935
- module.updateHash(moduleHash, {
3936
- chunkGraph,
3937
- runtime,
3938
- runtimeTemplate
3939
- });
3940
- const moduleHashDigest = /** @type {string} */ (
3941
- moduleHash.digest(hashDigest)
3942
- );
3945
+ let moduleHashDigest;
3946
+ try {
3947
+ const moduleHash = createHash(hashFunction);
3948
+ module.updateHash(moduleHash, {
3949
+ chunkGraph,
3950
+ runtime,
3951
+ runtimeTemplate
3952
+ });
3953
+ moduleHashDigest = /** @type {string} */ (moduleHash.digest(hashDigest));
3954
+ } catch (err) {
3955
+ errors.push(new ModuleHashingError(module, err));
3956
+ moduleHashDigest = "XXXXXX";
3957
+ }
3943
3958
  chunkGraph.setModuleHashes(
3944
3959
  module,
3945
3960
  runtime,
@@ -4090,6 +4105,7 @@ This prevents using hashes of each other and should be avoided.`);
4090
4105
  const codeGenerationJobs = [];
4091
4106
  /** @type {Map<string, Map<Module, {module: Module, hash: string, runtime: RuntimeSpec, runtimes: RuntimeSpec[]}>>} */
4092
4107
  const codeGenerationJobsMap = new Map();
4108
+ const errors = [];
4093
4109
 
4094
4110
  const processChunk = chunk => {
4095
4111
  // Last minute module hash generation for modules that depend on chunk hashes
@@ -4104,7 +4120,8 @@ This prevents using hashes of each other and should be avoided.`);
4104
4120
  hashFunction,
4105
4121
  runtimeTemplate,
4106
4122
  hashDigest,
4107
- hashDigestLength
4123
+ hashDigestLength,
4124
+ errors
4108
4125
  );
4109
4126
  let hashMap = codeGenerationJobsMap.get(hash);
4110
4127
  if (hashMap) {
@@ -4128,15 +4145,16 @@ This prevents using hashes of each other and should be avoided.`);
4128
4145
  }
4129
4146
  }
4130
4147
  this.logger.timeAggregate("hashing: hash runtime modules");
4131
- this.logger.time("hashing: hash chunks");
4132
- const chunkHash = createHash(hashFunction);
4133
4148
  try {
4149
+ this.logger.time("hashing: hash chunks");
4150
+ const chunkHash = createHash(hashFunction);
4134
4151
  if (outputOptions.hashSalt) {
4135
4152
  chunkHash.update(outputOptions.hashSalt);
4136
4153
  }
4137
4154
  chunk.updateHash(chunkHash, chunkGraph);
4138
4155
  this.hooks.chunkHash.call(chunk, chunkHash, {
4139
4156
  chunkGraph,
4157
+ codeGenerationResults: this.codeGenerationResults,
4140
4158
  moduleGraph: this.moduleGraph,
4141
4159
  runtimeTemplate: this.runtimeTemplate
4142
4160
  });
@@ -4160,6 +4178,12 @@ This prevents using hashes of each other and should be avoided.`);
4160
4178
  };
4161
4179
  otherChunks.forEach(processChunk);
4162
4180
  for (const chunk of runtimeChunks) processChunk(chunk);
4181
+ if (errors.length > 0) {
4182
+ errors.sort(compareSelect(err => err.module, compareModulesByIdentifier));
4183
+ for (const error of errors) {
4184
+ this.errors.push(error);
4185
+ }
4186
+ }
4163
4187
 
4164
4188
  this.logger.timeAggregateEnd("hashing: hash runtime modules");
4165
4189
  this.logger.timeAggregateEnd("hashing: hash chunks");
@@ -4799,6 +4823,9 @@ This prevents using hashes of each other and should be avoided.`);
4799
4823
  chunkGraph.connectChunkAndModule(chunk, module);
4800
4824
  }
4801
4825
 
4826
+ /** @type {WebpackError[]} */
4827
+ const errors = [];
4828
+
4802
4829
  // Hash modules
4803
4830
  for (const module of modules) {
4804
4831
  this._createModuleHash(
@@ -4808,15 +4835,14 @@ This prevents using hashes of each other and should be avoided.`);
4808
4835
  hashFunction,
4809
4836
  runtimeTemplate,
4810
4837
  hashDigest,
4811
- hashDigestLength
4838
+ hashDigestLength,
4839
+ errors
4812
4840
  );
4813
4841
  }
4814
4842
 
4815
4843
  const codeGenerationResults = new CodeGenerationResults(
4816
4844
  this.outputOptions.hashFunction
4817
4845
  );
4818
- /** @type {WebpackError[]} */
4819
- const errors = [];
4820
4846
  /**
4821
4847
  * @param {Module} module the module
4822
4848
  * @param {Callback} callback callback
@@ -61,7 +61,7 @@ const makeSerializable = require("./util/makeSerializable");
61
61
 
62
62
  /**
63
63
  * @typedef {Object} ContextModuleOptionsExtras
64
- * @property {string} resource
64
+ * @property {false|string|string[]} resource
65
65
  * @property {string=} resourceQuery
66
66
  * @property {string=} resourceFragment
67
67
  * @property {TODO} resolveOptions
@@ -92,23 +92,36 @@ class ContextModule extends Module {
92
92
  * @param {ContextModuleOptions} options options object
93
93
  */
94
94
  constructor(resolveDependencies, options) {
95
- const parsed = parseResource(options ? options.resource : "");
96
- const resource = parsed.path;
97
- const resourceQuery = (options && options.resourceQuery) || parsed.query;
98
- const resourceFragment =
99
- (options && options.resourceFragment) || parsed.fragment;
100
-
101
- super("javascript/dynamic", resource);
95
+ if (!options || typeof options.resource === "string") {
96
+ const parsed = parseResource(
97
+ options ? /** @type {string} */ (options.resource) : ""
98
+ );
99
+ const resource = parsed.path;
100
+ const resourceQuery = (options && options.resourceQuery) || parsed.query;
101
+ const resourceFragment =
102
+ (options && options.resourceFragment) || parsed.fragment;
103
+
104
+ super("javascript/dynamic", resource);
105
+ /** @type {ContextModuleOptions} */
106
+ this.options = {
107
+ ...options,
108
+ resource,
109
+ resourceQuery,
110
+ resourceFragment
111
+ };
112
+ } else {
113
+ super("javascript/dynamic");
114
+ /** @type {ContextModuleOptions} */
115
+ this.options = {
116
+ ...options,
117
+ resource: options.resource,
118
+ resourceQuery: options.resourceQuery || "",
119
+ resourceFragment: options.resourceFragment || ""
120
+ };
121
+ }
102
122
 
103
123
  // Info from Factory
104
124
  this.resolveDependencies = resolveDependencies;
105
- /** @type {ContextModuleOptions} */
106
- this.options = {
107
- ...options,
108
- resource,
109
- resourceQuery,
110
- resourceFragment
111
- };
112
125
  if (options && options.resolveOptions !== undefined) {
113
126
  this.resolveOptions = options.resolveOptions;
114
127
  }
@@ -155,7 +168,12 @@ class ContextModule extends Module {
155
168
  }
156
169
 
157
170
  _createIdentifier() {
158
- let identifier = this.context;
171
+ let identifier =
172
+ this.context ||
173
+ (typeof this.options.resource === "string" ||
174
+ this.options.resource === false
175
+ ? `${this.options.resource}`
176
+ : this.options.resource.join("|"));
159
177
  if (this.options.resourceQuery) {
160
178
  identifier += `|${this.options.resourceQuery}`;
161
179
  }
@@ -220,7 +238,19 @@ class ContextModule extends Module {
220
238
  * @returns {string} a user readable identifier of the module
221
239
  */
222
240
  readableIdentifier(requestShortener) {
223
- let identifier = requestShortener.shorten(this.context) + "/";
241
+ let identifier;
242
+ if (this.context) {
243
+ identifier = requestShortener.shorten(this.context) + "/";
244
+ } else if (
245
+ typeof this.options.resource === "string" ||
246
+ this.options.resource === false
247
+ ) {
248
+ identifier = requestShortener.shorten(`${this.options.resource}`) + "/";
249
+ } else {
250
+ identifier = this.options.resource
251
+ .map(r => requestShortener.shorten(r) + "/")
252
+ .join(" ");
253
+ }
224
254
  if (this.options.resourceQuery) {
225
255
  identifier += ` ${this.options.resourceQuery}`;
226
256
  }
@@ -270,11 +300,30 @@ class ContextModule extends Module {
270
300
  * @returns {string | null} an identifier for library inclusion
271
301
  */
272
302
  libIdent(options) {
273
- let identifier = contextify(
274
- options.context,
275
- this.context,
276
- options.associatedObjectForCache
277
- );
303
+ let identifier;
304
+
305
+ if (this.context) {
306
+ identifier = contextify(
307
+ options.context,
308
+ this.context,
309
+ options.associatedObjectForCache
310
+ );
311
+ } else if (typeof this.options.resource === "string") {
312
+ identifier = contextify(
313
+ options.context,
314
+ this.options.resource,
315
+ options.associatedObjectForCache
316
+ );
317
+ } else if (this.options.resource === false) {
318
+ identifier = "false";
319
+ } else {
320
+ identifier = this.options.resource
321
+ .map(res =>
322
+ contextify(options.context, res, options.associatedObjectForCache)
323
+ )
324
+ .join(" ");
325
+ }
326
+
278
327
  if (this.layer) identifier = `(${this.layer})/${identifier}`;
279
328
  if (this.options.mode) {
280
329
  identifier += ` ${this.options.mode}`;
@@ -323,8 +372,9 @@ class ContextModule extends Module {
323
372
  // build if enforced
324
373
  if (this._forceBuild) return callback(null, true);
325
374
 
326
- // always build when we have no snapshot
327
- if (!this.buildInfo.snapshot) return callback(null, true);
375
+ // always build when we have no snapshot and context
376
+ if (!this.buildInfo.snapshot)
377
+ return callback(null, Boolean(this.context || this.options.resource));
328
378
 
329
379
  fileSystemInfo.checkSnapshotValid(this.buildInfo.snapshot, (err, valid) => {
330
380
  callback(err, !valid);
@@ -439,10 +489,16 @@ class ContextModule extends Module {
439
489
  );
440
490
  return;
441
491
  }
492
+ if (!this.context && !this.options.resource) return callback();
493
+
442
494
  compilation.fileSystemInfo.createSnapshot(
443
495
  startTime,
444
496
  null,
445
- [this.context],
497
+ this.context
498
+ ? [this.context]
499
+ : typeof this.options.resource === "string"
500
+ ? [this.options.resource]
501
+ : /** @type {string[]} */ (this.options.resource),
446
502
  null,
447
503
  SNAPSHOT_OPTIONS,
448
504
  (err, snapshot) => {
@@ -466,7 +522,15 @@ class ContextModule extends Module {
466
522
  missingDependencies,
467
523
  buildDependencies
468
524
  ) {
469
- contextDependencies.add(this.context);
525
+ if (this.context) {
526
+ contextDependencies.add(this.context);
527
+ } else if (typeof this.options.resource === "string") {
528
+ contextDependencies.add(this.options.resource);
529
+ } else if (this.options.resource === false) {
530
+ return;
531
+ } else {
532
+ for (const res of this.options.resource) contextDependencies.add(res);
533
+ }
470
534
  }
471
535
 
472
536
  /**