@depup/webpack 5.109.0-depup.0 → 5.109.2-depup.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 (62) hide show
  1. package/README.md +4 -4
  2. package/bin/webpack.js +12 -0
  3. package/changes.json +4 -4
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/CleanPlugin.js +11 -9
  6. package/lib/CompatibilityPlugin.js +31 -0
  7. package/lib/Compilation.js +21 -5
  8. package/lib/Compiler.js +16 -4
  9. package/lib/DefinePlugin.js +47 -20
  10. package/lib/ExportsInfo.js +22 -5
  11. package/lib/ExternalModule.js +10 -7
  12. package/lib/FileSystemInfo.js +126 -26
  13. package/lib/FlagDependencyUsagePlugin.js +33 -12
  14. package/lib/InitFragment.js +20 -32
  15. package/lib/ModuleFilenameHelpers.js +13 -2
  16. package/lib/NormalModule.js +80 -71
  17. package/lib/NormalModuleFactory.js +8 -2
  18. package/lib/RuntimePlugin.js +40 -113
  19. package/lib/Template.js +23 -3
  20. package/lib/cache/PackFileCacheStrategy.js +259 -5
  21. package/lib/config/defaults.js +21 -2
  22. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  23. package/lib/container/ModuleFederationPlugin.js +15 -9
  24. package/lib/css/CssGenerator.js +8 -2
  25. package/lib/css/CssLoadingRuntimeModule.js +25 -12
  26. package/lib/css/CssModulesPlugin.js +29 -18
  27. package/lib/css/CssParser.js +135 -50
  28. package/lib/css/syntax.js +862 -765
  29. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  30. package/lib/dependencies/CommonJsFullRequireDependency.js +33 -4
  31. package/lib/dependencies/CommonJsImportsParserPlugin.js +2 -0
  32. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  33. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  34. package/lib/dependencies/HarmonyImportDependency.js +1 -1
  35. package/lib/dependencies/HarmonyImportGuard.js +2 -1
  36. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  37. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +15 -9
  38. package/lib/hmr/lazyCompilationBackend.js +19 -4
  39. package/lib/html/HtmlGenerator.js +23 -3
  40. package/lib/html/HtmlModulesPlugin.js +30 -12
  41. package/lib/html/syntax.js +2485 -2399
  42. package/lib/ids/IdHelpers.js +24 -10
  43. package/lib/javascript/JavascriptModulesPlugin.js +83 -49
  44. package/lib/javascript/JavascriptParser.js +49 -13
  45. package/lib/javascript/syntax.js +700 -43
  46. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +10 -2
  47. package/lib/node/ReadFileCompileWasmPlugin.js +10 -2
  48. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  49. package/lib/optimize/ConcatenatedModule.js +19 -15
  50. package/lib/optimize/ModuleConcatenationPlugin.js +73 -24
  51. package/lib/optimize/RealContentHashPlugin.js +10 -7
  52. package/lib/optimize/SplitChunksPlugin.js +146 -44
  53. package/lib/prefetch/ResourceHintPlugin.js +96 -72
  54. package/lib/serialization/BinaryMiddleware.js +270 -938
  55. package/lib/serialization/FileMiddleware.js +299 -60
  56. package/lib/stats/DefaultStatsFactoryPlugin.js +204 -198
  57. package/lib/stats/DefaultStatsPrinterPlugin.js +24 -166
  58. package/lib/stats/StatsFactory.js +31 -9
  59. package/lib/util/identifier.js +21 -1
  60. package/package.json +10 -10
  61. package/schemas/WebpackOptions.json +110 -55
  62. package/types.d.ts +832 -247
@@ -7,6 +7,8 @@
7
7
  const { constants } = require("buffer");
8
8
  const { pipeline } = require("stream");
