@atlaspack/core 2.14.1-dev.144 → 2.14.1-dev.146

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 (67) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/lib/AssetGraph.js +17 -6
  3. package/lib/Atlaspack.js +24 -5
  4. package/lib/BundleGraph.js +6 -5
  5. package/lib/Dependency.js +6 -2
  6. package/lib/Environment.js +4 -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 +337 -82
  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/public/Asset.js +3 -2
  16. package/lib/public/Bundle.js +2 -1
  17. package/lib/public/BundleGraph.js +21 -5
  18. package/lib/public/Config.js +98 -3
  19. package/lib/public/Dependency.js +2 -1
  20. package/lib/public/MutableBundleGraph.js +2 -1
  21. package/lib/public/Target.js +2 -1
  22. package/lib/requests/AssetGraphRequest.js +13 -1
  23. package/lib/requests/AssetRequest.js +2 -1
  24. package/lib/requests/BundleGraphRequest.js +13 -1
  25. package/lib/requests/ConfigRequest.js +27 -4
  26. package/lib/requests/TargetRequest.js +18 -16
  27. package/lib/requests/WriteBundleRequest.js +15 -3
  28. package/lib/requests/WriteBundlesRequest.js +1 -0
  29. package/lib/resolveOptions.js +5 -6
  30. package/package.json +22 -18
  31. package/src/AssetGraph.js +12 -6
  32. package/src/Atlaspack.js +29 -13
  33. package/src/BundleGraph.js +13 -8
  34. package/src/Dependency.js +13 -5
  35. package/src/Environment.js +8 -5
  36. package/src/EnvironmentManager.js +145 -0
  37. package/src/InternalConfig.js +6 -5
  38. package/src/PackagerRunner.js +72 -20
  39. package/src/RequestTracker.js +567 -131
  40. package/src/UncommittedAsset.js +23 -3
  41. package/src/applyRuntimes.js +6 -1
  42. package/src/assetUtils.js +4 -3
  43. package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
  44. package/src/atlaspack-v3/worker/worker.js +7 -0
  45. package/src/public/Asset.js +9 -2
  46. package/src/public/Bundle.js +2 -1
  47. package/src/public/BundleGraph.js +22 -5
  48. package/src/public/Config.js +129 -14
  49. package/src/public/Dependency.js +2 -1
  50. package/src/public/MutableBundleGraph.js +2 -1
  51. package/src/public/Target.js +2 -1
  52. package/src/requests/AssetGraphRequest.js +13 -3
  53. package/src/requests/AssetRequest.js +2 -1
  54. package/src/requests/BundleGraphRequest.js +13 -3
  55. package/src/requests/ConfigRequest.js +33 -9
  56. package/src/requests/TargetRequest.js +19 -25
  57. package/src/requests/WriteBundleRequest.js +14 -8
  58. package/src/requests/WriteBundlesRequest.js +1 -0
  59. package/src/resolveOptions.js +6 -8
  60. package/src/types.js +9 -7
  61. package/test/Environment.test.js +43 -34
  62. package/test/EnvironmentManager.test.js +192 -0
  63. package/test/PublicEnvironment.test.js +10 -7
  64. package/test/RequestTracker.test.js +115 -3
  65. package/test/public/Config.test.js +108 -0
  66. package/test/requests/ConfigRequest.test.js +187 -3
  67. 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 () {
@@ -30,6 +34,13 @@ function _buildCache() {
30
34
  };
31
35
  return data;
32
36
  }
37
+ function _cache() {
38
+ const data = require("@atlaspack/cache");
39
+ _cache = function () {
40
+ return data;
41
+ };
42
+ return data;
43
+ }
33
44
  function _featureFlags() {
34
45
  const data = require("@atlaspack/feature-flags");
35
46
  _featureFlags = function () {
@@ -45,7 +56,7 @@ function _graph() {
45
56
  return data;
46
57
  }
47
58
  function _logger() {
48
- const data = _interopRequireDefault(require("@atlaspack/logger"));
59
+ const data = _interopRequireWildcard(require("@atlaspack/logger"));
49
60
  _logger = function () {
50
61
  return data;
51
62
  };
@@ -77,6 +88,14 @@ var _projectPath = require("./projectPath");
77
88
  var _ReporterRunner = require("./ReporterRunner");
78
89
  var _ConfigRequest = require("./requests/ConfigRequest");
79
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");
80
99
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
81
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); }
82
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; }
@@ -144,7 +163,7 @@ const nodeFromOption = (option, value) => ({
144
163
  hash: (0, _utils2.hashFromOption)(value)
145
164
  });
