Under the hood of React Native

Under the hood of React Native

Slides from my talk at the Reactive conference in Bratislava, Slovakia

Video: https://www.youtube.com/watch?v=8N4f4h6SThc

C0e29150e7b848e783d3195b6bdced49?s=128

Martin Konicek

November 04, 2015
Tweet

Transcript

  1. 1.

    Martin Konicek Facebook, React Native mkonicek martinkonicek Reactive conf
 Bratislava,

    Slovakia
 November 4, 2015 Under the hood of
 React Native
  2. 3.

    React Native React Native is a library for building native

    apps with React. We are at a React conference and there were other talks about React Native, therefore I’ll assume you’re already familiar with React and basics of React Native.
  3. 4.

    Let’s have a quick look at the main benefits of

    React Native compared to traditional mobile development. 
 If you’re developing for the web, one thing you’re used to is that you can reload to quickly see your changes. You probably know that React Native gives you that same experience. Compare that to compiling and copying the binary to the device every time you make a change.
  4. 5.

    The way this works is there is a local development

    server (a custom packager, you can imagine it as something similar to Webpack) that runs on your machine and serves a bundle containing all the JS needed to run your app. On a small change it can return a new bundle really fast. GET /index.bundle
  5. 6.

    Standardized APIs
 Another big thing for me personally is that

    I don’t have to learn a completely different set APIs for Android and iOS.
 
 For example, I don’t need to learn twice how to write to disk, make network requests, or show a scrollable list of items.
  6. 7.

    Standardized APIs
 <View style={styles.row}> ... </View> —————————— styles = {

    row: { flexDirection: 'row', alignItems: 'center', padding: 5, backgroundColor: 'white', }, }; That’s also true for layout, which is done exactly the same way on Android and iOS using Flexbox that you already know from the web.
  7. 8.

    Layout-only View Removal Before After What’s interesting is React Native

    uses one Android-specific optimization related to layout. These two UIs look the same but the view hierarchy on the right is simpler.
  8. 9.

    Layout-only View Removal Before After Goal The way it works

    is we remove all the native views that only define layout but are not visible in any way (e.g. don’t have color). This means Android has to do less work traversing the view hierarchy which means a more performant UI.
  9. 10.

    Building the Ads Manager Now that we’ve done a very

    quick intro to React Native let’s look at how we built the Ads Manager. This was the first app built fully in React Native both for iOS and Android.
  10. 12.

    And here’s what the Android version looks like.
 Both apps

    have native look and feel, I encourage you to try them out. You’ll need to have ads on Facebook to have the full experience but you can play with it even without having ads.
  11. 13.

    3 month iOS release→Android release Same dev team 85%+ code

    shared What was the experience for the Ads Manager team like?
 
 It only took them 3 months to build the Android app once the iOS version was finished. It was one team of people building for both platforms. Most people on the team had mainly JS experience, some iOS and some Android experience.
 
 It turned out they could even share most of the code between platforms. Note that code sharing wasn’t an explicit goal - React Native is learn once write anywhere, not write once run anywhere.
  12. 15.

    How do you share code? OK, even though sharing code

    wasn’t an explicit goal it sounds really cool. How do you do that?
  13. 16.

    AdDetailView.js The easy case is when a part of the

    UI looks and works the same way on iOS and Android.
 Like in this screen, where we’re sharing all the UI and data fetching logic.
  14. 17.

    AdDetailView.js But within a shared JS file you can still

    check what platform you’re running on. On this slide notice another interesting thing - AdsManagerText - a component that defines a consistent look for text across the whole app, making it easy to change it in one place.
  15. 18.

    To take another example, look at the main Ads Manager

    screen on iOS and Android. There’s a ScrollView that has the same contents on both platforms but you can see that it has different pull-to-refresh behavior, specific to the platform.
  16. 19.

    RefreshableScrollView.ios.js On iOS, the implementation might look like this, where

    we put an Activity indicator above the ScrollView. (This is just a simplified example.)
 Note the .ios.js file extension.
  17. 21.

    CampaignsView.js Finally, since the RefreshableScrollView has the same API on

    both platforms we can simply use it on both platforms.
  18. 23.

    GET /index.bundle?platform=ios GET /index.bundle?platform=android You saw at beginning of the

    talk there’s the packager which serves JS to your application.
  19. 24.

    .js, .ios.js .js, .android.js The packager chooses the JS files

    based on the platform when creating the bundle. Simple.
  20. 25.

    Architecture
 (Android) iOS: @tadeuzagallo Now we’ll go a little bit

    deeper and look how React Native runs your code.
 It’s important to note I’ll focus on Android here but the iOS architecture is very similar. You can read about the iOS architecture in a blogpost by my colleague Tadeu.
  21. 26.

    Native JS VM Java/C++ Bridge (C++/Java) At the core of

    React Native is a bridge that lets native code calls JS and vice versa. 
 
 There’s a JS VM (we use JavaScriptCore) running your code. On Android, JavaScriptCore needs to ship with your application which means a Hello World Android app is about 3.5MB. On iOS JavaScriptCore is part of the system.
  22. 27.

    Native JS VM Java/C++ Bridge (C++/Java) A common question I

    get about the architecture of React Native is whether the application code runs in a WebView. There is no WebView, it’s JS running in a VM and controlling native UI.
  23. 28.

    Java JS Bridge Now let’s zoom in. This is the

    same as on the previous slide, native on the left, JS on the right.
  24. 29.

    Java JS Bridge AppRegistry.runApplication(   ‘MyApp’,   new  WritableMap()); require(‘AppRegistry’).runApplication(‘MyApp’,

     {}); [1, 1, [‘MyApp’, {}]] It’s important to realize that we always start in native. In this case native decided to start your app. It sends a function id and arguments to JS using our custom JSON-based protocol over the C++ bridge.

  25. 30.

    Java JS Bridge require(‘AppRegistry’).runApplication(‘MyApp’,  {}); UIManager.createView(2,  ’View’,  {…});
 UIManager.createView(3,  ’Text’,

     {…});
 … UIManagerModule#createView(2,  ‘View’,  …)   View  newView  =  new  View();   UIManagerModule#createView(3,  ‘Text’,  …)   TextView  newView  =  new  TextView();   … [[2, 3, [2, ‘View’, {…}]], [2, 3, [3, ‘Text’, {…}]]] JS calls that function which in turn leads to a bunch of calls from JS to native. For example, create a TextView, send a network request. These are all batched together and sent back to native asynchronously.

  26. 31.

    Java JS Bridge require(‘AppRegistry’).runApplication(‘MyApp’,  {}); UIManager.createView(2,  ’View’,  {…});
 UIManager.createView(3,  ’Text’,

     {…});
 … [[2, 3, [2, ‘View’, {…}]], [2, 3, [3, ‘Text’, {…}]]] The calls are batched together so we don’t pay the overhead for each individual call. However, in some cases, such as when JS is doing lots of work, it might be better to split the batch into several batches. Imagine JS wants to create a few views, then read something from disk and do lots of computation, then update more views. It can be better to flush the queue of calls (see MessageQueue.js) into native early - this way the person using your app can see something on the screen meanwhile JS is still doing work.
  27. 32.

    UI Event Queue JS Event Queue Native Modules Event Queue

    In the previous slide we said the calls between native and JS are asynchronous. To explain that let’s look at the threading model.
 
 There’s the main thread with a queue of events. This is the Android main thread. Then there are two additional threads - one that runs operations on native modules and one that runs your JS (on iOS this works a little bit differently, refer to Tadeu’s blogpost). Each thread is processing a separate queue of events.
  28. 33.

    UI Event Queue JS Event Queue Native Modules Event Queue

    Touch Event Now imagine the OS told us (on the native main thread) there’s a touch event.
 
 The touch event here serves just as an example. If you are interested in how touch handling works refer to the awesome talk by @alex_frantic in the Videos section on the React Native website.
  29. 34.

    Handle Event
 -> bridge -> Runs JS Touch Event Touch

    Event UI Event Queue Native Modules Event Queue JS Event Queue Based on the touch event we add an event to the JS queue. The JS thread synchronously calls your application code via the bridge. This runs your JS which calls e.g. setState and render.

  30. 35.

    Handle Event
 -> bridge -> Runs JS Handle Event
 ->

    bridge -> Runs JS Touch Event Touch Event UI Event Queue Native Modules Event Queue JS Event Queue Dispatch View Updates Update UI Dispatch View Updates Update UI As you saw in the bridge overview, your code returns operations to be done by native modules. These can be things like adding, removing or updating native views, sending network requests etc.
 
 Then on the native modules thread we calculate new layout and finally update the views that need to change, on the main thread. Why not do layout in JS? We need to measure text - only native can do that.
 
 The whole cycle from the touch event to updating the UI should ideally happen within 16ms: this is how JS- driven animations work too but instead of a touch event there’s a timer event that fires on every frame.
  31. 36.

    Here’s an example of an animation created using the Animated

    API. Every frame a timer fires in native, JS computes new positions and tells native to update the views.
  32. 37.

    In some cases when JS is busy doing other work

    this can cause dropped frames. A workaround for this is to use the InteractionManager to delay work until animations have finished.
 
 In the future we might look into offloading animations created using the Animated API to run entirely in native.
  33. 38.

    Modularity Another important thing to know about the architecture is

    that it is very modular. There are two important abstractions that define what’s exported from native to JS: Modules and View Managers.
  34. 39.

    Modules 
 React.NativeModules.Dialog.show(‘♥  Bratislava’) DialogModule#show(“♥  Bratislava”) => A module has

    some state and methods that JS can call.
 In this example, we’re calling a Dialog module in JS which translates to a call on the native dialog module in Java.
  35. 40.

    View Managers <Text  />   <Switch  /> =>    new

     TextView(getContext())   =>    new  Switch(getContext()) View managers define how JS views map to native views. When you specify you want to render a Switch in JS there’s a View Manager that knows how to do that and creates an Android Switch.
  36. 41.

    View Managers <Text  />   <Switch  /> =>    new

     TextView(getContext())   =>    new  Switch(getContext()) The coolest thing about this is that you can easily define your own view managers and modules. This is my other favorite part about React Native - you can simply drop down to native code when needed. … define your own
  37. 42.

    Here is the code of MainReactPackage. It simply defines modules

    and view managers that are available to any app. You can see things like storage, network, Image or ScrollView.
  38. 43.

    MainActivity.java Let’s say you’ve implemented support for native maps. To

    start using the new native feature in your app, create a simple package with you view manager. Then simply add that package to your app’s MainActivity.
  39. 44.

    https://react.parts And what’s even cooler is you can then publish

    your package to npm and register it on react.parts to make it easily discoverable. Then everyone can find and use your code in their apps.
 
 The process of creating and linking native modules could be made smoother, we plan to work on that.
  40. 45.
  41. 46.

    If you look at our github repo an interesting thing

    to notice is we’ll soon have 400 unique contributors to the project.
  42. 47.
  43. 48.

    2 weeks of development release branch: e.g 0.14 2 weeks

    of stability master publish to npm e.g. 0.14.0-rc Every two weeks we cut a release branch from master and publish to npm so you can try out the new release candidate.
  44. 49.

    2 weeks of development publish to npm, e.g. 0.14.0 release

    branch: e.g 0.14 2 weeks of stability 2 weeks of development master publish to npm e.g. 0.14.0-rc We cherry-pick bug-fixes and sometimes small features from master during the following two weeks, then publish to npm and the whole process repeats.
  45. 50.

    For every version you can find release notes on github.


    Thank you James Ide (@JI) for taking the time to write these!
  46. 51.

    Exporting commits to github The next thing that’s useful to

    understand is how we sync code between the internal fb repo and github.
  47. 52.

    If you go to github and look at the commits,

    you’ll see commit messages like this one. What are the differential revision and sync id?
  48. 53.

    To explain that we first have to explain how we

    store the code internally. The source of truth for the React Native codebase is an internal Mercurial repo called fbsource. Here’s a simplified illustration of its layout. The react- native-github folder is a copy of what you see on github.
 
 We also have some Facebook-specific extensions like error reporting to the Facebook backend and of course there are apps like the Ads Manager, for example.
  49. 54.

    The way we commit code internally is we use a

    tool called Phabricator. Each code change is submitted to Phabricator and is called a “diff”. It is very similar to a pull request on github. A diff has to be reviewed by at least one person.
 
 Phabricator is integrated with our CI system - what you see at the bottom are test results. A lot of these tests are commit-blocking.
  50. 55.

    Once a diff has been committed (AKA it “has landed”)

    we create a patch only for those files that are open source (in the react-native- github folder), apply this to the github repo and push. This is done by a job that runs every minute.
  51. 56.

    And again, here’s what the diff looks like once it

    has been landed and exported to github.
  52. 57.

    Pull Requests OK, we’ve talked about getting code from the

    facebook codebase to github. What about the other way?
  53. 59.

    We have a bot that looks at files touched by

    the pull request and mentions people who are likely to be the best reviewers for the PR.
  54. 60.

    Once the code review is done and we’re happy with

    the PR, we comment ‘shipit’ which imports the code into Phabricator.
  55. 61.

    Here is a diff created from that pull request. 


    The most important thing in this slide are the Ads Manager tests. A pull request can affect closed- source apps and we need to make sure everything works when we merge the code into the fb repo.
  56. 62.

    Once the code is merged internally it gets exported to

    github like any other commit, keeping the original author of the PR which is important.
  57. 64.

    SUMMARY To sum it up: A pull request gets turned

    into a diff, is tested and merged into the fb codebase and then exported to github like any other internal commit.
  58. 66.

    If you want to send a PR and looking for

    a place to get started, look for issues labeled ‘Good first task’.
  59. 67.

    You’ll probably notice the number of issues is too high.

    One reason is a lot of those issues are really questions. It would be awesome to have a way to put a banner on github saying those questions are best asked on Stack Overflow.
  60. 69.

    Another thing about github issues is that they have no

    voting system. We’d really like to know what the most important things are for you guys. We started using a website called Product Pains.
 
 You help us by voting, submitting new issues there and above all helping fix the most important ones.
  61. 70.

    You can see that the top one is to get

    Android to feature parity with iOS. This means open sourcing more of the views and modules we’ve been using internally. 
 
 To do that we need to clean up the APIs and add good examples. I’m working on this.
  62. 71.

    Another thing you can help us with is documentation.
 


    Here’s an old version of documentation for one of the most common things people try to do - using an Android device for development. After a few conversations on github and Twitter I realized:
  63. 72.

    .. these were the points where it was easy to

    get stuck. It’s important to note this particular part of the docs has been improved only thanks to the feedback on Twitter. We’ve written the Android docs but we are Android engineers so it’s often not obvious what’s difficult. You guys are in the best position to tell us what needs to be clarified.
 
 Tweet at @martinkonicek or even better send pull requests that improve documentation every time you get even a little bit stuck. Happy to review them.
  64. 73.

    Thanks! @martinkonicek Thanks to all of you, the community around

    React Native is just amazing.
 
 This is the project with the most potential I’ve ever worked on and I’m very excited to see what we’ll build together.