Enable HMR

Reviewed By: svcscm

Differential Revision: D2932137

fb-gh-sync-id: 8bfab09aaac22ae498ac4fa896eee495111abc0d
shipit-source-id: 8bfab09aaac22ae498ac4fa896eee495111abc0d
This commit is contained in:
Adam Miskiewicz 2016-02-12 08:09:43 -08:00 committed by facebook-github-bot-4
parent 8eddead868
commit e018aa3100
10 changed files with 137 additions and 33 deletions

View File

@ -10,6 +10,7 @@
*/
'use strict';
const Platform = require('Platform');
const invariant = require('invariant');
const processColor = require('processColor');
@ -18,23 +19,26 @@ const processColor = require('processColor');
* runtime to reflects those changes.
*/
const HMRClient = {
enable(platform, bundleEntry) {
enable(platform, bundleEntry, host, port) {
invariant(platform, 'Missing required parameter `platform`');
invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
// TODO(martinb) receive host and port as parameters
const host = 'localhost';
const port = '8081';
invariant(host, 'Missing required paramenter `host`');
// need to require WebSocket inside of `enable` function because
// this module is defined as a `polyfillGlobal`.
// See `InitializeJavascriptAppEngine.js`
const WebSocket = require('WebSocket');
const activeWS = new WebSocket(
`ws://${host}:${port}/hot?platform=${platform}&` +
`bundleEntry=${bundleEntry.replace('.bundle', '.js')}`
);
const wsHostPort = port !== null && port !== ''
? `${host}:${port}`
: host;
// Build the websocket url
const wsUrl = `ws://${wsHostPort}/hot?` +
`platform=${platform}&` +
`bundleEntry=${bundleEntry.replace('.bundle', '.js')}`;
const activeWS = new WebSocket(wsUrl);
activeWS.onerror = (e) => {
throw new Error(
`Hot loading isn't working because it cannot connect to the development server.
@ -50,10 +54,16 @@ Error: ${e.message}`
);
};
activeWS.onmessage = ({data}) => {
const DevLoadingView = require('NativeModules').DevLoadingView;
let DevLoadingView = require('NativeModules').DevLoadingView;
if (!DevLoadingView) {
DevLoadingView = {
showMessage() {},
hide() {},
};
}
data = JSON.parse(data);
switch(data.type) {
switch (data.type) {
case 'update-start': {
DevLoadingView.showMessage(
'Hot Loading...',
@ -67,8 +77,13 @@ Error: ${e.message}`
const sourceMappingURLs = data.body.sourceMappingURLs;
const sourceURLs = data.body.sourceURLs;
const RCTRedBox = require('NativeModules').RedBox;
RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
if (Platform.OS === 'ios') {
const RCTRedBox = require('NativeModules').RedBox;
RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
} else {
const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
}
modules.forEach((code, i) => {
code = code + '\n\n' + sourceMappingURLs[i];
@ -82,8 +97,8 @@ Error: ${e.message}`
// 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 __injectHMRUpdate === 'function'
? __injectHMRUpdate
const injectFunction = typeof global.nativeInjectHMRUpdate === 'function'
? global.nativeInjectHMRUpdate
: eval;
injectFunction(code, sourceURLs[i]);

View File

@ -461,7 +461,9 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
[self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path]];
NSString *host = self.bundleURL.host;
NSNumber *port = self.bundleURL.port;
[self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path, host, RCTNullIfNil(port)]];
}
#endif

View File

@ -341,6 +341,20 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
name:event
object:nil];
}
// Inject handler used by HMR
[self addSynchronousHookWithName:@"nativeInjectHMRUpdate" usingBlock:^(NSString *sourceCode, NSString *sourceCodeURL) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return;
}
JSStringRef execJSString = JSStringCreateWithUTF8CString(sourceCode.UTF8String);
JSStringRef jsURL = JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, NULL);
JSStringRelease(jsURL);
JSStringRelease(execJSString);
}];
#endif
}
@ -513,21 +527,6 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
RCTAssertParam(sourceURL);
__weak RCTJSCExecutor *weakSelf = self;
#if RCT_DEV
_context.context[@"__injectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
JSStringRef execJSString = JSStringCreateWithUTF8CString(sourceCode.UTF8String);
JSStringRef jsURL = JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, NULL);
JSStringRelease(jsURL);
JSStringRelease(execJSString);
};
#endif
[self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
RCTJSCExecutor *strongSelf = weakSelf;

View File

@ -19,6 +19,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.core.ExceptionsManagerModule;
import com.facebook.react.devsupport.HMRClient;
import com.facebook.react.modules.core.JSTimersExecution;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
import com.facebook.react.modules.core.Timing;
@ -95,6 +96,7 @@ import com.facebook.systrace.Systrace;
RCTNativeAppEventEmitter.class,
AppRegistry.class,
com.facebook.react.bridge.Systrace.class,
HMRClient.class,
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
}

