webpack 4.1.0 → 4.4.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.
Files changed (118) hide show
  1. package/README.md +719 -721
  2. package/bin/webpack.js +69 -10
  3. package/lib/APIPlugin.js +84 -84
  4. package/lib/AmdMainTemplatePlugin.js +75 -77
  5. package/lib/AsyncDependencyToInitialChunkError.js +21 -23
  6. package/lib/BannerPlugin.js +101 -101
  7. package/lib/Chunk.js +477 -469
  8. package/lib/ChunkTemplate.js +51 -53
  9. package/lib/Compilation.js +1858 -1851
  10. package/lib/Compiler.js +493 -478
  11. package/lib/ConcurrentCompilationError.js +19 -0
  12. package/lib/ContextModule.js +696 -685
  13. package/lib/ContextModuleFactory.js +245 -243
  14. package/lib/DefinePlugin.js +197 -197
  15. package/lib/DelegatedModule.js +101 -101
  16. package/lib/DependenciesBlockVariable.js +51 -52
  17. package/lib/Dependency.js +53 -52
  18. package/lib/DllModule.js +54 -54
  19. package/lib/DllModuleFactory.js +29 -29
  20. package/lib/EnvironmentPlugin.js +65 -67
  21. package/lib/EvalDevToolModuleTemplatePlugin.js +60 -60
  22. package/lib/EvalSourceMapDevToolModuleTemplatePlugin.js +105 -105
  23. package/lib/ExportPropertyMainTemplatePlugin.js +40 -40
  24. package/lib/ExternalModule.js +159 -159
  25. package/lib/FunctionModuleTemplatePlugin.js +98 -98
  26. package/lib/HotModuleReplacement.runtime.js +631 -631
  27. package/lib/HotModuleReplacementPlugin.js +407 -406
  28. package/lib/HotUpdateChunkTemplate.js +78 -80
  29. package/lib/JavascriptGenerator.js +228 -229
  30. package/lib/JavascriptModulesPlugin.js +184 -158
  31. package/lib/JsonGenerator.js +42 -42
  32. package/lib/MainTemplate.js +406 -402
  33. package/lib/Module.js +343 -340
  34. package/lib/ModuleBuildError.js +42 -42
  35. package/lib/ModuleError.js +28 -28
  36. package/lib/ModuleFilenameHelpers.js +166 -166
  37. package/lib/ModuleTemplate.js +77 -79
  38. package/lib/ModuleWarning.js +30 -30
  39. package/lib/MultiCompiler.js +271 -259
  40. package/lib/MultiModule.js +78 -75
  41. package/lib/MultiModuleFactory.js +23 -23
  42. package/lib/MultiWatching.js +38 -37
  43. package/lib/NoModeWarning.js +23 -21
  44. package/lib/NormalModule.js +478 -470
  45. package/lib/NormalModuleFactory.js +483 -481
  46. package/lib/OptionsDefaulter.js +80 -86
  47. package/lib/Parser.js +2074 -2071
  48. package/lib/ProgressPlugin.js +231 -231
  49. package/lib/RawModule.js +54 -55
  50. package/lib/RecordIdsPlugin.js +160 -160
  51. package/lib/RemovedPluginError.js +13 -13
  52. package/lib/ResolverFactory.js +64 -67
  53. package/lib/RuntimeTemplate.js +267 -297
  54. package/lib/SetVarMainTemplatePlugin.js +57 -57
  55. package/lib/SourceMapDevToolPlugin.js +302 -308
  56. package/lib/Stats.js +1234 -1212
  57. package/lib/Template.js +205 -205
  58. package/lib/TemplatedPathPlugin.js +170 -143
  59. package/lib/UmdMainTemplatePlugin.js +264 -269
  60. package/lib/Watching.js +193 -193
  61. package/lib/WebAssemblyParser.js +50 -54
  62. package/lib/WebpackOptionsApply.js +401 -401
  63. package/lib/WebpackOptionsDefaulter.js +337 -317
  64. package/lib/WebpackOptionsValidationError.js +316 -319
  65. package/lib/debug/ProfilingPlugin.js +409 -405
  66. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +328 -311
  67. package/lib/dependencies/AMDRequireContextDependency.js +20 -20
  68. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +270 -241
  69. package/lib/dependencies/HarmonyAcceptImportDependency.js +23 -23
  70. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +620 -606
  71. package/lib/dependencies/HarmonyExportSpecifierDependency.js +53 -53
  72. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +214 -214
  73. package/lib/dependencies/HarmonyImportSpecifierDependency.js +154 -156
  74. package/lib/dependencies/ImportDependenciesBlock.js +17 -17
  75. package/lib/dependencies/ImportDependency.js +34 -34
  76. package/lib/dependencies/ImportEagerDependency.js +32 -32
  77. package/lib/dependencies/ImportParserPlugin.js +175 -179
  78. package/lib/dependencies/ImportWeakDependency.js +34 -34
  79. package/lib/dependencies/JsonExportsDependency.js +25 -25
  80. package/lib/dependencies/ModuleDependency.js +20 -20
  81. package/lib/dependencies/NullDependency.js +20 -20
  82. package/lib/dependencies/RequireContextDependency.js +22 -22
  83. package/lib/dependencies/RequireIncludeDependency.js +40 -40
  84. package/lib/dependencies/WebpackMissingModule.js +20 -22
  85. package/lib/node/NodeChunkTemplatePlugin.js +31 -31
  86. package/lib/node/NodeHotUpdateChunkTemplatePlugin.js +36 -36
  87. package/lib/node/NodeMainTemplatePlugin.js +320 -273
  88. package/lib/node/ReadFileCompileWasmMainTemplatePlugin.js +113 -115
  89. package/lib/optimize/AggressiveSplittingPlugin.js +281 -281
  90. package/lib/optimize/ConcatenatedModule.js +1364 -1366
  91. package/lib/optimize/RemoveParentModulesPlugin.js +114 -114
  92. package/lib/optimize/SplitChunksPlugin.js +519 -491
  93. package/lib/performance/SizeLimitsPlugin.js +105 -105
  94. package/lib/util/TrackingSet.js +35 -35
  95. package/lib/util/objectToMap.js +10 -10
  96. package/lib/wasm/WasmModuleTemplatePlugin.js +106 -106
  97. package/lib/web/JsonpChunkTemplatePlugin.js +47 -47
  98. package/lib/web/JsonpExportMainTemplatePlugin.js +47 -47
  99. package/lib/web/JsonpHotUpdateChunkTemplatePlugin.js +39 -39
  100. package/lib/web/JsonpMainTemplatePlugin.js +425 -403
  101. package/lib/webpack.js +182 -179
  102. package/lib/webworker/WebWorkerChunkTemplatePlugin.js +35 -35
  103. package/lib/webworker/WebWorkerHotUpdateChunkTemplatePlugin.js +40 -40
  104. package/lib/webworker/WebWorkerMainTemplatePlugin.js +177 -154
  105. package/package.json +9 -8
  106. package/schemas/WebpackOptions.json +1973 -1951
  107. package/schemas/ajv.absolutePath.js +55 -29
  108. package/schemas/plugins/BannerPlugin.json +85 -85
  109. package/schemas/plugins/DllPlugin.json +28 -28
  110. package/schemas/plugins/DllReferencePlugin.json +99 -99
  111. package/schemas/plugins/HashedModuleIdsPlugin.json +24 -24
  112. package/schemas/plugins/LoaderOptionsPlugin.json +26 -26
  113. package/schemas/plugins/SourceMapDevToolPlugin.json +187 -187
  114. package/schemas/plugins/WatchIgnorePlugin.json +16 -16
  115. package/schemas/plugins/debug/ProfilingPlugin.json +12 -12
  116. package/schemas/plugins/optimize/AggressiveSplittingPlugin.json +22 -22
  117. package/schemas/plugins/optimize/LimitChunkCountPlugin.json +15 -15
  118. package/schemas/plugins/optimize/MinChunkSizePlugin.json +13 -13
