From b5d123a99cce74453024a4e08abeb6411053834f Mon Sep 17 00:00:00 2001 From: Rafael Oleza Date: Fri, 19 Jan 2018 07:59:21 -0800 Subject: [PATCH] Add generic HMR Client to metro Reviewed By: BYK Differential Revision: D6752277 fbshipit-source-id: 9d5e9e16e7d848fd12454136c6ff10a0a4fa3ae1 --- packages/metro/package.json | 1 + .../metro/src/lib/bundle-modules/HMRClient.js | 102 ++++++++++++++++++ yarn.lock | 4 + 3 files changed, 107 insertions(+) create mode 100644 packages/metro/src/lib/bundle-modules/HMRClient.js diff --git a/packages/metro/package.json b/packages/metro/package.json index dc50f593..82746c9c 100644 --- a/packages/metro/package.json +++ b/packages/metro/package.json @@ -29,6 +29,7 @@ "core-js": "^2.2.2", "debug": "^2.2.0", "denodeify": "^1.2.1", + "eventemitter3": "^3.0.0", "fbjs": "^0.8.14", "fs-extra": "^1.0.0", "graceful-fs": "^4.1.3", diff --git a/packages/metro/src/lib/bundle-modules/HMRClient.js b/packages/metro/src/lib/bundle-modules/HMRClient.js new file mode 100644 index 00000000..3ffa2099 --- /dev/null +++ b/packages/metro/src/lib/bundle-modules/HMRClient.js @@ -0,0 +1,102 @@ +/** + * 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. + * + * @flow + * @format + */ +'use strict'; + +const EventEmitter = require('eventemitter3'); + +/** + * The Hot Module Reloading Client connects to metro via Websockets, to receive + * updates from it and propagate them to the runtime to reflect the changes. + */ +class HMRClient extends EventEmitter { + _wsClient: ?WebSocket; + _url: string; + + constructor(url: string) { + super(); + + this._url = url; + } + + enable() { + if (this._wsClient) { + this.disable(); + } + + // Access the global WebSocket object only after enabling the client, + // since some polyfills do the initialization lazily. + const WSConstructor = global.WebSocket; + + // create the WebSocket connection. + this._wsClient = new WSConstructor(this._url); + + this._wsClient.onerror = e => { + this.emit('connection-error', e); + }; + + this._wsClient.onmessage = message => { + const data = JSON.parse(message.data); + + switch (data.type) { + case 'update-start': + this.emit('update-start'); + break; + + case 'update': + const {modules, sourceMappingURLs, sourceURLs} = data.body; + + this.emit('update'); + + modules.forEach(({id, code}, i) => { + code += '\n\n' + sourceMappingURLs[i]; + + // on JSC we need to inject from native for sourcemaps to work + // (Safari doesn't support `sourceMappingURL` nor any variant when + // evaluating code) but on Chrome we can simply use eval + const injectFunction = + typeof global.nativeInjectHMRUpdate === 'function' + ? global.nativeInjectHMRUpdate + : eval; // eslint-disable-line no-eval + + injectFunction(code, sourceURLs[i]); + }); + break; + + case 'update-done': + this.emit('update-done'); + break; + + case 'error': + this.emit('error', { + type: data.body.type, + message: data.body.message, + }); + break; + + default: + this.emit('error', {type: 'unknown-message', message: data}); + } + }; + } + + disable() { + if (!this._wsClient) { + return; + } + + this._wsClient.close(); + + this._wsClient = undefined; + } +} + +module.exports = HMRClient; diff --git a/yarn.lock b/yarn.lock index 61a02e0a..df453113 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1969,6 +1969,10 @@ event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" +eventemitter3@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.0.0.tgz#fc29ecf233bd19fbd527bb4089bbf665dc90c1e3" + exec-sh@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10"