Skip to content

metro-file-map: Start watching while the cache is read - #2221

Draft
robhogan wants to merge 1 commit into
pr2220from
pr2221
Draft

robhogan wants to merge 1 commit into
pr2220from
pr2221

Conversation

@robhogan

@robhogan robhogan commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Without Watchman, a watch-mode start has to be watching before it crawls, so that a change made during the crawl isn't missed. NativeWatcher (macOS) isn't live as soon as it starts, so Watcher also confirms each root's watch with a health check before the crawl begins:

// Confirmed only once every backend has started, because starting one may
// restart the others' watches (NativeWatchers share one FSEvents stream).
this.#confirmWatches = !WatcherImpl.isLiveOnStart();
if (this.#confirmWatches) {
await Promise.all(
this.#options.roots.map(root => this.#waitUntilLive(root)),
);
}

That takes about 13ms on macOS, and build() only starts it once the cache has been read, although the watch doesn't need anything from the cache.

This starts the watch, and the Watchman capability check (which spawns watchman), while the cache is read. The crawl still waits for the confirmed watch, so nothing can be missed that wasn't before. To allow this, Watcher takes the cached state in crawl() rather than at construction.

Benchmark

This takes 13% off a watch-mode start on macOS (102ms -> 88ms). Linux and Windows watch with FallbackWatcher, which is live as soon as it starts, so they're unchanged, as are starts outside watch mode. With a cold cache there's no read to overlap with.

main #2220 This PR vs main vs #2220
macOS: Watch-mode start 130ms 102ms 88ms -32.1% (-32.8% to -31.2%) -13.0% (-14.0% to -11.3%)
Linux: Watch-mode start 1,889ms 493ms 491ms -74.0% (-74.2% to -73.6%) -0.5% (-1.4% to +0.5%)
Windows: Watch-mode start 2,569ms 524ms 524ms -79.6% (-79.7% to -79.5%) +0.1% (-0.5% to +0.6%)
macOS: Warm start 118ms 88ms 88ms -25.1% (-26.4% to -23.6%) +0.1% (-1.8% to +1.9%)
Linux: Warm start 636ms 353ms 353ms -44.5% (-45.3% to -44.1%) -0.1% (-1.8% to +1.1%)
Windows: Warm start 355ms 201ms 201ms -43.4% (-43.7% to -42.7%) +0.0% (-0.9% to +0.7%)

Changelog

 - **[Performance]**: metro-file-map: Start watching while the cache is read

Test plan

New unit tests. Benchmark as in #2104, with 22 rounds on macOS.

@robhogan
robhogan added this pull request to stack #2109 November 12, 2026 09:15
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Nov 12, 2026
Without Watchman, a watch-mode start has to be watching before it crawls, so that a change made during the crawl isn't missed. `NativeWatcher` (macOS) isn't live as soon as it starts, so `Watcher` also confirms each root's watch with a health check before the crawl begins:

https://github.com/react/metro/blob/ef50b94fba3bac242d06b20f03c7149987b3c2d6/packages/metro-file-map/src/Watcher.js#L375-L382

That takes about 13ms on macOS, and `build()` only starts it once the cache has been read, although the watch doesn't need anything from the cache.

This starts the watch, and the Watchman capability check (which spawns `watchman`), while the cache is read. The crawl still waits for the confirmed watch, so nothing can be missed that wasn't before. To allow this, `Watcher` takes the cached state in `crawl()` rather than at construction.

## Benchmark

This takes 13% off a watch-mode start on macOS (102ms -> 88ms). Linux and Windows watch with `FallbackWatcher`, which is live as soon as it starts, so they're unchanged, as are starts outside watch mode. With a cold cache there's no read to overlap with.

|  | `main` | #2220 | This PR | vs `main` | vs #2220 |
|---|---|---|---|---|---|
| macOS: Watch-mode start | 130ms | 102ms | 88ms | -32.1% (-32.8% to -31.2%) | -13.0% (-14.0% to -11.3%) |
| Linux: Watch-mode start | 1,889ms | 493ms | 491ms | -74.0% (-74.2% to -73.6%) | -0.5% (-1.4% to +0.5%) |
| Windows: Watch-mode start | 2,569ms | 524ms | 524ms | -79.6% (-79.7% to -79.5%) | +0.1% (-0.5% to +0.6%) |
| macOS: Warm start | 118ms | 88ms | 88ms | -25.1% (-26.4% to -23.6%) | +0.1% (-1.8% to +1.9%) |
| Linux: Warm start | 636ms | 353ms | 353ms | -44.5% (-45.3% to -44.1%) | -0.1% (-1.8% to +1.1%) |
| Windows: Warm start | 355ms | 201ms | 201ms | -43.4% (-43.7% to -42.7%) | +0.0% (-0.9% to +0.7%) |

## Changelog

```
 - **[Performance]**: metro-file-map: Start watching while the cache is read
```

## Test plan

New unit tests. Benchmark as in #2104, with 22 rounds on macOS.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant