Enable HMR
Reviewed By: svcscm Differential Revision: D2932137 fb-gh-sync-id: 8bfab09aaac22ae498ac4fa896eee495111abc0d shipit-source-id: 8bfab09aaac22ae498ac4fa896eee495111abc0d
This commit is contained in:
parent
8eddead868
commit
e018aa3100
|
@ -10,6 +10,7 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const Platform = require('Platform');
|
||||||
const invariant = require('invariant');
|
const invariant = require('invariant');
|
||||||
const processColor = require('processColor');
|
const processColor = require('processColor');
|
||||||
|
|
||||||
|
@ -18,23 +19,26 @@ const processColor = require('processColor');
|
||||||
* runtime to reflects those changes.
|
* runtime to reflects those changes.
|
||||||
*/
|
*/
|
||||||
const HMRClient = {
|
const HMRClient = {
|
||||||
enable(platform, bundleEntry) {
|
enable(platform, bundleEntry, host, port) {
|
||||||
invariant(platform, 'Missing required parameter `platform`');
|
invariant(platform, 'Missing required parameter `platform`');
|
||||||
invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
|
invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
|
||||||
|
invariant(host, 'Missing required paramenter `host`');
|
||||||
// TODO(martinb) receive host and port as parameters
|
|
||||||
const host = 'localhost';
|
|
||||||
const port = '8081';
|
|
||||||
|
|
||||||
// need to require WebSocket inside of `enable` function because
|
// need to require WebSocket inside of `enable` function because
|
||||||
// this module is defined as a `polyfillGlobal`.
|
// this module is defined as a `polyfillGlobal`.
|
||||||
// See `InitializeJavascriptAppEngine.js`
|
// See `InitializeJavascriptAppEngine.js`
|
||||||
const WebSocket = require('WebSocket');
|
const WebSocket = require('WebSocket');
|
||||||
|
|
||||||
const activeWS = new WebSocket(
|
const wsHostPort = port !== null && port !== ''
|
||||||
`ws://${host}:${port}/hot?platform=${platform}&` +
|
? `${host}:${port}`
|
||||||
`bundleEntry=${bundleEntry.replace('.bundle', '.js')}`
|
: 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) => {
|
activeWS.onerror = (e) => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Hot loading isn't working because it cannot connect to the development server.
|
`Hot loading isn't working because it cannot connect to the development server.
|
||||||
|
@ -50,10 +54,16 @@ Error: ${e.message}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
activeWS.onmessage = ({data}) => {
|
activeWS.onmessage = ({data}) => {
|
||||||
const DevLoadingView = require('NativeModules').DevLoadingView;
|
let DevLoadingView = require('NativeModules').DevLoadingView;
|
||||||
|
if (!DevLoadingView) {
|
||||||
|
DevLoadingView = {
|
||||||
|
showMessage() {},
|
||||||
|
hide() {},
|
||||||
|
};
|
||||||
|
}
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
|
|
||||||
switch(data.type) {
|
switch (data.type) {
|
||||||
case 'update-start': {
|
case 'update-start': {
|
||||||
DevLoadingView.showMessage(
|
DevLoadingView.showMessage(
|
||||||
'Hot Loading...',
|
'Hot Loading...',
|
||||||
|
@ -67,8 +77,13 @@ Error: ${e.message}`
|
||||||
const sourceMappingURLs = data.body.sourceMappingURLs;
|
const sourceMappingURLs = data.body.sourceMappingURLs;
|
||||||
const sourceURLs = data.body.sourceURLs;
|
const sourceURLs = data.body.sourceURLs;
|
||||||
|
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
const RCTRedBox = require('NativeModules').RedBox;
|
const RCTRedBox = require('NativeModules').RedBox;
|
||||||
RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
|
RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
|
||||||
|
} else {
|
||||||
|
const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
|
||||||
|
RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
|
||||||
|
}
|
||||||
|
|
||||||
modules.forEach((code, i) => {
|
modules.forEach((code, i) => {
|
||||||
code = code + '\n\n' + sourceMappingURLs[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
|
// on JSC we need to inject from native for sourcemaps to work
|
||||||
// (Safari doesn't support `sourceMappingURL` nor any variant when
|
// (Safari doesn't support `sourceMappingURL` nor any variant when
|
||||||
// evaluating code) but on Chrome we can simply use eval
|
// evaluating code) but on Chrome we can simply use eval
|
||||||
const injectFunction = typeof __injectHMRUpdate === 'function'
|
const injectFunction = typeof global.nativeInjectHMRUpdate === 'function'
|
||||||
? __injectHMRUpdate
|
? global.nativeInjectHMRUpdate
|
||||||
: eval;
|
: eval;
|
||||||
|
|
||||||
injectFunction(code, sourceURLs[i]);
|
injectFunction(code, sourceURLs[i]);
|
||||||
|
|
|
@ -461,7 +461,9 @@ RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
|
||||||
|
|
||||||
if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
|
if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
|
||||||
NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
|
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
|
#endif
|
||||||
|
|
|
@ -341,6 +341,20 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
|
||||||
name:event
|
name:event
|
||||||
object:nil];
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,21 +527,6 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
|
||||||
RCTAssertParam(sourceURL);
|
RCTAssertParam(sourceURL);
|
||||||
|
|
||||||
__weak RCTJSCExecutor *weakSelf = self;
|
__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((^{
|
[self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
|
||||||
RCTJSCExecutor *strongSelf = weakSelf;
|
RCTJSCExecutor *strongSelf = weakSelf;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
import com.facebook.react.modules.core.ExceptionsManagerModule;
|
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.JSTimersExecution;
|
||||||
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
|
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;
|
||||||
import com.facebook.react.modules.core.Timing;
|
import com.facebook.react.modules.core.Timing;
|
||||||
|
@ -95,6 +96,7 @@ import com.facebook.systrace.Systrace;
|
||||||
RCTNativeAppEventEmitter.class,
|
RCTNativeAppEventEmitter.class,
|
||||||
AppRegistry.class,
|
AppRegistry.class,
|
||||||
com.facebook.react.bridge.Systrace.class,
|
com.facebook.react.bridge.Systrace.class,
|
||||||
|
HMRClient.class,
|
||||||
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
|
DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
|
||||||
void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
|
void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
|
||||||
void showNewJSError(String message, ReadableArray details, int errorCookie);
|
void showNewJSError(String message, ReadableArray details, int errorCookie);
|
||||||
void updateJSError(final String message, final ReadableArray details, final int errorCookie);
|
void updateJSError(final String message, final ReadableArray details, final int errorCookie);
|
||||||
|
void hideRedboxDialog();
|
||||||
void showDevOptionsDialog();
|
void showDevOptionsDialog();
|
||||||
void setDevSupportEnabled(boolean isDevSupportEnabled);
|
void setDevSupportEnabled(boolean isDevSupportEnabled);
|
||||||
boolean getDevSupportEnabled();
|
boolean getDevSupportEnabled();
|
||||||
|
|
|
@ -13,6 +13,8 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.ExecutionException;
|
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.CatalystInstance;
|
||||||
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
|
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
|
||||||
import com.facebook.react.bridge.JavaJSExecutor;
|
import com.facebook.react.bridge.JavaJSExecutor;
|
||||||
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
|
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.bridge.ReadableArray;
|
import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.UiThreadUtil;
|
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(
|
private void showNewError(
|
||||||
final String message,
|
final String message,
|
||||||
final StackFrame[] stack,
|
final StackFrame[] stack,
|
||||||
|
@ -522,6 +531,18 @@ public class DevSupportManagerImpl implements DevSupportManager {
|
||||||
mDebugOverlayController = new DebugOverlayController(reactContext);
|
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();
|
reloadSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,11 @@ public class DisabledDevSupportManager implements DevSupportManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideRedboxDialog() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showDevOptionsDialog() {
|
public void showDevOptionsDialog() {
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -77,4 +77,11 @@ public class ExceptionsManagerModule extends BaseJavaModule {
|
||||||
mDevSupportManager.updateJSError(title, details, exceptionId);
|
mDevSupportManager.updateJSError(title, details, exceptionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void dismissRedbox() {
|
||||||
|
if (mDevSupportManager.getDevSupportEnabled()) {
|
||||||
|
mDevSupportManager.hideRedboxDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,13 @@ static JSValueRef nativePerformanceNow(
|
||||||
size_t argumentCount,
|
size_t argumentCount,
|
||||||
const JSValueRef arguments[],
|
const JSValueRef arguments[],
|
||||||
JSValueRef *exception);
|
JSValueRef *exception);
|
||||||
|
static JSValueRef nativeInjectHMRUpdate(
|
||||||
|
JSContextRef ctx,
|
||||||
|
JSObjectRef function,
|
||||||
|
JSObjectRef thisObject,
|
||||||
|
size_t argumentCount,
|
||||||
|
const JSValueRef arguments[],
|
||||||
|
JSValueRef *exception);
|
||||||
|
|
||||||
static std::string executeJSCallWithJSC(
|
static std::string executeJSCallWithJSC(
|
||||||
JSGlobalContextRef ctx,
|
JSGlobalContextRef ctx,
|
||||||
|
@ -93,6 +100,7 @@ JSCExecutor::JSCExecutor(FlushImmediateCallback cb, const std::string& cacheDir)
|
||||||
installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
|
installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
|
||||||
installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
|
installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
|
||||||
installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);
|
installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);
|
||||||
|
installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
|
||||||
|
|
||||||
installGlobalFunction(m_context, "nativeLoggingHook", JSLogging::nativeHook);
|
installGlobalFunction(m_context, "nativeLoggingHook", JSLogging::nativeHook);
|
||||||
|
|
||||||
|
@ -471,4 +479,16 @@ static JSValueRef nativePerformanceNow(
|
||||||
return JSValueMakeNumber(ctx, (nano / (double)NANOSECONDS_IN_MILLISECOND));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
Loading…
Reference in New Issue