Repository navigation
Conversation
robhogan
added a commit
that referenced
this pull request
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.
robhogan
added a commit
that referenced
this pull request
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.
…rker With Metro's defaults - lazy SHA-1s, no `hasteImplModulePath` and `enableGlobalPackages: false` - no file needs a worker at startup. Even so, every crawled file goes through `FileProcessor` to find that out: a check for `node_modules` in its path, a call to the Haste plugin's filter, which always says no, and an array for the plugins to run. This makes `HastePlugin.getWorker()` return `null` when the plugin has nothing to compute, so it gets no worker and no per-file data slot (as `MockPlugin` already does). `processBatch` then returns straight away when there's no SHA-1 to compute and no plugin runs on batches. Any plugin after Haste moves down a slot in each file's metadata, so this bumps `CACHE_BREAKER`. ## Benchmark This takes 13-14% off applying the crawl on a cold start on macOS and Linux (Linux: 84ms -> 73ms), or 4-5% off a cold start on Linux and Windows (Linux: 315ms -> 298ms). macOS's cold starts are too noisy to show it. On Linux and Windows, a cache write takes one of two times (on Linux, about 14ms or 20ms), and this PR's writes take the shorter one more often (Linux: 12 of 20 runs against 5, Windows: 20 against 8). That's presumably because there's less garbage to collect, but it makes the cache write medians below jumpy. On macOS, writes are 0.1ms slower. | | `main` | #2219 | This PR | vs `main` | vs #2219 | |---|---|---|---|---|---| | macOS: Applying the crawl in a cold start | 18.7ms | 17.6ms | 15.1ms | -18.9% (-20.7% to -17.3%) | -14.2% (-16.2% to -12.5%) | | Linux: Applying the crawl in a cold start | 85ms | 84ms | 73ms | -14.1% (-16.4% to -10.2%) | -13.2% (-15.5% to -9.7%) | | Windows: Applying the crawl in a cold start | 47ms | 42ms | 40ms | -15.5% (-18.1% to -11.8%) | -6.2% (-13.1% to +0.1%) | | macOS: Cache write in a cold start | 7.8ms | 4.4ms | 4.5ms | -41.9% (-42.7% to -40.3%) | +3.5% (+2.6% to +4.6%) | | Linux: Cache write in a cold start | 33ms | 20ms | 14.0ms | -57.8% (-59.0% to -42.0%) | -30.5% (-32.3% to -5.1%) | | Windows: Cache write in a cold start | 20ms | 13.8ms | 9.4ms | -54.2% (-55.9% to -52.6%) | -32.1% (-34.8% to +2.1%) | | macOS: Cold start | 129ms | 95ms | 91ms | -29.8% (-38.6% to -24.2%) | -4.7% (-16.7% to +4.8%) | | Linux: Cold start | 646ms | 315ms | 298ms | -53.9% (-54.6% to -53.5%) | -5.4% (-7.2% to -4.6%) | | Windows: Cold start | 362ms | 172ms | 164ms | -54.7% (-55.7% to -53.7%) | -4.5% (-5.7% to -2.2%) | ## Changelog ``` - **[Performance]**: metro-file-map: Skip the per-file worker pass when no file needs a worker ``` ## Test plan New unit tests. Benchmark as in #2104, with 22 rounds on macOS.
robhogan
added a commit
that referenced
this pull request
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With Metro's defaults - lazy SHA-1s, no
hasteImplModulePathandenableGlobalPackages: false- no file needs a worker at startup. Even so, every crawled file goes throughFileProcessorto find that out: a check fornode_modulesin its path, a call to the Haste plugin's filter, which always says no, and an array for the plugins to run.This makes
HastePlugin.getWorker()returnnullwhen the plugin has nothing to compute, so it gets no worker and no per-file data slot (asMockPluginalready does).processBatchthen returns straight away when there's no SHA-1 to compute and no plugin runs on batches.Any plugin after Haste moves down a slot in each file's metadata, so this bumps
CACHE_BREAKER.Benchmark
This takes 13-14% off applying the crawl on a cold start on macOS and Linux (Linux: 84ms -> 73ms), or 4-5% off a cold start on Linux and Windows (Linux: 315ms -> 298ms). macOS's cold starts are too noisy to show it.
On Linux and Windows, a cache write takes one of two times (on Linux, about 14ms or 20ms), and this PR's writes take the shorter one more often (Linux: 12 of 20 runs against 5, Windows: 20 against 8). That's presumably because there's less garbage to collect, but it makes the cache write medians below jumpy. On macOS, writes are 0.1ms slower.
mainmainChangelog
Test plan
New unit tests. Benchmark as in #2104, with 22 rounds on macOS.