Skip to content

Commit b942eca

Browse files
robhoganfacebook-github-bot
authored andcommitted
Improve Fast Refresh responsiveness when watching a large number of files (#913)
Summary: Pull Request resolved: #913 Following D40829941 (7191173), where we changed the output of `metro-file-map`'s `build()` to return "live" references to the file system and the Haste module map (rather than snapshots), we no longer require further snapshots to be emitted as part of a change event payload. In fact, `ChangeEvent['snapshotFS']` and `ChangeEvent['moduleMap']` are currently only referenced in test code. Removing them means we don't have to copy potentially large data structures on each emitted change. With ~300k files and ~150k Haste map entries, this amounts to ~200ms more responsive fast refresh - in general this boost will be proportional to the total number of files+Haste modules watched. After this, a `HasteFS` instance is only constructed once, on Metro startup. That clears the way for an alternative (more expensive to initialise) implementation capable of performing lookups through symlinks. Changelog: [Performance] Improve Fast Refresh responsiveness when watching a large number of files. Reviewed By: motiz88 Differential Revision: D42303139 fbshipit-source-id: b158ca2fdcddf02ecb2f2d17bc5b35e448fed0ae
1 parent 7d89ee4 commit b942eca

File tree

3 files changed

+34
-55
lines changed

3 files changed

+34
-55
lines changed

packages/metro-file-map/src/__tests__/index-test.js

+34-33
Original file line numberDiff line numberDiff line change
@@ -1364,13 +1364,13 @@ describe('HasteMap', () => {
13641364
}
13651365

13661366
hm_it('build returns a "live" fileSystem and hasteModuleMap', async hm => {
1367-
const initialResult = await hm.build();
1367+
const {fileSystem, hasteModuleMap} = await hm.build();
13681368
const filePath = path.join('/', 'project', 'fruits', 'Banana.js');
1369-
expect(initialResult.fileSystem.getModuleName(filePath)).toBeDefined();
1370-
expect(initialResult.hasteModuleMap.getModule('Banana')).toBe(filePath);
1369+
expect(fileSystem.getModuleName(filePath)).toBeDefined();
1370+
expect(hasteModuleMap.getModule('Banana')).toBe(filePath);
13711371
mockDeleteFile(path.join('/', 'project', 'fruits'), 'Banana.js');
13721372
mockDeleteFile(path.join('/', 'project', 'fruits'), 'Banana.js');
1373-
const {eventsQueue, snapshotFS, moduleMap} = await waitForItToChange(hm);
1373+
const {eventsQueue} = await waitForItToChange(hm);
13741374
expect(eventsQueue).toHaveLength(1);
13751375
const deletedBanana = {
13761376
filePath,
@@ -1379,10 +1379,8 @@ describe('HasteMap', () => {
13791379
};
13801380
expect(eventsQueue).toEqual([deletedBanana]);
13811381
// Verify that the initial result has been updated
1382-
expect(initialResult.fileSystem.getModuleName(filePath)).toBeNull();
1383-
expect(initialResult.hasteModuleMap.getModule('Banana')).toBeNull();
1384-
expect(snapshotFS.getModuleName(filePath)).toBeNull();
1385-
expect(moduleMap.getModule('Banana')).toBeNull();
1382+
expect(fileSystem.getModuleName(filePath)).toBeNull();
1383+
expect(hasteModuleMap.getModule('Banana')).toBeNull();
13861384
});
13871385

13881386
const MOCK_CHANGE_FILE = {
@@ -1398,6 +1396,7 @@ describe('HasteMap', () => {
13981396
};
13991397

14001398
hm_it('handles several change events at once', async hm => {
1399+
const {fileSystem, hasteModuleMap} = await hm.build();
14011400
mockFs[path.join('/', 'project', 'fruits', 'Tomato.js')] = `
14021401
// Tomato!
14031402
`;
@@ -1419,7 +1418,7 @@ describe('HasteMap', () => {
14191418
path.join('/', 'project', 'fruits'),
14201419
MOCK_CHANGE_FILE,
14211420
);
1422-
const {eventsQueue, snapshotFS, moduleMap} = await waitForItToChange(hm);
1421+
const {eventsQueue} = await waitForItToChange(hm);
14231422
expect(eventsQueue).toEqual([
14241423
{
14251424
filePath: path.join('/', 'project', 'fruits', 'Tomato.js'),
@@ -1433,12 +1432,12 @@ describe('HasteMap', () => {
14331432
},
14341433
]);
14351434
expect(
1436-
snapshotFS.getModuleName(
1435+
fileSystem.getModuleName(
14371436
path.join('/', 'project', 'fruits', 'Tomato.js'),
14381437
),
14391438
).not.toBeNull();
1440-
expect(moduleMap.getModule('Tomato')).toBeDefined();
1441-
expect(moduleMap.getModule('Pear')).toBe(
1439+
expect(hasteModuleMap.getModule('Tomato')).toBeDefined();
1440+
expect(hasteModuleMap.getModule('Pear')).toBe(
14421441
path.join('/', 'project', 'fruits', 'Pear.js'),
14431442
);
14441443
});
@@ -1466,6 +1465,7 @@ describe('HasteMap', () => {
14661465
hm_it(
14671466
'emits a change even if a file in node_modules has changed',
14681467
async hm => {
1468+
const {fileSystem} = await hm.build();
14691469
const e = mockEmitters[path.join('/', 'project', 'fruits')];
14701470
e.emit(
14711471
'all',
@@ -1474,7 +1474,7 @@ describe('HasteMap', () => {
14741474
path.join('/', 'project', 'fruits', 'node_modules', ''),
14751475
MOCK_CHANGE_FILE,
14761476
);
1477-
const {eventsQueue, snapshotFS} = await waitForItToChange(hm);
1477+
const {eventsQueue} = await waitForItToChange(hm);
14781478
const filePath = path.join(
14791479
'/',
14801480
'project',
@@ -1486,16 +1486,16 @@ describe('HasteMap', () => {
14861486
expect(eventsQueue).toEqual([
14871487
{filePath, metadata: MOCK_CHANGE_FILE, type: 'add'},
14881488
]);
1489-
expect(snapshotFS.getModuleName(filePath)).toBeDefined();
1489+
expect(fileSystem.getModuleName(filePath)).toBeDefined();
14901490
},
14911491
);
14921492

14931493
hm_it(
14941494
'correctly tracks changes to both platform-specific versions of a single module name',
14951495
async hm => {
1496-
const {hasteModuleMap: initMM} = await hm.build();
1497-
expect(initMM.getModule('Orange', 'ios')).toBeTruthy();
1498-
expect(initMM.getModule('Orange', 'android')).toBeTruthy();
1496+
const {hasteModuleMap, fileSystem} = await hm.build();
1497+
expect(hasteModuleMap.getModule('Orange', 'ios')).toBeTruthy();
1498+
expect(hasteModuleMap.getModule('Orange', 'android')).toBeTruthy();
14991499
const e = mockEmitters[path.join('/', 'project', 'fruits')];
15001500
e.emit(
15011501
'all',
@@ -1511,9 +1511,7 @@ describe('HasteMap', () => {
15111511
path.join('/', 'project', 'fruits'),
15121512
MOCK_CHANGE_FILE,
15131513
);
1514-
const {eventsQueue, snapshotFS, moduleMap} = await waitForItToChange(
1515-
hm,
1516-
);
1514+
const {eventsQueue} = await waitForItToChange(hm);
15171515
expect(eventsQueue).toHaveLength(2);
15181516
expect(eventsQueue).toEqual([
15191517
{
@@ -1528,20 +1526,20 @@ describe('HasteMap', () => {
15281526
},
15291527
]);
15301528
expect(
1531-
snapshotFS.getModuleName(
1529+
fileSystem.getModuleName(
15321530
path.join('/', 'project', 'fruits', 'Orange.ios.js'),
15331531
),
15341532
).toBeTruthy();
15351533
expect(
1536-
snapshotFS.getModuleName(
1534+
fileSystem.getModuleName(
15371535
path.join('/', 'project', 'fruits', 'Orange.android.js'),
15381536
),
15391537
).toBeTruthy();
1540-
const iosVariant = moduleMap.getModule('Orange', 'ios');
1538+
const iosVariant = hasteModuleMap.getModule('Orange', 'ios');
15411539
expect(iosVariant).toBe(
15421540
path.join('/', 'project', 'fruits', 'Orange.ios.js'),
15431541
);
1544-
const androidVariant = moduleMap.getModule('Orange', 'android');
1542+
const androidVariant = hasteModuleMap.getModule('Orange', 'android');
15451543
expect(androidVariant).toBe(
15461544
path.join('/', 'project', 'fruits', 'Orange.android.js'),
15471545
);
@@ -1560,6 +1558,7 @@ describe('HasteMap', () => {
15601558

15611559
describe('recovery from duplicate module IDs', () => {
15621560
async function setupDuplicates(hm) {
1561+
const {fileSystem, hasteModuleMap} = await hm.build();
15631562
mockFs[path.join('/', 'project', 'fruits', 'Pear.js')] = `
15641563
// Pear!
15651564
`;
@@ -1581,14 +1580,14 @@ describe('HasteMap', () => {
15811580
path.join('/', 'project', 'fruits', 'another'),
15821581
MOCK_CHANGE_FILE,
15831582
);
1584-
const {snapshotFS, moduleMap} = await waitForItToChange(hm);
1583+
await waitForItToChange(hm);
15851584
expect(
1586-
snapshotFS.exists(
1585+
fileSystem.exists(
15871586
path.join('/', 'project', 'fruits', 'another', 'Pear.js'),
15881587
),
15891588
).toBe(true);
15901589
try {
1591-
moduleMap.getModule('Pear');
1590+
hasteModuleMap.getModule('Pear');
15921591
throw new Error('should be unreachable');
15931592
} catch (error) {
15941593
const {
@@ -1612,6 +1611,7 @@ describe('HasteMap', () => {
16121611
hm_it(
16131612
'recovers when the oldest version of the duplicates is fixed',
16141613
async hm => {
1614+
const {hasteModuleMap} = await hm.build();
16151615
await setupDuplicates(hm);
16161616
mockFs[path.join('/', 'project', 'fruits', 'Pear.js')] = null;
16171617
mockFs[path.join('/', 'project', 'fruits', 'Pear2.js')] = `
@@ -1632,17 +1632,18 @@ describe('HasteMap', () => {
16321632
path.join('/', 'project', 'fruits'),
16331633
MOCK_CHANGE_FILE,
16341634
);
1635-
const {moduleMap} = await waitForItToChange(hm);
1636-
expect(moduleMap.getModule('Pear')).toBe(
1635+
await waitForItToChange(hm);
1636+
expect(hasteModuleMap.getModule('Pear')).toBe(
16371637
path.join('/', 'project', 'fruits', 'another', 'Pear.js'),
16381638
);
1639-
expect(moduleMap.getModule('Pear2')).toBe(
1639+
expect(hasteModuleMap.getModule('Pear2')).toBe(
16401640
path.join('/', 'project', 'fruits', 'Pear2.js'),
16411641
);
16421642
},
16431643
);
16441644

16451645
hm_it('recovers when the most recent duplicate is fixed', async hm => {
1646+
const {hasteModuleMap} = await hm.build();
16461647
await setupDuplicates(hm);
16471648
mockFs[path.join('/', 'project', 'fruits', 'another', 'Pear.js')] =
16481649
null;
@@ -1664,11 +1665,11 @@ describe('HasteMap', () => {
16641665
path.join('/', 'project', 'fruits', 'another'),
16651666
MOCK_CHANGE_FILE,
16661667
);
1667-
const {moduleMap} = await waitForItToChange(hm);
1668-
expect(moduleMap.getModule('Pear')).toBe(
1668+
await waitForItToChange(hm);
1669+
expect(hasteModuleMap.getModule('Pear')).toBe(
16691670
path.join('/', 'project', 'fruits', 'Pear.js'),
16701671
);
1671-
expect(moduleMap.getModule('Pear2')).toBe(
1672+
expect(hasteModuleMap.getModule('Pear2')).toBe(
16721673
path.join('/', 'project', 'fruits', 'another', 'Pear2.js'),
16731674
);
16741675
});

packages/metro-file-map/src/flow-types.js

-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ export type CacheManagerFactory = (
5959
export type ChangeEvent = {
6060
logger: ?RootPerfLogger,
6161
eventsQueue: EventsQueue,
62-
snapshotFS: FileSystem,
63-
moduleMap: ModuleMap,
6462
};
6563

6664
export type ChangeEventMetadata = {

packages/metro-file-map/src/index.js

-20
Original file line numberDiff line numberDiff line change
@@ -799,25 +799,6 @@ export default class HasteMap extends EventEmitter {
799799
return this._worker;
800800
}
801801

802-
_getSnapshot(data: InternalData): {
803-
snapshotFS: FileSystem,
804-
moduleMap: HasteModuleMap,
805-
} {
806-
const rootDir = this._options.rootDir;
807-
return {
808-
snapshotFS: new HasteFS({
809-
files: new Map(data.files),
810-
rootDir,
811-
}),
812-
moduleMap: new HasteModuleMap({
813-
duplicates: new Map(data.duplicates),
814-
map: new Map(data.map),
815-
mocks: new Map(data.mocks),
816-
rootDir,
817-
}),
818-
};
819-
}
820-
821802
_removeIfExists(data: InternalData, relativeFilePath: Path) {
822803
const fileMetadata = data.files.get(relativeFilePath);
823804
if (!fileMetadata) {
@@ -900,7 +881,6 @@ export default class HasteMap extends EventEmitter {
900881
const changeEvent: ChangeEvent = {
901882
logger: hmrPerfLogger,
902883
eventsQueue,
903-
...this._getSnapshot(data),
904884
};
905885
this.emit('change', changeEvent);
906886
eventsQueue = [];

0 commit comments

Comments
 (0)