mirror of https://github.com/status-im/metro.git
Create brand-new HMR server using the Delta Bundler
Reviewed By: mjesun Differential Revision: D5765024 fbshipit-source-id: 3f51ab1564a93b8268e51c0a0a97ea3ef5bd6681
This commit is contained in:
parent
387a55de40
commit
0265758e5e
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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;
|
|
@ -251,6 +251,9 @@ class TerminalReporter {
|
|||
case 'worker_stderr_chunk':
|
||||
this._logWorkerChunk('stderr', event.chunk);
|
||||
break;
|
||||
case 'hmr_client_error':
|
||||
this._logHmrClientError(event.error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,6 +399,15 @@ class TerminalReporter {
|
|||
.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
|
||||
* corresponding JSON reporter easily and have a consistent reporting.
|
||||
|
|
|
@ -85,6 +85,10 @@ export type ReportableEvent =
|
|||
| {
|
||||
type: 'worker_stderr_chunk',
|
||||
chunk: string,
|
||||
}
|
||||
| {
|
||||
type: 'hmr_client_error',
|
||||
error: Error,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue