Make HMR faster

Summary:
public

At the moment, when the user changes a file we end up pulling the dependencies of the entry point to build the bundle. This could take a long time if the bundle is big. To avoid it, lets introduce a new parameter to `getDependencies` to be able to avoid processing the modules recursively and reuse the resolution responseto build the bundle.

Reviewed By: davidaurelio

Differential Revision: D2862850

fb-gh-sync-id: b8ae2b811a8ae9aec5612f9655d1c762671ce730
This commit is contained in:
Martín Bigio 2016-01-29 10:14:37 -08:00 committed by facebook-github-bot-9
parent c8a0a3eff6
commit 65b8ff17f3
8 changed files with 112 additions and 36 deletions

View File

@ -74,6 +74,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
resolutionResponse: response,
};
});
});
@ -123,7 +124,23 @@ function attachHMRServer({httpServer, path, packagerServer}) {
// to the client may have changed
const oldDependencies = client.shallowDependencies[filename];
if (arrayEquals(deps, oldDependencies)) {
return [packagerServer.getModuleForPath(filename)];
// Need to create a resolution response to pass to the bundler
// to process requires after transform. By providing a
// specific response we can compute a non recursive one which
// is the least we need and improve performance.
return packagerServer.getDependencies({
platform: client.platform,
dev: true,
entryFile: filename,
recursive: true,
}).then(response => {
const module = packagerServer.getModuleForPath(filename);
return {
modulesToUpdate: [module],
resolutionResponse: response,
};
});
}
// if there're new dependencies compare the full list of
@ -133,9 +150,10 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dependenciesCache,
dependenciesModulesCache,
shallowDependencies,
resolutionResponse,
}) => {
if (!client) {
return [];
return {};
}
// build list of modules for which we'll send HMR updates
@ -151,10 +169,13 @@ function attachHMRServer({httpServer, path, packagerServer}) {
client.dependenciesModulesCache = dependenciesModulesCache;
client.shallowDependencies = shallowDependencies;
return modulesToUpdate;
return {
modulesToUpdate,
resolutionResponse,
};
});
})
.then(modulesToUpdate => {
.then(({modulesToUpdate, resolutionResponse}) => {
if (!client) {
return;
}
@ -168,6 +189,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
entryFile: client.bundleEntry,
platform: client.platform,
modules: modulesToUpdate,
resolutionResponse,
})
})
.then(bundle => {

View File

@ -206,11 +206,12 @@ describe('Bundler', function() {
});
pit('gets the list of dependencies from the resolver', function() {
return bundler.getDependencies('/root/foo.js', true)
.then(
() => expect(getDependencies)
.toBeCalledWith('/root/foo.js', { dev: true })
);
return bundler.getDependencies('/root/foo.js', true).then(() =>
expect(getDependencies).toBeCalledWith(
'/root/foo.js',
{ dev: true, recursive: true },
)
);
});
describe('getOrderedDependencyPaths', () => {

View File

@ -235,14 +235,48 @@ class Bundler {
unbundle: isUnbundle,
hot: hot,
entryModuleOnly,
resolutionResponse,
}) {
const findEventId = Activity.startEvent('find dependencies');
let transformEventId;
const moduleSystemDeps = includeSystemDependencies
? this._resolver.getModuleSystemDependencies(
{ dev: isDev, platform, isUnbundle }
)
: [];
const findModules = () => {
const findEventId = Activity.startEvent('find dependencies');
return this.getDependencies(entryFile, isDev, platform).then(response => {
Activity.endEvent(findEventId);
bundle.setMainModuleId(response.mainModuleId);
bundle.setMainModuleName(response.mainModuleId);
if (!entryModuleOnly && bundle.setNumPrependedModules) {
bundle.setNumPrependedModules(
response.numPrependedDependencies + moduleSystemDeps.length
);
}
return {
response,
modulesToProcess: response.dependencies,
};
});
};
const useProvidedModules = () => {
const moduleId = this._resolver.getModuleForPath(entryFile);
bundle.setMainModuleId(moduleId);
bundle.setMainModuleName(moduleId);
return Promise.resolve({
response: resolutionResponse,
modulesToProcess: modules
});
};
return (
modules ? useProvidedModules() : findModules()
).then(({response, modulesToProcess}) => {
return this.getDependencies(entryFile, isDev, platform).then((response) => {
Activity.endEvent(findEventId);
bundle.setMainModuleId(response.mainModuleId);
bundle.setMainModuleName(response.mainModuleId);
transformEventId = Activity.startEvent('transform');
let dependencies;
@ -259,10 +293,6 @@ class Bundler {
const modulesToProcess = modules || response.dependencies;
dependencies = moduleSystemDeps.concat(modulesToProcess);
bundle.setNumPrependedModules && bundle.setNumPrependedModules(
response.numPrependedDependencies + moduleSystemDeps.length
);
}
let bar;
@ -280,7 +310,6 @@ class Bundler {
module => {
return this._transformModule(
bundle,
response,
module,
platform,
isDev,
@ -347,7 +376,6 @@ class Bundler {
response.dependencies.map(
module => this._transformModule(
bundle,
response,
module,
platform,
isDev,
@ -418,8 +446,15 @@ class Bundler {
return this._resolver.getModuleForPath(entryFile);
}
getDependencies(main, isDev, platform) {
return this._resolver.getDependencies(main, { dev: isDev, platform });
getDependencies(main, isDev, platform, recursive = true) {
return this._resolver.getDependencies(
main,
{
dev: isDev,
platform,
recursive,
},
);
}
getOrderedDependencyPaths({ entryFile, dev, platform }) {
@ -454,7 +489,7 @@ class Bundler {
);
}
_transformModule(bundle, response, module, platform = null, dev = true, hot = false) {
_transformModule(bundle, module, platform = null, dev = true, hot = false) {
if (module.isAsset_DEPRECATED()) {
return this._generateAssetModule_DEPRECATED(bundle, module);
} else if (module.isAsset()) {

View File

@ -103,7 +103,7 @@ class ResolutionRequest {
);
}
getOrderedDependencies(response, mocksPattern) {
getOrderedDependencies(response, mocksPattern, recursive = true) {
return this._getAllMocks(mocksPattern).then(allMocks => {
const entry = this._moduleCache.getModule(this._entryPath);
const mocks = Object.create(null);
@ -163,7 +163,9 @@ class ResolutionRequest {
p = p.then(() => {
if (!visited[modDep.hash()]) {
visited[modDep.hash()] = true;
return collect(modDep);
if (recursive) {
return collect(modDep);
}
}
return null;
});

View File

@ -158,7 +158,7 @@ class DependencyGraph {
return this._moduleCache.getModule(entryFile);
}
getDependencies(entryPath, platform) {
getDependencies(entryPath, platform, recursive = true) {
return this.load().then(() => {
platform = this._getRequestPlatform(entryPath, platform);
const absPath = this._getAbsolutePath(entryPath);
@ -177,7 +177,11 @@ class DependencyGraph {
const response = new ResolutionResponse();
return Promise.all([
req.getOrderedDependencies(response, this._opts.mocksPattern),
req.getOrderedDependencies(
response,
this._opts.mocksPattern,
recursive,
),
req.getAsyncDependencies(response),
]).then(() => response);
});

View File

@ -186,7 +186,8 @@ describe('Resolver', function() {
return depResolver.getDependencies('/root/index.js', { dev: true })
.then(function(result) {
expect(result.mainModuleId).toEqual('index');
expect(DependencyGraph.mock.instances[0].getDependencies).toBeCalledWith('/root/index.js', undefined);
expect(DependencyGraph.mock.instances[0].getDependencies)
.toBeCalledWith('/root/index.js', undefined, true);
expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
expect(result.dependencies[result.dependencies.length - 1])
.toBe(module);

View File

@ -64,6 +64,10 @@ const getDependenciesValidateOpts = declareOpts({
type: 'boolean',
default: false
},
recursive: {
type: 'boolean',
default: true,
},
});
class Resolver {
@ -116,15 +120,17 @@ class Resolver {
getDependencies(main, options) {
const opts = getDependenciesValidateOpts(options);
return this._depGraph.getDependencies(main, opts.platform).then(
resolutionResponse => {
this._getPolyfillDependencies().reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
return this._depGraph.getDependencies(
main,
opts.platform,
opts.recursive,
).then(resolutionResponse => {
this._getPolyfillDependencies().reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
return resolutionResponse.finalize();
}
);
return resolutionResponse.finalize();
});
}
getModuleSystemDependencies(options) {

View File

@ -137,6 +137,10 @@ const dependencyOpts = declareOpts({
type: 'string',
required: true,
},
recursive: {
type: 'boolean',
default: true,
},
});
class Server {
@ -269,6 +273,7 @@ class Server {
opts.entryFile,
opts.dev,
opts.platform,
opts.recursive,
);
});
}