Send HMR updates only for files on the bundle

Summary:
public

Compute the dependencies of the bundle entry file just before sending HMR updates. In case the file that was changed doesn't belong to the bundle bail.

Reviewed By: vjeux

Differential Revision: D2793736

fb-gh-sync-id: f858e71b0dd5fe4f5b2307a22c6cef627eb66a22
This commit is contained in:
Martín Bigio 2015-12-29 18:24:08 -08:00 committed by facebook-github-bot-8
parent 5f850fbede
commit b5081abae3
5 changed files with 113 additions and 15 deletions

View File

@ -22,16 +22,76 @@ function attachHMRServer({httpServer, path, packagerServer}) {
client = null; client = null;
} }
// Returns a promise with the full list of dependencies and the shallow
// dependencies each file on the dependency list has for the give platform
// and entry file.
function getDependencies(platform, bundleEntry) {
return packagerServer.getDependencies({
platform: platform,
dev: true,
entryFile: bundleEntry,
}).then(response => {
// for each dependency builds the object:
// `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
return Promise.all(Object.values(response.dependencies).map(dep => {
if (dep.isAsset() || dep.isAsset_DEPRECATED() || dep.isJSON()) {
return Promise.resolve({path: dep.path, deps: []});
}
return packagerServer.getShallowDependencies(dep.path)
.then(deps => {
return {
path: dep.path,
deps,
};
});
}))
.then(deps => {
// list with all the dependencies the bundle entry has
const dependenciesCache = response.dependencies.map(dep => dep.path);
// map that indicates the shallow dependency each file included on the
// bundle has
const shallowDependencies = {};
deps.forEach(dep => shallowDependencies[dep.path] = dep.deps);
return {dependenciesCache, shallowDependencies};
});
});
}
packagerServer.addFileChangeListener(filename => { packagerServer.addFileChangeListener(filename => {
if (!client) { if (!client) {
return; return;
} }
packagerServer.buildBundleForHMR({ packagerServer.getShallowDependencies(filename)
entryFile: filename, .then(deps => {
platform: client.platform, // if the file dependencies have change we need to invalidate the
}) // dependencies caches because the list of files we need to send to the
.then(bundle => client.ws.send(bundle)); // client may have changed
if (arrayEquals(deps, client.shallowDependencies[filename])) {
return Promise.resolve();
}
return getDependencies(client.platform, client.bundleEntry)
.then(({dependenciesCache, shallowDependencies}) => {
// invalidate caches
client.dependenciesCache = dependenciesCache;
client.shallowDependencies = shallowDependencies;
});
})
.then(() => {
// make sure the file was modified is part of the bundle
if (!client.shallowDependencies[filename]) {
return;
}
return packagerServer.buildBundleForHMR({
platform: client.platform,
entryFile: filename,
})
.then(bundle => client.ws.send(bundle));
})
.done();
}); });
const WebSocketServer = require('ws').Server; const WebSocketServer = require('ws').Server;
@ -44,19 +104,37 @@ function attachHMRServer({httpServer, path, packagerServer}) {
wss.on('connection', ws => { wss.on('connection', ws => {
console.log('[Hot Module Replacement] Client connected'); console.log('[Hot Module Replacement] Client connected');
const params = querystring.parse(url.parse(ws.upgradeReq.url).query); const params = querystring.parse(url.parse(ws.upgradeReq.url).query);
client = {
ws,
platform: params.platform,
bundleEntry: params.bundleEntry,
};
client.ws.on('error', e => { getDependencies(params.platform, params.bundleEntry)
console.error('[Hot Module Replacement] Unexpected error', e); .then(({dependenciesCache, shallowDependencies}) => {
disconnect(); client = {
}); ws,
platform: params.platform,
bundleEntry: params.bundleEntry,
dependenciesCache,
shallowDependencies,
};
client.ws.on('close', () => disconnect()); client.ws.on('error', e => {
console.error('[Hot Module Replacement] Unexpected error', e);
disconnect();
});
client.ws.on('close', () => disconnect());
})
.done();
}); });
} }
function arrayEquals(arrayA, arrayB) {
arrayA = arrayA || [];
arrayB = arrayB || [];
return (
arrayA.length === arrayB.length &&
arrayA.every((element, index) => {
return element === arrayB[index];
})
);
}
module.exports = attachHMRServer; module.exports = attachHMRServer;

View File

@ -310,6 +310,10 @@ class Bundler {
this._transformer.invalidateFile(filePath); this._transformer.invalidateFile(filePath);
} }
getShallowDependencies(entryFile) {
return this._resolver.getShallowDependencies(entryFile);
}
getDependencies(main, isDev, platform) { getDependencies(main, isDev, platform) {
return this._resolver.getDependencies(main, { dev: isDev, platform }); return this._resolver.getDependencies(main, { dev: isDev, platform });
} }

View File

@ -133,6 +133,14 @@ class DependencyGraph {
return this._loading; return this._loading;
} }
/**
* Returns a promise with the direct dependencies the module associated to
* the given entryPath has.
*/
getShallowDependencies(entryPath) {
return this._moduleCache.getModule(entryPath).getDependencies();
}
getDependencies(entryPath, platform) { getDependencies(entryPath, platform) {
return this.load().then(() => { return this.load().then(() => {
platform = this._getRequestPlatform(entryPath, platform); platform = this._getRequestPlatform(entryPath, platform);

View File

@ -99,6 +99,10 @@ class Resolver {
this._polyfillModuleNames = opts.polyfillModuleNames || []; this._polyfillModuleNames = opts.polyfillModuleNames || [];
} }
getShallowDependencies(entryFile) {
return this._depGraph.getShallowDependencies(entryFile);
}
getDependencies(main, options) { getDependencies(main, options) {
const opts = getDependenciesValidateOpts(options); const opts = getDependenciesValidateOpts(options);

View File

@ -246,6 +246,10 @@ class Server {
}); });
} }
getShallowDependencies(entryFile) {
return this._bundler.getShallowDependencies(entryFile);
}
getDependencies(options) { getDependencies(options) {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (!options.platform) { if (!options.platform) {