webpack 4.24.0 → 4.26.1

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.
@@ -4,8 +4,15 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ const validateOptions = require("schema-utils");
8
+ const schema = require("../schemas/plugins/ProgressPlugin.json");
9
+
10
+ /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginArgument} ProgressPluginArgument */
11
+ /** @typedef {import("../declarations/plugins/ProgressPlugin").ProgressPluginOptions} ProgressPluginOptions */
12
+
7
13
  const createDefaultHandler = profile => {
8
14
  let lineCaretPosition = 0;
15
+ let lastMessage = "";
9
16
  let lastState;
10
17
  let lastStateTime;
11
18
 
@@ -46,8 +53,11 @@ const createDefaultHandler = profile => {
46
53
  lastStateTime = now;
47
54
  }
48
55
  }
49
- goToLineStart(msg);
50
- process.stderr.write(msg);
56
+ if (lastMessage !== msg) {
57
+ goToLineStart(msg);
58
+ process.stderr.write(msg);
59
+ lastMessage = msg;
60
+ }
51
61
  };
52
62
 
53
63
  const goToLineStart = nextMessage => {
@@ -66,19 +76,34 @@ const createDefaultHandler = profile => {
66
76
  };
67
77
 
68
78
  class ProgressPlugin {
79
+ /**
80
+ * @param {ProgressPluginArgument} options options
81
+ */
69
82
  constructor(options) {
70
83
  if (typeof options === "function") {
71
84
  options = {
72
85
  handler: options
73
86
  };
74
87
  }
88
+
75
89
  options = options || {};
90
+ validateOptions(schema, options, "Progress Plugin");
91
+ options = Object.assign({}, ProgressPlugin.defaultOptions, options);
92
+
76
93
  this.profile = options.profile;
77
94
  this.handler = options.handler;
95
+ this.modulesCount = options.modulesCount;
96
+ this.showEntries = options.entries;
97
+ this.showModules = options.modules;
98
+ this.showActiveModules = options.activeModules;
78
99
  }
79
100
 
80
101
  apply(compiler) {
102
+ const { modulesCount } = this;
81
103
  const handler = this.handler || createDefaultHandler(this.profile);
104
+ const showEntries = this.showEntries;
105
+ const showModules = this.showModules;
106
+ const showActiveModules = this.showActiveModules;
82
107
  if (compiler.compilers) {
83
108
  const states = new Array(compiler.compilers.length);
84
109
  compiler.compilers.forEach((compiler, idx) => {
@@ -95,48 +120,97 @@ class ProgressPlugin {
95
120
  });
96
121
  } else {
97
122
  let lastModulesCount = 0;
98
- let moduleCount = 500;
123
+ let lastEntriesCount = 0;
124
+ let moduleCount = modulesCount;
125
+ let entriesCount = 1;
99
126
  let doneModules = 0;
100
- const activeModules = [];
101
-
102
- const update = module => {
103
- handler(
104
- 0.1 + (doneModules / Math.max(lastModulesCount, moduleCount)) * 0.6,
105
- "building modules",
106
- `${doneModules}/${moduleCount} modules`,
107
- `${activeModules.length} active`,
108
- activeModules[activeModules.length - 1]
109
- );
127
+ let doneEntries = 0;
128
+ const activeModules = new Set();
129
+ let lastActiveModule = "";
130
+
131
+ const update = () => {
132
+ const percentByModules =
133
+ doneModules / Math.max(lastModulesCount, moduleCount);
134
+ const percentByEntries =
135
+ doneEntries / Math.max(lastEntriesCount, entriesCount);
136
+
137
+ const items = [
138
+ 0.1 + Math.max(percentByModules, percentByEntries) * 0.6,
139
+ "building"
140
+ ];
141
+ if (showEntries) {
142
+ items.push(`${doneEntries}/${entriesCount} entries`);
143
+ }
144
+ if (showModules) {
145
+ items.push(`${doneModules}/${moduleCount} modules`);
146
+ }
147
+ if (showActiveModules) {
148
+ items.push(`${activeModules.size} active`);
149
+ items.push(lastActiveModule);
150
+ }
151
+ handler(...items);
152
+ };
153
+
154
+ const moduleAdd = module => {
155
+ moduleCount++;
156
+ if (showActiveModules) {
157
+ const ident = module.identifier();
158
+ if (ident) {
159
+ activeModules.add(ident);
160
+ lastActiveModule = ident;
161
+ }
162
+ }
163
+ update();
164
+ };
165
+
166
+ const entryAdd = (entry, name) => {
167
+ entriesCount++;
168
+ update();
110
169
  };
111
170
 
112
171
  const moduleDone = module => {
113
172
  doneModules++;
114
- const ident = module.identifier();
115
- if (ident) {
116
- const idx = activeModules.indexOf(ident);
117
- if (idx >= 0) activeModules.splice(idx, 1);
173
+ if (showActiveModules) {
174
+ const ident = module.identifier();
175
+ if (ident) {
176
+ activeModules.delete(ident);
177
+ if (lastActiveModule === ident) {
178
+ lastActiveModule = "";
179
+ for (const m of activeModules) {
180
+ lastActiveModule = m;
181
+ }
182
+ }
183
+ }
118
184
  }
119
185
  update();
120
186
  };
187
+
188
+ const entryDone = (entry, name) => {
189
+ doneEntries++;
190
+ update();
191
+ };
192
+
121
193
  compiler.hooks.compilation.tap("ProgressPlugin", compilation => {
122
194
  if (compilation.compiler.isChild()) return;
123
195
  lastModulesCount = moduleCount;
124
- moduleCount = 0;
125
- doneModules = 0;
196
+ lastEntriesCount = entriesCount;
197
+ moduleCount = entriesCount = 0;
198
+ doneModules = doneEntries = 0;
126
199
  handler(0, "compiling");
127
- compilation.hooks.buildModule.tap("ProgressPlugin", module => {
128
- moduleCount++;
129
- const ident = module.identifier();
130
- if (ident) {
131
- activeModules.push(ident);
132
- }
133
- update();
134
- });
200
+
201
+ compilation.hooks.buildModule.tap("ProgressPlugin", moduleAdd);
135
202
  compilation.hooks.failedModule.tap("ProgressPlugin", moduleDone);
136
203
  compilation.hooks.succeedModule.tap("ProgressPlugin", moduleDone);
204
+
205
+ compilation.hooks.addEntry.tap("ProgressPlugin", entryAdd);
206
+ compilation.hooks.failedEntry.tap("ProgressPlugin", entryDone);
207
+ compilation.hooks.succeedEntry.tap("ProgressPlugin", entryDone);
208
+
137
209
  const hooks = {
138
210
  finishModules: "finish module graph",
139
211
  seal: "sealing",
212
+ beforeChunks: "chunk graph",
213
+ afterChunks: "after chunk graph",
140
214
  optimizeDependenciesBasic: "basic dependencies optimization",
141
215
  optimizeDependencies: "dependencies optimization",
142
216
  optimizeDependenciesAdvanced: "advanced dependencies optimization",
@@ -171,6 +245,7 @@ class ProgressPlugin {
171
245
  recordModules: "record modules",
172
246
  recordChunks: "record chunks",
173
247
  beforeHash: "hashing",
248
+ contentHash: "content hashing",
174
249
  afterHash: "after hashing",
175
250
  recordHash: "record hash",
176
251
  beforeModuleAssets: "module assets processing",
@@ -243,4 +318,14 @@ class ProgressPlugin {
243
318
  }
244
319
  }
245
320
  }
321
+
322
+ ProgressPlugin.defaultOptions = {
323
+ profile: false,
324
+ modulesCount: 500,
325
+ modules: true,
326
+ activeModules: true,
327
+ // TODO webpack 5 default this to true
328
+ entries: false
329
+ };
330
+
246
331
  module.exports = ProgressPlugin;
@@ -76,9 +76,8 @@ class ProvidePlugin {
76
76
  normalModuleFactory.hooks.parser
77
77
  .for("javascript/dynamic")
78
78
  .tap("ProvidePlugin", handler);
79
- normalModuleFactory.hooks.parser
80
- .for("javascript/esm")
81
- .tap("ProvidePlugin", handler);
79
+
80
+ // Disable ProvidePlugin for javascript/esm, see https://github.com/webpack/webpack/issues/7032
82
81
  }
83
82
  );
84
83
  }
@@ -302,10 +302,10 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
302
302
  this.set("optimization.minimizer", "make", options => [
303
303
  {
304
304
  apply: compiler => {
305
- // Lazy load the uglifyjs plugin
306
- const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
305
+ // Lazy load the Terser plugin
306
+ const TerserPlugin = require("terser-webpack-plugin");
307
307
  const SourceMapDevToolPlugin = require("./SourceMapDevToolPlugin");
308
- new UglifyJsPlugin({
308
+ new TerserPlugin({
309
309
  cache: true,
310
310
  parallel: true,
311
311
  sourceMap:
@@ -136,7 +136,9 @@ class AMDDefineDependencyParserPlugin {
136
136
  param.range,
137
137
  param,
138
138
  expr,
139
- this.options
139
+ this.options,
140
+ {},
141
+ parser
140
142
  );
141
143
  if (!dep) return;
142
144
  dep.loc = expr.loc;
@@ -142,7 +142,9 @@ class AMDRequireDependenciesBlockParserPlugin {
142
142
  param.range,
143
143
  param,
144
144
  expr,
145
- this.options
145
+ this.options,
146
+ {},
147
+ parser
146
148
  );
147
149
  if (!dep) return;
148
150
  dep.loc = expr.loc;
@@ -35,7 +35,9 @@ class CommonJsRequireDependencyParserPlugin {
35
35
  expr.range,
36
36
  param,
37
37
  expr,
38
- options
38
+ options,
39
+ {},
40
+ parser
39
41
  );
40
42
  if (!dep) return;
41
43
  dep.loc = expr.loc;
@@ -47,7 +47,9 @@ ContextDependencyHelpers.create = (
47
47
  param,
48
48
  expr,
49
49
  options,
50
- contextOptions
50
+ contextOptions,
51
+ // when parser is not passed in, expressions won't be walked
52
+ parser = null
51
53
  ) => {
52
54
  if (param.isTemplateString()) {
53
55
  let prefixRaw = param.quasis[0].string;
@@ -55,19 +57,30 @@ ContextDependencyHelpers.create = (
55
57
  param.quasis.length > 1
56
58
  ? param.quasis[param.quasis.length - 1].string
57
59
  : "";
58
- const prefixRange = [param.quasis[0].range[0], param.quasis[0].range[1]];
59
- const postfixRange =
60
- param.quasis.length > 1
61
- ? param.quasis[param.quasis.length - 1].range
62
- : "";
60
+
63
61
  const valueRange = param.range;
64
62
  const { context, prefix } = splitContextFromPrefix(prefixRaw);
65
63
  const { postfix, query } = splitQueryFromPostfix(postfixRaw);
66
- // If there are more than two quasis, maybe the generated RegExp can be more precise?
64
+
65
+ // When there are more than two quasis, the generated RegExp can be more precise
66
+ // We join the quasis with the expression regexp
67
+ const innerQuasis = param.quasis.slice(1, param.quasis.length - 1);
68
+ const innerRegExp =
69
+ options.wrappedContextRegExp.source +
70
+ innerQuasis
71
+ .map(q => quotemeta(q.string) + options.wrappedContextRegExp.source)
72
+ .join("");
73
+
74
+ // Example: `./context/pre${e}inner${e}inner2${e}post?query`
75
+ // context: "./context"
76
+ // prefix: "./pre"
77
+ // innerQuasis: [BEE("inner"), BEE("inner2")]
78
+ // (BEE = BasicEvaluatedExpression)
79
+ // postfix: "post"
80
+ // query: "?query"
81
+ // regExp: /^\.\/pre.*inner.*inner2.*post$/
67
82
  const regExp = new RegExp(
68
- `^${quotemeta(prefix)}${options.wrappedContextRegExp.source}${quotemeta(
69
- postfix
70
- )}$`
83
+ `^${quotemeta(prefix)}${innerRegExp}${quotemeta(postfix)}$`
71
84
  );
72
85
  const dep = new Dep(
73
86
  Object.assign(
@@ -84,18 +97,48 @@ ContextDependencyHelpers.create = (
84
97
  );
85
98
  dep.loc = expr.loc;
86
99
  const replaces = [];
87
- if (prefixRange && prefix !== prefixRaw) {
88
- replaces.push({
89
- range: prefixRange,
90
- value: prefix
91
- });
92
- }
93
- if (postfixRange && postfix !== postfixRaw) {
94
- replaces.push({
95
- range: postfixRange,
96
- value: postfix
97
- });
98
- }
100
+
101
+ param.parts.forEach((part, i) => {
102
+ if (i % 2 === 0) {
103
+ // Quasis or merged quasi
104
+ let range = part.range;
105
+ let value = part.string;
106
+ if (param.templateStringKind === "cooked") {
107
+ value = JSON.stringify(value);
108
+ value = value.slice(1, value.length - 1);
109
+ }
110
+ if (i === 0) {
111
+ // prefix
112
+ value = prefix;
113
+ range = [param.range[0], part.range[1]];
114
+ value =
115
+ (param.templateStringKind === "cooked" ? "`" : "String.raw`") +
116
+ value;
117
+ } else if (i === param.parts.length - 1) {
118
+ // postfix
119
+ value = postfix;
120
+ range = [part.range[0], param.range[1]];
121
+ value = value + "`";
122
+ } else if (
123
+ part.expression &&
124
+ part.expression.type === "TemplateElement" &&
125
+ part.expression.value.raw === value
126
+ ) {
127
+ // Shortcut when it's a single quasi and doesn't need to be replaced
128
+ return;
129
+ }
130
+ replaces.push({
131
+ range,
132
+ value
133
+ });
134
+ } else {
135
+ // Expression
136
+ if (parser) {
137
+ parser.walkExpression(part.expression);
138
+ }
139
+ }
140
+ });
141
+
99
142
  dep.replaces = replaces;
100
143
  dep.critical =
101
144
  options.wrappedContextCritical &&
@@ -137,13 +180,13 @@ ContextDependencyHelpers.create = (
137
180
  );
138
181
  dep.loc = expr.loc;
139
182
  const replaces = [];
140
- if (prefixRange && prefix !== prefixRaw) {
183
+ if (prefixRange) {
141
184
  replaces.push({
142
185
  range: prefixRange,
143
186
  value: JSON.stringify(prefix)
144
187
  });
145
188
  }
146
- if (postfixRange && postfix !== postfixRaw) {
189
+ if (postfixRange) {
147
190
  replaces.push({
148
191
  range: postfixRange,
149
192
  value: JSON.stringify(postfix)
@@ -153,6 +196,13 @@ ContextDependencyHelpers.create = (
153
196
  dep.critical =
154
197
  options.wrappedContextCritical &&
155
198
  "a part of the request of a dependency is an expression";
199
+
200
+ if (parser && param.wrappedInnerExpressions) {
201
+ for (const part of param.wrappedInnerExpressions) {
202
+ if (part.expression) parser.walkExpression(part.expression);
203
+ }
204
+ }
205
+
156
206
  return dep;
157
207
  } else {
158
208
  const dep = new Dep(
@@ -172,6 +222,11 @@ ContextDependencyHelpers.create = (
172
222
  dep.critical =
173
223
  options.exprContextCritical &&
174
224
  "the request of a dependency is an expression";
225
+
226
+ if (parser) {
227
+ parser.walkExpression(param.expression);
228
+ }
229
+
175
230
  return dep;
176
231
  }
177
232
  };
@@ -248,7 +248,8 @@ class ImportParserPlugin {
248
248
  namespaceObject: parser.state.module.buildMeta.strictHarmonyModule
249
249
  ? "strict"
250
250
  : true
251
- }
251
+ },
252
+ parser
252
253
  );
253
254
  if (!dep) return;
254
255
  dep.loc = expr.loc;
@@ -61,7 +61,8 @@ class RequireResolveDependencyParserPlugin {
61
61
  options,
62
62
  {
63
63
  mode: weak ? "weak" : "sync"
64
- }
64
+ },
65
+ parser
65
66
  );
66
67
  if (!dep) return;
67
68
  dep.loc = expr.loc;
@@ -795,7 +795,7 @@ module.exports = class SplitChunksPlugin {
795
795
  maxSizeQueueMap.get(chunk) || this.options.fallbackCacheGroup;
796
796
  if (!maxSize) continue;
797
797
  const results = deterministicGroupingForModules({
798
- maxSize,
798
+ maxSize: Math.max(minSize, maxSize),
799
799
  minSize,
800
800
  items: chunk.modulesIterable,
801
801
  getKey(module) {
@@ -124,6 +124,13 @@ module.exports = ({ maxSize, minSize, items, getSize, getKey }) => {
124
124
  /** @type {Node<T>[]} */
125
125
  const initialNodes = [];
126
126
 
127
+ // lexically ordering of keys
128
+ nodes.sort((a, b) => {
129
+ if (a.key < b.key) return -1;
130
+ if (a.key > b.key) return 1;
131
+ return 0;
132
+ });
133
+
127
134
  // return nodes bigger than maxSize directly as group
128
135
  for (const node of nodes) {
129
136
  if (node.size >= maxSize) {
@@ -134,13 +141,6 @@ module.exports = ({ maxSize, minSize, items, getSize, getKey }) => {
134
141
  }
135
142
 
136
143
  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
144
  // calculate similarities between lexically adjacent nodes
145
145
  /** @type {number[]} */
146
146
  const similarities = [];
@@ -150,76 +150,99 @@ module.exports = ({ maxSize, minSize, items, getSize, getKey }) => {
150
150
  similarities.push(similarity(a.key, b.key));
151
151
  }
152
152
 
153
- const queue = [new Group(initialNodes, similarities)];
153
+ const initialGroup = new Group(initialNodes, similarities);
154
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;
155
+ if (initialGroup.size < minSize) {
156
+ // We hit an edgecase where the working set is already smaller than minSize
157
+ // We merge it with the smallest result node to keep minSize intact
158
+ if (result.length > 0) {
159
+ const smallestGroup = result.reduce(
160
+ (min, group) => (min.size > group.size ? group : min)
161
+ );
162
+ for (const node of initialGroup.nodes) smallestGroup.nodes.push(node);
163
+ smallestGroup.nodes.sort((a, b) => {
164
+ if (a.key < b.key) return -1;
165
+ if (a.key > b.key) return 1;
166
+ return 0;
167
+ });
168
+ } else {
169
+ // There are no other nodes
170
+ // We use all nodes and have to accept that it's smaller than minSize
171
+ result.push(initialGroup);
161
172
  }
173
+ } else {
174
+ const queue = [initialGroup];
162
175
 
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
- }
176
+ while (queue.length) {
177
+ const group = queue.pop();
178
+ // only groups bigger than maxSize need to be splitted
179
+ if (group.size < maxSize) {
180
+ result.push(group);
181
+ continue;
182
+ }
177
183
 
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;
184
+ // find unsplittable area from left and right
185
+ // going minSize from left and right
186
+ // at least one node need to be included otherwise we get stuck
187
+ let left = 0;
188
+ let leftSize = 0;
189
+ while (leftSize <= minSize) {
190
+ leftSize += group.nodes[left].size;
191
+ left++;
192
+ }
193
+ let right = group.nodes.length - 1;
194
+ let rightSize = 0;
195
+ while (rightSize <= minSize) {
196
+ rightSize += group.nodes[right].size;
197
+ right--;
198
+ }
199
+
200
+ if (left - 1 > right) {
201
+ // can't split group while holding minSize
202
+ // because minSize is preferred of maxSize we return
203
+ // the group here even while it's too big
204
+ // To avoid this make sure maxSize > minSize * 3
205
+ result.push(group);
206
+ continue;
207
+ }
208
+ if (left <= right) {
209
+ // when there is a area between left and right
210
+ // we look for best split point
211
+ // we split at the minimum similarity
212
+ // here key space is separated the most
213
+ let best = left - 1;
214
+ let bestSimilarity = group.similarities[best];
215
+ for (let i = left; i <= right; i++) {
216
+ const similarity = group.similarities[i];
217
+ if (similarity < bestSimilarity) {
218
+ best = i;
219
+ bestSimilarity = similarity;
220
+ }
198
221
  }
222
+ left = best + 1;
223
+ right = best;
199
224
  }
200
- left = best + 1;
201
- right = best;
202
- }
203
225
 
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]);
226
+ // create two new groups for left and right area
227
+ // and queue them up
228
+ const rightNodes = [group.nodes[right + 1]];
229
+ /** @type {number[]} */
230
+ const rightSimilaries = [];
231
+ for (let i = right + 2; i < group.nodes.length; i++) {
232
+ rightSimilaries.push(group.similarities[i - 1]);
233
+ rightNodes.push(group.nodes[i]);
234
+ }
235
+ queue.push(new Group(rightNodes, rightSimilaries));
236
+
237
+ const leftNodes = [group.nodes[0]];
238
+ /** @type {number[]} */
239
+ const leftSimilaries = [];
240
+ for (let i = 1; i < left; i++) {
241
+ leftSimilaries.push(group.similarities[i - 1]);
242
+ leftNodes.push(group.nodes[i]);
243
+ }
244
+ queue.push(new Group(leftNodes, leftSimilaries));
221
245
  }
222
- queue.push(new Group(leftNodes, leftSimilaries));
223
246
  }
224
247
  }
225
248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack",
3
- "version": "4.24.0",
3
+ "version": "4.26.1",
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",
@@ -26,7 +26,7 @@
26
26
  "node-libs-browser": "^2.0.0",
27
27
  "schema-utils": "^0.4.4",
28
28
  "tapable": "^1.1.0",
29
- "uglifyjs-webpack-plugin": "^1.2.4",
29
+ "terser-webpack-plugin": "^1.1.0",
30
30
  "watchpack": "^1.5.0",
31
31
  "webpack-sources": "^1.3.0"
32
32
  },
@@ -56,6 +56,7 @@
56
56
  "jade": "^1.11.0",
57
57
  "jade-loader": "~0.8.0",
58
58
  "jest": "24.0.0-alpha.1",
59
+ "jest-junit": "^5.2.0",
59
60
  "json-loader": "^0.5.7",
60
61
  "json-schema-to-typescript": "^6.0.1",
61
62
  "less": "^2.5.1",