React Native’s JavaScript Execution Contexts

I’ve been working on a React Native project recently and came to the point where I needed to setup a testing framework. To accomplish this, I needed to deep-dive into React Native’s asset pipeline and runtime environments to better understand what’s going on behind the curtains.

React’s Runtime Environment

As most developers know, React Native will either run your Javascript code on a server on your computer (during development) or it will run the code on the phone itself in production environments.

But how it gets a message out of native code and into Javascript code is both interesting and complex. Here’s what I learned today:

A visual overview of React Native’s multiple JavaScript execution contexts

RCTContextExecutor

RCTContextExecutor is the JavaScript executor for production apps. It runs React Native using Apple’s JavaScriptCore engine on the device. JavascriptCore is some pretty fun tech, which you can read about ala NSHipster (link).

RCTContextExecutor keeps a pointer to RCTJavaScriptContext which is the main runtime engine for JS. As batched Javascript calls come in from RCTBridge (or more precisely RCTBatchedBridge) they are fed into the Javascript environment, which will interpret the corresponding JSON encoded message and return back a response. We can see the point where the JSON message is pushed into JavaScriptCore:

JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, NULL, 0, &jsError);

RCTWebSocketExecutor

RCTWebSocketExecutor is the JavaScript executor for development. It takes the code, that normally would've been run on JavaScriptCore (as explained above) and sends them over a WebSocket connection (ala Square’s SocketRocket library: link) to a Chrome Browser tab open on your development computer.

Sitting between iOS and React Native in the browser is a NodeJS WebSocket proxy. The Websocket proxy receives messages with JSON encoded directives from the device (see Inspecting React Native WebSocket traffic: link) and sends them to all registered listeners. The Websocket messages contain directives of which modules to lookup in memory and the relevant information is passed to it.

N->JS: RCTEventEmitter.receiveTouches([“topTouchStart”,[{“target”:382,”pageY”:212.5,”locationX”:54.5,”changedTouches”:null,”locationY”:72.5,”identifier”:1,”pageX”:54.5,”touches”:null,”timestamp”:39555908.379419}],[0]])

RCTWebViewExecutor

RCTWebViewExecutor is built for the purposes of using the Safari’s Debugging Console. It uses a UIWebView’s Javascript engine to power the React Native JS. This might seem an odd thing to do, but its benefit is that you can then debug in Safari’s Developer Console.

Below we can the where JSON messages are pushed into the webview for interpretation:

NSString *ret = [_webView stringByEvaluatingJavaScriptFromString:execString];

Conclusion

There’s ton more information to go through — but I'm tired and am thirsty for a beer. I'm in now way an expert on all of this, so please leave corrections, clarifications or comments.