Create brand-new HMR server using the Delta Bundler

Reviewed By: mjesun

Differential Revision: D5765024

fbshipit-source-id: 3f51ab1564a93b8268e51c0a0a97ea3ef5bd6681
This commit is contained in:
Rafael Oleza 2017-09-05 07:10:43 -07:00 committed by Facebook Github Bot
parent 387a55de40
commit 0265758e5e
4 changed files with 182 additions and 0 deletions

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* 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.
*
* @format
* @flow
*/
'use strict';
import type {Options as BundleOptions} from '../DeltaBundler';
/**
* Module to easily create the needed configuration parameters needed for the
* bundler for HMR (since a lot of params are not relevant in this use case).
*/
module.exports = function getBundlingOptionsForHmr(
entryFile: string,
platform: string,
): BundleOptions {
// These are the really meaningful bundling options. The others below are
// not relevant for HMR.
const mainOptions = {
deltaBundleId: null,
entryFile,
hot: true,
minify: false,
platform,
wrapModules: false,
};
return {
...mainOptions,
assetPlugins: [],
dev: true,
entryModuleOnly: false,
excludeSource: false,
generateSourceMaps: false,
inlineSourceMap: false,
isolateModuleIDs: false,
onProgress: null,
resolutionResponse: null,
runBeforeMainModule: [],
runModule: false,
sourceMapUrl: '',
unbundle: false,
};
};

View File

@ -0,0 +1,114 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* 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.
*
* @format
* @flow
*/
'use strict';
const getBundlingOptionsForHmr = require('./getBundlingOptionsForHmr');
const querystring = require('querystring');
const url = require('url');
import type DeltaTransformer from '../DeltaBundler/DeltaTransformer';
import type PackagerServer from '../Server';
import type {Reporter} from '../lib/reporting';
type Client = {|
deltaTransformer: DeltaTransformer,
sendFn: (data: string) => mixed,
|};
/**
* The HmrServer (Hot Module Reloading) implements a lightweight interface
* to communicate easily to the logic in the React Native repository (which
* is the one that handles the Web Socket connections).
*
* This interface allows the HmrServer to hook its own logic to WS clients
* getting connected, disconnected or having errors (through the
* `onClientConnect`, `onClientDisconnect` and `onClientError` methods).
*/
class HmrServer<TClient: Client> {
_packagerServer: PackagerServer;
_reporter: Reporter;
constructor(packagerServer: PackagerServer, reporter: Reporter) {
this._packagerServer = packagerServer;
this._reporter = reporter;
}
async onClientConnect(
clientUrl: string,
sendFn: (data: string) => mixed,
): Promise<Client> {
const {bundleEntry, platform} = querystring.parse(
/* $FlowFixMe: url might be null */
url.parse(clientUrl).query,
);
// Create a new DeltaTransformer for each client. Once the clients are
// modified to support Delta Bundles, they'll be able to pass the
// DeltaBundleId param through the WS connection and we'll be able to share
// the same DeltaTransformer between the WS connection and the HTTP one.
const deltaBundler = this._packagerServer.getDeltaBundler();
const {deltaTransformer} = await deltaBundler.getDeltaTransformer(
getBundlingOptionsForHmr(bundleEntry, platform),
);
// Trigger an initial build to start up the DeltaTransformer.
await deltaTransformer.getDelta();
// Listen to file changes.
const client = {sendFn, deltaTransformer};
deltaTransformer.on('change', this._handleFileChange.bind(this, client));
return client;
}
onClientError(client: TClient, e: Error) {
this._reporter.update({
type: 'hmr_client_error',
error: e,
});
this.onClientDisconnect(client);
}
onClientDisconnect(client: TClient) {
// We can safely remove all listeners from the delta transformer since the
// transformer is not shared between clients.
client.deltaTransformer.removeAllListeners('change');
}
async _handleFileChange(client: Client) {
client.sendFn(JSON.stringify({type: 'update-start'}));
client.sendFn(JSON.stringify(await this._prepareResponse(client)));
client.sendFn(JSON.stringify({type: 'update-done'}));
}
async _prepareResponse(client: Client): Promise<{type: string, body: {}}> {
const result = await client.deltaTransformer.getDelta();
const modules = [];
for (const id in result.delta) {
modules.push({id, code: result.delta[id]});
}
return {
type: 'update',
body: {
modules,
inverseDependencies: result.inverseDependencies,
sourceURLs: {},
sourceMappingURLs: {}, // TODO: handle Source Maps
},
};
}
}
module.exports = HmrServer;

View File

@ -251,6 +251,9 @@ class TerminalReporter {
case 'worker_stderr_chunk': case 'worker_stderr_chunk':
this._logWorkerChunk('stderr', event.chunk); this._logWorkerChunk('stderr', event.chunk);
break; break;
case 'hmr_client_error':
this._logHmrClientError(event.error);
break;
} }
} }
@ -396,6 +399,15 @@ class TerminalReporter {
.join('\n'); .join('\n');
} }
_logHmrClientError(e: Error): void {
reporting.logError(
this.terminal,
'A WebSocket client got a connection error. Please reload your device ' +
'to get HMR working again: %s',
e,
);
}
/** /**
* Single entry point for reporting events. That allows us to implement the * Single entry point for reporting events. That allows us to implement the
* corresponding JSON reporter easily and have a consistent reporting. * corresponding JSON reporter easily and have a consistent reporting.

View File

@ -85,6 +85,10 @@ export type ReportableEvent =
| { | {
type: 'worker_stderr_chunk', type: 'worker_stderr_chunk',
chunk: string, chunk: string,
}
| {
type: 'hmr_client_error',
error: Error,
}; };
/** /**