@atlaspack/core 2.16.2-dev.14 → 2.16.2-dev.1c70d50f9.99

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 (75) hide show
  1. package/CHANGELOG.md +196 -0
  2. package/lib/AssetGraph.js +27 -7
  3. package/lib/Atlaspack.js +10 -2
  4. package/lib/BundleGraph.js +6 -105
  5. package/lib/Dependency.js +6 -2
  6. package/lib/Environment.js +5 -3
  7. package/lib/EnvironmentManager.js +137 -0
  8. package/lib/InternalConfig.js +3 -2
  9. package/lib/PackagerRunner.js +52 -15
  10. package/lib/RequestTracker.js +313 -94
  11. package/lib/UncommittedAsset.js +20 -2
  12. package/lib/applyRuntimes.js +2 -1
  13. package/lib/assetUtils.js +2 -1
  14. package/lib/atlaspack-v3/worker/worker.js +8 -0
  15. package/lib/index.js +29 -1
  16. package/lib/public/Asset.js +3 -2
  17. package/lib/public/Bundle.js +2 -1
  18. package/lib/public/BundleGraph.js +21 -8
  19. package/lib/public/Config.js +91 -3
  20. package/lib/public/Dependency.js +2 -1
  21. package/lib/public/MutableBundleGraph.js +2 -1
  22. package/lib/public/Target.js +2 -1
  23. package/lib/requests/AssetGraphRequest.js +13 -1
  24. package/lib/requests/AssetGraphRequestRust.js +17 -2
  25. package/lib/requests/AssetRequest.js +2 -1
  26. package/lib/requests/BundleGraphRequest.js +13 -1
  27. package/lib/requests/ConfigRequest.js +27 -4
  28. package/lib/requests/DevDepRequest.js +21 -1
  29. package/lib/requests/PathRequest.js +10 -0
  30. package/lib/requests/TargetRequest.js +18 -16
  31. package/lib/requests/WriteBundleRequest.js +15 -3
  32. package/lib/requests/WriteBundlesRequest.js +1 -0
  33. package/lib/resolveOptions.js +4 -2
  34. package/package.json +18 -25
  35. package/src/AssetGraph.js +30 -7
  36. package/src/Atlaspack.js +13 -5
  37. package/src/BundleGraph.js +13 -175
  38. package/src/Dependency.js +13 -5
  39. package/src/Environment.js +9 -6
  40. package/src/EnvironmentManager.js +145 -0
  41. package/src/InternalConfig.js +6 -5
  42. package/src/PackagerRunner.js +72 -20
  43. package/src/RequestTracker.js +532 -150
  44. package/src/UncommittedAsset.js +23 -3
  45. package/src/applyRuntimes.js +6 -1
  46. package/src/assetUtils.js +4 -3
  47. package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
  48. package/src/atlaspack-v3/worker/worker.js +7 -0
  49. package/src/index.js +5 -1
  50. package/src/public/Asset.js +9 -2
  51. package/src/public/Bundle.js +2 -1
  52. package/src/public/BundleGraph.js +22 -15
  53. package/src/public/Config.js +128 -14
  54. package/src/public/Dependency.js +2 -1
  55. package/src/public/MutableBundleGraph.js +5 -2
  56. package/src/public/Target.js +2 -1
  57. package/src/requests/AssetGraphRequest.js +13 -3
  58. package/src/requests/AssetGraphRequestRust.js +14 -2
  59. package/src/requests/AssetRequest.js +2 -1
  60. package/src/requests/BundleGraphRequest.js +13 -3
  61. package/src/requests/ConfigRequest.js +33 -9
  62. package/src/requests/DevDepRequest.js +44 -12
  63. package/src/requests/PathRequest.js +4 -0
  64. package/src/requests/TargetRequest.js +19 -25
  65. package/src/requests/WriteBundleRequest.js +14 -8
  66. package/src/requests/WriteBundlesRequest.js +1 -0
  67. package/src/resolveOptions.js +4 -2
  68. package/src/types.js +10 -7
  69. package/test/Environment.test.js +43 -34
  70. package/test/EnvironmentManager.test.js +192 -0
  71. package/test/PublicEnvironment.test.js +10 -7
  72. package/test/RequestTracker.test.js +115 -3
  73. package/test/public/Config.test.js +108 -0
  74. package/test/requests/ConfigRequest.test.js +187 -3
  75. package/test/test-utils.js +4 -9
