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':
|
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.
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue