packager: attachHMRServer.js: Flow

Summary:
The breakage fixed by changeset [1] could have been identified earlier if we had typing on `attachHMRServer`, so I spent some time on that. This has revealed in turn a few functions across the codebase that were incorrectly typed, and that are now fixed.

[1] packager: attachHMRServer.js: fix callsite of Server#getModuleForPath()

Reviewed By: davidaurelio

Differential Revision: D4706241

fbshipit-source-id: fc4285245921ae45d5781a47d626fc0559dba998
This commit is contained in:
Jean Lauliac 2017-03-15 06:05:59 -07:00 committed by Facebook Github Bot
parent 18c239ee22
commit 7098c450d8
5 changed files with 62 additions and 33 deletions

View File

@ -5,7 +5,10 @@
* This source code is licensed under the BSD-style license found in the * This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant * LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/ */
'use strict'; 'use strict';
const querystring = require('querystring'); const querystring = require('querystring');
@ -13,15 +16,27 @@ const url = require('url');
const {getInverseDependencies} = require('../../../packager/src//node-haste'); const {getInverseDependencies} = require('../../../packager/src//node-haste');
import type HMRBundle from '../../../packager/src/Bundler/HMRBundle';
import type Server from '../../../packager/src/Server';
import type ResolutionResponse from '../../../packager/src/node-haste/DependencyGraph/ResolutionResponse';
import type Module from '../../../packager/src/node-haste/Module';
import type {Server as HTTPServer} from 'http';
const blacklist = [ const blacklist = [
'Libraries/Utilities/HMRClient.js', 'Libraries/Utilities/HMRClient.js',
]; ];
type HMROptions = {
httpServer: HTTPServer,
path: string,
packagerServer: Server,
};
/** /**
* Attaches a WebSocket based connection to the Packager to expose * Attaches a WebSocket based connection to the Packager to expose
* Hot Module Replacement updates to the simulator. * Hot Module Replacement updates to the simulator.
*/ */
function attachHMRServer({httpServer, path, packagerServer}) { function attachHMRServer({httpServer, path, packagerServer}: HMROptions) {
let client = null; let client = null;
function disconnect() { function disconnect() {
@ -33,18 +48,25 @@ function attachHMRServer({httpServer, path, packagerServer}) {
// - The full list of dependencies. // - The full list of dependencies.
// - The shallow dependencies each file on the dependency list has // - The shallow dependencies each file on the dependency list has
// - Inverse shallow dependencies map // - Inverse shallow dependencies map
function getDependencies(platform, bundleEntry) { function getDependencies(platform: string, bundleEntry: string): Promise<{
dependenciesCache: Array<string>,
dependenciesModulesCache: {[mixed]: Module},
shallowDependencies: {[string]: Array<Module>},
inverseDependenciesCache: mixed,
resolutionResponse: ResolutionResponse,
}> {
return packagerServer.getDependencies({ return packagerServer.getDependencies({
platform: platform, platform: platform,
dev: true, dev: true,
hot: true, hot: true,
entryFile: bundleEntry, entryFile: bundleEntry,
}).then(response => { }).then(response => {
const {getModuleId} = response; /* $FlowFixMe: getModuleId might be null */
const {getModuleId}: {getModuleId: () => number} = response;
// for each dependency builds the object: // for each dependency builds the object:
// `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}` // `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
return Promise.all(Object.values(response.dependencies).map(dep => { return Promise.all(response.dependencies.map((dep: Module) => {
return dep.getName().then(depName => { return dep.getName().then(depName => {
if (dep.isAsset() || dep.isJSON()) { if (dep.isAsset() || dep.isJSON()) {
return Promise.resolve({path: dep.path, deps: []}); return Promise.resolve({path: dep.path, deps: []});
@ -54,23 +76,23 @@ function attachHMRServer({httpServer, path, packagerServer}) {
dev: true, dev: true,
hot: true, hot: true,
entryFile: dep.path entryFile: dep.path
}) }).then(deps => {
.then(deps => { return {
return { path: dep.path,
path: dep.path, name: depName,
name: depName, deps,
deps, };
}; });
});
}); });
})) }))
.then(deps => { .then((deps: Array<{path: string, name?: string, deps: Array<Module>}>) => {
// list with all the dependencies' filenames the bundle entry has // list with all the dependencies' filenames the bundle entry has
const dependenciesCache = response.dependencies.map(dep => dep.path); const dependenciesCache = response.dependencies.map(dep => dep.path);
// map from module name to path // map from module name to path
const moduleToFilenameCache = Object.create(null); const moduleToFilenameCache = Object.create(null);
deps.forEach(dep => { deps.forEach(dep => {
/* $FlowFixMe: `name` could be null, but `deps` would be as well. */
moduleToFilenameCache[dep.name] = dep.path; moduleToFilenameCache[dep.name] = dep.path;
}); });
@ -114,6 +136,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
}); });
wss.on('connection', ws => { wss.on('connection', ws => {
/* $FlowFixMe: url might be null */
const params = querystring.parse(url.parse(ws.upgradeReq.url).query); const params = querystring.parse(url.parse(ws.upgradeReq.url).query);
getDependencies(params.platform, params.bundleEntry) getDependencies(params.platform, params.bundleEntry)
@ -195,11 +218,13 @@ function attachHMRServer({httpServer, path, packagerServer}) {
return {}; return {};
} }
const nonNullClient = client;
return packagerServer.getModuleForPath(filename).then(moduleToUpdate => { return packagerServer.getModuleForPath(filename).then(moduleToUpdate => {
// build list of modules for which we'll send HMR updates // build list of modules for which we'll send HMR updates
const modulesToUpdate = [moduleToUpdate]; const modulesToUpdate = [moduleToUpdate];
Object.keys(depsModulesCache).forEach(module => { Object.keys(depsModulesCache).forEach(module => {
if (!client.dependenciesModulesCache[module]) { if (!nonNullClient.dependenciesModulesCache[module]) {
modulesToUpdate.push(depsModulesCache[module]); modulesToUpdate.push(depsModulesCache[module]);
} }
}); });
@ -215,10 +240,10 @@ function attachHMRServer({httpServer, path, packagerServer}) {
modulesToUpdate.reverse(); modulesToUpdate.reverse();
// invalidate caches // invalidate caches
client.dependenciesCache = depsCache; nonNullClient.dependenciesCache = depsCache;
client.dependenciesModulesCache = depsModulesCache; nonNullClient.dependenciesModulesCache = depsModulesCache;
client.shallowDependencies = shallowDeps; nonNullClient.shallowDependencies = shallowDeps;
client.inverseDependenciesCache = inverseDepsCache; nonNullClient.inverseDependenciesCache = inverseDepsCache;
return resolutionResponse.copy({ return resolutionResponse.copy({
dependencies: modulesToUpdate dependencies: modulesToUpdate
@ -252,7 +277,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
resolutionResponse, resolutionResponse,
}, packagerHost, httpServerAddress.port); }, packagerHost, httpServerAddress.port);
}) })
.then(bundle => { .then((bundle: HMRBundle) => {
if (!client || !bundle || bundle.isEmpty()) { if (!client || !bundle || bundle.isEmpty()) {
return; return;
} }
@ -299,6 +324,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
}); });
promise.then(() => { promise.then(() => {
/* $FlowFixMe: assume `client` non-null */
client.ws.send(JSON.stringify({type: 'update-done'})); client.ws.send(JSON.stringify({type: 'update-done'}));
}); });
}); });
@ -316,7 +342,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
}); });
} }
function arrayEquals(arrayA, arrayB) { function arrayEquals<T>(arrayA: Array<T>, arrayB: Array<T>): boolean {
arrayA = arrayA || []; arrayA = arrayA || [];
arrayB = arrayB || []; arrayB = arrayB || [];
return ( return (

View File

@ -277,7 +277,7 @@ class Bundler {
); );
} }
hmrBundle(options: {platform: ?string}, host: string, port: number) { hmrBundle(options: {platform: ?string}, host: string, port: number): Promise<HMRBundle> {
return this._bundle({ return this._bundle({
...options, ...options,
bundle: new HMRBundle({ bundle: new HMRBundle({
@ -492,7 +492,7 @@ class Bundler {
minify?: boolean, minify?: boolean,
hot?: boolean, hot?: boolean,
generateSourceMaps?: boolean, generateSourceMaps?: boolean,
}) { }): Promise<Array<Module>> {
return this.getTransformOptions( return this.getTransformOptions(
entryFile, entryFile,
{ {

View File

@ -87,7 +87,7 @@ class Resolver {
getShallowDependencies( getShallowDependencies(
entryFile: string, entryFile: string,
transformOptions: TransformOptions, transformOptions: TransformOptions,
): Array<string> { ): Promise<Array<Module>> {
return this._depGraph.getShallowDependencies(entryFile, transformOptions); return this._depGraph.getShallowDependencies(entryFile, transformOptions);
} }

View File

@ -31,6 +31,7 @@ import type {Stats} from 'fs';
import type {IncomingMessage, ServerResponse} from 'http'; import type {IncomingMessage, ServerResponse} from 'http';
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
import type Bundle from '../Bundler/Bundle'; import type Bundle from '../Bundler/Bundle';
import type HMRBundle from '../Bundler/HMRBundle';
import type {Reporter} from '../lib/reporting'; import type {Reporter} from '../lib/reporting';
import type {GetTransformOptions} from '../Bundler'; import type {GetTransformOptions} from '../Bundler';
import type GlobalTransformCache from '../lib/GlobalTransformCache'; import type GlobalTransformCache from '../lib/GlobalTransformCache';
@ -157,7 +158,7 @@ class Server {
_assetServer: AssetServer; _assetServer: AssetServer;
_bundler: Bundler; _bundler: Bundler;
_debouncedFileChangeHandler: (filePath: string) => mixed; _debouncedFileChangeHandler: (filePath: string) => mixed;
_hmrFileChangeListener: (type: string, filePath: string) => mixed; _hmrFileChangeListener: ?(type: string, filePath: string) => mixed;
_reporter: Reporter; _reporter: Reporter;
_symbolicateInWorker: Symbolicate; _symbolicateInWorker: Symbolicate;
@ -244,9 +245,7 @@ class Server {
return this._bundler.end(); return this._bundler.end();
} }
setHMRFileChangeListener( setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed) {
listener: (type: string, filePath: string) => mixed,
) {
this._hmrFileChangeListener = listener; this._hmrFileChangeListener = listener;
} }
@ -276,7 +275,7 @@ class Server {
return bundle; return bundle;
} }
buildBundleFromUrl(reqUrl: string): Promise<mixed> { buildBundleFromUrl(reqUrl: string): Promise<Bundle> {
const options = this._getOptionsFromUrl(reqUrl); const options = this._getOptionsFromUrl(reqUrl);
return this.buildBundle(options); return this.buildBundle(options);
} }
@ -285,14 +284,14 @@ class Server {
options: {platform: ?string}, options: {platform: ?string},
host: string, host: string,
port: number, port: number,
): Promise<string> { ): Promise<HMRBundle> {
return this._bundler.hmrBundle(options, host, port); return this._bundler.hmrBundle(options, host, port);
} }
getShallowDependencies(options: { getShallowDependencies(options: {
entryFile: string, entryFile: string,
platform?: string, platform?: string,
}): Promise<mixed> { }): Promise<Array<Module>> {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (!options.platform) { if (!options.platform) {
options.platform = getPlatformExtension(options.entryFile); options.platform = getPlatformExtension(options.entryFile);
@ -335,10 +334,11 @@ class Server {
// If Hot Loading is enabled avoid rebuilding bundles and sending live // If Hot Loading is enabled avoid rebuilding bundles and sending live
// updates. Instead, send the HMR updates right away and clear the bundles // updates. Instead, send the HMR updates right away and clear the bundles
// cache so that if the user reloads we send them a fresh bundle // cache so that if the user reloads we send them a fresh bundle
if (this._hmrFileChangeListener) { const {_hmrFileChangeListener} = this;
if (_hmrFileChangeListener) {
// Clear cached bundles in case user reloads // Clear cached bundles in case user reloads
this._clearBundles(); this._clearBundles();
this._hmrFileChangeListener(type, filePath); _hmrFileChangeListener(type, filePath);
return; return;
} else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) { } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) {
// node module resolution can be affected by added or removed files // node module resolution can be affected by added or removed files

View File

@ -173,7 +173,10 @@ class DependencyGraph extends EventEmitter {
* Returns a promise with the direct dependencies the module associated to * Returns a promise with the direct dependencies the module associated to
* the given entryPath has. * the given entryPath has.
*/ */
getShallowDependencies(entryPath: string, transformOptions: TransformOptions) { getShallowDependencies(
entryPath: string,
transformOptions: TransformOptions,
): Promise<Array<Module>> {
return this._moduleCache return this._moduleCache
.getModule(entryPath) .getModule(entryPath)
.getDependencies(transformOptions); .getDependencies(transformOptions);