webpack 4.28.1 → 4.29.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.
@@ -1051,6 +1051,10 @@ export interface OutputOptions {
1051
1051
  * Specifies the name of each output file on disk. You must **not** specify an absolute path here! The `output.path` option determines the location on disk the files are written to, filename is used solely for naming the individual files.
1052
1052
  */
1053
1053
  filename?: string | Function;
1054
+ /**
1055
+ * Use the future version of asset emitting logic, which is allows freeing memory of assets after emitting. It could break plugins which assume that assets are still readable after emitting. Will be the new default in the next major version.
1056
+ */
1057
+ futureEmitAssets?: boolean;
1054
1058
  /**
1055
1059
  * An expression which is used to address the global object/scope in runtime code
1056
1060
  */
@@ -491,6 +491,8 @@ class Compilation extends Tapable {
491
491
  this._buildingModules = new Map();
492
492
  /** @private @type {Map<Module, Callback[]>} */
493
493
  this._rebuildingModules = new Map();
494
+ /** @type {Set<string>} */
495
+ this.emittedAssets = new Set();
494
496
  }
495
497
 
496
498
  getStats() {
package/lib/Compiler.js CHANGED
@@ -7,6 +7,7 @@
7
7
  const parseJson = require("json-parse-better-errors");
8
8
  const asyncLib = require("neo-async");
9
9
  const path = require("path");
10
+ const { Source } = require("webpack-sources");
10
11
  const util = require("util");
11
12
  const {
12
13
  Tapable,
@@ -188,6 +189,11 @@ class Compiler extends Tapable {
188
189
 
189
190
  /** @type {boolean} */
190
191
  this.watchMode = false;
192
+
193
+ /** @private @type {WeakMap<Source, { sizeOnlySource: SizeOnlySource, writtenTo: Map<string, number> }>} */
194
+ this._assetEmittingSourceCache = new WeakMap();
195
+ /** @private @type {Map<string, number>} */
196
+ this._assetEmittingWrittenFiles = new Map();
191
197
  }
192
198
 
193
199
  watch(watchOptions, handler) {
@@ -207,6 +213,10 @@ class Compiler extends Tapable {
207
213
  const finalCallback = (err, stats) => {
208
214
  this.running = false;
209
215
 
216
+ if (err) {
217
+ this.hooks.failed.call(err);
218
+ }
219
+
210
220
  if (callback !== undefined) return callback(err, stats);
211
221
  };
212
222
 
@@ -308,8 +318,9 @@ class Compiler extends Tapable {
308
318
  const emitFiles = err => {
309
319
  if (err) return callback(err);
310
320
 
311
- asyncLib.forEach(
321
+ asyncLib.forEachLimit(
312
322
  compilation.assets,
323
+ 15,
313
324
  (source, file, callback) => {
314
325
  let targetFile = file;
315
326
  const queryStringIdx = targetFile.indexOf("?");
@@ -323,19 +334,86 @@ class Compiler extends Tapable {
323
334
  outputPath,
324
335
  targetFile
325
336
  );
326
- if (source.existsAt === targetPath) {
327
- source.emitted = false;
328
- return callback();
337
+ // TODO webpack 5 remove futureEmitAssets option and make it on by default
338
+ if (this.options.output.futureEmitAssets) {
339
+ // check if the target file has already been written by this Compiler
340
+ const targetFileGeneration = this._assetEmittingWrittenFiles.get(
341
+ targetPath
342
+ );
343
+
344
+ // create an cache entry for this Source if not already existing
345
+ let cacheEntry = this._assetEmittingSourceCache.get(source);
346
+ if (cacheEntry === undefined) {
347
+ cacheEntry = {
348
+ sizeOnlySource: undefined,
349
+ writtenTo: new Map()
350
+ };
351
+ this._assetEmittingSourceCache.set(source, cacheEntry);
352
+ }
353
+
354
+ // if the target file has already been written
355
+ if (targetFileGeneration !== undefined) {
356
+ // check if the Source has been written to this target file
357
+ const writtenGeneration = cacheEntry.writtenTo.get(targetPath);
358
+ if (writtenGeneration === targetFileGeneration) {
359
+ // if yes, we skip writing the file
360
+ // as it's already there
361
+ // (we assume one doesn't remove files while the Compiler is running)
362
+ return callback();
363
+ }
364
+ }
365
+
366
+ // get the binary (Buffer) content from the Source
367
+ /** @type {Buffer} */
368
+ let content;
369
+ if (typeof source.buffer === "function") {
370
+ content = source.buffer();
371
+ } else {
372
+ const bufferOrString = source.source();
373
+ if (Buffer.isBuffer(bufferOrString)) {
374
+ content = bufferOrString;
375
+ } else {
376
+ content = Buffer.from(bufferOrString, "utf8");
377
+ }
378
+ }
379
+
380
+ // Create a replacement resource which only allows to ask for size
381
+ // This allows to GC all memory allocated by the Source
382
+ // (expect when the Source is stored in any other cache)
383
+ cacheEntry.sizeOnlySource = new SizeOnlySource(content.length);
384
+ compilation.assets[file] = cacheEntry.sizeOnlySource;
385
+
386
+ // Write the file to output file system
387
+ this.outputFileSystem.writeFile(targetPath, content, err => {
388
+ if (err) return callback(err);
389
+
390
+ // information marker that the asset has been emitted
391
+ compilation.emittedAssets.add(file);
392
+
393
+ // cache the information that the Source has been written to that location
394
+ const newGeneration =
395
+ targetFileGeneration === undefined
396
+ ? 1
397
+ : targetFileGeneration + 1;
398
+ cacheEntry.writtenTo.set(targetPath, newGeneration);
399
+ this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
400
+ callback();
401
+ });
402
+ } else {
403
+ if (source.existsAt === targetPath) {
404
+ source.emitted = false;
405
+ return callback();
406
+ }
407
+ let content = source.source();
408
+
409
+ if (!Buffer.isBuffer(content)) {
410
+ content = Buffer.from(content, "utf8");
411
+ }
412
+
413
+ source.existsAt = targetPath;
414
+ source.emitted = true;
415
+ this.outputFileSystem.writeFile(targetPath, content, callback);
329
416
  }
330
- let content = source.source();
331
-
332
- if (!Buffer.isBuffer(content)) {
333
- content = Buffer.from(content, "utf8");
334
- }
335
-
336
- source.existsAt = targetPath;
337
- source.emitted = true;
338
- this.outputFileSystem.writeFile(targetPath, content, callback);
339
417
  };
340
418
 
341
419
  if (targetFile.match(/\/|\\/)) {
@@ -558,3 +636,48 @@ class Compiler extends Tapable {
558
636
  }
559
637
 
560
638
  module.exports = Compiler;
639
+
640
+ class SizeOnlySource extends Source {
641
+ constructor(size) {
642
+ super();
643
+ this._size = size;
644
+ }
645
+
646
+ _error() {
647
+ return new Error(
648
+ "Content and Map of this Source is no longer available (only size() is supported)"
649
+ );
650
+ }
651
+
652
+ size() {
653
+ return this._size;
654
+ }
655
+
656
+ /**
657
+ * @param {any} options options
658
+ * @returns {string} the source
659
+ */
660
+ source(options) {
661
+ throw this._error();
662
+ }
663
+
664
+ node() {
665
+ throw this._error();
666
+ }
667
+
668
+ listMap() {
669
+ throw this._error();
670
+ }
671
+
672
+ map() {
673
+ throw this._error();
674
+ }
675
+
676
+ listNode() {
677
+ throw this._error();
678
+ }
679
+
680
+ updateHash() {
681
+ throw this._error();
682
+ }
683
+ }
@@ -38,6 +38,8 @@ class IgnorePlugin {
38
38
  * @returns {TODO|null} returns result or null if result should be ignored
39
39
  */
40
40
  checkIgnore(result) {
41
+ if (!result) return result;
42
+
41
43
  if (
42
44
  "checkResource" in this.options &&
43
45
  this.options.checkResource &&
package/lib/Parser.js CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  // Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
8
8
 
9
- const acorn = require("acorn-dynamic-import").default;
9
+ const acorn = require("acorn");
10
+ const acornDynamicImport = require("acorn-dynamic-import").default;
10
11
  const { Tapable, SyncBailHook, HookMap } = require("tapable");
11
12
  const util = require("util");
12
13
  const vm = require("vm");
@@ -14,6 +15,8 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
14
15
  const StackedSetMap = require("./util/StackedSetMap");
15
16
  const TrackingSet = require("./util/TrackingSet");
16
17
 
18
+ const acornParser = acorn.Parser.extend(acornDynamicImport);
19
+
17
20
  const joinRanges = (startRange, endRange) => {
18
21
  if (!endRange) return startRange;
19
22
  if (!startRange) return endRange;
@@ -25,10 +28,7 @@ const defaultParserOptions = {
25
28
  locations: true,
26
29
  ecmaVersion: 2019,
27
30
  sourceType: "module",
28
- onComment: null,
29
- plugins: {
30
- dynamicImport: true
31
- }
31
+ onComment: null
32
32
  };
33
33
 
34
34
  // regexp to match at lease one "magic comment"
@@ -2133,9 +2133,13 @@ class Parser extends Tapable {
2133
2133
  sourceType: this.sourceType,
2134
2134
  locations: false
2135
2135
  });
2136
+ // TODO(https://github.com/acornjs/acorn/issues/741)
2137
+ // @ts-ignore
2136
2138
  if (ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") {
2137
2139
  throw new Error("evaluate: Source is not a expression");
2138
2140
  }
2141
+ // TODO(https://github.com/acornjs/acorn/issues/741)
2142
+ // @ts-ignore
2139
2143
  return this.evaluateExpression(ast.body[0].expression);
2140
2144
  }
2141
2145
 
@@ -2226,7 +2230,7 @@ class Parser extends Tapable {
2226
2230
  let error;
2227
2231
  let threw = false;
2228
2232
  try {
2229
- ast = acorn.parse(code, parserOptions);
2233
+ ast = acornParser.parse(code, parserOptions);
2230
2234
  } catch (e) {
2231
2235
  error = e;
2232
2236
  threw = true;
@@ -2238,7 +2242,7 @@ class Parser extends Tapable {
2238
2242
  parserOptions.onComment.length = 0;
2239
2243
  }
2240
2244
  try {
2241
- ast = acorn.parse(code, parserOptions);
2245
+ ast = acornParser.parse(code, parserOptions);
2242
2246
  threw = false;
2243
2247
  } catch (e) {
2244
2248
  threw = true;
package/lib/Stats.js CHANGED
@@ -400,7 +400,10 @@ class Stats {
400
400
  size: compilation.assets[asset].size(),
401
401
  chunks: [],
402
402
  chunkNames: [],
403
- emitted: compilation.assets[asset].emitted
403
+ // TODO webpack 5: remove .emitted
404
+ emitted:
405
+ compilation.assets[asset].emitted ||
406
+ compilation.emittedAssets.has(asset)
404
407
  };
405
408
 
406
409
  if (showPerformance) {
@@ -463,7 +463,7 @@ class WebpackOptionsApply extends OptionsApply {
463
463
  if (options.optimization.minimize) {
464
464
  for (const minimizer of options.optimization.minimizer) {
465
465
  if (typeof minimizer === "function") {
466
- minimizer.apply(compiler);
466
+ minimizer.call(compiler, compiler);
467
467
  } else {
468
468
  minimizer.apply(compiler);
469
469
  }
@@ -1,4 +1,6 @@
1
1
  const fs = require("fs");
2
+ const path = require("path");
3
+ const mkdirp = require("mkdirp");
2
4
  const { Tracer } = require("chrome-trace-event");
3
5
  const validateOptions = require("schema-utils");
4
6
  const schema = require("../../schemas/plugins/debug/ProfilingPlugin.json");
@@ -93,6 +95,10 @@ const createTrace = outputPath => {
93
95
  noStream: true
94
96
  });
95
97
  const profiler = new Profiler(inspector);
98
+ if (/\/|\\/.test(outputPath)) {
99
+ const dirPath = path.dirname(outputPath);
100
+ mkdirp.sync(dirPath);
101
+ }
96
102
  const fsStream = fs.createWriteStream(outputPath);
97
103
 
98
104
  let counter = 0;
@@ -36,8 +36,12 @@ module.exports = class AMDRequireDependenciesBlock extends AsyncDependenciesBloc
36
36
  } else {
37
37
  this.range = expr.range;
38
38
  }
39
- const dep = new AMDRequireDependency(this);
39
+ const dep = this.newRequireDependency();
40
40
  dep.loc = loc;
41
41
  this.addDependency(dep);
42
42
  }
43
+
44
+ newRequireDependency() {
45
+ return new AMDRequireDependency(this);
46
+ }
43
47
  };
@@ -13,9 +13,13 @@ module.exports = class HarmonyDetectionParserPlugin {
13
13
  const isStrictHarmony = parser.state.module.type === "javascript/esm";
14
14
  const isHarmony =
15
15
  isStrictHarmony ||
16
- ast.body.some(statement => {
17
- return /^(Import|Export).*Declaration$/.test(statement.type);
18
- });
16
+ ast.body.some(
17
+ statement =>
18
+ statement.type === "ImportDeclaration" ||
19
+ statement.type === "ExportDefaultDeclaration" ||
20
+ statement.type === "ExportNamedDeclaration" ||
21
+ statement.type === "ExportAllDeclaration"
22
+ );
19
23
  if (isHarmony) {
20
24
  const module = parser.state.module;
21
25
  const compatDep = new HarmonyCompatibilityDependency(module);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpack",
3
- "version": "4.28.1",
3
+ "version": "4.29.0",
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",
@@ -9,8 +9,8 @@
9
9
  "@webassemblyjs/helper-module-context": "1.7.11",
10
10
  "@webassemblyjs/wasm-edit": "1.7.11",
11
11
  "@webassemblyjs/wasm-parser": "1.7.11",
12
- "acorn": "^5.6.2",
13
- "acorn-dynamic-import": "^3.0.0",
12
+ "acorn": "^6.0.5",
13
+ "acorn-dynamic-import": "^4.0.0",
14
14
  "ajv": "^6.1.0",
15
15
  "ajv-keywords": "^3.1.0",
16
16
  "chrome-trace-event": "^1.0.0",
@@ -867,6 +867,10 @@
867
867
  }
868
868
  ]
869
869
  },
870
+ "futureEmitAssets": {
871
+ "description": "Use the future version of asset emitting logic, which is allows freeing memory of assets after emitting. It could break plugins which assume that assets are still readable after emitting. Will be the new default in the next major version.",
872
+ "type": "boolean"
873
+ },
870
874
  "globalObject": {
871
875
  "description": "An expression which is used to address the global object/scope in runtime code",
872
876
  "type": "string",