@atlaspack/core 2.16.2-canary.13 → 2.16.2-canary.130

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 (95) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/index.d.ts +4 -0
  3. package/lib/AssetGraph.js +27 -7
  4. package/lib/Atlaspack.js +35 -27
  5. package/lib/AtlaspackConfig.schema.js +7 -1
  6. package/lib/BundleGraph.js +8 -5
  7. package/lib/Dependency.js +6 -2
  8. package/lib/Environment.js +5 -3
  9. package/lib/EnvironmentManager.js +137 -0
  10. package/lib/InternalConfig.js +3 -2
  11. package/lib/PackagerRunner.js +54 -16
  12. package/lib/RequestTracker.js +345 -132
  13. package/lib/SymbolPropagation.js +14 -0
  14. package/lib/Transformation.js +2 -2
  15. package/lib/UncommittedAsset.js +20 -2
  16. package/lib/applyRuntimes.js +2 -1
  17. package/lib/assetUtils.js +2 -1
  18. package/lib/atlaspack-v3/AtlaspackV3.js +16 -3
  19. package/lib/atlaspack-v3/worker/compat/environment.js +2 -2
  20. package/lib/atlaspack-v3/worker/compat/mutable-asset.js +6 -6
  21. package/lib/atlaspack-v3/worker/compat/plugin-config.js +5 -5
  22. package/lib/atlaspack-v3/worker/index.js +3 -0
  23. package/lib/atlaspack-v3/worker/worker.js +8 -0
  24. package/lib/dumpGraphToGraphViz.js +1 -1
  25. package/lib/index.js +29 -1
  26. package/lib/public/Asset.js +7 -9
  27. package/lib/public/Bundle.js +12 -13
  28. package/lib/public/BundleGraph.js +3 -2
  29. package/lib/public/BundleGroup.js +2 -3
  30. package/lib/public/Config.js +95 -8
  31. package/lib/public/Dependency.js +4 -4
  32. package/lib/public/Environment.js +2 -3
  33. package/lib/public/MutableBundleGraph.js +5 -4
  34. package/lib/public/PluginOptions.js +1 -2
  35. package/lib/public/Target.js +4 -4
  36. package/lib/requests/AssetGraphRequest.js +13 -1
  37. package/lib/requests/AssetGraphRequestRust.js +17 -2
  38. package/lib/requests/AssetRequest.js +2 -1
  39. package/lib/requests/BundleGraphRequest.js +13 -1
  40. package/lib/requests/ConfigRequest.js +27 -4
  41. package/lib/requests/DevDepRequest.js +11 -1
  42. package/lib/requests/PathRequest.js +10 -0
  43. package/lib/requests/TargetRequest.js +18 -16
  44. package/lib/requests/WriteBundleRequest.js +15 -3
  45. package/lib/requests/WriteBundlesRequest.js +22 -1
  46. package/lib/resolveOptions.js +7 -4
  47. package/lib/worker.js +18 -1
  48. package/package.json +18 -25
  49. package/src/AssetGraph.js +30 -7
  50. package/src/Atlaspack.js +40 -23
  51. package/src/BundleGraph.js +13 -8
  52. package/src/Dependency.js +13 -5
  53. package/src/Environment.js +9 -6
  54. package/src/EnvironmentManager.js +145 -0
  55. package/src/InternalConfig.js +6 -5
  56. package/src/PackagerRunner.js +72 -20
  57. package/src/RequestTracker.js +526 -157
  58. package/src/SymbolPropagation.js +13 -1
  59. package/src/UncommittedAsset.js +23 -3
  60. package/src/applyRuntimes.js +6 -1
  61. package/src/assetUtils.js +4 -3
  62. package/src/atlaspack-v3/AtlaspackV3.js +24 -3
  63. package/src/atlaspack-v3/worker/compat/plugin-config.js +9 -5
  64. package/src/atlaspack-v3/worker/index.js +2 -1
  65. package/src/atlaspack-v3/worker/worker.js +7 -0
  66. package/src/index.js +5 -1
  67. package/src/public/Asset.js +13 -6
  68. package/src/public/Bundle.js +12 -11
  69. package/src/public/BundleGraph.js +10 -2
  70. package/src/public/BundleGroup.js +2 -2
  71. package/src/public/Config.js +132 -18
  72. package/src/public/Dependency.js +4 -3
  73. package/src/public/Environment.js +2 -2
  74. package/src/public/MutableBundleGraph.js +8 -5
  75. package/src/public/PluginOptions.js +1 -1
  76. package/src/public/Target.js +4 -3
  77. package/src/requests/AssetGraphRequest.js +13 -3
  78. package/src/requests/AssetGraphRequestRust.js +14 -2
  79. package/src/requests/AssetRequest.js +2 -1
  80. package/src/requests/BundleGraphRequest.js +13 -3
  81. package/src/requests/ConfigRequest.js +33 -9
  82. package/src/requests/DevDepRequest.js +22 -9
  83. package/src/requests/PathRequest.js +4 -0
  84. package/src/requests/TargetRequest.js +19 -25
  85. package/src/requests/WriteBundleRequest.js +14 -8
  86. package/src/requests/WriteBundlesRequest.js +31 -3
  87. package/src/resolveOptions.js +4 -2
  88. package/src/types.js +10 -7
  89. package/test/Environment.test.js +43 -34
  90. package/test/EnvironmentManager.test.js +192 -0
  91. package/test/PublicEnvironment.test.js +10 -7
  92. package/test/RequestTracker.test.js +124 -29
  93. package/test/public/Config.test.js +108 -0
  94. package/test/requests/ConfigRequest.test.js +199 -7
  95. 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,9 @@ class RequestGraph extends _graph().ContentGraph {
671
721
  }
