webpack 4.13.0 → 4.16.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.
- package/bin/webpack.js +7 -2
- package/hot/dev-server.js +2 -2
- package/hot/only-dev-server.js +2 -2
- package/hot/poll.js +5 -2
- package/hot/signal.js +2 -2
- package/lib/AsyncDependenciesBlock.js +44 -0
- package/lib/AutomaticPrefetchPlugin.js +2 -2
- package/lib/Chunk.js +56 -6
- package/lib/ChunkGroup.js +2 -2
- package/lib/ChunkTemplate.js +14 -2
- package/lib/CommentCompilationWarning.js +3 -3
- package/lib/CompatibilityPlugin.js +1 -1
- package/lib/Compilation.js +494 -36
- package/lib/Compiler.js +57 -4
- package/lib/ContextModule.js +23 -16
- package/lib/DelegatedModule.js +9 -1
- package/lib/DelegatedModuleFactoryPlugin.js +7 -1
- package/lib/DependenciesBlock.js +36 -3
- package/lib/DependenciesBlockVariable.js +22 -0
- package/lib/Dependency.js +33 -6
- package/lib/DllEntryPlugin.js +4 -1
- package/lib/DynamicEntryPlugin.js +21 -1
- package/lib/EntryOptionPlugin.js +12 -0
- package/lib/Entrypoint.js +1 -1
- package/lib/EnvironmentPlugin.js +8 -1
- package/lib/ExtendedAPIPlugin.js +8 -4
- package/lib/ExternalModuleFactoryPlugin.js +1 -1
- package/lib/FlagDependencyUsagePlugin.js +18 -13
- package/lib/Generator.js +1 -1
- package/lib/GraphHelpers.js +2 -1
- package/lib/HotModuleReplacement.runtime.js +8 -5
- package/lib/HotModuleReplacementPlugin.js +115 -117
- package/lib/IgnorePlugin.js +1 -1
- package/lib/MainTemplate.js +19 -4
- package/lib/Module.js +9 -3
- package/lib/ModuleReason.js +8 -0
- package/lib/MultiEntryPlugin.js +25 -3
- package/lib/NormalModule.js +5 -23
- package/lib/RuleSet.js +3 -3
- package/lib/RuntimeTemplate.js +4 -0
- package/lib/SingleEntryPlugin.js +20 -1
- package/lib/Stats.js +12 -5
- package/lib/Template.js +4 -1
- package/lib/UmdMainTemplatePlugin.js +12 -12
- package/lib/UseStrictPlugin.js +1 -1
- package/lib/WebpackError.js +4 -0
- package/lib/WebpackOptionsApply.js +92 -10
- package/lib/WebpackOptionsDefaulter.js +23 -6
- package/lib/WebpackOptionsValidationError.js +0 -1
- package/lib/compareLocations.js +13 -17
- package/lib/debug/ProfilingPlugin.js +5 -7
- package/lib/dependencies/AMDDefineDependencyParserPlugin.js +4 -6
- package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +0 -2
- package/lib/dependencies/DependencyReference.js +4 -0
- package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +18 -8
- package/lib/dependencies/LoaderDependency.js +3 -0
- package/lib/dependencies/LoaderPlugin.js +21 -2
- package/lib/dependencies/ModuleDependency.js +3 -0
- package/lib/dependencies/MultiEntryDependency.js +5 -0
- package/lib/dependencies/SingleEntryDependency.js +3 -0
- package/lib/dependencies/SystemPlugin.js +1 -1
- package/lib/formatLocation.js +55 -41
- package/lib/node/NodeMainTemplateAsync.runtime.js +1 -1
- package/lib/node/NodeMainTemplatePlugin.js +2 -2
- package/lib/node/NodeSourcePlugin.js +1 -1
- package/lib/optimize/ConcatenatedModule.js +24 -8
- package/lib/optimize/ModuleConcatenationPlugin.js +29 -14
- package/lib/optimize/NaturalChunkOrderPlugin.js +41 -0
- package/lib/optimize/OccurrenceChunkOrderPlugin.js +61 -0
- package/lib/optimize/OccurrenceModuleOrderPlugin.js +103 -0
- package/lib/optimize/OccurrenceOrderPlugin.js +2 -0
- package/lib/optimize/SplitChunksPlugin.js +168 -18
- package/lib/util/Semaphore.js +12 -0
- package/lib/util/SetHelpers.js +4 -4
- package/lib/util/SortableSet.js +1 -1
- package/lib/util/cachedMerge.js +1 -1
- package/lib/util/createHash.js +15 -0
- package/lib/util/deterministicGrouping.js +251 -0
- package/lib/util/identifier.js +27 -0
- package/lib/wasm/WasmFinalizeExportsPlugin.js +5 -2
- package/lib/wasm/WasmMainTemplatePlugin.js +10 -4
- package/lib/wasm/WebAssemblyGenerator.js +12 -12
- package/lib/wasm/WebAssemblyInInitialChunkError.js +88 -0
- package/lib/wasm/WebAssemblyModulesPlugin.js +28 -0
- package/lib/web/JsonpMainTemplatePlugin.js +1 -1
- package/lib/web/WebEnvironmentPlugin.js +18 -18
- package/lib/webpack.js +7 -0
- package/lib/webpack.web.js +2 -2
- package/lib/webworker/WebWorkerMainTemplatePlugin.js +1 -1
- package/package.json +21 -11
- package/schemas/WebpackOptions.json +70 -4
- package/schemas/plugins/optimize/OccurrenceOrderChunkIdsPlugin.json +10 -0
- package/schemas/plugins/optimize/OccurrenceOrderModuleIdsPlugin.json +10 -0
@@ -8,9 +8,16 @@ const crypto = require("crypto");
|
|
8
8
|
const SortableSet = require("../util/SortableSet");
|
9
9
|
const GraphHelpers = require("../GraphHelpers");
|
10
10
|
const { isSubset } = require("../util/SetHelpers");
|
11
|
+
const deterministicGrouping = require("../util/deterministicGrouping");
|
12
|
+
const contextify = require("../util/identifier").contextify;
|
11
13
|
|
14
|
+
/** @typedef {import("../Compiler")} Compiler */
|
12
15
|
/** @typedef {import("../Chunk")} Chunk */
|
13
16
|
/** @typedef {import("../Module")} Module */
|
17
|
+
/** @typedef {import("../util/deterministicGrouping").Options<Module>} DeterministicGroupingOptionsForModule */
|
18
|
+
/** @typedef {import("../util/deterministicGrouping").GroupedItems<Module>} DeterministicGroupingGroupedItemsForModule */
|
19
|
+
|
20
|
+
const deterministicGroupingForModules = /** @type {function(DeterministicGroupingOptionsForModule): DeterministicGroupingGroupedItemsForModule[]} */ (deterministicGrouping);
|
14
21
|
|
15
22
|
const hashFilename = name => {
|
16
23
|
return crypto
|
@@ -104,25 +111,29 @@ module.exports = class SplitChunksPlugin {
|
|
104
111
|
options.chunks || "all"
|
105
112
|
),
|
106
113
|
minSize: options.minSize || 0,
|
114
|
+
maxSize: options.maxSize || 0,
|
107
115
|
minChunks: options.minChunks || 1,
|
108
116
|
maxAsyncRequests: options.maxAsyncRequests || 1,
|
109
117
|
maxInitialRequests: options.maxInitialRequests || 1,
|
110
|
-
|
111
|
-
SplitChunksPlugin.normalizeName({
|
112
|
-
name: options.name,
|
113
|
-
automaticNameDelimiter: options.automaticNameDelimiter
|
114
|
-
}) || (() => {}),
|
118
|
+
hidePathInfo: options.hidePathInfo || false,
|
115
119
|
filename: options.filename || undefined,
|
116
120
|
getCacheGroups: SplitChunksPlugin.normalizeCacheGroups({
|
117
121
|
cacheGroups: options.cacheGroups,
|
122
|
+
name: options.name,
|
118
123
|
automaticNameDelimiter: options.automaticNameDelimiter
|
119
|
-
})
|
124
|
+
}),
|
125
|
+
automaticNameDelimiter: options.automaticNameDelimiter,
|
126
|
+
fallbackCacheGroup: SplitChunksPlugin.normalizeFallbackCacheGroup(
|
127
|
+
options.fallbackCacheGroup || {},
|
128
|
+
options
|
129
|
+
)
|
120
130
|
};
|
121
131
|
}
|
122
132
|
|
123
|
-
static normalizeName({ name, automaticNameDelimiter }) {
|
133
|
+
static normalizeName({ name, automaticNameDelimiter, automaticNamePrefix }) {
|
124
134
|
if (name === true) {
|
125
|
-
|
135
|
+
/** @type {WeakMap<Chunk[], Record<string, string>>} */
|
136
|
+
const cache = new WeakMap();
|
126
137
|
const fn = (module, chunks, cacheGroup) => {
|
127
138
|
let cacheEntry = cache.get(chunks);
|
128
139
|
if (cacheEntry === undefined) {
|
@@ -137,10 +148,12 @@ module.exports = class SplitChunksPlugin {
|
|
137
148
|
return;
|
138
149
|
}
|
139
150
|
names.sort();
|
140
|
-
|
141
|
-
|
142
|
-
?
|
143
|
-
:
|
151
|
+
const prefix =
|
152
|
+
typeof automaticNamePrefix === "string"
|
153
|
+
? automaticNamePrefix
|
154
|
+
: cacheGroup;
|
155
|
+
const namePrefix = prefix ? prefix + automaticNameDelimiter : "";
|
156
|
+
let name = namePrefix + names.join(automaticNameDelimiter);
|
144
157
|
// Filenames and paths can't be too long otherwise an
|
145
158
|
// ENAMETOOLONG error is raised. If the generated name if too
|
146
159
|
// long, it is truncated and a hash is appended. The limit has
|
@@ -177,7 +190,27 @@ module.exports = class SplitChunksPlugin {
|
|
177
190
|
if (typeof chunks === "function") return chunks;
|
178
191
|
}
|
179
192
|
|
180
|
-
static
|
193
|
+
static normalizeFallbackCacheGroup(
|
194
|
+
{
|
195
|
+
minSize = undefined,
|
196
|
+
maxSize = undefined,
|
197
|
+
automaticNameDelimiter = undefined
|
198
|
+
},
|
199
|
+
{
|
200
|
+
minSize: defaultMinSize = undefined,
|
201
|
+
maxSize: defaultMaxSize = undefined,
|
202
|
+
automaticNameDelimiter: defaultAutomaticNameDelimiter = undefined
|
203
|
+
}
|
204
|
+
) {
|
205
|
+
return {
|
206
|
+
minSize: typeof minSize === "number" ? minSize : defaultMinSize || 0,
|
207
|
+
maxSize: typeof maxSize === "number" ? maxSize : defaultMaxSize || 0,
|
208
|
+
automaticNameDelimiter:
|
209
|
+
automaticNameDelimiter || defaultAutomaticNameDelimiter || "~"
|
210
|
+
};
|
211
|
+
}
|
212
|
+
|
213
|
+
static normalizeCacheGroups({ cacheGroups, name, automaticNameDelimiter }) {
|
181
214
|
if (typeof cacheGroups === "function") {
|
182
215
|
// TODO webpack 5 remove this
|
183
216
|
if (cacheGroups.length !== 1) {
|
@@ -216,15 +249,21 @@ module.exports = class SplitChunksPlugin {
|
|
216
249
|
results.push({
|
217
250
|
key: key,
|
218
251
|
priority: option.priority,
|
219
|
-
getName:
|
220
|
-
|
221
|
-
|
222
|
-
|
252
|
+
getName:
|
253
|
+
SplitChunksPlugin.normalizeName({
|
254
|
+
name: option.name || name,
|
255
|
+
automaticNameDelimiter:
|
256
|
+
typeof option.automaticNameDelimiter === "string"
|
257
|
+
? option.automaticNameDelimiter
|
258
|
+
: automaticNameDelimiter,
|
259
|
+
automaticNamePrefix: option.automaticNamePrefix
|
260
|
+
}) || (() => {}),
|
223
261
|
chunksFilter: SplitChunksPlugin.normalizeChunksFilter(
|
224
262
|
option.chunks
|
225
263
|
),
|
226
264
|
enforce: option.enforce,
|
227
265
|
minSize: option.minSize,
|
266
|
+
maxSize: option.maxSize,
|
228
267
|
minChunks: option.minChunks,
|
229
268
|
maxAsyncRequests: option.maxAsyncRequests,
|
230
269
|
maxInitialRequests: option.maxInitialRequests,
|
@@ -278,6 +317,10 @@ module.exports = class SplitChunksPlugin {
|
|
278
317
|
return false;
|
279
318
|
}
|
280
319
|
|
320
|
+
/**
|
321
|
+
* @param {Compiler} compiler webpack compiler
|
322
|
+
* @returns {void}
|
323
|
+
*/
|
281
324
|
apply(compiler) {
|
282
325
|
compiler.hooks.thisCompilation.tap("SplitChunksPlugin", compilation => {
|
283
326
|
let alreadyOptimized = false;
|
@@ -447,6 +490,12 @@ module.exports = class SplitChunksPlugin {
|
|
447
490
|
chunksKeys: new Set()
|
448
491
|
})
|
449
492
|
);
|
493
|
+
} else {
|
494
|
+
if (info.cacheGroup !== cacheGroup) {
|
495
|
+
if (info.cacheGroup.priority < cacheGroup.priority) {
|
496
|
+
info.cacheGroup = cacheGroup;
|
497
|
+
}
|
498
|
+
}
|
450
499
|
}
|
451
500
|
info.modules.add(module);
|
452
501
|
info.size += module.size();
|
@@ -486,6 +535,12 @@ module.exports = class SplitChunksPlugin {
|
|
486
535
|
: cacheGroupSource.enforce
|
487
536
|
? 0
|
488
537
|
: this.options.minSize,
|
538
|
+
maxSize:
|
539
|
+
cacheGroupSource.maxSize !== undefined
|
540
|
+
? cacheGroupSource.maxSize
|
541
|
+
: cacheGroupSource.enforce
|
542
|
+
? 0
|
543
|
+
: this.options.maxSize,
|
489
544
|
minChunks:
|
490
545
|
cacheGroupSource.minChunks !== undefined
|
491
546
|
? cacheGroupSource.minChunks
|
@@ -512,6 +567,10 @@ module.exports = class SplitChunksPlugin {
|
|
512
567
|
cacheGroupSource.filename !== undefined
|
513
568
|
? cacheGroupSource.filename
|
514
569
|
: this.options.filename,
|
570
|
+
automaticNameDelimiter:
|
571
|
+
cacheGroupSource.automaticNameDelimiter !== undefined
|
572
|
+
? cacheGroupSource.automaticNameDelimiter
|
573
|
+
: this.options.automaticNameDelimiter,
|
515
574
|
reuseExistingChunk: cacheGroupSource.reuseExistingChunk
|
516
575
|
};
|
517
576
|
// For all combination of chunk selection
|
@@ -537,6 +596,9 @@ module.exports = class SplitChunksPlugin {
|
|
537
596
|
}
|
538
597
|
}
|
539
598
|
|
599
|
+
/** @type {Map<Chunk, {minSize: number, maxSize: number, automaticNameDelimiter: string}>} */
|
600
|
+
const maxSizeQueueMap = new Map();
|
601
|
+
|
540
602
|
while (chunksInfoMap.size > 0) {
|
541
603
|
// Find best matching entry
|
542
604
|
let bestEntryKey;
|
@@ -563,6 +625,7 @@ module.exports = class SplitChunksPlugin {
|
|
563
625
|
|
564
626
|
let chunkName = item.name;
|
565
627
|
// Variable for the new chunk (lazy created)
|
628
|
+
/** @type {Chunk} */
|
566
629
|
let newChunk;
|
567
630
|
// When no chunk name, check if we can reuse a chunk instead of creating a new one
|
568
631
|
let isReused = false;
|
@@ -591,7 +654,7 @@ module.exports = class SplitChunksPlugin {
|
|
591
654
|
isReused = true;
|
592
655
|
}
|
593
656
|
}
|
594
|
-
// Check if maxRequests condition can be
|
657
|
+
// Check if maxRequests condition can be fulfilled
|
595
658
|
|
596
659
|
const usedChunks = Array.from(item.chunks).filter(chunk => {
|
597
660
|
// skip if we address ourself
|
@@ -689,6 +752,22 @@ module.exports = class SplitChunksPlugin {
|
|
689
752
|
}
|
690
753
|
}
|
691
754
|
}
|
755
|
+
|
756
|
+
if (item.cacheGroup.maxSize > 0) {
|
757
|
+
const oldMaxSizeSettings = maxSizeQueueMap.get(newChunk);
|
758
|
+
maxSizeQueueMap.set(newChunk, {
|
759
|
+
minSize: Math.max(
|
760
|
+
oldMaxSizeSettings ? oldMaxSizeSettings.minSize : 0,
|
761
|
+
item.cacheGroup.minSize
|
762
|
+
),
|
763
|
+
maxSize: Math.min(
|
764
|
+
oldMaxSizeSettings ? oldMaxSizeSettings.maxSize : Infinity,
|
765
|
+
item.cacheGroup.maxSize
|
766
|
+
),
|
767
|
+
automaticNameDelimiter: item.cacheGroup.automaticNameDelimiter
|
768
|
+
});
|
769
|
+
}
|
770
|
+
|
692
771
|
// remove all modules from other entries and update size
|
693
772
|
for (const [key, info] of chunksInfoMap) {
|
694
773
|
if (isOverlap(info.chunks, item.chunks)) {
|
@@ -709,6 +788,77 @@ module.exports = class SplitChunksPlugin {
|
|
709
788
|
}
|
710
789
|
}
|
711
790
|
}
|
791
|
+
|
792
|
+
// Make sure that maxSize is fulfilled
|
793
|
+
for (const chunk of compilation.chunks.slice()) {
|
794
|
+
const { minSize, maxSize, automaticNameDelimiter } =
|
795
|
+
maxSizeQueueMap.get(chunk) || this.options.fallbackCacheGroup;
|
796
|
+
if (!maxSize) continue;
|
797
|
+
const results = deterministicGroupingForModules({
|
798
|
+
maxSize,
|
799
|
+
minSize,
|
800
|
+
items: chunk.modulesIterable,
|
801
|
+
getKey(module) {
|
802
|
+
const ident = contextify(
|
803
|
+
compilation.options.context,
|
804
|
+
module.identifier()
|
805
|
+
);
|
806
|
+
const name = module.nameForCondition
|
807
|
+
? contextify(
|
808
|
+
compilation.options.context,
|
809
|
+
module.nameForCondition()
|
810
|
+
)
|
811
|
+
: ident.replace(/^.*!|\?[^?!]*$/g, "");
|
812
|
+
const fullKey =
|
813
|
+
name + automaticNameDelimiter + hashFilename(ident);
|
814
|
+
return fullKey.replace(/[\\/?]/g, "_");
|
815
|
+
},
|
816
|
+
getSize(module) {
|
817
|
+
return module.size();
|
818
|
+
}
|
819
|
+
});
|
820
|
+
results.sort((a, b) => {
|
821
|
+
if (a.key < b.key) return -1;
|
822
|
+
if (a.key > b.key) return 1;
|
823
|
+
return 0;
|
824
|
+
});
|
825
|
+
for (let i = 0; i < results.length; i++) {
|
826
|
+
const group = results[i];
|
827
|
+
const key = this.options.hidePathInfo
|
828
|
+
? hashFilename(group.key)
|
829
|
+
: group.key;
|
830
|
+
let name = chunk.name
|
831
|
+
? chunk.name + automaticNameDelimiter + key
|
832
|
+
: null;
|
833
|
+
if (name && name.length > 100) {
|
834
|
+
name =
|
835
|
+
name.slice(0, 100) +
|
836
|
+
automaticNameDelimiter +
|
837
|
+
hashFilename(name);
|
838
|
+
}
|
839
|
+
let newPart;
|
840
|
+
if (i !== results.length - 1) {
|
841
|
+
newPart = compilation.addChunk(name);
|
842
|
+
chunk.split(newPart);
|
843
|
+
newPart.chunkReason = chunk.chunkReason;
|
844
|
+
// Add all modules to the new chunk
|
845
|
+
for (const module of group.items) {
|
846
|
+
if (typeof module.chunkCondition === "function") {
|
847
|
+
if (!module.chunkCondition(newPart)) continue;
|
848
|
+
}
|
849
|
+
// Add module to new chunk
|
850
|
+
GraphHelpers.connectChunkAndModule(newPart, module);
|
851
|
+
// Remove module from used chunks
|
852
|
+
chunk.removeModule(module);
|
853
|
+
module.rewriteChunkInReasons(chunk, [newPart]);
|
854
|
+
}
|
855
|
+
} else {
|
856
|
+
// change the chunk to be a part
|
857
|
+
newPart = chunk;
|
858
|
+
chunk.name = name;
|
859
|
+
}
|
860
|
+
}
|
861
|
+
}
|
712
862
|
}
|
713
863
|
);
|
714
864
|
});
|
package/lib/util/Semaphore.js
CHANGED
@@ -5,12 +5,24 @@
|
|
5
5
|
"use strict";
|
6
6
|
|
7
7
|
class Semaphore {
|
8
|
+
/**
|
9
|
+
* Creates an instance of Semaphore.
|
10
|
+
*
|
11
|
+
* @param {number} available the amount available number of "tasks"
|
12
|
+
* in the Semaphore
|
13
|
+
*/
|
8
14
|
constructor(available) {
|
9
15
|
this.available = available;
|
16
|
+
/** @type {(function(): void)[]} */
|
10
17
|
this.waiters = [];
|
18
|
+
/** @private */
|
11
19
|
this._continue = this._continue.bind(this);
|
12
20
|
}
|
13
21
|
|
22
|
+
/**
|
23
|
+
* @param {function(): void} callback function block to capture and run
|
24
|
+
* @returns {void}
|
25
|
+
*/
|
14
26
|
acquire(callback) {
|
15
27
|
if (this.available > 0) {
|
16
28
|
this.available--;
|
package/lib/util/SetHelpers.js
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
* @param {Set[]} sets an array of sets being checked for shared elements
|
6
6
|
* @returns {Set<TODO>} returns a new Set containing the intersecting items
|
7
7
|
*/
|
8
|
-
|
8
|
+
const intersect = sets => {
|
9
9
|
if (sets.length === 0) return new Set();
|
10
10
|
if (sets.length === 1) return new Set(sets[0]);
|
11
11
|
let minSize = Infinity;
|
@@ -28,7 +28,7 @@ function intersect(sets) {
|
|
28
28
|
}
|
29
29
|
}
|
30
30
|
return current;
|
31
|
-
}
|
31
|
+
};
|
32
32
|
|
33
33
|
/**
|
34
34
|
* Checks if a set is the subset of another set
|
@@ -36,13 +36,13 @@ function intersect(sets) {
|
|
36
36
|
* @param {Set<TODO>} smallSet the set whos elements might be contained inside of bigSet
|
37
37
|
* @returns {boolean} returns true if smallSet contains all elements inside of the bigSet
|
38
38
|
*/
|
39
|
-
|
39
|
+
const isSubset = (bigSet, smallSet) => {
|
40
40
|
if (bigSet.size < smallSet.size) return false;
|
41
41
|
for (const item of smallSet) {
|
42
42
|
if (!bigSet.has(item)) return false;
|
43
43
|
}
|
44
44
|
return true;
|
45
|
-
}
|
45
|
+
};
|
46
46
|
|
47
47
|
exports.intersect = intersect;
|
48
48
|
exports.isSubset = isSubset;
|
package/lib/util/SortableSet.js
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
class SortableSet extends Set {
|
8
8
|
/**
|
9
9
|
* Create a new sortable set
|
10
|
-
* @param {
|
10
|
+
* @param {Iterable<T>=} initialIterable The initial iterable value
|
11
11
|
* @typedef {function(T, T): number} SortFunction
|
12
12
|
* @param {SortFunction=} defaultSort Default sorting function
|
13
13
|
*/
|
package/lib/util/cachedMerge.js
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
const mergeCache = new WeakMap();
|
8
8
|
|
9
9
|
/**
|
10
|
-
* Merges two given objects and caches the result to avoid computation if same objects passed as
|
10
|
+
* Merges two given objects and caches the result to avoid computation if same objects passed as arguments again.
|
11
11
|
* @example
|
12
12
|
* // performs Object.assign(first, second), stores the result in WeakMap and returns result
|
13
13
|
* cachedMerge({a: 1}, {a: 2})
|
package/lib/util/createHash.js
CHANGED
@@ -4,8 +4,18 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
/** @typedef {{new(): Hash}} HashConstructor */
|
8
|
+
/**
|
9
|
+
* @typedef {Object} Hash
|
10
|
+
* @property {function(string|Buffer, string=): Hash} update
|
11
|
+
* @property {function(string): string} digest
|
12
|
+
*/
|
13
|
+
|
7
14
|
const BULK_SIZE = 1000;
|
8
15
|
|
16
|
+
/**
|
17
|
+
* @implements {Hash}
|
18
|
+
*/
|
9
19
|
class BulkUpdateDecorator {
|
10
20
|
constructor(hash) {
|
11
21
|
this.hash = hash;
|
@@ -63,6 +73,11 @@ class DebugHash {
|
|
63
73
|
}
|
64
74
|
}
|
65
75
|
|
76
|
+
/**
|
77
|
+
* Creates a hash by name or function
|
78
|
+
* @param {string | HashConstructor} algorithm the algorithm name or a constructor creating a hash
|
79
|
+
* @returns {Hash} the hash
|
80
|
+
*/
|
66
81
|
module.exports = algorithm => {
|
67
82
|
if (typeof algorithm === "function") {
|
68
83
|
return new BulkUpdateDecorator(new algorithm());
|
@@ -0,0 +1,251 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
// Simulations show these probabilities for a single change
|
4
|
+
// 93.1% that one group is invalidated
|
5
|
+
// 4.8% that two groups are invalidated
|
6
|
+
// 1.1% that 3 groups are invalidated
|
7
|
+
// 0.1% that 4 or more groups are invalidated
|
8
|
+
//
|
9
|
+
// And these for removing/adding 10 lexically adjacent files
|
10
|
+
// 64.5% that one group is invalidated
|
11
|
+
// 24.8% that two groups are invalidated
|
12
|
+
// 7.8% that 3 groups are invalidated
|
13
|
+
// 2.7% that 4 or more groups are invalidated
|
14
|
+
//
|
15
|
+
// And these for removing/adding 3 random files
|
16
|
+
// 0% that one group is invalidated
|
17
|
+
// 3.7% that two groups are invalidated
|
18
|
+
// 80.8% that 3 groups are invalidated
|
19
|
+
// 12.3% that 4 groups are invalidated
|
20
|
+
// 3.2% that 5 or more groups are invalidated
|
21
|
+
|
22
|
+
/**
|
23
|
+
*
|
24
|
+
* @param {string} a key
|
25
|
+
* @param {string} b key
|
26
|
+
* @returns {number} the similarity as number
|
27
|
+
*/
|
28
|
+
const similarity = (a, b) => {
|
29
|
+
const l = Math.min(a.length, b.length);
|
30
|
+
let dist = 0;
|
31
|
+
for (let i = 0; i < l; i++) {
|
32
|
+
const ca = a.charCodeAt(i);
|
33
|
+
const cb = b.charCodeAt(i);
|
34
|
+
dist += Math.max(0, 10 - Math.abs(ca - cb));
|
35
|
+
}
|
36
|
+
return dist;
|
37
|
+
};
|
38
|
+
|
39
|
+
/**
|
40
|
+
* @param {string} a key
|
41
|
+
* @param {string} b key
|
42
|
+
* @returns {string} the common part and a single char for the difference
|
43
|
+
*/
|
44
|
+
const getName = (a, b) => {
|
45
|
+
const l = Math.min(a.length, b.length);
|
46
|
+
let r = "";
|
47
|
+
for (let i = 0; i < l; i++) {
|
48
|
+
const ca = a.charAt(i);
|
49
|
+
const cb = b.charAt(i);
|
50
|
+
r += ca;
|
51
|
+
if (ca === cb) {
|
52
|
+
continue;
|
53
|
+
}
|
54
|
+
return r;
|
55
|
+
}
|
56
|
+
return a;
|
57
|
+
};
|
58
|
+
|
59
|
+
/**
|
60
|
+
* @template T
|
61
|
+
*/
|
62
|
+
class Node {
|
63
|
+
/**
|
64
|
+
* @param {T} item item
|
65
|
+
* @param {string} key key
|
66
|
+
* @param {number} size size
|
67
|
+
*/
|
68
|
+
constructor(item, key, size) {
|
69
|
+
this.item = item;
|
70
|
+
this.key = key;
|
71
|
+
this.size = size;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @template T
|
77
|
+
*/
|
78
|
+
class Group {
|
79
|
+
/**
|
80
|
+
* @param {Node<T>[]} nodes nodes
|
81
|
+
* @param {number[]} similarities similarities between the nodes (length = nodes.length - 1)
|
82
|
+
*/
|
83
|
+
constructor(nodes, similarities) {
|
84
|
+
this.nodes = nodes;
|
85
|
+
this.similarities = similarities;
|
86
|
+
this.size = nodes.reduce((size, node) => size + node.size, 0);
|
87
|
+
/** @type {string} */
|
88
|
+
this.key = undefined;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
/**
|
93
|
+
* @template T
|
94
|
+
* @typedef {Object} GroupedItems<T>
|
95
|
+
* @property {string} key
|
96
|
+
* @property {T[]} items
|
97
|
+
* @property {number} size
|
98
|
+
*/
|
99
|
+
|
100
|
+
/**
|
101
|
+
* @template T
|
102
|
+
* @typedef {Object} Options
|
103
|
+
* @property {number} maxSize maximum size of a group
|
104
|
+
* @property {number} minSize minimum size of a group (preferred over maximum size)
|
105
|
+
* @property {Iterable<T>} items a list of items
|
106
|
+
* @property {function(T): number} getSize function to get size of an item
|
107
|
+
* @property {function(T): string} getKey function to get the key of an item
|
108
|
+
*/
|
109
|
+
|
110
|
+
/**
|
111
|
+
* @template T
|
112
|
+
* @param {Options<T>} options options object
|
113
|
+
* @returns {GroupedItems<T>[]} grouped items
|
114
|
+
*/
|
115
|
+
module.exports = ({ maxSize, minSize, items, getSize, getKey }) => {
|
116
|
+
/** @type {Group<T>[]} */
|
117
|
+
const result = [];
|
118
|
+
|
119
|
+
const nodes = Array.from(
|
120
|
+
items,
|
121
|
+
item => new Node(item, getKey(item), getSize(item))
|
122
|
+
);
|
123
|
+
|
124
|
+
/** @type {Node<T>[]} */
|
125
|
+
const initialNodes = [];
|
126
|
+
|
127
|
+
// return nodes bigger than maxSize directly as group
|
128
|
+
for (const node of nodes) {
|
129
|
+
if (node.size >= maxSize) {
|
130
|
+
result.push(new Group([node], []));
|
131
|
+
} else {
|
132
|
+
initialNodes.push(node);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
if (initialNodes.length > 0) {
|
137
|
+
// lexically ordering of keys
|
138
|
+
initialNodes.sort((a, b) => {
|
139
|
+
if (a.key < b.key) return -1;
|
140
|
+
if (a.key > b.key) return 1;
|
141
|
+
return 0;
|
142
|
+
});
|
143
|
+
|
144
|
+
// calculate similarities between lexically adjacent nodes
|
145
|
+
/** @type {number[]} */
|
146
|
+
const similarities = [];
|
147
|
+
for (let i = 1; i < initialNodes.length; i++) {
|
148
|
+
const a = initialNodes[i - 1];
|
149
|
+
const b = initialNodes[i];
|
150
|
+
similarities.push(similarity(a.key, b.key));
|
151
|
+
}
|
152
|
+
|
153
|
+
const queue = [new Group(initialNodes, similarities)];
|
154
|
+
|
155
|
+
while (queue.length) {
|
156
|
+
const group = queue.pop();
|
157
|
+
// only groups bigger than maxSize need to be splitted
|
158
|
+
if (group.size < maxSize) {
|
159
|
+
result.push(group);
|
160
|
+
continue;
|
161
|
+
}
|
162
|
+
|
163
|
+
// find unsplittable area from left and right
|
164
|
+
// going minSize from left and right
|
165
|
+
let left = 0;
|
166
|
+
let leftSize = 0;
|
167
|
+
while (leftSize < minSize) {
|
168
|
+
leftSize += group.nodes[left].size;
|
169
|
+
left++;
|
170
|
+
}
|
171
|
+
let right = group.nodes.length - 1;
|
172
|
+
let rightSize = 0;
|
173
|
+
while (rightSize < minSize) {
|
174
|
+
rightSize += group.nodes[right].size;
|
175
|
+
right--;
|
176
|
+
}
|
177
|
+
|
178
|
+
if (left - 1 > right) {
|
179
|
+
// can't split group while holding minSize
|
180
|
+
// because minSize is preferred of maxSize we return
|
181
|
+
// the group here even while it's too big
|
182
|
+
// To avoid this make sure maxSize > minSize * 3
|
183
|
+
result.push(group);
|
184
|
+
continue;
|
185
|
+
}
|
186
|
+
if (left <= right) {
|
187
|
+
// when there is a area between left and right
|
188
|
+
// we look for best split point
|
189
|
+
// we split at the minimum similarity
|
190
|
+
// here key space is separated the most
|
191
|
+
let best = left - 1;
|
192
|
+
let bestSimilarity = group.similarities[best];
|
193
|
+
for (let i = left; i <= right; i++) {
|
194
|
+
const similarity = group.similarities[i];
|
195
|
+
if (similarity < bestSimilarity) {
|
196
|
+
best = i;
|
197
|
+
bestSimilarity = similarity;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
left = best + 1;
|
201
|
+
right = best;
|
202
|
+
}
|
203
|
+
|
204
|
+
// create two new groups for left and right area
|
205
|
+
// and queue them up
|
206
|
+
const rightNodes = [group.nodes[right + 1]];
|
207
|
+
/** @type {number[]} */
|
208
|
+
const rightSimilaries = [];
|
209
|
+
for (let i = right + 2; i < group.nodes.length; i++) {
|
210
|
+
rightSimilaries.push(group.similarities[i - 1]);
|
211
|
+
rightNodes.push(group.nodes[i]);
|
212
|
+
}
|
213
|
+
queue.push(new Group(rightNodes, rightSimilaries));
|
214
|
+
|
215
|
+
const leftNodes = [group.nodes[0]];
|
216
|
+
/** @type {number[]} */
|
217
|
+
const leftSimilaries = [];
|
218
|
+
for (let i = 1; i < left; i++) {
|
219
|
+
leftSimilaries.push(group.similarities[i - 1]);
|
220
|
+
leftNodes.push(group.nodes[i]);
|
221
|
+
}
|
222
|
+
queue.push(new Group(leftNodes, leftSimilaries));
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
// lexically ordering
|
227
|
+
result.sort((a, b) => {
|
228
|
+
if (a.nodes[0].key < b.nodes[0].key) return -1;
|
229
|
+
if (a.nodes[0].key > b.nodes[0].key) return 1;
|
230
|
+
return 0;
|
231
|
+
});
|
232
|
+
|
233
|
+
// give every group a name
|
234
|
+
for (let i = 0; i < result.length; i++) {
|
235
|
+
const group = result[i];
|
236
|
+
const first = group.nodes[0];
|
237
|
+
const last = group.nodes[group.nodes.length - 1];
|
238
|
+
let name = getName(first.key, last.key);
|
239
|
+
group.key = name;
|
240
|
+
}
|
241
|
+
|
242
|
+
// return the results
|
243
|
+
return result.map(group => {
|
244
|
+
/** @type {GroupedItems} */
|
245
|
+
return {
|
246
|
+
key: group.key,
|
247
|
+
items: group.nodes.map(node => node.item),
|
248
|
+
size: group.size
|
249
|
+
};
|
250
|
+
});
|
251
|
+
};
|
package/lib/util/identifier.js
CHANGED
@@ -74,3 +74,30 @@ exports.makePathsRelative = (context, identifier, cache) => {
|
|
74
74
|
return relativePath;
|
75
75
|
}
|
76
76
|
};
|
77
|
+
|
78
|
+
/**
|
79
|
+
* @param {string} context absolute context path
|
80
|
+
* @param {string} request any request string may containing absolute paths, query string, etc.
|
81
|
+
* @returns {string} a new request string avoiding absolute paths when possible
|
82
|
+
*/
|
83
|
+
exports.contextify = (context, request) => {
|
84
|
+
return request
|
85
|
+
.split("!")
|
86
|
+
.map(r => {
|
87
|
+
const splitPath = r.split("?", 2);
|
88
|
+
if (/^[a-zA-Z]:\\/.test(splitPath[0])) {
|
89
|
+
splitPath[0] = path.win32.relative(context, splitPath[0]);
|
90
|
+
if (!/^[a-zA-Z]:\\/.test(splitPath[0])) {
|
91
|
+
splitPath[0] = splitPath[0].replace(/\\/g, "/");
|
92
|
+
}
|
93
|
+
}
|
94
|
+
if (/^\//.test(splitPath[0])) {
|
95
|
+
splitPath[0] = path.posix.relative(context, splitPath[0]);
|
96
|
+
}
|
97
|
+
if (!/^(\.\.\/|\/|[a-zA-Z]:\\)/.test(splitPath[0])) {
|
98
|
+
splitPath[0] = "./" + splitPath[0];
|
99
|
+
}
|
100
|
+
return splitPath.join("?");
|
101
|
+
})
|
102
|
+
.join("!");
|
103
|
+
};
|