Repository navigation
Conversation
robhogan
added a commit
that referenced
this pull request
Oct 25, 2026
Without Watchman, `Watcher` starts its backends before the crawl and buffers their events, so that a change made during the crawl isn't missed: https://github.com/react/metro/blob/56efab302200568c919ac1b3c28dacf8d84fc692/packages/metro-file-map/src/Watcher.js#L118-L127 For `FallbackWatcher` that means walking every root to watch each directory, and then the node crawler walks the same tree again. This lets the crawl do both. Just before the crawler lists a directory it passes it to the watcher, which watches it - the order the watcher's own walk uses, so a change still can't slip between the two. Directories the crawl skips as ignored are passed on too, and the watcher walks those itself unless it ignores them as well (eg `node_modules` without `retainAllFiles`). The hand-off is an internal second parameter of the built-in node crawler, so there's no API change. A `crawlerFactory` crawler, a scoped recrawl and a root added with `addRoot` keep the watcher's own walk. The longer-term shape for this is a backend that owns both crawling and watching, which this would move into. ## Benchmark This takes another 25% off watch-mode startup on Linux and Windows. With the rest of this stack, watch-mode startup without Watchman goes from 1,894ms to 508ms (-73%) on Linux, and from 2,580ms to 539ms (-79%) on Windows. On macOS, which uses `NativeWatcher`, PRs 2-5 measure the same as the first PR (124ms). | | `main` | #2107 | This PR | vs `main` | vs #2107 | |---|---|---|---|---|---| | Linux: Watch-mode start, warm cache | 1,894ms | 685ms | 508ms | -73.2% (-73.4% to -72.9%) | -25.9% (-26.4% to -25.4%) | | Windows: Watch-mode start, warm cache | 2,580ms | 721ms | 539ms | -79.1% (-79.3% to -78.3%) | -25.2% (-27.3% to -22.3%) | | Linux: Watch-mode start, cold cache | 1,876ms | 652ms | 472ms | -74.8% (-75.2% to -74.5%) | -27.6% (-28.5% to -26.7%) | | Windows: Watch-mode start, cold cache | 2,567ms | 695ms | 516ms | -79.9% (-80.2% to -79.8%) | -25.7% (-28.2% to -24.9%) | | Linux: Heap after a watch-mode start | 31.8MB | 28.3MB | 28.3MB | -11.1% (-11.1% to -11.0%) | -0.0% (-0.0% to -0.0%) | | Windows: Heap after a watch-mode start | 31.9MB | 28.4MB | 28.4MB | -11.1% (-11.1% to -11.1%) | -0.0% (-0.0% to -0.0%) | | Linux: RSS after a watch-mode start | 197.4MB | 176.3MB | 170.9MB | -13.4% (-15.0% to -12.0%) | -3.1% (-4.6% to -1.6%) | | Windows: RSS after a watch-mode start | 205.7MB | 182.6MB | 184.6MB | -10.3% (-10.7% to -9.4%) | +1.1% (+0.9% to +1.4%) | ## Changelog ``` - **[Performance]**: metro-file-map: Set up file watching during the crawl instead of after it, on Linux and Windows without Watchman ``` ## Test plan New `Watcher-test.js` cases: the crawl's directories reach a `FallbackWatcher`, and a recrawl or a `crawlerFactory` crawler leaves it to walk. New `watch-integration-test.js` case for a directory the crawl skips but the watcher doesn't. Benchmark as in #2104.
robhogan
added a commit
that referenced
this pull request
Oct 25, 2026
Without Watchman, `Watcher` starts its backends before the crawl, so that nothing changed during the crawl is missed: https://github.com/react/metro/blob/56efab302200568c919ac1b3c28dacf8d84fc692/packages/metro-file-map/src/Watcher.js#L118-L127 For `FallbackWatcher` that means walking the tree once to watch it and again to crawl it. This lets the crawl do both: the crawler hands each directory to the watcher just before listing it, the order the watcher's own walk uses. Directories the crawl skips as ignored are handed over too, for the watcher to walk unless it ignores them as well (eg `node_modules` without `retainAllFiles`). This is internal to the built-in node crawler. A `crawlerFactory` crawler, a recrawl and a root added with `addRoot` keep the watcher's own walk. ## Benchmark This takes another 28-30% off watch-mode startup on Linux and Windows. With the rest of this stack, watch-mode startup without Watchman goes from 1,896ms to 510ms (-73%) on Linux, and from 2,554ms to 522ms (-80%) on Windows. On macOS, which uses `NativeWatcher`, PRs 2-5 measure within noise of #2104. | | `main` | #2107 | This PR | vs `main` | vs #2107 | |---|---|---|---|---|---| | Linux: Watch-mode start, warm cache | 1,896ms | 707ms | 510ms | -73.1% (-73.4% to -72.8%) | -27.9% (-28.6% to -27.2%) | | Windows: Watch-mode start, warm cache | 2,554ms | 721ms | 522ms | -79.6% (-79.7% to -79.4%) | -27.7% (-28.0% to -27.2%) | | Linux: Watch-mode start, cold cache | 1,882ms | 699ms | 491ms | -73.9% (-74.5% to -73.5%) | -29.7% (-31.1% to -28.5%) | | Windows: Watch-mode start, cold cache | 2,547ms | 709ms | 507ms | -80.1% (-80.2% to -80.0%) | -28.5% (-28.9% to -27.9%) | | Linux: Heap after a watch-mode start | 31.8MB | 28.3MB | 28.3MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Windows: Heap after a watch-mode start | 31.9MB | 28.4MB | 28.4MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Linux: RSS after a watch-mode start | 197.6MB | 175.2MB | 174.3MB | -11.8% (-12.3% to -11.3%) | -0.5% (-0.9% to +0.0%) | | Windows: RSS after a watch-mode start | 206.1MB | 182.9MB | 183.9MB | -10.7% (-11.3% to -10.3%) | +0.6% (+0.4% to +0.8%) | ## Changelog ``` - **[Performance]**: metro-file-map: Set up file watching during the crawl instead of after it, on Linux and Windows without Watchman ``` ## Test plan New unit tests, and a `watch-integration-test.js` case for a directory the crawl skips but the watcher doesn't. Benchmark as in #2104.
robhogan
added a commit
that referenced
this pull request
Oct 26, 2026
Without Watchman, `Watcher` starts its backends before the crawl, so that nothing changed during the crawl is missed: https://github.com/react/metro/blob/56efab302200568c919ac1b3c28dacf8d84fc692/packages/metro-file-map/src/Watcher.js#L118-L127 For `FallbackWatcher` that means walking the tree once to watch it and again to crawl it. This lets the crawl do both: the crawler hands each directory to the watcher just before listing it, the order the watcher's own walk uses. Directories the crawl skips as ignored are handed over too, for the watcher to walk unless it ignores them as well (eg `node_modules` without `retainAllFiles`). This is internal to the built-in node crawler. A `crawlerFactory` crawler, a recrawl and a root added with `addRoot` keep the watcher's own walk. ## Benchmark This takes another 28-30% off watch-mode startup on Linux and Windows. With the rest of this stack, watch-mode startup without Watchman goes from 1,896ms to 510ms (-73%) on Linux, and from 2,554ms to 522ms (-80%) on Windows. On macOS, which uses `NativeWatcher`, PRs 2-5 measure within noise of #2104. | | `main` | #2107 | This PR | vs `main` | vs #2107 | |---|---|---|---|---|---| | Linux: Watch-mode start, warm cache | 1,896ms | 707ms | 510ms | -73.1% (-73.4% to -72.8%) | -27.9% (-28.6% to -27.2%) | | Windows: Watch-mode start, warm cache | 2,554ms | 721ms | 522ms | -79.6% (-79.7% to -79.4%) | -27.7% (-28.0% to -27.2%) | | Linux: Watch-mode start, cold cache | 1,882ms | 699ms | 491ms | -73.9% (-74.5% to -73.5%) | -29.7% (-31.1% to -28.5%) | | Windows: Watch-mode start, cold cache | 2,547ms | 709ms | 507ms | -80.1% (-80.2% to -80.0%) | -28.5% (-28.9% to -27.9%) | | Linux: Heap after a watch-mode start | 31.8MB | 28.3MB | 28.3MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Windows: Heap after a watch-mode start | 31.9MB | 28.4MB | 28.4MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Linux: RSS after a watch-mode start | 197.6MB | 175.2MB | 174.3MB | -11.8% (-12.3% to -11.3%) | -0.5% (-0.9% to +0.0%) | | Windows: RSS after a watch-mode start | 206.1MB | 182.9MB | 183.9MB | -10.7% (-11.3% to -10.3%) | +0.6% (+0.4% to +0.8%) | ## Changelog ``` - **[Performance]**: metro-file-map: Set up file watching during the crawl instead of after it, on Linux and Windows without Watchman ``` ## Test plan New unit tests, and a `watch-integration-test.js` case for a directory the crawl skips but the watcher doesn't. Benchmark as in #2104.
`FallbackWatcher` keeps an entry for every file it has seen: https://github.com/react/metro/blob/56efab302200568c919ac1b3c28dacf8d84fc692/packages/metro-file-map/src/watchers/FallbackWatcher.js#L53-L56 It only needs them to decide which vanished paths to report, and to report a deleted directory's files one by one. A file's deletion is wanted if its name passes the same include checks as its other events, and the file map already removes everything under a directory it's told is deleted, which is how `NativeWatcher` reports one. So this keeps entries only for directories (including ones it couldn't watch) and symlinks, which can't be judged by name. ## Benchmark This takes another 22-28% off watch-mode startup on Linux and Windows (Linux: 969ms -> 707ms), and 3MB (10%) off the heap after startup on this app. | | `main` | #2106 | This PR | vs `main` | vs #2106 | |---|---|---|---|---|---| | Linux: Watch-mode start, warm cache | 1,896ms | 969ms | 707ms | -62.7% (-63.2% to -62.4%) | -27.1% (-27.6% to -26.5%) | | Windows: Watch-mode start, warm cache | 2,554ms | 926ms | 721ms | -71.8% (-71.9% to -71.6%) | -22.1% (-22.3% to -21.8%) | | Linux: Watch-mode start, cold cache | 1,882ms | 967ms | 699ms | -62.9% (-63.3% to -62.6%) | -27.7% (-28.9% to -27.3%) | | Windows: Watch-mode start, cold cache | 2,547ms | 912ms | 709ms | -72.2% (-72.4% to -72.0%) | -22.3% (-22.9% to -21.9%) | | Linux: Heap after a watch-mode start | 31.8MB | 31.4MB | 28.3MB | -11.0% (-11.0% to -11.0%) | -9.8% (-9.8% to -9.8%) | | Windows: Heap after a watch-mode start | 31.9MB | 31.5MB | 28.4MB | -11.0% (-11.0% to -11.0%) | -9.8% (-9.8% to -9.8%) | | Linux: RSS after a watch-mode start | 197.6MB | 180.3MB | 175.2MB | -11.3% (-11.9% to -10.9%) | -2.8% (-3.4% to -2.4%) | | Windows: RSS after a watch-mode start | 206.1MB | 187.0MB | 182.9MB | -11.2% (-11.7% to -10.9%) | -2.2% (-2.5% to -2.0%) | ## Changelog ``` - **[Performance]**: metro-file-map: Start the file watcher faster, and with less memory, on Linux and Windows without Watchman ``` ## Test plan New unit tests, and a `watch-integration-test.js` that drives FileMap in watch mode over a real directory, deleting directories and symlinks and moving directories out (with `FallbackWatcher`, or `NativeWatcher` on macOS). Benchmark as in #2104.
robhogan
added a commit
that referenced
this pull request
Oct 26, 2026
Without Watchman, `Watcher` starts its backends before the crawl, so that nothing changed during the crawl is missed: https://github.com/react/metro/blob/56efab302200568c919ac1b3c28dacf8d84fc692/packages/metro-file-map/src/Watcher.js#L118-L127 For `FallbackWatcher` that means walking the tree once to watch it and again to crawl it. This lets the crawl do both: the crawler hands each directory to the watcher just before listing it, the order the watcher's own walk uses. Directories the crawl skips as ignored are handed over too, for the watcher to walk unless it ignores them as well (eg `node_modules` without `retainAllFiles`). This is internal to the built-in node crawler. A `crawlerFactory` crawler, a recrawl and a root added with `addRoot` keep the watcher's own walk. ## Benchmark This takes another 28-30% off watch-mode startup on Linux and Windows. With the rest of this stack, watch-mode startup without Watchman goes from 1,896ms to 510ms (-73%) on Linux, and from 2,554ms to 522ms (-80%) on Windows. On macOS, which uses `NativeWatcher`, PRs 2-5 measure within noise of #2104. | | `main` | #2107 | This PR | vs `main` | vs #2107 | |---|---|---|---|---|---| | Linux: Watch-mode start, warm cache | 1,896ms | 707ms | 510ms | -73.1% (-73.4% to -72.8%) | -27.9% (-28.6% to -27.2%) | | Windows: Watch-mode start, warm cache | 2,554ms | 721ms | 522ms | -79.6% (-79.7% to -79.4%) | -27.7% (-28.0% to -27.2%) | | Linux: Watch-mode start, cold cache | 1,882ms | 699ms | 491ms | -73.9% (-74.5% to -73.5%) | -29.7% (-31.1% to -28.5%) | | Windows: Watch-mode start, cold cache | 2,547ms | 709ms | 507ms | -80.1% (-80.2% to -80.0%) | -28.5% (-28.9% to -27.9%) | | Linux: Heap after a watch-mode start | 31.8MB | 28.3MB | 28.3MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Windows: Heap after a watch-mode start | 31.9MB | 28.4MB | 28.4MB | -11.1% (-11.1% to -11.1%) | -0.1% (-0.1% to -0.1%) | | Linux: RSS after a watch-mode start | 197.6MB | 175.2MB | 174.3MB | -11.8% (-12.3% to -11.3%) | -0.5% (-0.9% to +0.0%) | | Windows: RSS after a watch-mode start | 206.1MB | 182.9MB | 183.9MB | -10.7% (-11.3% to -10.3%) | +0.6% (+0.4% to +0.8%) | ## Changelog ``` - **[Performance]**: metro-file-map: Set up file watching during the crawl instead of after it, on Linux and Windows without Watchman ``` ## Test plan New unit tests, and a `watch-integration-test.js` case for a directory the crawl skips but the watcher doesn't. Benchmark as in #2104.
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.
FallbackWatcherkeeps an entry for every file it has seen:metro/packages/metro-file-map/src/watchers/FallbackWatcher.js
Lines 53 to 56 in 56efab3
It only needs them to decide which vanished paths to report, and to report a deleted directory's files one by one. A file's deletion is wanted if its name passes the same include checks as its other events, and the file map already removes everything under a directory it's told is deleted, which is how
NativeWatcherreports one. So this keeps entries only for directories (including ones it couldn't watch) and symlinks, which can't be judged by name.Benchmark
This takes another 22-28% off watch-mode startup on Linux and Windows (Linux: 969ms -> 707ms), and 3MB (10%) off the heap after startup on this app.
mainmainChangelog
Test plan
New unit tests, and a
watch-integration-test.jsthat drives FileMap in watch mode over a real directory, deleting directories and symlinks and moving directories out (withFallbackWatcher, orNativeWatcheron macOS). Benchmark as in #2104.