672
722
  }
673
723
  }
724
+ const invalidationsAfter = this.getInvalidNodeCount();
725
+ const invalidationsForEvent = invalidationsAfter - invalidationsBefore;
726
+ invalidationsByPath.set(_path, (invalidationsByPath.get(_path) ?? 0) + invalidationsForEvent);
674
727
  }
675
728
  if ((0, _featureFlags().getFeatureFlag)('fixQuadraticCacheInvalidation')) {
676
729
  cleanUpOrphans(this);
@@ -688,7 +741,10 @@ class RequestGraph extends _graph().ContentGraph {
688
741
  numberOfInvalidatedNodes: invalidatedNodes.size
689
742
  }
690
743
  });
691
- return didInvalidate && this.invalidNodeIds.size > 0;
744
+ return {
745
+ didInvalidate,
746
+ invalidationsByPath
747
+ };
692
748
  }
693
749
  hasCachedRequestChunk(index) {
694
750
  return this.cachedRequestChunks.has(index);
@@ -699,6 +755,13 @@ class RequestGraph extends _graph().ContentGraph {
699
755
  removeCachedRequestChunkForNode(nodeId) {
700
756
  this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
701
757
  }
758
+
759
+ /**
760
+ * Returns the number of invalidated nodes in the graph.
761
+ */
762
+ getInvalidNodeCount() {
763
+ return this.invalidNodeIds.size;
764
+ }
702
765
  }
703
766
  exports.RequestGraph = RequestGraph;
704
767
  class RequestTracker {
@@ -714,11 +777,6 @@ class RequestTracker {
714
777
  this.options = options;
715
778
  this.rustAtlaspack = rustAtlaspack;
716
779
  }
717
-
718
- // TODO: refactor (abortcontroller should be created by RequestTracker)
719
- setSignal(signal) {
720
- this.signal = signal;
721
- }
722
780
  startRequest(request) {
723
781
  let didPreviouslyExist = this.graph.hasContentKey(request.id);
724
782
  let requestNodeId;
@@ -857,7 +915,6 @@ class RequestTracker {
857
915
  options: this.options,
858
916
  rustAtlaspack: this.rustAtlaspack
859
917
  });
860
- (0, _utils2.assertSignalNotAborted)(this.signal);
861
918
  this.completeRequest(requestNodeId);
862
919
  deferred.resolve(true);
863
920
  return result;
@@ -895,40 +952,41 @@ class RequestTracker {
895
952
  }
896
953
  createAPI(requestId, previousInvalidations) {
897
954
  let subRequestContentKeys = new Set();
898
- return {
899
- api: {
900
- invalidateOnFileCreate: input => this.graph.invalidateOnFileCreate(requestId, input),
901
- invalidateOnConfigKeyChange: (filePath, configKey, contentHash) => this.graph.invalidateOnConfigKeyChange(requestId, filePath, configKey, contentHash),
902
- invalidateOnFileDelete: filePath => this.graph.invalidateOnFileDelete(requestId, filePath),
903
- invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath),
904
- invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
905
- invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
906
- invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
907
- invalidateOnOptionChange: option => this.graph.invalidateOnOptionChange(requestId, option, this.options[option]),
908
- getInvalidations: () => previousInvalidations,
909
- storeResult: (result, cacheKey) => {
910
- this.storeResult(requestId, result, cacheKey);
911
- },
912
- getSubRequests: () => this.graph.getSubRequests(requestId),
913
- getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
914
- getPreviousResult: ifMatch => {
915
- var _this$graph$getNode;
916
- let contentKey = (0, _nullthrows().default)((_this$graph$getNode = this.graph.getNode(requestId)) === null || _this$graph$getNode === void 0 ? void 0 : _this$graph$getNode.id);
917
- return this.getRequestResult(contentKey, ifMatch);
918
- },
919
- getRequestResult: id => this.getRequestResult(id),
920
- canSkipSubrequest: contentKey => {
921
- if (this.graph.hasContentKey(contentKey) && this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
922
- subRequestContentKeys.add(contentKey);
923
- return true;
924
- }
925
- return false;
926
- },
927
- runRequest: (subRequest, opts) => {
928
- subRequestContentKeys.add(subRequest.id);
929
- return this.runRequest(subRequest, opts);
955
+ let api = {
956
+ invalidateOnFileCreate: input => this.graph.invalidateOnFileCreate(requestId, input),
957
+ invalidateOnConfigKeyChange: (filePath, configKey, contentHash) => this.graph.invalidateOnConfigKeyChange(requestId, filePath, configKey, contentHash),
958
+ invalidateOnFileDelete: filePath => this.graph.invalidateOnFileDelete(requestId, filePath),
959
+ invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath),
960
+ invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
961
+ invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
962
+ invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
963
+ invalidateOnOptionChange: option => this.graph.invalidateOnOptionChange(requestId, option, this.options[option]),
964
+ getInvalidations: () => previousInvalidations,
965
+ storeResult: (result, cacheKey) => {
966
+ this.storeResult(requestId, result, cacheKey);
967
+ },
968
+ getSubRequests: () => this.graph.getSubRequests(requestId),
969
+ getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
970
+ getPreviousResult: ifMatch => {
971
+ var _this$graph$getNode;
972
+ let contentKey = (0, _nullthrows().default)((_this$graph$getNode = this.graph.getNode(requestId)) === null || _this$graph$getNode === void 0 ? void 0 : _this$graph$getNode.id);
973
+ return this.getRequestResult(contentKey, ifMatch);
974
+ },
975
+ getRequestResult: id => this.getRequestResult(id),
976
+ canSkipSubrequest: contentKey => {
977
+ if (this.graph.hasContentKey(contentKey) && this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
978
+ subRequestContentKeys.add(contentKey);
979
+ return true;
930
980
  }
981
+ return false;
931
982
  },
983
+ runRequest: (subRequest, opts) => {
984
+ subRequestContentKeys.add(subRequest.id);
985
+ return this.runRequest(subRequest, opts);
986
+ }
987
+ };
988
+ return {
989
+ api,
932
990
  subRequestContentKeys
933
991
  };
934
992
  }
@@ -944,84 +1002,88 @@ class RequestTracker {
944
1002
  return result;
945
1003
  }
946
1004
  }
947
- await runCacheImprovements(async cache => {
948
- await cache.getNativeRef().startWriteTransaction();
949
- }, () => Promise.resolve());
950
1005
  let cacheKey = getCacheKey(this.options);
951
- let requestGraphKey = `requestGraph-${cacheKey}`;
1006
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1007
+ let snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
952
1008
  if (this.options.shouldDisableCache) {
953
1009
  return;
954
1010
  }
955
1011
  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;
1012
+ await runCacheImprovements(async cache => {
1013
+ await cache.getNativeRef().startWriteTransaction();
1014
+ }, () => Promise.resolve());
1015
+ try {
981
1016
  (0, _ReporterRunner.report)({
982
1017
  type: 'cache',
983
- phase: 'write',
1018
+ phase: 'start',
984
1019
  total,
985
1020
  size: this.graph.nodes.length
986
1021
  });
987
- };
988
- let queue = new (_utils().PromiseQueue)({
989
- maxConcurrent: 32
990
- });
1022
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1023
+ await (0, _EnvironmentManager.writeEnvironmentsToCache)(options.cache);
1024
+ }
1025
+ let serialisedGraph = this.graph.serialize();
991
1026
 
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));
1027
+ // Delete an existing request graph cache, to prevent invalid states
1028
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1029
+ const serialiseAndSet = async (key, contents) => {
1030
+ if (signal !== null && signal !== void 0 && signal.aborted) {
1031
+ throw new Error('Serialization was aborted');
1032
+ }
1033
+ await runCacheImprovements(cache => {
1034
+ (0, _logger().instrument)(`RequestTracker::writeToCache::cache.put(${key})`, () => {
1035
+ cache.getNativeRef().putNoConfirm(key, (0, _buildCache().serialize)(contents));
1036
+ });
1037
+ return Promise.resolve();
1038
+ }, async () => {
1039
+ await this.options.cache.setLargeBlob(key, (0, _buildCache().serialize)(contents), signal ? {
1040
+ signal: signal
1041
+ } : undefined);
1042
+ });
1043
+ total += 1;
1044
+ (0, _ReporterRunner.report)({
1045
+ type: 'cache',
1046
+ phase: 'write',
1047
+ total,
1048
+ size: this.graph.nodes.length
1049
+ });
1050
+ };
1051
+ let queue = new (_utils().PromiseQueue)({
1052
+ maxConcurrent: 32
1053
+ });
999
1054
 
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;
1055
+ // Preallocating a sparse array is faster than pushing when N is high enough
1056
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1057
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1058
+ let node = serialisedGraph.nodes[i];
1059
+ let resultCacheKey = node === null || node === void 0 ? void 0 : node.resultCacheKey;
1060
+ if ((node === null || node === void 0 ? void 0 : node.type) === REQUEST && resultCacheKey != null && (node === null || node === void 0 ? void 0 : node.result) != null) {
1061
+ queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1062
+
1063
+ // eslint-disable-next-line no-unused-vars
1064
+ let {
1065
+ result: _,
1066
+ ...newNode
1067
+ } = node;
1068
+ cacheableNodes[i] = newNode;
1069
+ } else {
1070
+ cacheableNodes[i] = node;
1071
+ }
1008
1072
  }
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
- }));
1073
+ let nodeCountsPerBlob = [];
1074
+ for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
1075
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1076
+ let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
1077
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1078
+ if (!this.graph.hasCachedRequestChunk(i)) {
1079
+ // We assume the request graph nodes are immutable and won't change
1080
+ let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1081
+ queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
1082
+ // Succeeded in writing to disk, save that we have completed this chunk
1083
+ this.graph.setCachedRequestChunk(i);
1084
+ }));
1085
+ }
1022
1086
  }