146
165
  const nodeFromConfigKey = (fileName, configKey, contentHash) => ({
147
- id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${configKey}`,
166
+ id: `config_key:${(0, _projectPath.fromProjectPathRelative)(fileName)}:${JSON.stringify(configKey)}`,
148
167
  type: CONFIG_KEY,
149
168
  configKey,
150
169
  contentHash
@@ -272,6 +291,12 @@ class RequestGraph extends _graph().ContentGraph {
272
291
  // If the node is invalidated, the cached request chunk on disk needs to be re-written
273
292
  this.removeCachedRequestChunkForNode(nodeId);
274
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
+ */
275
300
  invalidateUnpredictableNodes() {
276
301
  for (let nodeId of this.unpredicatableNodeIds) {
277
302
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
@@ -279,6 +304,10 @@ class RequestGraph extends _graph().ContentGraph {
279
304
  this.invalidateNode(nodeId, _constants.STARTUP);
280
305
  }
281
306
  }
307
+
308
+ /**
309
+ * Effectively uncacheable nodes.
310
+ */
282
311
  invalidateOnBuildNodes() {
283
312
  for (let nodeId of this.invalidateOnBuildNodeIds) {
284
313
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
@@ -286,29 +315,45 @@ class RequestGraph extends _graph().ContentGraph {
286
315
  this.invalidateNode(nodeId, _constants.STARTUP);
287
316
  }
288
317
  }
318
+
319
+ /**
320
+ * Nodes invalidated by environment changes, corresponds to `env: ...` inputs.
321
+ */
289
322
  invalidateEnvNodes(env) {
323
+ const invalidatedKeys = [];
290
324
  for (let nodeId of this.envNodeIds) {
291
325
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
292
326
  (0, _assert().default)(node.type === ENV);
293
- if (env[keyFromEnvContentKey(node.id)] !== node.value) {
327
+ const key = keyFromEnvContentKey(node.id);
328
+ if (env[key] !== node.value) {
329
+ invalidatedKeys.push(key);
294
330
  let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
295
331
  for (let parentNode of parentNodes) {
296
332
  this.invalidateNode(parentNode, _constants.ENV_CHANGE);
297
333
  }
298
334
  }
299
335
  }
336
+ return invalidatedKeys;
300
337
  }
338
+
339
+ /**
340
+ * Nodes invalidated by option changes.
341
+ */
301
342
  invalidateOptionNodes(options) {
343
+ const invalidatedKeys = [];
302
344
  for (let nodeId of this.optionNodeIds) {
303
345
  let node = (0, _nullthrows().default)(this.getNode(nodeId));
304
346
  (0, _assert().default)(node.type === OPTION);
305
- 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);
306
350
  let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
307
351
  for (let parentNode of parentNodes) {
308
352
  this.invalidateNode(parentNode, _constants.OPTION_CHANGE);
309
353
  }
310
354
  }
311
355
  }
356
+ return invalidatedKeys;
312
357
  }
313
358
  invalidateOnConfigKeyChange(requestNodeId, filePath, configKey, contentHash) {
314
359
  let configKeyNodeId = this.addNode(nodeFromConfigKey(filePath, configKey, contentHash));
@@ -401,8 +446,8 @@ class RequestGraph extends _graph().ContentGraph {
401
446
  this.invalidateOnBuildNodeIds.add(requestNodeId);
402
447
  }
403
448
  invalidateOnEnvChange(requestNodeId, env, value) {
404
- let envNode = nodeFromEnv(env, value);
405
- let envNodeId = this.addNode(envNode);
449
+ const envNode = nodeFromEnv(env, value);
450
+ const envNodeId = this.addNode(envNode);
406
451
  if (!this.hasEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
407
452
  this.addEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update);
408
453
  }
@@ -549,10 +594,12 @@ class RequestGraph extends _graph().ContentGraph {
549
594
  aboveCache.set(fileNameNodeId, above);
550
595
  return above;
551
596
  };
597
+ const invalidationsByPath = new Map();
552
598
  for (let {
553
599
  path: _path,
554
600
  type
555
601
  } of events) {
602
+ const invalidationsBefore = this.getInvalidNodeCount();
556
603
  if (!enableOptimization && process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' && ++count === 256) {
557
604
  let duration = Date.now() - startTime;
558
605
  predictedTime = duration * (events.length >> 8);
@@ -589,7 +636,10 @@ class RequestGraph extends _graph().ContentGraph {
589
636
  this.invalidNodeIds.add(id);
590
637
  }
591
638
  }
592
- return true;
639
+ return {
640
+ didInvalidate: true,
641
+ invalidationsByPath: new Map()
642
+ };
593
643
  }
594
644
 
595
645
  // sometimes mac os reports update events as create events.
@@ -646,10 +696,18 @@ class RequestGraph extends _graph().ContentGraph {
646
696
  this.removeNode(nodeId, removeOrphans);
647
697
  }
648
698
  let configKeyNodes = this.configKeyNodes.get(_filePath);
649
- 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
+ const isConfigKeyChange = (0, _featureFlags().getFeatureFlag)('granularTsConfigInvalidation') || type === 'delete' || type === 'update';
707
+ if (configKeyNodes && isConfigKeyChange) {
650
708
  for (let nodeId of configKeyNodes) {
651
709
  let isInvalid = type === 'delete';
652
- if (type === 'update') {
710
+ if (type !== 'delete') {
653
711
  let node = this.getNode(nodeId);
654
712
  (0, _assert().default)(node && node.type === CONFIG_KEY);
655
713
  let contentHash = await (0, _ConfigRequest.getConfigKeyContentHash)(_filePath, node.configKey, options);
@@ -664,6 +722,8 @@ class RequestGraph extends _graph().ContentGraph {
664
722
  }
665
723
  }
666
724
  }
725
+ const invalidationsAfter = this.getInvalidNodeCount();
726
+ invalidationsByPath.set(_path, (invalidationsByPath.get(_path) ?? 0) + (invalidationsAfter - invalidationsBefore));
667
727
  }
668
728
  if ((0, _featureFlags().getFeatureFlag)('fixQuadraticCacheInvalidation')) {
669
729
  cleanUpOrphans(this);
@@ -681,7 +741,10 @@ class RequestGraph extends _graph().ContentGraph {
681
741
  numberOfInvalidatedNodes: invalidatedNodes.size
682
742
  }
683
743
  });
684
- return didInvalidate && this.invalidNodeIds.size > 0;
744
+ return {
745
+ didInvalidate,
746
+ invalidationsByPath
747
+ };
685
748
  }
686
749
  hasCachedRequestChunk(index) {
687
750
  return this.cachedRequestChunks.has(index);
@@ -692,6 +755,13 @@ class RequestGraph extends _graph().ContentGraph {
692
755
  removeCachedRequestChunkForNode(nodeId) {
693
756
  this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
694
757
  }
758
+
759
+ /**
760
+ * Returns the number of invalidated nodes in the graph.
761
+ */
762
+ getInvalidNodeCount() {
763
+ return this.invalidNodeIds.size;
764
+ }
695
765
  }
696
766
  exports.RequestGraph = RequestGraph;
697
767
  class RequestTracker {
@@ -759,8 +829,10 @@ class RequestTracker {
759
829
  return result;
760
830
  } else if (node.resultCacheKey != null && ifMatch == null) {
761
831
  let key = node.resultCacheKey;
762
- (0, _assert().default)(this.options.cache.hasLargeBlob(key));
763
- let cachedResult = (0, _buildCache().deserialize)(await this.options.cache.getLargeBlob(key));
832
+ if (!(0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
833
+ (0, _assert().default)(this.options.cache.hasLargeBlob(key));
834
+ }
835
+ let cachedResult = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? (0, _nullthrows().default)(await this.options.cache.get(key)) : (0, _buildCache().deserialize)(await this.options.cache.getLargeBlob(key));
764
836
  node.result = cachedResult;
765
837
  return cachedResult;
766
838
  }
@@ -924,74 +996,99 @@ class RequestTracker {
924
996
  };
925
997
  }
926
998
  async writeToCache(signal) {
999
+ const options = this.options;
1000
+ async function runCacheImprovements(newPath, oldPath) {
1001
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1002
+ (0, _assert().default)(options.cache instanceof _cache().LMDBLiteCache);
1003
+ const result = await newPath(options.cache);
1004
+ return result;
1005
+ } else {
1006
+ const result = await oldPath();
1007
+ return result;
1008
+ }
1009
+ }
927
1010
  let cacheKey = getCacheKey(this.options);
928
- let requestGraphKey = `requestGraph-${cacheKey}`;
1011
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1012
+ let snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
929
1013
  if (this.options.shouldDisableCache) {
930
1014
  return;
931
1015
  }
932
1016
  let total = 0;
933
- (0, _ReporterRunner.report)({
934
- type: 'cache',
935
- phase: 'start',
936
- total,
937
- size: this.graph.nodes.length
938
- });
939
- let serialisedGraph = this.graph.serialize();
940
-
941
- // Delete an existing request graph cache, to prevent invalid states
942
- await this.options.cache.deleteLargeBlob(requestGraphKey);
943
- const serialiseAndSet = async (key, contents) => {
944
- if (signal !== null && signal !== void 0 && signal.aborted) {
945
- throw new Error('Serialization was aborted');
946
- }
947
- await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
948
- signal: signal
949
- } : undefined);
950
- total += 1;
1017
+ await runCacheImprovements(async cache => {
1018
+ await cache.getNativeRef().startWriteTransaction();
1019
+ }, () => Promise.resolve());
1020
+ try {
951
1021
  (0, _ReporterRunner.report)({
952
1022
  type: 'cache',
953
- phase: 'write',
1023
+ phase: 'start',
954
1024
  total,
955
1025
  size: this.graph.nodes.length
956
1026
  });
957
- };
958
- let queue = new (_utils().PromiseQueue)({
959
- maxConcurrent: 32
960
- });
1027
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1028
+ await (0, _EnvironmentManager.writeEnvironmentsToCache)(options.cache);
1029
+ }
1030
+ let serialisedGraph = this.graph.serialize();
1031
+
1032
+ // Delete an existing request graph cache, to prevent invalid states
1033
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1034
+ const serialiseAndSet = async (key, contents) => {
1035
+ if (signal !== null && signal !== void 0 && signal.aborted) {
1036
+ throw new Error('Serialization was aborted');
1037
+ }
1038
+ await runCacheImprovements(cache => {
1039
+ (0, _logger().instrument)(`RequestTracker::writeToCache::cache.put(${key})`, () => {
1040
+ cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
1041
+ });
1042
+ return Promise.resolve();
1043
+ }, async () => {
1044
+ await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
1045
+ signal: signal
1046
+ } : undefined);
1047
+ });
1048
+ total += 1;
1049
+ (0, _ReporterRunner.report)({
1050
+ type: 'cache',
1051
+ phase: 'write',
1052
+ total,
1053
+ size: this.graph.nodes.length
1054
+ });
1055
+ };
1056
+ let queue = new (_utils().PromiseQueue)({
1057
+ maxConcurrent: 32
1058
+ });
961
1059
 
962
- // Preallocating a sparse array is faster than pushing when N is high enough
963
- let cacheableNodes = new Array(serialisedGraph.nodes.length);
964
- for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
965
- let node = serialisedGraph.nodes[i];
966
- let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
967
- if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
968
- queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1060
+ // Preallocating a sparse array is faster than pushing when N is high enough
1061
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1062
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1063
+ let node = serialisedGraph.nodes[i];
1064
+ let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
1065
+ if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
1066
+ queue.add(() => serialiseAndSet(resultCacheKey, node.result));
969
1067
 
970
- // eslint-disable-next-line no-unused-vars
971
- let {
972
- result: _,
973
- ...newNode
974
- } = node;
975
- cacheableNodes[i] = newNode;
976
- } else {
977
- cacheableNodes[i] = node;
1068
+ // eslint-disable-next-line no-unused-vars
1069
+ let {
1070
+ result: _,
1071
+ ...newNode
1072
+ } = node;
1073
+ cacheableNodes[i] = newNode;
1074
+ } else {
1075
+ cacheableNodes[i] = node;
1076
+ }
978
1077
  }
979
- }
980
- let nodeCountsPerBlob = [];
981
- for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
982
- let nodesStartIndex = i * this.graph.nodesPerBlob;
983
- let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
984
- nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
985
- if (!this.graph.hasCachedRequestChunk(i)) {
986
- // We assume the request graph nodes are immutable and won't change
987
- let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
988
- queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
989
- // Succeeded in writing to disk, save that we have completed this chunk
990
- this.graph.setCachedRequestChunk(i);
991
- }));
1078
+ let nodeCountsPerBlob = [];
1079
+ for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
1080
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1081
+ let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
1082
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1083
+ if (!this.graph.hasCachedRequestChunk(i)) {
1084
+ // We assume the request graph nodes are immutable and won't change
1085
+ let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1086
+ queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
1087
+ // Succeeded in writing to disk, save that we have completed this chunk
1088
+ this.graph.setCachedRequestChunk(i);
1089
+ }));
1090
+ }
992
1091
  }
993
- }
994
- try {
995
1092
  await queue.run();
996
1093
 
997
1094
  // Set the request graph after the queue is flushed to avoid writing an invalid state
@@ -1000,12 +1097,23 @@ class RequestTracker {
1000
1097
  nodeCountsPerBlob,
1001
1098
  nodes: undefined
1002
1099
  });
1100
+ await runCacheImprovements(() => serialiseAndSet(`${cacheKey}/cache_metadata`, {
1101
+ version: _constants.ATLASPACK_VERSION,
1102
+ entries: this.options.entries,
1103
+ mode: this.options.mode,
1104
+ shouldBuildLazily: this.options.shouldBuildLazily,
1105
+ watchBackend: this.options.watchBackend
1106
+ }), () => Promise.resolve());
1003
1107
  let opts = getWatcherOptions(this.options);
1004
- let snapshotPath = _path2().default.join(this.options.cacheDir, `snapshot-${cacheKey}` + '.txt');
1108
+ let snapshotPath = _path2().default.join(this.options.cacheDir, snapshotKey + '.txt');
1005
1109
  await this.options.outputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts);
1006
1110
  } catch (err) {
1007
1111
  // If we have aborted, ignore the error and continue
1008
1112
  if (!(signal !== null && signal !== void 0 && signal.aborted)) throw err;
1113
+ } finally {
1114
+ await runCacheImprovements(async cache => {
1115
+ await cache.getNativeRef().commitWriteTransaction();
1116
+ }, () => Promise.resolve());
1009
1117
  }
1010
1118
  (0, _ReporterRunner.report)({
1011
1119
  type: 'cache',
@@ -1043,17 +1151,30 @@ function getWatcherOptions({
1043
1151
  };
1044
1152
  }
1045
1153
  function getCacheKey(options) {
1154
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1155
+ const hash = (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1156
+ return `RequestTracker/${_constants.ATLASPACK_VERSION}/${hash}`;
1157
+ }
1046
1158
  return (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1047
1159
  }
1048
1160
  function getRequestGraphNodeKey(index, cacheKey) {
1161
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1162
+ return `${cacheKey}/RequestGraph/nodes/${index}`;
1163
+ }
1049
1164
  return `requestGraph-nodes-${index}-${cacheKey}`;
1050
1165
  }
1051
1166
  async function readAndDeserializeRequestGraph(cache, requestGraphKey, cacheKey) {
1052
1167
  let bufferLength = 0;
1053
1168
  const getAndDeserialize = async key => {
1054
- let buffer = await cache.getLargeBlob(key);
1055
- bufferLength += Buffer.byteLength(buffer);
1056
- return (0, _buildCache().deserialize)(buffer);
1169
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1170
+ const buffer = await cache.getBlob(key);
1171
+ bufferLength += Buffer.byteLength(buffer);
1172
+ return (0, _buildCache().deserialize)(buffer);
1173
+ } else {
1174
+ const buffer = await cache.getLargeBlob(key);
1175
+ bufferLength += Buffer.byteLength(buffer);
1176
+ return (0, _buildCache().deserialize)(buffer);
1177
+ }
1057
1178
  };
1058
1179
  let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
1059
1180
  let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(async (nodesCount, i) => {
@@ -1075,19 +1196,33 @@ async function loadRequestGraph(options) {
1075
1196
  return new RequestGraph();
1076
1197
  }
1077
1198
  let cacheKey = getCacheKey(options);
1078
- let requestGraphKey = `requestGraph-${cacheKey}`;
1199
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1079
1200
  let timeout;
1080
- const snapshotKey = `snapshot-${cacheKey}`;
1201
+ const snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
1081
1202
  const snapshotPath = _path2().default.join(options.cacheDir, snapshotKey + '.txt');
1203
+ const commonMeta = {
1204
+ cacheKey,
1205
+ snapshotKey,
1206
+ cacheKeyOptions: {
1207
+ version: _constants.ATLASPACK_VERSION,
1208
+ entries: options.entries,
1209
+ mode: options.mode,
1210
+ shouldBuildLazily: options.shouldBuildLazily,
1211
+ watchBackend: options.watchBackend
1212
+ }
1213
+ };
1082
1214
  _logger().default.verbose({
1083
1215
  origin: '@atlaspack/core',
1084
1216
  message: 'Loading request graph',
1085
1217
  meta: {
1086
- cacheKey,
1087
- snapshotKey
1218
+ ...commonMeta
1088
1219
  }
1089
1220
  });
1090
- if (await options.cache.hasLargeBlob(requestGraphKey)) {
1221
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1222
+ await (0, _EnvironmentManager.loadEnvironmentsFromCache)(options.cache);
1223
+ }
1224
+ const hasRequestGraphInCache = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? await options.cache.has(requestGraphKey) : await options.cache.hasLargeBlob(requestGraphKey);
1225
+ if (hasRequestGraphInCache) {
1091
1226
  try {
1092
1227
  let {
1093
1228
  requestGraph
@@ -1106,16 +1241,29 @@ async function loadRequestGraph(options) {
1106
1241
  origin: '@atlaspack/core',
1107
1242
  message: `File system event count: ${events.length}`,
1108
1243
  meta: {
1244
+ ...commonMeta,
1109
1245
  trackableEvent: 'watcher_events_count',
1110
1246
  watcherEventCount: events.length,
1111
1247
  duration: Date.now() - startTime
1112
1248
  }
1113
1249
  });
1114
- requestGraph.invalidateUnpredictableNodes();
1115
- requestGraph.invalidateOnBuildNodes();
1116
- requestGraph.invalidateEnvNodes(options.env);
1117
- requestGraph.invalidateOptionNodes(options);
1118
- await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
1250
+ if ((0, _featureFlags().getFeatureFlag)('verboseRequestInvalidationStats')) {
1251
+ const invalidationStats = await invalidateRequestGraph(requestGraph, options, events);
1252
+ _logger().default.verbose({
1253
+ origin: '@atlaspack/core',
1254
+ message: 'Request track loaded from cache',
1255
+ meta: {
1256
+ ...commonMeta,
1257
+ trackableEvent: 'request_tracker_cache_key_hit',
1258
+ invalidationStats
1259
+ }
1260
+ });
1261
+ } else {
1262
+ requestGraph.invalidateUnpredictableNodes();
1263
+ requestGraph.invalidateOnBuildNodes();
1264
+ requestGraph.invalidateEnvNodes(options.env);
1265
+ requestGraph.invalidateOptionNodes(options);
1266
+ }
1119
1267
  return requestGraph;
1120
1268
  } catch (e) {
1121
1269
  // Prevent logging fs events took too long warning
@@ -1130,12 +1278,104 @@ async function loadRequestGraph(options) {
1130
1278
  origin: '@atlaspack/core',
1131
1279
  message: 'Cache entry for request tracker was not found, initializing a clean cache.',
1132
1280
  meta: {
1133
- cacheKey,
1134
- snapshotKey
1281
+ ...commonMeta,
1282
+ trackableEvent: 'request_tracker_cache_key_miss'
1135
1283
  }
1136
1284
  });
1137
1285
  return new RequestGraph();
1138
1286
  }
1287
+
1288
+ /**
1289
+ * A wrapper around an invalidation type / method
1290
+ */
1291
+
1292
+ /**
1293
+ * Details about an invalidation.
1294
+ *
1295
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
1296
+ * by path.
1297
+ *
1298
+ * If this is a env or option invalidation, this key will contain the list of changed environment
1299
+ * variables or options.
1300
+ */
1301
+
1302
+ /**
1303
+ * Number of invalidations for a given file-system event.
1304
+ */
1305
+
1306
+ /**
1307
+ * Information about a certain cache invalidation type.
1308
+ */
1309
+
1310
+ /**
1311
+ * Respond to unpredictable, build, environment changes, option changes and file-system events
1312
+ * invalidating RequestGraph nodes.
1313
+ *
1314
+ * Returns the count of nodes invalidated by each invalidation type.
1315
+ */
1316
+ async function invalidateRequestGraph(requestGraph, options, events) {
1317
+ const invalidationFns = [{
1318
+ key: 'unpredictable',
1319
+ fn: () => requestGraph.invalidateUnpredictableNodes()
1320
+ }, {
1321
+ key: 'onBuild',
1322
+ fn: () => requestGraph.invalidateOnBuildNodes()
1323
+ }, {
1324
+ key: 'env',
1325
+ fn: () => requestGraph.invalidateEnvNodes(options.env)
1326
+ }, {
1327
+ key: 'option',
1328
+ fn: () => requestGraph.invalidateOptionNodes(options)
1329
+ }, {
1330
+ key: 'fsEvents',
1331
+ fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events)
1332
+ }];
1333
+ const invalidations = [];
1334
+ for (const invalidation of invalidationFns) {
1335
+ invalidations.push(await runInvalidation(requestGraph, invalidation));
1336
+ }
1337
+ const invalidatedCount = invalidations.reduce((acc, invalidation) => acc + invalidation.count, 0);
1338
+ const requestCount = requestGraph.nodes.reduce((acc, node) => acc + ((node === null || node === void 0 ? void 0 : node.type) === REQUEST ? 1 : 0), 0);
1339
+ const nodeCount = requestGraph.nodes.length;
1340
+ return {
1341
+ invalidations,
1342
+ nodeCount,
1343
+ requestCount,
1344
+ invalidatedCount,
1345
+ nodeInvalidationRatio: invalidatedCount / nodeCount,
1346
+ requestInvalidationRatio: invalidatedCount / requestCount
1347
+ };
1348
+ }
1349
+ /**
1350
+ * Invalidate the request graph based on file-system events.
1351
+ *
1352
+ * Returns statistics about the invalidations.
1353
+ */
1354
+ async function invalidateRequestGraphFSEvents(requestGraph, options, events) {
1355
+ const {
1356
+ invalidationsByPath
1357
+ } = await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
1358
+ const biggestInvalidations = getBiggestFSEventsInvalidations(invalidationsByPath);
1359
+ return {
1360
+ biggestInvalidations
1361
+ };
1362
+ }
1363
+ /**
1364
+ * Runs an invalidation function and reports metrics.
1365
+ */
1366
+ async function runInvalidation(requestGraph, invalidationFn) {
1367
+ const start = _perf_hooks().performance.now();
1368
+ const startInvalidationCount = requestGraph.getInvalidNodeCount();
1369
+ const result = await invalidationFn.fn();
1370
+ const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
1371
+ const duration = _perf_hooks().performance.now() - start;
1372
+ return {
1373
+ key: invalidationFn.key,
1374
+ count,
1375
+ detail: result ?? null,
1376
+ duration
1377
+ };
1378
+ }
1139
1379
  function logErrorOnBailout(options, snapshotPath, e) {
1140
1380
  if (e.message && e.message.includes('invalid clockspec')) {
1141
1381
  const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
@@ -1175,4 +1415,19 @@ function cleanUpOrphans(graph) {
1175
1415
  }
1176
1416
  });
1177
1417
  return removedNodeIds;
1418
+ }
1419
+
1420
+ /**
1421
+ * Returns paths that invalidated the most nodes
1422
+ */
1423
+ function getBiggestFSEventsInvalidations(invalidationsByPath, limit = 10) {
1424
+ const invalidations = [];
1425
+ for (const [path, count] of invalidationsByPath) {
1426
+ invalidations.push({
1427
+ path,
1428
+ count
1429
+ });
1430
+ }
1431
+ invalidations.sort((a, b) => b.count - a.count);
1432
+ return invalidations.slice(0, limit);
1178
1433
  }