webpack 5.35.0 → 5.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of webpack might be problematic. Click here for more details.
- package/lib/ChunkGraph.js +113 -30
- package/lib/Compilation.js +71 -41
- package/lib/Compiler.js +25 -11
- package/lib/ContextModuleFactory.js +4 -3
- package/lib/Dependency.js +74 -4
- package/lib/FlagDependencyExportsPlugin.js +38 -37
- package/lib/InitFragment.js +21 -6
- package/lib/ModuleGraph.js +19 -53
- package/lib/NormalModuleFactory.js +27 -23
- package/lib/WebpackOptionsApply.js +1 -0
- package/lib/buildChunkGraph.js +7 -2
- package/lib/cache/PackFileCacheStrategy.js +65 -4
- package/lib/config/defaults.js +2 -1
- package/lib/config/normalization.js +1 -0
- package/lib/dependencies/HarmonyExportInitFragment.js +47 -0
- package/lib/dependencies/NullDependency.js +0 -8
- package/lib/dependencies/WorkerPlugin.js +18 -3
- package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +7 -0
- package/lib/javascript/JavascriptModulesPlugin.js +6 -2
- package/lib/javascript/JavascriptParser.js +39 -31
- package/lib/library/AmdLibraryPlugin.js +2 -1
- package/lib/optimize/InnerGraphPlugin.js +8 -9
- package/lib/util/AsyncQueue.js +6 -1
- package/lib/util/comparators.js +22 -16
- package/lib/util/smartGrouping.js +76 -40
- package/lib/web/JsonpChunkLoadingRuntimeModule.js +4 -2
- package/package.json +5 -5
- package/schemas/WebpackOptions.check.js +1 -1
- package/schemas/WebpackOptions.json +4 -0
- package/types.d.ts +101 -7
@@ -161,7 +161,7 @@ class JavascriptParser extends Parser {
|
|
161
161
|
evaluateCallExpressionMember: new HookMap(
|
162
162
|
() => new SyncBailHook(["expression", "param"])
|
163
163
|
),
|
164
|
-
/** @type {HookMap<SyncBailHook<[ExpressionNode | DeclarationNode, number], boolean | void>>} */
|
164
|
+
/** @type {HookMap<SyncBailHook<[ExpressionNode | DeclarationNode | PrivateIdentifierNode, number], boolean | void>>} */
|
165
165
|
isPure: new HookMap(
|
166
166
|
() => new SyncBailHook(["expression", "commentsStartPosition"])
|
167
167
|
),
|
@@ -176,8 +176,10 @@ class JavascriptParser extends Parser {
|
|
176
176
|
statementIf: new SyncBailHook(["statement"]),
|
177
177
|
/** @type {SyncBailHook<[ExpressionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
|
178
178
|
classExtendsExpression: new SyncBailHook(["expression", "statement"]),
|
179
|
-
/** @type {SyncBailHook<[MethodDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
|
179
|
+
/** @type {SyncBailHook<[MethodDefinitionNode | PropertyDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
|
180
180
|
classBodyElement: new SyncBailHook(["element", "statement"]),
|
181
|
+
/** @type {SyncBailHook<[ExpressionNode, MethodDefinitionNode | PropertyDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
|
182
|
+
classBodyValue: new SyncBailHook(["expression", "element", "statement"]),
|
181
183
|
/** @type {HookMap<SyncBailHook<[LabeledStatementNode], boolean | void>>} */
|
182
184
|
label: new HookMap(() => new SyncBailHook(["statement"])),
|
183
185
|
/** @type {SyncBailHook<[StatementNode, ImportSource], boolean | void>} */
|
@@ -1363,29 +1365,30 @@ class JavascriptParser extends Parser {
|
|
1363
1365
|
}
|
1364
1366
|
}
|
1365
1367
|
if (classy.body && classy.body.type === "ClassBody") {
|
1366
|
-
const
|
1367
|
-
for (const classElement of classy.body.body) {
|
1368
|
+
for (const classElement of /** @type {TODO} */ (classy.body.body)) {
|
1368
1369
|
if (!this.hooks.classBodyElement.call(classElement, classy)) {
|
1369
|
-
if (classElement.
|
1370
|
-
this.
|
1371
|
-
|
1372
|
-
|
1370
|
+
if (classElement.computed && classElement.key) {
|
1371
|
+
this.walkExpression(classElement.key);
|
1372
|
+
}
|
1373
|
+
if (classElement.value) {
|
1374
|
+
if (
|
1375
|
+
!this.hooks.classBodyValue.call(
|
1376
|
+
classElement.value,
|
1377
|
+
classElement,
|
1378
|
+
classy
|
1379
|
+
)
|
1380
|
+
) {
|
1381
|
+
const wasTopLevel = this.scope.topLevelScope;
|
1382
|
+
this.scope.topLevelScope = false;
|
1383
|
+
this.walkExpression(classElement.value);
|
1384
|
+
this.scope.topLevelScope = wasTopLevel;
|
1385
|
+
}
|
1373
1386
|
}
|
1374
|
-
// TODO add support for ClassProperty here once acorn supports it
|
1375
1387
|
}
|
1376
1388
|
}
|
1377
1389
|
}
|
1378
1390
|
}
|
1379
1391
|
|
1380
|
-
walkMethodDefinition(methodDefinition) {
|
1381
|
-
if (methodDefinition.computed && methodDefinition.key) {
|
1382
|
-
this.walkExpression(methodDefinition.key);
|
1383
|
-
}
|
1384
|
-
if (methodDefinition.value) {
|
1385
|
-
this.walkExpression(methodDefinition.value);
|
1386
|
-
}
|
1387
|
-
}
|
1388
|
-
|
1389
1392
|
// Pre walking iterates the scope for variable declarations
|
1390
1393
|
preWalkStatements(statements) {
|
1391
1394
|
for (let index = 0, len = statements.length; index < len; index++) {
|
@@ -3316,7 +3319,7 @@ class JavascriptParser extends Parser {
|
|
3316
3319
|
}
|
3317
3320
|
|
3318
3321
|
/**
|
3319
|
-
* @param {ExpressionNode | DeclarationNode | null | undefined} expr an expression
|
3322
|
+
* @param {ExpressionNode | DeclarationNode | PrivateIdentifierNode | null | undefined} expr an expression
|
3320
3323
|
* @param {number} commentsStartPos source position from which annotation comments are checked
|
3321
3324
|
* @returns {boolean} true, when the expression is pure
|
3322
3325
|
*/
|
@@ -3328,27 +3331,32 @@ class JavascriptParser extends Parser {
|
|
3328
3331
|
if (typeof result === "boolean") return result;
|
3329
3332
|
switch (expr.type) {
|
3330
3333
|
case "ClassDeclaration":
|
3331
|
-
case "ClassExpression":
|
3334
|
+
case "ClassExpression": {
|
3332
3335
|
if (expr.body.type !== "ClassBody") return false;
|
3333
3336
|
if (expr.superClass && !this.isPure(expr.superClass, expr.range[0])) {
|
3334
3337
|
return false;
|
3335
3338
|
}
|
3336
|
-
|
3337
|
-
|
3338
|
-
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3343
|
-
|
3344
|
-
|
3345
|
-
|
3346
|
-
|
3339
|
+
const items = /** @type {(MethodDefinitionNode | PropertyDefinitionNode)[]} */ (expr
|
3340
|
+
.body.body);
|
3341
|
+
return items.every(
|
3342
|
+
item =>
|
3343
|
+
(!item.computed ||
|
3344
|
+
!item.key ||
|
3345
|
+
this.isPure(item.key, item.range[0])) &&
|
3346
|
+
(!item.static ||
|
3347
|
+
!item.value ||
|
3348
|
+
this.isPure(
|
3349
|
+
item.value,
|
3350
|
+
item.key ? item.key.range[1] : item.range[0]
|
3351
|
+
))
|
3352
|
+
);
|
3353
|
+
}
|
3347
3354
|
|
3348
3355
|
case "FunctionDeclaration":
|
3349
3356
|
case "FunctionExpression":
|
3350
3357
|
case "ArrowFunctionExpression":
|
3351
3358
|
case "Literal":
|
3359
|
+
case "PrivateIdentifier":
|
3352
3360
|
return true;
|
3353
3361
|
|
3354
3362
|
case "VariableDeclaration":
|
@@ -107,7 +107,8 @@ class AmdLibraryPlugin extends AbstractLibraryPlugin {
|
|
107
107
|
const fnStart =
|
108
108
|
(modern
|
109
109
|
? `(${externalsArguments}) => {`
|
110
|
-
: `function(${externalsArguments}) {`) +
|
110
|
+
: `function(${externalsArguments}) {`) +
|
111
|
+
(iife || !chunk.hasRuntime() ? " return " : "\n");
|
111
112
|
const fnEnd = iife ? ";\n}" : "\n}";
|
112
113
|
|
113
114
|
if (this.requireAsWrapper) {
|
@@ -238,21 +238,20 @@ class InnerGraphPlugin {
|
|
238
238
|
}
|
239
239
|
);
|
240
240
|
|
241
|
-
parser.hooks.
|
241
|
+
parser.hooks.classBodyValue.tap(
|
242
242
|
"InnerGraphPlugin",
|
243
|
-
(element, statement) => {
|
243
|
+
(expression, element, statement) => {
|
244
244
|
if (!InnerGraph.isEnabled(parser.state)) return;
|
245
245
|
if (parser.scope.topLevelScope === true) {
|
246
246
|
const fn = classWithTopLevelSymbol.get(statement);
|
247
247
|
if (fn) {
|
248
|
-
if (
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
248
|
+
if (
|
249
|
+
!element.static ||
|
250
|
+
parser.isPure(
|
251
|
+
expression,
|
252
|
+
element.key ? element.key.range[1] : element.range[0]
|
253
|
+
)
|
253
254
|
) {
|
254
|
-
// TODO add test case once acorn supports it
|
255
|
-
// Currently this is not parsable
|
256
255
|
InnerGraph.setTopLevelSymbol(parser.state, fn);
|
257
256
|
} else {
|
258
257
|
InnerGraph.setTopLevelSymbol(parser.state, undefined);
|
package/lib/util/AsyncQueue.js
CHANGED
@@ -119,7 +119,12 @@ class AsyncQueue {
|
|
119
119
|
const entry = this._entries.get(key);
|
120
120
|
if (entry !== undefined) {
|
121
121
|
if (entry.state === DONE_STATE) {
|
122
|
-
|
122
|
+
if (inHandleResult++ > 3) {
|
123
|
+
process.nextTick(() => callback(entry.error, entry.result));
|
124
|
+
} else {
|
125
|
+
callback(entry.error, entry.result);
|
126
|
+
}
|
127
|
+
inHandleResult--;
|
123
128
|
} else if (entry.callbacks === undefined) {
|
124
129
|
entry.callbacks = [callback];
|
125
130
|
} else {
|
package/lib/util/comparators.js
CHANGED
@@ -433,21 +433,27 @@ exports.compareLocations = (a, b) => {
|
|
433
433
|
if (isObjectB) return -1;
|
434
434
|
return 0;
|
435
435
|
}
|
436
|
-
if ("start" in a
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
}
|
436
|
+
if ("start" in a) {
|
437
|
+
if ("start" in b) {
|
438
|
+
const ap = a.start;
|
439
|
+
const bp = b.start;
|
440
|
+
if (ap.line < bp.line) return -1;
|
441
|
+
if (ap.line > bp.line) return 1;
|
442
|
+
if (ap.column < bp.column) return -1;
|
443
|
+
if (ap.column > bp.column) return 1;
|
444
|
+
} else return -1;
|
445
|
+
} else if ("start" in b) return 1;
|
446
|
+
if ("name" in a) {
|
447
|
+
if ("name" in b) {
|
448
|
+
if (a.name < b.name) return -1;
|
449
|
+
if (a.name > b.name) return 1;
|
450
|
+
} else return -1;
|
451
|
+
} else if ("name" in b) return 1;
|
452
|
+
if ("index" in a) {
|
453
|
+
if ("index" in b) {
|
454
|
+
if (a.index < b.index) return -1;
|
455
|
+
if (a.index > b.index) return 1;
|
456
|
+
} else return -1;
|
457
|
+
} else if ("index" in b) return 1;
|
452
458
|
return 0;
|
453
459
|
};
|
@@ -23,9 +23,16 @@
|
|
23
23
|
|
24
24
|
/**
|
25
25
|
* @template T
|
26
|
+
* @template R
|
26
27
|
* @typedef {Object} ItemWithGroups
|
27
28
|
* @property {T} item
|
28
|
-
* @property {Set<
|
29
|
+
* @property {Set<Group<T, R>>} groups
|
30
|
+
*/
|
31
|
+
|
32
|
+
/**
|
33
|
+
* @template T
|
34
|
+
* @template R
|
35
|
+
* @typedef {{ config: GroupConfig<T, R>, name: string, alreadyGrouped: boolean, items: Set<ItemWithGroups<T, R>> | undefined }} Group
|
29
36
|
*/
|
30
37
|
|
31
38
|
/**
|
@@ -36,22 +43,32 @@
|
|
36
43
|
* @returns {(R | T)[]} grouped items
|
37
44
|
*/
|
38
45
|
const smartGrouping = (items, groupConfigs) => {
|
39
|
-
/** @type {Set<ItemWithGroups<T>>} */
|
46
|
+
/** @type {Set<ItemWithGroups<T, R>>} */
|
40
47
|
const itemsWithGroups = new Set();
|
41
|
-
/** @type {Map<string,
|
42
|
-
const
|
48
|
+
/** @type {Map<string, Group<T, R>>} */
|
49
|
+
const allGroups = new Map();
|
43
50
|
for (const item of items) {
|
51
|
+
/** @type {Set<Group<T, R>>} */
|
44
52
|
const groups = new Set();
|
45
53
|
for (let i = 0; i < groupConfigs.length; i++) {
|
46
54
|
const groupConfig = groupConfigs[i];
|
47
55
|
const keys = groupConfig.getKeys(item);
|
48
56
|
if (keys) {
|
49
|
-
for (const
|
50
|
-
const
|
51
|
-
|
52
|
-
|
57
|
+
for (const name of keys) {
|
58
|
+
const key = `${i}:${name}`;
|
59
|
+
let group = allGroups.get(key);
|
60
|
+
if (group === undefined) {
|
61
|
+
allGroups.set(
|
62
|
+
key,
|
63
|
+
(group = {
|
64
|
+
config: groupConfig,
|
65
|
+
name,
|
66
|
+
alreadyGrouped: false,
|
67
|
+
items: undefined
|
68
|
+
})
|
69
|
+
);
|
53
70
|
}
|
54
|
-
groups.add(
|
71
|
+
groups.add(group);
|
55
72
|
}
|
56
73
|
}
|
57
74
|
}
|
@@ -60,48 +77,62 @@ const smartGrouping = (items, groupConfigs) => {
|
|
60
77
|
groups
|
61
78
|
});
|
62
79
|
}
|
63
|
-
const alreadyGrouped = new Set();
|
64
80
|
/**
|
65
|
-
* @param {Set<ItemWithGroups<T>>} itemsWithGroups input items with groups
|
81
|
+
* @param {Set<ItemWithGroups<T, R>>} itemsWithGroups input items with groups
|
66
82
|
* @returns {(T | R)[]} groups items
|
67
83
|
*/
|
68
84
|
const runGrouping = itemsWithGroups => {
|
69
85
|
const totalSize = itemsWithGroups.size;
|
70
|
-
/** @type {Map<string, Set<ItemWithGroups<T>>>} */
|
71
|
-
const groupMap = new Map();
|
72
86
|
for (const entry of itemsWithGroups) {
|
73
87
|
for (const group of entry.groups) {
|
74
|
-
if (alreadyGrouped
|
75
|
-
const
|
76
|
-
if (
|
77
|
-
|
88
|
+
if (group.alreadyGrouped) continue;
|
89
|
+
const items = group.items;
|
90
|
+
if (items === undefined) {
|
91
|
+
group.items = new Set([entry]);
|
78
92
|
} else {
|
79
|
-
|
93
|
+
items.add(entry);
|
80
94
|
}
|
81
95
|
}
|
82
96
|
}
|
83
|
-
/** @type {Set<
|
84
|
-
const
|
97
|
+
/** @type {Map<Group<T, R>, { items: Set<ItemWithGroups<T, R>>, options: GroupOptions | false | undefined, used: boolean }>} */
|
98
|
+
const groupMap = new Map();
|
99
|
+
for (const group of allGroups.values()) {
|
100
|
+
if (group.items) {
|
101
|
+
const items = group.items;
|
102
|
+
group.items = undefined;
|
103
|
+
groupMap.set(group, {
|
104
|
+
items,
|
105
|
+
options: undefined,
|
106
|
+
used: false
|
107
|
+
});
|
108
|
+
}
|
109
|
+
}
|
85
110
|
/** @type {(T | R)[]} */
|
86
111
|
const results = [];
|
87
112
|
for (;;) {
|
113
|
+
/** @type {Group<T, R>} */
|
88
114
|
let bestGroup = undefined;
|
89
115
|
let bestGroupSize = -1;
|
90
116
|
let bestGroupItems = undefined;
|
91
117
|
let bestGroupOptions = undefined;
|
92
|
-
for (const [group,
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
groupConfig.
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
118
|
+
for (const [group, state] of groupMap) {
|
119
|
+
const { items, used } = state;
|
120
|
+
let options = state.options;
|
121
|
+
if (options === undefined) {
|
122
|
+
const groupConfig = group.config;
|
123
|
+
state.options = options =
|
124
|
+
(groupConfig.getOptions &&
|
125
|
+
groupConfig.getOptions(
|
126
|
+
group.name,
|
127
|
+
Array.from(items, ({ item }) => item)
|
128
|
+
)) ||
|
129
|
+
false;
|
130
|
+
}
|
131
|
+
|
101
132
|
const force = options && options.force;
|
102
133
|
if (!force) {
|
103
134
|
if (bestGroupOptions && bestGroupOptions.force) continue;
|
104
|
-
if (
|
135
|
+
if (used) continue;
|
105
136
|
if (items.size <= 1 || totalSize - items.size <= 1) {
|
106
137
|
continue;
|
107
138
|
}
|
@@ -137,25 +168,30 @@ const smartGrouping = (items, groupConfigs) => {
|
|
137
168
|
itemsWithGroups.delete(item);
|
138
169
|
// Remove all groups that items have from the map to not select them again
|
139
170
|
for (const group of item.groups) {
|
140
|
-
const
|
141
|
-
if (
|
142
|
-
|
143
|
-
|
171
|
+
const state = groupMap.get(group);
|
172
|
+
if (state !== undefined) {
|
173
|
+
state.items.delete(item);
|
174
|
+
if (state.items.size === 0) {
|
175
|
+
groupMap.delete(group);
|
176
|
+
} else {
|
177
|
+
state.options = undefined;
|
178
|
+
if (groupChildren) {
|
179
|
+
state.used = true;
|
180
|
+
}
|
181
|
+
}
|
144
182
|
}
|
145
183
|
}
|
146
184
|
}
|
147
185
|
groupMap.delete(bestGroup);
|
148
186
|
|
149
|
-
const
|
150
|
-
const
|
151
|
-
const key = bestGroup.slice(idx + 1);
|
152
|
-
const groupConfig = groupConfigs[+configKey];
|
187
|
+
const key = bestGroup.name;
|
188
|
+
const groupConfig = bestGroup.config;
|
153
189
|
|
154
190
|
const allItems = Array.from(items, ({ item }) => item);
|
155
191
|
|
156
|
-
alreadyGrouped
|
192
|
+
bestGroup.alreadyGrouped = true;
|
157
193
|
const children = groupChildren ? runGrouping(items) : allItems;
|
158
|
-
alreadyGrouped
|
194
|
+
bestGroup.alreadyGrouped = false;
|
159
195
|
|
160
196
|
results.push(groupConfig.createGroup(key, children, allItems));
|
161
197
|
}
|
@@ -400,7 +400,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
|
|
400
400
|
"}"
|
401
401
|
]),
|
402
402
|
"}",
|
403
|
-
"if(runtime) runtime(__webpack_require__);",
|
403
|
+
"if(runtime) var result = runtime(__webpack_require__);",
|
404
404
|
"if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);",
|
405
405
|
"for(;i < chunkIds.length; i++) {",
|
406
406
|
Template.indent([
|
@@ -411,7 +411,9 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
|
|
411
411
|
"installedChunks[chunkIds[i]] = 0;"
|
412
412
|
]),
|
413
413
|
"}",
|
414
|
-
withOnChunkLoad
|
414
|
+
withOnChunkLoad
|
415
|
+
? `return ${RuntimeGlobals.onChunksLoaded}(result);`
|
416
|
+
: ""
|
415
417
|
]
|
416
418
|
)}`,
|
417
419
|
"",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "webpack",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.36.2",
|
4
4
|
"author": "Tobias Koppers @sokra",
|
5
5
|
"description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.",
|
6
6
|
"license": "MIT",
|
@@ -10,7 +10,7 @@
|
|
10
10
|
"@webassemblyjs/ast": "1.11.0",
|
11
11
|
"@webassemblyjs/wasm-edit": "1.11.0",
|
12
12
|
"@webassemblyjs/wasm-parser": "1.11.0",
|
13
|
-
"acorn": "^8.
|
13
|
+
"acorn": "^8.2.1",
|
14
14
|
"browserslist": "^4.14.5",
|
15
15
|
"chrome-trace-event": "^1.0.2",
|
16
16
|
"enhanced-resolve": "^5.8.0",
|
@@ -39,7 +39,7 @@
|
|
39
39
|
"@babel/preset-react": "^7.10.4",
|
40
40
|
"@types/es-module-lexer": "^0.3.0",
|
41
41
|
"@types/jest": "^26.0.15",
|
42
|
-
"@types/node": "^
|
42
|
+
"@types/node": "^15.0.1",
|
43
43
|
"babel-loader": "^8.1.0",
|
44
44
|
"benchmark": "^2.1.4",
|
45
45
|
"bundle-loader": "^0.5.6",
|
@@ -55,7 +55,7 @@
|
|
55
55
|
"eslint": "^7.14.0",
|
56
56
|
"eslint-config-prettier": "^8.1.0",
|
57
57
|
"eslint-plugin-jest": "^24.1.3",
|
58
|
-
"eslint-plugin-jsdoc": "^
|
58
|
+
"eslint-plugin-jsdoc": "^33.0.0",
|
59
59
|
"eslint-plugin-node": "^11.0.0",
|
60
60
|
"eslint-plugin-prettier": "^3.1.4",
|
61
61
|
"file-loader": "^6.0.0",
|
@@ -92,7 +92,7 @@
|
|
92
92
|
"style-loader": "^2.0.0",
|
93
93
|
"terser": "^5.5.0",
|
94
94
|
"toml": "^3.0.0",
|
95
|
-
"tooling": "webpack/tooling#v1.
|
95
|
+
"tooling": "webpack/tooling#v1.18.0",
|
96
96
|
"ts-loader": "^8.0.2",
|
97
97
|
"typescript": "^4.2.0-beta",
|
98
98
|
"url-loader": "^4.1.0",
|