1023
- }
1024
- try {
1025
1087
  await queue.run();
1026
1088
 
1027
1089
  // Set the request graph after the queue is flushed to avoid writing an invalid state
@@ -1030,7 +1092,7 @@ class RequestTracker {
1030
1092
  nodeCountsPerBlob,
1031
1093
  nodes: undefined
1032
1094
  });
1033
- await runCacheImprovements(() => serialiseAndSet(`request_tracker:cache_metadata:${cacheKey}`, {
1095
+ await runCacheImprovements(() => serialiseAndSet(`${cacheKey}/cache_metadata`, {
1034
1096
  version: _constants.ATLASPACK_VERSION,
1035
1097
  entries: this.options.entries,
1036
1098
  mode: this.options.mode,
@@ -1038,15 +1100,16 @@ class RequestTracker {
1038
1100
  watchBackend: this.options.watchBackend
1039
1101
  }), () => Promise.resolve());
1040
1102
  let opts = getWatcherOptions(this.options);
1041
- let snapshotPath = _path2().default.join(this.options.cacheDir, `snapshot-${cacheKey}` + '.txt');
1103
+ let snapshotPath = _path2().default.join(this.options.cacheDir, snapshotKey + '.txt');
1042
1104
  await this.options.outputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts);
1043
1105
  } catch (err) {
1044
1106
  // If we have aborted, ignore the error and continue
1045
1107
  if (!(signal !== null && signal !== void 0 && signal.aborted)) throw err;
1108
+ } finally {
1109
+ await runCacheImprovements(async cache => {
1110
+ await cache.getNativeRef().commitWriteTransaction();
1111
+ }, () => Promise.resolve());
1046
1112
  }
1047
- await runCacheImprovements(async cache => {
1048
- await cache.getNativeRef().commitWriteTransaction();
1049
- }, () => Promise.resolve());
1050
1113
  (0, _ReporterRunner.report)({
1051
1114
  type: 'cache',
1052
1115
  phase: 'end',
@@ -1075,7 +1138,8 @@ function getWatcherOptions({
1075
1138
  watchDir,
1076
1139
  watchBackend
1077
1140
  }) {
1078
- const uniqueDirs = [...new Set([...watchIgnore, ...['.git', '.hg'], cacheDir])];
1141
+ const vcsDirs = ['.git', '.hg'];
1142
+ const uniqueDirs = [...new Set([...watchIgnore, ...vcsDirs, cacheDir])];
1079
1143
  const ignore = uniqueDirs.map(dir => _path2().default.resolve(watchDir, dir));
1080
1144
  return {
1081
1145
  ignore,
@@ -1083,17 +1147,30 @@ function getWatcherOptions({
1083
1147
  };
1084
1148
  }
1085
1149
  function getCacheKey(options) {
1150
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1151
+ const hash = (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1152
+ return `RequestTracker/${_constants.ATLASPACK_VERSION}/${hash}`;
1153
+ }
1086
1154
  return (0, _rust().hashString)(`${_constants.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
1087
1155
  }
1088
1156
  function getRequestGraphNodeKey(index, cacheKey) {
1157
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1158
+ return `${cacheKey}/RequestGraph/nodes/${index}`;
1159
+ }
1089
1160
  return `requestGraph-nodes-${index}-${cacheKey}`;
1090
1161
  }
1091
1162
  async function readAndDeserializeRequestGraph(cache, requestGraphKey, cacheKey) {
1092
1163
  let bufferLength = 0;
1093
1164
  const getAndDeserialize = async key => {
1094
- let buffer = await cache.getLargeBlob(key);
1095
- bufferLength += Buffer.byteLength(buffer);
1096
- return (0, _buildCache().deserialize)(buffer);
1165
+ if ((0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements')) {
1166
+ const buffer = await cache.getBlob(key);
1167
+ bufferLength += Buffer.byteLength(buffer);
1168
+ return (0, _buildCache().deserialize)(buffer);
1169
+ } else {
1170
+ const buffer = await cache.getLargeBlob(key);
1171
+ bufferLength += Buffer.byteLength(buffer);
1172
+ return (0, _buildCache().deserialize)(buffer);
1173
+ }
1097
1174
  };
1098
1175
  let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
1099
1176
  let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(async (nodesCount, i) => {
@@ -1115,19 +1192,33 @@ async function loadRequestGraph(options) {
1115
1192
  return new RequestGraph();
1116
1193
  }
1117
1194
  let cacheKey = getCacheKey(options);
1118
- let requestGraphKey = `requestGraph-${cacheKey}`;
1195
+ let requestGraphKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/RequestGraph` : `requestGraph-${cacheKey}`;
1119
1196
  let timeout;
1120
- const snapshotKey = `snapshot-${cacheKey}`;
1197
+ const snapshotKey = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? `${cacheKey}/snapshot` : `snapshot-${cacheKey}`;
1121
1198
  const snapshotPath = _path2().default.join(options.cacheDir, snapshotKey + '.txt');
1199
+ const commonMeta = {
1200
+ cacheKey,
1201
+ snapshotKey,
1202
+ cacheKeyOptions: {
1203
+ version: _constants.ATLASPACK_VERSION,
1204
+ entries: options.entries,
1205
+ mode: options.mode,
1206
+ shouldBuildLazily: options.shouldBuildLazily,
1207
+ watchBackend: options.watchBackend
1208
+ }
1209
+ };
1122
1210
  _logger().default.verbose({
1123
1211
  origin: '@atlaspack/core',
1124
1212
  message: 'Loading request graph',
1125
1213
  meta: {
1126
- cacheKey,
1127
- snapshotKey
1214
+ ...commonMeta
1128
1215
  }
1129
1216
  });
1130
- if (await options.cache.hasLargeBlob(requestGraphKey)) {
1217
+ if ((0, _featureFlags().getFeatureFlag)('environmentDeduplication')) {
1218
+ await (0, _EnvironmentManager.loadEnvironmentsFromCache)(options.cache);
1219
+ }
1220
+ const hasRequestGraphInCache = (0, _featureFlags().getFeatureFlag)('cachePerformanceImprovements') ? await options.cache.has(requestGraphKey) : await options.cache.hasLargeBlob(requestGraphKey);
1221
+ if (hasRequestGraphInCache) {
1131
1222
  try {
1132
1223
  let {
1133
1224
  requestGraph
@@ -1146,16 +1237,29 @@ async function loadRequestGraph(options) {
1146
1237
  origin: '@atlaspack/core',
1147
1238
  message: `File system event count: ${events.length}`,
1148
1239
  meta: {
1240
+ ...commonMeta,
1149
1241
  trackableEvent: 'watcher_events_count',
1150
1242
  watcherEventCount: events.length,
1151
1243
  duration: Date.now() - startTime
1152
1244
  }
1153
1245
  });
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);
1246
+ if ((0, _featureFlags().getFeatureFlag)('verboseRequestInvalidationStats')) {
1247
+ const invalidationStats = await invalidateRequestGraph(requestGraph, options, events);
1248
+ _logger().default.verbose({
1249
+ origin: '@atlaspack/core',
1250
+ message: 'Request track loaded from cache',
1251
+ meta: {
1252
+ ...commonMeta,
1253
+ trackableEvent: 'request_tracker_cache_key_hit',
1254
+ invalidationStats
1255
+ }
1256
+ });
1257
+ } else {
1258
+ requestGraph.invalidateUnpredictableNodes();
1259
+ requestGraph.invalidateOnBuildNodes();
1260
+ requestGraph.invalidateEnvNodes(options.env);
1261
+ requestGraph.invalidateOptionNodes(options);
1262
+ }
1159
1263
  return requestGraph;
1160
1264
  } catch (e) {
1161
1265
  // Prevent logging fs events took too long warning
@@ -1170,12 +1274,106 @@ async function loadRequestGraph(options) {
1170
1274
  origin: '@atlaspack/core',
1171
1275
  message: 'Cache entry for request tracker was not found, initializing a clean cache.',
1172
1276
  meta: {
1173
- cacheKey,
1174
- snapshotKey
1277
+ ...commonMeta,
1278
+ trackableEvent: 'request_tracker_cache_key_miss'
1175
1279
  }
1176
1280
  });
1177
1281
  return new RequestGraph();
1178
1282
  }
1283
+
1284
+ /**
1285
+ * A wrapper around an invalidation type / method
1286
+ */
1287
+
1288
+ /**
1289
+ * Details about an invalidation.
1290
+ *
1291
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
1292
+ * by path.
1293
+ *
1294
+ * If this is a env or option invalidation, this key will contain the list of changed environment
1295
+ * variables or options.
1296
+ */
1297
+
1298
+ /**
1299
+ * Number of invalidations for a given file-system event.
1300
+ */
1301
+
1302
+ /**
1303
+ * Information about a certain cache invalidation type.
1304
+ */
1305
+
1306
+ /**
1307
+ * Respond to unpredictable, build, environment changes, option changes and file-system events
1308
+ * invalidating RequestGraph nodes.
1309
+ *
1310
+ * Returns the count of nodes invalidated by each invalidation type.
1311
+ */
1312
+ async function invalidateRequestGraph(requestGraph, options, events) {
1313
+ const invalidationFns = [{
1314
+ key: 'unpredictable',
1315
+ fn: () => requestGraph.invalidateUnpredictableNodes()
1316
+ }, {
1317
+ key: 'onBuild',
1318
+ fn: () => requestGraph.invalidateOnBuildNodes()
1319
+ }, {
1320
+ key: 'env',
1321
+ fn: () => requestGraph.invalidateEnvNodes(options.env)
1322
+ }, {
1323
+ key: 'option',
1324
+ fn: () => requestGraph.invalidateOptionNodes(options)
1325
+ }, {
1326
+ key: 'fsEvents',
1327
+ fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events)
1328
+ }];
1329
+ const invalidations = [];
1330
+ for (const invalidation of invalidationFns) {
1331
+ invalidations.push(await runInvalidation(requestGraph, invalidation));
1332
+ }
1333
+ const invalidatedCount = invalidations.reduce((acc, invalidation) => acc + invalidation.count, 0);
1334
+ const requestCount = requestGraph.nodes.reduce((acc, node) => acc + ((node === null || node === void 0 ? void 0 : node.type) === REQUEST ? 1 : 0), 0);
1335
+ const nodeCount = requestGraph.nodes.length;
1336
+ const nodeInvalidationRatio = invalidatedCount / nodeCount;
1337
+ const requestInvalidationRatio = invalidatedCount / requestCount;
1338
+ return {
1339
+ invalidations,
1340
+ nodeCount,
1341
+ requestCount,
1342
+ invalidatedCount,
1343
+ nodeInvalidationRatio,
1344
+ requestInvalidationRatio
1345
+ };
1346
+ }
1347
+ /**
1348
+ * Invalidate the request graph based on file-system events.
1349
+ *
1350
+ * Returns statistics about the invalidations.
1351
+ */
1352
+ async function invalidateRequestGraphFSEvents(requestGraph, options, events) {
1353
+ const {
1354
+ invalidationsByPath
1355
+ } = await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
1356
+ const biggestInvalidations = getBiggestFSEventsInvalidations(invalidationsByPath);
1357
+ return {
1358
+ biggestInvalidations
1359
+ };
1360
+ }
1361
+ /**
1362
+ * Runs an invalidation function and reports metrics.
1363
+ */
1364
+ async function runInvalidation(requestGraph, invalidationFn) {
1365
+ const start = _perf_hooks().performance.now();
1366
+ const startInvalidationCount = requestGraph.getInvalidNodeCount();
1367
+ const result = await invalidationFn.fn();
1368
+ const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
1369
+ const duration = _perf_hooks().performance.now() - start;
1370
+ return {
1371
+ key: invalidationFn.key,
1372
+ count,
1373
+ detail: result ?? null,
1374
+ duration
1375
+ };
1376
+ }
1179
1377
  function logErrorOnBailout(options, snapshotPath, e) {
1180
1378
  if (e.message && e.message.includes('invalid clockspec')) {
1181
1379
  const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
@@ -1215,4 +1413,19 @@ function cleanUpOrphans(graph) {
1215
1413
  }
1216
1414
  });
1217
1415
  return removedNodeIds;
1416
+ }
1417
+
1418
+ /**
1419
+ * Returns paths that invalidated the most nodes
1420
+ */
1421
+ function getBiggestFSEventsInvalidations(invalidationsByPath, limit = 10) {
1422
+ const invalidations = [];
1423
+ for (const [path, count] of invalidationsByPath) {
1424
+ invalidations.push({
1425
+ path,
1426
+ count
1427
+ });
1428
+ }
1429
+ invalidations.sort((a, b) => b.count - a.count);
1430
+ return invalidations.slice(0, limit);
1218
1431
  }