mirror of https://github.com/status-im/metro.git
Cleanup HMR code, rename to MetroClient, add test
Reviewed By: BYK Differential Revision: D7083982 fbshipit-source-id: ac8193b8025e8ff37d7316e11cafc09b58b7d358
This commit is contained in:
parent
45dfd925fc
commit
063a6867b1
|
@ -9,92 +9,4 @@
|
|||
*/
|
||||
'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;
|
||||
module.exports = require('./MetroClient');
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('eventemitter3');
|
||||
|
||||
/**
|
||||
* The Hot Module Reloading Client connects to Metro via WebSocket, to receive
|
||||
* updates from it and propagate them to the runtime to reflect the changes.
|
||||
*/
|
||||
class MetroClient extends EventEmitter {
|
||||
_ws: ?WebSocket;
|
||||
_url: string;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
enable() {
|
||||
if (this._ws) {
|
||||
this.disable();
|
||||
}
|
||||
|
||||
// Access the global WebSocket object only after enabling the client,
|
||||
// since some polyfills do the initialization lazily.
|
||||
this._ws = new global.WebSocket(this._url);
|
||||
this._ws.onerror = error => {
|
||||
this.emit('connection-error', error);
|
||||
};
|
||||
this._ws.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];
|
||||
|
||||
// In 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._ws) {
|
||||
this._ws.close();
|
||||
this._ws = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetroClient;
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @emails oncall+js_foundation
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const MetroClient = require('../MetroClient');
|
||||
|
||||
let mockSocket = null;
|
||||
global.WebSocket = jest.fn(() => {
|
||||
mockSocket = {
|
||||
onerror: jest.fn(),
|
||||
onmessage: jest.fn(),
|
||||
close: jest.fn(),
|
||||
mockEmit: (type, data) => {
|
||||
if (mockSocket) {
|
||||
if (type === 'error') {
|
||||
mockSocket.onerror(data);
|
||||
} else {
|
||||
mockSocket.onmessage(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
return mockSocket;
|
||||
});
|
||||
|
||||
beforeEach(() => (mockSocket = null));
|
||||
|
||||
test('connects to a WebSocket and listens to messages', () => {
|
||||
const client = new MetroClient('wss://banana.com/phone');
|
||||
|
||||
const mockError = {
|
||||
message: 'An error occurred.',
|
||||
};
|
||||
const mockErrorCallback = jest.fn(data => expect(data).toEqual(mockError));
|
||||
const mockUpdateStartCallback = jest.fn();
|
||||
|
||||
expect(mockSocket).toBeNull();
|
||||
client.on('connection-error', mockErrorCallback);
|
||||
client.on('update-start', mockUpdateStartCallback);
|
||||
client.enable();
|
||||
if (!mockSocket) {
|
||||
throw new Error('mockSocket was not set when opening the connection.');
|
||||
}
|
||||
|
||||
mockSocket.mockEmit('message', {
|
||||
data: JSON.stringify({
|
||||
type: 'update-start',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(mockUpdateStartCallback).toBeCalled();
|
||||
|
||||
mockSocket.mockEmit('error', mockError);
|
||||
expect(mockErrorCallback).toBeCalled();
|
||||
|
||||
expect(mockSocket.close).not.toBeCalled();
|
||||
client.disable();
|
||||
expect(mockSocket.close).toBeCalled();
|
||||
});
|
Loading…
Reference in New Issue