From 7098c450d8274e441135405adf4e83871c74a8af Mon Sep 17 00:00:00 2001 From: Jean Lauliac Date: Wed, 15 Mar 2017 06:05:59 -0700 Subject: [PATCH] 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 --- local-cli/server/util/attachHMRServer.js | 66 +++++++++++++++++------- packager/src/Bundler/index.js | 4 +- packager/src/Resolver/index.js | 2 +- packager/src/Server/index.js | 18 +++---- packager/src/node-haste/index.js | 5 +- 5 files changed, 62 insertions(+), 33 deletions(-) diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js index 9e1dd4241..02c4f45ec 100644 --- a/local-cli/server/util/attachHMRServer.js +++ b/local-cli/server/util/attachHMRServer.js @@ -5,7 +5,10 @@ * 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 * of patent rights can be found in the PATENTS file in the same directory. + * + * @flow */ + 'use strict'; const querystring = require('querystring'); @@ -13,15 +16,27 @@ const url = require('url'); 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 = [ 'Libraries/Utilities/HMRClient.js', ]; +type HMROptions = { + httpServer: HTTPServer, + path: string, + packagerServer: Server, +}; + /** * Attaches a WebSocket based connection to the Packager to expose * Hot Module Replacement updates to the simulator. */ -function attachHMRServer({httpServer, path, packagerServer}) { +function attachHMRServer({httpServer, path, packagerServer}: HMROptions) { let client = null; function disconnect() { @@ -33,18 +48,25 @@ function attachHMRServer({httpServer, path, packagerServer}) { // - The full list of dependencies. // - The shallow dependencies each file on the dependency list has // - Inverse shallow dependencies map - function getDependencies(platform, bundleEntry) { + function getDependencies(platform: string, bundleEntry: string): Promise<{ + dependenciesCache: Array, + dependenciesModulesCache: {[mixed]: Module}, + shallowDependencies: {[string]: Array}, + inverseDependenciesCache: mixed, + resolutionResponse: ResolutionResponse, + }> { return packagerServer.getDependencies({ platform: platform, dev: true, hot: true, entryFile: bundleEntry, }).then(response => { - const {getModuleId} = response; + /* $FlowFixMe: getModuleId might be null */ + const {getModuleId}: {getModuleId: () => number} = response; // for each dependency builds the object: // `{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 => { if (dep.isAsset() || dep.isJSON()) { return Promise.resolve({path: dep.path, deps: []}); @@ -54,23 +76,23 @@ function attachHMRServer({httpServer, path, packagerServer}) { dev: true, hot: true, entryFile: dep.path - }) - .then(deps => { - return { - path: dep.path, - name: depName, - deps, - }; - }); + }).then(deps => { + return { + path: dep.path, + name: depName, + deps, + }; + }); }); })) - .then(deps => { + .then((deps: Array<{path: string, name?: string, deps: Array}>) => { // list with all the dependencies' filenames the bundle entry has const dependenciesCache = response.dependencies.map(dep => dep.path); // map from module name to path const moduleToFilenameCache = Object.create(null); deps.forEach(dep => { + /* $FlowFixMe: `name` could be null, but `deps` would be as well. */ moduleToFilenameCache[dep.name] = dep.path; }); @@ -114,6 +136,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { }); wss.on('connection', ws => { + /* $FlowFixMe: url might be null */ const params = querystring.parse(url.parse(ws.upgradeReq.url).query); getDependencies(params.platform, params.bundleEntry) @@ -195,11 +218,13 @@ function attachHMRServer({httpServer, path, packagerServer}) { return {}; } + const nonNullClient = client; + return packagerServer.getModuleForPath(filename).then(moduleToUpdate => { // build list of modules for which we'll send HMR updates const modulesToUpdate = [moduleToUpdate]; Object.keys(depsModulesCache).forEach(module => { - if (!client.dependenciesModulesCache[module]) { + if (!nonNullClient.dependenciesModulesCache[module]) { modulesToUpdate.push(depsModulesCache[module]); } }); @@ -215,10 +240,10 @@ function attachHMRServer({httpServer, path, packagerServer}) { modulesToUpdate.reverse(); // invalidate caches - client.dependenciesCache = depsCache; - client.dependenciesModulesCache = depsModulesCache; - client.shallowDependencies = shallowDeps; - client.inverseDependenciesCache = inverseDepsCache; + nonNullClient.dependenciesCache = depsCache; + nonNullClient.dependenciesModulesCache = depsModulesCache; + nonNullClient.shallowDependencies = shallowDeps; + nonNullClient.inverseDependenciesCache = inverseDepsCache; return resolutionResponse.copy({ dependencies: modulesToUpdate @@ -252,7 +277,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { resolutionResponse, }, packagerHost, httpServerAddress.port); }) - .then(bundle => { + .then((bundle: HMRBundle) => { if (!client || !bundle || bundle.isEmpty()) { return; } @@ -299,6 +324,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { }); promise.then(() => { + /* $FlowFixMe: assume `client` non-null */ client.ws.send(JSON.stringify({type: 'update-done'})); }); }); @@ -316,7 +342,7 @@ function attachHMRServer({httpServer, path, packagerServer}) { }); } -function arrayEquals(arrayA, arrayB) { +function arrayEquals(arrayA: Array, arrayB: Array): boolean { arrayA = arrayA || []; arrayB = arrayB || []; return ( diff --git a/packager/src/Bundler/index.js b/packager/src/Bundler/index.js index ce940a7f6..878e39ee2 100644 --- a/packager/src/Bundler/index.js +++ b/packager/src/Bundler/index.js @@ -277,7 +277,7 @@ class Bundler { ); } - hmrBundle(options: {platform: ?string}, host: string, port: number) { + hmrBundle(options: {platform: ?string}, host: string, port: number): Promise { return this._bundle({ ...options, bundle: new HMRBundle({ @@ -492,7 +492,7 @@ class Bundler { minify?: boolean, hot?: boolean, generateSourceMaps?: boolean, - }) { + }): Promise> { return this.getTransformOptions( entryFile, { diff --git a/packager/src/Resolver/index.js b/packager/src/Resolver/index.js index e73706bf8..55e899a13 100644 --- a/packager/src/Resolver/index.js +++ b/packager/src/Resolver/index.js @@ -87,7 +87,7 @@ class Resolver { getShallowDependencies( entryFile: string, transformOptions: TransformOptions, - ): Array { + ): Promise> { return this._depGraph.getShallowDependencies(entryFile, transformOptions); } diff --git a/packager/src/Server/index.js b/packager/src/Server/index.js index e4b7e9f9e..0750e6aa6 100644 --- a/packager/src/Server/index.js +++ b/packager/src/Server/index.js @@ -31,6 +31,7 @@ import type {Stats} from 'fs'; import type {IncomingMessage, ServerResponse} from 'http'; import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; import type Bundle from '../Bundler/Bundle'; +import type HMRBundle from '../Bundler/HMRBundle'; import type {Reporter} from '../lib/reporting'; import type {GetTransformOptions} from '../Bundler'; import type GlobalTransformCache from '../lib/GlobalTransformCache'; @@ -157,7 +158,7 @@ class Server { _assetServer: AssetServer; _bundler: Bundler; _debouncedFileChangeHandler: (filePath: string) => mixed; - _hmrFileChangeListener: (type: string, filePath: string) => mixed; + _hmrFileChangeListener: ?(type: string, filePath: string) => mixed; _reporter: Reporter; _symbolicateInWorker: Symbolicate; @@ -244,9 +245,7 @@ class Server { return this._bundler.end(); } - setHMRFileChangeListener( - listener: (type: string, filePath: string) => mixed, - ) { + setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed) { this._hmrFileChangeListener = listener; } @@ -276,7 +275,7 @@ class Server { return bundle; } - buildBundleFromUrl(reqUrl: string): Promise { + buildBundleFromUrl(reqUrl: string): Promise { const options = this._getOptionsFromUrl(reqUrl); return this.buildBundle(options); } @@ -285,14 +284,14 @@ class Server { options: {platform: ?string}, host: string, port: number, - ): Promise { + ): Promise { return this._bundler.hmrBundle(options, host, port); } getShallowDependencies(options: { entryFile: string, platform?: string, - }): Promise { + }): Promise> { return Promise.resolve().then(() => { if (!options.platform) { options.platform = getPlatformExtension(options.entryFile); @@ -335,10 +334,11 @@ class Server { // If Hot Loading is enabled avoid rebuilding bundles and sending live // 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 - if (this._hmrFileChangeListener) { + const {_hmrFileChangeListener} = this; + if (_hmrFileChangeListener) { // Clear cached bundles in case user reloads this._clearBundles(); - this._hmrFileChangeListener(type, filePath); + _hmrFileChangeListener(type, filePath); return; } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) { // node module resolution can be affected by added or removed files diff --git a/packager/src/node-haste/index.js b/packager/src/node-haste/index.js index 6af6a1901..fef220c58 100644 --- a/packager/src/node-haste/index.js +++ b/packager/src/node-haste/index.js @@ -173,7 +173,10 @@ class DependencyGraph extends EventEmitter { * Returns a promise with the direct dependencies the module associated to * the given entryPath has. */ - getShallowDependencies(entryPath: string, transformOptions: TransformOptions) { + getShallowDependencies( + entryPath: string, + transformOptions: TransformOptions, + ): Promise> { return this._moduleCache .getModule(entryPath) .getDependencies(transformOptions);