9
9
  const {
10
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
11
+ brotliDecompress,
10
12
  constants: zConstants,
11
13
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
12
14
  createBrotliCompress,
@@ -18,7 +20,10 @@ const {
18
20
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
19
21
  createZstdCompress,
20
22
  // eslint-disable-next-line n/no-unsupported-features/node-builtins
21
- createZstdDecompress
23
+ createZstdDecompress,
24
+ gunzip,
25
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
26
+ zstdDecompress
22
27
  } = require("zlib");
23
28
  const { DEFAULTS } = require("../config/defaults");
24
29
  const createHash = require("../util/createHash");
@@ -51,6 +56,8 @@ Section -> Buffer
51
56
  const VERSION = 0x01637077;
52
57
  const WRITE_LIMIT_TOTAL = 0x7fff0000;
53
58
  const WRITE_LIMIT_CHUNK = 511 * 1024 * 1024;
59
+ // headers and pointer sections are tiny; anything above this is a corrupt file
60
+ const MAX_HEADER_OR_POINTER_SIZE = 256 * 1024 * 1024;
54
61
 
55
62
  /**
56
63
  * Returns hash.
@@ -111,6 +118,7 @@ const readUInt64LE = Buffer.prototype.readBigUInt64LE
111
118
  * @param {string | boolean} name file base name
112
119
  * @param {(name: string | false, buffers: Buffer[], size: number) => Promise<void>} writeFile writes a file
113
120
  * @param {HashFunction=} hashFunction hash function to use
121
+ * @param {Set<string>=} retainedNames collects names of files that stay referenced without being rewritten
114
122
  * @returns {Promise<SerializeResult>} resulting file pointer and promise
115
123
  */
116
124
  const serialize = async (
@@ -118,7 +126,8 @@ const serialize = async (
118
126
  data,
119
127
  name,
120
128
  writeFile,
121
- hashFunction = DEFAULTS.HASH_FUNCTION
129
+ hashFunction = DEFAULTS.HASH_FUNCTION,
130
+ retainedNames = undefined
122
131
  ) => {
123
132
  /** @type {(Buffer[] | Buffer | Promise<SerializeResult>)[]} */
124
133
  const processedData = [];
@@ -144,6 +153,10 @@ const serialize = async (
144
153
  "Unexpected lazy value with non-this target (can't pass through lazy values)"
145
154
  );
146
155
  } else {
156
+ if (retainedNames !== undefined) {
157
+ // pointer buffer layout: u64 size + utf-8 file name
158
+ retainedNames.add(serializedInfo.toString("utf8", 8));
159
+ }
147
160
  processedData.push(serializedInfo);
148
161
  }
149
162
  } else {
@@ -157,7 +170,8 @@ const serialize = async (
157
170
  (content),
158
171
  (options && options.name) || true,
159
172
  writeFile,
160
- hashFunction
173
+ hashFunction,
174
+ retainedNames
161
175
  ).then((result) => {
162
176
  /** @type {LazyOptions} */
163
177
  (item.options).size = result.size;
@@ -444,7 +458,14 @@ const deserialize = async (middleware, name, readFile) => {
444
458
 
445
459
  /** @typedef {BufferSerializableType[]} DeserializedType */
446
460
  /** @typedef {true} SerializedType */
447
- /** @typedef {{ filename: string, extension?: string }} Context */
461
+ /**
462
+ * `writtenFiles`/`retainedFiles` collect file names (without extension) during
463
+ * `serialize`: files written in this run and files that stay referenced by
464
+ * lazy pointers without being rewritten. Retained files may reference further
465
+ * files on disk not listed here (nested lazy segments); use
466
+ * `getReferencedFilenames` to walk them.
467
+ * @typedef {{ filename: string, extension?: string, writtenFiles?: Set<string>, retainedFiles?: Set<string> }} Context
468
+ */
448
469
 
449
470
  /**
450
471
  * Represents FileMiddleware.
@@ -471,7 +492,7 @@ class FileMiddleware extends SerializerMiddleware {
471
492
  * @returns {SerializedType | Promise<SerializedType> | null} serialized data
472
493
  */
473
494
  serialize(data, context) {
474
- const { filename, extension = "" } = context;
495
+ const { filename, extension = "", writtenFiles, retainedFiles } = context;
475
496
  return new Promise((resolve, reject) => {
476
497
  mkdirp(this.fs, dirname(this.fs, filename), (err) => {
477
498
  if (err) return reject(err);
@@ -581,66 +602,74 @@ class FileMiddleware extends SerializerMiddleware {
581
602
  batchWrite();
582
603
  }
583
604
  );
584
- if (name) allWrittenFiles.add(file);
605
+ if (name) {
606
+ allWrittenFiles.add(file);
607
+ if (writtenFiles !== undefined) writtenFiles.add(name);
608
+ }
585
609
  };
586
610
 
587
611
  resolve(
588
- serialize(this, data, false, writeFile, this._hashFunction).then(
589
- async ({ backgroundJob }) => {
590
- await backgroundJob;
591
-
592
- // Rename the index file to disallow access during inconsistent file state
593
- await new Promise(
594
- /**
595
- * Handles the callback logic for this hook.
596
- * @param {(value?: undefined) => void} resolve resolve
597
- */
598
- (resolve) => {
599
- this.fs.rename(filename, `${filename}.old`, (_err) => {
600
- resolve();
601
- });
602
- }
603
- );
612
+ serialize(
613
+ this,
614
+ data,
615
+ false,
616
+ writeFile,
617
+ this._hashFunction,
618
+ retainedFiles
619
+ ).then(async ({ backgroundJob }) => {
620
+ await backgroundJob;
604
621
 
605
- // update all written files
606
- await Promise.all(
607
- Array.from(
608
- allWrittenFiles,
609
- (file) =>
610
- new Promise(
611
- /**
612
- * Handles the callback logic for this hook.
613
- * @param {(value?: undefined) => void} resolve resolve
614
- * @param {(reason?: Error | null) => void} reject reject
615
- * @returns {void}
616
- */
617
- (resolve, reject) => {
618
- this.fs.rename(`${file}_`, file, (err) => {
619
- if (err) return reject(err);
620
- resolve();
621
- });
622
- }
623
- )
624
- )
625
- );
622
+ // Rename the index file to disallow access during inconsistent file state
623
+ await new Promise(
624
+ /**
625
+ * Handles the callback logic for this hook.
626
+ * @param {(value?: undefined) => void} resolve resolve
627
+ */
628
+ (resolve) => {
629
+ this.fs.rename(filename, `${filename}.old`, (_err) => {
630
+ resolve();
631
+ });
632
+ }
633
+ );
626
634
 
627
- // As final step automatically update the index file to have a consistent pack again
628
- await new Promise(
629
- /**
630
- * Handles the callback logic for this hook.
631
- * @param {(value?: undefined) => void} resolve resolve
632
- * @returns {void}
633
- */
634
- (resolve) => {
635
- this.fs.rename(`${filename}_`, filename, (err) => {
636
- if (err) return reject(err);
637
- resolve();
638
- });
639
- }
640
- );
641
- return /** @type {true} */ (true);
642
- }
643
- )
635
+ // update all written files
636
+ await Promise.all(
637
+ Array.from(
638
+ allWrittenFiles,
639
+ (file) =>
640
+ new Promise(
641
+ /**
642
+ * Handles the callback logic for this hook.
643
+ * @param {(value?: undefined) => void} resolve resolve
644
+ * @param {(reason?: Error | null) => void} reject reject
645
+ * @returns {void}
646
+ */
647
+ (resolve, reject) => {
648
+ this.fs.rename(`${file}_`, file, (err) => {
649
+ if (err) return reject(err);
650
+ resolve();
651
+ });
652
+ }
653
+ )
654
+ )
655
+ );
656
+
657
+ // As final step automatically update the index file to have a consistent pack again
658
+ await new Promise(
659
+ /**
660
+ * Handles the callback logic for this hook.
661
+ * @param {(value?: undefined) => void} resolve resolve
662
+ * @returns {void}
663
+ */
664
+ (resolve) => {
665
+ this.fs.rename(`${filename}_`, filename, (err) => {
666
+ if (err) return reject(err);
667
+ resolve();
668
+ });
669
+ }
670
+ );
671
+ return /** @type {true} */ (true);
672
+ })
644
673
  );
645
674
  });
646
675
  });
@@ -808,6 +837,216 @@ class FileMiddleware extends SerializerMiddleware {
808
837
  }
809
838
  }
810
839
 
840
+ /**
841
+ * Extracts the file names referenced by lazy pointer sections from serialized content.
842
+ * @param {Buffer} buf decompressed file content
843
+ * @returns {string[]} referenced file names (without extension)
844
+ */
845
+ const parsePointerNames = (buf) => {
846
+ const version = buf.readUInt32LE(0);
847
+ if (version !== VERSION) {
848
+ throw new Error(`Invalid file version ${version}`);
849
+ }
850
+ const sectionCount = buf.readUInt32LE(4);
851
+ let offset = 8 + sectionCount * 4;
852
+ // a corrupt section table must abort the walk, never silently drop a name
853
+ if (offset > buf.length) {
854
+ throw new Error(`Invalid section count ${sectionCount}`);
855
+ }
856
+ /** @type {string[]} */
857
+ const names = [];
858
+ for (let i = 0; i < sectionCount; i++) {
859
+ const length = buf.readInt32LE(8 + i * 4);
860
+ if (length < 0) {
861
+ // pointer section: u64 size + utf-8 file name
862
+ const end = offset - length;
863
+ if (end > buf.length) {
864
+ throw new Error("Truncated pointer section");
865
+ }
866
+ names.push(buf.toString("utf8", offset + 8, end));
867
+ offset = end;
868
+ } else {
869
+ offset += length;
870
+ }
871
+ }
872
+ if (offset !== buf.length) {
873
+ throw new Error("Section table does not match file size");
874
+ }
875
+ return names;
876
+ };
877
+
878
+ /**
879
+ * Reads the pointer names of a compressed file by decompressing it fully
880
+ * (compressed content cannot be read by byte range).
881
+ * @param {IntermediateFileSystem} fs a file system
882
+ * @param {string} file absolute path of the serialized file
883
+ * @returns {Promise<string[]>} referenced file names (without extension)
884
+ */
885
+ const getReferencedFilenamesCompressed = (fs, file) =>
886
+ new Promise((resolve, reject) => {
887
+ fs.readFile(file, (err, rawContent) => {
888
+ if (err) return reject(err);
889
+ /**
890
+ * Parses the decompressed content.
891
+ * @param {Error | null} err error
892
+ * @param {Buffer=} content decompressed content
893
+ * @returns {void}
894
+ */
895
+ const onContent = (err, content) => {
896
+ if (err) return reject(err);
897
+ try {
898
+ resolve(parsePointerNames(/** @type {Buffer} */ (content)));
899
+ } catch (err_) {
900
+ reject(/** @type {Error} */ (err_));
901
+ }
902
+ };
903
+ const buf = /** @type {Buffer} */ (rawContent);
904
+ if (file.endsWith(".gz")) {
905
+ gunzip(buf, onContent);
906
+ } else if (file.endsWith(".br")) {
907
+ brotliDecompress(buf, onContent);
908
+ } else {
909
+ if (!zstdDecompress) {
910
+ return reject(
911
+ new Error("zstd decompression requires Node.js >= 22.15.0")
912
+ );
913
+ }
914
+ zstdDecompress(buf, onContent);
915
+ }
916
+ });
917
+ });
918
+
919
+ /**
920
+ * Reads the pointer names of an uncompressed file by reading only the header
921
+ * and the pointer sections instead of the whole file.
922
+ * @param {IntermediateFileSystem} fs a file system
923
+ * @param {string} file absolute path of the serialized file
924
+ * @returns {Promise<string[]>} referenced file names (without extension)
925
+ */
926
+ const getReferencedFilenamesUncompressed = (fs, file) =>
927
+ new Promise((resolve, reject) => {
928
+ fs.open(file, "r", (err, _fd) => {
929
+ if (err) return reject(err);
930
+ const fd = /** @type {number} */ (_fd);
931
+ /**
932
+ * Closes the file descriptor and rejects.
933
+ * @param {Error} err error
934
+ */
935
+ const fail = (err) => {
936
+ fs.close(fd, () => reject(err));
937
+ };
938
+ /**
939
+ * Reads exactly `length` bytes at `position`.
940
+ * @param {number} position file position
941
+ * @param {number} length byte count
942
+ * @param {(buffer: Buffer) => void} callback called with the filled buffer
943
+ * @returns {void}
944
+ */
945
+ const readAt = (position, length, callback) => {
946
+ // corrupt headers can request absurd sizes; must reject, not crash
947
+ if (length > MAX_HEADER_OR_POINTER_SIZE) {
948
+ return fail(new Error(`Invalid section size ${length} in ${file}`));
949
+ }
950
+ /** @type {Buffer} */
951
+ let buffer;
952
+ try {
953
+ buffer = Buffer.allocUnsafe(length);
954
+ } catch (err) {
955
+ return fail(/** @type {Error} */ (err));
956
+ }
957
+ let bytesDone = 0;
958
+ const readMore = () => {
959
+ fs.read(
960
+ fd,
961
+ buffer,
962
+ bytesDone,
963
+ length - bytesDone,
964
+ position + bytesDone,
965
+ (err, bytesRead) => {
966
+ if (err) return fail(err);
967
+ if (bytesRead === 0) {
968
+ return fail(new Error(`Unexpected end of file ${file}`));
969
+ }
970
+ bytesDone += bytesRead;
971
+ if (bytesDone < length) return readMore();
972
+ callback(buffer);
973
+ }
974
+ );
975
+ };
976
+ readMore();
977
+ };
978
+ readAt(0, 8, (header) => {
979
+ const version = header.readUInt32LE(0);
980
+ if (version !== VERSION) {
981
+ return fail(new Error(`Invalid file version ${version}`));
982
+ }
983
+ const sectionCount = header.readUInt32LE(4);
984
+ if (sectionCount === 0) {
985
+ return fs.close(fd, (err) => (err ? reject(err) : resolve([])));
986
+ }
987
+ readAt(8, sectionCount * 4, (lengthsBuffer) => {
988
+ /** @type {{ position: number, length: number }[]} */
989
+ const pointerSections = [];
990
+ let position = 8 + sectionCount * 4;
991
+ for (let i = 0; i < sectionCount; i++) {
992
+ const length = lengthsBuffer.readInt32LE(i * 4);
993
+ if (length < 0) {
994
+ pointerSections.push({ position, length: -length });
995
+ position -= length;
996
+ } else {
997
+ position += length;
998
+ }
999
+ }
1000
+ // the section table must account for exactly the whole file,
1001
+ // otherwise pointer reads land on wrong bytes and under-report
1002
+ fs.stat(file, (err, stats) => {
1003
+ if (err) return fail(err);
1004
+ const stat = /** @type {import("../util/fs").IStats} */ (stats);
1005
+ if (stat.size !== position) {
1006
+ return fail(
1007
+ new Error(`Section table does not match size of ${file}`)
1008
+ );
1009
+ }
1010
+ /** @type {string[]} */
1011
+ const names = [];
1012
+ /**
1013
+ * Reads the pointer section at `index`, then the next one.
1014
+ * @param {number} index pointer section index
1015
+ * @returns {void}
1016
+ */
1017
+ const readNext = (index) => {
1018
+ if (index >= pointerSections.length) {
1019
+ return fs.close(fd, (err) =>
1020
+ err ? reject(err) : resolve(names)
1021
+ );
1022
+ }
1023
+ const section = pointerSections[index];
1024
+ readAt(section.position, section.length, (buffer) => {
1025
+ // pointer section: u64 size + utf-8 file name
1026
+ names.push(buffer.toString("utf8", 8));
1027
+ readNext(index + 1);
1028
+ });
1029
+ };
1030
+ readNext(0);
1031
+ });
1032
+ });
1033
+ });
1034
+ });
1035
+ });
1036
+
1037
+ /**
1038
+ * Reads the file names referenced by a serialized file (its lazy pointer sections)
1039
+ * without deserializing its content. Referenced files may reference further files.
1040
+ * @param {IntermediateFileSystem} fs a file system
1041
+ * @param {string} file absolute path of the serialized file
1042
+ * @returns {Promise<string[]>} referenced file names (without extension)
1043
+ */
1044
+ const getReferencedFilenames = (fs, file) =>
1045
+ file.endsWith(".gz") || file.endsWith(".br") || file.endsWith(".zst")
1046
+ ? getReferencedFilenamesCompressed(fs, file)
1047
+ : getReferencedFilenamesUncompressed(fs, file);
1048
+
811
1049
  module.exports = FileMiddleware;
812
1050
  // Exposed for testing the content-buffer boundary handling in `deserialize`.
813
1051
  module.exports._deserialize = deserialize;
1052
+ module.exports.getReferencedFilenames = getReferencedFilenames;