Skip to content

Commit

Permalink
FSEventsWatcher: Don't walk project unnecessarily
Browse files Browse the repository at this point in the history
Summary:
Following D67579233, we no longer make the distinction between new and changed files in events from watcher *backends*.

For `FSEventsWatcher` (macOS), this straightforwardly means we can stop "tracking" files in the watcher, as the only purpose was to disambiguate new/changed, and to not emit deletion events for untracked files (which is already handled downstream for all watchers in `index.js`).

Changelog
```
 - **[Performance]**: Don't walk project on startup when using fsevents watcher on macOS
```

Reviewed By: huntie

Differential Revision: D67579239

fbshipit-source-id: 3761617437a3b9e8168a19e20da48e71e31ea3d9
  • Loading branch information
robhogan authored and facebook-github-bot committed Dec 30, 2024
1 parent a1c9cdb commit 122bdbd
Showing 1 changed file with 8 additions and 38 deletions.
46 changes: 8 additions & 38 deletions packages/metro-file-map/src/watchers/FSEventsWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type {
// $FlowFixMe[untyped-type-import]
import type {FSEvents} from 'fsevents';

import {includedByGlob, recReaddir, typeFromStat} from './common';
import {includedByGlob, typeFromStat} from './common';
import EventEmitter from 'events';
import {promises as fsPromises} from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -48,8 +48,6 @@ export default class FSEventsWatcher extends EventEmitter {
+dot: boolean;
+doIgnore: (path: string) => boolean;
+fsEventsWatchStopper: () => Promise<void>;
+watcherInitialReaddirPromise: Promise<void>;
_tracked: Set<string>;

static isSupported(): boolean {
return fsevents != null;
Expand Down Expand Up @@ -93,47 +91,26 @@ export default class FSEventsWatcher extends EventEmitter {
});
});

const startTime = performance.now();
debug('Watching %s', this.root);

this._tracked = new Set();
const trackPath = (filePath: string) => {
this._tracked.add(path.normalize(filePath));
};
this.watcherInitialReaddirPromise = new Promise(resolve => {
recReaddir(
this.root,
trackPath,
trackPath,
trackPath,
() => {
this.emit('ready');
debug(
'Scanned %s in %d',
this.root,
(performance.now() - startTime) / 1000,
);
resolve();
},
(...args) => {
this.emit('error', ...args);
resolve();
},
this.ignored,
);
// We can't fire this synchronously because we're inside the constructor -
// consumers would have no chance to attach event listeners.
//
// TODO: Refactor this work out of the constructor into an async method.
process.nextTick(() => {
this.emit('ready');
});
}

/**
* End watching.
*/
async close(callback?: () => void): Promise<void> {
await this.watcherInitialReaddirPromise;
await this.fsEventsWatchStopper();
this.removeAllListeners();

await new Promise(resolve => {
// it takes around 100ms for fsevents to release its resounces after
// it takes around 100ms for fsevents to release its resources after
// watching is stopped. See __tests__/server-torn-down-test.js
setTimeout(() => {
if (typeof callback === 'function') {
Expand Down Expand Up @@ -172,20 +149,13 @@ export default class FSEventsWatcher extends EventEmitter {
};

this._emit({event: TOUCH_EVENT, relativePath, metadata});
this._tracked.add(filepath);
} catch (error) {
if (error?.code !== 'ENOENT') {
this.emit('error', error);
return;
}

// Ignore files that aren't tracked and don't exist.
if (!this._tracked.has(filepath)) {
return;
}

this._emit({event: DELETE_EVENT, relativePath});
this._tracked.delete(filepath);
}
}

Expand Down

0 comments on commit 122bdbd

Please sign in to comment.