View File

@ -25,6 +25,7 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
void showNewJSError(String message, ReadableArray details, int errorCookie);
void updateJSError(final String message, final ReadableArray details, final int errorCookie);
void hideRedboxDialog();
void showDevOptionsDialog();
void setDevSupportEnabled(boolean isDevSupportEnabled);
boolean getDevSupportEnabled();

View File

@ -13,6 +13,8 @@ import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
@ -40,7 +42,6 @@ import com.facebook.react.R;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
@ -216,6 +217,14 @@ public class DevSupportManagerImpl implements DevSupportManager {
});
}
@Override
public void hideRedboxDialog() {
// dismiss redbox if exists
if (mRedBoxDialog != null) {
mRedBoxDialog.dismiss();
}
}
private void showNewError(
final String message,
final StackFrame[] stack,
@ -522,6 +531,18 @@ public class DevSupportManagerImpl implements DevSupportManager {
mDebugOverlayController = new DebugOverlayController(reactContext);
}
if (mDevSettings.isHotModuleReplacementEnabled() && mCurrentContext != null) {
try {
URL sourceUrl = new URL(getSourceUrl());
String path = sourceUrl.getPath().substring(1); // strip initial slash in path
String host = sourceUrl.getHost();
int port = sourceUrl.getPort();
mCurrentContext.getJSModule(HMRClient.class).enable("android", path, host, port);
} catch (MalformedURLException e) {
showNewJavaError(e.getMessage(), e);
}
}
reloadSettings();
}

View File

@ -46,6 +46,11 @@ public class DisabledDevSupportManager implements DevSupportManager {
}
@Override
public void hideRedboxDialog() {
}
@Override
public void showDevOptionsDialog() {

View File

@ -0,0 +1,32 @@
/**
* 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.
*/
package com.facebook.react.devsupport;
import com.facebook.react.bridge.JavaScriptModule;
/**
* JS module interface for HMRClient
*
* The HMR(Hot Module Replacement)Client allows for the application to receive updates
* from the packager server (over a web socket), allowing for injection of JavaScript to
* the running application (without a refresh).
*/
public interface HMRClient extends JavaScriptModule {
/**
* Enable the HMRClient so that the client will receive updates
* from the packager server.
* @param platform The platform in which HMR updates will be enabled. Should be "android".
* @param bundleEntry The path to the bundle entry file (e.g. index.ios.bundle).
* @param host The host that the HMRClient should communicate with.
* @param port The port that the HMRClient should communicate with on the host.
*/
void enable(String platform, String bundleEntry, String host, int port);
}

View File

@ -77,4 +77,11 @@ public class ExceptionsManagerModule extends BaseJavaModule {
mDevSupportManager.updateJSError(title, details, exceptionId);
}
}
@ReactMethod
public void dismissRedbox() {
if (mDevSupportManager.getDevSupportEnabled()) {
mDevSupportManager.hideRedboxDialog();
}
}
}

View File

@ -59,6 +59,13 @@ static JSValueRef nativePerformanceNow(
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static JSValueRef nativeInjectHMRUpdate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[],
JSValueRef *exception);
static std::string executeJSCallWithJSC(
JSGlobalContextRef ctx,
@ -93,6 +100,7 @@ JSCExecutor::JSCExecutor(FlushImmediateCallback cb, const std::string& cacheDir)
installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);
installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
installGlobalFunction(m_context, "nativeLoggingHook", JSLogging::nativeHook);
@ -140,7 +148,7 @@ void JSCExecutor::executeApplicationScript(
if (!jsSourceURL) {
evaluateScript(m_context, jsScript, jsSourceURL);
} else {
// If we're evaluating a script, get the device's cache dir
// If we're evaluating a script, get the device's cache dir
// in which a cache file for that script will be stored.
evaluateScript(m_context, jsScript, jsSourceURL, m_deviceCacheDir.c_str());
}
@ -471,4 +479,16 @@ static JSValueRef nativePerformanceNow(
return JSValueMakeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND));
}
static JSValueRef nativeInjectHMRUpdate(
JSContextRef ctx,
JSObjectRef function,
JSObjectRef thisObject,
size_t argumentCount,
const JSValueRef arguments[], JSValueRef *exception) {
String execJSString = Value(ctx, arguments[0]).toString();
String jsURL = Value(ctx, arguments[1]).toString();
evaluateScript(ctx, execJSString, jsURL);
return JSValueMakeUndefined(ctx);
}
} }