Added support for JavaScript third-party debuggers

Summary:* Add ability to configure the app that should open when starting debugging

axemclion discussed this feature with tadeuzagallo and martinbigio on: https://github.com/facebook/react-native/issues/5051
Closes https://github.com/facebook/react-native/pull/5683

Reviewed By: martinbigio

Differential Revision: D2971497

Pulled By: mkonicek

fb-gh-sync-id: 91c3ce68feed989658124bb96cb61d03dd032599
fbshipit-source-id: 91c3ce68feed989658124bb96cb61d03dd032599
This commit is contained in:
digeff 2016-04-07 13:14:39 -07:00 committed by Facebook Github Bot 1
parent b9396cd744
commit 4c8a9f0d00
8 changed files with 68 additions and 34 deletions

View File

@ -62,7 +62,7 @@ RCT_EXPORT_MODULE()
_injectedObjects = [NSMutableDictionary new]; _injectedObjects = [NSMutableDictionary new];
[_socket setDelegateDispatchQueue:_jsQueue]; [_socket setDelegateDispatchQueue:_jsQueue];
NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url]; NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-js-devtools" relativeToURL:_url];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil]; [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
if (![self connectToProxy]) { if (![self connectToProxy]) {
@ -82,7 +82,7 @@ RCT_EXPORT_MODULE()
if (!runtimeIsReady) { if (!runtimeIsReady) {
RCTLogError(@"Runtime is not ready for debugging.\n " RCTLogError(@"Runtime is not ready for debugging.\n "
"- Make sure Packager server is running.\n" "- Make sure Packager server is running.\n"
"- Make sure Chrome is running and not paused on a breakpoint or exception and try reloading again."); "- Make sure the JavaScript Debugger is running and not paused on a breakpoint or exception and try reloading again.");
[self invalidate]; [self invalidate];
return; return;
} }

View File

@ -185,7 +185,7 @@ RCT_EXPORT_MODULE()
[weakSelf.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; [weakSelf.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil];
}]]; }]];
_webSocketExecutorName = [_defaults objectForKey:@"websocket-executor-name"] ?: @"Chrome"; _webSocketExecutorName = [_defaults objectForKey:@"websocket-executor-name"] ?: @"JS Remotely";
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
@ -443,8 +443,8 @@ RCT_EXPORT_MODULE()
[weakSelf reload]; [weakSelf reload];
}]]; }]];
Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor"); Class jsDebuggingExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!chromeExecutorClass) { if (!jsDebuggingExecutorClass) {
[items addObject:[RCTDevMenuItem buttonItemWithTitle:[NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName] handler:^{ [items addObject:[RCTDevMenuItem buttonItemWithTitle:[NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName] handler:^{
UIAlertView *alert = RCTAlertView( UIAlertView *alert = RCTAlertView(
[NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName], [NSString stringWithFormat:@"%@ Debugger Unavailable", _webSocketExecutorName],
@ -455,10 +455,10 @@ RCT_EXPORT_MODULE()
[alert show]; [alert show];
}]]; }]];
} else { } else {
BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass; BOOL isDebuggingJS = _executorClass && _executorClass == jsDebuggingExecutorClass;
NSString *debugTitleChrome = isDebuggingInChrome ? [NSString stringWithFormat:@"Disable %@ Debugging", _webSocketExecutorName] : [NSString stringWithFormat:@"Debug in %@", _webSocketExecutorName]; NSString *debugTitleJS = isDebuggingJS ? [NSString stringWithFormat:@"Disable %@ Debugging", _webSocketExecutorName] : [NSString stringWithFormat:@"Debug %@", _webSocketExecutorName];
[items addObject:[RCTDevMenuItem buttonItemWithTitle:debugTitleChrome handler:^{ [items addObject:[RCTDevMenuItem buttonItemWithTitle:debugTitleJS handler:^{
weakSelf.executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass; weakSelf.executorClass = isDebuggingJS ? Nil : jsDebuggingExecutorClass;
}]]; }]];
} }

View File