@@ -1,491 +1,519 @@
1
- /*
2
- MIT License http://www.opensource.org/licenses/mit-license.php
3
- Author Tobias Koppers @sokra
4
- */
5
- "use strict";
6
-
7
- const crypto = require("crypto");
8
- const SortableSet = require("../util/SortableSet");
9
- const GraphHelpers = require("../GraphHelpers");
10
- const isSubset = require("../util/SetHelpers").isSubset;
11
-
12
- const hashFilename = name => {
13
- return crypto
14
- .createHash("md4")
15
- .update(name)
16
- .digest("hex")
17
- .slice(0, 8);
18
- };
19
-
20
- const sortByIdentifier = (a, b) => {
21
- if (a.identifier() > b.identifier()) return 1;
22
- if (a.identifier() < b.identifier()) return -1;
23
- return 0;
24
- };
25
-
26
- const getRequests = chunk => {
27
- let requests = 0;
28
- for (const chunkGroup of chunk.groupsIterable) {
29
- requests = Math.max(requests, chunkGroup.chunks.length);
30
- }
31
- return requests;
32
- };
33
-
34
- const getModulesSize = modules => {
35
- let sum = 0;
36
- for (const m of modules) sum += m.size();
37
- return sum;
38
- };
39
-
40
- const isOverlap = (a, b) => {
41
- for (const item of a.keys()) {
42
- if (b.has(item)) return true;
43
- }
44
- return false;
45
- };
46
-
47
- const compareEntries = (a, b) => {
48
- // 1. by priority
49
- const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
50
- if (diffPriority) return diffPriority;
51
- // 2. by number of chunks
52
- const diffCount = a.chunks.size - b.chunks.size;
53
- if (diffCount) return diffCount;
54
- // 3. by size reduction
55
- const aSizeReduce = a.size * (a.chunks.size - 1);
56
- const bSizeReduce = b.size * (b.chunks.size - 1);
57
- const diffSizeReduce = aSizeReduce - bSizeReduce;
58
- if (diffSizeReduce) return diffSizeReduce;
59
- // 4. by number of modules (to be able to compare by identifier)
60
- const modulesA = a.modules;
61
- const modulesB = b.modules;
62
- const diff = modulesA.size - modulesB.size;
63
- if (diff) return diff;
64
- // 5. by module identifiers
65
- modulesA.sort();
66
- modulesB.sort();
67
- const aI = modulesA[Symbol.iterator]();
68
- const bI = modulesB[Symbol.iterator]();
69
- // eslint-disable-next-line no-constant-condition
70
- while (true) {
71
- const aItem = aI.next();
72
- const bItem = bI.next();
73
- if (aItem.done) return 0;
74
- const aModuleIdentifier = aItem.value.identifier();
75
- const bModuleIdentifier = bItem.value.identifier();
76
- if (aModuleIdentifier > bModuleIdentifier) return -1;
77
- if (aModuleIdentifier < bModuleIdentifier) return 1;
78
- }
79
- };
80
-
81
- module.exports = class SplitChunksPlugin {
82
- constructor(options) {
83
- this.options = SplitChunksPlugin.normalizeOptions(options);
84
- }
85
-
86
- static normalizeOptions(options = {}) {
87
- return {
88
- chunks: options.chunks || "all",
89
- minSize: options.minSize || 0,
90
- minChunks: options.minChunks || 1,
91
- maxAsyncRequests: options.maxAsyncRequests || 1,
92
- maxInitialRequests: options.maxInitialRequests || 1,
93
- getName: SplitChunksPlugin.normalizeName(options.name) || (() => {}),
94
- filename: options.filename || undefined,
95
- getCacheGroups: SplitChunksPlugin.normalizeCacheGroups(
96
- options.cacheGroups
97
- )
98
- };
99
- }
100
-
101
- static normalizeName(option) {
102
- if (option === true) {
103
- const fn = (module, chunks, cacheGroup) => {
104
- const names = chunks.map(c => c.name);
105
- if (!names.every(Boolean)) return;
106
- names.sort();
107
- let name =
108
- (cacheGroup && cacheGroup !== "default" ? cacheGroup + "~" : "") +
109
- names.join("~");
110
- // Filenames and paths can't be too long otherwise an
111
- // ENAMETOOLONG error is raised. If the generated name if too
112
- // long, it is truncated and a hash is appended. The limit has
113
- // been set to 100 to prevent `[name].[chunkhash].[ext]` from
114
- // generating a 256+ character string.
115
- if (name.length > 100) {
116
- name = name.slice(0, 100) + "~" + hashFilename(name);
117
- }
118
- return name;
119
- };
120
- return fn;
121
- }
122
- if (typeof option === "string") {
123
- const fn = () => {
124
- return option;
125
- };
126
- return fn;
127
- }
128
- if (typeof option === "function") return option;
129
- }
130
-
131
- static normalizeCacheGroups(cacheGroups) {
132
- if (typeof cacheGroups === "function") {
133
- return cacheGroups;
134
- }
135
- if (cacheGroups && typeof cacheGroups === "object") {
136
- const fn = (module, chunks) => {
137
- let results;
138
- for (const key of Object.keys(cacheGroups)) {
139
- let option = cacheGroups[key];
140
- if (option === false) continue;
141
- if (option instanceof RegExp || typeof option === "string") {
142
- option = {
143
- test: option
144
- };
145
- }
146
- if (typeof option === "function") {
147
- let result = option(module);
148
- if (result) {
149
- if (results === undefined) results = [];
150
- for (const r of Array.isArray(result) ? result : [result]) {
151
- const result = Object.assign(
152
- {
153
- key
154
- },
155
- r
156
- );
157
- if (result.name) result.getName = () => result.name;
158
- results.push(result);
159
- }
160
- }
161
- } else if (SplitChunksPlugin.checkTest(option.test, module, chunks)) {
162
- if (results === undefined) results = [];
163
- results.push({
164
- key: key,
165
- priority: option.priority,
166
- getName: SplitChunksPlugin.normalizeName(option.name),
167
- chunks: option.chunks,
168
- enforce: option.enforce,
169
- minSize: option.minSize,
170
- minChunks: option.minChunks,
171
- maxAsyncRequests: option.maxAsyncRequests,
172
- maxInitialRequests: option.maxInitialRequests,
173
- filename: option.filename,
174
- reuseExistingChunk: option.reuseExistingChunk
175
- });
176
- }
177
- }
178
- return results;
179
- };
180
- return fn;
181
- }
182
- const fn = () => {};
183
- return fn;
184
- }
185
-
186
- static checkTest(test, module, chunks) {
187
- if (test === undefined) return true;
188
- if (typeof test === "function") return test(module, chunks);
189
- if (typeof test === "boolean") return test;
190
- const names = chunks
191
- .map(c => c.name)
192
- .concat(module.nameForCondition ? [module.nameForCondition()] : [])
193
- .filter(Boolean);
194
- if (typeof test === "string") {
195
- for (const name of names) if (name.startsWith(test)) return true;
196
- return false;
197
- }
198
- if (test instanceof RegExp) {
199
- for (const name of names) if (test.test(name)) return true;
200
- return false;
201
- }
202
- return false;
203
- }
204
-
205
- apply(compiler) {
206
- compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
207
- let alreadyOptimized = false;
208
- compilation.hooks.unseal.tap("SplitChunksPlugin", () => {
209
- alreadyOptimized = false;
210
- });
211
- compilation.hooks.optimizeChunksAdvanced.tap(
212
- "SplitChunksPlugin",
213
- chunks => {
214
- if (alreadyOptimized) return;
215
- alreadyOptimized = true;
216
- // Give each selected chunk an index (to create strings from chunks)
217
- const indexMap = new Map();
218
- let index = 1;
219
- for (const chunk of chunks) {
220
- indexMap.set(chunk, index++);
221
- }
222
- const getKey = chunks => {
223
- return Array.from(chunks, c => indexMap.get(c))
224
- .sort()
225
- .join();
226
- };
227
- // Create a list of possible combinations
228
- const chunkSetsInGraph = new Map(); // Map<string, Set<Chunk>>
229
- for (const module of compilation.modules) {
230
- const chunkIndices = getKey(module.chunksIterable);
231
- chunkSetsInGraph.set(chunkIndices, new Set(module.chunksIterable));
232
- }
233
- const combinations = new Map(); // Map<string, Set<Chunk>[]>
234
- for (const [key, chunksSet] of chunkSetsInGraph) {
235
- var array = [];
236
- for (const set of chunkSetsInGraph.values()) {
237
- if (isSubset(chunksSet, set)) {
238
- array.push(set);
239
- }
240
- }
241
- combinations.set(key, array);
242
- }
243
- // Map a list of chunks to a list of modules
244
- // For the key the chunk "index" is used, the value is a SortableSet of modules
245
- const chunksInfoMap = new Map();
246
- // Walk through all modules
247
- for (const module of compilation.modules) {
248
- // Get array of chunks
249
- const chunks = module.getChunks();
250
- // Get cache group
251
- let cacheGroups = this.options.getCacheGroups(module, chunks);
252
- if (!Array.isArray(cacheGroups)) continue;
253
- for (const cacheGroupSource of cacheGroups) {
254
- const cacheGroup = {
255
- key: cacheGroupSource.key,
256
- priority: cacheGroupSource.priority || 0,
257
- chunks: cacheGroupSource.chunks || this.options.chunks,
258
- minSize:
259
- cacheGroupSource.minSize !== undefined
260
- ? cacheGroupSource.minSize
261
- : cacheGroupSource.enforce ? 0 : this.options.minSize,
262
- minChunks:
263
- cacheGroupSource.minChunks !== undefined
264
- ? cacheGroupSource.minChunks
265
- : cacheGroupSource.enforce ? 1 : this.options.minChunks,
266
- maxAsyncRequests:
267
- cacheGroupSource.maxAsyncRequests !== undefined
268
- ? cacheGroupSource.maxAsyncRequests
269
- : cacheGroupSource.enforce
270
- ? Infinity
271
- : this.options.maxAsyncRequests,
272
- maxInitialRequests:
273
- cacheGroupSource.maxInitialRequests !== undefined
274
- ? cacheGroupSource.maxInitialRequests
275
- : cacheGroupSource.enforce
276
- ? Infinity
277
- : this.options.maxInitialRequests,
278
- getName:
279
- cacheGroupSource.getName !== undefined
280
- ? cacheGroupSource.getName
281
- : this.options.getName,
282
- filename:
283
- cacheGroupSource.filename !== undefined
284
- ? cacheGroupSource.filename
285
- : this.options.filename,
286
- reuseExistingChunk: cacheGroupSource.reuseExistingChunk
287
- };
288
- // For all combination of chunk selection
289
- for (const chunkCombination of combinations.get(getKey(chunks))) {
290
- // Get indices of chunks in which this module occurs
291
- const chunkIndices = Array.from(chunkCombination, chunk =>
292
- indexMap.get(chunk)
293
- );
294
- // Break if minimum number of chunks is not reached
295
- if (chunkIndices.length < cacheGroup.minChunks) continue;
296
- // Select chunks by configuration
297
- const selectedChunks =
298
- cacheGroup.chunks === "initial"
299
- ? Array.from(chunkCombination).filter(chunk =>
300
- chunk.canBeInitial()
301
- )
302
- : cacheGroup.chunks === "async"
303
- ? Array.from(chunkCombination).filter(
304
- chunk => !chunk.canBeInitial()
305
- )
306
- : Array.from(chunkCombination);
307
- // Determine name for split chunk
308
- const name = cacheGroup.getName(
309
- module,
310
- selectedChunks,
311
- cacheGroup.key
312
- );
313
- // Create key for maps
314
- // When it has a name we use the name as key
315
- // Elsewise we create the key from chunks and cache group key
316
- // This automatically merges equal names
317
- const chunksKey = getKey(selectedChunks);
318
- const key =
319
- (name && `name:${name}`) ||
320
- `chunks:${chunksKey} key:${cacheGroup.key}`;
321
- // Add module to maps
322
- let info = chunksInfoMap.get(key);
323
- if (info === undefined) {
324
- chunksInfoMap.set(
325
- key,
326
- (info = {
327
- modules: new SortableSet(undefined, sortByIdentifier),
328
- cacheGroup,
329
- name,
330
- chunks: new Map(),
331
- reusedableChunks: new Set(),
332
- chunksKeys: new Set()
333
- })
334
- );
335
- }
336
- info.modules.add(module);
337
- if (!info.chunksKeys.has(chunksKey)) {
338
- info.chunksKeys.add(chunksKey);
339
- for (const chunk of selectedChunks) {
340
- info.chunks.set(chunk, chunk.getNumberOfModules());
341
- }
342
- }
343
- }
344
- }
345
- }
346
- for (const [key, info] of chunksInfoMap) {
347
- // Get size of module lists
348
- info.size = getModulesSize(info.modules);
349
- if (info.size < info.cacheGroup.minSize) {
350
- chunksInfoMap.delete(key);
351
- }
352
- }
353
- let changed = false;
354
- while (chunksInfoMap.size > 0) {
355
- // Find best matching entry
356
- let bestEntryKey;
357
- let bestEntry;
358
- for (const pair of chunksInfoMap) {
359
- const key = pair[0];
360
- const info = pair[1];
361
- if (bestEntry === undefined) {
362
- bestEntry = info;
363
- bestEntryKey = key;
364
- } else if (compareEntries(bestEntry, info) < 0) {
365
- bestEntry = info;
366
- bestEntryKey = key;
367
- }
368
- }
369
-
370
- const item = bestEntry;
371
- chunksInfoMap.delete(bestEntryKey);
372
-
373
- let chunkName = item.name;
374
- // Variable for the new chunk (lazy created)
375
- let newChunk;
376
- // When no chunk name, check if we can reuse a chunk instead of creating a new one
377
- let isReused = false;
378
- if (item.cacheGroup.reuseExistingChunk) {
379
- for (const pair of item.chunks) {
380
- if (pair[1] === item.modules.size) {
381
- const chunk = pair[0];
382
- if (chunk.hasEntryModule()) continue;
383
- if (!newChunk || !newChunk.name) newChunk = chunk;
384
- else if (
385
- chunk.name &&
386
- chunk.name.length < newChunk.name.length
387
- )
388
- newChunk = chunk;
389
- else if (
390
- chunk.name &&
391
- chunk.name.length === newChunk.name.length &&
392
- chunk.name < newChunk.name
393
- )
394
- newChunk = chunk;
395
- chunkName = undefined;
396
- isReused = true;
397
- }
398
- }
399
- }
400
- // Walk through all chunks
401
- for (const chunk of item.chunks.keys()) {
402
- // skip if we address ourself
403
- if (chunk.name === chunkName || chunk === newChunk) continue;
404
- // respect max requests when not enforced
405
- const maxRequests = chunk.isOnlyInitial()
406
- ? item.cacheGroup.maxInitialRequests
407
- : chunk.canBeInitial()
408
- ? Math.min(
409
- item.cacheGroup.maxInitialRequests,
410
- item.cacheGroup.maxAsyncRequests
411
- )
412
- : item.cacheGroup.maxAsyncRequests;
413
- if (isFinite(maxRequests) && getRequests(chunk) >= maxRequests)
414
- continue;
415
- if (newChunk === undefined) {
416
- // Create the new chunk
417
- newChunk = compilation.addChunk(chunkName);
418
- }
419
- // Add graph connections for splitted chunk
420
- chunk.split(newChunk);
421
- // Remove all selected modules from the chunk
422
- for (const module of item.modules) {
423
- chunk.removeModule(module);
424
- module.rewriteChunkInReasons(chunk, [newChunk]);
425
- }
426
- }
427
- // If we successfully created a new chunk or reused one
428
- if (newChunk) {
429
- // Add a note to the chunk
430
- newChunk.chunkReason = isReused
431
- ? "reused as split chunk"
432
- : "split chunk";
433
- if (item.cacheGroup.key) {
434
- newChunk.chunkReason += ` (cache group: ${
435
- item.cacheGroup.key
436
- })`;
437
- }
438
- if (chunkName) {
439
- newChunk.chunkReason += ` (name: ${chunkName})`;
440
- // If the chosen name is already an entry point we remove the entry point
441
- const entrypoint = compilation.entrypoints.get(chunkName);
442
- if (entrypoint) {
443
- compilation.entrypoints.delete(chunkName);
444
- entrypoint.remove();
445
- newChunk.entryModule = undefined;
446
- }
447
- }
448
- if (item.cacheGroup.filename) {
449
- if (!newChunk.isOnlyInitial()) {
450
- throw new Error(
451
- "SplitChunksPlugin: You are trying to set a filename for a chunk which is (also) loaded on demand. " +
452
- "The runtime can only handle loading of chunks which match the chunkFilename schema. " +
453
- "Using a custom filename would fail at runtime. " +
454
- `(cache group: ${item.cacheGroup.key})`
455
- );
456
- }
457
- newChunk.filenameTemplate = item.cacheGroup.filename;
458
- }
459
- if (!isReused) {
460
- // Add all modules to the new chunk
461
- for (const module of item.modules) {
462
- GraphHelpers.connectChunkAndModule(newChunk, module);
463
- }
464
- }
465
- // remove all modules from other entries and update size
466
- for (const [key, info] of chunksInfoMap) {
467
- if (isOverlap(info.chunks, item.chunks)) {
468
- const oldSize = info.modules.size;
469
- for (const module of item.modules) {
470
- info.modules.delete(module);
471
- }
472
- if (info.modules.size === 0) {
473
- chunksInfoMap.delete(key);
474
- continue;
475
- }
476
- if (info.modules.size !== oldSize) {
477
- info.size = getModulesSize(info.modules);
478
- if (info.size < info.cacheGroup.minSize)
479
- chunksInfoMap.delete(key);
480
- }
481
- }
482
- }
483
- changed = true;
484
- }
485
- }
486
- if (changed) return true;
487
- }
488
- );
489
- });
490
- }
491
- };
1
+ /*
2
+ MIT License http://www.opensource.org/licenses/mit-license.php
3
+ Author Tobias Koppers @sokra
4
+ */
5
+ "use strict";
6
+
7
+ const crypto = require("crypto");
8
+ const SortableSet = require("../util/SortableSet");
9
+ const GraphHelpers = require("../GraphHelpers");
10
+ const { isSubset } = require("../util/SetHelpers");
11
+
12
+ const hashFilename = name => {
13
+ return crypto
14
+ .createHash("md4")
15
+ .update(name)
16
+ .digest("hex")
17
+ .slice(0, 8);
18
+ };
19
+
20
+ const sortByIdentifier = (a, b) => {
21
+ if (a.identifier() > b.identifier()) return 1;
22
+ if (a.identifier() < b.identifier()) return -1;
23
+ return 0;
24
+ };
25
+
26
+ const getRequests = chunk => {
27
+ let requests = 0;
28
+ for (const chunkGroup of chunk.groupsIterable) {
29
+ requests = Math.max(requests, chunkGroup.chunks.length);
30
+ }
31
+ return requests;
32
+ };
33
+
34
+ const getModulesSize = modules => {
35
+ let sum = 0;
36
+ for (const m of modules) sum += m.size();
37
+ return sum;
38
+ };
39
+
40
+ const isOverlap = (a, b) => {
41
+ for (const item of a.keys()) {
42
+ if (b.has(item)) return true;
43
+ }
44
+ return false;
45
+ };
46
+
47
+ const compareEntries = (a, b) => {
48
+ // 1. by priority
49
+ const diffPriority = a.cacheGroup.priority - b.cacheGroup.priority;
50
+ if (diffPriority) return diffPriority;
51
+ // 2. by number of chunks
52
+ const diffCount = a.chunks.size - b.chunks.size;
53
+ if (diffCount) return diffCount;
54
+ // 3. by size reduction
55
+ const aSizeReduce = a.size * (a.chunks.size - 1);
56
+ const bSizeReduce = b.size * (b.chunks.size - 1);
57
+ const diffSizeReduce = aSizeReduce - bSizeReduce;
58
+ if (diffSizeReduce) return diffSizeReduce;
59
+ // 4. by number of modules (to be able to compare by identifier)
60
+ const modulesA = a.modules;
61
+ const modulesB = b.modules;
62
+ const diff = modulesA.size - modulesB.size;
63
+ if (diff) return diff;
64
+ // 5. by module identifiers
65
+ modulesA.sort();
66
+ modulesB.sort();
67
+ const aI = modulesA[Symbol.iterator]();
68
+ const bI = modulesB[Symbol.iterator]();
69
+ // eslint-disable-next-line no-constant-condition
70
+ while (true) {
71
+ const aItem = aI.next();
72
+ const bItem = bI.next();
73
+ if (aItem.done) return 0;
74
+ const aModuleIdentifier = aItem.value.identifier();
75
+ const bModuleIdentifier = bItem.value.identifier();
76
+ if (aModuleIdentifier > bModuleIdentifier) return -1;
77
+ if (aModuleIdentifier < bModuleIdentifier) return 1;
78
+ }
79
+ };
80
+
81
+ module.exports = class SplitChunksPlugin {
82
+ constructor(options) {
83
+ this.options = SplitChunksPlugin.normalizeOptions(options);
84
+ }
85
+
86
+ static normalizeOptions(options = {}) {
87
+ return {
88
+ chunksFilter: SplitChunksPlugin.normalizeChunksFilter(
89
+ options.chunks || "all"
90
+ ),
91
+ minSize: options.minSize || 0,
92
+ minChunks: options.minChunks || 1,
93
+ maxAsyncRequests: options.maxAsyncRequests || 1,
94
+ maxInitialRequests: options.maxInitialRequests || 1,
95
+ getName:
96
+ SplitChunksPlugin.normalizeName({
97
+ name: options.name,
98
+ automaticNameDelimiter: options.automaticNameDelimiter
99
+ }) || (() => {}),
100
+ filename: options.filename || undefined,
101
+ getCacheGroups: SplitChunksPlugin.normalizeCacheGroups({
102
+ cacheGroups: options.cacheGroups,
103
+ automaticNameDelimiter: options.automaticNameDelimiter
104
+ })
105
+ };
106
+ }
107
+
108
+ static normalizeName({ name, automaticNameDelimiter }) {
109
+ if (name === true) {
110
+ const fn = (module, chunks, cacheGroup) => {
111
+ const names = chunks.map(c => c.name);
112
+ if (!names.every(Boolean)) return;
113
+ names.sort();
114
+ let name =
115
+ (cacheGroup && cacheGroup !== "default"
116
+ ? cacheGroup + automaticNameDelimiter
117
+ : "") + names.join(automaticNameDelimiter);
118
+ // Filenames and paths can't be too long otherwise an
119
+ // ENAMETOOLONG error is raised. If the generated name if too
120
+ // long, it is truncated and a hash is appended. The limit has
121
+ // been set to 100 to prevent `[name].[chunkhash].[ext]` from
122
+ // generating a 256+ character string.
123
+ if (name.length > 100) {
124
+ name =
125
+ name.slice(0, 100) + automaticNameDelimiter + hashFilename(name);
126
+ }
127
+ return name;
128
+ };
129
+ return fn;
130
+ }
131
+ if (typeof name === "string") {
132
+ const fn = () => {
133
+ return name;
134
+ };
135
+ return fn;
136
+ }
137
+ if (typeof name === "function") return name;
138
+ }
139
+
140
+ static normalizeChunksFilter(chunks) {
141
+ if (chunks === "initial") {
142
+ return chunk => chunk.canBeInitial();
143
+ }
144
+ if (chunks === "async") {
145
+ return chunk => !chunk.canBeInitial();
146
+ }
147
+ if (chunks === "all") {
148
+ return () => true;
149
+ }
150
+ if (typeof chunks === "function") return chunks;
151
+ }
152
+
153
+ static normalizeCacheGroups({ cacheGroups, automaticNameDelimiter }) {
154
+ if (typeof cacheGroups === "function") {
155
+ return cacheGroups;
156
+ }
157
+ if (cacheGroups && typeof cacheGroups === "object") {
158
+ const fn = (module, chunks) => {
159
+ let results;
160
+ for (const key of Object.keys(cacheGroups)) {
161
+ let option = cacheGroups[key];
162
+ if (option === false) continue;
163
+ if (option instanceof RegExp || typeof option === "string") {
164
+ option = {
165
+ test: option
166
+ };
167
+ }
168
+ if (typeof option === "function") {
169
+ let result = option(module);
170
+ if (result) {
171
+ if (results === undefined) results = [];
172
+ for (const r of Array.isArray(result) ? result : [result]) {
173
+ const result = Object.assign(
174
+ {
175
+ key
176
+ },
177
+ r
178
+ );
179
+ if (result.name) result.getName = () => result.name;
180
+ if (result.chunks) {
181
+ result.chunksFilter = SplitChunksPlugin.normalizeChunksFilter(
182
+ result.chunks
183
+ );
184
+ }
185
+ results.push(result);
186
+ }
187
+ }
188
+ } else if (SplitChunksPlugin.checkTest(option.test, module, chunks)) {
189
+ if (results === undefined) results = [];
190
+ results.push({
191
+ key: key,
192
+ priority: option.priority,
193
+ getName: SplitChunksPlugin.normalizeName({
194
+ name: option.name,
195
+ automaticNameDelimiter
196
+ }),
197
+ chunksFilter: SplitChunksPlugin.normalizeChunksFilter(
198
+ option.chunks
199
+ ),
200
+ enforce: option.enforce,
201
+ minSize: option.minSize,
202
+ minChunks: option.minChunks,
203
+ maxAsyncRequests: option.maxAsyncRequests,
204
+ maxInitialRequests: option.maxInitialRequests,
205
+ filename: option.filename,
206
+ reuseExistingChunk: option.reuseExistingChunk
207
+ });
208
+ }
209
+ }
210
+ return results;
211
+ };
212
+ return fn;
213
+ }
214
+ const fn = () => {};
215
+ return fn;
216
+ }
217
+
218
+ static checkTest(test, module, chunks) {
219
+ if (test === undefined) return true;
220
+ if (typeof test === "function") return test(module, chunks);
221
+ if (typeof test === "boolean") return test;
222
+ const names = chunks
223
+ .map(c => c.name)
224
+ .concat(module.nameForCondition ? [module.nameForCondition()] : [])
225
+ .filter(Boolean);
226
+ if (typeof test === "string") {
227
+ for (const name of names) if (name.startsWith(test)) return true;
228
+ return false;
229
+ }
230
+ if (test instanceof RegExp) {
231
+ for (const name of names) if (test.test(name)) return true;
232
+ return false;
233
+ }
234
+ return false;
235
+ }
236
+
237
+ apply(compiler) {
238
+ compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
239
+ let alreadyOptimized = false;
240
+ compilation.hooks.unseal.tap("SplitChunksPlugin", () => {
241
+ alreadyOptimized = false;
242
+ });
243
+ compilation.hooks.optimizeChunksAdvanced.tap(
244
+ "SplitChunksPlugin",
245
+ chunks => {
246
+ if (alreadyOptimized) return;
247
+ alreadyOptimized = true;
248
+ // Give each selected chunk an index (to create strings from chunks)
249
+ const indexMap = new Map();
250
+ let index = 1;
251
+ for (const chunk of chunks) {
252
+ indexMap.set(chunk, index++);
253
+ }
254
+ const getKey = chunks => {
255
+ return Array.from(chunks, c => indexMap.get(c))
256
+ .sort()
257
+ .join();
258
+ };
259
+ // Create a list of possible combinations
260
+ const chunkSetsInGraph = new Map(); // Map<string, Set<Chunk>>
261
+ for (const module of compilation.modules) {
262
+ const chunkIndices = getKey(module.chunksIterable);
263
+ chunkSetsInGraph.set(chunkIndices, new Set(module.chunksIterable));
264
+ }
265
+ const combinations = new Map(); // Map<string, Set<Chunk>[]>
266
+ for (const [key, chunksSet] of chunkSetsInGraph) {
267
+ var array = [];
268
+ for (const set of chunkSetsInGraph.values()) {
269
+ if (isSubset(chunksSet, set)) {
270
+ array.push(set);
271
+ }
272
+ }
273
+ combinations.set(key, array);
274
+ }
275
+ // Map a list of chunks to a list of modules
276
+ // For the key the chunk "index" is used, the value is a SortableSet of modules
277
+ const chunksInfoMap = new Map();
278
+ // Walk through all modules
279
+ for (const module of compilation.modules) {
280
+ // Get array of chunks
281
+ const chunks = module.getChunks();
282
+ // Get cache group
283
+ let cacheGroups = this.options.getCacheGroups(module, chunks);
284
+ if (!Array.isArray(cacheGroups)) continue;
285
+ for (const cacheGroupSource of cacheGroups) {
286
+ const cacheGroup = {
287
+ key: cacheGroupSource.key,
288
+ priority: cacheGroupSource.priority || 0,
289
+ chunksFilter:
290
+ cacheGroupSource.chunksFilter || this.options.chunksFilter,
291
+ minSize:
292
+ cacheGroupSource.minSize !== undefined
293
+ ? cacheGroupSource.minSize
294
+ : cacheGroupSource.enforce ? 0 : this.options.minSize,
295
+ minChunks:
296
+ cacheGroupSource.minChunks !== undefined
297
+ ? cacheGroupSource.minChunks
298
+ : cacheGroupSource.enforce ? 1 : this.options.minChunks,
299
+ maxAsyncRequests:
300
+ cacheGroupSource.maxAsyncRequests !== undefined
301
+ ? cacheGroupSource.maxAsyncRequests
302
+ : cacheGroupSource.enforce
303
+ ? Infinity
304
+ : this.options.maxAsyncRequests,
305
+ maxInitialRequests:
306
+ cacheGroupSource.maxInitialRequests !== undefined
307
+ ? cacheGroupSource.maxInitialRequests
308
+ : cacheGroupSource.enforce
309
+ ? Infinity
310
+ : this.options.maxInitialRequests,
311
+ getName:
312
+ cacheGroupSource.getName !== undefined
313
+ ? cacheGroupSource.getName
314
+ : this.options.getName,
315
+ filename:
316
+ cacheGroupSource.filename !== undefined
317
+ ? cacheGroupSource.filename
318
+ : this.options.filename,
319
+ reuseExistingChunk: cacheGroupSource.reuseExistingChunk
320
+ };
321
+ // For all combination of chunk selection
322
+ for (const chunkCombination of combinations.get(getKey(chunks))) {
323
+ // Get indices of chunks in which this module occurs
324
+ const chunkIndices = Array.from(chunkCombination, chunk =>
325
+ indexMap.get(chunk)
326
+ );
327
+ // Break if minimum number of chunks is not reached
328
+ if (chunkIndices.length < cacheGroup.minChunks) continue;
329
+ // Select chunks by configuration
330
+ const selectedChunks = Array.from(chunkCombination).filter(
331
+ cacheGroup.chunksFilter
332
+ );
333
+ // Break if minimum number of chunks is not reached
334
+ if (selectedChunks.length < cacheGroup.minChunks) continue;
335
+ // Determine name for split chunk
336
+ const name = cacheGroup.getName(
337
+ module,
338
+ selectedChunks,
339
+ cacheGroup.key
340
+ );
341
+ // Create key for maps
342
+ // When it has a name we use the name as key
343
+ // Elsewise we create the key from chunks and cache group key
344
+ // This automatically merges equal names
345
+ const chunksKey = getKey(selectedChunks);
346
+ const key =
347
+ (name && `name:${name}`) ||
348
+ `chunks:${chunksKey} key:${cacheGroup.key}`;
349
+ // Add module to maps
350
+ let info = chunksInfoMap.get(key);
351
+ if (info === undefined) {
352
+ chunksInfoMap.set(
353
+ key,
354
+ (info = {
355
+ modules: new SortableSet(undefined, sortByIdentifier),
356
+ cacheGroup,
357
+ name,
358
+ chunks: new Map(),
359
+ reusedableChunks: new Set(),
360
+ chunksKeys: new Set()
361
+ })
362
+ );
363
+ }
364
+ info.modules.add(module);
365
+ if (!info.chunksKeys.has(chunksKey)) {
366
+ info.chunksKeys.add(chunksKey);
367
+ for (const chunk of selectedChunks) {
368
+ info.chunks.set(chunk, chunk.getNumberOfModules());
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ for (const [key, info] of chunksInfoMap) {
375
+ // Get size of module lists
376
+ info.size = getModulesSize(info.modules);
377
+ if (info.size < info.cacheGroup.minSize) {
378
+ chunksInfoMap.delete(key);
379
+ }
380
+ }
381
+ let changed = false;
382
+ while (chunksInfoMap.size > 0) {
383
+ // Find best matching entry
384
+ let bestEntryKey;
385
+ let bestEntry;
386
+ for (const pair of chunksInfoMap) {
387
+ const key = pair[0];
388
+ const info = pair[1];
389
+ if (bestEntry === undefined) {
390
+ bestEntry = info;
391
+ bestEntryKey = key;
392
+ } else if (compareEntries(bestEntry, info) < 0) {
393
+ bestEntry = info;
394
+ bestEntryKey = key;
395
+ }
396
+ }
397
+
398
+ const item = bestEntry;
399
+ chunksInfoMap.delete(bestEntryKey);
400
+
401
+ let chunkName = item.name;
402
+ // Variable for the new chunk (lazy created)
403
+ let newChunk;
404
+ // When no chunk name, check if we can reuse a chunk instead of creating a new one
405
+ let isReused = false;
406
+ if (item.cacheGroup.reuseExistingChunk) {
407
+ for (const pair of item.chunks) {
408
+ if (pair[1] === item.modules.size) {
409
+ const chunk = pair[0];
410
+ if (chunk.hasEntryModule()) continue;
411
+ if (!newChunk || !newChunk.name) newChunk = chunk;
412
+ else if (
413
+ chunk.name &&
414
+ chunk.name.length < newChunk.name.length
415
+ )
416
+ newChunk = chunk;
417
+ else if (
418
+ chunk.name &&
419
+ chunk.name.length === newChunk.name.length &&
420
+ chunk.name < newChunk.name
421
+ )
422
+ newChunk = chunk;
423
+ chunkName = undefined;
424
+ isReused = true;
425
+ }
426
+ }
427
+ }
428
+ // Walk through all chunks
429
+ for (const chunk of item.chunks.keys()) {
430
+ // skip if we address ourself
431
+ if (chunk.name === chunkName || chunk === newChunk) continue;
432
+ // respect max requests when not enforced
433
+ const maxRequests = chunk.isOnlyInitial()
434
+ ? item.cacheGroup.maxInitialRequests
435
+ : chunk.canBeInitial()
436
+ ? Math.min(
437
+ item.cacheGroup.maxInitialRequests,
438
+ item.cacheGroup.maxAsyncRequests
439
+ )
440
+ : item.cacheGroup.maxAsyncRequests;
441
+ if (isFinite(maxRequests) && getRequests(chunk) >= maxRequests)
442
+ continue;
443
+ if (newChunk === undefined) {
444
+ // Create the new chunk
445
+ newChunk = compilation.addChunk(chunkName);
446
+ }
447
+ // Add graph connections for splitted chunk
448
+ chunk.split(newChunk);
449
+ // Remove all selected modules from the chunk
450
+ for (const module of item.modules) {
451
+ chunk.removeModule(module);
452
+ module.rewriteChunkInReasons(chunk, [newChunk]);
453
+ }
454
+ }
455
+ // If we successfully created a new chunk or reused one
456
+ if (newChunk) {
457
+ // Add a note to the chunk
458
+ newChunk.chunkReason = isReused
459
+ ? "reused as split chunk"
460
+ : "split chunk";
461
+ if (item.cacheGroup.key) {
462
+ newChunk.chunkReason += ` (cache group: ${
463
+ item.cacheGroup.key
464
+ })`;
465
+ }
466
+ if (chunkName) {
467
+ newChunk.chunkReason += ` (name: ${chunkName})`;
468
+ // If the chosen name is already an entry point we remove the entry point
469
+ const entrypoint = compilation.entrypoints.get(chunkName);
470
+ if (entrypoint) {
471
+ compilation.entrypoints.delete(chunkName);
472
+ entrypoint.remove();
473
+ newChunk.entryModule = undefined;
474
+ }
475
+ }
476
+ if (item.cacheGroup.filename) {
477
+ if (!newChunk.isOnlyInitial()) {
478
+ throw new Error(
479
+ "SplitChunksPlugin: You are trying to set a filename for a chunk which is (also) loaded on demand. " +
480
+ "The runtime can only handle loading of chunks which match the chunkFilename schema. " +
481
+ "Using a custom filename would fail at runtime. " +
482
+ `(cache group: ${item.cacheGroup.key})`
483
+ );
484
+ }
485
+ newChunk.filenameTemplate = item.cacheGroup.filename;
486
+ }
487
+ if (!isReused) {
488
+ // Add all modules to the new chunk
489
+ for (const module of item.modules) {
490
+ GraphHelpers.connectChunkAndModule(newChunk, module);
491
+ }
492
+ }
493
+ // remove all modules from other entries and update size
494
+ for (const [key, info] of chunksInfoMap) {
495
+ if (isOverlap(info.chunks, item.chunks)) {
496
+ const oldSize = info.modules.size;
497
+ for (const module of item.modules) {
498
+ info.modules.delete(module);
499
+ }
500
+ if (info.modules.size === 0) {
501
+ chunksInfoMap.delete(key);
502
+ continue;
503
+ }
504
+ if (info.modules.size !== oldSize) {
505
+ info.size = getModulesSize(info.modules);
506
+ if (info.size < info.cacheGroup.minSize)
507
+ chunksInfoMap.delete(key);
508
+ }
509
+ }
510
+ }
511
+ changed = true;
512
+ }
513
+ }
514
+ if (changed) return true;
515
+ }
516
+ );
517
+ });
518
+ }
519
+ };