@@ -6,9 +6,13 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.RequestGraph = void 0;
7
7
  exports.cleanUpOrphans = cleanUpOrphans;
8
8
  exports.default = void 0;
9
+ exports.getBiggestFSEventsInvalidations = getBiggestFSEventsInvalidations;
9
10
  exports.getWatcherOptions = getWatcherOptions;
11
+ exports.invalidateRequestGraph = invalidateRequestGraph;
12
+ exports.invalidateRequestGraphFSEvents = invalidateRequestGraphFSEvents;
10
13
  exports.readAndDeserializeRequestGraph = readAndDeserializeRequestGraph;
11
14
  exports.requestTypes = exports.requestGraphEdgeTypes = void 0;
15
+ exports.runInvalidation = runInvalidation;
12
16
  function _assert() {
13
17
  const data = _interopRequireWildcard(require("assert"));
14
18
  _assert = function () {
@@ -84,6 +88,14 @@ var _projectPath = require("./projectPath");
84
88
  var _ReporterRunner = require("./ReporterRunner");
85
89
  var _ConfigRequest = require("./requests/ConfigRequest");
86
90
  var _utils2 = require("./utils");
91
+ function _perf_hooks() {
92
+ const data = require("perf_hooks");
93
+ _perf_hooks = function () {
94
+ return data;
95
+ };
96
+ return data;
97
+ }
98
+ var _EnvironmentManager = require("./EnvironmentManager");
87
99
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
88
100
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
89
101
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -151,7 +163,7 @@ const nodeFromOption = (option, value) => ({
151
163
  hash: (0, _utils2.hashFromOption)(value)
152
164
  });
153
165
  const nodeFromConfigKey = (fileName, configKey, contentHash) => ({
154
- id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${configKey}`,
166
+ id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${JSON.stringify(configKey)}`,
155
167
  type: CONFIG_KEY,
156
168
  configKey,
157
169
  contentHash
@@ -279,6 +291,12 @@ class RequestGraph extends _graph().ContentGraph {
279
291
  // If the node is invalidated, the cached request chunk on disk needs to be re-written
280
292
  this.removeCachedRequestChunkForNode(nodeId);
281
293
  }
294
+
295
+ /**
296
+ * Nodes that are invalidated on start-up, such as JavaScript babel configuration files which are
297
+ * imported when the build kicks-off and might doing arbitrary work such as reading from the file
298
+ * system.
299
+ */
282
300
  invalidateUnpredictableNodes() {
283
301
  for (let nodeId of this.unpredicatableNodeIds) {
284
302
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
@@ -286,6 +304,10 @@ class RequestGraph extends _graph().ContentGraph {
286
304
  this.invalidateNode(nodeId, _constants.STARTUP);
287
305
  }
288
306
  }
307
+
308
+ /**
309
+ * Effectively uncacheable nodes.
310
+ */
289
311
  invalidateOnBuildNodes() {
290
312
  for (let nodeId of this.invalidateOnBuildNodeIds) {
291
313
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
@@ -293,29 +315,45 @@ class RequestGraph extends _graph().ContentGraph {
293
315
  this.invalidateNode(nodeId, _constants.STARTUP);
294
316
  }
295
317
  }
318
+
319
+ /**
320
+ * Nodes invalidated by environment changes, corresponds to `env: ...` inputs.
321
+ */
296
322
  invalidateEnvNodes(env) {
323
+ const invalidatedKeys = [];
297
324
  for (let nodeId of this.envNodeIds) {
298
325
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
299
326
  (0, _assert().default)(node.type === ENV);
300
- if (env[keyFromEnvContentKey(node.id)] !== node.value) {
327
+ const key = keyFromEnvContentKey(node.id);
328
+ if (env[key] !== node.value) {
329
+ invalidatedKeys.push(key);
301
330
  let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
302
331
  for (let parentNode of parentNodes) {
303
332
  this.invalidateNode(parentNode, _constants.ENV_CHANGE);
304
333
  }
305
334
  }
306
335
  }
336
+ return invalidatedKeys;
307
337
  }
338
+
339
+ /**
340
+ * Nodes invalidated by option changes.
341
+ */
308
342
  invalidateOptionNodes(options) {
343
+ const invalidatedKeys = [];
309
344
  for (let nodeId of this.optionNodeIds) {
310
345
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
311
346
  (0, _assert().default)(node.type === OPTION);
312
- if ((0, _utils2.hashFromOption)(options[keyFromOptionContentKey(node.id)]) !== node.hash) {
347
+ const key = keyFromOptionContentKey(node.id);
348
+ if ((0, _utils2.hashFromOption)(options[key]) !== node.hash) {
349
+ invalidatedKeys.push(key);
313
350
  let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
314
351
  for (let parentNode of parentNodes) {
315
352
  this.invalidateNode(parentNode, _constants.OPTION_CHANGE);
316
353
  }
317
354
  }
318
355
  }
356
+ return invalidatedKeys;
319
357
  }
320
358
  invalidateOnConfigKeyChange(requestNodeId, filePath, configKey, contentHash) {
321
359
  let configKeyNodeId = this.addNode(nodeFromConfigKey(filePath, configKey, contentHash));
@@ -408,8 +446,8 @@ class RequestGraph extends _graph().ContentGraph {
408
446
  this.invalidateOnBuildNodeIds.add(requestNodeId);
409
447
  }
410
448
  invalidateOnEnvChange(requestNodeId, env, value) {
411
- let envNode = nodeFromEnv(env, value);
412
- let envNodeId = this.addNode(envNode);
449
+ const envNode = nodeFromEnv(env, value);
450
+ const envNodeId = this.addNode(envNode);
413
451
  if (!this.hasEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
414
452
  this.addEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update);
415
453
  }
@@ -556,10 +594,12 @@ class RequestGraph extends _graph().ContentGraph {
556
594
  aboveCache.set(fileNameNodeId, above);
557
595
  return above;
558
596
  };
597
+ const invalidationsByPath = new Map();
559
598
  for (let {
560
599
  path: _path,
561
600
  type
562
601
  } of events) {
602
+ const invalidationsBefore = this.getInvalidNodeCount();
563
603
  if (!enableOptimization && process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' && ++count === 256) {
564
604
  let duration = Date.now() - startTime;
565
605
  predictedTime = duration * (events.length >> 8);
@@ -596,7 +636,10 @@ class RequestGraph extends _graph().ContentGraph {
596
636
  this.invalidNodeIds.add(id);
597
637
  }
598
638
  }
599
- return true;
639
+ return {
640
+ didInvalidate: true,
641
+ invalidationsByPath: new Map()
642
+ };
600
643
  }
601
644
 
602
645
  // sometimes mac os reports update events as create events.
@@ -653,10 +696,17 @@ class RequestGraph extends _graph().ContentGraph {
653
696
  this.removeNode(nodeId, removeOrphans);
654
697
  }
655
698
  let configKeyNodes = this.configKeyNodes.get(_filePath);
656
- if (configKeyNodes && (type === 'delete' || type === 'update')) {
699
+
700
+ // With granular invalidations we will always run this block,
701
+ // so even if we get a create event (for whatever reason), we will still
702
+ // try to limit invalidations from config key changes through hashing.
703
+ //
704
+ // Currently create events can invalidate a large number of nodes due to
705
+ // "create above" invalidations.
706
+ if (configKeyNodes) {
657
707
  for (let nodeId of configKeyNodes) {
658
708
  let isInvalid = type === 'delete';
659
- if (type === 'update') {
709
+ if (type !== 'delete') {
660
710
  let node = this.getNode(nodeId);
661
711
  (0, _assert().default)(node && node.type === CONFIG_KEY);
662
712
  let contentHash = await (0, _ConfigRequest.getConfigKeyContentHash)(_filePath, node.configKey, options);
@@ -671,6 +721,8 @@ class RequestGraph extends _graph().ContentGraph {
671
721
  }
672
722
  }
673
723
  }
724
+ const invalidationsAfter = this.getInvalidNodeCount();
725
+ invalidationsByPath.set(_path, (invalidationsByPath.get(_path) ?? 0) + (invalidationsAfter - invalidationsBefore));
674
726
  }
675
727
  if ((0, _featureFlags().getFeatureFlag)('fixQuadraticCacheInvalidation')) {
676
728
  cleanUpOrphans(this);
@@ -688,7 +740,10 @@ class RequestGraph extends _graph().ContentGraph {
688
740
  numberOfInvalidatedNodes: invalidatedNodes.size
689
741
  }
690
742
  });
691
- return didInvalidate && this.invalidNodeIds.size > 0;
743
+ return {
744
+ didInvalidate,
745
+ invalidationsByPath
746
+ };
692
747
  }
693
748
  hasCachedRequestChunk(index) {
694
749
  return this.cachedRequestChunks.has(index);
@@ -699,6 +754,13 @@ class RequestGraph extends _graph().ContentGraph {
699
754
  removeCachedRequestChunkForNode(nodeId) {
700
755
  this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
701
756
  }
757
+
758
+ /**
759
+ * Returns the number of invalidated nodes in the graph.
760
+ */
761
+ getInvalidNodeCount() {
762
+ return this.invalidNodeIds.size;
763
+ }
702
764
  }
703
765
  exports.RequestGraph = RequestGraph;
704
766
  class RequestTracker {
@@ -717,6 +779,9 @@ class RequestTracker {
717
779
 
718
780
  // TODO: refactor (abortcontroller should be created by RequestTracker)
719
781
  setSignal(signal) {
782
+ if ((0, _featureFlags().getFeatureFlag)('fixBuildAbortCorruption')) {
783
+ return;
784
+ }
720
785
  this.signal = signal;
721
786
  }
722
787
  startRequest(request) {
@@ -857,7 +922,9 @@ class RequestTracker {
857
922
  options: this.options,
858
923
  rustAtlaspack: this.rustAtlaspack
859
924
  });
860
- (0, _utils2.assertSignalNotAborted)(this.signal);
925
+ if (!(0, _featureFlags().getFeatureFlag)('fixBuildAbortCorruption')) {
926
+ (0, _utils2.assertSignalNotAborted)(this.signal);
927
+ }
861
928
  this.completeRequest(requestNodeId);
862
929
  deferred.resolve(true);
863
930
  return result;
@@ -944,84 +1011,88 @@ class RequestTracker {
944
1011
  return result;
945
1012
  }
946
1013
  }
947
- await runCacheImprovements(async cache => {
948
- await cache.getNativeRef().startWriteTransaction();
949
- }, () => Promise.resolve());
950
1014
  let cacheKey = getCacheKey(this.options);
951
- let requestGraphKey = `requestGraph-${cacheKey}`;
1015
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1016
+ let snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
952
1017
  if (this.options.shouldDisableCache) {
953
1018
  return;
954
1019
  }
955
1020
  let total = 0;
956
- (0, _ReporterRunner.report)({
957
- type: 'cache',
958
- phase: 'start',
959
- total,
960
- size: this.graph.nodes.length
961
- });
962
- let serialisedGraph = this.graph.serialize();
963
-
964
- // Delete an existing request graph cache, to prevent invalid states
965
- await this.options.cache.deleteLargeBlob(requestGraphKey);
966
- const serialiseAndSet = async (key, contents) => {
967
- if (signal !== null && signal !== void 0 && signal.aborted) {
968
- throw new Error('Serialization was aborted');
969
- }
970
- await runCacheImprovements(cache => {
971
- (0, _logger().instrument)(`cache.put(${key})`, () => {
972
- cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
973
- });
974
- return Promise.resolve();
975
- }, async () => {
976
- await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
977
- signal: signal
978
- } : undefined);
979
- });
980
- total += 1;
1021
+ await runCacheImprovements(async cache => {
1022
+ await cache.getNativeRef().startWriteTransaction();
1023
+ }, () => Promise.resolve());
1024
+ try {
981
1025
  (0, _ReporterRunner.report)({
982
1026
  type: 'cache',
983
- phase: 'write',
1027
+ phase: 'start',
984
1028
  total,
985
1029
  size: this.graph.nodes.length
986
1030
  });
987
- };
988
- let queue = new (_utils().PromiseQueue)({
989
- maxConcurrent: 32
990
- });
1031
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1032
+ await (0, _EnvironmentManager.writeEnvironmentsToCache)(options.cache);
1033
+ }
1034
+ let serialisedGraph = this.graph.serialize();
991
1035
 
992
- // Preallocating a sparse array is faster than pushing when N is high enough
993
- let cacheableNodes = new Array(serialisedGraph.nodes.length);
994
- for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
995
- let node = serialisedGraph.nodes[i];
996
- let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
997
- if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
998
- queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1036
+ // Delete an existing request graph cache, to prevent invalid states
1037
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1038
+ const serialiseAndSet = async (key, contents) => {
1039
+ if (signal !== null && signal !== void 0 && signal.aborted) {
1040
+ throw new Error('Serialization was aborted');
1041
+ }
1042
+ await runCacheImprovements(cache => {
1043
+ (0, _logger().instrument)(`RequestTracker::writeToCache::cache.put(${key})`, () => {
1044
+ cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
1045
+ });
1046
+ return Promise.resolve();
1047
+ }, async () => {
1048
+ await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
1049
+ signal: signal
1050
+ } : undefined);
1051
+ });
1052
+ total += 1;
1053
+ (0, _ReporterRunner.report)({
1054
+ type: 'cache',
1055
+ phase: 'write',
1056
+ total,
1057
+ size: this.graph.nodes.length
1058
+ });
1059
+ };
1060
+ let queue = new (_utils().PromiseQueue)({
1061
+ maxConcurrent: 32
1062
+ });
999
1063
 
1000
- // eslint-disable-next-line no-unused-vars
1001
- let {
1002
- result: _,
1003
- ...newNode
1004
- } = node;
1005
- cacheableNodes[i] = newNode;
1006
- } else {
1007
- cacheableNodes[i] = node;
1064
+ // Preallocating a sparse array is faster than pushing when N is high enough
1065
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1066
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1067
+ let node = serialisedGraph.nodes[i];
1068
+ let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
1069
+ if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
1070
+ queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1071
+
1072
+ // eslint-disable-next-line no-unused-vars
1073
+ let {
1074
+ result: _,
1075
+ ...newNode
1076
+ } = node;
1077
+ cacheableNodes[i] = newNode;
1078
+ } else {
1079
+ cacheableNodes[i] = node;
1080
+ }
1008
1081
  }
1009
- }
1010
- let nodeCountsPerBlob = [];
1011
- for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
1012
- let nodesStartIndex = i * this.graph.nodesPerBlob;
1013
- let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
1014
- nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1015
- if (!this.graph.hasCachedRequestChunk(i)) {
1016
- // We assume the request graph nodes are immutable and won't change
1017
- let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1018
- queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
1019
- // Succeeded in writing to disk, save that we have completed this chunk
1020
- this.graph.setCachedRequestChunk(i);
1021
- }));
1082
+ let nodeCountsPerBlob = [];
1083
+ for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
1084
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1085
+ let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
1086
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1087
+ if (!this.graph.hasCachedRequestChunk(i)) {
1088
+ // We assume the request graph nodes are immutable and won't change
1089
+ let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1090
+ queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
1091
+ // Succeeded in writing to disk, save that we have completed this chunk
1092
+ this.graph.setCachedRequestChunk(i);
1093
+ }));
1094
+ }
1022
1095
  }