@ -69,7 +69,7 @@ public abstract class JSBundleLoader {
* This loader is used when proxy debugging is enabled. In that case there is no point in fetching * This loader is used when proxy debugging is enabled. In that case there is no point in fetching
* the bundle from device as remote executor will have to do it anyway. * the bundle from device as remote executor will have to do it anyway.
* *
* @param proxySourceURL the URL to load the JS bundle from in Chrome * @param proxySourceURL the URL to load the JS bundle from in the JavaScript proxy
* @param realSourceURL the URL to report as the source URL, e.g. for asset loading * @param realSourceURL the URL to report as the source URL, e.g. for asset loading
*/ */
public static JSBundleLoader createRemoteDebuggerBundleLoader( public static JSBundleLoader createRemoteDebuggerBundleLoader(

View File

@ -54,8 +54,8 @@ public class DevServerHelper {
"http://%s/%s.bundle?platform=android&dev=%s&hot=%s&minify=%s"; "http://%s/%s.bundle?platform=android&dev=%s&hot=%s&minify=%s";
private static final String SOURCE_MAP_URL_FORMAT = private static final String SOURCE_MAP_URL_FORMAT =
BUNDLE_URL_FORMAT.replaceFirst("\\.bundle", ".map"); BUNDLE_URL_FORMAT.replaceFirst("\\.bundle", ".map");
private static final String LAUNCH_CHROME_DEVTOOLS_COMMAND_URL_FORMAT = private static final String LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT =
"http://%s/launch-chrome-devtools"; "http://%s/launch-js-devtools";
private static final String ONCHANGE_ENDPOINT_URL_FORMAT = private static final String ONCHANGE_ENDPOINT_URL_FORMAT =
"http://%s/onchange"; "http://%s/onchange";
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client"; private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
@ -366,13 +366,13 @@ public class DevServerHelper {
return String.format(Locale.US, ONCHANGE_ENDPOINT_URL_FORMAT, getDebugServerHost()); return String.format(Locale.US, ONCHANGE_ENDPOINT_URL_FORMAT, getDebugServerHost());
} }
private String createLaunchChromeDevtoolsCommandUrl() { private String createLaunchJSDevtoolsCommandUrl() {
return String.format(LAUNCH_CHROME_DEVTOOLS_COMMAND_URL_FORMAT, getDebugServerHost()); return String.format(LAUNCH_JS_DEVTOOLS_COMMAND_URL_FORMAT, getDebugServerHost());
} }
public void launchChromeDevtools() { public void launchJSDevtools() {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(createLaunchChromeDevtoolsCommandUrl()) .url(createLaunchJSDevtoolsCommandUrl())
.build(); .build();
mClient.newCall(request).enqueue(new Callback() { mClient.newCall(request).enqueue(new Callback() {
@Override @Override
@ -398,7 +398,7 @@ public class DevServerHelper {
public String getJSBundleURLForRemoteDebugging(String mainModuleName) { public String getJSBundleURLForRemoteDebugging(String mainModuleName) {
// The host IP we use when connecting to the JS bundle server from the emulator is not the // The host IP we use when connecting to the JS bundle server from the emulator is not the
// same as the one needed to connect to the same server from the Chrome proxy running on the // same as the one needed to connect to the same server from the JavaScript proxy running on the
// host itself. // host itself.
return createBundleURL(getHostForJSProxy(), mainModuleName, getDevMode(), getHMR(), getJSMinifyMode()); return createBundleURL(getHostForJSProxy(), mainModuleName, getDevMode(), getHMR(), getJSMinifyMode());
} }

View File

@ -139,7 +139,7 @@ public class DevSupportManagerImpl implements DevSupportManager {
if (DevServerHelper.getReloadAppAction(context).equals(action)) { if (DevServerHelper.getReloadAppAction(context).equals(action)) {
if (intent.getBooleanExtra(DevServerHelper.RELOAD_APP_EXTRA_JS_PROXY, false)) { if (intent.getBooleanExtra(DevServerHelper.RELOAD_APP_EXTRA_JS_PROXY, false)) {
mIsUsingJSProxy = true; mIsUsingJSProxy = true;
mDevServerHelper.launchChromeDevtools(); mDevServerHelper.launchJSDevtools();
} else { } else {
mIsUsingJSProxy = false; mIsUsingJSProxy = false;
} }
@ -584,7 +584,7 @@ public class DevSupportManagerImpl implements DevSupportManager {
private void reloadJSInProxyMode(final ProgressDialog progressDialog) { private void reloadJSInProxyMode(final ProgressDialog progressDialog) {
// When using js proxy, there is no need to fetch JS bundle as proxy executor will do that // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that
// anyway // anyway
mDevServerHelper.launchChromeDevtools(); mDevServerHelper.launchJSDevtools();
JavaJSExecutor.Factory factory = new JavaJSExecutor.Factory() { JavaJSExecutor.Factory factory = new JavaJSExecutor.Factory() {
@Override @Override

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="catalyst_reloadjs" project="catalyst" translatable="false">Reload JS</string> <string name="catalyst_reloadjs" project="catalyst" translatable="false">Reload JS</string>
<string name="catalyst_debugjs" project="catalyst" translatable="false">Debug in Chrome</string> <string name="catalyst_debugjs" project="catalyst" translatable="false">Debug JS Remotely</string>
<string name="catalyst_debugjs_off" project="catalyst" translatable="false">Stop Chrome Debugging</string> <string name="catalyst_debugjs_off" project="catalyst" translatable="false">Stop JS Remotely Debugging</string>
<string name="catalyst_hot_module_replacement" project="catalyst" translatable="false">Enable Hot Reloading</string> <string name="catalyst_hot_module_replacement" project="catalyst" translatable="false">Enable Hot Reloading</string>
<string name="catalyst_hot_module_replacement_off" project="catalyst" translatable="false">Disable Hot Reloading</string> <string name="catalyst_hot_module_replacement_off" project="catalyst" translatable="false">Disable Hot Reloading</string>
<string name="catalyst_live_reload" project="catalyst" translatable="false">Enable Live Reload</string> <string name="catalyst_live_reload" project="catalyst" translatable="false">Enable Live Reload</string>

View File

@ -34,7 +34,7 @@ You can use `console.error` to display a full screen error on a red background.
These boxes only appear when you're running your app in dev mode. These boxes only appear when you're running your app in dev mode.
### Chrome Developer Tools ### Chrome Developer Tools
To debug the JavaScript code in Chrome, select `Debug in Chrome` from the developer menu. This will open a new tab at [http://localhost:8081/debugger-ui](http://localhost:8081/debugger-ui). To debug the JavaScript code in Chrome, select `Debug JS Remotely` from the developer menu. This will open a new tab at [http://localhost:8081/debugger-ui](http://localhost:8081/debugger-ui).
In Chrome, press `⌘ + option + i` or select `View``Developer``Developer Tools` to toggle the developer tools console. Enable [Pause On Caught Exceptions](http://stackoverflow.com/questions/2233339/javascript-is-there-a-way-to-get-chrome-to-break-on-all-errors/17324511#17324511) for a better debugging experience. In Chrome, press `⌘ + option + i` or select `View``Developer``Developer Tools` to toggle the developer tools console. Enable [Pause On Caught Exceptions](http://stackoverflow.com/questions/2233339/javascript-is-there-a-way-to-get-chrome-to-break-on-all-errors/17324511#17324511) for a better debugging experience.
@ -43,6 +43,9 @@ To debug on a real device:
1. On iOS - open the file `RCTWebSocketExecutor.m` and change `localhost` to the IP address of your computer. Shake the device to open the development menu with the option to start debugging. 1. On iOS - open the file `RCTWebSocketExecutor.m` and change `localhost` to the IP address of your computer. Shake the device to open the development menu with the option to start debugging.
2. On Android, if you're running Android 5.0+ device connected via USB you can use `adb` command line tool to setup port forwarding from the device to your computer. For that run: `adb reverse tcp:8081 tcp:8081` (see [this link](http://developer.android.com/tools/help/adb.html) for help on `adb` command). Alternatively, you can [open dev menu](#debugging-react-native-apps) on the device and select `Dev Settings`, then update `Debug server host for device` setting to the IP address of your computer. 2. On Android, if you're running Android 5.0+ device connected via USB you can use `adb` command line tool to setup port forwarding from the device to your computer. For that run: `adb reverse tcp:8081 tcp:8081` (see [this link](http://developer.android.com/tools/help/adb.html) for help on `adb` command). Alternatively, you can [open dev menu](#debugging-react-native-apps) on the device and select `Dev Settings`, then update `Debug server host for device` setting to the IP address of your computer.
### Custom JavaScript debugger
To use a custom JavaScript debugger define the `REACT_DEBUGGER` environment variable to a command that will start your custom debugger. That variable will be read from the Packager process. If that environment variable is set, selecting `Debug JS Remotely` from the developer menu will execute that command instead of opening Chrome. The exact command to be executed is the contents of the REACT_DEBUGGER environment variable followed by the space separated paths of all project roots (e.g. If you set REACT_DEBUGGER="node /path/to/launchDebugger.js --port 2345 --type ReactNative" then the command "node /path/to/launchDebugger.js --port 2345 --type ReactNative /path/to/reactNative/app" will end up being executed). Custom debugger commands executed this way should be short-lived processes, and they shouldn't produce more than 200 kilobytes of output.
### Live Reload ### Live Reload
This option allows for your JS changes to trigger automatic reload on the connected device/emulator. To enable this option: This option allows for your JS changes to trigger automatic reload on the connected device/emulator. To enable this option:

View File

@ -8,6 +8,7 @@
*/ */
'use strict'; 'use strict';
var child_process = require('child_process');
var execFile = require('child_process').execFile; var execFile = require('child_process').execFile;
var fs = require('fs'); var fs = require('fs');
var opn = require('opn'); var opn = require('opn');
@ -24,7 +25,39 @@ function getChromeAppName() {
} }
} }
module.exports = function(options, isDebuggerConnected) { function launchChromeDevTools(port) {
var debuggerURL = 'http://localhost:' + port + '/debugger-ui';
console.log('Launching Dev Tools...');
opn(debuggerURL, {app: [getChromeAppName()]}, function(err) {
if (err) {
console.error('Google Chrome exited with error:', err);
}
});
}
function escapePath(path) {
return '"' + path + '"'; // " Can escape paths with spaces in OS X, Windows, and *nix
}
function launchDevTools(options, isChromeConnected) {
// Explicit config always wins
var customDebugger = process.env.REACT_DEBUGGER;
if (customDebugger) {
var projects = options.projectRoots.map(escapePath).join(' ');
var command = customDebugger + ' ' + projects;
console.log('Starting custom debugger by executing: ' + command);
child_process.exec(command, function (error, stdout, stderr) {
if (error !== null) {
console.log('Error while starting custom debugger: ' + error);
}
});
} else if (!isChromeConnected()) {
// Dev tools are not yet open; we need to open a session
launchChromeDevTools(options.port);
}
}
module.exports = function(options, isChromeConnected) {
return function(req, res, next) { return function(req, res, next) {
if (req.url === '/debugger-ui') { if (req.url === '/debugger-ui') {
var debuggerPath = path.join(__dirname, '..', 'util', 'debugger.html'); var debuggerPath = path.join(__dirname, '..', 'util', 'debugger.html');
@ -41,18 +74,16 @@ module.exports = function(options, isDebuggerConnected) {
'If you still need this, please let us know.' 'If you still need this, please let us know.'
); );
} else if (req.url === '/launch-chrome-devtools') { } else if (req.url === '/launch-chrome-devtools') {
if (isDebuggerConnected()) { // TODO: Remove this case in the future
// Dev tools are already open; no need to open another session console.log(
'The method /launch-chrome-devtools is deprecated. You are ' +
' probably using an application created with an older CLI with the ' +
' packager of a newer CLI. Please upgrade your application: ' +
'https://facebook.github.io/react-native/docs/upgrading.html');
launchDevTools(options, isChromeConnected);
res.end('OK'); res.end('OK');
return; } else if (req.url === '/launch-js-devtools') {
} launchDevTools(options, isChromeConnected);
var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui';
console.log('Launching Dev Tools...');
opn(debuggerURL, {app: [getChromeAppName()]}, function(err) {
if (err) {
console.error('Google Chrome exited with error:', err);
}
});
res.end('OK'); res.end('OK');
} else { } else {
next(); next();