@atlaspack/core 2.17.3 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/lib/AssetGraph.js +17 -6
  3. package/lib/Atlaspack.js +3 -1
  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 +191 -89
  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 +4 -2
  30. package/package.json +13 -13
  31. package/src/AssetGraph.js +12 -6
  32. package/src/Atlaspack.js +5 -4
  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 +330 -146
  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 +4 -2
  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
@@ -30,15 +30,15 @@ import nullthrows from 'nullthrows';
30
30
 
31
31
  import {
32
32
  ATLASPACK_VERSION,
33
- VALID,
34
- INITIAL_BUILD,
35
33
  FILE_CREATE,
36
- FILE_UPDATE,
37
34
  FILE_DELETE,
35
+ FILE_UPDATE,
38
36
  ENV_CHANGE,
37
+ ERROR,
38
+ INITIAL_BUILD,
39
39
  OPTION_CHANGE,
40
40
  STARTUP,
41
- ERROR,
41
+ VALID,
42
42
  } from './constants';
43
43
  import type {AtlaspackV3} from './atlaspack-v3/AtlaspackV3';
44
44
  import {
@@ -69,6 +69,12 @@ import type {
69
69
  InternalGlob,
70
70
  } from './types';
71
71
  import {BuildAbortError, assertSignalNotAborted, hashFromOption} from './utils';
72
+ import {performance} from 'perf_hooks';
73
+
74
+ import {
75
+ loadEnvironmentsFromCache,
76
+ writeEnvironmentsToCache,
77
+ } from './EnvironmentManager';
72
78
 
73
79
  export const requestGraphEdgeTypes = {
74
80
  subrequest: 2,
@@ -143,7 +149,7 @@ type OptionNode = {|
143
149
  type ConfigKeyNode = {|
144
150
  id: ContentKey,
145
151
  +type: typeof CONFIG_KEY,
146
- configKey: string,
152
+ configKey: string[],
147
153
  contentHash: string,
148
154
  |};
149
155
 
@@ -215,7 +221,7 @@ export type RunAPI<TResult: RequestResult> = {|
215
221
  invalidateOnFileUpdate: (ProjectPath) => void,
216
222
  invalidateOnConfigKeyChange: (
217
223
  filePath: ProjectPath,
218
- configKey: string,
224
+ configKey: string[],
219
225
  contentHash: string,
220
226
  ) => void,
221
227
  invalidateOnStartup: () => void,
@@ -282,10 +288,12 @@ const nodeFromOption = (option: string, value: mixed): RequestGraphNode => ({
282
288
 
283
289
  const nodeFromConfigKey = (
284
290
  fileName: ProjectPath,
285
- configKey: string,
291
+ configKey: string[],
286
292
  contentHash: string,
287
293
  ): RequestGraphNode => ({
288
- id: `config_key:${fromProjectPathRelative(fileName)}:${configKey}`,
294
+ id: `config_key:${fromProjectPathRelative(fileName)}:${JSON.stringify(
295
+ configKey,
296
+ )}`,
289
297
  type: CONFIG_KEY,
290
298
  configKey,
291
299
  contentHash,
@@ -526,7 +534,7 @@ export class RequestGraph extends ContentGraph<
526
534
  invalidateOnConfigKeyChange(
527
535
  requestNodeId: NodeId,
528
536
  filePath: ProjectPath,
529
- configKey: string,
537
+ configKey: string[],
530
538
  contentHash: string,
531
539
  ) {
532
540
  let configKeyNodeId = this.addNode(
@@ -713,8 +721,8 @@ export class RequestGraph extends ContentGraph<
713
721
  env: string,
714
722
  value: string | void,
715
723
  ) {
716
- let envNode = nodeFromEnv(env, value);
717
- let envNodeId = this.addNode(envNode);
724
+ const envNode = nodeFromEnv(env, value);
725
+ const envNodeId = this.addNode(envNode);
718
726
 
719
727
  if (
720
728
  !this.hasEdge(
@@ -934,7 +942,10 @@ export class RequestGraph extends ContentGraph<
934
942
  * True if this is the start-up (loading phase) invalidation.
935
943
  */
936
944
  isInitialBuild: boolean = false,
937
- ): Async<boolean> {
945
+ ): Promise<{|
946
+ didInvalidate: boolean,
947
+ invalidationsByPath: Map<string, number>,
948
+ |}> {
938
949
  let didInvalidate = false;
939
950
  let count = 0;
940
951
  let predictedTime = 0;
@@ -972,7 +983,10 @@ export class RequestGraph extends ContentGraph<
972
983
  return above;
973
984
  };
974
985
 
986
+ const invalidationsByPath = new Map();
975
987
  for (let {path: _path, type} of events) {
988
+ const invalidationsBefore = this.getInvalidNodeCount();
989
+
976
990
  if (
977
991
  !enableOptimization &&
978
992
  process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' &&
@@ -1018,7 +1032,10 @@ export class RequestGraph extends ContentGraph<
1018
1032
  this.invalidNodeIds.add(id);
1019
1033
  }
1020
1034
  }
1021
- return true;
1035
+ return {
1036
+ didInvalidate: true,
1037
+ invalidationsByPath: new Map(),
1038
+ };
1022
1039
  }
1023
1040
 
1024
1041
  // sometimes mac os reports update events as create events.
@@ -1099,11 +1116,22 @@ export class RequestGraph extends ContentGraph<
1099
1116
  }
1100
1117
 
1101
1118
  let configKeyNodes = this.configKeyNodes.get(_filePath);
1102
- if (configKeyNodes && (type === 'delete' || type === 'update')) {
1119
+
1120
+ // With granular invalidations we will always run this block,
1121
+ // so even if we get a create event (for whatever reason), we will still
1122
+ // try to limit invalidations from config key changes through hashing.
1123
+ //
1124
+ // Currently create events can invalidate a large number of nodes due to
1125
+ // "create above" invalidations.
1126
+ const isConfigKeyChange =
1127
+ getFeatureFlag('granularTsConfigInvalidation') ||
1128
+ type === 'delete' ||
1129
+ type === 'update';
1130
+ if (configKeyNodes && isConfigKeyChange) {
1103
1131
  for (let nodeId of configKeyNodes) {
1104
1132
  let isInvalid = type === 'delete';
1105
1133
 
1106
- if (type === 'update') {
1134
+ if (type !== 'delete') {
1107
1135
  let node = this.getNode(nodeId);
1108
1136
  invariant(node && node.type === CONFIG_KEY);
1109
1137
 
@@ -1131,6 +1159,13 @@ export class RequestGraph extends ContentGraph<
1131
1159
  }
1132
1160
  }
1133
1161
  }
1162
+
1163
+ const invalidationsAfter = this.getInvalidNodeCount();
1164
+ const invalidationsForEvent = invalidationsAfter - invalidationsBefore;
1165
+ invalidationsByPath.set(
1166
+ _path,
1167
+ (invalidationsByPath.get(_path) ?? 0) + invalidationsForEvent,
1168
+ );
1134
1169
  }
1135
1170
 
1136
1171
  if (getFeatureFlag('fixQuadraticCacheInvalidation')) {
@@ -1151,7 +1186,10 @@ export class RequestGraph extends ContentGraph<
1151
1186
  },
1152
1187
  });
1153
1188
 
1154
- return didInvalidate && this.invalidNodeIds.size > 0;
1189
+ return {
1190
+ didInvalidate,
1191
+ invalidationsByPath,
1192
+ };
1155
1193
  }
1156
1194
 
1157
1195
  hasCachedRequestChunk(index: number): boolean {
@@ -1165,6 +1203,13 @@ export class RequestGraph extends ContentGraph<
1165
1203
  removeCachedRequestChunkForNode(nodeId: number): void {
1166
1204
  this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
1167
1205
  }
1206
+
1207
+ /**
1208
+ * Returns the number of invalidated nodes in the graph.
1209
+ */
1210
+ getInvalidNodeCount(): number {
1211
+ return this.invalidNodeIds.size;
1212
+ }
1168
1213
  }
1169
1214
 
1170
1215
  export default class RequestTracker {
@@ -1288,7 +1333,13 @@ export default class RequestTracker {
1288
1333
  }
1289
1334
  }
1290
1335
 
1291
- respondToFSEvents(events: Array<Event>, threshold: number): Async<boolean> {
1336
+ respondToFSEvents(
1337
+ events: Array<Event>,
1338
+ threshold: number,
1339
+ ): Promise<{|
1340
+ didInvalidate: boolean,
1341
+ invalidationsByPath: Map<string, number>,
1342
+ |}> {
1292
1343
  return this.graph.respondToFSEvents(events, this.options, threshold);
1293
1344
  }
1294
1345
 
@@ -1499,130 +1550,143 @@ export default class RequestTracker {
1499
1550
  }
1500
1551
  }
1501
1552
 
1553
+ let cacheKey = getCacheKey(this.options);
1554
+ let requestGraphKey = getFeatureFlag('cachePerformanceImprovements')
1555
+ ? `${cacheKey}/RequestGraph`
1556
+ : `requestGraph-${cacheKey}`;
1557
+ let snapshotKey = getFeatureFlag('cachePerformanceImprovements')
1558
+ ? `${cacheKey}/snapshot`
1559
+ : `snapshot-${cacheKey}`;
1560
+
1561
+ if (this.options.shouldDisableCache) {
1562
+ return;
1563
+ }
1564
+
1565
+ let total = 0;
1502
1566
  await runCacheImprovements(
1503
1567
  async (cache) => {
1504
1568
  await cache.getNativeRef().startWriteTransaction();
1505
1569
  },
1506
1570
  () => Promise.resolve(),
1507
1571
  );
1572
+ try {
1573
+ report({
1574
+ type: 'cache',
1575
+ phase: 'start',
1576
+ total,
1577
+ size: this.graph.nodes.length,
1578
+ });
1508
1579
 
1509
- let cacheKey = getCacheKey(this.options);
1510
- let requestGraphKey = `requestGraph-${cacheKey}`;
1511
- let snapshotKey = `snapshot-${cacheKey}`;
1512
-
1513
- if (this.options.shouldDisableCache) {
1514
- return;
1515
- }
1580
+ if (getFeatureFlag('environmentDeduplication')) {
1581
+ await writeEnvironmentsToCache(options.cache);
1582
+ }
1516
1583
 
1517
- let total = 0;
1518
- report({
1519
- type: 'cache',
1520
- phase: 'start',
1521
- total,
1522
- size: this.graph.nodes.length,
1523
- });
1584
+ let serialisedGraph = this.graph.serialize();
1524
1585
 
1525
- let serialisedGraph = this.graph.serialize();
1586
+ // Delete an existing request graph cache, to prevent invalid states
1587
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1526
1588
 
1527
- // Delete an existing request graph cache, to prevent invalid states
1528
- await this.options.cache.deleteLargeBlob(requestGraphKey);
1589
+ const serialiseAndSet = async (
1590
+ key: string,
1591
+ // $FlowFixMe serialise input is any type
1592
+ contents: any,
1593
+ ): Promise<void> => {
1594
+ if (signal?.aborted) {
1595
+ throw new Error('Serialization was aborted');
1596
+ }
1529
1597
 
1530
- const serialiseAndSet = async (
1531
- key: string,
1532
- // $FlowFixMe serialise input is any type
1533
- contents: any,
1534
- ): Promise<void> => {
1535
- if (signal?.aborted) {
1536
- throw new Error('Serialization was aborted');
1537
- }
1598
+ await runCacheImprovements(
1599
+ (cache) => {
1600
+ instrument(
1601
+ `RequestTracker::writeToCache::cache.put(${key})`,
1602
+ () => {
1603
+ cache.getNativeRef().putNoConfirm(key, serialize(contents));
1604
+ },
1605
+ );
1606
+ return Promise.resolve();
1607
+ },
1608
+ async () => {
1609
+ await this.options.cache.setLargeBlob(
1610
+ key,
1611
+ serialize(contents),
1612
+ signal
1613
+ ? {
1614
+ signal: signal,
1615
+ }
1616
+ : undefined,
1617
+ );
1618
+ },
1619
+ );
1538
1620
 
1539
- await runCacheImprovements(
1540
- (cache) => {
1541
- instrument(`cache.put(${key})`, () => {
1542
- cache.getNativeRef().putNoConfirm(key, serialize(contents));
1543
- });
1544
- return Promise.resolve();
1545
- },
1546
- async () => {
1547
- await this.options.cache.setLargeBlob(
1548
- key,
1549
- serialize(contents),
1550
- signal
1551
- ? {
1552
- signal: signal,
1553
- }
1554
- : undefined,
1555
- );
1556
- },
1557
- );
1621
+ total += 1;
1558
1622
 
1559
- total += 1;
1623
+ report({
1624
+ type: 'cache',
1625
+ phase: 'write',
1626
+ total,
1627
+ size: this.graph.nodes.length,
1628
+ });
1629
+ };
1560
1630
 
1561
- report({
1562
- type: 'cache',
1563
- phase: 'write',
1564
- total,
1565
- size: this.graph.nodes.length,
1631
+ let queue = new PromiseQueue({
1632
+ maxConcurrent: 32,
1566
1633
  });
1567
- };
1568
1634
 
1569
- let queue = new PromiseQueue({
1570
- maxConcurrent: 32,
1571
- });
1572
-
1573
- // Preallocating a sparse array is faster than pushing when N is high enough
1574
- let cacheableNodes = new Array(serialisedGraph.nodes.length);
1575
- for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1576
- let node = serialisedGraph.nodes[i];
1635
+ // Preallocating a sparse array is faster than pushing when N is high enough
1636
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1637
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1638
+ let node = serialisedGraph.nodes[i];
1577
1639
 
1578
- let resultCacheKey = node?.resultCacheKey;
1579
- if (
1580
- node?.type === REQUEST &&
1581
- resultCacheKey != null &&
1582
- node?.result != null
1583
- ) {
1584
- queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1640
+ let resultCacheKey = node?.resultCacheKey;
1641
+ if (
1642
+ node?.type === REQUEST &&
1643
+ resultCacheKey != null &&
1644
+ node?.result != null
1645
+ ) {
1646
+ queue.add(() => serialiseAndSet(resultCacheKey, node.result));
1585
1647
 
1586
- // eslint-disable-next-line no-unused-vars
1587
- let {result: _, ...newNode} = node;
1588
- cacheableNodes[i] = newNode;
1589
- } else {
1590
- cacheableNodes[i] = node;
1648
+ // eslint-disable-next-line no-unused-vars
1649
+ let {result: _, ...newNode} = node;
1650
+ cacheableNodes[i] = newNode;
1651
+ } else {
1652
+ cacheableNodes[i] = node;
1653
+ }
1591
1654
  }
1592
- }
1593
1655
 
1594
- let nodeCountsPerBlob = [];
1656
+ let nodeCountsPerBlob = [];
1595
1657
 
1596
- for (
1597
- let i = 0;
1598
- i * this.graph.nodesPerBlob < cacheableNodes.length;
1599
- i += 1
1600
- ) {
1601
- let nodesStartIndex = i * this.graph.nodesPerBlob;
1602
- let nodesEndIndex = Math.min(
1603
- (i + 1) * this.graph.nodesPerBlob,
1604
- cacheableNodes.length,
1605
- );
1658
+ for (
1659
+ let i = 0;
1660
+ i * this.graph.nodesPerBlob < cacheableNodes.length;
1661
+ i += 1
1662
+ ) {
1663
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1664
+ let nodesEndIndex = Math.min(
1665
+ (i + 1) * this.graph.nodesPerBlob,
1666
+ cacheableNodes.length,
1667
+ );
1606
1668
 
1607
- nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1669
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1608
1670
 
1609
- if (!this.graph.hasCachedRequestChunk(i)) {
1610
- // We assume the request graph nodes are immutable and won't change
1611
- let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1671
+ if (!this.graph.hasCachedRequestChunk(i)) {
1672
+ // We assume the request graph nodes are immutable and won't change
1673
+ let nodesToCache = cacheableNodes.slice(
1674
+ nodesStartIndex,
1675
+ nodesEndIndex,
1676
+ );
1612
1677
 
1613
- queue.add(() =>
1614
- serialiseAndSet(
1615
- getRequestGraphNodeKey(i, cacheKey),
1616
- nodesToCache,
1617
- ).then(() => {
1618
- // Succeeded in writing to disk, save that we have completed this chunk
1619
- this.graph.setCachedRequestChunk(i);
1620
- }),
1621
- );
1678
+ queue.add(() =>
1679
+ serialiseAndSet(
1680
+ getRequestGraphNodeKey(i, cacheKey),
1681
+ nodesToCache,
1682
+ ).then(() => {
1683
+ // Succeeded in writing to disk, save that we have completed this chunk
1684
+ this.graph.setCachedRequestChunk(i);
1685
+ }),
1686
+ );
1687
+ }
1622
1688
  }
1623
- }
1624
1689
 
1625
- try {
1626
1690
  await queue.run();
1627
1691
 
1628
1692
  // Set the request graph after the queue is flushed to avoid writing an invalid state
@@ -1634,7 +1698,7 @@ export default class RequestTracker {
1634
1698
 
1635
1699
  await runCacheImprovements(
1636
1700
  () =>
1637
- serialiseAndSet(`request_tracker:cache_metadata:${cacheKey}`, {
1701
+ serialiseAndSet(`${cacheKey}/cache_metadata`, {
1638
1702
  version: ATLASPACK_VERSION,
1639
1703
  entries: this.options.entries,
1640
1704
  mode: this.options.mode,
@@ -1655,15 +1719,15 @@ export default class RequestTracker {
1655
1719
  } catch (err) {
1656
1720
  // If we have aborted, ignore the error and continue
1657
1721
  if (!signal?.aborted) throw err;
1722
+ } finally {
1723
+ await runCacheImprovements(
1724
+ async (cache) => {
1725
+ await cache.getNativeRef().commitWriteTransaction();
1726
+ },
1727
+ () => Promise.resolve(),
1728
+ );
1658
1729
  }
1659
1730
 
1660
- await runCacheImprovements(
1661
- async (cache) => {
1662
- await cache.getNativeRef().commitWriteTransaction();
1663
- },
1664
- () => Promise.resolve(),
1665
- );
1666
-
1667
1731
  report({type: 'cache', phase: 'end', total, size: this.graph.nodes.length});
1668
1732
  }
1669
1733
 
@@ -1695,6 +1759,18 @@ export function getWatcherOptions({
1695
1759
  }
1696
1760
 
1697
1761
  function getCacheKey(options) {
1762
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1763
+ const hash = hashString(
1764
+ `${ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${
1765
+ options.mode
1766
+ }:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${
1767
+ options.watchBackend ?? ''
1768
+ }`,
1769
+ );
1770
+
1771
+ return `RequestTracker/${ATLASPACK_VERSION}/${hash}`;
1772
+ }
1773
+
1698
1774
  return hashString(
1699
1775
  `${ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${
1700
1776
  options.shouldBuildLazily ? 'lazy' : 'eager'
@@ -1703,6 +1779,10 @@ function getCacheKey(options) {
1703
1779
  }
1704
1780
 
1705
1781
  function getRequestGraphNodeKey(index: number, cacheKey: string) {
1782
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1783
+ return `${cacheKey}/RequestGraph/nodes/${index}`;
1784
+ }
1785
+
1706
1786
  return `requestGraph-nodes-${index}-${cacheKey}`;
1707
1787
  }
1708
1788
 
@@ -1714,9 +1794,15 @@ export async function readAndDeserializeRequestGraph(
1714
1794
  let bufferLength = 0;
1715
1795
 
1716
1796
  const getAndDeserialize = async (key: string) => {
1717
- let buffer = await cache.getLargeBlob(key);
1718
- bufferLength += Buffer.byteLength(buffer);
1719
- return deserialize(buffer);
1797
+ if (getFeatureFlag('cachePerformanceImprovements')) {
1798
+ const buffer = await cache.getBlob(key);
1799
+ bufferLength += Buffer.byteLength(buffer);
1800
+ return deserialize(buffer);
1801
+ } else {
1802
+ const buffer = await cache.getLargeBlob(key);
1803
+ bufferLength += Buffer.byteLength(buffer);
1804
+ return deserialize(buffer);
1805
+ }
1720
1806
  };
1721
1807
 
1722
1808
  let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
@@ -1749,9 +1835,14 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1749
1835
  }
1750
1836
 
1751
1837
  let cacheKey = getCacheKey(options);
1752
- let requestGraphKey = `requestGraph-${cacheKey}`;
1838
+ let requestGraphKey = getFeatureFlag('cachePerformanceImprovements')
1839
+ ? `${cacheKey}/RequestGraph`
1840
+ : `requestGraph-${cacheKey}`;
1841
+
1753
1842
  let timeout;
1754
- const snapshotKey = `snapshot-${cacheKey}`;
1843
+ const snapshotKey = getFeatureFlag('cachePerformanceImprovements')
1844
+ ? `${cacheKey}/snapshot`
1845
+ : `snapshot-${cacheKey}`;
1755
1846
  const snapshotPath = path.join(options.cacheDir, snapshotKey + '.txt');
1756
1847
 
1757
1848
  const commonMeta = {
@@ -1774,7 +1865,15 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1774
1865
  },
1775
1866
  });
1776
1867
 
1777
- if (await options.cache.hasLargeBlob(requestGraphKey)) {
1868
+ if (getFeatureFlag('environmentDeduplication')) {
1869
+ await loadEnvironmentsFromCache(options.cache);
1870
+ }
1871
+
1872
+ const hasRequestGraphInCache = getFeatureFlag('cachePerformanceImprovements')
1873
+ ? await options.cache.has(requestGraphKey)
1874
+ : await options.cache.hasLargeBlob(requestGraphKey);
1875
+
1876
+ if (hasRequestGraphInCache) {
1778
1877
  try {
1779
1878
  let {requestGraph} = await readAndDeserializeRequestGraph(
1780
1879
  options.cache,
@@ -1863,7 +1962,11 @@ async function loadRequestGraph(options): Async<RequestGraph> {
1863
1962
  */
1864
1963
  type InvalidationFn = {|
1865
1964
  key: string,
1866
- fn: () => string[] | void | Promise<void>,
1965
+ fn: () =>
1966
+ | InvalidationDetail
1967
+ | Promise<InvalidationDetail>
1968
+ | void
1969
+ | Promise<void>,
1867
1970
  |};
1868
1971
 
1869
1972
  type InvalidationStats = {|
@@ -1893,6 +1996,33 @@ type InvalidationStats = {|
1893
1996
  invalidations: InvalidationFnStats[],
1894
1997
  |};
1895
1998
 
1999
+ /**
2000
+ * Details about an invalidation.
2001
+ *
2002
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
2003
+ * by path.
2004
+ *
2005
+ * If this is a env or option invalidation, this key will contain the list of changed environment
2006
+ * variables or options.
2007
+ */
2008
+ type InvalidationDetail = string[] | FSInvalidationStats;
2009
+
2010
+ /**
2011
+ * Number of invalidations for a given file-system event.
2012
+ */
2013
+ type FSInvalidation = {|
2014
+ path: string,
2015
+ count: number,
2016
+ |};
2017
+
2018
+ type FSInvalidationStats = {|
2019
+ /**
2020
+ * This list will be sorted by the number of nodes invalidated and only the top 10 will be
2021
+ * included.
2022
+ */
2023
+ biggestInvalidations: FSInvalidation[],
2024
+ |};
2025
+
1896
2026
  /**
1897
2027
  * Information about a certain cache invalidation type.
1898
2028
  */
@@ -1913,8 +2043,14 @@ type InvalidationFnStats = {|
1913
2043
  count: number,
1914
2044
  /**
1915
2045
  * If this is a env or option invalidation, this key will contain the list of changed values.
2046
+ *
2047
+ * If this is a fs events invalidation, this key will contain statistics about invalidations
1916
2048
  */
1917
- changes: null | string[],
2049
+ detail: null | InvalidationDetail,
2050
+ /**
2051
+ * Time in milliseconds it took to run the invalidation.
2052
+ */
2053
+ duration: number,
1918
2054
  |};
1919
2055
 
1920
2056
  /**
@@ -1923,7 +2059,7 @@ type InvalidationFnStats = {|
1923
2059
  *
1924
2060
  * Returns the count of nodes invalidated by each invalidation type.
1925
2061
  */
1926
- async function invalidateRequestGraph(
2062
+ export async function invalidateRequestGraph(
1927
2063
  requestGraph: RequestGraph,
1928
2064
  options: AtlaspackOptions,
1929
2065
  events: Event[],
@@ -1947,14 +2083,7 @@ async function invalidateRequestGraph(
1947
2083
  },
1948
2084
  {
1949
2085
  key: 'fsEvents',
1950
- fn: async () => {
1951
- await requestGraph.respondToFSEvents(
1952
- options.unstableFileInvalidations || events,
1953
- options,
1954
- 10000,
1955
- true,
1956
- );
1957
- },
2086
+ fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events),
1958
2087
  },
1959
2088
  ];
1960
2089
 
@@ -1984,22 +2113,61 @@ async function invalidateRequestGraph(
1984
2113
  };
1985
2114
  }
1986
2115
 
2116
+ interface InvalidateRequestGraphFSEventsInput {
2117
+ respondToFSEvents(
2118
+ events: Event[],
2119
+ options: AtlaspackOptions,
2120
+ timeout: number,
2121
+ shouldLog: boolean,
2122
+ ): Promise<{invalidationsByPath: Map<string, number>, ...}>;
2123
+ }
2124
+
2125
+ /**
2126
+ * Invalidate the request graph based on file-system events.
2127
+ *
2128
+ * Returns statistics about the invalidations.
2129
+ */
2130
+ export async function invalidateRequestGraphFSEvents(
2131
+ requestGraph: InvalidateRequestGraphFSEventsInput,
2132
+ options: AtlaspackOptions,
2133
+ events: Event[],
2134
+ ): Promise<FSInvalidationStats> {
2135
+ const {invalidationsByPath} = await requestGraph.respondToFSEvents(
2136
+ options.unstableFileInvalidations || events,
2137
+ options,
2138
+ 10000,
2139
+ true,
2140
+ );
2141
+ const biggestInvalidations =
2142
+ getBiggestFSEventsInvalidations(invalidationsByPath);
2143
+
2144
+ return {
2145
+ biggestInvalidations,
2146
+ };
2147
+ }
2148
+
2149
+ interface RunInvalidationInput {
2150
+ getInvalidNodeCount(): number;
2151
+ }
2152
+
1987
2153
  /**
1988
2154
  * Runs an invalidation function and reports metrics.
1989
2155
  */
1990
- async function runInvalidation(
1991
- requestGraph: RequestGraph,
2156
+ export async function runInvalidation(
2157
+ requestGraph: RunInvalidationInput,
1992
2158
  invalidationFn: InvalidationFn,
1993
2159
  ): Promise<InvalidationFnStats> {
1994
- const startInvalidationCount = requestGraph.invalidNodeIds.size;
2160
+ const start = performance.now();
2161
+ const startInvalidationCount = requestGraph.getInvalidNodeCount();
1995
2162
  const result = await invalidationFn.fn();
1996
- const count = requestGraph.invalidNodeIds.size - startInvalidationCount;
2163
+ const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
2164
+ const duration = performance.now() - start;
1997
2165
 
1998
2166
  return {
1999
2167
  key: invalidationFn.key,
2000
2168
  count,
2001
- changes:
2002
- typeof result === 'object' && Array.isArray(result) ? result : null,
2169
+ detail: result ?? null,
2170
+ duration,
2003
2171
  };
2004
2172
  }
2005
2173
 
@@ -2054,3 +2222,19 @@ export function cleanUpOrphans<N, E: number>(graph: Graph<N, E>): NodeId[] {
2054
2222
 
2055
2223
  return removedNodeIds;
2056
2224
  }
2225
+
2226
+ /**
2227
+ * Returns paths that invalidated the most nodes
2228
+ */
2229
+ export function getBiggestFSEventsInvalidations(
2230
+ invalidationsByPath: Map<string, number>,
2231
+ limit: number = 10,
2232
+ ): Array<FSInvalidation> {
2233
+ const invalidations = [];
2234
+ for (const [path, count] of invalidationsByPath) {
2235
+ invalidations.push({path, count});
2236
+ }
2237
+ invalidations.sort((a, b) => b.count - a.count);
2238
+
2239
+ return invalidations.slice(0, limit);
2240
+ }