1023
- }
1024
- try {
1025
1096
  await queue.run();
1026
1097
 
1027
1098
  // Set the request graph after the queue is flushed to avoid writing an invalid state
@@ -1030,7 +1101,7 @@ class RequestTracker {
1030
1101
  nodeCountsPerBlob,
1031
1102
  nodes: undefined
1032
1103
  });
1033
- await runCacheImprovements(() => serialiseAndSet(`request_tracker:cache_metadata:${cacheKey}`, {
1104
+ await runCacheImprovements(() => serialiseAndSet(`${cacheKey}/cache_metadata`, {
1034
1105
  version: _constants.ATLASPACK_VERSION,
1035
1106
  entries: this.options.entries,
1036
1107
  mode: this.options.mode,
@@ -1038,15 +1109,16 @@ class RequestTracker {
1038
1109
  watchBackend: this.options.watchBackend
1039
1110
  }), () => Promise.resolve());
1040
1111
  let opts = getWatcherOptions(this.options);
1041
- let snapshotPath = _path2().default.join(this.options.cacheDir, `snapshot-${cacheKey}` + '.txt');
1112
+ let snapshotPath = _path2().default.join(this.options.cacheDir, snapshotKey + '.txt');
1042
1113
  await this.options.outputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts);
1043
1114
  } catch (err) {
1044
1115
  // If we have aborted, ignore the error and continue
1045
1116
  if (!(signal !== null && signal !== void 0 && signal.aborted)) throw err;
1117
+ } finally {
1118
+ await runCacheImprovements(async cache => {
1119
+ await cache.getNativeRef().commitWriteTransaction();
1120
+ }, () => Promise.resolve());
1046
1121
  }
1047
- await runCacheImprovements(async cache => {
1048
- await cache.getNativeRef().commitWriteTransaction();
1049
- }, () => Promise.resolve());
1050
1122
  (0, _ReporterRunner.report)({
1051
1123
  type: 'cache',
1052
1124
  phase: 'end',
@@ -1083,17 +1155,30 @@ function getWatcherOptions({
1083
1155
  };
1084
1156
  }
1085
1157
  function getCacheKey(options) {
1158
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1159
+ const hash = (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1160
+ return `RequestTracker/${_constants.ATLASPACK_VERSION}/${hash}`;
1161
+ }
1086
1162
  return (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1087
1163
  }
1088
1164
  function getRequestGraphNodeKey(index, cacheKey) {
1165
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1166
+ return `${cacheKey}/RequestGraph/nodes/${index}`;
1167
+ }
1089
1168
  return `requestGraph-nodes-${index}-${cacheKey}`;
1090
1169
  }
1091
1170
  async function readAndDeserializeRequestGraph(cache, requestGraphKey, cacheKey) {
1092
1171
  let bufferLength = 0;
1093
1172
  const getAndDeserialize = async key => {
1094
- let buffer = await cache.getLargeBlob(key);
1095
- bufferLength += Buffer.byteLength(buffer);
1096
- return (0, _buildCache().deserialize)(buffer);
1173
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1174
+ const buffer = await cache.getBlob(key);
1175
+ bufferLength += Buffer.byteLength(buffer);
1176
+ return (0, _buildCache().deserialize)(buffer);
1177
+ } else {
1178
+ const buffer = await cache.getLargeBlob(key);
1179
+ bufferLength += Buffer.byteLength(buffer);
1180
+ return (0, _buildCache().deserialize)(buffer);
1181
+ }
1097
1182
  };
1098
1183
  let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
1099
1184
  let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(async (nodesCount, i) => {
@@ -1115,19 +1200,33 @@ async function loadRequestGraph(options) {
1115
1200
  return new RequestGraph();
1116
1201
  }
1117
1202
  let cacheKey = getCacheKey(options);
1118
- let requestGraphKey = `requestGraph-${cacheKey}`;
1203
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1119
1204
  let timeout;
1120
- const snapshotKey = `snapshot-${cacheKey}`;
1205
+ const snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
1121
1206
  const snapshotPath = _path2().default.join(options.cacheDir, snapshotKey + '.txt');
1207
+ const commonMeta = {
1208
+ cacheKey,
1209
+ snapshotKey,
1210
+ cacheKeyOptions: {
1211
+ version: _constants.ATLASPACK_VERSION,
1212
+ entries: options.entries,
1213
+ mode: options.mode,
1214
+ shouldBuildLazily: options.shouldBuildLazily,
1215
+ watchBackend: options.watchBackend
1216
+ }
1217
+ };
1122
1218
  _logger().default.verbose({
1123
1219
  origin: '@atlaspack/core',
1124
1220
  message: 'Loading request graph',
1125
1221
  meta: {
1126
- cacheKey,
1127
- snapshotKey
1222
+ ...commonMeta
1128
1223
  }
1129
1224
  });
1130
- if (await options.cache.hasLargeBlob(requestGraphKey)) {
1225
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1226
+ await (0, _EnvironmentManager.loadEnvironmentsFromCache)(options.cache);
1227
+ }
1228
+ const hasRequestGraphInCache = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? await options.cache.has(requestGraphKey) : await options.cache.hasLargeBlob(requestGraphKey);
1229
+ if (hasRequestGraphInCache) {
1131
1230
  try {
1132
1231
  let {
1133
1232
  requestGraph
@@ -1146,16 +1245,29 @@ async function loadRequestGraph(options) {
1146
1245
  origin: '@atlaspack/core',
1147
1246
  message: `File system event count: ${events.length}`,
1148
1247
  meta: {
1248
+ ...commonMeta,
1149
1249
  trackableEvent: 'watcher_events_count',
1150
1250
  watcherEventCount: events.length,
1151
1251
  duration: Date.now() - startTime
1152
1252
  }
1153
1253
  });
1154
- requestGraph.invalidateUnpredictableNodes();
1155
- requestGraph.invalidateOnBuildNodes();
1156
- requestGraph.invalidateEnvNodes(options.env);
1157
- requestGraph.invalidateOptionNodes(options);
1158
- await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
1254
+ if ((0, _featureFlags().getFeatureFlag)('verboseRequestInvalidationStats')) {
1255
+ const invalidationStats = await invalidateRequestGraph(requestGraph, options, events);
1256
+ _logger().default.verbose({
1257
+ origin: '@atlaspack/core',
1258
+ message: 'Request track loaded from cache',
1259
+ meta: {
1260
+ ...commonMeta,
1261
+ trackableEvent: 'request_tracker_cache_key_hit',
1262
+ invalidationStats
1263
+ }
1264
+ });
1265
+ } else {
1266
+ requestGraph.invalidateUnpredictableNodes();
1267
+ requestGraph.invalidateOnBuildNodes();
1268
+ requestGraph.invalidateEnvNodes(options.env);
1269
+ requestGraph.invalidateOptionNodes(options);
1270
+ }
1159
1271
  return requestGraph;
1160
1272
  } catch (e) {
1161
1273
  // Prevent logging fs events took too long warning
@@ -1170,12 +1282,104 @@ async function loadRequestGraph(options) {
1170
1282
  origin: '@atlaspack/core',
1171
1283
  message: 'Cache entry for request tracker was not found, initializing a clean cache.',
1172
1284
  meta: {
1173
- cacheKey,
1174
- snapshotKey
1285
+ ...commonMeta,
1286
+ trackableEvent: 'request_tracker_cache_key_miss'
1175
1287
  }
1176
1288
  });
1177
1289
  return new RequestGraph();
1178
1290
  }
1291
+
1292
+ /**
1293
+ * A wrapper around an invalidation type / method
1294
+ */
1295
+
1296
+ /**
1297
+ * Details about an invalidation.
1298
+ *
1299
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
1300
+ * by path.
1301
+ *
1302
+ * If this is a env or option invalidation, this key will contain the list of changed environment
1303
+ * variables or options.
1304
+ */
1305
+
1306
+ /**
1307
+ * Number of invalidations for a given file-system event.
1308
+ */
1309
+
1310
+ /**
1311
+ * Information about a certain cache invalidation type.
1312
+ */
1313
+
1314
+ /**
1315
+ * Respond to unpredictable, build, environment changes, option changes and file-system events
1316
+ * invalidating RequestGraph nodes.
1317
+ *
1318
+ * Returns the count of nodes invalidated by each invalidation type.
1319
+ */
1320
+ async function invalidateRequestGraph(requestGraph, options, events) {
1321
+ const invalidationFns = [{
1322
+ key: 'unpredictable',
1323
+ fn: () => requestGraph.invalidateUnpredictableNodes()
1324
+ }, {
1325
+ key: 'onBuild',
1326
+ fn: () => requestGraph.invalidateOnBuildNodes()
1327
+ }, {
1328
+ key: 'env',
1329
+ fn: () => requestGraph.invalidateEnvNodes(options.env)
1330
+ }, {
1331
+ key: 'option',
1332
+ fn: () => requestGraph.invalidateOptionNodes(options)
1333
+ }, {
1334
+ key: 'fsEvents',
1335
+ fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events)
1336
+ }];
1337
+ const invalidations = [];
1338
+ for (const invalidation of invalidationFns) {
1339
+ invalidations.push(await runInvalidation(requestGraph, invalidation));
1340
+ }
1341
+ const invalidatedCount = invalidations.reduce((acc, invalidation) => acc + invalidation.count, 0);
1342
+ const requestCount = requestGraph.nodes.reduce((acc, node) => acc + ((node === null || node === void 0 ? void 0 : node.type) === REQUEST ? 1 : 0), 0);
1343
+ const nodeCount = requestGraph.nodes.length;
1344
+ return {
1345
+ invalidations,
1346
+ nodeCount,
1347
+ requestCount,
1348
+ invalidatedCount,
1349
+ nodeInvalidationRatio: invalidatedCount / nodeCount,
1350
+ requestInvalidationRatio: invalidatedCount / requestCount
1351
+ };
1352
+ }
1353
+ /**
1354
+ * Invalidate the request graph based on file-system events.
1355
+ *
1356
+ * Returns statistics about the invalidations.
1357
+ */
1358
+ async function invalidateRequestGraphFSEvents(requestGraph, options, events) {
1359
+ const {
1360
+ invalidationsByPath
1361
+ } = await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
1362
+ const biggestInvalidations = getBiggestFSEventsInvalidations(invalidationsByPath);
1363
+ return {
1364
+ biggestInvalidations
1365
+ };
1366
+ }
1367
+ /**
1368
+ * Runs an invalidation function and reports metrics.
1369
+ */
1370
+ async function runInvalidation(requestGraph, invalidationFn) {
1371
+ const start = _perf_hooks().performance.now();
1372
+ const startInvalidationCount = requestGraph.getInvalidNodeCount();
1373
+ const result = await invalidationFn.fn();
1374
+ const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
1375
+ const duration = _perf_hooks().performance.now() - start;
1376
+ return {
1377
+ key: invalidationFn.key,
1378
+ count,
1379
+ detail: result ?? null,
1380
+ duration
1381
+ };
1382
+ }
1179
1383
  function logErrorOnBailout(options, snapshotPath, e) {
1180
1384
  if (e.message && e.message.includes('invalid clockspec')) {
1181
1385
  const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
@@ -1215,4 +1419,19 @@ function cleanUpOrphans(graph) {
1215
1419
  }
1216
1420
  });
1217
1421
  return removedNodeIds;
1422
+ }
1423
+
1424
+ /**
1425
+ * Returns paths that invalidated the most nodes
1426
+ */
1427
+ function getBiggestFSEventsInvalidations(invalidationsByPath, limit = 10) {
1428
+ const invalidations = [];
1429
+ for (const [path, count] of invalidationsByPath) {
1430
+ invalidations.push({
1431
+ path,
1432
+ count
1433
+ });
1434
+ }
1435
+ invalidations.sort((a, b) => b.count - a.count);
1436
+ return invalidations.slice(0, limit);
1218
1437
  }