[react-native] enable react devtools from JavascriptCore

This commit is contained in:
Jared Forsyth 2015-07-23 16:56:23 -07:00
parent 10ffe170c2
commit 9e4af68d91
4 changed files with 132 additions and 10 deletions

View File

@ -0,0 +1,105 @@
/**
* 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.
*
* @providesModule setupDevtools
* @flow
*/
'use strict';
function setupDevtools() {
var messageListeners = [];
var closeListeners = [];
var ws = new window.WebSocket('ws://localhost:8081/devtools');
// this is accessed by the eval'd backend code
var FOR_BACKEND = { // eslint-disable-line no-unused-vars
resolveRNStyle: require('flattenStyle'),
wall: {
listen(fn) {
messageListeners.push(fn);
},
onClose(fn) {
closeListeners.push(fn);
},
send(data) {
ws.send(JSON.stringify(data));
},
},
};
ws.onclose = () => {
console.warn('devtools socket closed');
closeListeners.forEach(fn => fn());
};
ws.onerror = error => {
console.warn('devtools socket errored', error);
closeListeners.forEach(fn => fn());
};
ws.onopen = function () {
tryToConnect();
};
function tryToConnect() {
ws.send('attach:agent');
var _interval = setInterval(() => ws.send('attach:agent'), 500);
ws.onmessage = evt => {
if (evt.data.indexOf('eval:') === 0) {
clearInterval(_interval);
initialize(evt.data.slice('eval:'.length));
}
};
}
function initialize(text) {
try {
// FOR_BACKEND is used by the eval'd code
eval(text); // eslint-disable-line no-eval
} catch (e) {
console.error('Failed to eval' + e.message);
return;
}
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({
CurrentOwner: require('ReactCurrentOwner'),
InstanceHandles: require('ReactInstanceHandles'),
Mount: require('ReactNativeMount'),
Reconciler: require('ReactReconciler'),
TextComponent: require('ReactNativeTextComponent'),
});
ws.onmessage = handleMessage;
}
function handleMessage(evt) {
var data;
try {
data = JSON.parse(evt.data);
} catch (e) {
return console.error('failed to parse json: ' + evt.data);
}
// the devtools closed
if (data.$close || data.$error) {
closeListeners.forEach(fn => fn());
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.emit('shutdown');
tryToConnect();
return;
}
if (data.$open) {
return; // ignore
}
messageListeners.forEach(fn => {
try {
fn(data);
} catch (e) {
// jsc doesn't play so well with tracebacks that go into eval'd code,
// so the stack trace here will stop at the `eval()` call. Getting the
// message that caused the error is the best we can do for now.
console.log(data);
throw e;
}
});
}
}
module.exports = setupDevtools;

View File

@ -20,11 +20,9 @@ var StyleSheet = require('StyleSheet');
var UIManager = require('NativeModules').UIManager; var UIManager = require('NativeModules').UIManager;
var View = require('View'); var View = require('View');
var REACT_DEVTOOLS_HOOK: ?Object = typeof window !== 'undefined' ? window.__REACT_DEVTOOLS_GLOBAL_HOOK__ : null; if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
if (REACT_DEVTOOLS_HOOK) {
// required for devtools to be able to edit react native styles // required for devtools to be able to edit react native styles
REACT_DEVTOOLS_HOOK.resolveRNStyle = require('flattenStyle'); window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = require('flattenStyle');
} }
class Inspector extends React.Component { class Inspector extends React.Component {
@ -43,12 +41,12 @@ class Inspector extends React.Component {
} }
componentDidMount() { componentDidMount() {
if (REACT_DEVTOOLS_HOOK) { if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
this.attachToDevtools = this.attachToDevtools.bind(this); this.attachToDevtools = this.attachToDevtools.bind(this);
REACT_DEVTOOLS_HOOK.on('react-devtools', this.attachToDevtools); window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('react-devtools', this.attachToDevtools);
// if devtools is already started // if devtools is already started
if (REACT_DEVTOOLS_HOOK.reactDevtoolsAgent) { if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent) {
this.attachToDevtools(REACT_DEVTOOLS_HOOK.reactDevtoolsAgent); this.attachToDevtools(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent);
} }
} }
} }
@ -57,8 +55,8 @@ class Inspector extends React.Component {
if (this._subs) { if (this._subs) {
this._subs.map(fn => fn()); this._subs.map(fn => fn());
} }
if (REACT_DEVTOOLS_HOOK) { if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
REACT_DEVTOOLS_HOOK.off('react-devtools', this.attachToDevtools); window.__REACT_DEVTOOLS_GLOBAL_HOOK__.off('react-devtools', this.attachToDevtools);
} }
} }

View File

@ -69,6 +69,11 @@ function renderApplication<D, P, S>(
rootTag, rootTag,
'Expect to have a valid rootTag, instead got ', rootTag 'Expect to have a valid rootTag, instead got ', rootTag
); );
// not when debugging in chrome
if (__DEV__ && !window.document) {
var setupDevtools = require('setupDevtools');
setupDevtools();
}
React.render( React.render(
<AppContainer rootTag={rootTag}> <AppContainer rootTag={rootTag}>
<RootComponent <RootComponent

View File

@ -17,7 +17,19 @@ function attachToServer(server, path) {
}); });
var clients = []; var clients = [];
function sendSpecial(message) {
clients.forEach(function (cn) {
try {
cn.send(JSON.stringify(message));
} catch(e) {
console.warn('WARN: ' + e.message);
}
});
}
wss.on('connection', function(ws) { wss.on('connection', function(ws) {
var id = Math.random().toString(15).slice(10, 20);
sendSpecial({$open: id});
clients.push(ws); clients.push(ws);
var allClientsExcept = function(ws) { var allClientsExcept = function(ws) {
@ -26,10 +38,12 @@ function attachToServer(server, path) {
ws.onerror = function() { ws.onerror = function() {
clients = allClientsExcept(ws); clients = allClientsExcept(ws);
sendSpecial({$error: id});
}; };
ws.onclose = function() { ws.onclose = function() {
clients = allClientsExcept(ws); clients = allClientsExcept(ws);
sendSpecial({$close: id});
}; };
ws.on('message', function(message) { ws.on('message', function(message) {