diff --git a/.buckconfig b/.buckconfig
new file mode 100644
index 000000000000..9f6c686b2fc1
--- /dev/null
+++ b/.buckconfig
@@ -0,0 +1,9 @@
+
+[android]
+  target = Google Inc.:Google APIs:23
+
+[maven_repositories]
+  central = https://repo1.maven.org/maven2
+
+[alias]
+  movies = //Examples/Movies/android/app:app
diff --git a/.buckjavaargs b/.buckjavaargs
new file mode 100644
index 000000000000..11452478196f
--- /dev/null
+++ b/.buckjavaargs
@@ -0,0 +1 @@
+-Xmx512m -XX:+HeapDumpOnOutOfMemoryError
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000000..3b281f2f7d5a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
diff --git a/.eslintrc b/.eslintrc
index 5a7692b800d2..d707517a0d62 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -19,6 +19,7 @@
     "__DEV__": true,
     "__dirname": false,
     "__fbBatchedBridgeConfig": false,
+    "alert": false,
     "cancelAnimationFrame": false,
     "clearImmediate": true,
     "clearInterval": false,
@@ -26,8 +27,11 @@
     "console": false,
     "document": false,
     "escape": false,
+    "Event": false,
+    "EventTarget": false,
     "exports": false,
     "fetch": false,
+    "FormData": false,
     "global": false,
     "jest": false,
     "Map": true,
@@ -43,7 +47,29 @@
     "setTimeout": false,
     "window": false,
     "XMLHttpRequest": false,
-    "pit": false
+    "pit": false,
+
+    // Flow global types.
+    "ReactComponent": false,
+    "ReactClass": false,
+    "ReactElement": false,
+    "ReactPropsCheckType": false,
+    "ReactPropsChainableTypeChecker": false,
+    "ReactPropTypes": false,
+    "SyntheticEvent": false,
+    "$Either": false,
+    "$All": false,
+    "$Tuple": false,
+    "$Supertype": false,
+    "$Subtype": false,
+    "$Shape": false,
+    "$Diff": false,
+    "$Keys": false,
+    "$Enum": false,
+    "$Exports": false,
+    "$FlowIssue": false,
+    "$FlowFixMe": false,
+    "$FixMe": false
   },
 
   "rules": {
@@ -81,13 +107,12 @@
     "curly": 1,                      // specify curly brace conventions for all control statements
     "default-case": 0,               // require default case in switch statements (off by default)
     "dot-notation": 1,               // encourages use of dot notation whenever possible
-    "eqeqeq": 1,                     // require the use of === and !==
+    "eqeqeq": [1, "allow-null"],     // require the use of === and !==
     "guard-for-in": 0,               // make sure for-in loops have an if statement (off by default)
     "no-alert": 1,                   // disallow the use of alert, confirm, and prompt
     "no-caller": 1,                  // disallow use of arguments.caller or arguments.callee
     "no-div-regex": 1,               // disallow division operators explicitly at beginning of regular expression (off by default)
     "no-else-return": 0,             // disallow else after a return in an if (off by default)
-    "no-empty-label": 1,             // disallow use of labels for anything other then loops and switches
     "no-eq-null": 0,                 // disallow comparisons to null without a type-checking operator (off by default)
     "no-eval": 1,                    // disallow use of eval()
     "no-extend-native": 1,           // disallow adding to native types
@@ -156,6 +181,8 @@
   // These rules are purely matters of style and are quite subjective.
 
     "key-spacing": 0,
+    "keyword-spacing": 1,            // enforce spacing before and after keywords
+    "jsx-quotes": [1, "prefer-double"],
     "comma-spacing": 0,
     "no-multi-spaces": 0,
     "brace-style": 0,                // enforce one true brace style (off by default)
@@ -179,11 +206,9 @@
     "quote-props": 0,                // require quotes around object literal property names (off by default)
     "semi": 1,                       // require or disallow use of semicolons instead of ASI
     "sort-vars": 0,                  // sort variables within the same declaration block (off by default)
-    "space-after-keywords": 1,       // require a space after certain keywords (off by default)
     "space-in-brackets": 0,          // require or disallow spaces inside brackets (off by default)
     "space-in-parens": 0,            // require or disallow spaces inside parentheses (off by default)
     "space-infix-ops": 1,            // require spaces around operators
-    "space-return-throw-case": 1,    // require a space after return, throw, and case
     "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default)
     "max-nested-callbacks": 0,       // specify the maximum depth callbacks can be nested (off by default)
     "one-var": 0,                    // allow just one var statement per function (off by default)
@@ -201,7 +226,6 @@
 
     "react/display-name": 0,
     "react/jsx-boolean-value": 0,
-    "react/jsx-quotes": [1, "double", "avoid-escape"],
     "react/jsx-no-undef": 1,
     "react/jsx-sort-props": 0,
     "react/jsx-uses-react": 0,
diff --git a/.flowconfig b/.flowconfig
index 7f4891b33340..3d5e14681dcd 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -14,17 +14,21 @@
 
 # Ignore react and fbjs where there are overlaps, but don't ignore
 # anything that react-native relies on
-.*/node_modules/fbjs-haste/.*/__tests__/.*
-.*/node_modules/fbjs-haste/__forks__/Map.js
-.*/node_modules/fbjs-haste/__forks__/Promise.js
-.*/node_modules/fbjs-haste/__forks__/fetch.js
-.*/node_modules/fbjs-haste/core/ExecutionEnvironment.js
-.*/node_modules/fbjs-haste/core/isEmpty.js
-.*/node_modules/fbjs-haste/crypto/crc32.js
-.*/node_modules/fbjs-haste/stubs/ErrorUtils.js
-.*/node_modules/react-haste/React.js
-.*/node_modules/react-haste/renderers/dom/ReactDOM.js
-.*/node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
+.*/node_modules/fbjs/lib/Map.js
+.*/node_modules/fbjs/lib/fetch.js
+.*/node_modules/fbjs/lib/ExecutionEnvironment.js
+.*/node_modules/fbjs/lib/ErrorUtils.js
+
+# Flow has a built-in definition for the 'react' module which we prefer to use
+# over the currently-untyped source
+.*/node_modules/react/react.js
+.*/node_modules/react/lib/React.js
+.*/node_modules/react/lib/ReactDOM.js
+
+.*/__mocks__/.*
+.*/__tests__/.*
+
+.*/commoner/test/source/widget/share.js
 
 # Ignore commoner tests
 .*/node_modules/commoner/test/.*
@@ -46,18 +50,21 @@ Libraries/react-native/react-native-interface.js
 [options]
 module.system=haste
 
+esproposal.class_static_fields=enable
+esproposal.class_instance_fields=enable
+
 munge_underscores=true
 
 module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
-module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
+module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub'
 
 suppress_type=$FlowIssue
 suppress_type=$FlowFixMe
 suppress_type=$FixMe
 
-suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
-suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-8]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
+suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-1]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
 suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
 
 [version]
-0.18.1
+0.21.0
diff --git a/.gitignore b/.gitignore
index c4b7a9b476d9..cd545b912fdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,8 +21,16 @@ DerivedData
 *.xcuserstate
 project.xcworkspace
 
-# Xcode, Gradle
-build/
+# Gradle
+/build/
+/Examples/**/android/app/build/
+/ReactAndroid/build/
+
+# Buck
+.buckd
+buck-out
+/ReactAndroid/src/main/jni/prebuilt/lib/armeabi-v7a/
+/ReactAndroid/src/main/jni/prebuilt/lib/x86/
 
 # Android
 .idea
diff --git a/.travis.yml b/.travis.yml
index ea84c8bc8ab5..32ad533ff3a5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,21 @@
 language: objective-c
 
-osx_image: xcode7
+osx_image: xcode7.2
 
 cache:
-    directories:
-        - node_modules
-        - .nvm
+  directories:
+    - node_modules
+    - .nvm
 
 install:
   - brew reinstall nvm
   - mkdir -p .nvm
   - export NVM_DIR="$PWD/.nvm"
   - source $(brew --prefix nvm)/nvm.sh
-  - nvm install 4
+  - nvm install 5
   - rm -Rf "${TMPDIR}/jest_preprocess_cache"
   - npm config set spin=false
-  - npm install -g flow-bin@`node -p "require('fs').readFileSync('.flowconfig', 'utf8').split('[version]')[1].trim()"`
+  - npm config set progress=false
   - npm install
 
 script:
@@ -23,41 +23,20 @@ script:
   if [ "$TEST_TYPE" = objc ]
   then
 
-    ./scripts/objc-test.sh
+    travis_retry ./scripts/objc-test.sh
 
   elif [ "$TEST_TYPE" = js ]
   then
 
-    flow check && npm test -- '\/Libraries\/'
+    npm install github@0.2.4
+    cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; npm run flow --silent -- --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js
+    npm run flow && npm test
 
-  elif [ "$TEST_TYPE" = packager ]
+  elif [ "$TEST_TYPE" = e2e-objc ]
   then
 
-    npm test -- '\/packager\/'
+    travis_retry ./scripts/e2e-test.sh --ios
 
-  elif [ "$TEST_TYPE" = cli ]
-  then
-
-    npm test -- '\/(local|private|react-native)-cli\/'
-
-  elif [ "$TEST_TYPE" = build_website ]
-  then
-
-    cd website
-    $(which npm) install
-    ./setup.sh
-    if [ "$TRAVIS_PULL_REQUEST" = false ] && [ "$TRAVIS_BRANCH" = master ]; then
-      # Automatically publish the website
-      echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" >~/.netrc
-      ./publish.sh
-    else
-      # Make sure the website builds without error
-      node server/generate.js
-    fi
-
-  elif [ "$TEST_TYPE" = e2e ]
-  then
-    ./scripts/e2e-test.sh
   else
     echo "Unknown test type: $TEST_TYPE"
     exit 1
@@ -67,19 +46,16 @@ env:
   matrix:
     - TEST_TYPE=objc
     - TEST_TYPE=js
-    - TEST_TYPE=packager
-    - TEST_TYPE=cli
-    - TEST_TYPE=build_website
-    - TEST_TYPE=e2e
-  global:
-    # $GITHUB_TOKEN
-    - secure: "HlmG8M2DmBUSBh6KH1yVIe/8gR4iibg4WfcHq1x/xYQxGbvleq7NOo04V6eFHnl9cvZCu+PKH0841WLnGR7c4BBf47GVu/o16nXzggPumHKy++lDzxFPlJ1faMDfjg/5vjbAxRUe7D3y98hQSeGHH4tedc8LvTaFLVu7iiGqvjU="
-    # $APPETIZE_TOKEN
-    - secure: "egsvVSpszTzrNd6bN62DsVAzMiSZI/OHgdizfPryqvqWBf655ztE6XFQSEFNpuIAzSKDDF25ioT8iPfVsbC1iK6HDWHfmqYxML0L+OoU0gi+hV2oKUBFZDZ1fwSnFoWuBdNdMDpLlUxvJp6N1WyfNOB2dxuZUt8eTt48Hi3+Hpc="
-    # $S3_TOKEN
-    - secure: "lY8JZPA0A7zT7L5KF9BBg34XYWIeR/RJiEvE7l7oVr88KnEPtyd//79eHhhVKnUnav7zsk5QJwkcX0MxKTp/dp4G0Am+zOX+sfA8kQrJ+2/+FzFW7AEsW/kHByfaIEIly9DQvUFt4I4oMm8nQZysJLahDgNWglyI3RTuJp//hcY="
+    - TEST_TYPE=e2e-objc
 
 branches:
   only:
     - master
     - /^.*-stable$/
+
+notifications:
+  email:
+    recipients:
+      - bestander@gmail.com
+    on_failure: change
+    on_success: change
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aebef40cc862..b5d0c744d117 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,12 +17,30 @@ The core team will be monitoring for pull requests. When we get one, we'll run s
 *Before* submitting a pull request, please make sure the following is done…
 
 1. Fork the repo and create your branch from `master`.
-2. If you've added code that should be tested, add tests!
+2. **Describe your test plan in your commit.** If you've added code that should be tested, add tests!
 3. If you've changed APIs, update the documentation.
-4. Ensure tests pass on Travis.
-5. Make sure your code lints (`node linter.js <files touched>`).
-6. Squash your commits (`git rebase -i`).
-7. If you haven't already, complete the CLA.
+4. Add the copyright notice to the top of any new files you've added.
+5. Ensure tests pass on Travis and Circle CI.
+6. Make sure your code lints (`node linter.js <files touched>`).
+7. Squash your commits (`git rebase -i`).
+8. If you haven't already, sign the [CLA](https://code.facebook.com/cla).
+
+#### Copyright Notice for files
+
+Copy and paste this to the top of your new file(s):
+
+```JS
+/**
+ * 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.
+ */
+```
+
+If you've added a new module, add a `@providesModule <moduleName>` at the end of the comment. This will allow the haste package manager to find it.
 
 ### Contributor License Agreement (CLA)
 
@@ -67,6 +85,14 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
 * Do not use the optional parameters of `setTimeout` and `setInterval`
 * 80 character line length
 
+#### JSX
+
+* Prefer `'` over `"` for string literal props
+* When wrapping opening tags over multiple lines, place one prop per line
+* `{}` of props should hug their values (no spaces)
+* Place the closing `>` of opening tags on the same line as the last prop
+* Place the closing `/>` of self-closing tags on their own line and left-align them with the opening `<`
+
 #### Objective-C
 
 * Space after `@property` declarations
diff --git a/Examples/.eslintrc b/Examples/.eslintrc
new file mode 100644
index 000000000000..8dc9d22c11ec
--- /dev/null
+++ b/Examples/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "rules": {
+    "no-alert": 0
+  }
+}
diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js
index 90111d38b871..a6c97a3e3eda 100644
--- a/Examples/2048/Game2048.js
+++ b/Examples/2048/Game2048.js
@@ -53,6 +53,8 @@ class Board extends React.Component {
 }
 
 class Tile extends React.Component {
+  state: any;
+
   static _getPosition(index): number {
     return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
   }
@@ -147,6 +149,7 @@ class GameEndOverlay extends React.Component {
 class Game2048 extends React.Component {
   startX: number;
   startY: number;
+  state: any;
 
   constructor(props: {}) {
     super(props);
@@ -237,7 +240,7 @@ var styles = StyleSheet.create({
     marginBottom: 20,
   },
   tryAgain: {
-    backgroundColor: '#887766',
+    backgroundColor: '#887761',
     padding: 20,
     borderRadius: 5,
   },
@@ -279,7 +282,7 @@ var styles = StyleSheet.create({
     backgroundColor: '#eeeecc',
   },
   tile8: {
-    backgroundColor: '#ffbb88',
+    backgroundColor: '#ffbb87',
   },
   tile16: {
     backgroundColor: '#ff9966',
diff --git a/Examples/2048/GameBoard.js b/Examples/2048/GameBoard.js
index 1d7f34085b10..6ed92e6b578d 100644
--- a/Examples/2048/GameBoard.js
+++ b/Examples/2048/GameBoard.js
@@ -17,7 +17,7 @@
 'use strict';
 
 // NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js
-//     with no modificiation except to format it for CommonJS and fix lint/flow errors
+//     with no modification except to format it for CommonJS and fix lint/flow errors
 
 var rotateLeft = function (matrix) {
   var rows = matrix.length;
diff --git a/Examples/Movies/MovieCell.js b/Examples/Movies/MovieCell.js
index 62062cb2af22..c90931c08768 100644
--- a/Examples/Movies/MovieCell.js
+++ b/Examples/Movies/MovieCell.js
@@ -18,7 +18,6 @@
 var React = require('react-native');
 var {
   Image,
-  PixelRatio,
   Platform,
   StyleSheet,
   Text,
@@ -99,8 +98,7 @@ var styles = StyleSheet.create({
   },
   cellBorder: {
     backgroundColor: 'rgba(0, 0, 0, 0.1)',
-    // Trick to get the thinest line the device can display
-    height: 1 / PixelRatio.get(),
+    height: StyleSheet.hairlineWidth,
     marginLeft: 4,
   },
 });
diff --git a/Examples/Movies/MovieScreen.js b/Examples/Movies/MovieScreen.js
index 09d6544dff59..ff228a8a3e7c 100644
--- a/Examples/Movies/MovieScreen.js
+++ b/Examples/Movies/MovieScreen.js
@@ -18,7 +18,6 @@
 var React = require('react-native');
 var {
   Image,
-  PixelRatio,
   ScrollView,
   StyleSheet,
   Text,
@@ -152,7 +151,7 @@ var styles = StyleSheet.create({
   },
   separator: {
     backgroundColor: 'rgba(0, 0, 0, 0.1)',
-    height: 1 / PixelRatio.get(),
+    height: StyleSheet.hairlineWidth,
     marginVertical: 10,
   },
   castTitle: {
diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js
index 1bc6bb906cea..2e681ed34f50 100644
--- a/Examples/Movies/SearchScreen.js
+++ b/Examples/Movies/SearchScreen.js
@@ -27,7 +27,7 @@ var {
 } = React;
 var TimerMixin = require('react-timer-mixin');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var dismissKeyboard = require('dismissKeyboard');
 
 var MovieCell = require('./MovieCell');
@@ -316,7 +316,7 @@ var SearchScreen = React.createClass({
           onSearchChange={this.onSearchChange}
           isLoading={this.state.isLoading}
           onFocus={() =>
-            this.refs.listview && this.refs.listview.getScrollResponder().scrollTo(0, 0)}
+            this.refs.listview && this.refs.listview.getScrollResponder().scrollTo({ x: 0, y: 0 })}
         />
         <View style={styles.separator} />
         {content}
diff --git a/Examples/Movies/android/app/BUCK b/Examples/Movies/android/app/BUCK
new file mode 100644
index 000000000000..25c73fd54415
--- /dev/null
+++ b/Examples/Movies/android/app/BUCK
@@ -0,0 +1,43 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_binary(
+  name = 'app',
+  manifest = 'src/main/AndroidManifest.xml',
+  keystore = '//keystores:debug',
+  deps = [
+    ':movies-lib',
+  ],
+)
+
+android_library(
+  name = 'movies-lib',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/devsupport:devsupport'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/shell:shell'),
+    react_native_target('java/com/facebook/react/touch:touch'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/views/image:image'),
+    react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
+    react_native_target('java/com/facebook/react/views/scroll:scroll'),
+    react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_target('java/com/facebook/react/views/view:view'),
+    # .so files are prebuilt by Gradle with `./gradlew :ReactAndroid:packageReactNdkLibsForBuck`
+    react_native_target('jni/prebuilt:reactnative-libs'),
+    react_native_target('jni/prebuilt:android-jsc'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    ':res',
+  ],
+)
+
+
+android_resource(
+  name = 'res',
+  res = 'src/main/res',
+  package = 'com.facebook.react.movies',
+)
diff --git a/Examples/Movies/android/app/src/main/AndroidManifest.xml b/Examples/Movies/android/app/src/main/AndroidManifest.xml
index 2123d078fdf1..8aaec60819da 100644
--- a/Examples/Movies/android/app/src/main/AndroidManifest.xml
+++ b/Examples/Movies/android/app/src/main/AndroidManifest.xml
@@ -2,6 +2,8 @@
     package="com.facebook.react.movies">
 
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
     <application
       android:allowBackup="true"
diff --git a/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java b/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
index 3b2626601a34..a5dc16093b8d 100644
--- a/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
+++ b/Examples/Movies/android/app/src/main/java/com/facebook/react/movies/MoviesActivity.java
@@ -11,79 +11,42 @@
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  */
-
 package com.facebook.react.movies;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.KeyEvent;
-
-import com.facebook.react.LifecycleState;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.ReactRootView;
-import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactPackage;
 import com.facebook.react.shell.MainReactPackage;
 
-public class MoviesActivity extends Activity implements DefaultHardwareBackBtnHandler {
-
-  private ReactInstanceManager mReactInstanceManager;
-
-  @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    setContentView(R.layout.activity_main);
-
-    mReactInstanceManager = ReactInstanceManager.builder()
-        .setApplication(getApplication())
-        .setBundleAssetName("MoviesApp.android.bundle")
-        .setJSMainModuleName("Examples/Movies/MoviesApp.android")
-        .addPackage(new MainReactPackage())
-        .setUseDeveloperSupport(true)
-        .setInitialLifecycleState(LifecycleState.RESUMED)
-        .build();
+import java.util.Arrays;
+import java.util.List;
 
-    ((ReactRootView) findViewById(R.id.react_root_view))
-        .startReactApplication(mReactInstanceManager, "MoviesApp", null);
-  }
+import javax.annotation.Nullable;
 
+public class MoviesActivity extends ReactActivity {
   @Override
-  public boolean onKeyUp(int keyCode, KeyEvent event) {
-    if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
-      mReactInstanceManager.showDevOptionsDialog();
-      return true;
-    }
-    return super.onKeyUp(keyCode, event);
+  protected String getMainComponentName() {
+    return "MoviesApp";
   }
 
   @Override
-  protected void onPause() {
-    super.onPause();
-
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onPause();
-    }
-  }
+  protected @Nullable String getBundleAssetName() {
+    return "MoviesApp.android.bundle";
+  };
 
   @Override
-  protected void onResume() {
-    super.onResume();
-
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onResume(this, this);
-    }
+  protected String getJSMainModuleName() {
+    return "Examples/Movies/MoviesApp.android";
   }
 
   @Override
-  public void onBackPressed() {
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onBackPressed();
-    } else {
-      super.onBackPressed();
-    }
+  protected boolean getUseDeveloperSupport() {
+    return true;
   }
 
   @Override
-  public void invokeDefaultOnBackPressed() {
-    super.onBackPressed();
+  protected List<ReactPackage> getPackages() {
+    return Arrays.<ReactPackage>asList(
+      new MainReactPackage()
+    );
   }
 }
diff --git a/Examples/TicTacToe/TicTacToeApp.js b/Examples/TicTacToe/TicTacToeApp.js
index 15652ee4de64..3b562194a044 100755
--- a/Examples/TicTacToe/TicTacToeApp.js
+++ b/Examples/TicTacToe/TicTacToeApp.js
@@ -89,7 +89,6 @@ class Board {
         }
       }
     }
-
     return this.winner() === null;
   }
 }
@@ -306,7 +305,7 @@ var styles = StyleSheet.create({
     textAlign: 'center',
   },
   newGame: {
-    backgroundColor: '#887766',
+    backgroundColor: '#887765',
     padding: 20,
     borderRadius: 5,
   },
diff --git a/Examples/UIExplorer/ActionSheetIOSExample.js b/Examples/UIExplorer/ActionSheetIOSExample.js
index 31aedca627c6..b749400c037a 100644
--- a/Examples/UIExplorer/ActionSheetIOSExample.js
+++ b/Examples/UIExplorer/ActionSheetIOSExample.js
@@ -20,6 +20,7 @@ var {
   ActionSheetIOS,
   StyleSheet,
   Text,
+  UIManager,
   View,
 } = React;
 
@@ -27,7 +28,7 @@ var BUTTONS = [
   'Option 0',
   'Option 1',
   'Option 2',
-  'Destruct',
+  'Delete',
   'Cancel',
 ];
 var DESTRUCTIVE_INDEX = 3;
@@ -65,6 +66,39 @@ var ActionSheetExample = React.createClass({
   }
 });
 
+var ActionSheetTintExample = React.createClass({
+  getInitialState() {
+    return {
+      clicked: 'none',
+    };
+  },
+
+  render() {
+    return (
+      <View>
+        <Text onPress={this.showActionSheet} style={style.button}>
+          Click to show the ActionSheet
+        </Text>
+        <Text>
+          Clicked button: {this.state.clicked}
+        </Text>
+      </View>
+    );
+  },
+
+  showActionSheet() {
+    ActionSheetIOS.showActionSheetWithOptions({
+      options: BUTTONS,
+      cancelButtonIndex: CANCEL_INDEX,
+      destructiveButtonIndex: DESTRUCTIVE_INDEX,
+      tintColor: 'green',
+    },
+    (buttonIndex) => {
+      this.setState({ clicked: BUTTONS[buttonIndex] });
+    });
+  }
+});
+
 var ShareActionSheetExample = React.createClass({
   getInitialState() {
     return {
@@ -87,11 +121,14 @@ var ShareActionSheetExample = React.createClass({
 
   showShareActionSheet() {
     ActionSheetIOS.showShareActionSheetWithOptions({
-      url: 'https://code.facebook.com',
-    },
-    (error) => {
-      console.error(error);
+      url: this.props.url,
+      message: 'message to go with the shared url',
+      subject: 'a subject to go in the email heading',
+      excludedActivityTypes: [
+        'com.apple.UIKit.activity.PostToTwitter'
+      ]
     },
+    (error) => alert(error),
     (success, method) => {
       var text;
       if (success) {
@@ -104,6 +141,50 @@ var ShareActionSheetExample = React.createClass({
   }
 });
 
+var ShareScreenshotExample = React.createClass({
+  getInitialState() {
+    return {
+      text: ''
+    };
+  },
+
+  render() {
+    return (
+      <View>
+        <Text onPress={this.showShareActionSheet} style={style.button}>
+          Click to show the Share ActionSheet
+        </Text>
+        <Text>
+          {this.state.text}
+        </Text>
+      </View>
+    );
+  },
+
+  showShareActionSheet() {
+    // Take the snapshot (returns a temp file uri)
+    UIManager.takeSnapshot('window').then((uri) => {
+      // Share image data
+      ActionSheetIOS.showShareActionSheetWithOptions({
+        url: uri,
+        excludedActivityTypes: [
+          'com.apple.UIKit.activity.PostToTwitter'
+        ]
+      },
+      (error) => alert(error),
+      (success, method) => {
+        var text;
+        if (success) {
+          text = `Shared via ${method}`;
+        } else {
+          text = 'You didn\'t share';
+        }
+        this.setState({text});
+      });
+    }).catch((error) => alert(error));
+  }
+});
+
 var style = StyleSheet.create({
   button: {
     marginBottom: 10,
@@ -118,8 +199,26 @@ exports.examples = [
     title: 'Show Action Sheet',
     render(): ReactElement { return <ActionSheetExample />; }
   },
+  {
+    title: 'Show Action Sheet with tinted buttons',
+    render(): ReactElement { return <ActionSheetTintExample />; }
+  },
   {
     title: 'Show Share Action Sheet',
-    render(): ReactElement { return <ShareActionSheetExample />; }
+    render(): ReactElement {
+      return <ShareActionSheetExample url="https://code.facebook.com" />;
+    }
+  },
+  {
+    title: 'Share Local Image',
+    render(): ReactElement {
+      return <ShareActionSheetExample url="bunny.png" />;
+    }
+  },
+  {
+    title: 'Share Screenshot',
+    render(): ReactElement {
+      return <ShareScreenshotExample />;
+    }
   }
 ];
diff --git a/Examples/UIExplorer/AlertExample.js b/Examples/UIExplorer/AlertExample.js
new file mode 100644
index 000000000000..37f47dd9b01d
--- /dev/null
+++ b/Examples/UIExplorer/AlertExample.js
@@ -0,0 +1,136 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+'use strict';
+
+var React = require('react-native');
+var {
+  Alert,
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+  View,
+} = React;
+
+var UIExplorerBlock = require('./UIExplorerBlock');
+
+// corporate ipsum > lorem ipsum
+var alertMessage = 'Credibly reintermediate next-generation potentialities after goal-oriented ' +
+                   'catalysts for change. Dynamically revolutionize.';
+
+/**
+ * Simple alert examples.
+ */
+var SimpleAlertExampleBlock = React.createClass({
+
+  render: function() {
+    return (
+      <View>
+        <TouchableHighlight style={styles.wrapper}
+          onPress={() => Alert.alert(
+            'Alert Title',
+            alertMessage,
+          )}>
+          <View style={styles.button}>
+            <Text>Alert with message and default button</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight style={styles.wrapper}
+          onPress={() => Alert.alert(
+            'Alert Title',
+            alertMessage,
+            [
+              {text: 'OK', onPress: () => console.log('OK Pressed!')},
+            ]
+          )}>
+          <View style={styles.button}>
+            <Text>Alert with one button</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight style={styles.wrapper}
+          onPress={() => Alert.alert(
+            'Alert Title',
+            alertMessage,
+            [
+              {text: 'Cancel', onPress: () => console.log('Cancel Pressed!')},
+              {text: 'OK', onPress: () => console.log('OK Pressed!')},
+            ]
+          )}>
+          <View style={styles.button}>
+            <Text>Alert with two buttons</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight style={styles.wrapper}
+          onPress={() => Alert.alert(
+            'Alert Title',
+            null,
+            [
+              {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
+              {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
+              {text: 'Baz', onPress: () => console.log('Baz Pressed!')},
+            ]
+          )}>
+          <View style={styles.button}>
+            <Text>Alert with three buttons</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight style={styles.wrapper}
+          onPress={() => Alert.alert(
+            'Foo Title',
+            alertMessage,
+            '..............'.split('').map((dot, index) => ({
+              text: 'Button ' + index,
+              onPress: () => console.log('Pressed ' + index)
+            }))
+          )}>
+          <View style={styles.button}>
+            <Text>Alert with too many buttons</Text>
+          </View>
+        </TouchableHighlight>
+      </View>
+    );
+  },
+});
+
+var AlertExample = React.createClass({
+  statics: {
+    title: 'Alert',
+    description: 'Alerts display a concise and informative message ' +
+    'and prompt the user to make a decision.',
+  },
+  render: function() {
+    return (
+      <UIExplorerBlock title={'Alert'}>
+        <SimpleAlertExampleBlock />
+      </UIExplorerBlock>
+    );
+  }
+});
+
+var styles = StyleSheet.create({
+  wrapper: {
+    borderRadius: 5,
+    marginBottom: 5,
+  },
+  button: {
+    backgroundColor: '#eeeeee',
+    padding: 10,
+  },
+});
+
+module.exports = {
+  AlertExample,
+  SimpleAlertExampleBlock,
+};
diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js
index e09c883c137e..d7f0bcf0d464 100644
--- a/Examples/UIExplorer/AlertIOSExample.js
+++ b/Examples/UIExplorer/AlertIOSExample.js
@@ -24,103 +24,87 @@ var {
   AlertIOS,
 } = React;
 
+var { SimpleAlertExampleBlock } = require('./AlertExample');
+
 exports.framework = 'React';
 exports.title = 'AlertIOS';
 exports.description = 'iOS alerts and action sheets';
 exports.examples = [{
   title: 'Alerts',
+  render() {
+    return <SimpleAlertExampleBlock />;
+  }
+},
+{
+  title: 'Prompt Options',
+  render(): ReactElement {
+    return <PromptOptions />;
+  }
+},
+{
+  title: 'Prompt Types',
   render() {
     return (
       <View>
-        <TouchableHighlight style={styles.wrapper}
-          onPress={() => AlertIOS.alert(
-            'Foo Title',
-            'My Alert Msg'
-          )}>
-          <View style={styles.button}>
-            <Text>Alert with message and default button</Text>
-          </View>
-        </TouchableHighlight>
-        <TouchableHighlight style={styles.wrapper}
-          onPress={() => AlertIOS.alert(
-            null,
-            null,
-            [
-              {text: 'Button', onPress: () => console.log('Button Pressed!')},
-            ]
-          )}>
-          <View style={styles.button}>
-            <Text>Alert with only one button</Text>
-          </View>
-        </TouchableHighlight>
-        <TouchableHighlight style={styles.wrapper}
-          onPress={() => AlertIOS.alert(
-            'Foo Title',
-            'My Alert Msg',
-            [
-              {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
-              {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
-            ]
-          )}>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={() => AlertIOS.prompt('Plain Text Entry')}>
+
           <View style={styles.button}>
-            <Text>Alert with two buttons</Text>
+            <Text>
+              plain-text
+            </Text>
           </View>
+
         </TouchableHighlight>
-        <TouchableHighlight style={styles.wrapper}
-          onPress={() => AlertIOS.alert(
-            'Foo Title',
-            null,
-            [
-              {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
-              {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
-              {text: 'Baz', onPress: () => console.log('Baz Pressed!')},
-            ]
-          )}>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={() => AlertIOS.prompt('Secure Text', null, null, 'secure-text')}>
+
           <View style={styles.button}>
-            <Text>Alert with 3 buttons</Text>
+            <Text>
+              secure-text
+            </Text>
           </View>
+
         </TouchableHighlight>
-        <TouchableHighlight style={styles.wrapper}
-          onPress={() => AlertIOS.alert(
-            'Foo Title',
-            'My Alert Msg',
-            '..............'.split('').map((dot, index) => ({
-              text: 'Button ' + index,
-              onPress: () => console.log('Pressed ' + index)
-            }))
-          )}>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={() => AlertIOS.prompt('Login & Password', null, null, 'login-password')}>
+
           <View style={styles.button}>
-            <Text>Alert with too many buttons</Text>
+            <Text>
+              login-password
+            </Text>
           </View>
+
         </TouchableHighlight>
       </View>
     );
   }
-},
-{
-  title: 'Prompt',
-  render(): React.Component {
-    return <PromptExample />
-  }
 }];
 
-class PromptExample extends React.Component {
+class PromptOptions extends React.Component {
+  state: any;
+  customButtons: Array<Object>;
+
   constructor(props) {
     super(props);
 
-    this.promptResponse = this.promptResponse.bind(this);
-    this.state = {
-      promptValue: undefined,
-    };
+    // $FlowFixMe this seems to be a Flow bug, `saveResponse` is defined below
+    this.saveResponse = this.saveResponse.bind(this);
 
-    this.title = 'Type a value';
-    this.defaultValue = 'Default value';
-    this.buttons = [{
-      text: 'Custom cancel',
-    }, {
+    this.customButtons = [{
       text: 'Custom OK',
-      onPress: this.promptResponse
+      onPress: this.saveResponse
+    }, {
+      text: 'Custom Cancel',
+      style: 'cancel',
     }];
+
+    this.state = {
+      promptValue: undefined,
+    };
   }
 
   render() {
@@ -132,7 +116,7 @@ class PromptExample extends React.Component {
 
         <TouchableHighlight
           style={styles.wrapper}
-          onPress={this.prompt.bind(this, this.title, null, null, this.promptResponse)}>
+          onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse)}>
 
           <View style={styles.button}>
             <Text>
@@ -143,7 +127,7 @@ class PromptExample extends React.Component {
 
         <TouchableHighlight
           style={styles.wrapper}
-          onPress={this.prompt.bind(this, this.title, null, this.buttons, null)}>
+          onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons)}>
 
           <View style={styles.button}>
             <Text>
@@ -154,22 +138,22 @@ class PromptExample extends React.Component {
 
         <TouchableHighlight
           style={styles.wrapper}
-          onPress={this.prompt.bind(this, this.title, this.defaultValue, null, this.promptResponse)}>
+          onPress={() => AlertIOS.prompt('Type a value', null, this.saveResponse, undefined, 'Default value')}>
 
           <View style={styles.button}>
             <Text>
-              prompt with title, default value & callback
+              prompt with title, callback & default value
             </Text>
           </View>
         </TouchableHighlight>
 
         <TouchableHighlight
           style={styles.wrapper}
-          onPress={this.prompt.bind(this, this.title, this.defaultValue, this.buttons, null)}>
+          onPress={() => AlertIOS.prompt('Type a value', null, this.customButtons, 'login-password', 'admin@site.com')}>
 
           <View style={styles.button}>
             <Text>
-              prompt with title, default value & custom buttons
+              prompt with title, custom buttons, login/password & default value
             </Text>
           </View>
         </TouchableHighlight>
@@ -177,13 +161,8 @@ class PromptExample extends React.Component {
     );
   }
 
-  prompt() {
-    // Flow's apply support is broken: #7035621
-    ((AlertIOS.prompt: any).apply: any)(AlertIOS, arguments);
-  }
-
-  promptResponse(promptValue) {
-    this.setState({ promptValue });
+  saveResponse(promptValue) {
+    this.setState({ promptValue: JSON.stringify(promptValue) });
   }
 }
 
diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/AnimatedExample.js
index e417cacbefcc..1068ac000268 100644
--- a/Examples/UIExplorer/AnimatedExample.js
+++ b/Examples/UIExplorer/AnimatedExample.js
@@ -39,6 +39,8 @@ exports.examples = [
       'mounts.',
     render: function() {
       class FadeInView extends React.Component {
+        state: any;
+
         constructor(props) {
           super(props);
           this.state = {
@@ -66,6 +68,8 @@ exports.examples = [
         }
       }
       class FadeInExample extends React.Component {
+        state: any;
+
         constructor(props) {
           super(props);
           this.state = {
@@ -195,6 +199,7 @@ exports.examples = [
           {['Composite', 'Easing', 'Animations!'].map(
             (text, ii) => (
               <Animated.View
+                key={text}
                 style={[styles.content, {
                   left: this.anims[ii]
                 }]}>
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js
index d713b5f66d90..c87bc29823bc 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js
@@ -32,6 +32,10 @@ var CIRCLE_MARGIN = 18;
 var NUM_CIRCLES = 30;
 
 class Circle extends React.Component {
+  state: any;
+  props: any;
+  longTimer: number;
+
   _onLongPress: () => void;
   _toggleIsActive: () => void;
   constructor(props: Object): void {
@@ -156,6 +160,13 @@ class Circle extends React.Component {
 }
 
 class AnExApp extends React.Component {
+  state: any;
+  props: any;
+
+  static title = 'Animated - Gratuitous App';
+  static description = 'Bunch of Animations - tap a circle to ' +
+    'open a view with more animations, or longPress and drag to reorder circles.';
+
   _onMove: (position: Point) => void;
   constructor(props: any): void {
     super(props);
@@ -266,10 +277,6 @@ function moveToClosest({activeKey, keys, restLayouts}, position) {
   }
 }
 
-AnExApp.title = 'Animated - Gratuitous App';
-AnExApp.description = 'Bunch of Animations - tap a circle to ' +
-  'open a view with more animations, or longPress and drag to reorder circles.';
-
 var styles = StyleSheet.create({
   container: {
     flex: 1,
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js
index bebad819d5c5..39f15a8ec8ea 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExBobble.js
@@ -36,6 +36,8 @@ var BOBBLE_SPOTS = [...Array(NUM_BOBBLES)].map((_, i) => {  // static positions
 });
 
 class AnExBobble extends React.Component {
+  state: any;
+
   constructor(props: Object) {
     super(props);
     this.state = {};
@@ -95,6 +97,7 @@ class AnExBobble extends React.Component {
           return (
             <Animated.Image
               {...handlers}
+              key={i}
               source={{uri: BOBBLE_IMGS[j]}}
               style={[styles.circle, {
                 backgroundColor: randColor(),                             // re-renders are obvious
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js
index cf4083cff8c1..596617f1780e 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js
@@ -26,6 +26,8 @@ var {
 } = React;
 
 class AnExChained extends React.Component {
+  state: any;
+
   constructor(props: Object) {
     super(props);
     this.state = {
@@ -77,6 +79,7 @@ class AnExChained extends React.Component {
           return (
             <Animated.Image
               {...handlers}
+              key={i}
               source={{uri: CHAIN_IMGS[j]}}
               style={[styles.sticker, {
                 transform: this.state.stickers[j].getTranslateTransform(), // simple conversion
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js
index f96acb380a8d..e7712cfc52e8 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js
@@ -12,6 +12,7 @@
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  *
  * @providesModule AnExScroll
+ * @flow
  */
 'use strict';
 
@@ -26,12 +27,7 @@ var {
 } = React;
 
 class AnExScroll extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      scrollX: new Animated.Value(0),
-    };
-  }
+  state: any = { scrollX: new Animated.Value(0) };
 
   render() {
     var width = this.props.panelWidth;
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js
index f25301f7756f..769a0f79a2d2 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExSet.js
@@ -31,6 +31,8 @@ var AnExScroll = require('./AnExScroll');
 var AnExTilt = require('./AnExTilt');
 
 class AnExSet extends React.Component {
+  state: any;
+
   constructor(props: Object) {
     super(props);
     function randColor() {
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js
index 3cea77917088..42b5523c011c 100644
--- a/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js
+++ b/Examples/UIExplorer/AnimatedGratuitousApp/AnExTilt.js
@@ -26,6 +26,8 @@ var {
 } = React;
 
 class AnExTilt extends React.Component {
+  state: any;
+
   constructor(props: Object) {
     super(props);
     this.state = {
diff --git a/Examples/UIExplorer/AppStateExample.js b/Examples/UIExplorer/AppStateExample.js
new file mode 100644
index 000000000000..07d70be82de7
--- /dev/null
+++ b/Examples/UIExplorer/AppStateExample.js
@@ -0,0 +1,80 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @providesModule AppStateExample
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  AppState,
+  Text,
+  View
+} = React;
+
+var AppStateSubscription = React.createClass({
+  getInitialState() {
+    return {
+      appState: AppState.currentState,
+      previousAppStates: [],
+    };
+  },
+  componentDidMount: function() {
+    AppState.addEventListener('change', this._handleAppStateChange);
+  },
+  componentWillUnmount: function() {
+    AppState.removeEventListener('change', this._handleAppStateChange);
+  },
+  _handleAppStateChange: function(appState) {
+    var previousAppStates = this.state.previousAppStates.slice();
+    previousAppStates.push(this.state.appState);
+    this.setState({
+      appState,
+      previousAppStates,
+    });
+  },
+  render() {
+    if (this.props.showCurrentOnly) {
+      return (
+        <View>
+          <Text>{this.state.appState}</Text>
+        </View>
+      );
+    }
+    return (
+      <View>
+        <Text>{JSON.stringify(this.state.previousAppStates)}</Text>
+      </View>
+    );
+  }
+});
+
+exports.title = 'AppState';
+exports.description = 'app background status';
+exports.examples = [
+  {
+    title: 'AppState.currentState',
+    description: 'Can be null on app initialization',
+    render() { return <Text>{AppState.currentState}</Text>; }
+  },
+  {
+    title: 'Subscribed AppState:',
+    description: 'This changes according to the current state, so you can only ever see it rendered as "active"',
+    render(): ReactElement { return <AppStateSubscription showCurrentOnly={true} />; }
+  },
+  {
+    title: 'Previous states:',
+    render(): ReactElement { return <AppStateSubscription showCurrentOnly={false} />; }
+  },
+];
diff --git a/Examples/UIExplorer/AsyncStorageExample.js b/Examples/UIExplorer/AsyncStorageExample.js
index a091c0caaede..a3ca76437474 100644
--- a/Examples/UIExplorer/AsyncStorageExample.js
+++ b/Examples/UIExplorer/AsyncStorageExample.js
@@ -80,7 +80,7 @@ var BasicStorageExample = React.createClass({
         </Text>
         <Text>{' '}</Text>
         <Text>Messages:</Text>
-        {this.state.messages.map((m) => <Text>{m}</Text>)}
+        {this.state.messages.map((m) => <Text key={m}>{m}</Text>)}
       </View>
     );
   },
diff --git a/Examples/UIExplorer/BorderExample.js b/Examples/UIExplorer/BorderExample.js
index c6c22f5531f0..2dedfdd8e6ae 100644
--- a/Examples/UIExplorer/BorderExample.js
+++ b/Examples/UIExplorer/BorderExample.js
@@ -98,6 +98,34 @@ var styles = StyleSheet.create({
     marginRight: 10,
     backgroundColor: 'lightgrey',
   },
+  border9: {
+    borderWidth: 10,
+    borderTopLeftRadius: 10,
+    borderBottomRightRadius: 20,
+    borderColor: 'black',
+  },
+  border10: {
+    borderWidth: 10,
+    backgroundColor: 'white',
+    borderTopLeftRadius: 10,
+    borderBottomRightRadius: 20,
+    borderColor: 'black',
+    elevation: 10,
+  },
+  border11: {
+    width: 0,
+    height: 0,
+    borderStyle: 'solid',
+    overflow: 'hidden',
+    borderTopWidth: 50,
+    borderRightWidth: 0,
+    borderBottomWidth: 50,
+    borderLeftWidth: 100,
+    borderTopColor: 'transparent',
+    borderRightColor: 'transparent',
+    borderBottomColor: 'transparent',
+    borderLeftColor: 'red',
+  },
 });
 
 exports.title = 'Border';
@@ -180,4 +208,26 @@ exports.examples = [
       );
     }
   },
+  {
+    title: 'Corner Radii',
+    description: 'borderTopLeftRadius & borderBottomRightRadius',
+    render() {
+      return <View style={[styles.box, styles.border9]} />;
+    }
+  },
+  {
+    title: 'Corner Radii / Elevation',
+    description: 'borderTopLeftRadius & borderBottomRightRadius & elevation',
+    platform: 'android',
+    render() {
+      return <View style={[styles.box, styles.border10]} />;
+    }
+  },
+  {
+    title: 'CSS Trick - Triangle',
+    description: 'create a triangle by manipulating border colors and widths',
+    render() {
+      return <View style={[styles.border11]} />;
+    }
+  },
 ];
diff --git a/Examples/UIExplorer/BoxShadowExample.js b/Examples/UIExplorer/BoxShadowExample.js
new file mode 100644
index 000000000000..12568c1b67bc
--- /dev/null
+++ b/Examples/UIExplorer/BoxShadowExample.js
@@ -0,0 +1,85 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  Image,
+  StyleSheet,
+  View
+} = React;
+
+var styles = StyleSheet.create({
+  box: {
+    width: 100,
+    height: 100,
+    borderWidth: 2,
+  },
+  shadow1: {
+    shadowOpacity: 0.5,
+    shadowRadius: 3,
+    shadowOffset: {width: 2, height: 2},
+  },
+  shadow2: {
+    shadowOpacity: 1.0,
+    shadowColor: 'red',
+    shadowRadius: 0,
+    shadowOffset: {width: 3, height: 3},
+  },
+});
+
+exports.title = 'Box Shadow';
+exports.description = 'Demonstrates some of the shadow styles available to Views.';
+exports.examples = [
+  {
+    title: 'Basic shadow',
+    description: 'shadowOpacity: 0.5, shadowOffset: {2, 2}',
+    render() {
+      return <View style={[styles.box, styles.shadow1]} />;
+    }
+  },
+  {
+    title: 'Colored shadow',
+    description: 'shadowColor: \'red\', shadowRadius: 0',
+    render() {
+      return <View style={[styles.box, styles.shadow2]} />;
+    }
+  },
+  {
+    title: 'Shaped shadow',
+    description: 'borderRadius: 50',
+    render() {
+      return <View style={[styles.box, styles.shadow1, {borderRadius: 50}]} />;
+    }
+  },
+  {
+    title: 'Image shadow',
+    description: 'Image shadows are derived exactly from the pixels.',
+    render() {
+      return <Image
+        source={require('./hawk.png')}
+        style={[styles.box, styles.shadow1, {borderWidth: 0, overflow: 'visible'}]}
+      />;
+    }
+  },
+  {
+    title: 'Child shadow',
+    description: 'For views without an opaque background color, shadow will be derived from the subviews.',
+    render() {
+      return <View style={[styles.box, styles.shadow1, {backgroundColor: 'transparent'}]}>
+        <View style={[styles.box, {width: 80, height: 80, borderRadius: 40, margin: 8, backgroundColor: 'red'}]}/>
+      </View>;
+    }
+  },
+];
diff --git a/Examples/UIExplorer/CameraRollExample.ios.js b/Examples/UIExplorer/CameraRollExample.js
similarity index 72%
rename from Examples/UIExplorer/CameraRollExample.ios.js
rename to Examples/UIExplorer/CameraRollExample.js
index d783d9d8e243..d431560722f2 100644
--- a/Examples/UIExplorer/CameraRollExample.ios.js
+++ b/Examples/UIExplorer/CameraRollExample.js
@@ -15,24 +15,25 @@
  */
 'use strict';
 
-var React = require('react-native');
-var {
+const React = require('react-native');
+const {
   CameraRoll,
   Image,
   SliderIOS,
   StyleSheet,
-  SwitchIOS,
+  Switch,
   Text,
   View,
   TouchableOpacity
 } = React;
 
-var CameraRollView = require('./CameraRollView.ios');
-var AssetScaledImageExampleView = require('./AssetScaledImageExample');
+const CameraRollView = require('./CameraRollView');
 
-var CAMERA_ROLL_VIEW = 'camera_roll_view';
+const AssetScaledImageExampleView = require('./AssetScaledImageExample');
 
-var CameraRollExample = React.createClass({
+const CAMERA_ROLL_VIEW = 'camera_roll_view';
+
+const CameraRollExample = React.createClass({
 
   getInitialState() {
     return {
@@ -45,7 +46,7 @@ var CameraRollExample = React.createClass({
   render() {
     return (
       <View>
-        <SwitchIOS
+        <Switch
           onValueChange={this._onSwitchChange}
           value={this.state.bigImages} />
         <Text>{(this.state.bigImages ? 'Big' : 'Small') + ' Images'}</Text>
@@ -65,22 +66,24 @@ var CameraRollExample = React.createClass({
   },
 
   loadAsset(asset){
-    this.props.navigator.push({
-      title: 'Camera Roll Image',
-      component: AssetScaledImageExampleView,
-      backButtonTitle: 'Back',
-      passProps: { asset: asset },
-    });
+    if (this.props.navigator) {
+      this.props.navigator.push({
+        title: 'Camera Roll Image',
+        component: AssetScaledImageExampleView,
+        backButtonTitle: 'Back',
+        passProps: { asset: asset },
+      });
+    }
   },
 
   _renderImage(asset) {
-    var imageSize = this.state.bigImages ? 150 : 75;
-    var imageStyle = [styles.image, {width: imageSize, height: imageSize}];
-    var location = asset.node.location.longitude ?
+    const imageSize = this.state.bigImages ? 150 : 75;
+    const imageStyle = [styles.image, {width: imageSize, height: imageSize}];
+    const location = asset.node.location.longitude ?
       JSON.stringify(asset.node.location) : 'Unknown location';
     return (
-      <TouchableOpacity onPress={ this.loadAsset.bind( this, asset ) }>
-        <View key={asset} style={styles.row}>
+      <TouchableOpacity key={asset} onPress={ this.loadAsset.bind( this, asset ) }>
+        <View style={styles.row}>
           <Image
             source={asset.node.image}
             style={imageStyle}
@@ -97,9 +100,9 @@ var CameraRollExample = React.createClass({
   },
 
   _onSliderChange(value) {
-    var options = CameraRoll.GroupTypesOptions;
-    var index = Math.floor(value * options.length * 0.99);
-    var groupTypes = options[index];
+    const options = CameraRoll.GroupTypesOptions;
+    const index = Math.floor(value * options.length * 0.99);
+    const groupTypes = options[index];
     if (groupTypes !== this.state.groupTypes) {
       this.setState({groupTypes: groupTypes});
     }
@@ -111,7 +114,7 @@ var CameraRollExample = React.createClass({
   }
 });
 
-var styles = StyleSheet.create({
+const styles = StyleSheet.create({
   row: {
     flexDirection: 'row',
     flex: 1,
diff --git a/Examples/UIExplorer/CameraRollView.ios.js b/Examples/UIExplorer/CameraRollView.js
similarity index 97%
rename from Examples/UIExplorer/CameraRollView.ios.js
rename to Examples/UIExplorer/CameraRollView.js
index 74507aa2c1b5..a8824d048462 100644
--- a/Examples/UIExplorer/CameraRollView.ios.js
+++ b/Examples/UIExplorer/CameraRollView.js
@@ -22,6 +22,7 @@ var {
   CameraRoll,
   Image,
   ListView,
+  Platform,
   StyleSheet,
   View,
 } = React;
@@ -139,6 +140,10 @@ var CameraRollView = React.createClass({
       groupTypes: this.props.groupTypes,
       assetType: this.props.assetType,
     };
+    if (Platform.OS === "android") {
+      // not supported in android
+      delete fetchParams.groupTypes;
+    }
     if (this.state.lastCursor) {
       fetchParams.after = this.state.lastCursor;
     }
diff --git a/Examples/UIExplorer/ClipboardExample.js b/Examples/UIExplorer/ClipboardExample.js
new file mode 100644
index 000000000000..fa2814a2a7d8
--- /dev/null
+++ b/Examples/UIExplorer/ClipboardExample.js
@@ -0,0 +1,65 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  Clipboard,
+  View,
+  Text,
+} = React;
+
+var ClipboardExample = React.createClass({
+  getInitialState() {
+    return {
+      content: 'Content will appear here'
+    };
+  },
+
+  async _setClipboardContent(){
+    Clipboard.setString('Hello World');
+    try {
+      var content = await Clipboard.getString();
+      this.setState({content});
+    } catch (e) {
+      this.setState({content:e.message});
+    }
+  },
+
+  render() {
+    return (
+      <View>
+        <Text onPress={this._setClipboardContent} style={{color: 'blue'}}>
+          Tap to put "Hello World" in the clipboard
+        </Text>
+        <Text style={{color: 'red', marginTop: 20}}>
+          {this.state.content}
+        </Text>
+      </View>
+    );
+  }
+});
+
+exports.title = 'Clipboard';
+exports.description = 'Show Clipboard contents.';
+exports.examples = [
+  {
+    title: 'Clipboard.setString() and getString()',
+    render() {
+      return <ClipboardExample/>;
+    }
+  }
+];
diff --git a/Examples/UIExplorer/DatePickerAndroidExample.js b/Examples/UIExplorer/DatePickerAndroidExample.js
new file mode 100644
index 000000000000..e59517d91329
--- /dev/null
+++ b/Examples/UIExplorer/DatePickerAndroidExample.js
@@ -0,0 +1,117 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  DatePickerAndroid,
+  StyleSheet,
+  Text,
+  TouchableWithoutFeedback,
+} = React;
+
+var UIExplorerBlock = require('./UIExplorerBlock');
+var UIExplorerPage = require('./UIExplorerPage');
+
+var DatePickerAndroidExample = React.createClass({
+
+  statics: {
+    title: 'DatePickerAndroid',
+    description: 'Standard Android date picker dialog',
+  },
+
+  getInitialState() {
+    return {
+      presetDate: new Date(2020, 4, 5),
+      allDate: new Date(2020, 4, 5),
+      simpleText: 'pick a date',
+      minText: 'pick a date, no earlier than today',
+      maxText: 'pick a date, no later than today',
+      presetText: 'pick a date, preset to 2020/5/5',
+      allText: 'pick a date between 2020/5/1 and 2020/5/10',
+    };
+  },
+
+  async showPicker(stateKey, options) {
+    try {
+      var newState = {};
+      const {action, year, month, day} = await DatePickerAndroid.open(options);      
+      if (action === DatePickerAndroid.dismissedAction) {
+        newState[stateKey + 'Text'] = 'dismissed';
+      } else {
+        var date = new Date(year, month, day);
+        newState[stateKey + 'Text'] = date.toLocaleDateString();
+        newState[stateKey + 'Date'] = date;
+      }
+      this.setState(newState);
+    } catch ({code, message}) {
+      console.warn(`Error in example '${stateKey}': `, message);
+    }
+  },
+
+  render() {
+    return (
+      <UIExplorerPage title="DatePickerAndroid">
+        <UIExplorerBlock title="Simple date picker">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'simple', {date: this.state.simpleDate})}>
+            <Text style={styles.text}>{this.state.simpleText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Date picker with pre-set date">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'preset', {date: this.state.presetDate})}>
+            <Text style={styles.text}>{this.state.presetText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Date picker with minDate">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'min', {
+              date: this.state.minDate,
+              minDate: new Date(),
+            })}>
+            <Text style={styles.text}>{this.state.minText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Date picker with maxDate">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'max', {
+              date: this.state.maxDate,
+              maxDate: new Date(),
+            })}>
+            <Text style={styles.text}>{this.state.maxText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Date picker with all options">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'all', {
+              date: this.state.allDate,
+              minDate: new Date(2020, 4, 1),
+              maxDate: new Date(2020, 4, 10),
+            })}>
+            <Text style={styles.text}>{this.state.allText}</Text>
+          </TouchableWithoutFeedback>
+          </UIExplorerBlock>
+      </UIExplorerPage>
+    );
+  },
+});
+
+var styles = StyleSheet.create({
+  text: {
+    color: 'black',
+  },
+});
+
+module.exports = DatePickerAndroidExample;
diff --git a/Examples/UIExplorer/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js
index 4513f19e228b..ac9deaadfaf7 100644
--- a/Examples/UIExplorer/ExampleTypes.js
+++ b/Examples/UIExplorer/ExampleTypes.js
@@ -18,7 +18,6 @@
 
 export type Example = {
   title: string,
-  /* $FlowFixMe(>=0.16.0) */
   render: () => ?ReactElement<any, any, any>,
   description?: string,
   platform?: string;
diff --git a/Examples/UIExplorer/GeolocationExample.js b/Examples/UIExplorer/GeolocationExample.js
index d9dd4e842b74..61d7d3778bda 100644
--- a/Examples/UIExplorer/GeolocationExample.js
+++ b/Examples/UIExplorer/GeolocationExample.js
@@ -49,11 +49,15 @@ var GeolocationExample = React.createClass({
 
   componentDidMount: function() {
     navigator.geolocation.getCurrentPosition(
-      (initialPosition) => this.setState({initialPosition}),
+      (position) => {
+        var initialPosition = JSON.stringify(position);
+        this.setState({initialPosition});
+      },
       (error) => alert(error.message),
       {enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}
     );
-    this.watchID = navigator.geolocation.watchPosition((lastPosition) => {
+    this.watchID = navigator.geolocation.watchPosition((position) => {
+      var lastPosition = JSON.stringify(position);
       this.setState({lastPosition});
     });
   },
@@ -67,11 +71,11 @@ var GeolocationExample = React.createClass({
       <View>
         <Text>
           <Text style={styles.title}>Initial position: </Text>
-          {JSON.stringify(this.state.initialPosition)}
+          {this.state.initialPosition}
         </Text>
         <Text>
           <Text style={styles.title}>Current position: </Text>
-          {JSON.stringify(this.state.lastPosition)}
+          {this.state.lastPosition}
         </Text>
       </View>
     );
diff --git a/Examples/UIExplorer/ImageCapInsetsExample.js b/Examples/UIExplorer/ImageCapInsetsExample.js
index 8e17ac9285a7..f75f7da98d30 100644
--- a/Examples/UIExplorer/ImageCapInsetsExample.js
+++ b/Examples/UIExplorer/ImageCapInsetsExample.js
@@ -35,6 +35,7 @@ var ImageCapInsetsExample = React.createClass({
           <Image
             source={require('image!story-background')}
             style={styles.storyBackground}
+            resizeMode={Image.resizeMode.stretch}
             capInsets={{left: 0, right: 0, bottom: 0, top: 0}}
           />
         </View>
@@ -45,6 +46,7 @@ var ImageCapInsetsExample = React.createClass({
           <Image
             source={require('image!story-background')}
             style={styles.storyBackground}
+            resizeMode={Image.resizeMode.stretch}
             capInsets={{left: 15, right: 15, bottom: 15, top: 15}}
           />
         </View>
@@ -66,7 +68,6 @@ var styles = StyleSheet.create({
     width: 250,
     height: 150,
     borderWidth: 1,
-    resizeMode: Image.resizeMode.stretch,
   },
   text: {
     fontSize: 13.5,
diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js
index affd36796772..868bbd25f333 100644
--- a/Examples/UIExplorer/ImageEditingExample.js
+++ b/Examples/UIExplorer/ImageEditingExample.js
@@ -20,15 +20,16 @@ var React = require('react-native');
 var {
   CameraRoll,
   Image,
+  ImageEditor,
   NativeModules,
+  Platform,
   ScrollView,
   StyleSheet,
   Text,
   TouchableHighlight,
+  UIManager,
   View,
 } = React;
-var ImageEditingManager = NativeModules.ImageEditingManager;
-var RCTScrollViewConsts = NativeModules.UIManager.RCTScrollView.Constants;
 
 var PAGE_SIZE = 20;
 
@@ -42,14 +43,17 @@ type ImageSize = {
   height: number;
 };
 
-type TransformData = {
+type ImageCropData = {
   offset: ImageOffset;
   size: ImageSize;
-}
+  displaySize?: ?ImageSize;
+  resizeMode?: ?any;
+};
 
 class SquareImageCropper extends React.Component {
+  state: any;
   _isMounted: boolean;
-  _transformData: TransformData;
+  _transformData: ImageCropData;
 
   constructor(props) {
     super(props);
@@ -63,22 +67,21 @@ class SquareImageCropper extends React.Component {
     this._fetchRandomPhoto();
   }
 
-  _fetchRandomPhoto() {
-    CameraRoll.getPhotos(
-      {first: PAGE_SIZE},
-      (data) => {
-        if (!this._isMounted) {
-          return;
-        }
-        var edges = data.edges;
-        var edge = edges[Math.floor(Math.random() * edges.length)];
-        var randomPhoto = edge && edge.node && edge.node.image;
-        if (randomPhoto) {
-          this.setState({randomPhoto});
-        }
-      },
-      (error) => undefined
-    );
+  async _fetchRandomPhoto() {
+    try {
+      const data = await CameraRoll.getPhotos({first: PAGE_SIZE});
+      if (!this._isMounted) {
+        return;
+      }
+      var edges = data.edges;
+      var edge = edges[Math.floor(Math.random() * edges.length)];
+      var randomPhoto = edge && edge.node && edge.node.image;
+      if (randomPhoto) {
+        this.setState({randomPhoto});
+      }
+    } catch (error) {
+      console.warn("Can't get a photo from camera roll", error);
+    }
   }
 
   componentWillUnmount() {
@@ -166,7 +169,7 @@ class SquareImageCropper extends React.Component {
   }
 
   _crop() {
-    ImageEditingManager.cropImage(
+    ImageEditor.cropImage(
       this.state.randomPhoto.uri,
       this._transformData,
       (croppedImageURI) => this.setState({croppedImageURI}),
@@ -186,29 +189,49 @@ class SquareImageCropper extends React.Component {
 }
 
 class ImageCropper extends React.Component {
-  _scaledImageSize: ImageSize;
   _contentOffset: ImageOffset;
+  _maximumZoomScale: number;
+  _minimumZoomScale: number;
+  _scaledImageSize: ImageSize;
+  _horizontal: boolean;
 
   componentWillMount() {
     // Scale an image to the minimum size that is large enough to completely
     // fill the crop box.
     var widthRatio = this.props.image.width / this.props.size.width;
     var heightRatio = this.props.image.height / this.props.size.height;
-    if (widthRatio < heightRatio) {
+    this._horizontal = widthRatio > heightRatio;
+    if (this._horizontal) {
       this._scaledImageSize = {
-        width: this.props.size.width,
-        height: this.props.image.height / widthRatio,
+        width: this.props.image.width / heightRatio,
+        height: this.props.size.height,
       };
     } else {
       this._scaledImageSize = {
-        width: this.props.image.width / heightRatio,
-        height: this.props.size.height,
+        width: this.props.size.width,
+        height: this.props.image.height / widthRatio,
       };
+      if (Platform.OS === 'android') {
+        // hack to work around Android ScrollView a) not supporting zoom, and
+        // b) not supporting vertical scrolling when nested inside another
+        // vertical ScrollView (which it is, when displayed inside UIExplorer)
+        this._scaledImageSize.width *= 2;
+        this._scaledImageSize.height *= 2;
+        this._horizontal = true;
+      }
     }
     this._contentOffset = {
       x: (this._scaledImageSize.width - this.props.size.width) / 2,
       y: (this._scaledImageSize.height - this.props.size.height) / 2,
     };
+    this._maximumZoomScale = Math.min(
+      this.props.image.width / this._scaledImageSize.width,
+      this.props.image.height / this._scaledImageSize.height
+    );
+    this._minimumZoomScale = Math.max(
+      this.props.size.width / this._scaledImageSize.width,
+      this.props.size.height / this._scaledImageSize.height 
+    );
     this._updateTransformData(
       this._contentOffset,
       this._scaledImageSize,
@@ -230,7 +253,7 @@ class ImageCropper extends React.Component {
     var sizeRatioX = croppedImageSize.width / scaledImageSize.width;
     var sizeRatioY = croppedImageSize.height / scaledImageSize.height;
 
-    this.props.onTransformDataChange && this.props.onTransformDataChange({
+    var cropData: ImageCropData = {
       offset: {
         x: this.props.image.width * offsetRatioX,
         y: this.props.image.height * offsetRatioY,
@@ -239,23 +262,20 @@ class ImageCropper extends React.Component {
         width: this.props.image.width * sizeRatioX,
         height: this.props.image.height * sizeRatioY,
       },
-    });
+    };
+    this.props.onTransformDataChange && this.props.onTransformDataChange(cropData);
   }
 
   render() {
-    var decelerationRate =
-      RCTScrollViewConsts && RCTScrollViewConsts.DecelerationRate ?
-        RCTScrollViewConsts.DecelerationRate.Fast :
-        0;
-
     return (
       <ScrollView
         alwaysBounceVertical={true}
         automaticallyAdjustContentInsets={false}
         contentOffset={this._contentOffset}
-        decelerationRate={decelerationRate}
-        horizontal={true}
-        maximumZoomScale={3.0}
+        decelerationRate="fast"
+        horizontal={this._horizontal}
+        maximumZoomScale={this._maximumZoomScale}
+        minimumZoomScale={this._minimumZoomScale}
         onMomentumScrollEnd={this._onScroll.bind(this)}
         onScrollEndDrag={this._onScroll.bind(this)}
         showsHorizontalScrollIndicator={false}
@@ -270,8 +290,8 @@ class ImageCropper extends React.Component {
 }
 
 exports.framework = 'React';
-exports.title = 'ImageEditingManager';
-exports.description = 'Cropping and scaling with ImageEditingManager';
+exports.title = 'ImageEditor';
+exports.description = 'Cropping and scaling with ImageEditor';
 exports.examples = [{
   title: 'Image Cropping',
   render() {
diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js
index 4cab0c1a1cda..1a9af6c877cd 100644
--- a/Examples/UIExplorer/ImageExample.js
+++ b/Examples/UIExplorer/ImageExample.js
@@ -28,9 +28,46 @@ var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACS
 
 var ImageCapInsetsExample = require('./ImageCapInsetsExample');
 
-var NetworkImageExample = React.createClass({
-  watchID: (null: ?number),
+var NetworkImageCallbackExample = React.createClass({
+  getInitialState: function() {
+    return {
+      events: [],
+      mountTime: new Date(),
+    };
+  },
+
+  componentWillMount() {
+    this.setState({mountTime: new Date()});
+  },
 
+  render: function() {
+    var { mountTime } = this.state;
+
+    return (
+      <View>
+        <Image
+          source={this.props.source}
+          style={[styles.base, {overflow: 'visible'}]}
+          onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
+          onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
+          onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)}
+        />
+
+        <Text style={{marginTop: 20}}>
+          {this.state.events.join('\n')}
+        </Text>
+      </View>
+    );
+  },
+
+  _loadEventFired(event) {
+    this.setState((state) => {
+      return state.events = [...state.events, event];
+    });
+  }
+});
+
+var NetworkImageExample = React.createClass({
   getInitialState: function() {
     return {
       error: false,
@@ -58,6 +95,38 @@ var NetworkImageExample = React.createClass({
   }
 });
 
+var ImageSizeExample = React.createClass({
+  getInitialState: function() {
+    return {
+      width: 0,
+      height: 0,
+    };
+  },
+  componentDidMount: function() {
+    Image.getSize(this.props.source.uri, (width, height) => {
+      this.setState({width, height});
+    });
+  },
+  render: function() {
+    return (
+      <View style={{flexDirection: 'row'}}>
+        <Image
+          style={{
+            width: 60,
+            height: 60,
+            backgroundColor: 'transparent',
+            marginRight: 10,
+          }}
+          source={this.props.source} />
+        <Text>
+          Actual dimensions:{'\n'}
+          Width: {this.state.width}, Height: {this.state.height}
+        </Text>
+      </View>
+    );
+  },
+});
+
 exports.displayName = (undefined: ?string);
 exports.framework = 'React';
 exports.title = '<Image>';
@@ -79,19 +148,27 @@ exports.examples = [
   },
   {
     title: 'Plain Static Image',
-    description: 'Static assets should be required by prefixing with `image!` ' +
-      'and are located in the app bundle.',
+    description: 'Static assets should be placed in the source code tree, and ' +
+    'required in the same way as JavaScript modules.',
     render: function() {
       return (
         <View style={styles.horizontal}>
-          <Image source={require('image!uie_thumb_normal')} style={styles.icon} />
-          <Image source={require('image!uie_thumb_selected')} style={styles.icon} />
-          <Image source={require('image!uie_comment_normal')} style={styles.icon} />
-          <Image source={require('image!uie_comment_highlighted')} style={styles.icon} />
+          <Image source={require('./uie_thumb_normal.png')} style={styles.icon} />
+          <Image source={require('./uie_thumb_selected.png')} style={styles.icon} />
+          <Image source={require('./uie_comment_normal.png')} style={styles.icon} />
+          <Image source={require('./uie_comment_highlighted.png')} style={styles.icon} />
         </View>
       );
     },
   },
+  {
+    title: 'Image Loading Events',
+    render: function() {
+      return (
+        <NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
+      );
+    },
+  },
   {
     title: 'Error Handler',
     render: function() {
@@ -110,6 +187,20 @@ exports.examples = [
     },
     platform: 'ios',
   },
+  {
+    title: 'defaultSource',
+    description: 'Show a placeholder image when a network image is loading',
+    render: function() {
+      return (
+        <Image
+          defaultSource={require('./bunny.png')}
+          source={{uri: 'http://facebook.github.io/origami/public/images/birds.jpg'}}
+          style={styles.base}
+        />
+      );
+    },
+    platform: 'ios',
+  },
   {
     title: 'Border Color',
     render: function() {
@@ -243,19 +334,19 @@ exports.examples = [
         <View>
           <View style={styles.horizontal}>
             <Image
-              source={require('image!uie_thumb_normal')}
+              source={require('./uie_thumb_normal.png')}
               style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa' }]}
             />
             <Image
-              source={require('image!uie_thumb_normal')}
+              source={require('./uie_thumb_normal.png')}
               style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
             />
             <Image
-              source={require('image!uie_thumb_normal')}
+              source={require('./uie_thumb_normal.png')}
               style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
             />
             <Image
-              source={require('image!uie_thumb_normal')}
+              source={require('./uie_thumb_normal.png')}
               style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
             />
           </View>
@@ -361,6 +452,13 @@ exports.examples = [
     },
     platform: 'ios',
   },
+  {
+    title: 'Image Size',
+    render: function() {
+      return <ImageSizeExample source={fullImage} />; 
+    },
+    platform: 'ios',
+  },
 ];
 
 var fullImage = {uri: 'http://facebook.github.io/react/img/logo_og.png'};
diff --git a/Examples/UIExplorer/LayoutEventsExample.js b/Examples/UIExplorer/LayoutEventsExample.js
index bcbb5cbb7f93..a6b0cea89c0f 100644
--- a/Examples/UIExplorer/LayoutEventsExample.js
+++ b/Examples/UIExplorer/LayoutEventsExample.js
@@ -24,19 +24,30 @@ var {
   View,
 } = React;
 
+type Layout = {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+};
+
 type LayoutEvent = {
   nativeEvent: {
-    layout: {
-      x: number;
-      y: number;
-      width: number;
-      height: number;
-    };
+    layout: Layout,
   };
 };
 
+type State = {
+  containerStyle?: { width: number },
+  extraText?: string,
+  imageLayout?: Layout,
+  textLayout?: Layout,
+  viewLayout?: Layout,
+  viewStyle: { margin: number },
+};
+
 var LayoutEventExample = React.createClass({
-  getInitialState: function() {
+  getInitialState(): State {
     return {
       viewStyle: {
         margin: 20,
diff --git a/Examples/UIExplorer/IntentAndroidExample.android.js b/Examples/UIExplorer/LinkingExample.js
similarity index 87%
rename from Examples/UIExplorer/IntentAndroidExample.android.js
rename to Examples/UIExplorer/LinkingExample.js
index e655bd9650c8..fbf36bfc071d 100644
--- a/Examples/UIExplorer/IntentAndroidExample.android.js
+++ b/Examples/UIExplorer/LinkingExample.js
@@ -15,7 +15,7 @@
 
 var React = require('react-native');
 var {
-  IntentAndroid,
+  Linking,
   StyleSheet,
   Text,
   TouchableNativeFeedback,
@@ -30,9 +30,9 @@ var OpenURLButton = React.createClass({
   },
 
   handleClick: function() {
-    IntentAndroid.canOpenURL(this.props.url, (supported) => {
+    Linking.canOpenURL(this.props.url).then(supported => {
       if (supported) {
-        IntentAndroid.openURL(this.props.url);
+        Linking.openURL(this.props.url);
       } else {
         console.log('Don\'t know how to open URI: ' + this.props.url);
       }
@@ -54,8 +54,8 @@ var OpenURLButton = React.createClass({
 var IntentAndroidExample = React.createClass({
 
   statics: {
-    title: 'IntentAndroid',
-    description: 'Shows how to use Android Intents to open URLs.',
+    title: 'Linking',
+    description: 'Shows how to use Linking to open URLs.',
   },
 
   render: function() {
@@ -64,7 +64,9 @@ var IntentAndroidExample = React.createClass({
         <OpenURLButton url={'https://www.facebook.com'} />
         <OpenURLButton url={'http://www.facebook.com'} />
         <OpenURLButton url={'http://facebook.com'} />
+        <OpenURLButton url={'fb://notifications'} />
         <OpenURLButton url={'geo:37.484847,-122.148386'} />
+        <OpenURLButton url={'tel:9876543210'} />
       </UIExplorerBlock>
     );
   },
diff --git a/Examples/UIExplorer/ListViewExample.js b/Examples/UIExplorer/ListViewExample.js
index 6e18c6fb6553..ef16151ed221 100644
--- a/Examples/UIExplorer/ListViewExample.js
+++ b/Examples/UIExplorer/ListViewExample.js
@@ -21,6 +21,7 @@ var {
   ListView,
   TouchableHighlight,
   StyleSheet,
+  RecyclerViewBackedScrollView,
   Text,
   View,
 } = React;
@@ -29,7 +30,7 @@ var UIExplorerPage = require('./UIExplorerPage');
 
 var ListViewSimpleExample = React.createClass({
   statics: {
-    title: '<ListView> - Simple',
+    title: '<ListView>',
     description: 'Performant, scrollable list of data.'
   },
 
@@ -49,12 +50,14 @@ var ListViewSimpleExample = React.createClass({
   render: function() {
     return (
       <UIExplorerPage
-        title={this.props.navigator ? null : '<ListView> - Simple'}
+        title={this.props.navigator ? null : '<ListView>'}
         noSpacer={true}
         noScroll={true}>
         <ListView
           dataSource={this.state.dataSource}
           renderRow={this._renderRow}
+          renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
+          renderSeparator={(sectionID, rowID) => <View key={`${sectionID}-${rowID}`} style={styles.separator} />}
         />
       </UIExplorerPage>
     );
@@ -62,9 +65,7 @@ var ListViewSimpleExample = React.createClass({
 
   _renderRow: function(rowData: string, sectionID: number, rowID: number) {
     var rowHash = Math.abs(hashCode(rowData));
-    var imgSource = {
-      uri: THUMB_URLS[rowHash % THUMB_URLS.length],
-    };
+    var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length];
     return (
       <TouchableHighlight onPress={() => this._pressRow(rowID)}>
         <View>
@@ -74,7 +75,6 @@ var ListViewSimpleExample = React.createClass({
               {rowData + ' - ' + LOREM_IPSUM.substr(0, rowHash % 301 + 10)}
             </Text>
           </View>
-          <View style={styles.separator} />
         </View>
       </TouchableHighlight>
     );
@@ -98,18 +98,18 @@ var ListViewSimpleExample = React.createClass({
 });
 
 var THUMB_URLS = [
-  'Thumbnails/like.png',
-  'Thumbnails/dislike.png',
-  'Thumbnails/call.png',
-  'Thumbnails/fist.png',
-  'Thumbnails/bandaged.png',
-  'Thumbnails/flowers.png',
-  'Thumbnails/heart.png',
-  'Thumbnails/liking.png',
-  'Thumbnails/party.png',
-  'Thumbnails/poke.png',
-  'Thumbnails/superlike.png',
-  'Thumbnails/victory.png',
+  require('./Thumbnails/like.png'),
+  require('./Thumbnails/dislike.png'),
+  require('./Thumbnails/call.png'),
+  require('./Thumbnails/fist.png'),
+  require('./Thumbnails/bandaged.png'),
+  require('./Thumbnails/flowers.png'),
+  require('./Thumbnails/heart.png'),
+  require('./Thumbnails/liking.png'),
+  require('./Thumbnails/party.png'),
+  require('./Thumbnails/poke.png'),
+  require('./Thumbnails/superlike.png'),
+  require('./Thumbnails/victory.png'),
   ];
 var LOREM_IPSUM = 'Lorem ipsum dolor sit amet, ius ad pertinax oportere accommodare, an vix civibus corrumpit referrentur. Te nam case ludus inciderint, te mea facilisi adipiscing. Sea id integre luptatum. In tota sale consequuntur nec. Erat ocurreret mei ei. Eu paulo sapientem vulputate est, vel an accusam intellegam interesset. Nam eu stet pericula reprimique, ea vim illud modus, putant invidunt reprehendunt ne qui.';
 
diff --git a/Examples/UIExplorer/ListViewGridLayoutExample.js b/Examples/UIExplorer/ListViewGridLayoutExample.js
index 6af00aa8a947..4d8017335d56 100644
--- a/Examples/UIExplorer/ListViewGridLayoutExample.js
+++ b/Examples/UIExplorer/ListViewGridLayoutExample.js
@@ -26,18 +26,18 @@ var {
 } = React;
 
 var THUMB_URLS = [
-  'Thumbnails/like.png',
-  'Thumbnails/dislike.png',
-  'Thumbnails/call.png',
-  'Thumbnails/fist.png',
-  'Thumbnails/bandaged.png',
-  'Thumbnails/flowers.png',
-  'Thumbnails/heart.png',
-  'Thumbnails/liking.png',
-  'Thumbnails/party.png',
-  'Thumbnails/poke.png',
-  'Thumbnails/superlike.png',
-  'Thumbnails/victory.png',
+  require('./Thumbnails/like.png'),
+  require('./Thumbnails/dislike.png'),
+  require('./Thumbnails/call.png'),
+  require('./Thumbnails/fist.png'),
+  require('./Thumbnails/bandaged.png'),
+  require('./Thumbnails/flowers.png'),
+  require('./Thumbnails/heart.png'),
+  require('./Thumbnails/liking.png'),
+  require('./Thumbnails/party.png'),
+  require('./Thumbnails/poke.png'),
+  require('./Thumbnails/superlike.png'),
+  require('./Thumbnails/victory.png'),
 ];
 
 var ListViewGridLayoutExample = React.createClass({
@@ -67,6 +67,9 @@ var ListViewGridLayoutExample = React.createClass({
       <ListView
         contentContainerStyle={styles.list}
         dataSource={this.state.dataSource}
+        initialListSize={21}
+        pageSize={3} // should be a multiple of the no. of visible cells per row
+        scrollRenderAheadDistance={500}
         renderRow={this._renderRow}
       />
     );
@@ -74,9 +77,7 @@ var ListViewGridLayoutExample = React.createClass({
 
   _renderRow: function(rowData: string, sectionID: number, rowID: number) {
     var rowHash = Math.abs(hashCode(rowData));
-    var imgSource = {
-      uri: THUMB_URLS[rowHash % THUMB_URLS.length],
-    };
+    var imgSource = THUMB_URLS[rowHash % THUMB_URLS.length];
     return (
       <TouchableHighlight onPress={() => this._pressRow(rowID)} underlayColor="transparent">
         <View>
diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js
index b82f272c9dbb..a1406c52ff95 100644
--- a/Examples/UIExplorer/ListViewPagingExample.js
+++ b/Examples/UIExplorer/ListViewPagingExample.js
@@ -11,6 +11,7 @@
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  *
+ * @provides ListViewPagingExample
  * @flow
  */
 'use strict';
@@ -26,21 +27,25 @@ var {
   View,
 } = React;
 
-var PAGE_SIZE = 4;
+var NativeModules = require('NativeModules');
+var {
+  UIManager,
+} = NativeModules;
+
 var THUMB_URLS = [
-  'Thumbnails/like.png',
-  'Thumbnails/dislike.png',
-  'Thumbnails/call.png',
-  'Thumbnails/fist.png',
-  'Thumbnails/bandaged.png',
-  'Thumbnails/flowers.png',
-  'Thumbnails/heart.png',
-  'Thumbnails/liking.png',
-  'Thumbnails/party.png',
-  'Thumbnails/poke.png',
-  'Thumbnails/superlike.png',
-  'Thumbnails/victory.png',
-  ];
+  require('./Thumbnails/like.png'),
+  require('./Thumbnails/dislike.png'),
+  require('./Thumbnails/call.png'),
+  require('./Thumbnails/fist.png'),
+  require('./Thumbnails/bandaged.png'),
+  require('./Thumbnails/flowers.png'),
+  require('./Thumbnails/heart.png'),
+  require('./Thumbnails/liking.png'),
+  require('./Thumbnails/party.png'),
+  require('./Thumbnails/poke.png'),
+  require('./Thumbnails/superlike.png'),
+  require('./Thumbnails/victory.png'),
+];
 var NUM_SECTIONS = 100;
 var NUM_ROWS_PER_SECTION = 10;
 
@@ -48,6 +53,10 @@ var Thumb = React.createClass({
   getInitialState: function() {
     return {thumbIndex: this._getThumbIdx(), dir: 'row'};
   },
+  componentWillMount: function() {
+    UIManager.setLayoutAnimationEnabledExperimental &&
+      UIManager.setLayoutAnimationEnabledExperimental(true);
+  },
   _getThumbIdx: function() {
     return Math.floor(Math.random() * THUMB_URLS.length);
   },
@@ -64,9 +73,9 @@ var Thumb = React.createClass({
       <TouchableOpacity
         onPress={this._onPressThumb}
         style={[styles.buttonContents, {flexDirection: this.state.dir}]}>
-        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
-        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
-        <Image style={styles.img} source={{uri: THUMB_URLS[this.state.thumbIndex]}} />
+        <Image style={styles.img} source={THUMB_URLS[this.state.thumbIndex]} />
+        <Image style={styles.img} source={THUMB_URLS[this.state.thumbIndex]} />
+        <Image style={styles.img} source={THUMB_URLS[this.state.thumbIndex]} />
         {this.state.dir === 'column' ?
           <Text>
             Oooo, look at this new text!  So awesome it may just be crazy.
@@ -172,8 +181,8 @@ var ListViewPagingExample = React.createClass({
         renderSectionHeader={this.renderSectionHeader}
         renderRow={this.renderRow}
         initialListSize={10}
-        pageSize={PAGE_SIZE}
-        scrollRenderAheadDistance={2000}
+        pageSize={4}
+        scrollRenderAheadDistance={500}
       />
     );
   },
diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js
index 572017574759..d11d3006ac47 100644
--- a/Examples/UIExplorer/MapViewExample.js
+++ b/Examples/UIExplorer/MapViewExample.js
@@ -17,10 +17,13 @@
 
 var React = require('react-native');
 var {
+  Image,
   MapView,
+  PropTypes,
   StyleSheet,
   Text,
   TextInput,
+  TouchableOpacity,
   View,
 } = React;
 
@@ -34,22 +37,20 @@ var regionText = {
 var MapRegionInput = React.createClass({
 
   propTypes: {
-    region: React.PropTypes.shape({
-      latitude: React.PropTypes.number.isRequired,
-      longitude: React.PropTypes.number.isRequired,
-      latitudeDelta: React.PropTypes.number.isRequired,
-      longitudeDelta: React.PropTypes.number.isRequired,
+    region: PropTypes.shape({
+      latitude: PropTypes.number.isRequired,
+      longitude: PropTypes.number.isRequired,
+      latitudeDelta: PropTypes.number,
+      longitudeDelta: PropTypes.number,
     }),
-    onChange: React.PropTypes.func.isRequired,
+    onChange: PropTypes.func.isRequired,
   },
 
-  getInitialState: function() {
+  getInitialState() {
     return {
       region: {
         latitude: 0,
         longitude: 0,
-        latitudeDelta: 0,
-        longitudeDelta: 0,
       }
     };
   },
@@ -91,7 +92,9 @@ var MapRegionInput = React.createClass({
             {'Latitude delta'}
           </Text>
           <TextInput
-            value={'' + region.latitudeDelta}
+            value={
+              region.latitudeDelta == null ? '' : String(region.latitudeDelta)
+            }
             style={styles.textInput}
             onChange={this._onChangeLatitudeDelta}
             selectTextOnFocus={true}
@@ -102,7 +105,9 @@ var MapRegionInput = React.createClass({
             {'Longitude delta'}
           </Text>
           <TextInput
-            value={'' + region.longitudeDelta}
+            value={
+              region.longitudeDelta == null ? '' : String(region.longitudeDelta)
+            }
             style={styles.textInput}
             onChange={this._onChangeLongitudeDelta}
             selectTextOnFocus={true}
@@ -135,10 +140,12 @@ var MapRegionInput = React.createClass({
 
   _change: function() {
     this.setState({
-      latitude: parseFloat(regionText.latitude),
-      longitude: parseFloat(regionText.longitude),
-      latitudeDelta: parseFloat(regionText.latitudeDelta),
-      longitudeDelta: parseFloat(regionText.longitudeDelta),
+      region: {
+        latitude: parseFloat(regionText.latitude),
+        longitude: parseFloat(regionText.longitude),
+        latitudeDelta: parseFloat(regionText.latitudeDelta),
+        longitudeDelta: parseFloat(regionText.longitudeDelta),
+      },
     });
     this.props.onChange(this.state.region);
   },
@@ -149,10 +156,10 @@ var MapViewExample = React.createClass({
 
   getInitialState() {
     return {
-      mapRegion: null,
-      mapRegionInput: null,
-      annotations: null,
       isFirstLoad: true,
+      mapRegion: undefined,
+      mapRegionInput: undefined,
+      annotations: [],
     };
   },
 
@@ -163,12 +170,12 @@ var MapViewExample = React.createClass({
           style={styles.map}
           onRegionChange={this._onRegionChange}
           onRegionChangeComplete={this._onRegionChangeComplete}
-          region={this.state.mapRegion || undefined}
-          annotations={this.state.annotations || undefined}
+          region={this.state.mapRegion}
+          annotations={this.state.annotations}
         />
         <MapRegionInput
           onChange={this._onRegionInputChanged}
-          region={this.state.mapRegionInput || undefined}
+          region={this.state.mapRegionInput}
         />
       </View>
     );
@@ -208,6 +215,42 @@ var MapViewExample = React.createClass({
 
 });
 
+var AnnotationExample = React.createClass({
+
+  getInitialState() {
+    return {
+      isFirstLoad: true,
+      annotations: [],
+      mapRegion: undefined,
+    };
+  },
+
+  render() {
+    if (this.state.isFirstLoad) {
+      var onRegionChangeComplete = (region) => {
+        this.setState({
+          isFirstLoad: false,
+          annotations: [{
+            longitude: region.longitude,
+            latitude: region.latitude,
+            ...this.props.annotation,
+          }],
+        });
+      };
+    }
+
+    return (
+      <MapView
+        style={styles.map}
+        onRegionChangeComplete={onRegionChangeComplete}
+        region={this.state.mapRegion}
+        annotations={this.state.annotations}
+      />
+    );
+  },
+
+});
+
 var styles = StyleSheet.create({
   map: {
     height: 150,
@@ -242,12 +285,123 @@ exports.description = 'Base component to display maps';
 exports.examples = [
   {
     title: 'Map',
-    render(): ReactElement { return <MapViewExample />; }
+    render() {
+      return <MapViewExample />;
+    }
+  },
+  {
+    title: 'showsUserLocation + followUserLocation',
+    render() {
+      return (
+        <MapView
+          style={styles.map}
+          showsUserLocation={true}
+          followUserLocation={true}
+        />
+      );
+    }
   },
   {
-    title: 'Map shows user location',
+    title: 'Callout example',
     render() {
-      return  <MapView style={styles.map} showsUserLocation={true} />;
+      return <AnnotationExample style={styles.map} annotation={{
+        title: 'More Info...',
+        rightCalloutView: (
+          <TouchableOpacity
+            onPress={() => {
+              alert('You Are Here');
+            }}>
+            <Image
+              style={{width:30, height:30}}
+              source={require('./uie_thumb_selected.png')}
+            />
+          </TouchableOpacity>
+        ),
+      }}/>;
     }
-  }
+  },
+  {
+    title: 'Annotation focus example',
+    render() {
+      return <AnnotationExample style={styles.map} annotation={{
+        title: 'More Info...',
+        onFocus: () => {
+          alert('Annotation gets focus');
+        },
+        onBlur: () => {
+          alert('Annotation lost focus');
+        }
+      }}/>;
+    }
+  },
+  {
+    title: 'Draggable pin',
+    render() {
+      return <AnnotationExample style={styles.map} annotation={{
+        draggable: true,
+        onDragStateChange: (event) => {
+          console.log('Drag state: ' + event.state);
+        },
+      }}/>;
+    }
+  },
+  {
+    title: 'Custom pin color',
+    render() {
+      return <AnnotationExample style={styles.map} annotation={{
+        title: 'You Are Purple',
+        tintColor: MapView.PinColors.PURPLE,
+      }}/>;
+    }
+  },
+  {
+    title: 'Custom pin image',
+    render() {
+      return <AnnotationExample style={styles.map} annotation={{
+        title: 'Thumbs Up!',
+        image: require('image!uie_thumb_big'),
+      }}/>;
+    }
+  },
+  {
+    title: 'Custom pin view',
+    render() {
+      return <AnnotationExample style={styles.map} annotation={{
+        title: 'Thumbs Up!',
+        view: <View style={{
+          alignItems: 'center',
+        }}>
+          <Text style={{fontWeight: 'bold', color: '#f007'}}>
+            Thumbs Up!
+          </Text>
+          <Image
+            style={{width: 90, height: 65, resizeMode: 'cover'}}
+            source={require('image!uie_thumb_big')}
+          />
+        </View>,
+      }}/>;
+    }
+  },
+  {
+    title: 'Custom overlay',
+    render() {
+      return <MapView
+        style={styles.map}
+        region={{
+          latitude: 39.06,
+          longitude: -95.22,
+        }}
+        overlays={[{
+          coordinates:[
+            {latitude: 32.47, longitude: -107.85},
+            {latitude: 45.13, longitude: -94.48},
+            {latitude: 39.27, longitude: -83.25},
+            {latitude: 32.47, longitude: -107.85},
+          ],
+          strokeColor: '#f007',
+          lineWidth: 3,
+        }]}
+      />;
+    }
+  },
 ];
diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js
new file mode 100644
index 000000000000..15251a4cf2f7
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js
@@ -0,0 +1,164 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  StyleSheet,
+  ScrollView,
+  Text,
+  TouchableHighlight,
+  TouchableOpacity
+} = React;
+
+var _getRandomRoute = function() {
+  return {
+    title: '#' + Math.ceil(Math.random() * 1000),
+  };
+};
+
+var Navigator = NavigationExperimental.LegacyNavigator;
+
+class NavButton extends React.Component {
+  render() {
+    return (
+      <TouchableHighlight
+        style={styles.button}
+        underlayColor="#B5B5B5"
+        onPress={this.props.onPress}>
+        <Text style={styles.buttonText}>{this.props.text}</Text>
+      </TouchableHighlight>
+    );
+  }
+}
+
+var BreadcrumbNavSample = React.createClass({
+
+  componentWillMount: function() {
+    this._navBarRouteMapper = {
+      rightContentForRoute: function(route, navigator) {
+        return null;
+      },
+      titleContentForRoute: function(route, navigator) {
+        return (
+          <TouchableOpacity
+            onPress={() => navigator.push(_getRandomRoute())}>
+            <Text style={styles.titleText}>{route.title}</Text>
+          </TouchableOpacity>
+        );
+      },
+      iconForRoute: function(route, navigator) {
+        return (
+          <TouchableOpacity
+            onPress={() => { navigator.popToRoute(route); }}
+            style={styles.crumbIconPlaceholder}
+          />
+        );
+      },
+      separatorForRoute: function(route, navigator) {
+        return (
+          <TouchableOpacity
+            onPress={navigator.pop}
+            style={styles.crumbSeparatorPlaceholder}
+          />
+        );
+      }
+    };
+  },
+
+  _renderScene: function(route, navigator) {
+    return (
+      <ScrollView style={styles.scene}>
+        <NavButton
+          onPress={() => { navigator.push(_getRandomRoute()); }}
+          text="Push"
+        />
+        <NavButton
+          onPress={() => { navigator.immediatelyResetRouteStack([_getRandomRoute(), _getRandomRoute()]); }}
+          text="Reset w/ 2 scenes"
+        />
+        <NavButton
+          onPress={() => { navigator.popToTop(); }}
+          text="Pop to top"
+        />
+        <NavButton
+          onPress={() => { navigator.replace(_getRandomRoute()); }}
+          text="Replace"
+        />
+        <NavButton
+          onPress={() => { this.props.navigator.pop(); }}
+          text="Close breadcrumb example"
+        />
+      </ScrollView>
+    );
+  },
+
+  render: function() {
+    return (
+      <Navigator
+        style={styles.container}
+        initialRoute={_getRandomRoute()}
+        renderScene={this._renderScene}
+        navigationBar={
+          <Navigator.BreadcrumbNavigationBar
+            routeMapper={this._navBarRouteMapper}
+          />
+        }
+      />
+    );
+  },
+
+
+
+});
+
+var styles = StyleSheet.create({
+  scene: {
+    paddingTop: 50,
+    flex: 1,
+  },
+  button: {
+    backgroundColor: 'white',
+    padding: 15,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    borderBottomColor: '#CDCDCD',
+  },
+  buttonText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  container: {
+    overflow: 'hidden',
+    backgroundColor: '#dddddd',
+    flex: 1,
+  },
+  titleText: {
+    fontSize: 18,
+    color: '#666666',
+    textAlign: 'center',
+    fontWeight: 'bold',
+    lineHeight: 32,
+  },
+  crumbIconPlaceholder: {
+    flex: 1,
+    backgroundColor: '#666666',
+  },
+  crumbSeparatorPlaceholder: {
+    flex: 1,
+    backgroundColor: '#aaaaaa',
+  },
+});
+
+module.exports = BreadcrumbNavSample;
diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js
new file mode 100644
index 000000000000..aa2b9fd7df6c
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/JumpingNavSample.js
@@ -0,0 +1,218 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  StyleSheet,
+  ScrollView,
+  TabBarIOS,
+  Text,
+  TouchableHighlight,
+  View,
+} = React;
+
+var _getRandomRoute = function() {
+  return {
+    randNumber: Math.ceil(Math.random() * 1000),
+  };
+};
+
+var Navigator = NavigationExperimental.LegacyNavigator;
+
+class NavButton extends React.Component {
+  render() {
+    return (
+      <TouchableHighlight
+        style={styles.button}
+        underlayColor="#B5B5B5"
+        onPress={this.props.onPress}>
+        <Text style={styles.buttonText}>{this.props.text}</Text>
+      </TouchableHighlight>
+    );
+  }
+}
+
+var ROUTE_STACK = [
+  _getRandomRoute(),
+  _getRandomRoute(),
+  _getRandomRoute(),
+];
+var INIT_ROUTE_INDEX = 1;
+
+class JumpingNavBar extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      tabIndex: props.initTabIndex,
+    };
+  }
+  handleWillFocus(route) {
+    var tabIndex = ROUTE_STACK.indexOf(route);
+    this.setState({ tabIndex, });
+  }
+  render() {
+    return (
+      <View style={styles.tabs}>
+        <TabBarIOS>
+          <TabBarIOS.Item
+            icon={require('image!tabnav_notification')}
+            selected={this.state.tabIndex === 0}
+            onPress={() => {
+              this.props.onTabIndex(0);
+              this.setState({ tabIndex: 0, });
+            }}>
+            <View />
+          </TabBarIOS.Item>
+          <TabBarIOS.Item
+            icon={require('image!tabnav_list')}
+            selected={this.state.tabIndex === 1}
+            onPress={() => {
+              this.props.onTabIndex(1);
+              this.setState({ tabIndex: 1, });
+            }}>
+            <View />
+          </TabBarIOS.Item>
+          <TabBarIOS.Item
+            icon={require('image!tabnav_settings')}
+            selected={this.state.tabIndex === 2}
+            onPress={() => {
+              this.props.onTabIndex(2);
+              this.setState({ tabIndex: 2, });
+            }}>
+            <View />
+          </TabBarIOS.Item>
+        </TabBarIOS>
+      </View>
+    );
+  }
+}
+
+var JumpingNavSample = React.createClass({
+  render: function() {
+    return (
+      <Navigator
+        debugOverlay={false}
+        style={styles.appContainer}
+        ref={(navigator) => {
+          this._navigator = navigator;
+        }}
+        initialRoute={ROUTE_STACK[INIT_ROUTE_INDEX]}
+        initialRouteStack={ROUTE_STACK}
+        renderScene={this.renderScene}
+        configureScene={() => ({
+          ...Navigator.SceneConfigs.HorizontalSwipeJump,
+        })}
+        navigationBar={
+          <JumpingNavBar
+            ref={(navBar) => { this.navBar = navBar; }}
+            initTabIndex={INIT_ROUTE_INDEX}
+            routeStack={ROUTE_STACK}
+            onTabIndex={(index) => {
+              this._navigator.jumpTo(ROUTE_STACK[index]);
+            }}
+          />
+        }
+      />
+    );
+  },
+
+  renderScene: function(route, navigator) {
+    var backBtn;
+    var forwardBtn;
+    if (ROUTE_STACK.indexOf(route) !== 0) {
+      backBtn = (
+        <NavButton
+          onPress={() => {
+            navigator.jumpBack();
+          }}
+          text="jumpBack"
+        />
+      );
+    }
+    if (ROUTE_STACK.indexOf(route) !== ROUTE_STACK.length - 1) {
+      forwardBtn = (
+        <NavButton
+          onPress={() => {
+            navigator.jumpForward();
+          }}
+          text="jumpForward"
+        />
+      );
+    }
+    return (
+      <ScrollView style={styles.scene}>
+        <Text style={styles.messageText}>#{route.randNumber}</Text>
+        {backBtn}
+        {forwardBtn}
+        <NavButton
+          onPress={() => {
+            navigator.jumpTo(ROUTE_STACK[1]);
+          }}
+          text="jumpTo middle route"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.pop();
+          }}
+          text="Exit Navigation Example"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({
+              message: 'Came from jumping example',
+            });
+          }}
+          text="Nav Menu"
+        />
+      </ScrollView>
+    );
+  },
+});
+
+var styles = StyleSheet.create({
+  button: {
+    backgroundColor: 'white',
+    padding: 15,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    borderBottomColor: '#CDCDCD',
+  },
+  buttonText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  appContainer: {
+    overflow: 'hidden',
+    backgroundColor: '#dddddd',
+    flex: 1,
+  },
+  messageText: {
+    fontSize: 17,
+    fontWeight: '500',
+    padding: 15,
+    marginTop: 50,
+    marginLeft: 15,
+  },
+  scene: {
+    flex: 1,
+    paddingTop: 20,
+    backgroundColor: '#EAEAEA',
+  },
+  tabs: {
+    height: 50,
+  }
+});
+
+module.exports = JumpingNavSample;
diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js
new file mode 100644
index 000000000000..ed4169751418
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/LegacyNavigatorExample.js
@@ -0,0 +1,212 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  ScrollView,
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+} = React;
+
+var BreadcrumbNavSample = require('./BreadcrumbNavSample');
+var NavigationBarSample = require('./NavigationBarSample');
+var JumpingNavSample = require('./JumpingNavSample');
+
+var Navigator = NavigationExperimental.LegacyNavigator;
+
+class NavButton extends React.Component {
+  render() {
+    return (
+      <TouchableHighlight
+        style={styles.button}
+        underlayColor="#B5B5B5"
+        onPress={this.props.onPress}>
+        <Text style={styles.buttonText}>{this.props.text}</Text>
+      </TouchableHighlight>
+    );
+  }
+}
+
+class NavMenu extends React.Component {
+  render() {
+    return (
+      <ScrollView style={styles.scene}>
+        <Text style={styles.messageText}>{this.props.message}</Text>
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({
+              message: 'Swipe right to dismiss',
+              sceneConfig: Navigator.SceneConfigs.FloatFromRight,
+            });
+          }}
+          text="Float in from right"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({
+              message: 'Swipe down to dismiss',
+              sceneConfig: Navigator.SceneConfigs.FloatFromBottom,
+            });
+          }}
+          text="Float in from bottom"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.pop();
+          }}
+          text="Pop"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.popToTop();
+          }}
+          text="Pop to top"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({ id: 'navbar' });
+          }}
+          text="Navbar Example"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({ id: 'jumping' });
+          }}
+          text="Jumping Example"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.navigator.push({ id: 'breadcrumbs' });
+          }}
+          text="Breadcrumbs Example"
+        />
+        <NavButton
+          onPress={() => {
+            this.props.onExampleExit();
+          }}
+          text="Exit <LegacyNavigator> Example"
+        />
+      </ScrollView>
+    );
+  }
+}
+
+var TabBarExample = React.createClass({
+
+  statics: {
+    title: '<LegacyNavigator> (Experimental)',
+    description: 'Experimental navigator that ports the API of the current ' +
+      'Navigator component',
+  },
+
+  renderScene: function(route, nav) {
+    switch (route.id) {
+      case 'navbar':
+        return <NavigationBarSample navigator={nav} />;
+      case 'breadcrumbs':
+        return <BreadcrumbNavSample navigator={nav} />;
+      case 'jumping':
+        return <JumpingNavSample navigator={nav} />;
+      default:
+        return (
+          <NavMenu
+            message={route.message}
+            navigator={nav}
+            onExampleExit={this.props.onExampleExit}
+          />
+        );
+    }
+  },
+
+  render: function() {
+    return (
+      <Navigator
+        ref={this._setNavigatorRef}
+        style={styles.container}
+        initialRoute={{ message: 'First Scene', }}
+        renderScene={this.renderScene}
+        configureScene={(route) => {
+          if (route.sceneConfig) {
+            return route.sceneConfig;
+          }
+          return Navigator.SceneConfigs.FloatFromBottom;
+        }}
+      />
+    );
+  },
+
+
+  componentWillUnmount: function() {
+    this._listeners && this._listeners.forEach(listener => listener.remove());
+  },
+
+  _setNavigatorRef: function(navigator) {
+    if (navigator !== this._navigator) {
+      this._navigator = navigator;
+
+      if (navigator) {
+        var callback = (event) => {
+          console.log(
+            `TabBarExample: event ${event.type}`,
+            {
+              route: JSON.stringify(event.data.route),
+              target: event.target,
+              type: event.type,
+            }
+          );
+        };
+        // Observe focus change events from the owner.
+        this._listeners = [
+          navigator.navigationContext.addListener('willfocus', callback),
+          navigator.navigationContext.addListener('didfocus', callback),
+        ];
+      }
+    }
+  },
+});
+
+var styles = StyleSheet.create({
+  messageText: {
+    fontSize: 17,
+    fontWeight: '500',
+    padding: 15,
+    marginTop: 50,
+    marginLeft: 15,
+  },
+  container: {
+    flex: 1,
+  },
+  button: {
+    backgroundColor: 'white',
+    padding: 15,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    borderBottomColor: '#CDCDCD',
+  },
+  buttonText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  scene: {
+    flex: 1,
+    paddingTop: 20,
+    backgroundColor: '#ccc',
+  }
+});
+
+TabBarExample.external = true;
+
+module.exports = TabBarExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js
new file mode 100644
index 000000000000..9b9081dd0c7f
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js
@@ -0,0 +1,203 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  ScrollView,
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+  TouchableOpacity,
+} = React;
+
+var cssVar = require('cssVar');
+
+var Navigator = NavigationExperimental.LegacyNavigator;
+
+class NavButton extends React.Component {
+  render() {
+    return (
+      <TouchableHighlight
+        style={styles.button}
+        underlayColor="#B5B5B5"
+        onPress={this.props.onPress}>
+        <Text style={styles.buttonText}>{this.props.text}</Text>
+      </TouchableHighlight>
+    );
+  }
+}
+
+var NavigationBarRouteMapper = {
+
+  LeftButton: function(route, navigator, index, navState) {
+    if (index === 0) {
+      return null;
+    }
+
+    var previousRoute = navState.routeStack[index - 1];
+    return (
+      <TouchableOpacity
+        onPress={() => navigator.pop()}
+        style={styles.navBarLeftButton}>
+        <Text style={[styles.navBarText, styles.navBarButtonText]}>
+          {previousRoute.title}
+        </Text>
+      </TouchableOpacity>
+    );
+  },
+
+  RightButton: function(route, navigator, index, navState) {
+    return (
+      <TouchableOpacity
+        onPress={() => navigator.push(newRandomRoute())}
+        style={styles.navBarRightButton}>
+        <Text style={[styles.navBarText, styles.navBarButtonText]}>
+          Next
+        </Text>
+      </TouchableOpacity>
+    );
+  },
+
+  Title: function(route, navigator, index, navState) {
+    return (
+      <Text style={[styles.navBarText, styles.navBarTitleText]}>
+        {route.title} [{index}]
+      </Text>
+    );
+  },
+
+};
+
+function newRandomRoute() {
+  return {
+    title: '#' + Math.ceil(Math.random() * 1000),
+  };
+}
+
+var NavigationBarSample = React.createClass({
+
+  componentWillMount: function() {
+    var navigator = this.props.navigator;
+
+    var callback = (event) => {
+      console.log(
+        `NavigationBarSample : event ${event.type}`,
+        {
+          route: JSON.stringify(event.data.route),
+          target: event.target,
+          type: event.type,
+        }
+      );
+    };
+
+    // Observe focus change events from this component.
+    this._listeners = [
+      navigator.navigationContext.addListener('willfocus', callback),
+      navigator.navigationContext.addListener('didfocus', callback),
+    ];
+  },
+
+  componentWillUnmount: function() {
+    this._listeners && this._listeners.forEach(listener => listener.remove());
+  },
+
+  render: function() {
+    return (
+      <Navigator
+        debugOverlay={false}
+        style={styles.appContainer}
+        initialRoute={newRandomRoute()}
+        renderScene={(route, navigator) => (
+          <ScrollView style={styles.scene}>
+            <Text style={styles.messageText}>{route.content}</Text>
+            <NavButton
+              onPress={() => {
+                navigator.immediatelyResetRouteStack([
+                  newRandomRoute(),
+                  newRandomRoute(),
+                  newRandomRoute(),
+                ]);
+              }}
+              text="Reset w/ 3 scenes"
+            />
+            <NavButton
+              onPress={() => {
+                this.props.navigator.pop();
+              }}
+              text="Exit NavigationBar Example"
+            />
+          </ScrollView>
+        )}
+        navigationBar={
+          <Navigator.NavigationBar
+            routeMapper={NavigationBarRouteMapper}
+            style={styles.navBar}
+          />
+        }
+      />
+    );
+  },
+
+});
+
+var styles = StyleSheet.create({
+  messageText: {
+    fontSize: 17,
+    fontWeight: '500',
+    padding: 15,
+    marginTop: 50,
+    marginLeft: 15,
+  },
+  button: {
+    backgroundColor: 'white',
+    padding: 15,
+    borderBottomWidth: StyleSheet.hairlineWidth,
+    borderBottomColor: '#CDCDCD',
+  },
+  buttonText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  navBar: {
+    backgroundColor: 'white',
+  },
+  navBarText: {
+    fontSize: 16,
+    marginVertical: 10,
+  },
+  navBarTitleText: {
+    color: cssVar('fbui-bluegray-60'),
+    fontWeight: '500',
+    marginVertical: 9,
+  },
+  navBarLeftButton: {
+    paddingLeft: 10,
+  },
+  navBarRightButton: {
+    paddingRight: 10,
+  },
+  navBarButtonText: {
+    color: cssVar('fbui-accent-blue'),
+  },
+  scene: {
+    flex: 1,
+    paddingTop: 20,
+    backgroundColor: '#EAEAEA',
+  },
+});
+
+module.exports = NavigationBarSample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js
new file mode 100644
index 000000000000..e74d80e26306
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js
@@ -0,0 +1,128 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  Animated,
+  NavigationExperimental,
+  StyleSheet,
+  ScrollView,
+} = React;
+var NavigationExampleRow = require('./NavigationExampleRow');
+var {
+  AnimatedView: NavigationAnimatedView,
+  Card: NavigationCard,
+  RootContainer: NavigationRootContainer,
+  Reducer: NavigationReducer,
+  Header: NavigationHeader,
+} = NavigationExperimental;
+
+const NavigationBasicReducer = NavigationReducer.StackReducer({
+  getPushedReducerForAction: (action) => {
+    if (action.type === 'push') {
+      return (state) => state || {key: action.key};
+    }
+    return null;
+  },
+  getReducerForState: (initialState) => (state) => state || initialState,
+  initialState: {
+    key: 'AnimatedExampleStackKey',
+    index: 0,
+    children: [
+      {key: 'First Route'},
+    ],
+  },
+});
+
+class NavigationAnimatedExample extends React.Component {
+  componentWillMount() {
+    this._renderNavigated = this._renderNavigated.bind(this);
+  }
+  render() {
+    return (
+      <NavigationRootContainer
+        reducer={NavigationBasicReducer}
+        ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
+        persistenceKey="NavigationAnimExampleState"
+        renderNavigation={this._renderNavigated}
+      />
+    );
+  }
+  handleBackAction() {
+    return (
+      this.navRootContainer &&
+      this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
+    );
+  }
+  _renderNavigated(navigationState, onNavigate) {
+    if (!navigationState) {
+      return null;
+    }
+    return (
+      <NavigationAnimatedView
+        navigationState={navigationState}
+        style={styles.animatedView}
+        renderOverlay={(props) => (
+          <NavigationHeader
+            navigationState={props.navigationParentState}
+            position={props.position}
+            getTitle={state => state.key}
+          />
+        )}
+        setTiming={(pos, navState) => {
+          Animated.timing(pos, {toValue: navState.index, duration: 1000}).start();
+        }}
+        renderScene={(props) => (
+          <NavigationCard
+            key={props.navigationState.key}
+            index={props.index}
+            navigationState={props.navigationParentState}
+            position={props.position}
+            layout={props.layout}>
+            <ScrollView style={styles.scrollView}>
+              <NavigationExampleRow
+                text={props.navigationState.key}
+              />
+              <NavigationExampleRow
+                text="Push!"
+                onPress={() => {
+                  onNavigate({
+                    type: 'push',
+                    key: 'Route #' + props.navigationParentState.children.length
+                  });
+                }}
+              />
+              <NavigationExampleRow
+                text="Exit Animated Nav Example"
+                onPress={this.props.onExampleExit}
+              />
+            </ScrollView>
+          </NavigationCard>
+        )}
+      />
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  animatedView: {
+    flex: 1,
+  },
+  scrollView: {
+    marginTop: 64
+  },
+});
+
+module.exports = NavigationAnimatedExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js
new file mode 100644
index 000000000000..447ab2051fba
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js
@@ -0,0 +1,100 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+const React = require('react-native');
+const {
+  NavigationExperimental,
+  ScrollView,
+  StyleSheet,
+} = React;
+const NavigationExampleRow = require('./NavigationExampleRow');
+const {
+  RootContainer: NavigationRootContainer,
+  Reducer: NavigationReducer,
+} = NavigationExperimental;
+const StackReducer = NavigationReducer.StackReducer;
+
+const NavigationBasicReducer = NavigationReducer.StackReducer({
+  getPushedReducerForAction: (action) => {
+    if (action.type === 'push') {
+      return (state) => state || {key: action.key};
+    }
+    return null;
+  },
+  getReducerForState: (initialState) => (state) => state || initialState,
+  initialState: {
+    key: 'BasicExampleStackKey',
+    index: 0,
+    children: [
+      {key: 'First Route'},
+    ],
+  },
+});
+
+const NavigationBasicExample = React.createClass({
+  render: function() {
+    return (
+      <NavigationRootContainer
+        reducer={NavigationBasicReducer}
+        persistenceKey="NavigationBasicExampleState"
+        ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
+        renderNavigation={(navState, onNavigate) => {
+          if (!navState) { return null; }
+          return (
+            <ScrollView style={styles.topView}>
+              <NavigationExampleRow
+                text={`Current page: ${navState.children[navState.index].key}`}
+              />
+              <NavigationExampleRow
+                text={`Push page #${navState.children.length}`}
+                onPress={() => {
+                  onNavigate({ type: 'push', key: 'page #' + navState.children.length });
+                }}
+              />
+              <NavigationExampleRow
+                text="pop"
+                onPress={() => {
+                  onNavigate(NavigationRootContainer.getBackAction());
+                }}
+              />
+              <NavigationExampleRow
+                text="Exit Basic Nav Example"
+                onPress={this.props.onExampleExit}
+              />
+            </ScrollView>
+          );
+        }}
+      />
+    );
+  },
+
+  handleBackAction: function() {
+    return (
+      this.navRootContainer &&
+      this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
+    );
+  },
+
+});
+
+const styles = StyleSheet.create({
+  topView: {
+    backgroundColor: '#E9E9EF',
+    flex: 1,
+    paddingTop: 30,
+  },
+});
+
+module.exports = NavigationBasicExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
new file mode 100644
index 000000000000..e7feb7c60b9b
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
@@ -0,0 +1,151 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+const NavigationExampleRow = require('./NavigationExampleRow');
+const NavigationRootContainer = require('NavigationRootContainer');
+const React = require('react-native');
+
+const {
+  NavigationExperimental,
+  StyleSheet,
+  ScrollView,
+} = React;
+
+const NavigationCardStack = NavigationExperimental.CardStack;
+const NavigationStateUtils = NavigationExperimental.StateUtils;
+
+function reduceNavigationState(initialState) {
+  return (currentState, action) => {
+    switch (action.type) {
+      case 'RootContainerInitialAction':
+        return initialState;
+
+      case 'push':
+        return NavigationStateUtils.push(currentState, {key: action.key});
+
+      case 'back':
+      case 'pop':
+        return currentState.index > 0 ?
+          NavigationStateUtils.pop(currentState) :
+          currentState;
+
+      default:
+        return currentState;
+    }
+  };
+}
+
+const ExampleReducer = reduceNavigationState({
+  index: 0,
+  children: [{key: 'First Route'}],
+});
+
+class NavigationCardStackExample extends React.Component {
+
+  constructor(props, context) {
+    super(props, context);
+
+    this._renderNavigation = this._renderNavigation.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+    this._toggleDirection = this._toggleDirection.bind(this);
+
+    this.state = {isHorizontal: true};
+  }
+
+  render() {
+    return (
+      <NavigationRootContainer
+        reducer={ExampleReducer}
+        renderNavigation={this._renderNavigation}
+        style={styles.main}
+      />
+    );
+  }
+
+  _renderNavigation(navigationState, onNavigate) {
+    return (
+      <NavigationCardStack
+        direction={this.state.isHorizontal ? 'horizontal' : 'vertical'}
+        navigationState={navigationState}
+        onNavigate={onNavigate}
+        renderScene={this._renderScene}
+        style={styles.main}
+      />
+    );
+  }
+
+  _renderScene(props) {
+    const {navigationParentState, onNavigate} = props;
+    return (
+      <ScrollView style={styles.scrollView}>
+        <NavigationExampleRow
+          text={
+            this.state.isHorizontal ?
+            'direction = "horizontal"' :
+            'direction = "vertical"'
+          }
+          onPress={this._toggleDirection}
+        />
+        <NavigationExampleRow
+          text={'route = ' + props.navigationState.key}
+        />
+        <NavigationExampleRow
+          text="Push Route"
+          onPress={() => {
+            onNavigate({
+              type: 'push',
+              key: 'Route ' + navigationParentState.children.length,
+            });
+          }}
+        />
+        <NavigationExampleRow
+          text="Pop Route"
+          onPress={() => {
+            onNavigate({
+              type: 'pop',
+            });
+          }}
+        />
+        <NavigationExampleRow
+          text="Exit Card Stack Example"
+          onPress={this.props.onExampleExit}
+        />
+      </ScrollView>
+    );
+  }
+
+  _toggleDirection() {
+    this.setState({
+      isHorizontal: !this.state.isHorizontal,
+    });
+  }
+
+  _onNavigate(action) {
+    if (action && action.type === 'back') {
+      this._pop();
+    }
+  }
+}
+
+const styles = StyleSheet.create({
+  main: {
+    flex: 1,
+  },
+  scrollView: {
+    marginTop: 64
+  },
+});
+
+module.exports = NavigationCardStackExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js
new file mode 100644
index 000000000000..bc1d184db5de
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js
@@ -0,0 +1,280 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const {
+  NavigationExperimental,
+  ScrollView,
+  StyleSheet,
+  View,
+} = React;
+const {
+  AnimatedView: NavigationAnimatedView,
+  Card: NavigationCard,
+  Container: NavigationContainer,
+  RootContainer: NavigationRootContainer,
+  Header: NavigationHeader,
+  Reducer: NavigationReducer,
+  View: NavigationView,
+} = NavigationExperimental;
+const NavigationExampleRow = require('./NavigationExampleRow');
+const NavigationExampleTabBar = require('./NavigationExampleTabBar');
+
+import type {NavigationParentState} from 'NavigationStateUtils';
+
+type Action = {
+  isExitAction?: boolean,
+};
+
+const ExampleExitAction = () => ({
+  isExitAction: true,
+});
+ExampleExitAction.match = (action: Action) => (
+  action && action.isExitAction === true
+);
+
+const PageAction = (type) => ({
+  type,
+  isPageAction: true,
+});
+PageAction.match = (action) => (
+  action && action.isPageAction === true
+);
+
+const ExampleProfilePageAction = (type) => ({
+  ...PageAction(type),
+  isProfilePageAction: true,
+});
+ExampleProfilePageAction.match = (action) => (
+  action && action.isProfilePageAction === true
+);
+
+const ExampleInfoAction = () => PageAction('InfoPage');
+
+const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage');
+
+const _jsInstanceUniqueId = '' + Date.now();
+let _uniqueIdCount = 0;
+function pageStateActionMap(action) {
+  return {
+    key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++),
+    type: action.type,
+  };
+}
+
+const ExampleAppReducer = NavigationReducer.TabsReducer({
+  key: 'AppNavigationState',
+  initialIndex: 0,
+  tabReducers: [
+    NavigationReducer.StackReducer({
+      getPushedReducerForAction: (action) => {
+        if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) {
+          return (state) => (state || pageStateActionMap(action));
+        }
+        return null;
+      },
+      initialState: {
+        key: 'notifs',
+        index: 0,
+        children: [
+          {key: 'base', type: 'NotifsPage'},
+        ],
+      },
+    }),
+    NavigationReducer.StackReducer({
+      getPushedReducerForAction: (action) => {
+        if (PageAction.match(action) && !ExampleProfilePageAction.match(action)) {
+          return (state) => (state || pageStateActionMap(action));
+        }
+        return null;
+      },
+      initialState: {
+        key: 'settings',
+        index: 0,
+        children: [
+          {key: 'base', type: 'SettingsPage'},
+        ],
+      },
+    }),
+    NavigationReducer.StackReducer({
+      getPushedReducerForAction: (action) => {
+        if (PageAction.match(action) || ExampleProfilePageAction.match(action)) {
+          return (state) => (state || pageStateActionMap(action));
+        }
+        return null;
+      },
+      initialState: {
+        key: 'profile',
+        index: 0,
+        children: [
+          {key: 'base', type: 'ProfilePage'},
+        ],
+      },
+    }),
+  ],
+});
+
+function stateTypeTitleMap(pageState) {
+  switch (pageState.type) {
+    case 'ProfilePage':
+      return 'Profile Page';
+    case 'NotifsPage':
+      return 'Notifications';
+    case 'SettingsPage':
+      return 'Settings';
+    case 'InfoPage':
+      return 'Info Page';
+    case 'NotifProfilePage':
+      return 'Page in Profile';
+  }
+}
+
+class ExampleTabScreen extends React.Component {
+  render() {
+    return (
+      <NavigationAnimatedView
+        style={styles.tabContent}
+        navigationState={this.props.navigationState}
+        renderOverlay={this._renderHeader.bind(this)}
+        renderScene={this._renderScene.bind(this)}
+      />
+    );
+  }
+  _renderHeader(props) {
+    return (
+      <NavigationHeader
+        navigationState={props.navigationParentState}
+        position={props.position}
+        layout={props.layout}
+        getTitle={state => stateTypeTitleMap(state)}
+      />
+    );
+  }
+  _renderScene(props) {
+    return (
+      <NavigationCard
+        key={props.navigationState.key}
+        index={props.index}
+        navigationState={props.navigationParentState}
+        position={props.position}
+        layout={props.layout}>
+        <ScrollView style={styles.scrollView}>
+          <NavigationExampleRow
+            text="Open page"
+            onPress={() => {
+              this.props.onNavigate(ExampleInfoAction());
+            }}
+          />
+          <NavigationExampleRow
+            text="Open a page in the profile tab"
+            onPress={() => {
+              this.props.onNavigate(ExampleNotifProfileAction());
+            }}
+          />
+          <NavigationExampleRow
+            text="Exit Composition Example"
+            onPress={() => {
+              this.props.onNavigate(ExampleExitAction());
+            }}
+          />
+        </ScrollView>
+      </NavigationCard>
+    );
+  }
+}
+ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);
+
+class NavigationCompositionExample extends React.Component {
+  navRootContainer: NavigationRootContainer;
+
+  render() {
+    return (
+      <NavigationRootContainer
+        reducer={ExampleAppReducer}
+        persistenceKey="NavigationCompositionState"
+        ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
+        renderNavigation={this.renderApp.bind(this)}
+      />
+    );
+  }
+  handleBackAction(): boolean {
+    return (
+      this.navRootContainer &&
+      this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
+    );
+  }
+  renderApp(navigationState: NavigationParentState, onNavigate: Function) {
+    if (!navigationState) {
+      return null;
+    }
+    return (
+      <View style={styles.topView}>
+        <ExampleMainView
+          navigationState={navigationState}
+          onExampleExit={this.props.onExampleExit}
+        />
+        <NavigationExampleTabBar
+          tabs={navigationState.children}
+          index={navigationState.index}
+        />
+      </View>
+    );
+  }
+}
+
+class ExampleMainView extends React.Component {
+  render() {
+    return (
+      <NavigationView
+        navigationState={this.props.navigationState}
+        style={styles.tabsContent}
+        renderScene={(tabState, index) => (
+          <ExampleTabScreen
+            key={tabState.key}
+            navigationState={tabState}
+            onNavigate={this._handleNavigation.bind(this, tabState.key)}
+          />
+        )}
+      />
+    );
+  }
+  _handleNavigation(tabKey, action) {
+    if (ExampleExitAction.match(action)) {
+      this.props.onExampleExit();
+      return;
+    }
+    this.props.onNavigate(action);
+  }
+}
+ExampleMainView = NavigationContainer.create(ExampleMainView);
+
+const styles = StyleSheet.create({
+  topView: {
+    flex: 1,
+  },
+  tabsContent: {
+    flex: 1,
+  },
+  scrollView: {
+    marginTop: 64
+  },
+  tabContent: {
+    flex: 1,
+  },
+});
+
+module.exports = NavigationCompositionExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js
new file mode 100644
index 000000000000..4e431e30a722
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationExampleRow.js
@@ -0,0 +1,65 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  Text,
+  PixelRatio,
+  StyleSheet,
+  View,
+  TouchableHighlight,
+} = React;
+
+var NavigationExampleRow = React.createClass({
+  render: function() {
+    if (this.props.onPress) {
+      return (
+        <TouchableHighlight
+          style={styles.row}
+          underlayColor="#D0D0D0"
+          onPress={this.props.onPress}>
+          <Text style={styles.buttonText}>
+            {this.props.text}
+          </Text>
+        </TouchableHighlight>
+      );
+    }
+    return (
+      <View style={styles.row}>
+        <Text style={styles.rowText}>
+          {this.props.text}
+        </Text>
+      </View>
+    );
+  },
+});
+
+const styles = StyleSheet.create({
+  row: {
+    padding: 15,
+    backgroundColor: 'white',
+    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomColor: '#CDCDCD',
+  },
+  rowText: {
+    fontSize: 17,
+  },
+  buttonText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+});
+
+module.exports = NavigationExampleRow;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExampleTabBar.js b/Examples/UIExplorer/NavigationExperimental/NavigationExampleTabBar.js
new file mode 100644
index 000000000000..6dbaab095b9b
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationExampleTabBar.js
@@ -0,0 +1,81 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  View,
+} = React;
+const {
+  Container: NavigationContainer,
+  Reducer: NavigationReducer,
+} = NavigationExperimental;
+const {
+  JumpToAction,
+} = NavigationReducer.TabsReducer;
+
+var NavigationExampleTabBar = React.createClass({
+  render: function() {
+    return (
+      <View style={styles.tabBar}>
+        {this.props.tabs.map(this._renderTab)}
+      </View>
+    );
+  },
+  _renderTab: function(tab, index) {
+    var textStyle = [styles.tabButtonText];
+    if (this.props.index === index) {
+      textStyle.push(styles.selectedTabButtonText);
+    }
+    return (
+      <TouchableOpacity
+        style={styles.tabButton}
+        key={tab.key}
+        onPress={() => {
+          this.props.onNavigate(JumpToAction(index));
+        }}>
+        <Text style={textStyle}>
+          {tab.key}
+        </Text>
+      </TouchableOpacity>
+    );
+  },
+});
+
+NavigationExampleTabBar = NavigationContainer.create(NavigationExampleTabBar);
+
+const styles = StyleSheet.create({
+  tabBar: {
+    height: 50,
+    flexDirection: 'row',
+  },
+  tabButton: {
+    flex: 1,
+  },
+  tabButtonText: {
+    paddingTop: 20,
+    textAlign: 'center',
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  selectedTabButtonText: {
+    color: 'blue',
+  },
+});
+
+module.exports = NavigationExampleTabBar;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js
new file mode 100644
index 000000000000..0781570da28d
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js
@@ -0,0 +1,152 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+var React = require('react-native');
+var {
+  AsyncStorage,
+  ScrollView,
+  StyleSheet,
+  View,
+} = React;
+var NavigationExampleRow = require('./NavigationExampleRow');
+
+/*
+ * Heads up! This file is not the real navigation example- only a utility to switch between them.
+ *
+ * To learn how to use the Navigation API, take a look at the following example files:
+ */
+var EXAMPLES = {
+  'Tabs': require('./NavigationTabsExample'),
+  'Basic': require('./NavigationBasicExample'),
+  'Animated Example': require('./NavigationAnimatedExample'),
+  'Composition': require('./NavigationCompositionExample'),
+  'Card Stack Example': require('./NavigationCardStackExample'),
+  'Tic Tac Toe': require('./NavigationTicTacToeExample'),
+};
+
+var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample';
+
+var NavigationExperimentalExample = React.createClass({
+  statics: {
+    title: 'Navigation (Experimental)',
+    description: 'Upcoming navigation APIs and animated navigation views',
+    external: true,
+  },
+
+  getInitialState: function() {
+    return {
+      example: null,
+    };
+  },
+
+  componentDidMount() {
+    AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => {
+      if (err || !example || !EXAMPLES[example]) {
+        this.setState({
+          example: 'menu',
+        });
+        return;
+      }
+      this.setState({
+        example,
+      });
+    });
+  },
+
+  setExample: function(example) {
+    this.setState({
+      example,
+    });
+    AsyncStorage.setItem(EXAMPLE_STORAGE_KEY, example);
+  },
+
+  _renderMenu: function() {
+    var exitRow = null;
+    if (this.props.onExampleExit) {
+      exitRow = (
+        <NavigationExampleRow
+          text="Exit Navigation Examples"
+          onPress={this.props.onExampleExit}
+        />
+      );
+    }
+    return (
+      <View style={styles.menu}>
+        <ScrollView>
+          {this._renderExampleList()}
+          {exitRow}
+        </ScrollView>
+      </View>
+    );
+  },
+
+  _renderExampleList: function() {
+    return Object.keys(EXAMPLES).map(exampleName => (
+      <NavigationExampleRow
+        key={exampleName}
+        text={exampleName}
+        onPress={() => {
+          this.setExample(exampleName);
+        }}
+      />
+    ));
+  },
+
+  _exitInnerExample: function() {
+    this.setExample('menu');
+  },
+
+  handleBackAction: function() {
+    const wasHandledByExample = (
+      this.exampleRef &&
+      this.exampleRef.handleBackAction &&
+      this.exampleRef.handleBackAction()
+    );
+    if (wasHandledByExample) {
+      return true;
+    }
+    if (this.state.example && this.state.example !== 'menu') {
+      this._exitInnerExample();
+      return true;
+    }
+    return false;
+  },
+
+  render: function() {
+    if (this.state.example === 'menu') {
+      return this._renderMenu();
+    }
+    if (EXAMPLES[this.state.example]) {
+      var Component = EXAMPLES[this.state.example];
+      return (
+        <Component
+          onExampleExit={this._exitInnerExample}
+          ref={exampleRef => { this.exampleRef = exampleRef; }}
+        />
+      );
+    }
+    return null;
+  },
+});
+
+const styles = StyleSheet.create({
+  menu: {
+    backgroundColor: '#E9E9EF',
+    flex: 1,
+    marginTop: 20,
+  },
+});
+
+module.exports = NavigationExperimentalExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js
new file mode 100644
index 000000000000..ae4de59a6da8
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationTabsExample.js
@@ -0,0 +1,111 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+'use strict';
+
+const React = require('react-native');
+const {
+  NavigationExperimental,
+  ScrollView,
+  StyleSheet,
+  View,
+} = React;
+const {
+  Container: NavigationContainer,
+  RootContainer: NavigationRootContainer,
+  Reducer: NavigationReducer,
+} = NavigationExperimental;
+
+const NavigationExampleRow = require('./NavigationExampleRow');
+const NavigationExampleTabBar = require('./NavigationExampleTabBar');
+
+class ExmpleTabPage extends React.Component {
+  render() {
+    const currentTabRoute = this.props.tabs[this.props.index];
+    return (
+      <ScrollView style={styles.tabPage}>
+        <NavigationExampleRow
+          text={`Current Tab is: ${currentTabRoute.key}`}
+        />
+        {this.props.tabs.map((tab, index) => (
+          <NavigationExampleRow
+            key={tab.key}
+            text={`Go to ${tab.key}`}
+            onPress={() => {
+              this.props.onNavigate(NavigationReducer.TabsReducer.JumpToAction(index));
+            }}
+          />
+        ))}
+        <NavigationExampleRow
+          text="Exit Tabs Example"
+          onPress={this.props.onExampleExit}
+        />
+      </ScrollView>
+    );
+  }
+}
+ExmpleTabPage = NavigationContainer.create(ExmpleTabPage);
+
+const ExampleTabsReducer = NavigationReducer.TabsReducer({
+  tabReducers: [
+    (lastRoute) => lastRoute || {key: 'one'},
+    (lastRoute) => lastRoute || {key: 'two'},
+    (lastRoute) => lastRoute || {key: 'three'},
+  ],
+});
+
+class NavigationTabsExample extends React.Component {
+  render() {
+    return (
+      <NavigationRootContainer
+        reducer={ExampleTabsReducer}
+        persistenceKey="NAV_EXAMPLE_STATE_TABS"
+        ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
+        renderNavigation={(navigationState) => {
+          if (!navigationState) { return null; }
+          return (
+            <View style={styles.topView}>
+              <ExmpleTabPage
+                tabs={navigationState.children}
+                index={navigationState.index}
+                onExampleExit={this.props.onExampleExit}
+              />
+              <NavigationExampleTabBar
+                tabs={navigationState.children}
+                index={navigationState.index}
+              />
+            </View>
+          );
+        }}
+      />
+    );
+  }
+  handleBackAction() {
+    return (
+      this.navRootContainer &&
+      this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  topView: {
+    flex: 1,
+    paddingTop: 30,
+  },
+  tabPage: {
+    backgroundColor: '#E9E9EF',
+  },
+});
+
+module.exports = NavigationTabsExample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js
new file mode 100644
index 000000000000..d0604ec08eae
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js
@@ -0,0 +1,314 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @providesModule NavigationTicTacToeExample
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  NavigationExperimental,
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+  View,
+} = React;
+const {
+  Container: NavigationContainer,
+  RootContainer: NavigationRootContainer,
+} = NavigationExperimental;
+
+type GameGrid = Array<Array<?string>>;
+
+const evenOddPlayerMap = ['O', 'X'];
+const rowLeterMap = ['a', 'b', 'c'];
+
+function parseGame(game: string): GameGrid {
+  const gameTurns = game ? game.split('-') : [];
+  const grid = Array(3);
+  for (let i = 0; i < 3; i++) {
+    const row = Array(3);
+    for (let j = 0; j < 3; j++) {
+      const turnIndex = gameTurns.indexOf(rowLeterMap[i]+j);
+      if (turnIndex === -1) {
+        row[j] = null;
+      } else {
+        row[j] = evenOddPlayerMap[turnIndex % 2];
+      }
+    }
+    grid[i] = row;
+  }
+  return grid;
+}
+
+function playTurn(game: string, row: number, col: number): string {
+  const turn = rowLeterMap[row] + col;
+  return game ? (game + '-' + turn) : turn;
+}
+
+function getWinner(gameString: string): ?string {
+  const game = parseGame(gameString);
+  for (var i = 0; i < 3; i++) {
+    if (game[i][0] !== null && game[i][0] === game[i][1] &&
+        game[i][0] === game[i][2]) {
+      return game[i][0];
+    }
+  }
+  for (var i = 0; i < 3; i++) {
+    if (game[0][i] !== null && game[0][i] === game[1][i] &&
+        game[0][i] === game[2][i]) {
+      return game[0][i];
+    }
+  }
+  if (game[0][0] !== null && game[0][0] === game[1][1] &&
+      game[0][0] === game[2][2]) {
+    return game[0][0];
+  }
+  if (game[0][2] !== null && game[0][2] === game[1][1] &&
+      game[0][2] === game[2][0]) {
+    return game[0][2];
+  }
+  return null;
+}
+
+function isGameOver(gameString: string): boolean {
+  if (getWinner(gameString)) {
+    return true;
+  }
+  const game = parseGame(gameString);
+  for (var i = 0; i < 3; i++) {
+    for (var j = 0; j < 3; j++) {
+      if (game[i][j] === null) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+class Cell extends React.Component {
+  props: any;
+  cellStyle() {
+    switch (this.props.player) {
+      case 'X':
+        return styles.cellX;
+      case 'O':
+        return styles.cellO;
+      default:
+        return null;
+    }
+  }
+  textStyle() {
+    switch (this.props.player) {
+      case 'X':
+        return styles.cellTextX;
+      case 'O':
+        return styles.cellTextO;
+      default:
+        return {};
+    }
+  }
+  render() {
+    return (
+      <TouchableHighlight
+        onPress={this.props.onPress}
+        underlayColor="transparent"
+        activeOpacity={0.5}>
+        <View style={[styles.cell, this.cellStyle()]}>
+          <Text style={[styles.cellText, this.textStyle()]}>
+            {this.props.player}
+          </Text>
+        </View>
+      </TouchableHighlight>
+    );
+  }
+}
+
+function GameEndOverlay(props) {
+  if (!isGameOver(props.game)) {
+    return <View />;
+  }
+  const winner = getWinner(props.game);
+  return (
+    <View style={styles.overlay}>
+      <Text style={styles.overlayMessage}>
+        {winner ? winner + ' wins!' : 'It\'s a tie!'}
+      </Text>
+      <TouchableHighlight
+        onPress={() => props.onNavigate(GameActions.Reset())}
+        underlayColor="transparent"
+        activeOpacity={0.5}>
+        <View style={styles.newGame}>
+          <Text style={styles.newGameText}>New Game</Text>
+        </View>
+      </TouchableHighlight>
+    </View>
+  );
+}
+GameEndOverlay = NavigationContainer.create(GameEndOverlay);
+
+function TicTacToeGame(props) {
+  var rows = parseGame(props.game).map((cells, row) =>
+    <View key={'row' + row} style={styles.row}>
+      {cells.map((player, col) =>
+        <Cell
+          key={'cell' + col}
+          player={player}
+          onPress={() => props.onNavigate(GameActions.Turn(row, col))}
+        />
+      )}
+    </View>
+  );
+  return (
+    <View style={styles.container}>
+      <Text
+        style={styles.closeButton}
+        onPress={props.onExampleExit}>
+        Close
+      </Text>
+      <Text style={styles.title}>EXTREME T3</Text>
+      <View style={styles.board}>
+        {rows}
+      </View>
+      <GameEndOverlay
+        game={props.game}
+      />
+    </View>
+  );
+}
+TicTacToeGame = NavigationContainer.create(TicTacToeGame);
+
+const GameActions = {
+  Turn: (row, col) => ({type: 'TicTacToeTurnAction', row, col }),
+  Reset: (row, col) => ({type: 'TicTacToeResetAction' }),
+};
+
+function GameReducer(lastGame: ?string, action: Object): string {
+  if (!lastGame) {
+    lastGame = '';
+  }
+  if (action.type === 'TicTacToeResetAction') {
+    return '';
+  }
+  if (!isGameOver(lastGame) && action.type === 'TicTacToeTurnAction') {
+    return playTurn(lastGame, action.row, action.col);
+  }
+  return lastGame;
+}
+
+class NavigationTicTacToeExample extends React.Component {
+  static GameView = TicTacToeGame;
+  static GameReducer = GameReducer;
+  static GameActions = GameActions;
+  render() {
+    return (
+      <NavigationRootContainer
+        reducer={GameReducer}
+        persistenceKey="TicTacToeGameState"
+        renderNavigation={(game) => (
+          <TicTacToeGame
+            game={game}
+            onExampleExit={this.props.onExampleExit}
+          />
+        )}
+      />
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  closeButton: {
+    position: 'absolute',
+    left: 10,
+    top: 30,
+    fontSize: 14,
+  },
+  container: {
+    flex: 1,
+    justifyContent: 'center',
+    alignItems: 'center',
+    backgroundColor: 'white'
+  },
+  title: {
+    fontFamily: 'Chalkduster',
+    fontSize: 39,
+    marginBottom: 20,
+  },
+  board: {
+    padding: 5,
+    backgroundColor: '#47525d',
+    borderRadius: 10,
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  cell: {
+    width: 80,
+    height: 80,
+    borderRadius: 5,
+    backgroundColor: '#7b8994',
+    margin: 5,
+    flex: 1,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  cellX: {
+    backgroundColor: '#72d0eb',
+  },
+  cellO: {
+    backgroundColor: '#7ebd26',
+  },
+  cellText: {
+    fontSize: 50,
+    fontFamily: 'AvenirNext-Bold',
+  },
+  cellTextX: {
+    color: '#19a9e5',
+  },
+  cellTextO: {
+    color: '#b9dc2f',
+  },
+  overlay: {
+    position: 'absolute',
+    top: 0,
+    bottom: 0,
+    left: 0,
+    right: 0,
+    backgroundColor: 'rgba(221, 221, 221, 0.5)',
+    flex: 1,
+    flexDirection: 'column',
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  overlayMessage: {
+    fontSize: 40,
+    marginBottom: 20,
+    marginLeft: 20,
+    marginRight: 20,
+    fontFamily: 'AvenirNext-DemiBold',
+    textAlign: 'center',
+  },
+  newGame: {
+    backgroundColor: '#887766',
+    padding: 20,
+    borderRadius: 5,
+  },
+  newGameText: {
+    color: 'white',
+    fontSize: 20,
+    fontFamily: 'AvenirNext-DemiBold',
+  },
+});
+
+module.exports = NavigationTicTacToeExample;
diff --git a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
index 62196e5f2f4d..e05a8c39e839 100644
--- a/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
+++ b/Examples/UIExplorer/Navigator/BreadcrumbNavSample.js
@@ -15,7 +15,6 @@
 
 var React = require('react-native');
 var {
-  PixelRatio,
   Navigator,
   StyleSheet,
   ScrollView,
@@ -131,7 +130,7 @@ var styles = StyleSheet.create({
   button: {
     backgroundColor: 'white',
     padding: 15,
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: '#CDCDCD',
   },
   buttonText: {
diff --git a/Examples/UIExplorer/Navigator/JumpingNavSample.js b/Examples/UIExplorer/Navigator/JumpingNavSample.js
index eecabcbce9ca..54fe312c2202 100644
--- a/Examples/UIExplorer/Navigator/JumpingNavSample.js
+++ b/Examples/UIExplorer/Navigator/JumpingNavSample.js
@@ -16,7 +16,6 @@
 var React = require('react-native');
 var {
   Navigator,
-  PixelRatio,
   StyleSheet,
   ScrollView,
   TabBarIOS,
@@ -185,7 +184,7 @@ var styles = StyleSheet.create({
   button: {
     backgroundColor: 'white',
     padding: 15,
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: '#CDCDCD',
   },
   buttonText: {
diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js
index 77e0740028a7..25f45f0ab303 100644
--- a/Examples/UIExplorer/Navigator/NavigationBarSample.js
+++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js
@@ -16,7 +16,6 @@
 
 var React = require('react-native');
 var {
-  PixelRatio,
   Navigator,
   ScrollView,
   StyleSheet,
@@ -164,7 +163,7 @@ var styles = StyleSheet.create({
   button: {
     backgroundColor: 'white',
     padding: 15,
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: '#CDCDCD',
   },
   buttonText: {
diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js
index aa156410933c..7346af4542d4 100644
--- a/Examples/UIExplorer/Navigator/NavigatorExample.js
+++ b/Examples/UIExplorer/Navigator/NavigatorExample.js
@@ -10,13 +10,14 @@
  * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
+ *
+ * @providesModule NavigatorExample
+ */
 'use strict';
 
 var React = require('react-native');
 var {
   Navigator,
-  PixelRatio,
   ScrollView,
   StyleSheet,
   Text,
@@ -190,7 +191,7 @@ var styles = StyleSheet.create({
   button: {
     backgroundColor: 'white',
     padding: 15,
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: '#CDCDCD',
   },
   buttonText: {
diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js
index 4078cac6db64..1735633c9d34 100644
--- a/Examples/UIExplorer/NavigatorIOSColorsExample.js
+++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js
@@ -16,7 +16,7 @@
 var React = require('react-native');
 var {
   NavigatorIOS,
-  StatusBarIOS,
+  StatusBar,
   StyleSheet,
   Text,
   View
@@ -45,7 +45,7 @@ var NavigatorIOSColors = React.createClass({
 
   render: function() {
     // Set StatusBar with light contents to get better contrast
-    StatusBarIOS.setStyle('light-content');
+    StatusBar.setBarStyle('light-content');
 
     return (
       <NavigatorIOS
@@ -55,7 +55,7 @@ var NavigatorIOSColors = React.createClass({
           title: '<NavigatorIOS>',
           rightButtonTitle: 'Done',
           onRightButtonPress: () => {
-            StatusBarIOS.setStyle('default');
+            StatusBar.setBarStyle('default');
             this.props.onExampleExit();
           },
           passProps: {
diff --git a/Examples/UIExplorer/NavigatorIOSExample.js b/Examples/UIExplorer/NavigatorIOSExample.js
index 4a2011a65461..3c6f19f94dfc 100644
--- a/Examples/UIExplorer/NavigatorIOSExample.js
+++ b/Examples/UIExplorer/NavigatorIOSExample.js
@@ -15,12 +15,12 @@
  */
 'use strict';
 
-var React = require('react-native');
-var ViewExample = require('./ViewExample');
-var createExamplePage = require('./createExamplePage');
-var {
+const React = require('react-native');
+const ViewExample = require('./ViewExample');
+const createExamplePage = require('./createExamplePage');
+const {
   AlertIOS,
-  PixelRatio,
+  NavigatorIOS,
   ScrollView,
   StyleSheet,
   Text,
@@ -28,8 +28,7 @@ var {
   View,
 } = React;
 
-var EmptyPage = React.createClass({
-
+const EmptyPage = React.createClass({
   render: function() {
     return (
       <View style={styles.emptyPage}>
@@ -39,41 +38,24 @@ var EmptyPage = React.createClass({
       </View>
     );
   },
-
 });
 
-var NavigatorIOSExample = React.createClass({
-
-  statics: {
-    title: '<NavigatorIOS>',
-    description: 'iOS navigation capabilities',
-  },
-
+const NavigatorIOSExamplePage = React.createClass({
   render: function() {
     var recurseTitle = 'Recurse Navigation';
-    if (!this.props.topExampleRoute) {
+    if (!this.props.depth || this.props.depth === 1) {
       recurseTitle += ' - more examples here';
     }
     return (
       <ScrollView style={styles.list}>
-        <View style={styles.line}/>
-        <View style={styles.group}>
-          <View style={styles.row}>
-            <Text style={styles.rowNote}>
-              See &lt;UIExplorerApp&gt; for top-level usage.
-            </Text>
-          </View>
-        </View>
-        <View style={styles.line}/>
-        <View style={styles.groupSpace}/>
         <View style={styles.line}/>
         <View style={styles.group}>
           {this._renderRow(recurseTitle, () => {
             this.props.navigator.push({
               title: NavigatorIOSExample.title,
-              component: NavigatorIOSExample,
+              component: NavigatorIOSExamplePage,
               backButtonTitle: 'Custom Back',
-              passProps: {topExampleRoute: this.props.topExampleRoute || this.props.route},
+              passProps: {depth: this.props.depth ? this.props.depth + 1 : 1},
             });
           })}
           {this._renderRow('Push View Example', () => {
@@ -123,40 +105,39 @@ var NavigatorIOSExample = React.createClass({
           {this._renderRow('Pop to top', () => {
             this.props.navigator.popToTop();
           })}
-          {this._renderRow('Replace here', () => {
-            var prevRoute = this.props.route;
-            this.props.navigator.replace({
-              title: 'New Navigation',
-              component: EmptyPage,
-              rightButtonTitle: 'Undo',
-              onRightButtonPress: () => this.props.navigator.replace(prevRoute),
-              passProps: {
-                text: 'The component is replaced, but there is currently no ' +
-                  'way to change the right button or title of the current route',
-              }
-            });
-          })}
+          {this._renderReplace()}
           {this._renderReplacePrevious()}
           {this._renderReplacePreviousAndPop()}
-          {this._renderPopToTopNavExample()}
+          {this._renderRow('Exit NavigatorIOS Example', this.props.onExampleExit)}
         </View>
         <View style={styles.line}/>
       </ScrollView>
     );
   },
 
-  _renderPopToTopNavExample: function() {
-    if (!this.props.topExampleRoute) {
+  _renderReplace: function() {
+    if (!this.props.depth) {
+      // this is to avoid replacing the top of the stack
       return null;
     }
-    return this._renderRow('Pop to top NavigatorIOSExample', () => {
-      this.props.navigator.popToRoute(this.props.topExampleRoute);
+    return this._renderRow('Replace here', () => {
+      var prevRoute = this.props.route;
+      this.props.navigator.replace({
+        title: 'New Navigation',
+        component: EmptyPage,
+        rightButtonTitle: 'Undo',
+        onRightButtonPress: () => this.props.navigator.replace(prevRoute),
+        passProps: {
+          text: 'The component is replaced, but there is currently no ' +
+            'way to change the right button or title of the current route',
+        }
+      });
     });
   },
 
   _renderReplacePrevious: function() {
-    if (!this.props.topExampleRoute) {
-      // this is to avoid replacing the UIExplorerList at the top of the stack
+    if (!this.props.depth || this.props.depth < 2) {
+      // this is to avoid replacing the top of the stack
       return null;
     }
     return this._renderRow('Replace previous', () => {
@@ -172,8 +153,8 @@ var NavigatorIOSExample = React.createClass({
   },
 
   _renderReplacePreviousAndPop: function() {
-    if (!this.props.topExampleRoute) {
-      // this is to avoid replacing the UIExplorerList at the top of the stack
+    if (!this.props.depth || this.props.depth < 2) {
+      // this is to avoid replacing the top of the stack
       return null;
     }
     return this._renderRow('Replace previous and pop', () => {
@@ -204,7 +185,34 @@ var NavigatorIOSExample = React.createClass({
   },
 });
 
-var styles = StyleSheet.create({
+const NavigatorIOSExample = React.createClass({
+  statics: {
+    title: '<NavigatorIOS>',
+    description: 'iOS navigation capabilities',
+    external: true,
+  },
+
+  render: function() {
+    const {onExampleExit} = this.props;
+    return (
+      <NavigatorIOS
+        style={styles.container}
+        initialRoute={{
+          title: NavigatorIOSExample.title,
+          component: NavigatorIOSExamplePage,
+          passProps: {onExampleExit},
+        }}
+        itemWrapperStyle={styles.itemWrapper}
+        tintColor="#008888"
+      />
+    );
+  },
+});
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+  },
   customWrapperStyle: {
     backgroundColor: '#bbdddd',
   },
@@ -227,7 +235,7 @@ var styles = StyleSheet.create({
   },
   line: {
     backgroundColor: '#bbbbbb',
-    height: 1 / PixelRatio.get(),
+    height: StyleSheet.hairlineWidth,
   },
   row: {
     backgroundColor: 'white',
@@ -236,7 +244,7 @@ var styles = StyleSheet.create({
     paddingVertical: 15,
   },
   separator: {
-    height: 1 / PixelRatio.get(),
+    height: StyleSheet.hairlineWidth,
     backgroundColor: '#bbbbbb',
     marginLeft: 15,
   },
diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js
index 6ab1805dfee4..97af9e1e00be 100644
--- a/Examples/UIExplorer/NetInfoExample.js
+++ b/Examples/UIExplorer/NetInfoExample.js
@@ -15,83 +15,84 @@
  */
 'use strict';
 
-var React = require('react-native');
-var {
+const React = require('react-native');
+const {
   NetInfo,
   Text,
-  View
+  View,
+  TouchableWithoutFeedback,
 } = React;
 
-var ReachabilitySubscription = React.createClass({
+const ConnectionInfoSubscription = React.createClass({
   getInitialState() {
     return {
-      reachabilityHistory: [],
+      connectionInfoHistory: [],
     };
   },
   componentDidMount: function() {
     NetInfo.addEventListener(
-      'change',
-      this._handleReachabilityChange
+        'change',
+        this._handleConnectionInfoChange
     );
   },
   componentWillUnmount: function() {
     NetInfo.removeEventListener(
-      'change',
-      this._handleReachabilityChange
+        'change',
+        this._handleConnectionInfoChange
     );
   },
-  _handleReachabilityChange: function(reachability) {
-    var reachabilityHistory = this.state.reachabilityHistory.slice();
-    reachabilityHistory.push(reachability);
+  _handleConnectionInfoChange: function(connectionInfo) {
+    const connectionInfoHistory = this.state.connectionInfoHistory.slice();
+    connectionInfoHistory.push(connectionInfo);
     this.setState({
-      reachabilityHistory,
+      connectionInfoHistory,
     });
   },
   render() {
     return (
-      <View>
-        <Text>{JSON.stringify(this.state.reachabilityHistory)}</Text>
-      </View>
+        <View>
+          <Text>{JSON.stringify(this.state.connectionInfoHistory)}</Text>
+        </View>
     );
   }
 });
 
-var ReachabilityCurrent = React.createClass({
+const ConnectionInfoCurrent = React.createClass({
   getInitialState() {
     return {
-      reachability: null,
+      connectionInfo: null,
     };
   },
   componentDidMount: function() {
     NetInfo.addEventListener(
-      'change',
-      this._handleReachabilityChange
+        'change',
+        this._handleConnectionInfoChange
     );
     NetInfo.fetch().done(
-      (reachability) => { this.setState({reachability}); }
+        (connectionInfo) => { this.setState({connectionInfo}); }
     );
   },
   componentWillUnmount: function() {
     NetInfo.removeEventListener(
-      'change',
-      this._handleReachabilityChange
+        'change',
+        this._handleConnectionInfoChange
     );
   },
-  _handleReachabilityChange: function(reachability) {
+  _handleConnectionInfoChange: function(connectionInfo) {
     this.setState({
-      reachability,
+      connectionInfo,
     });
   },
   render() {
     return (
-      <View>
-        <Text>{this.state.reachability}</Text>
-      </View>
+        <View>
+          <Text>{this.state.connectionInfo}</Text>
+        </View>
     );
   }
 });
 
-var IsConnected = React.createClass({
+const IsConnected = React.createClass({
   getInitialState() {
     return {
       isConnected: null,
@@ -99,17 +100,17 @@ var IsConnected = React.createClass({
   },
   componentDidMount: function() {
     NetInfo.isConnected.addEventListener(
-      'change',
-      this._handleConnectivityChange
+        'change',
+        this._handleConnectivityChange
     );
     NetInfo.isConnected.fetch().done(
-      (isConnected) => { this.setState({isConnected}); }
+        (isConnected) => { this.setState({isConnected}); }
     );
   },
   componentWillUnmount: function() {
     NetInfo.isConnected.removeEventListener(
-      'change',
-      this._handleConnectivityChange
+        'change',
+        this._handleConnectivityChange
     );
   },
   _handleConnectivityChange: function(isConnected) {
@@ -119,9 +120,37 @@ var IsConnected = React.createClass({
   },
   render() {
     return (
-      <View>
-        <Text>{this.state.isConnected ? 'Online' : 'Offline'}</Text>
-      </View>
+        <View>
+          <Text>{this.state.isConnected ? 'Online' : 'Offline'}</Text>
+        </View>
+    );
+  }
+});
+
+const IsConnectionExpensive = React.createClass({
+  getInitialState() {
+    return {
+      isConnectionExpensive: (null : ?boolean),
+    };
+  },
+  _checkIfExpensive() {
+    NetInfo.isConnectionExpensive().then(
+        isConnectionExpensive => { this.setState({isConnectionExpensive}); }
+    );
+  },
+  render() {
+    return (
+        <View>
+          <TouchableWithoutFeedback onPress={this._checkIfExpensive}>
+            <View>
+              <Text>Click to see if connection is expensive:
+                {this.state.isConnectionExpensive === true ? 'Expensive' :
+                this.state.isConnectionExpensive === false ? 'Not expensive'
+                : 'Unknown'}
+              </Text>
+            </View>
+          </TouchableWithoutFeedback>
+        </View>
     );
   }
 });
@@ -135,13 +164,19 @@ exports.examples = [
     render(): ReactElement { return <IsConnected />; }
   },
   {
-    title: 'NetInfo.reachabilityIOS',
-    description: 'Asynchronously load and observe iOS reachability',
-    render(): ReactElement { return <ReachabilityCurrent />; }
+    title: 'NetInfo.update',
+    description: 'Asynchronously load and observe connectionInfo',
+    render(): ReactElement { return <ConnectionInfoCurrent />; }
+  },
+  {
+    title: 'NetInfo.updateHistory',
+    description: 'Observed updates to connectionInfo',
+    render(): ReactElement { return <ConnectionInfoSubscription />; }
   },
   {
-    title: 'NetInfo.reachabilityIOS',
-    description: 'Observed updates to iOS reachability',
-    render(): ReactElement { return <ReachabilitySubscription />; }
+    platform: 'android',
+    title: 'NetInfo.isConnectionExpensive (Android)',
+    description: 'Asynchronously check isConnectionExpensive',
+    render(): ReactElement { return <IsConnectionExpensive />; }
   },
 ];
diff --git a/Examples/UIExplorer/PanResponderExample.js b/Examples/UIExplorer/PanResponderExample.js
index c2e6a667ce0d..47833af3f5a8 100644
--- a/Examples/UIExplorer/PanResponderExample.js
+++ b/Examples/UIExplorer/PanResponderExample.js
@@ -11,7 +11,7 @@
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  *
- * @flow-weak
+ * @flow weak
  */
 'use strict';
 
@@ -79,7 +79,8 @@ var PanResponderExample = React.createClass({
   },
 
   _highlight: function() {
-    this.circle && this.circle.setNativeProps({
+    const circle = this.circle;
+    circle && circle.setNativeProps({
       style: {
         backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)
       }
@@ -87,7 +88,8 @@ var PanResponderExample = React.createClass({
   },
 
   _unHighlight: function() {
-    this.circle && this.circle.setNativeProps({
+    const circle = this.circle;
+    circle && circle.setNativeProps({
       style: {
         backgroundColor: processColor(CIRCLE_COLOR)
       }
diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js
new file mode 100644
index 000000000000..cdbc649626b4
--- /dev/null
+++ b/Examples/UIExplorer/PickerAndroidExample.js
@@ -0,0 +1,138 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const StyleSheet = require('StyleSheet');
+const UIExplorerBlock = require('UIExplorerBlock');
+const UIExplorerPage = require('UIExplorerPage');
+
+const {
+  Picker,
+  Text,
+  TouchableWithoutFeedback,
+} = React;
+const Item = Picker.Item;
+
+const PickerExample = React.createClass({
+
+  statics: {
+    title: '<Picker>',
+    description: 'Provides multiple options to choose from, using either a dropdown menu or a dialog.',
+  },
+
+  getInitialState: function() {
+    return {
+      selected1: 'key1',
+      selected2: 'key1',
+      selected3: 'key1',
+      color: 'red',
+      mode: Picker.MODE_DIALOG,
+    };
+  },
+
+  render: function() {
+    return (
+      <UIExplorerPage title="<Picker>">
+        <UIExplorerBlock title="Basic Picker">
+          <Picker
+            style={styles.picker}
+            selectedValue={this.state.selected1}
+            onValueChange={this.onValueChange.bind(this, 'selected1')}>
+            <Item label="hello" value="key0" />
+            <Item label="world" value="key1" />
+          </Picker>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Disabled picker">
+          <Picker style={styles.picker} enabled={false} selectedValue={this.state.selected1}>
+            <Item label="hello" value="key0" />
+            <Item label="world" value="key1" />
+          </Picker>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Dropdown Picker">
+          <Picker
+            style={styles.picker}
+            selectedValue={this.state.selected2}
+            onValueChange={this.onValueChange.bind(this, 'selected2')}
+            mode="dropdown">
+            <Item label="hello" value="key0" />
+            <Item label="world" value="key1" />
+          </Picker>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Picker with prompt message">
+          <Picker
+            style={styles.picker}
+            selectedValue={this.state.selected3}
+            onValueChange={this.onValueChange.bind(this, 'selected3')}
+            prompt="Pick one, just one">
+            <Item label="hello" value="key0" />
+            <Item label="world" value="key1" />
+          </Picker>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Picker with no listener">
+          <Picker style={styles.picker}>
+            <Item label="hello" value="key0" />
+            <Item label="world" value="key1" />
+          </Picker>
+          <Text>
+            Cannot change the value of this picker because it doesn't update selectedValue.
+          </Text>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Colorful pickers">
+          <Picker
+            style={[styles.picker, {color: 'white', backgroundColor: '#333'}]}
+            selectedValue={this.state.color}
+            onValueChange={this.onValueChange.bind(this, 'color')}
+            mode="dropdown">
+            <Item label="red" color="red" value="red" />
+            <Item label="green" color="green" value="green" />
+            <Item label="blue" color="blue" value="blue" />
+          </Picker>
+          <Picker
+            style={styles.picker}
+            selectedValue={this.state.color}
+            onValueChange={this.onValueChange.bind(this, 'color')}
+            mode="dialog">
+            <Item label="red" color="red" value="red" />
+            <Item label="green" color="green" value="green" />
+            <Item label="blue" color="blue" value="blue" />
+          </Picker>
+        </UIExplorerBlock>
+      </UIExplorerPage>
+    );
+  },
+
+  changeMode: function() {
+    const newMode = this.state.mode === Picker.MODE_DIALOG
+        ? Picker.MODE_DROPDOWN
+        : Picker.MODE_DIALOG;
+    this.setState({mode: newMode});
+  },
+
+  onValueChange: function(key: string, value: string) {
+    const newState = {};
+    newState[key] = value;
+    this.setState(newState);
+  },
+});
+
+var styles = StyleSheet.create({
+  picker: {
+    width: 100,
+  },
+});
+
+module.exports = PickerExample;
diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js
index 31c81ccccdad..51b8f9f30e76 100644
--- a/Examples/UIExplorer/PickerIOSExample.js
+++ b/Examples/UIExplorer/PickerIOSExample.js
@@ -87,24 +87,21 @@ var PickerExample = React.createClass({
               key={carMake}
               value={carMake}
               label={CAR_MAKES_AND_MODELS[carMake].name}
-              />
-            )
-          )}
+            />
+          ))}
         </PickerIOS>
         <Text>Please choose a model of {make.name}:</Text>
         <PickerIOS
           selectedValue={this.state.modelIndex}
           key={this.state.carMake}
           onValueChange={(modelIndex) => this.setState({modelIndex})}>
-          {CAR_MAKES_AND_MODELS[this.state.carMake].models.map(
-            (modelName, modelIndex) => (
-              <PickerItemIOS
-                key={this.state.carmake + '_' + modelIndex}
-                value={modelIndex}
-                label={modelName}
-              />
-            ))
-          }
+          {CAR_MAKES_AND_MODELS[this.state.carMake].models.map((modelName, modelIndex) => (
+            <PickerItemIOS
+              key={this.state.carMake + '_' + modelIndex}
+              value={modelIndex}
+              label={modelName}
+            />
+          ))}
         </PickerIOS>
         <Text>You selected: {selectionString}</Text>
       </View>
@@ -112,6 +109,34 @@ var PickerExample = React.createClass({
   },
 });
 
+var PickerStyleExample = React.createClass({
+  getInitialState: function() {
+    return {
+      carMake: 'cadillac',
+      modelIndex: 0,
+    };
+  },
+
+  render: function() {
+    var make = CAR_MAKES_AND_MODELS[this.state.carMake];
+    var selectionString = make.name + ' ' + make.models[this.state.modelIndex];
+    return (
+      <PickerIOS
+        itemStyle={{fontSize: 25, color: 'red', textAlign: 'left', fontWeight: 'bold'}}
+        selectedValue={this.state.carMake}
+        onValueChange={(carMake) => this.setState({carMake, modelIndex: 0})}>
+        {Object.keys(CAR_MAKES_AND_MODELS).map((carMake) => (
+          <PickerItemIOS
+            key={carMake}
+            value={carMake}
+            label={CAR_MAKES_AND_MODELS[carMake].name}
+          />
+        ))}
+      </PickerIOS>
+    );
+  },
+});
+
 exports.displayName = (undefined: ?string);
 exports.title = '<PickerIOS>';
 exports.description = 'Render lists of selectable options with UIPickerView.';
@@ -121,4 +146,10 @@ exports.examples = [
   render: function(): ReactElement {
     return <PickerExample />;
   },
+},
+{
+  title: '<PickerIOS> with custom styling',
+  render: function(): ReactElement {
+    return <PickerStyleExample />;
+  },
 }];
diff --git a/Examples/UIExplorer/ProgressBarAndroidExample.android.js b/Examples/UIExplorer/ProgressBarAndroidExample.android.js
index b58cd73ca586..19b182bc7692 100644
--- a/Examples/UIExplorer/ProgressBarAndroidExample.android.js
+++ b/Examples/UIExplorer/ProgressBarAndroidExample.android.js
@@ -60,6 +60,10 @@ var ProgressBarAndroidExample = React.createClass({
           <ProgressBar />
         </UIExplorerBlock>
 
+        <UIExplorerBlock title="Normal ProgressBar">
+          <ProgressBar styleAttr="Normal" />
+        </UIExplorerBlock>
+
         <UIExplorerBlock title="Small ProgressBar">
           <ProgressBar styleAttr="Small" />
         </UIExplorerBlock>
diff --git a/Examples/UIExplorer/PushNotificationIOSExample.js b/Examples/UIExplorer/PushNotificationIOSExample.js
index bd6109f1f09b..9fcf8cd1c343 100644
--- a/Examples/UIExplorer/PushNotificationIOSExample.js
+++ b/Examples/UIExplorer/PushNotificationIOSExample.js
@@ -84,6 +84,8 @@ class NotificationExample extends React.Component {
 }
 
 class NotificationPermissionExample extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {permissions: null};
@@ -126,7 +128,7 @@ exports.description = 'Apple PushNotification and badge value';
 exports.examples = [
 {
   title: 'Badge Number',
-  render(): React.Component {
+  render(): ReactElement {
     PushNotificationIOS.requestPermissions();
 
     return (
@@ -145,13 +147,13 @@ exports.examples = [
 },
 {
   title: 'Push Notifications',
-  render(): React.Component {
+  render(): ReactElement {
     return <NotificationExample />;
   }
 },
 {
   title: 'Notifications Permissions',
-  render(): React.Component {
+  render(): ReactElement {
     return <NotificationPermissionExample />;
   }
 }];
diff --git a/Examples/UIExplorer/RCTRootViewIOSExample.js b/Examples/UIExplorer/RCTRootViewIOSExample.js
index 174dcf2bb947..5aa56cf881e4 100644
--- a/Examples/UIExplorer/RCTRootViewIOSExample.js
+++ b/Examples/UIExplorer/RCTRootViewIOSExample.js
@@ -15,19 +15,20 @@
  */
 
 'use strict';
-var React = require('react-native');
-var {
+
+const React = require('react-native');
+const {
   StyleSheet,
   Text,
   View,
 } = React;
 
-var requireNativeComponent = require('requireNativeComponent');
-var UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView');
-var FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView');
+const requireNativeComponent = require('requireNativeComponent');
 
 class AppPropertiesUpdateExample extends React.Component {
   render() {
+    // Do not require this unless we are actually rendering.
+    const UpdatePropertiesExampleView = requireNativeComponent('UpdatePropertiesExampleView');
     return (
       <View style={styles.container}>
         <Text style={styles.text}>
@@ -45,6 +46,8 @@ class AppPropertiesUpdateExample extends React.Component {
 
 class RootViewSizeFlexibilityExample extends React.Component {
   render() {
+    // Do not require this unless we are actually rendering.
+    const FlexibleSizeExampleView = requireNativeComponent('FlexibleSizeExampleView');
     return (
       <View style={styles.container}>
         <Text style={styles.text}>
@@ -60,7 +63,7 @@ class RootViewSizeFlexibilityExample extends React.Component {
   }
 }
 
-var styles = StyleSheet.create({
+const styles = StyleSheet.create({
   container: {
     flex: 1,
     backgroundColor: '#F5FCFF',
@@ -79,7 +82,7 @@ exports.description = 'Examples that show useful methods when embedding React Na
 exports.examples = [
 {
   title: 'Updating app properties in runtime',
-  render(): React.Component {
+  render(): ReactElement {
     return (
       <AppPropertiesUpdateExample/>
     );
@@ -87,7 +90,7 @@ exports.examples = [
 },
 {
   title: 'RCTRootView\'s size flexibility',
-  render(): React.Component {
+  render(): ReactElement {
     return (
       <RootViewSizeFlexibilityExample/>
     );
diff --git a/Examples/UIExplorer/RefreshControlExample.js b/Examples/UIExplorer/RefreshControlExample.js
new file mode 100644
index 000000000000..f56c9fed5188
--- /dev/null
+++ b/Examples/UIExplorer/RefreshControlExample.js
@@ -0,0 +1,125 @@
+/**
+* The examples provided by Facebook are for non-commercial testing and
+* evaluation purposes only.
+*
+* Facebook reserves all rights not expressly granted.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*
+*/
+'use strict';
+
+const React = require('react-native');
+const {
+  ScrollView,
+  StyleSheet,
+  RefreshControl,
+  Text,
+  TouchableWithoutFeedback,
+  View,
+} = React;
+
+const styles = StyleSheet.create({
+  row: {
+    borderColor: 'grey',
+    borderWidth: 1,
+    padding: 20,
+    backgroundColor: '#3a5795',
+    margin: 5,
+  },
+  text: {
+    alignSelf: 'center',
+    color: '#fff',
+  },
+  scrollview: {
+    flex: 1,
+  },
+});
+
+const Row = React.createClass({
+  _onClick: function() {
+    this.props.onClick(this.props.data);
+  },
+  render: function() {
+    return (
+     <TouchableWithoutFeedback onPress={this._onClick} >
+        <View style={styles.row}>
+          <Text style={styles.text}>
+            {this.props.data.text + ' (' + this.props.data.clicks + ' clicks)'}
+          </Text>
+        </View>
+      </TouchableWithoutFeedback>
+    );
+  },
+});
+
+const RefreshControlExample = React.createClass({
+  statics: {
+    title: '<RefreshControl>',
+    description: 'Adds pull-to-refresh support to a scrollview.'
+  },
+
+  getInitialState() {
+    return {
+      isRefreshing: false,
+      loaded: 0,
+      rowData: Array.from(new Array(20)).map(
+        (val, i) => ({text: 'Initial row ' + i, clicks: 0})),
+    };
+  },
+
+  _onClick(row) {
+    row.clicks++;
+    this.setState({
+      rowData: this.state.rowData,
+    });
+  },
+
+  render() {
+    const rows = this.state.rowData.map((row, ii) => {
+      return <Row key={ii} data={row} onClick={this._onClick}/>;
+    });
+    return (
+      <ScrollView
+        style={styles.scrollview}
+        refreshControl={
+          <RefreshControl
+            refreshing={this.state.isRefreshing}
+            onRefresh={this._onRefresh}
+            tintColor="#ff0000"
+            title="Loading..."
+            colors={['#ff0000', '#00ff00', '#0000ff']}
+            progressBackgroundColor="#ffff00"
+          />
+        }>
+        {rows}
+      </ScrollView>
+    );
+  },
+
+  _onRefresh() {
+    this.setState({isRefreshing: true});
+    setTimeout(() => {
+      // prepend 10 items
+      const rowData = Array.from(new Array(10))
+      .map((val, i) => ({
+        text: 'Loaded row ' + (+this.state.loaded + i),
+        clicks: 0,
+      }))
+      .concat(this.state.rowData);
+
+      this.setState({
+        loaded: this.state.loaded + 10,
+        isRefreshing: false,
+        rowData: rowData,
+      });
+    }, 5000);
+  },
+});
+
+module.exports = RefreshControlExample;
diff --git a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js
new file mode 100644
index 000000000000..9c48357033fb
--- /dev/null
+++ b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js
@@ -0,0 +1,77 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const {
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+  View,
+} = React;
+
+class RootViewSizeFlexibilityExampleApp extends React.Component {
+  state: any;
+
+  constructor(props: {toggled: boolean}) {
+    super(props);
+    this.state = { toggled: false };
+  }
+
+  _onPressButton() {
+    this.setState({ toggled: !this.state.toggled });
+  }
+
+  render() {
+    const viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer;
+
+    return (
+      <TouchableHighlight onPress={this._onPressButton.bind(this)}>
+        <View style={viewStyle}>
+          <View style={styles.center}>
+            <Text style={styles.whiteText}>
+              React Native Button
+            </Text>
+          </View>
+        </View>
+      </TouchableHighlight>
+    );
+  }
+
+}
+
+const styles = StyleSheet.create({
+  bigContainer: {
+    flex: 1,
+    height: 60,
+    backgroundColor: 'gray',
+  },
+  smallContainer: {
+    flex: 1,
+    height: 40,
+    backgroundColor: 'gray',
+  },
+  center: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
+  whiteText: {
+    color: 'white',
+  }
+});
+
+module.exports = RootViewSizeFlexibilityExampleApp;
diff --git a/Examples/UIExplorer/ScrollViewExample.js b/Examples/UIExplorer/ScrollViewExample.js
index 53d19148b67f..469c9a63c113 100644
--- a/Examples/UIExplorer/ScrollViewExample.js
+++ b/Examples/UIExplorer/ScrollViewExample.js
@@ -19,6 +19,8 @@ var React = require('react-native');
 var {
   ScrollView,
   StyleSheet,
+  Text,
+  TouchableOpacity,
   View,
   Image
 } = React;
@@ -31,27 +33,45 @@ exports.examples = [
   title: '<ScrollView>',
   description: 'To make content scrollable, wrap it within a <ScrollView> component',
   render: function() {
+    var _scrollView: ScrollView;
     return (
-      <ScrollView
-        automaticallyAdjustContentInsets={false}
-        onScroll={() => { console.log('onScroll!'); }}
-        scrollEventThrottle={200}
-        style={styles.scrollView}>
-        {THUMBS.map(createThumbRow)}
-      </ScrollView>
+      <View>
+        <ScrollView
+          ref={(scrollView) => { _scrollView = scrollView; }}
+          automaticallyAdjustContentInsets={false}
+          onScroll={() => { console.log('onScroll!'); }}
+          scrollEventThrottle={200}
+          style={styles.scrollView}>
+          {THUMBS.map(createThumbRow)}
+        </ScrollView>
+        <TouchableOpacity
+          style={styles.button}
+          onPress={() => { _scrollView.scrollTo({y: 0}); }}>
+          <Text>Scroll to top</Text>
+        </TouchableOpacity>
+      </View>
     );
   }
 }, {
   title: '<ScrollView> (horizontal = true)',
   description: 'You can display <ScrollView>\'s child components horizontally rather than vertically',
   render: function() {
+    var _scrollView: ScrollView;
     return (
-      <ScrollView
-        automaticallyAdjustContentInsets={false}
-        horizontal={true}
-        style={[styles.scrollView, styles.horizontalScrollView]}>
-        {THUMBS.map(createThumbRow)}
-      </ScrollView>
+      <View>
+        <ScrollView
+          ref={(scrollView) => { _scrollView = scrollView; }}
+          automaticallyAdjustContentInsets={false}
+          horizontal={true}
+          style={[styles.scrollView, styles.horizontalScrollView]}>
+          {THUMBS.map(createThumbRow)}
+        </ScrollView>
+        <TouchableOpacity
+          style={styles.button}
+          onPress={() => { _scrollView.scrollTo({x: 0}); }}>
+          <Text>Scroll to start</Text>
+        </TouchableOpacity>
+      </View>
     );
   }
 }];
diff --git a/Examples/UIExplorer/SetPropertiesExampleApp.js b/Examples/UIExplorer/SetPropertiesExampleApp.js
index 748bbc3ccb4a..f5dc9c8eab39 100644
--- a/Examples/UIExplorer/SetPropertiesExampleApp.js
+++ b/Examples/UIExplorer/SetPropertiesExampleApp.js
@@ -13,17 +13,18 @@
  *
  * @flow
  */
-
 'use strict';
 
-var React = require('React');
-var Text = require('Text');
-var View = require('View');
+const React = require('react-native');
+const {
+  Text,
+  View,
+} = React;
 
-var SetPropertiesExampleApp = React.createClass({
+class SetPropertiesExampleApp extends React.Component {
 
-  render: function() {
-    var wrapperStyle = {
+  render() {
+    const wrapperStyle = {
       backgroundColor: this.props.color,
       flex: 1,
       alignItems: 'center',
@@ -37,7 +38,8 @@ var SetPropertiesExampleApp = React.createClass({
         </Text>
       </View>
     );
-  },
-});
+  }
+
+}
 
 module.exports = SetPropertiesExampleApp;
diff --git a/Examples/UIExplorer/SliderIOSExample.js b/Examples/UIExplorer/SliderIOSExample.js
index 245fe10ad57e..baee69ae8f02 100644
--- a/Examples/UIExplorer/SliderIOSExample.js
+++ b/Examples/UIExplorer/SliderIOSExample.js
@@ -83,5 +83,39 @@ exports.examples = [
     render(): ReactElement {
       return <SliderExample step={0.25} />;
     }
-  }
+  },
+  {
+    title: 'Custom min/max track tint color',
+    render(): ReactElement {
+      return (
+        <SliderExample
+          minimumTrackTintColor={'red'}
+          maximumTrackTintColor={'green'}
+        />
+      );
+    }
+  },
+  {
+    title: 'Custom thumb image',
+    render(): ReactElement {
+      return <SliderExample thumbImage={require('./uie_thumb_big.png')} />;
+    }
+  },
+  {
+    title: 'Custom track image',
+    render(): ReactElement {
+      return <SliderExample trackImage={require('./slider.png')} />;
+    }
+  },
+  {
+    title: 'Custom min/max track image',
+    render(): ReactElement {
+      return (
+        <SliderExample
+          minimumTrackImage={require('./slider-left.png')}
+          maximumTrackImage={require('./slider-right.png')}
+        />
+      );
+    }
+  },
 ];
diff --git a/Examples/UIExplorer/SnapshotExample.js b/Examples/UIExplorer/SnapshotExample.js
new file mode 100644
index 000000000000..d843acee80d3
--- /dev/null
+++ b/Examples/UIExplorer/SnapshotExample.js
@@ -0,0 +1,73 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  Image,
+  StyleSheet,
+  Text,
+  UIManager,
+  View,
+} = React;
+
+var ScreenshotExample = React.createClass({
+  getInitialState() {
+    return {
+      uri: undefined,
+    };
+  },
+
+  render() {
+    return (
+      <View>
+        <Text onPress={this.takeScreenshot} style={style.button}>
+          Click to take a screenshot
+        </Text>
+        <Image style={style.image} source={{uri: this.state.uri}}/>
+      </View>
+    );
+  },
+
+  takeScreenshot() {
+    UIManager
+      .takeSnapshot('window', {format: 'jpeg', quality: 0.8}) // See UIManager.js for options
+      .then((uri) => this.setState({uri}))
+      .catch((error) => alert(error));
+  }
+});
+
+var style = StyleSheet.create({
+  button: {
+    marginBottom: 10,
+    fontWeight: '500',
+  },
+  image: {
+    flex: 1,
+    height: 300,
+    resizeMode: 'contain',
+    backgroundColor: 'black',
+  },
+});
+
+exports.title = 'Snapshot / Screenshot';
+exports.description = 'API to capture images from the screen.';
+exports.examples = [
+  {
+    title: 'Take screenshot',
+    render(): ReactElement { return <ScreenshotExample />; }
+  },
+];
diff --git a/Examples/UIExplorer/StatusBarExample.js b/Examples/UIExplorer/StatusBarExample.js
new file mode 100644
index 000000000000..e33dbd71a98b
--- /dev/null
+++ b/Examples/UIExplorer/StatusBarExample.js
@@ -0,0 +1,311 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const {
+  StyleSheet,
+  View,
+  Text,
+  TouchableHighlight,
+  StatusBar,
+} = React;
+
+type BarStyle = 'default' | 'light-content';
+type ShowHideTransition = 'fade' | 'slide';
+
+type State = {
+  animated: boolean,
+  backgroundColor: string,
+  hidden?: boolean,
+  showHideTransition: ShowHideTransition,
+  translucent?: boolean,
+  barStyle?: BarStyle,
+  networkActivityIndicatorVisible?: boolean
+};
+
+exports.framework = 'React';
+exports.title = '<StatusBar>';
+exports.description = 'Component for controlling the status bar';
+
+const colors = [
+  '#ff0000',
+  '#00ff00',
+  '#0000ff',
+];
+
+const barStyles = [
+  'default',
+  'light-content',
+];
+
+const showHideTransitions = [
+  'fade',
+  'slide',
+];
+
+function getValue(values: Array<any>, index: number): any {
+  return values[index % values.length];
+}
+
+const StatusBarExample = React.createClass({
+  getInitialState(): State {
+    return {
+      animated: true,
+      backgroundColor: getValue(colors, 0),
+      showHideTransition: getValue(showHideTransitions, 0),
+    };
+  },
+
+  _colorIndex: 0,
+  _barStyleIndex: 0,
+  _showHideTransitionIndex: 0,
+
+  render() {
+    return (
+      <View>
+        <StatusBar
+          backgroundColor={this.state.backgroundColor}
+          translucent={this.state.translucent}
+          hidden={this.state.hidden}
+          showHideTransition={this.state.showHideTransition}
+          animated={this.state.animated}
+          barStyle={this.state.barStyle}
+          networkActivityIndicatorVisible={this.state.networkActivityIndicatorVisible}
+        />
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => this.setState({animated: !this.state.animated})}>
+            <View style={styles.button}>
+              <Text>animated: {this.state.animated ? 'true' : 'false'}</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => this.setState({hidden: !this.state.hidden})}>
+            <View style={styles.button}>
+              <Text>hidden: {this.state.hidden ? 'true' : 'false'}</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <Text style={styles.title}>iOS</Text>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this._barStyleIndex++;
+              this.setState({barStyle: getValue(barStyles, this._barStyleIndex)});
+            }}>
+            <View style={styles.button}>
+              <Text>style: '{getValue(barStyles, this._barStyleIndex)}'</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => this.setState({
+              networkActivityIndicatorVisible: !this.state.networkActivityIndicatorVisible,
+            })}>
+            <View style={styles.button}>
+              <Text>
+                networkActivityIndicatorVisible:
+                {this.state.networkActivityIndicatorVisible ? 'true' : 'false'}
+              </Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this._showHideTransitionIndex++;
+              this.setState({
+                showHideTransition:
+                getValue(showHideTransitions, this._showHideTransitionIndex),
+              });
+            }}>
+            <View style={styles.button}>
+              <Text>
+                showHideTransition:
+                '{getValue(showHideTransitions, this._showHideTransitionIndex)}'
+              </Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <Text style={styles.title}>Android</Text>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this._colorIndex++;
+              this.setState({backgroundColor: getValue(colors, this._colorIndex)});
+            }}>
+            <View style={styles.button}>
+              <Text>backgroundColor: '{getValue(colors, this._colorIndex)}'</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this.setState({
+                translucent: !this.state.translucent,
+                backgroundColor: !this.state.translucent ? 'rgba(0, 0, 0, 0.4)' : 'black',
+              });
+            }}>
+            <View style={styles.button}>
+              <Text>translucent: {this.state.translucent ? 'true' : 'false'}</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+      </View>
+    );
+  },
+});
+
+const StatusBarStaticExample = React.createClass({
+  _colorIndex: 0,
+  _barStyleIndex: 0,
+  _showHideTransitionIndex: 0,
+
+  getInitialState() {
+    return {
+      backgroundColor: getValue(colors, 0),
+      barStyle: getValue(barStyles, 0),
+      hidden: false,
+      networkActivityIndicatorVisible: false,
+      translucent: false,
+    };
+  },
+
+  render() {
+    return (
+      <View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              const hidden = !this.state.hidden;
+              StatusBar.setHidden(hidden, 'slide');
+              this.setState({hidden});
+            }}>
+            <View style={styles.button}>
+              <Text>hidden: {this.state.hidden ? 'true' : 'false'}</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <Text style={styles.title}>iOS</Text>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this._barStyleIndex++;
+              const barStyle = getValue(barStyles, this._barStyleIndex);
+              StatusBar.setBarStyle(barStyle, true);
+              this.setState({barStyle});
+            }}>
+            <View style={styles.button}>
+              <Text>style: '{getValue(barStyles, this._barStyleIndex)}'</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              const networkActivityIndicatorVisible = !this.state.networkActivityIndicatorVisible;
+              StatusBar.setNetworkActivityIndicatorVisible(networkActivityIndicatorVisible);
+              this.setState({networkActivityIndicatorVisible});
+            }}>
+            <View style={styles.button}>
+              <Text>
+                networkActivityIndicatorVisible:
+                {this.state.networkActivityIndicatorVisible ? 'true' : 'false'}
+              </Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <Text style={styles.title}>Android</Text>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              this._colorIndex++;
+              const backgroundColor = getValue(colors, this._colorIndex);
+              StatusBar.setBackgroundColor(backgroundColor, true);
+              this.setState({backgroundColor});
+            }}>
+            <View style={styles.button}>
+              <Text>backgroundColor: '{getValue(colors, this._colorIndex)}'</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+        <View>
+          <TouchableHighlight
+            style={styles.wrapper}
+            onPress={() => {
+              const translucent = !this.state.translucent;
+              const backgroundColor = !this.state.translucent ? 'rgba(0, 0, 0, 0.4)' : 'black';
+              StatusBar.setTranslucent(translucent);
+              StatusBar.setBackgroundColor(backgroundColor, true);
+              this.setState({
+                translucent,
+                backgroundColor,
+              });
+            }}>
+            <View style={styles.button}>
+              <Text>translucent: {this.state.translucent ? 'true' : 'false'}</Text>
+            </View>
+          </TouchableHighlight>
+        </View>
+      </View>
+    );
+  },
+});
+
+exports.examples = [{
+  title: 'StatusBar',
+  render() {
+    return <StatusBarExample />;
+  },
+}, {
+  title: 'StatusBar static API',
+  render() {
+    return <StatusBarStaticExample />;
+  },
+}];
+
+var styles = StyleSheet.create({
+  wrapper: {
+    borderRadius: 5,
+    marginBottom: 5,
+  },
+  button: {
+    borderRadius: 5,
+    backgroundColor: '#eeeeee',
+    padding: 10,
+  },
+  title: {
+    marginTop: 16,
+    marginBottom: 8,
+    fontWeight: 'bold',
+  }
+});
diff --git a/Examples/UIExplorer/StatusBarIOSExample.js b/Examples/UIExplorer/StatusBarIOSExample.js
index 28d5e2a7c076..a7765d8563ac 100644
--- a/Examples/UIExplorer/StatusBarIOSExample.js
+++ b/Examples/UIExplorer/StatusBarIOSExample.js
@@ -33,7 +33,7 @@ exports.examples = [{
     return (
       <View>
         {['default', 'light-content'].map((style) =>
-          <TouchableHighlight style={styles.wrapper}
+          <TouchableHighlight key={style} style={styles.wrapper}
             onPress={() => StatusBarIOS.setStyle(style)}>
             <View style={styles.button}>
               <Text>setStyle('{style}')</Text>
@@ -49,7 +49,7 @@ exports.examples = [{
     return (
       <View>
         {['default', 'light-content'].map((style) =>
-          <TouchableHighlight style={styles.wrapper}
+          <TouchableHighlight key={style} style={styles.wrapper}
             onPress={() => StatusBarIOS.setStyle(style, true)}>
             <View style={styles.button}>
               <Text>setStyle('{style}', true)</Text>
@@ -65,7 +65,7 @@ exports.examples = [{
     return (
       <View>
         {['none', 'fade', 'slide'].map((animation) =>
-          <View>
+          <View key={animation}>
             <TouchableHighlight style={styles.wrapper}
               onPress={() => StatusBarIOS.setHidden(true, animation)}>
               <View style={styles.button}>
diff --git a/Examples/UIExplorer/SwitchAndroidExample.android.js b/Examples/UIExplorer/SwitchAndroidExample.android.js
deleted file mode 100644
index 7b6f38e1602c..000000000000
--- a/Examples/UIExplorer/SwitchAndroidExample.android.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- */
-'use strict';
-
-var React = require('React');
-
-var SwitchAndroid = require('SwitchAndroid');
-var Text = require('Text');
-var UIExplorerBlock = require('UIExplorerBlock');
-var UIExplorerPage = require('UIExplorerPage');
-
-var SwitchAndroidExample = React.createClass({
-  statics: {
-    title: '<SwitchAndroid>',
-    description: 'Standard Android two-state toggle component.'
-  },
-
-  getInitialState : function() {
-    return {
-      trueSwitchIsOn: true,
-      falseSwitchIsOn: false,
-      colorTrueSwitchIsOn: true,
-      colorFalseSwitchIsOn: false,
-      eventSwitchIsOn: false,
-    };
-  },
-
-  render: function() {
-    return (
-      <UIExplorerPage title="<SwitchAndroid>">
-        <UIExplorerBlock title="Switches can be set to true or false">
-          <SwitchAndroid
-            onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
-            style={{marginBottom: 10}}
-            value={this.state.falseSwitchIsOn} />
-          <SwitchAndroid
-            onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
-            value={this.state.trueSwitchIsOn} />
-        </UIExplorerBlock>
-        <UIExplorerBlock title="Switches can be disabled">
-          <SwitchAndroid
-            disabled={true}
-            style={{marginBottom: 10}}
-            value={true} />
-          <SwitchAndroid
-            disabled={true}
-            value={false} />
-        </UIExplorerBlock>
-        <UIExplorerBlock title="Change events can be detected">
-          <SwitchAndroid
-            onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
-            style={{marginBottom: 10}}
-            value={this.state.eventSwitchIsOn} />
-          <SwitchAndroid
-            onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
-            style={{marginBottom: 10}}
-            value={this.state.eventSwitchIsOn} />
-          <Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
-        </UIExplorerBlock>
-        <UIExplorerBlock title="Switches are controlled components">
-          <SwitchAndroid />
-          <SwitchAndroid value={true} />
-        </UIExplorerBlock>
-      </UIExplorerPage>
-    );
-  }
-});
-
-module.exports = SwitchAndroidExample;
diff --git a/Examples/UIExplorer/SwitchIOSExample.js b/Examples/UIExplorer/SwitchExample.js
similarity index 91%
rename from Examples/UIExplorer/SwitchIOSExample.js
rename to Examples/UIExplorer/SwitchExample.js
index feedfbff3ad2..5ab41fe15a2f 100644
--- a/Examples/UIExplorer/SwitchIOSExample.js
+++ b/Examples/UIExplorer/SwitchExample.js
@@ -17,7 +17,7 @@
 
 var React = require('react-native');
 var {
-  SwitchIOS,
+  Switch,
   Text,
   View
 } = React;
@@ -32,11 +32,11 @@ var BasicSwitchExample = React.createClass({
   render() {
     return (
       <View>
-        <SwitchIOS
+        <Switch
           onValueChange={(value) => this.setState({falseSwitchIsOn: value})}
           style={{marginBottom: 10}}
           value={this.state.falseSwitchIsOn} />
-        <SwitchIOS
+        <Switch
           onValueChange={(value) => this.setState({trueSwitchIsOn: value})}
           value={this.state.trueSwitchIsOn} />
       </View>
@@ -48,11 +48,11 @@ var DisabledSwitchExample = React.createClass({
   render() {
     return (
       <View>
-        <SwitchIOS
+        <Switch
           disabled={true}
           style={{marginBottom: 10}}
           value={true} />
-        <SwitchIOS
+        <Switch
           disabled={true}
           value={false} />
       </View>
@@ -70,14 +70,14 @@ var ColorSwitchExample = React.createClass({
   render() {
     return (
       <View>
-        <SwitchIOS
+        <Switch
           onValueChange={(value) => this.setState({colorFalseSwitchIsOn: value})}
           onTintColor="#00ff00"
           style={{marginBottom: 10}}
           thumbTintColor="#0000ff"
           tintColor="#ff0000"
           value={this.state.colorFalseSwitchIsOn} />
-        <SwitchIOS
+        <Switch
           onValueChange={(value) => this.setState({colorTrueSwitchIsOn: value})}
           onTintColor="#00ff00"
           thumbTintColor="#0000ff"
@@ -99,22 +99,22 @@ var EventSwitchExample = React.createClass({
     return (
       <View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
         <View>
-          <SwitchIOS
+          <Switch
             onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
             style={{marginBottom: 10}}
             value={this.state.eventSwitchIsOn} />
-          <SwitchIOS
+          <Switch
             onValueChange={(value) => this.setState({eventSwitchIsOn: value})}
             style={{marginBottom: 10}}
             value={this.state.eventSwitchIsOn} />
             <Text>{this.state.eventSwitchIsOn ? 'On' : 'Off'}</Text>
         </View>
         <View>
-          <SwitchIOS
+          <Switch
             onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
             style={{marginBottom: 10}}
             value={this.state.eventSwitchRegressionIsOn} />
-          <SwitchIOS
+          <Switch
             onValueChange={(value) => this.setState({eventSwitchRegressionIsOn: value})}
             style={{marginBottom: 10}}
             value={this.state.eventSwitchRegressionIsOn} />
@@ -125,10 +125,7 @@ var EventSwitchExample = React.createClass({
   }
 });
 
-exports.title = '<SwitchIOS>';
-exports.displayName = 'SwitchExample';
-exports.description = 'Native boolean input';
-exports.examples = [
+var examples = [
   {
     title: 'Switches can be set to true or false',
     render(): ReactElement { return <BasicSwitchExample />; }
@@ -137,16 +134,24 @@ exports.examples = [
     title: 'Switches can be disabled',
     render(): ReactElement { return <DisabledSwitchExample />; }
   },
-  {
-    title: 'Custom colors can be provided',
-    render(): ReactElement { return <ColorSwitchExample />; }
-  },
   {
     title: 'Change events can be detected',
     render(): ReactElement { return <EventSwitchExample />; }
   },
   {
     title: 'Switches are controlled components',
-    render(): ReactElement { return <SwitchIOS />; }
+    render(): ReactElement { return <Switch />; }
   }
 ];
+
+if (React.Platform.OS === 'ios') {
+  examples.push({
+    title: 'Custom colors can be provided',
+    render(): ReactElement { return <ColorSwitchExample />; }
+  });
+}
+
+exports.title = '<Switch>';
+exports.displayName = 'SwitchExample';
+exports.description = 'Native boolean input';
+exports.examples = examples;
diff --git a/Examples/UIExplorer/TextExample.android.js b/Examples/UIExplorer/TextExample.android.js
index 18defae27027..7c0c7868f4c4 100644
--- a/Examples/UIExplorer/TextExample.android.js
+++ b/Examples/UIExplorer/TextExample.android.js
@@ -365,6 +365,11 @@ var TextExample = React.createClass({
             This text contains an inline image <Image source={require('./flux.png')}/>. Neat, huh?
           </Text>
         </UIExplorerBlock>
+        <UIExplorerBlock title="Text shadow">
+          <Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}>
+            Demo text shadow
+          </Text>
+        </UIExplorerBlock>
       </UIExplorerPage>
     );
   }
diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js
index 57a2f032cdbf..3de37c42349d 100644
--- a/Examples/UIExplorer/TextExample.ios.js
+++ b/Examples/UIExplorer/TextExample.ios.js
@@ -419,7 +419,18 @@ exports.examples = [
     return (
       <View>
         <Text>
-          This text contains an inline image <Image source={require('./flux.png')}/>. Neat, huh?
+          This text contains an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh?
+        </Text>
+      </View>
+    );
+  },
+}, {
+  title: 'Text shadow',
+  render: function() {
+    return (
+      <View>
+        <Text style={{fontSize: 20, textShadowOffset: {width: 2, height: 2}, textShadowRadius: 1, textShadowColor: '#00cccc'}}>
+          Demo text shadow
         </Text>
       </View>
     );
diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js
index 31156b50d5f8..1a436a91236d 100644
--- a/Examples/UIExplorer/TextInputExample.android.js
+++ b/Examples/UIExplorer/TextInputExample.android.js
@@ -72,6 +72,29 @@ var TextEventsExample = React.createClass({
   }
 });
 
+class AutoExpandingTextInput extends React.Component {
+  constructor(props) {
+    super(props);
+    this.state = {text: '', height: 0};
+  }
+  render() {
+    return (
+      <TextInput
+        {...this.props}
+        multiline={true}
+        onChange={(event) => {
+          this.setState({
+            text: event.nativeEvent.text,
+            height: event.nativeEvent.contentSize.height,
+          });
+        }}
+        style={[styles.default, {height: Math.max(35, this.state.height)}]}
+        value={this.state.text}
+      />
+    );
+  }
+}
+
 class RewriteExample extends React.Component {
   constructor(props) {
     super(props);
@@ -155,6 +178,60 @@ class TokenizedTextExample extends React.Component {
   }
 }
 
+var BlurOnSubmitExample = React.createClass({
+  focusNextField(nextField) {
+    this.refs[nextField].focus();
+  },
+
+  render: function() {
+    return (
+      <View>
+        <TextInput
+          ref="1"
+          style={styles.singleLine}
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
+          blurOnSubmit={false}
+          onSubmitEditing={() => this.focusNextField('2')}
+        />
+        <TextInput
+          ref="2"
+          style={styles.singleLine}
+          keyboardType="email-address"
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
+          blurOnSubmit={false}
+          onSubmitEditing={() => this.focusNextField('3')}
+        />
+        <TextInput
+          ref="3"
+          style={styles.singleLine}
+          keyboardType="url"
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
+          blurOnSubmit={false}
+          onSubmitEditing={() => this.focusNextField('4')}
+        />
+        <TextInput
+          ref="4"
+          style={styles.singleLine}
+          keyboardType="numeric"
+          placeholder="blurOnSubmit = false"
+          blurOnSubmit={false}
+          onSubmitEditing={() => this.focusNextField('5')}
+        />
+        <TextInput
+          ref="5"
+          style={styles.singleLine}
+          keyboardType="numbers-and-punctuation"
+          placeholder="blurOnSubmit = true"
+          returnKeyType="done"
+        />
+      </View>
+    );
+  }
+});
+
 var styles = StyleSheet.create({
   multiline: {
     height: 60,
@@ -185,7 +262,13 @@ exports.examples = [
   {
     title: 'Auto-focus',
     render: function() {
-      return <TextInput autoFocus={true} style={styles.singleLine} />;
+      return (
+        <TextInput
+          autoFocus={true}
+          style={styles.singleLine}
+          accessibilityLabel="I am the accessibility label for text input"
+        />
+      );
     }
   },
   {
@@ -242,6 +325,7 @@ exports.examples = [
         'default',
         'email-address',
         'numeric',
+        'phone-pad',
       ];
       var examples = keyboardTypes.map((type) => {
         return (
@@ -256,6 +340,10 @@ exports.examples = [
       return <View>{examples}</View>;
     }
   },
+  {
+    title: 'Blur on submit',
+    render: function(): ReactElement { return <BlurOnSubmitExample />; },
+  },
   {
     title: 'Event handling',
     render: function(): ReactElement { return <TextEventsExample />; },
@@ -298,6 +386,11 @@ exports.examples = [
               Darker backgroundColor
             </Text>
           </TextInput>
+          <TextInput
+            defaultValue="Highlight Color is red"
+            selectionColor={'red'}
+            style={styles.singleLine}>
+          </TextInput>
         </View>
       );
     }
@@ -347,25 +440,19 @@ exports.examples = [
             placeholder="multiline, aligned top-left"
             placeholderTextColor="red"
             multiline={true}
-            textAlign="start"
-            textAlignVertical="top"
-            style={styles.multiline}
+            style={[styles.multiline, {textAlign: "left", textAlignVertical: "top"}]}
           />
           <TextInput
             autoCorrect={true}
             placeholder="multiline, aligned center"
             placeholderTextColor="green"
             multiline={true}
-            textAlign="center"
-            textAlignVertical="center"
-            style={[styles.multiline]}
+            style={[styles.multiline, {textAlign: "center", textAlignVertical: "center"}]}
           />
           <TextInput
             autoCorrect={true}
             multiline={true}
-            textAlign="end"
-            textAlignVertical="bottom"
-            style={[styles.multiline, {color: 'blue'}]}>
+            style={[styles.multiline, {color: 'blue'}, {textAlign: "right", textAlignVertical: "bottom"}]}>
             <Text style={styles.multiline}>multiline with children, aligned bottom-right</Text>
           </TextInput>
         </View>
@@ -390,6 +477,20 @@ exports.examples = [
       );
     }
   },
+  {
+    title: 'Auto-expanding',
+    render: function() {
+      return (
+        <View>
+          <AutoExpandingTextInput
+            placeholder="height increases with content"
+            enablesReturnKeyAutomatically={true}
+            returnKeyType="done"
+          />
+        </View>
+      );
+    }
+  },
   {
     title: 'Attributed text',
     render: function() {
diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js
index 5f13d822fd54..239197dbacbb 100644
--- a/Examples/UIExplorer/TextInputExample.ios.js
+++ b/Examples/UIExplorer/TextInputExample.ios.js
@@ -96,7 +96,34 @@ var TextEventsExample = React.createClass({
   }
 });
 
+class AutoExpandingTextInput extends React.Component {
+  state: any;
+
+  constructor(props) {
+    super(props);
+    this.state = {text: '', height: 0};
+  }
+  render() {
+    return (
+      <TextInput
+        {...this.props}
+        multiline={true}
+        onChange={(event) => {
+          this.setState({
+            text: event.nativeEvent.text,
+            height: event.nativeEvent.contentSize.height,
+          });
+        }}
+        style={[styles.default, {height: Math.max(35, this.state.height)}]}
+        value={this.state.text}
+      />
+    );
+  }
+}
+
 class RewriteExample extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: ''};
@@ -125,7 +152,32 @@ class RewriteExample extends React.Component {
   }
 }
 
+class RewriteExampleInvalidCharacters extends React.Component {
+  state: any;
+
+  constructor(props) {
+    super(props);
+    this.state = {text: ''};
+  }
+  render() {
+    return (
+      <View style={styles.rewriteContainer}>
+        <TextInput
+          multiline={false}
+          onChangeText={(text) => {
+            this.setState({text: text.replace(/\s/g, '')});
+          }}
+          style={styles.default}
+          value={this.state.text}
+        />
+      </View>
+    );
+  }
+}
+
 class TokenizedTextExample extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: 'Hello #World'};
@@ -158,7 +210,7 @@ class TokenizedTextExample extends React.Component {
     //highlight hashtags
     parts = parts.map((text) => {
       if (/^#/.test(text)) {
-        return <Text style={styles.hashtag}>{text}</Text>;
+        return <Text key={text} style={styles.hashtag}>{text}</Text>;
       } else {
         return text;
       }
@@ -181,52 +233,52 @@ class TokenizedTextExample extends React.Component {
 
 var BlurOnSubmitExample = React.createClass({
   focusNextField(nextField) {
-    this.refs[nextField].focus()
+    this.refs[nextField].focus();
   },
 
   render: function() {
     return (
       <View>
         <TextInput
-          ref='1'
+          ref="1"
           style={styles.default}
-          placeholder='blurOnSubmit = false'
-          returnKeyType='next'
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
           blurOnSubmit={false}
           onSubmitEditing={() => this.focusNextField('2')}
         />
         <TextInput
-          ref='2'
+          ref="2"
           style={styles.default}
-          keyboardType='email-address'
-          placeholder='blurOnSubmit = false'
-          returnKeyType='next'
+          keyboardType="email-address"
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
           blurOnSubmit={false}
           onSubmitEditing={() => this.focusNextField('3')}
         />
         <TextInput
-          ref='3'
+          ref="3"
           style={styles.default}
-          keyboardType='url'
-          placeholder='blurOnSubmit = false'
-          returnKeyType='next'
+          keyboardType="url"
+          placeholder="blurOnSubmit = false"
+          returnKeyType="next"
           blurOnSubmit={false}
           onSubmitEditing={() => this.focusNextField('4')}
         />
         <TextInput
-          ref='4'
+          ref="4"
           style={styles.default}
-          keyboardType='numeric'
-          placeholder='blurOnSubmit = false'
+          keyboardType="numeric"
+          placeholder="blurOnSubmit = false"
           blurOnSubmit={false}
           onSubmitEditing={() => this.focusNextField('5')}
         />
         <TextInput
-          ref='5'
+          ref="5"
           style={styles.default}
-          keyboardType='numbers-and-punctuation'
-          placeholder='blurOnSubmit = true'
-          returnKeyType='done'
+          keyboardType="numbers-and-punctuation"
+          placeholder="blurOnSubmit = true"
+          returnKeyType="done"
         />
       </View>
     );
@@ -304,7 +356,13 @@ exports.examples = [
   {
     title: 'Auto-focus',
     render: function() {
-      return <TextInput autoFocus={true} style={styles.default} />;
+      return (
+        <TextInput
+          autoFocus={true}
+          style={styles.default}
+          accessibilityLabel="I am the accessibility label for text input"
+        />
+      );
     }
   },
   {
@@ -313,6 +371,12 @@ exports.examples = [
       return <RewriteExample />;
     }
   },
+  {
+    title: 'Live Re-Write (no spaces allowed)',
+    render: function() {
+      return <RewriteExampleInvalidCharacters />;
+    }
+  },
   {
     title: 'Auto-capitalize',
     render: function() {
@@ -486,6 +550,25 @@ exports.examples = [
       );
     }
   },
+  {
+    title: 'Colored highlight/cursor for text input',
+    render: function() {
+      return (
+        <View>
+          <TextInput
+            style={styles.default}
+            selectionColor={"green"}
+            defaultValue="Highlight me"
+          />
+          <TextInput
+            style={styles.default}
+            selectionColor={"rgba(86, 76, 205, 1)"}
+            defaultValue="Highlight me"
+          />
+        </View>
+      );
+    }
+  },
   {
     title: 'Clear button mode',
     render: function () {
@@ -544,6 +627,27 @@ exports.examples = [
       );
     }
   },
+  {
+    title: 'Blur on submit',
+    render: function(): ReactElement { return <BlurOnSubmitExample />; },
+  },
+  {
+    title: 'Multiline blur on submit',
+    render: function() {
+      return (
+        <View>
+          <TextInput
+            style={styles.multiline}
+            placeholder="blurOnSubmit = true"
+            returnKeyType="next"
+            blurOnSubmit={true}
+            multiline={true}
+            onSubmitEditing={event => alert(event.nativeEvent.text)}
+          />
+        </View>
+      );
+    }
+  },
   {
     title: 'Multiline',
     render: function() {
@@ -583,13 +687,23 @@ exports.examples = [
     }
   },
   {
-    title: 'Attributed text',
+    title: 'Auto-expanding',
     render: function() {
-      return <TokenizedTextExample />;
+      return (
+        <View>
+          <AutoExpandingTextInput
+            placeholder="height increases with content"
+            enablesReturnKeyAutomatically={true}
+            returnKeyType="done"
+          />
+        </View>
+      );
     }
   },
   {
-    title: 'Blur on submit',
-    render: function(): ReactElement { return <BlurOnSubmitExample />; },
+    title: 'Attributed text',
+    render: function() {
+      return <TokenizedTextExample />;
+    }
   },
 ];
diff --git a/Examples/UIExplorer/TimePickerAndroidExample.js b/Examples/UIExplorer/TimePickerAndroidExample.js
new file mode 100644
index 000000000000..06733fc35c88
--- /dev/null
+++ b/Examples/UIExplorer/TimePickerAndroidExample.js
@@ -0,0 +1,112 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  TimePickerAndroid,
+  StyleSheet,
+  Text,
+  TouchableWithoutFeedback,
+} = React;
+
+var UIExplorerBlock = require('./UIExplorerBlock');
+var UIExplorerPage = require('./UIExplorerPage');
+
+var TimePickerAndroidExample = React.createClass({
+
+  statics: {
+    title: 'TimePickerAndroid',
+    description: 'Standard Android time picker dialog',
+  },
+
+  getInitialState() {
+    // *Text, *Hour and *Minute are set by successCallback -- this updates the text with the time
+    // picked by the user and makes it so the next time they open it the hour and minute they picked
+    // before is displayed.
+    return {
+      isoFormatText: 'pick a time (24-hour format)',
+      presetHour: 4,
+      presetMinute: 4,
+      presetText: 'pick a time, default: 4:04AM',
+      simpleText: 'pick a time',
+    };
+  },
+
+  async showPicker(stateKey, options) {
+    try {
+      const {action, minute, hour} = await TimePickerAndroid.open(options);
+      var newState = {};
+      if (action === TimePickerAndroid.timeSetAction) {
+        newState[stateKey + 'Text'] = _formatTime(hour, minute);
+        newState[stateKey + 'Hour'] = hour;
+        newState[stateKey + 'Minute'] = minute;
+      } else if (action === TimePickerAndroid.dismissedAction) {
+        newState[stateKey + 'Text'] = 'dismissed';
+      }
+      this.setState(newState);
+    } catch ({code, message}) {
+      console.warn(`Error in example '${stateKey}': `, message);
+    }
+  },
+
+  render() {
+    return (
+      <UIExplorerPage title="TimePickerAndroid">
+        <UIExplorerBlock title="Simple time picker">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'simple')}>
+            <Text style={styles.text}>{this.state.simpleText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+        <UIExplorerBlock title="Time picker with pre-set time">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'preset', {
+              hour: this.state.presetHour,
+              minute: this.state.presetMinute,
+            })}>
+            <Text style={styles.text}>{this.state.presetText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+
+        <UIExplorerBlock title="Time picker with 24-hour time format">
+          <TouchableWithoutFeedback
+            onPress={this.showPicker.bind(this, 'isoFormat', {
+              hour: this.state.isoFormatHour,
+              minute: this.state.isoFormatMinute,
+              is24Hour: true,
+            })}>
+            <Text style={styles.text}>{this.state.isoFormatText}</Text>
+          </TouchableWithoutFeedback>
+        </UIExplorerBlock>
+      </UIExplorerPage>
+    );
+  },
+});
+
+/**
+ * Returns e.g. '3:05'.
+ */
+function _formatTime(hour, minute) {
+  return hour + ':' + (minute < 10 ? '0' + minute : minute);
+}
+
+var styles = StyleSheet.create({
+  text: {
+    color: 'black',
+  },
+});
+
+module.exports = TimePickerAndroidExample;
+
diff --git a/Examples/UIExplorer/TimerExample.js b/Examples/UIExplorer/TimerExample.js
index 55272a8220af..51a8f1af557c 100644
--- a/Examples/UIExplorer/TimerExample.js
+++ b/Examples/UIExplorer/TimerExample.js
@@ -171,9 +171,20 @@ exports.examples = [
           }
           return (
             <View>
-              {timer}
+              {this.state.showTimer && this._renderTimer()}
               <UIExplorerButton onPress={this._toggleTimer}>
-                {toggleText}
+                {this.state.showTimer ? 'Unmount timer' : 'Mount new timer'}
+              </UIExplorerButton>
+            </View>
+          );
+        },
+
+        _renderTimer: function() {
+          return (
+            <View>
+              <TimerTester ref="interval" dt={25} type="setInterval" />
+              <UIExplorerButton onPress={() => this.refs.interval.clear() }>
+                Clear interval
               </UIExplorerButton>
             </View>
           );
diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index f8ced267db8a..5b28f2e134d4 100644
--- a/Examples/UIExplorer/TouchableExample.js
+++ b/Examples/UIExplorer/TouchableExample.js
@@ -23,6 +23,9 @@ var {
   Text,
   TouchableHighlight,
   TouchableOpacity,
+  UIManager,
+  Platform,
+  TouchableNativeFeedback,
   View,
 } = React;
 
@@ -54,7 +57,7 @@ exports.examples = [
             activeOpacity={1}
             animationVelocity={0}
             underlayColor="rgb(210, 230, 255)"
-            onPress={() => console.log('custom THW text - hightlight')}>
+            onPress={() => console.log('custom THW text - highlight')}>
             <View style={styles.wrapperCustom}>
               <Text style={styles.text}>
                 Tap Here For Custom Highlight!
@@ -85,7 +88,28 @@ exports.examples = [
   render: function(): ReactElement {
     return <TouchableDelayEvents />;
   },
-}];
+}, {
+  title: '3D Touch / Force Touch',
+  description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches',
+  render: function(): ReactElement {
+    return <ForceTouchExample />;
+  },
+  platform: 'ios',
+}, {
+   title: 'Touchable Hit Slop',
+   description: '<Touchable*> components accept hitSlop prop which extends the touch area ' +
+     'without changing the view bounds.',
+   render: function(): ReactElement {
+     return <TouchableHitSlop />;
+   },
+ }, {
+   title: 'Disabled Touchable*',
+   description: '<Touchable*> components accept disabled prop which prevents ' +
+     'any interaction with component',
+   render: function(): ReactElement {
+     return <TouchableDisabled />;
+   },
+ }];
 
 var TextOnPressBox = React.createClass({
   getInitialState: function() {
@@ -133,18 +157,21 @@ var TouchableFeedbackEvents = React.createClass({
     return (
       <View testID="touchable_feedback_events">
         <View style={[styles.row, {justifyContent: 'center'}]}>
-        <TouchableOpacity
-          style={styles.wrapper}
-          testID="touchable_feedback_events_button"
-          onPress={() => this._appendEvent('press')}
-          onPressIn={() => this._appendEvent('pressIn')}
-          onPressOut={() => this._appendEvent('pressOut')}
-          onLongPress={() => this._appendEvent('longPress')}>
-          <Text style={styles.button}>
-            Press Me
-          </Text>
-        </TouchableOpacity>
-      </View>
+          <TouchableOpacity
+            style={styles.wrapper}
+            testID="touchable_feedback_events_button"
+            accessibilityLabel="touchable feedback events"
+            accessibilityTraits="button"
+            accessibilityComponentType="button"
+            onPress={() => this._appendEvent('press')}
+            onPressIn={() => this._appendEvent('pressIn')}
+            onPressOut={() => this._appendEvent('pressOut')}
+            onLongPress={() => this._appendEvent('longPress')}>
+            <Text style={styles.button}>
+              Press Me
+            </Text>
+          </TouchableOpacity>
+        </View>
         <View testID="touchable_feedback_events_console" style={styles.eventLogBox}>
           {this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
         </View>
@@ -169,21 +196,21 @@ var TouchableDelayEvents = React.createClass({
     return (
       <View testID="touchable_delay_events">
         <View style={[styles.row, {justifyContent: 'center'}]}>
-        <TouchableOpacity
-          style={styles.wrapper}
-          testID="touchable_delay_events_button"
-          onPress={() => this._appendEvent('press')}
-          delayPressIn={400}
-          onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
-          delayPressOut={1000}
-          onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
-          delayLongPress={800}
-          onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
-          <Text style={styles.button}>
-            Press Me
-          </Text>
-        </TouchableOpacity>
-      </View>
+          <TouchableOpacity
+            style={styles.wrapper}
+            testID="touchable_delay_events_button"
+            onPress={() => this._appendEvent('press')}
+            delayPressIn={400}
+            onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
+            delayPressOut={1000}
+            onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
+            delayLongPress={800}
+            onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
+            <Text style={styles.button}>
+              Press Me
+            </Text>
+          </TouchableOpacity>
+        </View>
         <View style={styles.eventLogBox} testID="touchable_delay_events_console">
           {this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
         </View>
@@ -198,6 +225,148 @@ var TouchableDelayEvents = React.createClass({
   },
 });
 
+var ForceTouchExample = React.createClass({
+  getInitialState: function() {
+    return {
+      force: 0,
+    };
+  },
+  _renderConsoleText: function() {
+    return View.forceTouchAvailable ?
+      'Force: ' + this.state.force.toFixed(3) :
+      '3D Touch is not available on this device';
+  },
+  render: function() {
+    return (
+      <View testID="touchable_3dtouch_event">
+        <View style={styles.forceTouchBox} testID="touchable_3dtouch_output">
+          <Text>{this._renderConsoleText()}</Text>
+        </View>
+        <View style={[styles.row, {justifyContent: 'center'}]}>
+          <View
+            style={styles.wrapper}
+            testID="touchable_3dtouch_button"
+            onStartShouldSetResponder={() => true}
+            onResponderMove={(event) => this.setState({force: event.nativeEvent.force})}
+            onResponderRelease={(event) => this.setState({force: 0})}>
+            <Text style={styles.button}>
+              Press Me
+            </Text>
+          </View>
+        </View>
+      </View>
+    );
+  },
+});
+
+var TouchableHitSlop = React.createClass({
+  getInitialState: function() {
+    return {
+      timesPressed: 0,
+    };
+  },
+  onPress: function() {
+    this.setState({
+      timesPressed: this.state.timesPressed + 1,
+    });
+  },
+  render: function() {
+    var log = '';
+    if (this.state.timesPressed > 1) {
+      log = this.state.timesPressed + 'x onPress';
+    } else if (this.state.timesPressed > 0) {
+      log = 'onPress';
+    }
+
+    return (
+      <View testID="touchable_hit_slop">
+        <View style={[styles.row, {justifyContent: 'center'}]}>
+          <TouchableOpacity
+            onPress={this.onPress}
+            style={styles.hitSlopWrapper}
+            hitSlop={{top: 30, bottom: 30, left: 60, right: 60}}
+            testID="touchable_hit_slop_button">
+            <Text style={styles.hitSlopButton}>
+              Press Outside This View
+            </Text>
+          </TouchableOpacity>
+         </View>
+        <View style={styles.logBox}>
+          <Text>
+            {log}
+          </Text>
+        </View>
+      </View>
+    );
+  }
+});
+
+var TouchableDisabled = React.createClass({
+  render: function() {
+    return (
+      <View>
+        <TouchableOpacity disabled={true} style={[styles.row, styles.block]}>
+          <Text style={styles.disabledButton}>Disabled TouchableOpacity</Text>
+        </TouchableOpacity>
+
+        <TouchableOpacity disabled={false} style={[styles.row, styles.block]}>
+          <Text style={styles.button}>Enabled TouchableOpacity</Text>
+        </TouchableOpacity>
+
+        <TouchableHighlight
+          activeOpacity={1}
+          disabled={true}
+          animationVelocity={0}
+          underlayColor="rgb(210, 230, 255)"
+          style={[styles.row, styles.block]}
+          onPress={() => console.log('custom THW text - highlight')}>
+          <Text style={styles.disabledButton}>
+            Disabled TouchableHighlight
+          </Text>
+        </TouchableHighlight>
+
+        <TouchableHighlight
+          activeOpacity={1}
+          animationVelocity={0}
+          underlayColor="rgb(210, 230, 255)"
+          style={[styles.row, styles.block]}
+          onPress={() => console.log('custom THW text - highlight')}>
+          <Text style={styles.button}>
+            Disabled TouchableHighlight
+          </Text>
+        </TouchableHighlight>
+
+        {Platform.OS === 'android' &&
+          <TouchableNativeFeedback
+            style={[styles.row, styles.block]}
+            onPress={() => console.log('custom TNF has been clicked')}
+            background={TouchableNativeFeedback.SelectableBackground()}>
+            <View>
+              <Text style={[styles.button, styles.nativeFeedbackButton]}>
+                Enabled TouchableNativeFeedback
+              </Text>
+            </View>
+          </TouchableNativeFeedback>
+        }
+
+        {Platform.OS === 'android' &&
+          <TouchableNativeFeedback
+            disabled={true}
+            style={[styles.row, styles.block]}
+            onPress={() => console.log('custom TNF has been clicked')}
+            background={TouchableNativeFeedback.SelectableBackground()}>
+            <View>
+              <Text style={[styles.disabledButton, styles.nativeFeedbackButton]}>
+                Disabled TouchableNativeFeedback
+              </Text>
+            </View>
+          </TouchableNativeFeedback>
+        }
+      </View>
+    );
+  }
+});
+
 var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
 
 var styles = StyleSheet.create({
@@ -216,9 +385,23 @@ var styles = StyleSheet.create({
   text: {
     fontSize: 16,
   },
+  block: {
+    padding: 10,
+  },
   button: {
     color: '#007AFF',
   },
+  disabledButton: {
+    color: '#007AFF',
+    opacity: 0.5,
+  },
+  nativeFeedbackButton: {
+    textAlign: 'center',
+    margin: 10,
+  },
+  hitSlopButton: {
+    color: 'white',
+  },
   wrapper: {
     borderRadius: 8,
   },
@@ -226,10 +409,14 @@ var styles = StyleSheet.create({
     borderRadius: 8,
     padding: 6,
   },
+  hitSlopWrapper: {
+    backgroundColor: 'red',
+    marginVertical: 30,
+  },
   logBox: {
     padding: 20,
     margin: 10,
-    borderWidth: 1 / PixelRatio.get(),
+    borderWidth: StyleSheet.hairlineWidth,
     borderColor: '#f0f0f0',
     backgroundColor: '#f9f9f9',
   },
@@ -237,9 +424,17 @@ var styles = StyleSheet.create({
     padding: 10,
     margin: 10,
     height: 120,
-    borderWidth: 1 / PixelRatio.get(),
+    borderWidth: StyleSheet.hairlineWidth,
+    borderColor: '#f0f0f0',
+    backgroundColor: '#f9f9f9',
+  },
+  forceTouchBox: {
+    padding: 10,
+    margin: 10,
+    borderWidth: StyleSheet.hairlineWidth,
     borderColor: '#f0f0f0',
     backgroundColor: '#f9f9f9',
+    alignItems: 'center',
   },
   textBlock: {
     fontWeight: '500',
diff --git a/Examples/UIExplorer/TransformExample.js b/Examples/UIExplorer/TransformExample.js
index 7da9eb5ac4e4..01a2813d41c5 100644
--- a/Examples/UIExplorer/TransformExample.js
+++ b/Examples/UIExplorer/TransformExample.js
@@ -10,6 +10,7 @@
  * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * @flow
  */
 'use strict';
 
diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index 05416d4245ef..95f9215cee98 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -8,12 +8,18 @@
 
 /* Begin PBXBuildFile section */
 		1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; };
+		13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */; };
+		1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; };
+		1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1861C04AB9F0091BED0 /* flux@3x.png */; };
+		1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1871C04AB9F0091BED0 /* hawk.png */; };
+		1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */; };
+		1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 1323F18E1C04ABEB0091BED0 /* Thumbnails */; };
 		13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FE81AA91428003F314A /* libRCTImage.a */; };
 		134180011AA9153C003F314A /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13417FEF1AA914B8003F314A /* libRCTText.a */; };
 		1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
-		134366AF1BFD2C76000A2DE1 /* Thumbnails in Resources */ = {isa = PBXBuildFile; fileRef = 134366AE1BFD2C76000A2DE1 /* Thumbnails */; };
 		134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
 		134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
+		134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */; };
 		138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
 		138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 138DEE091B9EDDDB007F4EA5 /* libRCTCameraRoll.a */; };
 		1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
@@ -22,6 +28,7 @@
 		13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
 		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
 		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+		13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */; };
 		13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
 		13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; };
 		143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
@@ -29,7 +36,7 @@
 		147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; };
 		1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; };
 		1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; };
-		1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
+		1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
 		1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */; };
 		1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */; };
 		1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */; };
@@ -51,9 +58,9 @@
 		14D6D7291B2222EF001FB087 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14AADF041AC3DB95002390C9 /* libReact.a */; };
 		14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; };
 		272E6B3F1BEA849E001FCF37 /* UpdatePropertiesExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */; };
+		27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */; };
 		27F441EC1BEBE5030039B79C /* FlexibleSizeExampleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */; };
 		3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
-		3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */; };
 		3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */; };
 		834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
 		83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
@@ -173,12 +180,18 @@
 /* Begin PBXFileReference section */
 		004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; };
+		13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = "<group>"; };
+		1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = "<group>"; };
+		1323F1861C04AB9F0091BED0 /* flux@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flux@3x.png"; sourceTree = "<group>"; };
+		1323F1871C04AB9F0091BED0 /* hawk.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = hawk.png; sourceTree = "<group>"; };
+		1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = uie_thumb_big.png; sourceTree = "<group>"; };
+		1323F18E1C04ABEB0091BED0 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = "<group>"; };
 		13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../../Libraries/Image/RCTImage.xcodeproj; sourceTree = "<group>"; };
 		13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = "<group>"; };
 		134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
-		134366AE1BFD2C76000A2DE1 /* Thumbnails */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Thumbnails; sourceTree = "<group>"; };
 		134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
 		134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
+		134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitTests.m; sourceTree = "<group>"; };
 		138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
 		138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCameraRoll.xcodeproj; path = ../../Libraries/CameraRoll/RCTCameraRoll.xcodeproj; sourceTree = "<group>"; };
 		1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
@@ -190,6 +203,7 @@
 		13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = UIExplorer/Images.xcassets; sourceTree = "<group>"; };
 		13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = UIExplorer/Info.plist; sourceTree = "<group>"; };
 		13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
+		13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTURLUtilsTests.m; sourceTree = "<group>"; };
 		13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
 		13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
 		13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = "<group>"; };
@@ -206,7 +220,7 @@
 		144D21231B2204C5006DB32B /* RCTImageUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtilTests.m; sourceTree = "<group>"; };
 		1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = "<group>"; };
 		1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = "<group>"; };
-		1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = "<group>"; };
+		1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSCExecutorTests.m; sourceTree = "<group>"; };
 		1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_NSURLTests.m; sourceTree = "<group>"; };
 		1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIFontTests.m; sourceTree = "<group>"; };
 		1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcherTests.m; sourceTree = "<group>"; };
@@ -226,10 +240,10 @@
 		14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = "<group>"; };
 		272E6B3B1BEA849E001FCF37 /* UpdatePropertiesExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UpdatePropertiesExampleView.h; path = UIExplorer/NativeExampleViews/UpdatePropertiesExampleView.h; sourceTree = "<group>"; };
 		272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UpdatePropertiesExampleView.m; path = UIExplorer/NativeExampleViews/UpdatePropertiesExampleView.m; sourceTree = "<group>"; };
+		27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootViewIntegrationTests.m; sourceTree = "<group>"; };
 		27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FlexibleSizeExampleView.m; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m; sourceTree = "<group>"; };
 		27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = UIExplorer/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = "<group>"; };
 		357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
-		3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = uie_thumb_big.png; path = UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png; sourceTree = SOURCE_ROOT; };
 		3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerIntegrationTests.m; sourceTree = "<group>"; };
 		58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
 		83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
@@ -312,6 +326,18 @@
 			name = Libraries;
 			sourceTree = "<group>";
 		};
+		1323F18D1C04ABAC0091BED0 /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				13B07FB61A68108700A75B9A /* Info.plist */,
+				1323F1851C04AB9F0091BED0 /* bunny.png */,
+				1323F1861C04AB9F0091BED0 /* flux@3x.png */,
+				1323F1871C04AB9F0091BED0 /* hawk.png */,
+				1323F1881C04AB9F0091BED0 /* uie_thumb_big.png */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
 		13417FE41AA91428003F314A /* Products */ = {
 			isa = PBXGroup;
 			children = (
@@ -375,10 +401,10 @@
 				13B07FAF1A68108700A75B9A /* AppDelegate.h */,
 				13B07FB01A68108700A75B9A /* AppDelegate.m */,
 				13B07FB51A68108700A75B9A /* Images.xcassets */,
-				134366AE1BFD2C76000A2DE1 /* Thumbnails */,
-				13B07FB61A68108700A75B9A /* Info.plist */,
 				13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
 				13B07FB71A68108700A75B9A /* main.m */,
+				1323F18E1C04ABEB0091BED0 /* Thumbnails */,
+				1323F18D1C04ABAC0091BED0 /* Supporting Files */,
 			);
 			name = UIExplorer;
 			sourceTree = "<group>";
@@ -386,9 +412,10 @@
 		143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = {
 			isa = PBXGroup;
 			children = (
+				13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */,
 				1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */,
 				1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */,
-				1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */,
+				1497CFA61B21F5E400C1F8F2 /* RCTJSCExecutorTests.m */,
 				1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */,
 				1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
 				1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
@@ -399,6 +426,8 @@
 				144D21231B2204C5006DB32B /* RCTImageUtilTests.m */,
 				13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
 				13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
+				134CB9291C85A38800265FA6 /* RCTModuleInitTests.m */,
+				13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */,
 				1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */,
 				138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
 				1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
@@ -434,6 +463,7 @@
 		143BC5961B21E3E100462512 /* UIExplorerIntegrationTests */ = {
 			isa = PBXGroup;
 			children = (
+				27B885551BED29AF00008352 /* RCTRootViewIntegrationTests.m */,
 				3DB99D0B1BA0340600302749 /* UIExplorerIntegrationTests.m */,
 				143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */,
 				83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */,
@@ -445,7 +475,6 @@
 		143BC5971B21E3E100462512 /* Supporting Files */ = {
 			isa = PBXGroup;
 			children = (
-				3D36915A1BDA8CBB007B22D8 /* uie_thumb_big.png */,
 				143BC5981B21E3E100462512 /* Info.plist */,
 			);
 			name = "Supporting Files";
@@ -829,9 +858,13 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				134366AF1BFD2C76000A2DE1 /* Thumbnails in Resources */,
+				1323F18A1C04AB9F0091BED0 /* flux@3x.png in Resources */,
+				1323F18F1C04ABEB0091BED0 /* Thumbnails in Resources */,
+				1323F18B1C04AB9F0091BED0 /* hawk.png in Resources */,
 				13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
 				13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
+				1323F1891C04AB9F0091BED0 /* bunny.png in Resources */,
+				1323F18C1C04AB9F0091BED0 /* uie_thumb_big.png in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -839,7 +872,6 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				3D36915B1BDA8CBB007B22D8 /* uie_thumb_big.png in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -855,14 +887,17 @@
 				1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */,
 				1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
 				1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
-				1497CFAE1B21F5E400C1F8F2 /* RCTContextExecutorTests.m in Sources */,
+				1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */,
+				13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */,
 				1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
+				134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */,
 				1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
 				1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,
 				13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
 				1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
 				13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
 				138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
+				13B6C1A31C34225900D3FAF5 /* RCTURLUtilsTests.m in Sources */,
 				8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,
 				8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */,
 			);
@@ -886,6 +921,7 @@
 				3DB99D0C1BA0340600302749 /* UIExplorerIntegrationTests.m in Sources */,
 				83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */,
 				143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */,
+				27B885561BED29AF00008352 /* RCTRootViewIntegrationTests.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme
index e2f84182e74e..52560f0d6856 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
    LastUpgradeVersion = "0700"
-   version = "1.8">
+   version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
       buildImplicitDependencies = "YES">
@@ -51,10 +51,10 @@
       </BuildActionEntries>
    </BuildAction>
    <TestAction
+      buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      buildConfiguration = "Debug">
+      shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
          <TestableReference
             skipped = "NO">
@@ -90,11 +90,11 @@
       </AdditionalOptions>
    </TestAction>
    <LaunchAction
+      buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
-      buildConfiguration = "Debug"
       ignoresPersistentStateOnLaunch = "NO"
       debugDocumentVersioning = "YES"
       debugServiceExtension = "internal"
@@ -113,10 +113,10 @@
       </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
+      buildConfiguration = "Release"
       shouldUseLaunchSchemeArgsEnv = "YES"
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
-      buildConfiguration = "Release"
       debugDocumentVersioning = "YES">
       <BuildableProductRunnable
          runnableDebuggingMode = "0">
diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m
index 577e6dd94b5d..b0d19a59c170 100644
--- a/Examples/UIExplorer/UIExplorer/AppDelegate.m
+++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m
@@ -16,6 +16,7 @@
 
 #import "RCTBridge.h"
 #import "RCTJavaScriptLoader.h"
+#import "RCTLinkingManager.h"
 #import "RCTRootView.h"
 
 @interface AppDelegate() <RCTBridgeDelegate>
@@ -80,6 +81,14 @@ - (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
   return sourceURL;
 }
 
+
+- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
+  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
+{
+  return [RCTLinkingManager application:application openURL:url
+                      sourceApplication:sourceApplication annotation:annotation];
+}
+
 - (void)loadSourceForBridge:(RCTBridge *)bridge
                   withBlock:(RCTSourceLoadBlock)loadCallback
 {
diff --git a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib
index 3b7dcb4a082b..0556e6641fb0 100644
--- a/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib
+++ b/Examples/UIExplorer/UIExplorer/Base.lproj/LaunchScreen.xib
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="13F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9059" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
         <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
     </dependencies>
     <objects>
@@ -13,17 +14,20 @@
             <subviews>
                 <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="  Copyright (c) 2015 Facebook. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
                     <rect key="frame" x="20" y="439" width="441" height="21"/>
+                    <animations/>
                     <fontDescription key="fontDescription" type="system" pointSize="17"/>
                     <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
                     <nil key="highlightedColor"/>
                 </label>
                 <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="UIExplorer" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
                     <rect key="frame" x="20" y="140" width="441" height="43"/>
+                    <animations/>
                     <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
                     <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
                     <nil key="highlightedColor"/>
                 </label>
             </subviews>
+            <animations/>
             <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
             <constraints>
                 <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json
new file mode 100644
index 000000000000..da4a164c9186
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorer/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json
deleted file mode 100644
index e1ccff627e54..000000000000
--- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x",
-      "filename" : "uie_comment_highlighted@2x.png"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json
deleted file mode 100644
index 6f75231d5e6a..000000000000
--- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x",
-      "filename" : "uie_comment_normal@2x.png"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json
deleted file mode 100644
index cdd15d023904..000000000000
--- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "idiom" : "universal",
-      "filename" : "uie_thumb_big.png",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json
deleted file mode 100644
index 06a7acabf522..000000000000
--- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x",
-      "filename" : "uie_thumb_normal@2x.png"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json b/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json
deleted file mode 100644
index a00a3dc54d03..000000000000
--- a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/Contents.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "images" : [
-    {
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x",
-      "filename" : "uie_thumb_selected@2x.png"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    }
-  ],
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  }
-}
\ No newline at end of file
diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist
index 9e91b1cef8cc..d2e82af92142 100644
--- a/Examples/UIExplorer/UIExplorer/Info.plist
+++ b/Examples/UIExplorer/UIExplorer/Info.plist
@@ -18,10 +18,28 @@
 	<string>1.0</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>CFBundleURLName</key>
+			<string>com.reactjs.ios</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>rnuiexplorer</string>
+			</array>
+		</dict>
+	</array>
 	<key>CFBundleVersion</key>
 	<string>1</string>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+	</dict>
 	<key>NSLocationWhenInUseUsageDescription</key>
 	<string>You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!</string>
 	<key>UILaunchStoryboardName</key>
@@ -38,11 +56,5 @@
 	</array>
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<false/>
-	<key>NSAppTransportSecurity</key>
-  <dict>
-    <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/-->
-    <key>NSAllowsArbitraryLoads</key>
-    <true/>
-  </dict>
 </dict>
 </plist>
diff --git a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m
index 134d128cf835..ce899207027a 100644
--- a/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m
+++ b/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m
@@ -52,8 +52,7 @@ @implementation FlexibleSizeExampleView
 
 - (instancetype)initWithFrame:(CGRect)frame
 {
-  self = [super initWithFrame:frame];
-  if (self) {
+  if ((self = [super initWithFrame:frame])) {
     _sizeUpdated = NO;
 
     AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
diff --git a/Examples/UIExplorer/UIExplorerActions.js b/Examples/UIExplorer/UIExplorerActions.js
new file mode 100644
index 000000000000..8f7a59f4e23e
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerActions.js
@@ -0,0 +1,51 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+export type UIExplorerListWithFilterAction = {
+  type: 'UIExplorerListWithFilterAction',
+  filter: ?string;
+};
+
+export type UIExplorerExampleAction = {
+  type: 'UIExplorerExampleAction',
+  openExample: string;
+};
+
+import type {BackAction} from 'NavigationRootContainer';
+
+export type UIExplorerAction = BackAction | UIExplorerListWithFilterAction | UIExplorerExampleAction;
+
+function ExampleListWithFilter(filter: ?string): UIExplorerListWithFilterAction {
+  return {
+    type: 'UIExplorerListWithFilterAction',
+    filter,
+  };
+}
+
+function ExampleAction(openExample: string): UIExplorerExampleAction {
+  return {
+    type: 'UIExplorerExampleAction',
+    openExample,
+  };
+}
+
+const UIExplorerActions = {
+  ExampleListWithFilter,
+  ExampleAction,
+};
+
+module.exports = UIExplorerActions;
diff --git a/Examples/UIExplorer/UIExplorerApp.android.js b/Examples/UIExplorer/UIExplorerApp.android.js
index c9bd2418fe44..9e26c5f7b798 100644
--- a/Examples/UIExplorer/UIExplorerApp.android.js
+++ b/Examples/UIExplorer/UIExplorerApp.android.js
@@ -16,110 +16,162 @@
  */
 'use strict';
 
-var React = require('react-native');
-var {
+const React = require('react-native');
+const {
   AppRegistry,
   BackAndroid,
   Dimensions,
   DrawerLayoutAndroid,
+  NavigationExperimental,
   StyleSheet,
   ToolbarAndroid,
   View,
+  StatusBar,
 } = React;
-var UIExplorerList = require('./UIExplorerList.android');
+const {
+  RootContainer: NavigationRootContainer,
+} = NavigationExperimental;
+const UIExplorerActions = require('./UIExplorerActions');
+const UIExplorerExampleList = require('./UIExplorerExampleList');
+const UIExplorerList = require('./UIExplorerList');
+const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer');
+const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap');
 
 var DRAWER_WIDTH_LEFT = 56;
 
-var UIExplorerApp = React.createClass({
-  getInitialState: function() {
-    return {
-      example: this._getUIExplorerHome(),
-    };
-  },
-
-  _getUIExplorerHome: function() {
-    return {
-      title: 'UIExplorer',
-      component: this._renderHome(),
-    };
-  },
+class UIExplorerApp extends React.Component {
+  componentWillMount() {
+    BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress.bind(this));
+  }
 
-  componentWillMount: function() {
-    BackAndroid.addEventListener('hardwareBackPress', this._handleBackButtonPress);
-  },
+  render() {
+    return (
+      <NavigationRootContainer
+        persistenceKey="UIExplorerStateNavState"
+        ref={navRootRef => { this._navigationRootRef = navRootRef; }}
+        reducer={UIExplorerNavigationReducer}
+        renderNavigation={this._renderApp.bind(this)}
+      />
+    );
+  }
 
-  render: function() {
+  _renderApp(navigationState, onNavigate) {
+    if (!navigationState) {
+      return null;
+    }
     return (
       <DrawerLayoutAndroid
         drawerPosition={DrawerLayoutAndroid.positions.Left}
         drawerWidth={Dimensions.get('window').width - DRAWER_WIDTH_LEFT}
         keyboardDismissMode="on-drag"
+        onDrawerOpen={() => {
+          this._overrideBackPressForDrawerLayout = true;
+        }}
+        onDrawerClose={() => {
+          this._overrideBackPressForDrawerLayout = false;
+        }}
         ref={(drawer) => { this.drawer = drawer; }}
-        renderNavigationView={this._renderNavigationView}>
-        {this._renderNavigation()}
+        renderNavigationView={this._renderDrawerContent.bind(this, onNavigate)}>
+        {this._renderNavigation(navigationState, onNavigate)}
       </DrawerLayoutAndroid>
-      );
-  },
+    );
+  }
 
-  _renderNavigationView: function() {
+  _renderDrawerContent(onNavigate) {
     return (
-      <UIExplorerList
-        onSelectExample={this.onSelectExample}
-        isInDrawer={true}
+      <UIExplorerExampleList
+        list={UIExplorerList}
+        displayTitleRow={true}
+        disableSearch={true}
+        onNavigate={(action) => {
+          this.drawer && this.drawer.closeDrawer();
+          onNavigate(action);
+        }}
       />
     );
-  },
+  }
 
-  onSelectExample: function(example) {
-    this.drawer.closeDrawer();
-    if (example.title === this._getUIExplorerHome().title) {
-      example = this._getUIExplorerHome();
+  _renderNavigation(navigationState, onNavigate) {
+    if (navigationState.externalExample) {
+      var Component = UIExplorerList.Modules[navigationState.externalExample];
+      return (
+        <Component
+          onExampleExit={() => {
+            onNavigate(NavigationRootContainer.getBackAction());
+          }}
+          ref={(example) => { this._exampleRef = example; }}
+        />
+      );
     }
-    this.setState({
-      example: example,
-    });
-  },
-
-  _renderHome: function() {
-    var onSelectExample = this.onSelectExample;
-    return React.createClass({
-      render: function() {
-        return (
-          <UIExplorerList
-            onSelectExample={onSelectExample}
-            isInDrawer={false}
+    const {stack} = navigationState;
+    const title = UIExplorerStateTitleMap(stack.children[stack.index]);
+    if (stack && stack.children[1]) {
+      const {key} = stack.children[1];
+      const ExampleModule = UIExplorerList.Modules[key];
+      const ExampleComponent = UIExplorerExampleList.makeRenderable(ExampleModule);
+      return (
+        <View style={styles.container}>
+          <StatusBar
+            backgroundColor="#589c90"
           />
-        );
-      }
-    });
-  },
-
-  _renderNavigation: function() {
-    var Component = this.state.example.component;
+          <ToolbarAndroid
+            logo={require('image!launcher_icon')}
+            navIcon={require('image!ic_menu_black_24dp')}
+            onIconClicked={() => this.drawer.openDrawer()}
+            style={styles.toolbar}
+            title={title}
+          />
+          <ExampleComponent
+            ref={(example) => { this._exampleRef = example; }}
+          />
+        </View>
+      );
+    }
     return (
       <View style={styles.container}>
+        <StatusBar
+          backgroundColor="#589c90"
+        />
         <ToolbarAndroid
           logo={require('image!launcher_icon')}
           navIcon={require('image!ic_menu_black_24dp')}
           onIconClicked={() => this.drawer.openDrawer()}
           style={styles.toolbar}
-          title={this.state.example.title}
+          title={title}
+        />
+        <UIExplorerExampleList
+          list={UIExplorerList}
+          {...stack.children[0]}
         />
-        <Component />
       </View>
     );
-  },
+  }
 
-  _handleBackButtonPress: function() {
-    if (this.state.example.title !== this._getUIExplorerHome().title) {
-      this.onSelectExample(this._getUIExplorerHome());
+  _handleBackButtonPress() {
+    if (this._overrideBackPressForDrawerLayout) {
+      // This hack is necessary because drawer layout provides an imperative API
+      // with open and close methods. This code would be cleaner if the drawer
+      // layout provided an `isOpen` prop and allowed us to pass a `onDrawerClose` handler.
+      this.drawer && this.drawer.closeDrawer();
+      return true;
+    }
+    if (
+      this._exampleRef &&
+      this._exampleRef.handleBackAction &&
+      this._exampleRef.handleBackAction()
+    ) {
       return true;
     }
+    if (this._navigationRootRef) {
+      return this._navigationRootRef.handleNavigation(
+        NavigationRootContainer.getBackAction()
+      );
+    }
     return false;
-  },
-});
+  }
+}
 
-var styles = StyleSheet.create({
+const styles = StyleSheet.create({
   container: {
     flex: 1,
   },
diff --git a/Examples/UIExplorer/UIExplorerApp.ios.js b/Examples/UIExplorer/UIExplorerApp.ios.js
index c8c4a834f1fc..3072b36f71fe 100644
--- a/Examples/UIExplorer/UIExplorerApp.ios.js
+++ b/Examples/UIExplorer/UIExplorerApp.ios.js
@@ -16,133 +16,194 @@
  */
 'use strict';
 
-var React = require('react-native');
-var UIExplorerList = require('./UIExplorerList.ios');
-var {
+const React = require('react-native');
+const UIExplorerActions = require('./UIExplorerActions');
+const UIExplorerList = require('./UIExplorerList.ios');
+const UIExplorerExampleList = require('./UIExplorerExampleList');
+const UIExplorerNavigationReducer = require('./UIExplorerNavigationReducer');
+const UIExplorerStateTitleMap = require('./UIExplorerStateTitleMap');
+
+const {
+  Alert,
+  Animated,
   AppRegistry,
-  NavigatorIOS,
+  NavigationExperimental,
+  SnapshotViewIOS,
   StyleSheet,
   Text,
   TouchableHighlight,
-  View
+  View,
 } = React;
 
-var UIExplorerApp = React.createClass({
+const {
+  AnimatedView: NavigationAnimatedView,
+  Card: NavigationCard,
+  Header: NavigationHeader,
+  Reducer: NavigationReducer,
+  RootContainer: NavigationRootContainer,
+} = NavigationExperimental;
 
-  getInitialState: function() {
-    return {
-      openExternalExample: (null: ?React.Component),
-    };
-  },
+import type { Value } from 'Animated';
+
+import type { NavigationStateRendererProps } from 'NavigationAnimatedView';
+
+import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer';
+
+import type { UIExplorerExample } from './UIExplorerList.ios';
 
-  render: function() {
-    if (this.state.openExternalExample) {
-      var Example = this.state.openExternalExample;
+function PathActionMap(path: string): ?Object {
+  // Warning! Hacky parsing for example code. Use a library for this!
+  const exampleParts = path.split('/example/');
+  const exampleKey = exampleParts[1];
+  if (exampleKey) {
+    if (!UIExplorerList.Modules[exampleKey]) {
+      Alert.alert(`${exampleKey} example could not be found!`);
+      return null;
+    }
+    return UIExplorerActions.ExampleAction(exampleKey);
+  }
+  return null;
+}
+
+function URIActionMap(uri: ?string): ?Object {
+  // Warning! Hacky parsing for example code. Use a library for this!
+  if (!uri) {
+    return null;
+  }
+  const parts = uri.split('rnuiexplorer:/');
+  if (!parts[1]) {
+    return null;
+  }
+  const path = parts[1];
+  return PathActionMap(path);
+}
+
+
+class UIExplorerApp extends React.Component {
+  _navigationRootRef: ?NavigationRootContainer;
+  _renderNavigation: Function;
+  _renderOverlay: Function;
+  _renderScene: Function;
+  _renderCard: Function;
+  componentWillMount() {
+    this._renderNavigation = this._renderNavigation.bind(this);
+    this._renderOverlay = this._renderOverlay.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+    this._renderCard = this._renderCard.bind(this);
+  }
+  render() {
+    return (
+      <NavigationRootContainer
+        persistenceKey="UIExplorerState"
+        reducer={UIExplorerNavigationReducer}
+        ref={navRootRef => { this._navigationRootRef = navRootRef; }}
+        renderNavigation={this._renderNavigation}
+        linkingActionMap={URIActionMap}
+      />
+    );
+  }
+  _renderNavigation(navigationState: UIExplorerNavigationState, onNavigate: Function) {
+    if (!navigationState) {
+      return null;
+    }
+    if (navigationState.externalExample) {
+      var Component = UIExplorerList.Modules[navigationState.externalExample];
       return (
-        <Example
+        <Component
           onExampleExit={() => {
-            this.setState({ openExternalExample: null, });
+            onNavigate(NavigationRootContainer.getBackAction());
           }}
         />
       );
     }
-
+    const {stack} = navigationState;
     return (
-      <NavigatorIOS
+      <NavigationAnimatedView
+        navigationState={stack}
         style={styles.container}
-        initialRoute={{
-          title: 'UIExplorer',
-          component: UIExplorerList,
-          passProps: {
-            onExternalExampleRequested: (example) => {
-              this.setState({ openExternalExample: example, });
-            },
-          }
-        }}
-        itemWrapperStyle={styles.itemWrapper}
-        tintColor="#008888"
+        renderOverlay={this._renderOverlay}
+        renderScene={this._renderCard}
       />
     );
   }
-});
 
-var styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-  itemWrapper: {
-    backgroundColor: '#eaeaea',
-  },
-  bigContainer: {
-    flex: 1,
-    height: 60,
-    backgroundColor: 'gray',
-  },
-  smallContainer: {
-    flex: 1,
-    height: 40,
-    backgroundColor: 'gray',
-  },
-  center: {
-    flex: 1,
-    alignItems: 'center',
-    justifyContent: 'center',
-  },
-  whiteText: {
-    color: 'white',
+  _renderOverlay(props: NavigationStateRendererProps): ReactElement {
+    return (
+      <NavigationHeader
+        navigationState={props.navigationParentState}
+        position={props.position}
+        getTitle={UIExplorerStateTitleMap}
+      />
+    );
   }
-});
-
-var SetPropertiesExampleApp = React.createClass({
-
-  render: function() {
-    var wrapperStyle = {
-      backgroundColor: this.props.color,
-      flex: 1,
-      alignItems: 'center',
-      justifyContent: 'center'
-    };
 
+  _renderCard(props: NavigationStateRendererProps): ReactElement {
     return (
-      <View style={wrapperStyle}>
-        <Text>
-          Embedded React Native view
-        </Text>
-      </View>
+      <NavigationCard
+        index={props.index}
+        key={props.navigationState.key}
+        layout={props.layout}
+        navigationState={props.navigationParentState}
+        position={props.position}>
+        {this._renderScene(props.navigationState)}
+      </NavigationCard>
     );
-  },
-});
+  }
 
-var RootViewSizeFlexibilityExampleApp = React.createClass({
+  _renderScene(state: Object): ?ReactElement {
+    if (state.key === 'AppList') {
+      return (
+        <UIExplorerExampleList
+          list={UIExplorerList}
+          style={styles.exampleContainer}
+          {...state}
+        />
+      );
+    }
 
-  getInitialState: function () {
-    return { toggled: false };
-  },
+    const Example = UIExplorerList.Modules[state.key];
+    if (Example) {
+      const Component = UIExplorerExampleList.makeRenderable(Example);
+      return (
+        <View style={styles.exampleContainer}>
+          <Component />
+        </View>
+      );
+    }
+    return null;
+  }
+}
 
-  _onPressButton: function() {
-    this.setState({ toggled: !this.state.toggled });
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
   },
-
-  render: function() {
-    var viewStyle = this.state.toggled ? styles.bigContainer : styles.smallContainer;
-
-    return (
-      <TouchableHighlight onPress={this._onPressButton}>
-        <View style={viewStyle}>
-          <View style={styles.center}>
-            <Text style={styles.whiteText}>
-              React Native Button
-            </Text>
-          </View>
-        </View>
-      </TouchableHighlight>
-    );
+  exampleContainer: {
+    flex: 1,
+    paddingTop: 60,
   },
 });
 
-AppRegistry.registerComponent('SetPropertiesExampleApp', () => SetPropertiesExampleApp);
-AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => RootViewSizeFlexibilityExampleApp);
+AppRegistry.registerComponent('SetPropertiesExampleApp', () => require('./SetPropertiesExampleApp'));
+AppRegistry.registerComponent('RootViewSizeFlexibilityExampleApp', () => require('./RootViewSizeFlexibilityExampleApp'));
 AppRegistry.registerComponent('UIExplorerApp', () => UIExplorerApp);
-UIExplorerList.registerComponents();
+
+// Register suitable examples for snapshot tests
+UIExplorerList.ComponentExamples.concat(UIExplorerList.APIExamples).forEach((Example: UIExplorerExample) => {
+  const ExampleModule = Example.module;
+  if (ExampleModule.displayName) {
+    var Snapshotter = React.createClass({
+      render: function() {
+        var Renderable = UIExplorerExampleList.makeRenderable(ExampleModule);
+        return (
+          <SnapshotViewIOS>
+            <Renderable />
+          </SnapshotViewIOS>
+        );
+      },
+    });
+    AppRegistry.registerComponent(ExampleModule.displayName, () => Snapshotter);
+  }
+});
 
 module.exports = UIExplorerApp;
diff --git a/Examples/UIExplorer/UIExplorerExampleList.js b/Examples/UIExplorer/UIExplorerExampleList.js
new file mode 100644
index 000000000000..07a07f381f4f
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerExampleList.js
@@ -0,0 +1,218 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const UIExplorerActions = require('./UIExplorerActions');
+const {
+  ListView,
+  NavigationExperimental,
+  StyleSheet,
+  Text,
+  TextInput,
+  TouchableHighlight,
+  View,
+} = React;
+const createExamplePage = require('./createExamplePage');
+const {
+  Container: NavigationContainer,
+} = NavigationExperimental;
+
+import type {
+  UIExplorerExample,
+} from './UIExplorerList.ios'
+
+const ds = new ListView.DataSource({
+  rowHasChanged: (r1, r2) => r1 !== r2,
+  sectionHeaderHasChanged: (h1, h2) => h1 !== h2,
+});
+
+class UIExplorerExampleList extends React.Component {
+  constuctor(props: {
+    disableTitleRow: ?boolean,
+    onNavigate: Function,
+    filter: ?string,
+    list: {
+      ComponentExamples: Array<UIExplorerExample>,
+      APIExamples: Array<UIExplorerExample>,
+    },
+    style: ?any,
+  }) {
+
+  }
+  render(): ?ReactElement {
+    const filterText = this.props.filter || '';
+    const filterRegex = new RegExp(String(filterText), 'i');
+    const filter = (example) => filterRegex.test(example.module.title);
+
+    const dataSource = ds.cloneWithRowsAndSections({
+      components: this.props.list.ComponentExamples.filter(filter),
+      apis: this.props.list.APIExamples.filter(filter),
+    });
+    return (
+      <View style={[styles.listContainer, this.props.style]}>
+        {this._renderTitleRow()}
+        {this._renderTextInput()}
+        <ListView
+          style={styles.list}
+          dataSource={dataSource}
+          renderRow={this._renderExampleRow.bind(this)}
+          renderSectionHeader={this._renderSectionHeader}
+          keyboardShouldPersistTaps={true}
+          automaticallyAdjustContentInsets={false}
+          keyboardDismissMode="on-drag"
+        />
+      </View>
+    );
+  }
+
+  _renderTitleRow(): ?ReactElement {
+    if (!this.props.displayTitleRow) {
+      return null;
+    }
+    return this._renderRow(
+      'UIExplorer',
+      'React Native Examples',
+      'home_key',
+      () => {
+        this.props.onNavigate(
+          UIExplorerActions.ExampleListWithFilter('')
+        );
+      }
+    );
+  }
+
+  _renderTextInput(): ?ReactElement {
+    if (this.props.disableSearch) {
+      return null;
+    }
+    return (
+      <View style={styles.searchRow}>
+        <TextInput
+          autoCapitalize="none"
+          autoCorrect={false}
+          clearButtonMode="always"
+          onChangeText={text => {
+            this.props.onNavigate(UIExplorerActions.ExampleListWithFilter(text));
+          }}
+          placeholder="Search..."
+          style={[styles.searchTextInput, this.props.searchTextInputStyle]}
+          testID="explorer_search"
+          value={this.props.filter}
+        />
+      </View>
+    );
+  }
+
+  _renderSectionHeader(data: any, section: string): ?ReactElement {
+    return (
+      <Text style={styles.sectionHeader}>
+        {section.toUpperCase()}
+      </Text>
+    );
+  }
+
+  _renderExampleRow(example: {key: string, module: Object}): ?ReactElement {
+    return this._renderRow(
+      example.module.title,
+      example.module.description,
+      example.key,
+      () => this._handleRowPress(example.key)
+    );
+  }
+
+  _renderRow(title: string, description: string, key: ?string, handler: ?Function): ?ReactElement {
+    return (
+      <View key={key || title}>
+        <TouchableHighlight onPress={handler}>
+          <View style={styles.row}>
+            <Text style={styles.rowTitleText}>
+              {title}
+            </Text>
+            <Text style={styles.rowDetailText}>
+              {description}
+            </Text>
+          </View>
+        </TouchableHighlight>
+        <View style={styles.separator} />
+      </View>
+    );
+  }
+
+  _handleRowPress(exampleKey: string): void {
+    this.props.onNavigate(UIExplorerActions.ExampleAction(exampleKey))
+  }
+}
+
+function makeRenderable(example: any): ReactClass<any, any, any> {
+  return example.examples ?
+    createExamplePage(null, example) :
+    example;
+}
+
+UIExplorerExampleList = NavigationContainer.create(UIExplorerExampleList);
+UIExplorerExampleList.makeRenderable = makeRenderable;
+
+var styles = StyleSheet.create({
+  listContainer: {
+    flex: 1,
+  },
+  list: {
+    backgroundColor: '#eeeeee',
+  },
+  sectionHeader: {
+    padding: 5,
+    fontWeight: '500',
+    fontSize: 11,
+  },
+  group: {
+    backgroundColor: 'white',
+  },
+  row: {
+    backgroundColor: 'white',
+    justifyContent: 'center',
+    paddingHorizontal: 15,
+    paddingVertical: 8,
+  },
+  separator: {
+    height: StyleSheet.hairlineWidth,
+    backgroundColor: '#bbbbbb',
+    marginLeft: 15,
+  },
+  rowTitleText: {
+    fontSize: 17,
+    fontWeight: '500',
+  },
+  rowDetailText: {
+    fontSize: 15,
+    color: '#888888',
+    lineHeight: 20,
+  },
+  searchRow: {
+    backgroundColor: '#eeeeee',
+    padding: 10,
+  },
+  searchTextInput: {
+    backgroundColor: 'white',
+    borderColor: '#cccccc',
+    borderRadius: 3,
+    borderWidth: 1,
+    paddingLeft: 8,
+    height: 35,
+  },
+});
+
+module.exports = UIExplorerExampleList;
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m
new file mode 100644
index 000000000000..0f34cfb26e54
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTLoggingTests.m
@@ -0,0 +1,109 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <XCTest/XCTest.h>
+
+#import "RCTAssert.h"
+#import "RCTLog.h"
+#import "RCTBridge.h"
+
+@interface RCTLoggingTests : XCTestCase
+
+@end
+
+@implementation RCTLoggingTests
+{
+  RCTBridge *_bridge;
+
+  dispatch_semaphore_t _logSem;
+  RCTLogLevel _lastLogLevel;
+  RCTLogSource _lastLogSource;
+  NSString *_lastLogMessage;
+}
+
+- (void)setUp
+{
+#if RUNNING_ON_CI
+  NSURL *scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
+  RCTAssert(scriptURL != nil, @"Could not locate main.jsBundle");
+#else
+  NSString *app = @"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp";
+  NSURL *scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.bundle?platform=ios&dev=true", app]];
+#endif
+
+  _bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil];
+  NSDate *date = [NSDate dateWithTimeIntervalSinceNow:5];
+  while (date.timeIntervalSinceNow > 0 && _bridge.loading) {
+    [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+  }
+  XCTAssertFalse(_bridge.loading);
+
+  _logSem = dispatch_semaphore_create(0);
+  RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
+    if (source == RCTLogSourceJavaScript) {
+      _lastLogLevel = level;
+      _lastLogSource = source;
+      _lastLogMessage = message;
+      dispatch_semaphore_signal(_logSem);
+    }
+  });
+}
+
+- (void)tearDown
+{
+  [_bridge invalidate];
+  _bridge = nil;
+
+  RCTSetLogFunction(RCTDefaultLogFunction);
+}
+
+- (void)testLogging
+{
+  [_bridge enqueueJSCall:@"LoggingTestModule.logToConsole" args:@[@"Invoking console.log"]];
+  dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
+
+  XCTAssertEqual(_lastLogLevel, RCTLogLevelInfo);
+  XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
+  XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.log");
+
+  [_bridge enqueueJSCall:@"LoggingTestModule.warning" args:@[@"Generating warning"]];
+  dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
+
+  XCTAssertEqual(_lastLogLevel, RCTLogLevelWarning);
+  XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
+  XCTAssertEqualObjects(_lastLogMessage, @"Warning: Generating warning");
+
+  [_bridge enqueueJSCall:@"LoggingTestModule.invariant" args:@[@"Invariant failed"]];
+  dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
+
+  XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
+  XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
+  XCTAssertEqualObjects(_lastLogMessage, @"Invariant failed");
+
+  [_bridge enqueueJSCall:@"LoggingTestModule.logErrorToConsole" args:@[@"Invoking console.error"]];
+  dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
+
+  XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
+  XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
+  XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.error");
+
+  [_bridge enqueueJSCall:@"LoggingTestModule.throwError" args:@[@"Throwing an error"]];
+  dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
+
+  XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
+  XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
+  XCTAssertEqualObjects(_lastLogMessage, @"Throwing an error");
+}
+
+@end
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m
new file mode 100644
index 000000000000..9e665b4e12d3
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTRootViewIntegrationTests.m
@@ -0,0 +1,172 @@
+/**
+ * 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.
+ */
+
+//vs
+
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <UIKit/UIKit.h>
+#import <XCTest/XCTest.h>
+
+#import "RCTAssert.h"
+
+#import "RCTEventDispatcher.h"
+#import "RCTRootView.h"
+#import "RCTRootViewDelegate.h"
+
+#import <RCTTest/RCTTestRunner.h>
+
+#define RCT_TEST_DATA_CONFIGURATION_BLOCK(appName, testType, input, block) \
+- (void)test##appName##_##testType##_##input                               \
+{                                                                          \
+  [_runner runTest:_cmd                                                    \
+            module:@#appName                                               \
+      initialProps:@{@#input:@YES}                                         \
+configurationBlock:block];                                                 \
+}
+
+#define RCT_TEST_CONFIGURATION_BLOCK(appName, block)  \
+- (void)test##appName                                 \
+{                                                     \
+  [_runner runTest:_cmd                               \
+            module:@#appName                          \
+      initialProps:nil                                \
+configurationBlock:block];                            \
+}
+
+#define RCTNone   RCTRootViewSizeFlexibilityNone
+#define RCTHeight RCTRootViewSizeFlexibilityHeight
+#define RCTWidth  RCTRootViewSizeFlexibilityWidth
+#define RCTBoth   RCTRootViewSizeFlexibilityWidthAndHeight
+
+typedef void (^ControlBlock)(RCTRootView*);
+
+@interface SizeFlexibilityTestDelegate : NSObject<RCTRootViewDelegate>
+@end
+
+@implementation SizeFlexibilityTestDelegate
+
+- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
+{
+  [rootView.bridge.eventDispatcher sendAppEventWithName:@"rootViewDidChangeIntrinsicSize"
+                                                   body:@{@"width": @(rootView.intrinsicSize.width),
+                                                          @"height": @(rootView.intrinsicSize.height)}];
+}
+
+@end
+
+static SizeFlexibilityTestDelegate *sizeFlexibilityDelegate()
+{
+  static SizeFlexibilityTestDelegate *delegate;
+  if (delegate == nil) {
+    delegate = [SizeFlexibilityTestDelegate new];
+  }
+
+  return delegate;
+}
+
+static ControlBlock simpleSizeFlexibilityBlock(RCTRootViewSizeFlexibility sizeFlexibility)
+{
+  return ^(RCTRootView *rootView){
+    rootView.delegate = sizeFlexibilityDelegate();
+    rootView.sizeFlexibility = sizeFlexibility;
+  };
+}
+
+static ControlBlock multipleSizeFlexibilityUpdatesBlock(RCTRootViewSizeFlexibility finalSizeFlexibility)
+{
+  return ^(RCTRootView *rootView){
+
+    NSInteger arr[4] = {RCTNone,
+                        RCTHeight,
+                        RCTWidth,
+                        RCTBoth};
+
+    rootView.delegate = sizeFlexibilityDelegate();
+
+    for (int i = 0; i < 4; ++i) {
+      if (arr[i] != finalSizeFlexibility) {
+        rootView.sizeFlexibility = arr[i];
+      }
+    }
+
+    rootView.sizeFlexibility = finalSizeFlexibility;
+  };
+}
+
+static ControlBlock reactContentSizeUpdateBlock(RCTRootViewSizeFlexibility sizeFlexibility)
+{
+  return ^(RCTRootView *rootView){
+    rootView.delegate = sizeFlexibilityDelegate();
+    rootView.sizeFlexibility = sizeFlexibility;
+  };
+}
+
+static ControlBlock propertiesUpdateBlock()
+{
+  return ^(RCTRootView *rootView){
+    rootView.appProperties = @{@"markTestPassed":@YES};
+  };
+}
+
+@interface RCTRootViewIntegrationTests : XCTestCase
+
+@end
+
+@implementation RCTRootViewIntegrationTests
+{
+  RCTTestRunner *_runner;
+}
+
+- (void)setUp
+{
+#if __LP64__
+  RCTAssert(NO, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
+#endif
+
+  NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion;
+  RCTAssert((version.majorVersion == 8 && version.minorVersion >= 3) || version.majorVersion >= 9, @"Tests should be run on iOS 8.3+, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
+  _runner = RCTInitRunnerForApp(@"IntegrationTests/RCTRootViewIntegrationTestApp", nil);
+}
+
+#pragma mark Logic Tests
+
+// This list should be kept in sync with RCTRootViewIntegrationTestApp.js
+
+// Simple size flexibility tests - test if the content is measured properly
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, none, simpleSizeFlexibilityBlock(RCTNone));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, height, simpleSizeFlexibilityBlock(RCTHeight));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, width, simpleSizeFlexibilityBlock(RCTWidth));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, SingleUpdate, both, simpleSizeFlexibilityBlock(RCTBoth));
+
+// Consider multiple size flexibility updates in a row. Test if the view's flexibility mode eventually is set to the expected value
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, none, multipleSizeFlexibilityUpdatesBlock(RCTNone));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, height, multipleSizeFlexibilityUpdatesBlock(RCTHeight));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, width, multipleSizeFlexibilityUpdatesBlock(RCTWidth));
+RCT_TEST_DATA_CONFIGURATION_BLOCK(SizeFlexibilityUpdateTest, MultipleUpdates, both, multipleSizeFlexibilityUpdatesBlock(RCTBoth));
+
+// Test if the 'rootViewDidChangeIntrinsicSize' delegate method is called after the RN app decides internally to resize
+RCT_TEST_CONFIGURATION_BLOCK(ReactContentSizeUpdateTest, reactContentSizeUpdateBlock(RCTBoth))
+
+// Test if setting 'appProperties' property updates the RN app
+RCT_TEST_CONFIGURATION_BLOCK(PropertiesUpdateTest, propertiesUpdateBlock())
+
+@end
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png
index 5786896ee949..2c6573453937 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png
index c38d68b526b0..45b9547a82be 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png
index bfd5872677fd..f57db959a03a 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png
index 43718fb495b2..71d984921725 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png
index 8ec70eb0ce8e..bf417eb6f461 100644
Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
index 2f3639cdc08d..81c11503ca2a 100644
--- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
+++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m
@@ -52,7 +52,7 @@ - (void)test##name                      \
 RCT_TEST(LayoutExample)
 RCT_TEST(TextExample)
 RCT_TEST(SwitchExample)
-RCT_TEST(SliderExample)
+//RCT_TEST(SliderExample) // Disabled: #8985988
 //RCT_TEST(TabBarExample) // Disabled: #8985988
 
 - (void)testZZZNotInRecordMode
diff --git a/Examples/UIExplorer/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js
index 5a370a01ce06..68be333e5109 100644
--- a/Examples/UIExplorer/UIExplorerList.android.js
+++ b/Examples/UIExplorer/UIExplorerList.android.js
@@ -15,88 +15,171 @@
  */
 'use strict';
 
-var React = require('react-native');
-var {
-  StyleSheet,
-  View,
-} = React;
-var UIExplorerListBase = require('./UIExplorerListBase');
+export type UIExplorerExample = {
+  key: string;
+  module: React.Component;
+};
 
-var COMPONENTS = [
-  require('./ImageExample'),
-  require('./ProgressBarAndroidExample'),
-  require('./ScrollViewSimpleExample'),
-  require('./SwitchAndroidExample'),
-  require('./TextExample.android'),
-  require('./TextInputExample.android'),
-  require('./ToolbarAndroidExample'),
-  require('./TouchableExample'),
-  require('./ViewExample'),
-  require('./ViewPagerAndroidExample.android'),
+var ComponentExamples: Array<UIExplorerExample> = [
+  {
+    key: 'ImageExample',
+    module: require('./ImageExample'),
+  },
+  {
+    key: 'ListViewExample',
+    module: require('./ListViewExample'),
+  },
+  {
+    key: 'PickerAndroidExample',
+    module: require('./PickerAndroidExample'),
+  },
+  {
+    key: 'ProgressBarAndroidExample',
+    module: require('./ProgressBarAndroidExample'),
+  },
+  {
+    key: 'RefreshControlExample',
+    module: require('./RefreshControlExample'),
+  },
+  {
+    key: 'ScrollViewSimpleExample',
+    module: require('./ScrollViewSimpleExample'),
+  },
+  {
+    key: 'StatusBarExample',
+    module: require('./StatusBarExample'),
+  },
+  {
+    key: 'SwitchExample',
+    module: require('./SwitchExample'),
+  },
+  {
+    key: 'TextExample',
+    module: require('./TextExample'),
+  },
+  {
+    key: 'TextInputExample',
+    module: require('./TextInputExample'),
+  },
+  {
+    key: 'ToolbarAndroidExample',
+    module: require('./ToolbarAndroidExample'),
+  },
+  {
+    key: 'TouchableExample',
+    module: require('./TouchableExample'),
+  },
+  {
+    key: 'ViewExample',
+    module: require('./ViewExample'),
+  },
+  {
+    key: 'ViewPagerAndroidExample',
+    module: require('./ViewPagerAndroidExample'),
+  },
+  {
+    key: 'WebViewExample',
+    module: require('./WebViewExample'),
+  },
 ];
 
-var APIS = [
-  require('./AccessibilityAndroidExample.android'),
-  require('./BorderExample'),
-  require('./GeolocationExample'),
-  require('./IntentAndroidExample.android'),
-  require('./LayoutEventsExample'),
-  require('./LayoutExample'),
-  require('./PanResponderExample'),
-  require('./PointerEventsExample'),
-  require('./TimerExample'),
-  require('./ToastAndroidExample.android'),
-  require('./XHRExample'),
+const APIExamples = [
+  {
+    key: 'AccessibilityAndroidExample',
+    module: require('./AccessibilityAndroidExample'),
+  },
+  {
+    key: 'AlertExample',
+    module: require('./AlertExample').AlertExample,
+  },
+  {
+    key: 'AppStateExample',
+    module: require('./AppStateExample'),
+  },
+  {
+    key: 'BorderExample',
+    module: require('./BorderExample'),
+  },
+  {
+    key: 'CameraRollExample',
+    module: require('./CameraRollExample'),
+  },
+  {
+    key: 'ClipboardExample',
+    module: require('./ClipboardExample'),
+  },
+  {
+    key: 'DatePickerAndroidExample',
+    module: require('./DatePickerAndroidExample'),
+  },
+  {
+    key: 'GeolocationExample',
+    module: require('./GeolocationExample'),
+  },
+  {
+    key: 'ImageEditingExample',
+    module: require('./ImageEditingExample'),
+  },
+  {
+    key: 'LayoutEventsExample',
+    module: require('./LayoutEventsExample'),
+  },
+  {
+    key: 'LinkingExample',
+    module: require('./LinkingExample'),
+  },
+  {
+    key: 'LayoutExample',
+    module: require('./LayoutExample'),
+  },
+  {
+    key: 'NavigationExperimentalExample',
+    module: require('./NavigationExperimental/NavigationExperimentalExample'),
+  },
+  {
+    key: 'NetInfoExample',
+    module: require('./NetInfoExample'),
+  },
+  {
+    key: 'PanResponderExample',
+    module: require('./PanResponderExample'),
+  },
+  {
+    key: 'PointerEventsExample',
+    module: require('./PointerEventsExample'),
+  },
+  {
+    key: 'TimePickerAndroidExample',
+    module: require('./TimePickerAndroidExample'),
+  },
+  {
+    key: 'TimerExample',
+    module: require('./TimerExample'),
+  },
+  {
+    key: 'ToastAndroidExample',
+    module: require('./ToastAndroidExample'),
+  },
+  {
+    key: 'VibrationExample',
+    module: require('./VibrationExample'),
+  },
+  {
+    key: 'XHRExample',
+    module: require('./XHRExample'),
+  },
 ];
 
-type Props = {
-  onSelectExample: Function,
-  isInDrawer: bool,
-};
-
-class UIExplorerList extends React.Component {
-  props: Props;
-
-  render() {
-    return (
-      <UIExplorerListBase
-        components={COMPONENTS}
-        apis={APIS}
-        searchText=""
-        renderAdditionalView={this.renderAdditionalView.bind(this)}
-        onPressRow={this.onPressRow.bind(this)}
-      />
-    );
-  }
+const Modules = {};
 
-  renderAdditionalView(renderRow, renderTextInput): React.Component {
-    if (this.props.isInDrawer) {
-      var homePage = renderRow({
-        title: 'UIExplorer',
-        description: 'List of examples',
-      }, -1);
-      return (
-        <View>
-          {homePage}
-        </View>
-      );
-    }
-    return renderTextInput(styles.searchTextInput);
-  }
-
-  onPressRow(example: any) {
-    var Component = UIExplorerListBase.makeRenderable(example);
-    this.props.onSelectExample({
-      title: Component.title,
-      component: Component,
-    });
-  }
-}
-
-var styles = StyleSheet.create({
-  searchTextInput: {
-    padding: 2,
-  },
+APIExamples.concat(ComponentExamples).forEach(Example => {
+  Modules[Example.key] = Example.module;
 });
 
+const UIExplorerList = {
+  APIExamples,
+  ComponentExamples,
+  Modules,
+};
+
 module.exports = UIExplorerList;
diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js
index 940bd79ba42e..8d770d1f40be 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -15,144 +15,255 @@
  */
 'use strict';
 
-var React = require('react-native');
-var {
-  AppRegistry,
-  Settings,
-  SnapshotViewIOS,
-  StyleSheet,
-} = React;
-
-import type { NavigationContext } from 'NavigationContext';
-
-var UIExplorerListBase = require('./UIExplorerListBase');
-
-var COMPONENTS = [
-  require('./ActivityIndicatorIOSExample'),
-  require('./DatePickerIOSExample'),
-  require('./ImageExample'),
-  require('./LayoutEventsExample'),
-  require('./ListViewExample'),
-  require('./ListViewGridLayoutExample'),
-  require('./ListViewPagingExample'),
-  require('./MapViewExample'),
-  require('./ModalExample'),
-  require('./Navigator/NavigatorExample'),
-  require('./NavigatorIOSColorsExample'),
-  require('./NavigatorIOSExample'),
-  require('./PickerIOSExample'),
-  require('./ProgressViewIOSExample'),
-  require('./ScrollViewExample'),
-  require('./SegmentedControlIOSExample'),
-  require('./SliderIOSExample'),
-  require('./SwitchIOSExample'),
-  require('./TabBarIOSExample'),
-  require('./TextExample.ios'),
-  require('./TextInputExample.ios'),
-  require('./TouchableExample'),
-  require('./TransparentHitTestExample'),
-  require('./ViewExample'),
-  require('./WebViewExample'),
-];
+export type UIExplorerExample = {
+  key: string;
+  module: Object;
+};
 
-var APIS = [
-  require('./AccessibilityIOSExample'),
-  require('./ActionSheetIOSExample'),
-  require('./AdSupportIOSExample'),
-  require('./AlertIOSExample'),
-  require('./AnimatedExample'),
-  require('./AnimatedGratuitousApp/AnExApp'),
-  require('./AppStateIOSExample'),
-  require('./AsyncStorageExample'),
-  require('./BorderExample'),
-  require('./CameraRollExample.ios'),
-  require('./GeolocationExample'),
-  require('./LayoutExample'),
-  require('./NetInfoExample'),
-  require('./PanResponderExample'),
-  require('./PointerEventsExample'),
-  require('./PushNotificationIOSExample'),
-  require('./RCTRootViewIOSExample'),
-  require('./StatusBarIOSExample'),
-  require('./TimerExample'),
-  require('./VibrationIOSExample'),
-  require('./XHRExample.ios'),
-  require('./ImageEditingExample'),
+var ComponentExamples: Array<UIExplorerExample> = [
+  {
+    key: 'ActivityIndicatorIOSExample',
+    module: require('./ActivityIndicatorIOSExample'),
+  },
+  {
+    key: 'DatePickerIOSExample',
+    module: require('./DatePickerIOSExample'),
+  },
+  {
+    key: 'ImageExample',
+    module: require('./ImageExample'),
+  },
+  {
+    key: 'LayoutEventsExample',
+    module: require('./LayoutEventsExample'),
+  },
+  {
+    key: 'ListViewExample',
+    module: require('./ListViewExample'),
+  },
+  {
+    key: 'ListViewGridLayoutExample',
+    module: require('./ListViewGridLayoutExample'),
+  },
+  {
+    key: 'ListViewPagingExample',
+    module: require('./ListViewPagingExample'),
+  },
+  {
+    key: 'MapViewExample',
+    module: require('./MapViewExample'),
+  },
+  {
+    key: 'ModalExample',
+    module: require('./ModalExample'),
+  },
+  {
+    key: 'NavigatorExample',
+    module: require('./Navigator/NavigatorExample'),
+  },
+  {
+    key: 'NavigatorIOSColorsExample',
+    module: require('./NavigatorIOSColorsExample'),
+  },
+  {
+    key: 'NavigatorIOSExample',
+    module: require('./NavigatorIOSExample'),
+  },
+  {
+    key: 'PickerIOSExample',
+    module: require('./PickerIOSExample'),
+  },
+  {
+    key: 'ProgressViewIOSExample',
+    module: require('./ProgressViewIOSExample'),
+  },
+  {
+    key: 'RefreshControlExample',
+    module: require('./RefreshControlExample'),
+  },
+  {
+    key: 'ScrollViewExample',
+    module: require('./ScrollViewExample'),
+  },
+  {
+    key: 'SegmentedControlIOSExample',
+    module: require('./SegmentedControlIOSExample'),
+  },
+  {
+    key: 'SliderIOSExample',
+    module: require('./SliderIOSExample'),
+  },
+  {
+    key: 'StatusBarExample',
+    module: require('./StatusBarExample'),
+  },
+  {
+    key: 'SwitchExample',
+    module: require('./SwitchExample'),
+  },
+  {
+    key: 'TabBarIOSExample',
+    module: require('./TabBarIOSExample'),
+  },
+  {
+    key: 'TextExample',
+    module: require('./TextExample.ios'),
+  },
+  {
+    key: 'TextInputExample',
+    module: require('./TextInputExample.ios'),
+  },
+  {
+    key: 'TouchableExample',
+    module: require('./TouchableExample'),
+  },
+  {
+    key: 'TransparentHitTestExample',
+    module: require('./TransparentHitTestExample'),
+  },
+  {
+    key: 'ViewExample',
+    module: require('./ViewExample'),
+  },
+  {
+    key: 'WebViewExample',
+    module: require('./WebViewExample'),
+  },
 ];
 
-type Props = {
-  navigator: {
-    navigationContext: NavigationContext,
-    push: (route: {title: string, component: ReactClass<any,any,any>}) => void,
+var APIExamples: Array<UIExplorerExample> = [
+  {
+    key: 'AccessibilityIOSExample',
+    module: require('./AccessibilityIOSExample'),
   },
-  onExternalExampleRequested: Function,
-};
-
-class UIExplorerList extends React.Component {
-  props: Props;
-
-  render() {
-    return (
-      <UIExplorerListBase
-        components={COMPONENTS}
-        apis={APIS}
-        searchText={Settings.get('searchText')}
-        renderAdditionalView={this.renderAdditionalView.bind(this)}
-        search={this.search.bind(this)}
-        onPressRow={this.onPressRow.bind(this)}
-      />
-    );
-  }
-
-  renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component {
-    return renderTextInput(styles.searchTextInput);
-  }
-
-  search(text: mixed) {
-    Settings.set({searchText: text});
-  }
-
-  _openExample(example: any) {
-    if (example.external) {
-      this.props.onExternalExampleRequested(example);
-      return;
-    }
-
-    var Component = UIExplorerListBase.makeRenderable(example);
-    this.props.navigator.push({
-      title: Component.title,
-      component: Component,
-    });
-  }
-
-  onPressRow(example: any) {
-    this._openExample(example);
-  }
+  {
+    key: 'ActionSheetIOSExample',
+    module: require('./ActionSheetIOSExample'),
+  },
+  {
+    key: 'AdSupportIOSExample',
+    module: require('./AdSupportIOSExample'),
+  },
+  {
+    key: 'AlertIOSExample',
+    module: require('./AlertIOSExample'),
+  },
+  {
+    key: 'AnimatedExample',
+    module: require('./AnimatedExample'),
+  },
+  {
+    key: 'AnExApp',
+    module: require('./AnimatedGratuitousApp/AnExApp'),
+  },
+  {
+    key: 'AppStateIOSExample',
+    module: require('./AppStateIOSExample'),
+  },
+  {
+    key: 'AppStateExample',
+    module: require('./AppStateExample'),
+  },
+  {
+    key: 'AsyncStorageExample',
+    module: require('./AsyncStorageExample'),
+  },
+  {
+    key: 'BorderExample',
+    module: require('./BorderExample'),
+  },
+  {
+    key: 'BoxShadowExample',
+    module: require('./BoxShadowExample'),
+  },
+  {
+    key: 'CameraRollExample',
+    module: require('./CameraRollExample'),
+  },
+  {
+    key: 'ClipboardExample',
+    module: require('./ClipboardExample'),
+  },
+  {
+    key: 'GeolocationExample',
+    module: require('./GeolocationExample'),
+  },
+  {
+    key: 'ImageEditingExample',
+    module: require('./ImageEditingExample'),
+  },
+  {
+    key: 'LayoutExample',
+    module: require('./LayoutExample'),
+  },
+  {
+    key: 'LinkingExample',
+    module: require('./LinkingExample'),
+  },
+  {
+    key: 'NavigationExperimentalExample',
+    module: require('./NavigationExperimental/NavigationExperimentalExample'),
+  },
+  {
+    key: 'NavigationExperimentalLegacyNavigatorExample',
+    module: require('./NavigationExperimental/LegacyNavigator/LegacyNavigatorExample'),
+  },
+  {
+    key: 'NetInfoExample',
+    module: require('./NetInfoExample'),
+  },
+  {
+    key: 'PanResponderExample',
+    module: require('./PanResponderExample'),
+  },
+  {
+    key: 'PointerEventsExample',
+    module: require('./PointerEventsExample'),
+  },
+  {
+    key: 'PushNotificationIOSExample',
+    module: require('./PushNotificationIOSExample'),
+  },
+  {
+    key: 'RCTRootViewIOSExample',
+    module: require('./RCTRootViewIOSExample'),
+  },
+  {
+    key: 'SnapshotExample',
+    module: require('./SnapshotExample'),
+  },
+  {
+    key: 'StatusBarIOSExample',
+    module: require('./StatusBarIOSExample'),
+  },
+  {
+    key: 'TimerExample',
+    module: require('./TimerExample'),
+  },
+  {
+    key: 'TransformExample',
+    module: require('./TransformExample'),
+  },
+  {
+    key: 'VibrationExample',
+    module: require('./VibrationExample'),
+  },
+  {
+    key: 'XHRExample',
+    module: require('./XHRExample.ios'),
+  },
+];
 
-  // Register suitable examples for snapshot tests
-  static registerComponents() {
-    COMPONENTS.concat(APIS).forEach((Example) => {
-      if (Example.displayName) {
-        var Snapshotter = React.createClass({
-          render: function() {
-            var Renderable = UIExplorerListBase.makeRenderable(Example);
-            return (
-              <SnapshotViewIOS>
-                <Renderable />
-              </SnapshotViewIOS>
-            );
-          },
-        });
-        AppRegistry.registerComponent(Example.displayName, () => Snapshotter);
-      }
-    });
-  }
-}
+const Modules = {};
 
-var styles = StyleSheet.create({
-  searchTextInput: {
-    height: 30,
-  },
+APIExamples.concat(ComponentExamples).forEach(Example => {
+  Modules[Example.key] = Example.module;
 });
 
+const UIExplorerList = {
+  APIExamples,
+  ComponentExamples,
+  Modules,
+};
+
 module.exports = UIExplorerList;
diff --git a/Examples/UIExplorer/UIExplorerListBase.js b/Examples/UIExplorer/UIExplorerListBase.js
deleted file mode 100644
index 4b26a6976dc6..000000000000
--- a/Examples/UIExplorer/UIExplorerListBase.js
+++ /dev/null
@@ -1,195 +0,0 @@
-/**
- * The examples provided by Facebook are for non-commercial testing and
- * evaluation purposes only.
- *
- * Facebook reserves all rights not expressly granted.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
- * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
- * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- *
- * @flow
- */
-'use strict';
-
-var React = require('react-native');
-var {
-  ListView,
-  PixelRatio,
-  StyleSheet,
-  Text,
-  TextInput,
-  TouchableHighlight,
-  View,
-} = React;
-var createExamplePage = require('./createExamplePage');
-
-var ds = new ListView.DataSource({
-  rowHasChanged: (r1, r2) => r1 !== r2,
-  sectionHeaderHasChanged: (h1, h2) => h1 !== h2,
-});
-
-class UIExplorerListBase extends React.Component {
-  constructor(props: any) {
-    super(props);
-    this.state = {
-      dataSource: ds.cloneWithRowsAndSections({
-        components: [],
-        apis: [],
-      }),
-      searchText: this.props.searchText,
-    };
-  }
-
-  componentDidMount(): void {
-    this.search(this.state.searchText);
-  }
-
-  render() {
-    var topView = this.props.renderAdditionalView &&
-      this.props.renderAdditionalView(this.renderRow.bind(this), this.renderTextInput.bind(this));
-
-    return (
-      <View style={styles.listContainer}>
-        {topView}
-        <ListView
-          style={styles.list}
-          dataSource={this.state.dataSource}
-          renderRow={this.renderRow.bind(this)}
-          renderSectionHeader={this._renderSectionHeader}
-          keyboardShouldPersistTaps={true}
-          automaticallyAdjustContentInsets={false}
-          keyboardDismissMode="on-drag"
-        />
-      </View>
-    );
-  }
-
-  renderTextInput(searchTextInputStyle: any) {
-    return (
-      <View style={styles.searchRow}>
-        <TextInput
-          autoCapitalize="none"
-          autoCorrect={false}
-          clearButtonMode="always"
-          onChangeText={this.search.bind(this)}
-          placeholder="Search..."
-          style={[styles.searchTextInput, searchTextInputStyle]}
-          testID="explorer_search"
-          value={this.state.searchText}
-        />
-      </View>
-    );
-  }
-
-  _renderSectionHeader(data: any, section: string) {
-    return (
-      <View style={styles.sectionHeader}>
-        <Text style={styles.sectionHeaderTitle}>
-          {section.toUpperCase()}
-        </Text>
-      </View>
-    );
-  }
-
-  renderRow(example: any, i: number) {
-    return (
-      <View key={i}>
-        <TouchableHighlight onPress={() => this.onPressRow(example)}>
-          <View style={styles.row}>
-            <Text style={styles.rowTitleText}>
-              {example.title}
-            </Text>
-            <Text style={styles.rowDetailText}>
-              {example.description}
-            </Text>
-          </View>
-        </TouchableHighlight>
-        <View style={styles.separator} />
-      </View>
-    );
-  }
-
-  search(text: mixed): void {
-    this.props.search && this.props.search(text);
-
-    var regex = new RegExp(text, 'i');
-    var filter = (component) => regex.test(component.title);
-
-    this.setState({
-      dataSource: ds.cloneWithRowsAndSections({
-        components: this.props.components.filter(filter),
-        apis: this.props.apis.filter(filter),
-      }),
-      searchText: text,
-    });
-  }
-
-  onPressRow(example: any): void {
-    this.props.onPressRow && this.props.onPressRow(example);
-  }
-
-  static makeRenderable(example: any): ReactClass<any, any, any> {
-    return example.examples ?
-      createExamplePage(null, example) :
-      example;
-  }
-}
-
-var styles = StyleSheet.create({
-  listContainer: {
-    flex: 1,
-  },
-  list: {
-    backgroundColor: '#eeeeee',
-  },
-  sectionHeader: {
-    padding: 5,
-  },
-  group: {
-    backgroundColor: 'white',
-  },
-  sectionHeaderTitle: {
-    fontWeight: '500',
-    fontSize: 11,
-  },
-  row: {
-    backgroundColor: 'white',
-    justifyContent: 'center',
-    paddingHorizontal: 15,
-    paddingVertical: 8,
-  },
-  separator: {
-    height: 1 / PixelRatio.get(),
-    backgroundColor: '#bbbbbb',
-    marginLeft: 15,
-  },
-  rowTitleText: {
-    fontSize: 17,
-    fontWeight: '500',
-  },
-  rowDetailText: {
-    fontSize: 15,
-    color: '#888888',
-    lineHeight: 20,
-  },
-  searchRow: {
-    backgroundColor: '#eeeeee',
-    paddingTop: 75,
-    paddingLeft: 10,
-    paddingRight: 10,
-    paddingBottom: 10,
-  },
-  searchTextInput: {
-    backgroundColor: 'white',
-    borderColor: '#cccccc',
-    borderRadius: 3,
-    borderWidth: 1,
-    paddingLeft: 8,
-  },
-});
-
-module.exports = UIExplorerListBase;
diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/UIExplorerNavigationReducer.js
new file mode 100644
index 000000000000..3dee22c2c2eb
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js
@@ -0,0 +1,102 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS?
+const UIExplorerList = require('./UIExplorerList');
+const {
+  NavigationExperimental,
+} = React;
+const {
+  Reducer: NavigationReducer,
+} = NavigationExperimental;
+const StackReducer = NavigationReducer.StackReducer;
+
+import type {NavigationState} from 'NavigationStateUtils';
+
+import type {UIExplorerAction} from './UIExplorerActions';
+
+export type UIExplorerNavigationState = {
+  externalExample: ?string;
+  stack: NavigationState;
+};
+
+const UIExplorerStackReducer = StackReducer({
+  getPushedReducerForAction: (action) => {
+    if (action.type === 'UIExplorerExampleAction' && UIExplorerList.Modules[action.openExample]) {
+      return (state) => state || {key: action.openExample};
+    }
+    return null;
+  },
+  getReducerForState: (initialState) => (state) => state || initialState,
+  initialState: {
+    key: 'UIExplorerMainStack',
+    index: 0,
+    children: [
+      {key: 'AppList'},
+    ],
+  },
+});
+
+function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, action: any): UIExplorerNavigationState {
+  if (!lastState) {
+    return {
+      externalExample: null,
+      stack: UIExplorerStackReducer(null, action),
+    };
+  }
+  if (action.type === 'UIExplorerListWithFilterAction') {
+    return {
+      externalExample: null,
+      stack: {
+        key: 'UIExplorerMainStack',
+        index: 0,
+        children: [
+          {
+            key: 'AppList',
+            filter: action.filter,
+          },
+        ],
+      },
+    };
+  }
+  if (action.type === 'BackAction' && lastState.externalExample) {
+    return {
+      ...lastState,
+      externalExample: null,
+    };
+  }
+  if (action.type === 'UIExplorerExampleAction') {
+    const ExampleModule = UIExplorerList.Modules[action.openExample];
+    if (ExampleModule && ExampleModule.external) {
+      return {
+        ...lastState,
+        externalExample: action.openExample,
+      };
+    }
+  }
+  const newStack = UIExplorerStackReducer(lastState.stack, action);
+  if (newStack !== lastState.stack) {
+    return {
+      externalExample: null,
+      stack: newStack,      
+    }
+  }
+  return lastState;
+}
+
+module.exports = UIExplorerNavigationReducer;
diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/UIExplorerStateTitleMap.js
new file mode 100644
index 000000000000..111d2c49ab21
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerStateTitleMap.js
@@ -0,0 +1,33 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+// $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS?
+const UIExplorerList = require('./UIExplorerList');
+
+import type {NavigationState} from 'NavigationStateUtils';
+
+function StateTitleMap(state: NavigationState): string {
+  if (UIExplorerList.Modules[state.key]) {
+    return UIExplorerList.Modules[state.key].title
+  }
+  if (state.key === 'AppList') {
+    return 'UIExplorer';
+  }
+  return 'Unknown';
+}
+
+module.exports = StateTitleMap;
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
index fcd7961143c3..39c0404e0295 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/LayoutSubviewsOrderingTest.m
@@ -14,7 +14,7 @@ @implementation LayoutSubviewsOrderingTest
 /**
  * This test exists to insure that didLayoutSubviews is always called immediately after layoutSubviews for a VC:View
  * pair. In Catalyst we have multiple levels of ViewController containment, and we rely on this ordering
- * to insure that layoutGuides are set on RKViewControllers before Views further down in the heirarchy have
+ * to insure that layoutGuides are set on RKViewControllers before Views further down in the hierarchy have
  * their layoutSubviews called (and need to use the aforementioned layoutGuides)
  */
 - (void)testLayoutSubviewsOrdering
@@ -63,7 +63,7 @@ - (void)testLayoutSubviewsOrdering
     }
   }] viewDidLayoutSubviews];
 
-  // setup View heirarchy and force layout
+  // setup View hierarchy and force layout
   parentVC.view = parentView;
   childVC.view = childView;
   [parentVC addChildViewController:childVC];
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m
index 4fe7294fb3e9..a52400ffcb3f 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m
@@ -16,24 +16,18 @@
 #import <XCTest/XCTest.h>
 
 #import "RCTBridge.h"
-#import "RCTContextExecutor.h"
+#import "RCTBridge+Private.h"
+#import "RCTJSCExecutor.h"
 #import "RCTModuleMethod.h"
 #import "RCTRootView.h"
 
 #define RUN_RUNLOOP_WHILE(CONDITION) \
-_Pragma("clang diagnostic push") \
-_Pragma("clang diagnostic ignored \"-Wshadow\"") \
-NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \
-while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
-  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
-} \
-_Pragma("clang diagnostic pop")
-
-@interface RCTBridge (RCTAllocationTests)
-
-@property (nonatomic, weak) RCTBridge *batchedBridge;
-
-@end
+{ \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
+  while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
+    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+  } \
+}
 
 @interface RCTJavaScriptContext : NSObject
 
@@ -74,13 +68,40 @@ - (void)invalidate
 @interface RCTAllocationTests : XCTestCase
 @end
 
-@implementation RCTAllocationTests
+@implementation RCTAllocationTests {
+  NSURL *_bundleURL;
+}
+
+- (void)setUp
+{
+  [super setUp];
+
+  NSString *bundleContents =
+  @"var __fbBatchedBridge = {"
+   "  callFunctionReturnFlushedQueue: function() {},"
+   "  invokeCallbackAndReturnFlushedQueue: function() {},"
+   "  flushedQueue: function() {},"
+   "};";
+
+  NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
+  [[NSFileManager defaultManager] createDirectoryAtURL:tempDir withIntermediateDirectories:YES attributes:nil error:NULL];
+
+  _bundleURL = [tempDir URLByAppendingPathComponent:@"rctallocationtests-bundle.js"];
+  [bundleContents writeToURL:_bundleURL atomically:YES encoding:NSUTF8StringEncoding error:NULL];
+}
+
+- (void)tearDown
+{
+  [super tearDown];
+
+  [[NSFileManager defaultManager] removeItemAtURL:_bundleURL error:NULL];
+}
 
 - (void)testBridgeIsDeallocated
 {
   __weak RCTBridge *weakBridge;
   @autoreleasepool {
-    RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:nil
+    RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:_bundleURL
                                                     moduleName:@""
                                              initialProperties:nil
                                                  launchOptions:nil];
@@ -96,7 +117,7 @@ - (void)testModulesAreInvalidated
 {
   AllocationTestModule *module = [AllocationTestModule new];
   @autoreleasepool {
-    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil
+    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
                                               moduleProvider:^{
                                                 return @[module];
                                               }
@@ -114,11 +135,11 @@ - (void)testModulesAreDeallocated
   __weak AllocationTestModule *weakModule;
   @autoreleasepool {
     AllocationTestModule *module = [AllocationTestModule new];
-    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil
-                                moduleProvider:^{
-                                  return @[module];
-                                }
-                                 launchOptions:nil];
+    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
+                                              moduleProvider:^{
+                                                return @[module];
+                                              }
+                                               launchOptions:nil];
     weakModule = module;
     XCTAssertNotNil(weakModule, @"AllocationTestModule should have been created");
     (void)bridge;
@@ -132,7 +153,7 @@ - (void)testModuleMethodsAreDeallocated
 {
   __weak RCTModuleMethod *weakMethod;
   @autoreleasepool {
-    __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]];
+    __autoreleasing RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:@"test:(NSString *)a :(nonnull NSNumber *)b :(RCTResponseSenderBlock)c :(RCTResponseErrorBlock)d" JSMethodName:@"" moduleClass:[AllocationTestModule class]];
     weakMethod = method;
     XCTAssertNotNil(method, @"RCTModuleMethod should have been created");
   }
@@ -145,7 +166,7 @@ - (void)testJavaScriptExecutorIsDeallocated
 {
   __weak id<RCTJavaScriptExecutor> weakExecutor;
   @autoreleasepool {
-    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil
+    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
                                               moduleProvider:nil
                                                launchOptions:nil];
     weakExecutor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
@@ -161,11 +182,11 @@ - (void)testJavaScriptContextIsDeallocated
 {
   __weak id weakContext;
   @autoreleasepool {
-    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil
+    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
                                               moduleProvider:nil
                                                launchOptions:nil];
     id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
-    RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]));
+    RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"_context"]));
     XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created");
     (void)bridge;
   }
@@ -176,7 +197,7 @@ - (void)testJavaScriptContextIsDeallocated
 
 - (void)testContentViewIsInvalidated
 {
-  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil
+  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL
                                             moduleProvider:nil
                                              launchOptions:nil];
   __weak UIView *rootContentView;
@@ -195,7 +216,7 @@ - (void)testUnderlyingBridgeIsDeallocated
   RCTBridge *bridge;
   __weak id batchedBridge;
   @autoreleasepool {
-    bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil];
+    bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil];
     batchedBridge = bridge.batchedBridge;
     XCTAssertTrue([batchedBridge isValid], @"RCTBatchedBridge should be valid");
     [bridge reload];
@@ -205,6 +226,15 @@ - (void)testUnderlyingBridgeIsDeallocated
 
   XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated");
   XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated");
+
+  // Wait to complete the test until the new batchedbridge is also deallocated
+  @autoreleasepool {
+    batchedBridge = bridge.batchedBridge;
+    bridge = nil;
+  }
+
+  RUN_RUNLOOP_WHILE(batchedBridge != nil);
+  XCTAssertNil(batchedBridge);
 }
 
 @end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
index c201f5799389..08a1a1e37fec 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
@@ -16,18 +16,22 @@
 #import <XCTest/XCTest.h>
 
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTBridgeModule.h"
 #import "RCTJavaScriptExecutor.h"
 #import "RCTUtils.h"
 
-@interface RCTBridge (Testing)
-
-@property (nonatomic, strong, readonly) RCTBridge *batchedBridge;
-
-- (void)handleBuffer:(id)buffer;
-- (void)setUp;
-
-@end
+#define RUN_RUNLOOP_WHILE(CONDITION) \
+{ \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
+  while ((CONDITION)) { \
+    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+    if ([timeout timeIntervalSinceNow] <= 0) { \
+      XCTFail(@"Runloop timed out before condition was met"); \
+      break; \
+    } \
+  } \
+}
 
 @interface TestExecutor : NSObject <RCTJavaScriptExecutor>
 
@@ -37,6 +41,8 @@ @interface TestExecutor : NSObject <RCTJavaScriptExecutor>
 
 @implementation TestExecutor
 
+@synthesize valid = _valid;
+
 RCT_EXPORT_MODULE()
 
 - (void)setUp {}
@@ -51,13 +57,25 @@ - (instancetype)init
 
 - (BOOL)isValid
 {
-  return YES;
+  return _valid;
+}
+
+- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
+{
+  onComplete(nil, nil);
+}
+
+- (void)callFunctionOnModule:(__unused NSString *)module
+                      method:(__unused NSString *)method
+                   arguments:(__unused NSArray *)args
+                    callback:(RCTJavaScriptCallback)onComplete
+{
+  onComplete(nil, nil);
 }
 
-- (void)executeJSCall:(__unused NSString *)name
-               method:(__unused NSString *)method
-            arguments:(__unused NSArray *)arguments
-             callback:(RCTJavaScriptCallback)onComplete
+- (void)invokeCallbackID:(__unused NSNumber *)cbID
+               arguments:(__unused NSArray *)args
+                callback:(RCTJavaScriptCallback)onComplete
 {
   onComplete(nil, nil);
 }
@@ -82,13 +100,18 @@ - (void)injectJSONText:(NSString *)script
   onComplete(nil);
 }
 
-- (void)invalidate {}
+- (void)invalidate
+{
+  _valid = NO;
+}
 
 @end
 
 @interface RCTBridgeTests : XCTestCase <RCTBridgeModule>
 {
   RCTBridge *_bridge;
+  __weak TestExecutor *_jsExecutor;
+
   BOOL _testMethodCalled;
 }
 @end
@@ -108,33 +131,32 @@ - (void)setUp
                                    launchOptions:nil];
 
   _bridge.executorClass = [TestExecutor class];
+
   // Force to recreate the executor with the new class
   // - reload: doesn't work here since bridge hasn't loaded yet.
   [_bridge invalidate];
   [_bridge setUp];
+
+  _jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
+  XCTAssertNotNil(_jsExecutor);
 }
 
 - (void)tearDown
 {
   [super tearDown];
+
+  _testMethodCalled = NO;
+
   [_bridge invalidate];
+  RUN_RUNLOOP_WHILE(_jsExecutor.isValid);
+  _bridge = nil;
 }
 
-#define RUN_RUNLOOP_WHILE(CONDITION) \
-_Pragma("clang diagnostic push") \
-_Pragma("clang diagnostic ignored \"-Wshadow\"") \
-NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \
-while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
-  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
-} \
-_Pragma("clang diagnostic pop")
-
 - (void)testHookRegistration
 {
-  TestExecutor *executor =  [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"];
-
   NSString *injectedStuff;
-  RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
+  RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"]));
+  XCTAssertNotNil(injectedStuff);
 
   __block NSNumber *testModuleID = nil;
   __block NSDictionary<NSString *, id> *testConstants = nil;
@@ -159,27 +181,27 @@ - (void)testHookRegistration
 
 - (void)testCallNativeMethod
 {
-  TestExecutor *executor =  [_bridge.batchedBridge valueForKey:@"_javaScriptExecutor"];
-
   NSString *injectedStuff;
-  RUN_RUNLOOP_WHILE(!(injectedStuff = executor.injectedStuff[@"__fbBatchedBridgeConfig"]));
+  RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"]));
+  XCTAssertNotNil(injectedStuff);
 
   __block NSNumber *testModuleID = nil;
-  __block NSDictionary<NSString *, id> *testConstants = nil;
   __block NSNumber *testMethodID = nil;
 
   NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"];
   [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) {
     if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[0] isEqualToString:@"TestModule"]) {
       testModuleID = @(i);
-      testConstants = moduleConfig[1];
       testMethodID = @([moduleConfig[2] indexOfObject:@"testMethod"]);
       *stop = YES;
     }
   }];
 
+  XCTAssertNotNil(testModuleID);
+  XCTAssertNotNil(testMethodID);
+
   NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42];
-  NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args], @[], @1234567];
+  NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]];
 
   [_bridge.batchedBridge handleBuffer:buffer];
 
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m
index 5761849d69d4..e34d9f29c2aa 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m
@@ -18,27 +18,44 @@
 #import <OCMock/OCMock.h>
 #import "RCTEventDispatcher.h"
 
-@interface RCTTestEvent : RCTBaseEvent
-
-@property (nonatomic, assign) BOOL canCoalesce;
-
+@interface RCTTestEvent : NSObject  <RCTEvent>
+@property (atomic, assign, readwrite) BOOL canCoalesce;
 @end
 
 @implementation RCTTestEvent
+{
+  NSDictionary<NSString *, id> *_body;
+}
+
+@synthesize viewTag = _viewTag;
+@synthesize eventName = _eventName;
 
 - (instancetype)initWithViewTag:(NSNumber *)viewTag eventName:(NSString *)eventName body:(NSDictionary<NSString *, id> *)body
 {
-  if (self = [super initWithViewTag:viewTag eventName:eventName body:body]) {
-    self.canCoalesce = YES;
+  if (self = [super init]) {
+    _viewTag = viewTag;
+    _eventName = eventName;
+    _body = body;
+    _canCoalesce = YES;
   }
   return self;
 }
 
+- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
+{
+  return newEvent;
+}
+
 + (NSString *)moduleDotMethod
 {
   return @"RCTDeviceEventEmitter.emit";
 }
 
+- (NSArray *)arguments
+{
+  return @[_eventName, _body];
+}
+
 @end
 
 @interface RCTEventDispatcherTests : XCTestCase
@@ -62,7 +79,7 @@ - (void)setUp
 
   _bridge = [OCMockObject mockForClass:[RCTBridge class]];
   _eventDispatcher = [RCTEventDispatcher new];
-  ((id<RCTBridgeModule>)_eventDispatcher).bridge = _bridge;
+  [_eventDispatcher setValue:_bridge forKey:@"bridge"];
 
   _eventName = RCTNormalizeInputEventName(@"sampleEvent");
   _body = @{ @"foo": @"bar" };
@@ -76,7 +93,7 @@ - (void)setUp
 - (void)testLegacyEventsAreImmediatelyDispatched
 {
   [[_bridge expect] enqueueJSCall:_JSMethod
-                             args:@[_eventName, _body]];
+                             args:[_testEvent arguments]];
 
   [_eventDispatcher sendDeviceEventWithName:_eventName body:_body];
 
@@ -87,7 +104,7 @@ - (void)testNonCoalescingEventsAreImmediatelyDispatched
 {
   _testEvent.canCoalesce = NO;
   [[_bridge expect] enqueueJSCall:_JSMethod
-                             args:@[_eventName, _body]];
+                             args:[_testEvent arguments]];
 
   [_eventDispatcher sendEvent:_testEvent];
 
@@ -97,15 +114,32 @@ - (void)testNonCoalescingEventsAreImmediatelyDispatched
 - (void)testCoalescedEventShouldBeDispatchedOnFrameUpdate
 {
   [_eventDispatcher sendEvent:_testEvent];
-
+  [_bridge verify];
   [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
-                             args:@[_eventName, _body]];
+                             args:[_testEvent arguments]];
 
   [(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
 
   [_bridge verify];
 }
 
+- (void)testNonCoalescingEventForcesColescedEventsToBeImmediatelyDispatched
+{
+  RCTTestEvent *nonCoalescingEvent = [[RCTTestEvent alloc] initWithViewTag:nil
+                                                                 eventName:_eventName
+                                                                      body:@{}];
+  nonCoalescingEvent.canCoalesce = NO;
+  [_eventDispatcher sendEvent:_testEvent];
+
+  [[_bridge expect] enqueueJSCall:[[_testEvent class] moduleDotMethod]
+                             args:[_testEvent arguments]];
+  [[_bridge expect] enqueueJSCall:[[nonCoalescingEvent class] moduleDotMethod]
+                             args:[nonCoalescingEvent arguments]];
+
+  [_eventDispatcher sendEvent:nonCoalescingEvent];
+  [_bridge verify];
+}
+
 - (void)testBasicCoalescingReturnsLastEvent
 {
   RCTTestEvent *ignoredEvent = [[RCTTestEvent alloc] initWithViewTag:nil
@@ -116,7 +150,7 @@ - (void)testBasicCoalescingReturnsLastEvent
   [_eventDispatcher sendEvent:_testEvent];
 
   [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
-                             args:@[_eventName, _body]];
+                             args:[_testEvent arguments]];
 
   [(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
 
@@ -134,10 +168,10 @@ - (void)testDifferentEventTypesDontCoalesce
   [_eventDispatcher sendEvent:_testEvent];
 
   [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
-                             args:@[firstEventName, _body]];
+                             args:[firstEvent arguments]];
 
   [[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
-                             args:@[_eventName, _body]];
+                             args:[_testEvent arguments]];
 
   [(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
 
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
index 69162a764e07..9d5814c56a5b 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m
@@ -16,6 +16,14 @@
 #import "RCTUtils.h"
 #import "RCTNetworking.h"
 
+#define RUN_RUNLOOP_WHILE(CONDITION) \
+{ \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
+  while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
+    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+  } \
+}
+
 extern BOOL RCTIsGzippedData(NSData *data);
 
 @interface RCTNetworking (Private)
@@ -61,18 +69,21 @@ - (void)testDontRezipZippedData
 - (void)testRequestBodyEncoding
 {
   NSDictionary *query = @{
-                          @"url": @"http://example.com",
-                          @"method": @"POST",
-                          @"data": @{@"string": @"Hello World"},
-                          @"headers": @{@"Content-Encoding": @"gzip"},
-                          };
+    @"url": @"http://example.com",
+    @"method": @"POST",
+    @"data": @{@"string": @"Hello World"},
+    @"headers": @{@"Content-Encoding": @"gzip"},
+  };
 
   RCTNetworking *networker = [RCTNetworking new];
+  [networker setValue:dispatch_get_main_queue() forKey:@"methodQueue"];
   __block NSURLRequest *request = nil;
   [networker buildRequest:query completionBlock:^(NSURLRequest *_request) {
     request = _request;
   }];
 
+  RUN_RUNLOOP_WHILE(request == nil);
+
   XCTAssertNotNil(request);
   XCTAssertNotNil(request.HTTPBody);
   XCTAssertTrue(RCTIsGzippedData(request.HTTPBody));
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
index 6ee68b5c3803..fe68f0592a0b 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
@@ -15,7 +15,7 @@
 #import "RCTImageLoader.h"
 
 typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL);
-typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
+typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
 
 @interface RCTConcreteImageURLLoader : NSObject <RCTImageURLLoader>
 
@@ -26,7 +26,7 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(
 @end
 
 typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData);
-typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
+typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
 
 @interface RCTConcreteImageDecoder : NSObject <RCTImageDataDecoder>
 
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
index e41b09f3d7f6..e3b4cb0861f8 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
@@ -47,7 +47,7 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL
   return _canLoadImageURLHandler(requestURL);
 }
 
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
 {
   return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
 }
@@ -92,7 +92,7 @@ - (BOOL)canDecodeImageData:(NSData *)imageData
   return _canDecodeImageDataHandler(imageData);
 }
 
-- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
 {
   return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
 }
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
index 04f367312a10..5a7cc12bd28e 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
@@ -41,16 +41,15 @@ - (void)testImageLoading
 
   id<RCTImageURLLoader> loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
     return YES;
-  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
+  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
     progressHandler(1, 1);
     completionHandler(nil, image);
     return nil;
   }];
 
-  RCTImageLoader *imageLoader = [RCTImageLoader new];
-  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
+  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader]; } launchOptions:nil];
 
-  [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
+  [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
     XCTAssertEqual(progress, 1);
     XCTAssertEqual(total, 1);
   } completionBlock:^(NSError *loadError, id loadedImage) {
@@ -65,7 +64,7 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
 
   id<RCTImageURLLoader> loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
     return YES;
-  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
+  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
     progressHandler(1, 1);
     completionHandler(nil, image);
     return nil;
@@ -73,15 +72,14 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
 
   id<RCTImageURLLoader> loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
     return YES;
-  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
+  } loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
     XCTFail(@"Should not have used loader2");
     return nil;
   }];
 
-  RCTImageLoader *imageLoader = [RCTImageLoader new];
-  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
+  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2]; } launchOptions:nil];
 
-  [imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
+  [bridge.imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:RCTResizeModeContain progressBlock:^(int64_t progress, int64_t total) {
     XCTAssertEqual(progress, 1);
     XCTAssertEqual(total, 1);
   } completionBlock:^(NSError *loadError, id loadedImage) {
@@ -97,16 +95,15 @@ - (void)testImageDecoding
 
   id<RCTImageDataDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
     return YES;
-  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
+  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
     XCTAssertEqualObjects(imageData, data);
     completionHandler(nil, image);
     return nil;
   }];
 
-  RCTImageLoader *imageLoader = [RCTImageLoader new];
-  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
+  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder]; } launchOptions:nil];
 
-  RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
+  RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
     XCTAssertEqualObjects(decodedImage, image);
     XCTAssertNil(decodeError);
   }];
@@ -120,7 +117,7 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority
 
   id<RCTImageDataDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
     return YES;
-  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
+  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
     XCTAssertEqualObjects(imageData, data);
     completionHandler(nil, image);
     return nil;
@@ -128,15 +125,14 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority
 
   id<RCTImageDataDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
     return YES;
-  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
+  } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused RCTResizeMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
     XCTFail(@"Should not have used decoder2");
     return nil;
   }];
 
-  RCTImageLoader *imageLoader = [RCTImageLoader new];
-  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
+  NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2]; } launchOptions:nil];
 
-  RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
+  RCTImageLoaderCancellationBlock cancelBlock = [bridge.imageLoader decodeImageDataWithoutClipping:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:RCTResizeModeStretch completionBlock:^(NSError *decodeError, id decodedImage) {
     XCTAssertEqualObjects(decodedImage, image);
     XCTAssertNil(decodeError);
   }];
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m
index f1effc8d64e1..810e079ab211 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m
@@ -46,19 +46,19 @@ - (void)testLandscapeSourceLandscapeTarget
 
   {
     CGRect expected = {CGPointZero, {100, 20}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
     RCTAssertEqualRects(expected, result);
   }
 
   {
-    CGRect expected = {CGPointZero, {100, 10}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
+    CGRect expected = {{0, 5}, {100, 10}};
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
     RCTAssertEqualRects(expected, result);
   }
 
   {
     CGRect expected = {{-50, 0}, {200, 20}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
     RCTAssertEqualRects(expected, result);
   }
 }
@@ -70,19 +70,19 @@ - (void)testPortraitSourceLandscapeTarget
 
   {
     CGRect expected = {CGPointZero, {100, 20}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
     RCTAssertEqualRects(expected, result);
   }
 
   {
-    CGRect expected = {CGPointZero, {2, 20}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
+    CGRect expected = {{49, 0}, {2, 20}};
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
     RCTAssertEqualRects(expected, result);
   }
 
   {
     CGRect expected = {{0, -490}, {100, 1000}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
     RCTAssertEqualRects(expected, result);
   }
 }
@@ -94,19 +94,19 @@ - (void)testPortraitSourcePortraitTarget
 
   {
     CGRect expected = {CGPointZero, {20, 50}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
     RCTAssertEqualRects(expected, result);
   }
 
   {
-    CGRect expected = {CGPointZero, {5, 50}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit);
+    CGRect expected = {{7,0}, {5, 50}};
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeContain);
     RCTAssertEqualRects(expected, result);
   }
 
   {
     CGRect expected = {{0, -75}, {20, 200}};
-    CGRect result = RCTTargetRect(content, target, 2, UIViewContentModeScaleAspectFill);
+    CGRect result = RCTTargetRect(content, target, 2, RCTResizeModeCover);
     RCTAssertEqualRects(expected, result);
   }
 }
@@ -118,7 +118,7 @@ - (void)testRounding
 
   {
     CGRect expected = {{0, -75}, {20, 200}};
-    CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill);
+    CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeCover);
     RCTAssertEqualRects(expected, result);
   }
 }
@@ -129,8 +129,56 @@ - (void)testScaling
   CGSize target = {3, 3};
 
   CGRect expected = {CGPointZero, {3, 3}};
-  CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill);
+  CGRect result = RCTTargetRect(content, target, 1, RCTResizeModeStretch);
   RCTAssertEqualRects(expected, result);
 }
 
+- (void)testPlaceholderImage
+{
+  CGSize size = {45, 22};
+  CGFloat expectedScale = 1.0;
+  UIImage *image = RCTGetPlaceholderImage(size, nil);
+  RCTAssertEqualSizes(size, image.size);
+  XCTAssertEqual(expectedScale, image.scale);
+}
+
+- (void)testPlaceholderNonintegralSize
+{
+  CGSize size = {3.0/2, 7.0/3};
+  CGFloat expectedScale = 6;
+  CGSize pixelSize = {
+    round(size.width * expectedScale),
+    round(size.height * expectedScale)
+  };
+  UIImage *image = RCTGetPlaceholderImage(size, nil);
+  RCTAssertEqualSizes(size, image.size);
+  XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
+  XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
+  XCTAssertEqual(expectedScale, image.scale);
+}
+
+- (void)testPlaceholderSquareImage
+{
+  CGSize size = {333, 333};
+  CGFloat expectedScale = 1.0/333;
+  CGSize pixelSize = {1, 1};
+  UIImage *image = RCTGetPlaceholderImage(size, nil);
+  RCTAssertEqualSizes(size, image.size);
+  XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
+  XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
+  XCTAssertEqual(expectedScale, image.scale);
+}
+
+- (void)testPlaceholderNonsquareImage
+{
+  CGSize size = {640, 480};
+  CGFloat expectedScale = 1.0/160;
+  CGSize pixelSize = {4, 3};
+  UIImage *image = RCTGetPlaceholderImage(size, nil);
+  RCTAssertEqualSizes(size, image.size);
+  XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
+  XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
+  XCTAssertEqual(expectedScale, image.scale);
+}
+
 @end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m
similarity index 88%
rename from Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m
rename to Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m
index d00d650467bc..5d08f8f46f74 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSCExecutorTests.m
@@ -16,24 +16,24 @@
 
 #import <XCTest/XCTest.h>
 
-#import "RCTContextExecutor.h"
+#import "RCTJSCExecutor.h"
 #import "RCTUtils.h"
 
 #define RUN_PERF_TESTS 0
 
-@interface RCTContextExecutorTests : XCTestCase
+@interface RCTJSCExecutorTests : XCTestCase
 
 @end
 
-@implementation RCTContextExecutorTests
+@implementation RCTJSCExecutorTests
 {
-  RCTContextExecutor *_executor;
+  RCTJSCExecutor *_executor;
 }
 
 - (void)setUp
 {
   [super setUp];
-  _executor = [RCTContextExecutor new];
+  _executor = [RCTJSCExecutor new];
   [_executor setUp];
 }
 
@@ -97,8 +97,8 @@ - (void)testDeserializationPerf
 - (void)testJavaScriptCallSpeed
 {
 /**
- * Since we almost don't change the RCTContextExecutor logic, and this test is
- * very likely to become flaky (specially accross different devices) leave it
+ * Since we almost don't change the RCTJSCExecutor logic, and this test is
+ * very likely to become flaky (specially across different devices) leave it
  * to be run manually
  *
  * Previous Values: If you change the executor code, you should update this values
@@ -123,8 +123,13 @@ - (void)testJavaScriptCallSpeed
         } \
       } \
     }; \
+    var Bridge = { \
+      callFunctionReturnFlushedQueue: function(module, method, args) { \
+        modules[module].apply(modules[module], args); \
+      } \
+    }; \
     function require(module) { \
-      return modules[module]; \
+      return Bridge; \
     } \
   ";
 
@@ -138,12 +143,11 @@ function require(module) { \
       for (int j = 0; j < runs; j++) {
         @autoreleasepool {
           double start = _get_time_nanoseconds();
-          [_executor executeJSCall:@"module"
-                           method:@"method"
-                        arguments:params
-                         callback:^(id json, __unused NSError *unused) {
-                           XCTAssert([json isEqual:@YES], @"Invalid return");
-                         }];
+          [_executor callFunctionOnModule:@"module"
+                                   method:@"method"
+                                arguments:params
+                                 callback:^(__unused id json, __unused NSError *unused) {
+                                 }];
           double run = _get_time_nanoseconds() - start;
           if ((j % frequency) == frequency - 1) { // Warmup
             total += run;
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m
index fd8343c08c84..acacc3a34c46 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m
@@ -78,4 +78,35 @@ - (void)testLeadingWhitespace
   XCTAssertEqualObjects(obj, RCTJSONParse(json, NULL));
 }
 
+- (void)testNotJSONSerializable
+{
+  NSDictionary<NSString *, id> *obj = @{@"foo": [NSDate date]};
+  NSString *json = @"{\"foo\":null}";
+  XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
+}
+
+- (void)testNaN
+{
+  NSDictionary<NSString *, id> *obj = @{@"foo": @(NAN)};
+  NSString *json = @"{\"foo\":0}";
+  XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
+}
+
+- (void)testNotUTF8Convertible
+{
+  //see https://gist.github.com/0xced/56035d2f57254cf518b5
+  NSString *string = [[NSString alloc] initWithBytes:"\xd8\x00" length:2 encoding:NSUTF16StringEncoding];
+  NSDictionary<NSString *, id> *obj = @{@"foo": string};
+  NSString *json = @"{\"foo\":null}";
+  XCTAssertEqualObjects(json, RCTJSONStringify(obj, NULL));
+}
+
+- (void)testErrorPointer
+{
+  NSDictionary<NSString *, id> *obj = @{@"foo": [NSDate date]};
+  NSError *error;
+  XCTAssertNil(RCTJSONStringify(obj, &error));
+  XCTAssertNotNil(error);
+}
+
 @end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m
index 3f53812473e7..53d9d0f74336 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTMethodArgumentTests.m
@@ -23,14 +23,14 @@ @interface RCTMethodArgumentTests : XCTestCase
 
 @implementation RCTMethodArgumentTests
 
-extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
+extern SEL RCTParseMethodSignature(NSString *methodSignature, NSArray **argTypes);
 
 - (void)testOneArgument
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(NSInteger)foo";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:");
+  NSString *methodSignature = @"foo:(NSInteger)foo";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
   XCTAssertEqual(arguments.count, (NSUInteger)1);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
 }
@@ -38,9 +38,9 @@ - (void)testOneArgument
 - (void)testTwoArguments
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo:(NSInteger)foo bar:(BOOL)bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -49,9 +49,9 @@ - (void)testTwoArguments
 - (void)testSpaces
 {
   NSArray *arguments;
-  NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo : (NSInteger)foo bar : (BOOL) bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -60,9 +60,9 @@ - (void)testSpaces
 - (void)testNewlines
 {
   NSArray *arguments;
-  NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -71,9 +71,9 @@ - (void)testNewlines
 - (void)testUnnamedArgs
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo::");
+  NSString *methodSignature = @"foo:(NSInteger)foo:(BOOL)bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo::");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -82,9 +82,9 @@ - (void)testUnnamedArgs
 - (void)testUntypedUnnamedArgs
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:foo:bar:bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:::");
+  NSString *methodSignature = @"foo:foo:bar:bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:::");
   XCTAssertEqual(arguments.count, (NSUInteger)3);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id");
@@ -94,9 +94,9 @@ - (void)testUntypedUnnamedArgs
 - (void)testAttributes
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo:(__attribute__((unused)) NSString *)foo bar:(__unused BOOL)bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -105,9 +105,9 @@ - (void)testAttributes
 - (void)testNullability
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:baz:");
+  NSString *methodSignature = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:baz:");
   XCTAssertEqual(arguments.count, (NSUInteger)3);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
@@ -120,9 +120,9 @@ - (void)testNullability
 - (void)testSemicolonStripping
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo:(NSString *)foo bar:(BOOL)bar;";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
@@ -131,9 +131,9 @@ - (void)testSemicolonStripping
 - (void)testUnused
 {
   NSArray *arguments;
-  NSString *methodName = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar";
-  RCTParseObjCMethodName(&methodName, &arguments);
-  XCTAssertEqualObjects(methodName, @"foo:bar:");
+  NSString *methodSignature = @"foo:(__unused NSString *)foo bar:(NSNumber *)bar";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:bar:");
   XCTAssertEqual(arguments.count, (NSUInteger)2);
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
   XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
@@ -141,4 +141,44 @@ - (void)testUnused
   XCTAssertFalse(((RCTMethodArgument *)arguments[1]).unused);
 }
 
+- (void)testGenericArray
+{
+  NSArray *arguments;
+  NSString *methodSignature = @"foo:(NSArray<NSString *> *)foo;";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
+  XCTAssertEqual(arguments.count, (NSUInteger)1);
+  XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArray");
+}
+
+- (void)testNestedGenericArray
+{
+  NSArray *arguments;
+  NSString *methodSignature = @"foo:(NSArray<NSArray<NSString *> *> *)foo;";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
+  XCTAssertEqual(arguments.count, (NSUInteger)1);
+  XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSStringArrayArray");
+}
+
+- (void)testGenericSet
+{
+  NSArray *arguments;
+  NSString *methodSignature = @"foo:(NSSet<NSNumber *> *)foo;";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
+  XCTAssertEqual(arguments.count, (NSUInteger)1);
+  XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberSet");
+}
+
+- (void)testGenericDictionary
+{
+  NSArray *arguments;
+  NSString *methodSignature = @"foo:(NSDictionary<NSString *, NSNumber *> *)foo;";
+  SEL selector = RCTParseMethodSignature(methodSignature, &arguments);
+  XCTAssertEqualObjects(NSStringFromSelector(selector), @"foo:");
+  XCTAssertEqual(arguments.count, (NSUInteger)1);
+  XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSNumberDictionary");
+}
+
 @end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m
new file mode 100644
index 000000000000..77a997a4baa5
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m
@@ -0,0 +1,129 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+
+#import "RCTBridge.h"
+#import "RCTBridge+Private.h"
+#import "RCTBridgeModule.h"
+#import "RCTUtils.h"
+#import "RCTUIManager.h"
+#import "RCTViewManager.h"
+
+#define RUN_RUNLOOP_WHILE(CONDITION) \
+{ \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
+  while ((CONDITION)) { \
+    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+    if ([timeout timeIntervalSinceNow] <= 0) { \
+      XCTFail(@"Runloop timed out before condition was met"); \
+      break; \
+    } \
+  } \
+}
+
+@interface RCTTestViewManager : RCTViewManager
+@end
+
+@implementation RCTTestViewManager
+
+RCT_EXPORT_MODULE()
+
+- (NSArray<NSString *> *)customDirectEventTypes
+{
+  return @[@"foo"];
+}
+
+@end
+
+
+@interface RCTNotificationObserverModule : NSObject <RCTBridgeModule>
+
+@property (nonatomic, assign) BOOL didDetectViewManagerInit;
+
+@end
+
+@implementation RCTNotificationObserverModule
+
+@synthesize bridge = _bridge;
+
+RCT_EXPORT_MODULE()
+
+- (void)setBridge:(RCTBridge *)bridge
+{
+  _bridge = bridge;
+  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didInitViewManager:) name:RCTDidInitializeModuleNotification object:nil];
+}
+
+- (void)didInitViewManager:(NSNotification *)note
+{
+  id<RCTBridgeModule> module = note.userInfo[@"module"];
+  if ([module isKindOfClass:[RCTTestViewManager class]]) {
+    _didDetectViewManagerInit = YES;
+  }
+}
+
+- (void)dealloc
+{
+  [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+@end
+
+
+@interface RCTModuleInitNotificationRaceTests : XCTestCase <RCTBridgeDelegate>
+{
+  RCTBridge *_bridge;
+  RCTNotificationObserverModule *_notificationObserver;
+}
+@end
+
+@implementation RCTModuleInitNotificationRaceTests
+
+- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
+{
+  return nil;
+}
+
+- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
+{
+  return @[[RCTTestViewManager new], _notificationObserver];
+}
+
+- (void)setUp
+{
+  [super setUp];
+
+  _notificationObserver = [RCTNotificationObserverModule new];
+  _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
+}
+
+- (void)tearDown
+{
+  [super tearDown];
+
+  _notificationObserver = nil;
+  id<RCTJavaScriptExecutor> jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
+  [_bridge invalidate];
+  RUN_RUNLOOP_WHILE(jsExecutor.isValid);
+  _bridge = nil;
+}
+
+- (void)testViewManagerNotInitializedBeforeSetBridgeModule
+{
+  RUN_RUNLOOP_WHILE(!_notificationObserver.didDetectViewManagerInit);
+}
+
+@end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m
new file mode 100644
index 000000000000..83a549736809
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitTests.m
@@ -0,0 +1,261 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <Foundation/Foundation.h>
+#import <XCTest/XCTest.h>
+
+#import "RCTBridge.h"
+#import "RCTBridge+Private.h"
+#import "RCTBridgeModule.h"
+#import "RCTUtils.h"
+
+#define RUN_RUNLOOP_WHILE(CONDITION) \
+{ \
+  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
+  while ((CONDITION)) { \
+    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+    if ([timeout timeIntervalSinceNow] <= 0) { \
+      XCTFail(@"Runloop timed out before condition was met"); \
+      break; \
+    } \
+  } \
+}
+
+
+@interface RCTTestInjectedModule : NSObject <RCTBridgeModule>
+@end
+
+@implementation RCTTestInjectedModule
+
+@synthesize bridge = _bridge;
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+@end
+
+
+@interface RCTTestCustomInitModule : NSObject <RCTBridgeModule>
+
+@property (nonatomic, assign) BOOL initializedOnMainThread;
+
+@end
+
+@implementation RCTTestCustomInitModule
+
+@synthesize bridge = _bridge;
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+- (id)init
+{
+  if ((self = [super init])) {
+    _initializedOnMainThread = [NSThread isMainThread];
+  }
+  return self;
+}
+
+@end
+
+
+@interface RCTTestCustomSetBridgeModule : NSObject <RCTBridgeModule>
+
+@property (nonatomic, assign) BOOL setBridgeOnMainThread;
+
+@end
+
+@implementation RCTTestCustomSetBridgeModule
+
+@synthesize bridge = _bridge;
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+- (void)setBridge:(RCTBridge *)bridge
+{
+  _bridge = bridge;
+  _setBridgeOnMainThread = [NSThread isMainThread];
+}
+
+@end
+
+
+@interface RCTTestExportConstantsModule : NSObject <RCTBridgeModule>
+
+@property (nonatomic, assign) BOOL exportedConstants;
+@property (nonatomic, assign) BOOL exportedConstantsOnMainThread;
+
+@end
+
+@implementation RCTTestExportConstantsModule
+
+@synthesize bridge = _bridge;
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+- (NSDictionary<NSString *, id> *)constantsToExport
+{
+  _exportedConstants = YES;
+  _exportedConstantsOnMainThread = [NSThread isMainThread];
+  return @{ @"foo": @"bar" };
+}
+
+@end
+
+
+@interface RCTLazyInitModule : NSObject <RCTBridgeModule>
+@end
+
+@implementation RCTLazyInitModule
+
+@synthesize bridge = _bridge;
+@synthesize methodQueue = _methodQueue;
+
+RCT_EXPORT_MODULE()
+
+@end
+
+
+@interface RCTModuleInitTests : XCTestCase <RCTBridgeDelegate>
+{
+  RCTBridge *_bridge;
+  BOOL _injectedModuleInitNotificationSent;
+  BOOL _customInitModuleNotificationSent;
+  BOOL _customSetBridgeModuleNotificationSent;
+  BOOL _exportConstantsModuleNotificationSent;
+  BOOL _lazyInitModuleNotificationSent;
+  BOOL _lazyInitModuleNotificationSentOnMainThread;
+  BOOL _viewManagerModuleNotificationSent;
+  RCTTestInjectedModule *_injectedModule;
+}
+@end
+
+@implementation RCTModuleInitTests
+
+- (NSURL *)sourceURLForBridge:(__unused RCTBridge *)bridge
+{
+  return nil;
+}
+
+- (NSArray *)extraModulesForBridge:(__unused RCTBridge *)bridge
+{
+  return @[_injectedModule];
+}
+
+- (void)setUp
+{
+  [super setUp];
+
+  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moduleDidInit:) name:RCTDidInitializeModuleNotification object:nil];
+
+  _injectedModuleInitNotificationSent = NO;
+  _customInitModuleNotificationSent = NO;
+  _customSetBridgeModuleNotificationSent = NO;
+  _exportConstantsModuleNotificationSent = NO;
+  _lazyInitModuleNotificationSent = NO;
+  _viewManagerModuleNotificationSent = NO;
+  _injectedModule = [RCTTestInjectedModule new];
+  _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
+}
+
+- (void)tearDown
+{
+  [super tearDown];
+
+  [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTDidInitializeModuleNotification object:nil];
+
+  id<RCTJavaScriptExecutor> jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
+  [_bridge invalidate];
+  RUN_RUNLOOP_WHILE(jsExecutor.isValid);
+  _bridge = nil;
+}
+
+- (void)moduleDidInit:(NSNotification *)note
+{
+  id<RCTBridgeModule> module = note.userInfo[@"module"];
+  if ([module isKindOfClass:[RCTTestInjectedModule class]]) {
+    _injectedModuleInitNotificationSent = YES;
+  } else if ([module isKindOfClass:[RCTTestCustomInitModule class]]) {
+    _customInitModuleNotificationSent = YES;
+  } else if ([module isKindOfClass:[RCTTestCustomSetBridgeModule class]]) {
+    _customSetBridgeModuleNotificationSent = YES;
+  } else if ([module isKindOfClass:[RCTTestExportConstantsModule class]]) {
+    _exportConstantsModuleNotificationSent = YES;
+  } else if ([module isKindOfClass:[RCTLazyInitModule class]]) {
+    _lazyInitModuleNotificationSent = YES;
+    _lazyInitModuleNotificationSentOnMainThread = [NSThread isMainThread];
+  }
+}
+
+- (void)testInjectedModulesInitializedDuringBridgeInit
+{
+  XCTAssertEqual(_injectedModule, [_bridge moduleForClass:[RCTTestInjectedModule class]]);
+  XCTAssertEqual(_injectedModule.bridge, _bridge.batchedBridge);
+  XCTAssertNotNil(_injectedModule.methodQueue);
+  RUN_RUNLOOP_WHILE(!_injectedModuleInitNotificationSent);
+  XCTAssertTrue(_injectedModuleInitNotificationSent);
+}
+
+- (void)testCustomInitModuleInitializedAtBridgeStartup
+{
+  RUN_RUNLOOP_WHILE(!_customInitModuleNotificationSent);
+  XCTAssertTrue(_customInitModuleNotificationSent);
+  RCTTestCustomInitModule *module = [_bridge moduleForClass:[RCTTestCustomInitModule class]];
+  XCTAssertTrue(module.initializedOnMainThread);
+  XCTAssertEqual(module.bridge, _bridge.batchedBridge);
+  XCTAssertNotNil(module.methodQueue);
+}
+
+- (void)testCustomSetBridgeModuleInitializedAtBridgeStartup
+{
+  RUN_RUNLOOP_WHILE(!_customSetBridgeModuleNotificationSent);
+  XCTAssertTrue(_customSetBridgeModuleNotificationSent);
+  RCTTestCustomSetBridgeModule *module = [_bridge moduleForClass:[RCTTestCustomSetBridgeModule class]];
+  XCTAssertTrue(module.setBridgeOnMainThread);
+  XCTAssertEqual(module.bridge, _bridge.batchedBridge);
+  XCTAssertNotNil(module.methodQueue);
+}
+
+- (void)testExportConstantsModuleInitializedAtBridgeStartup
+{
+  RUN_RUNLOOP_WHILE(!_exportConstantsModuleNotificationSent);
+  XCTAssertTrue(_exportConstantsModuleNotificationSent);
+  RCTTestExportConstantsModule *module = [_bridge moduleForClass:[RCTTestExportConstantsModule class]];
+  RUN_RUNLOOP_WHILE(!module.exportedConstants);
+  XCTAssertTrue(module.exportedConstants);
+  XCTAssertTrue(module.exportedConstantsOnMainThread);
+  XCTAssertEqual(module.bridge, _bridge.batchedBridge);
+  XCTAssertNotNil(module.methodQueue);
+}
+
+- (void)testLazyInitModuleNotInitializedDuringBridgeInit
+{
+  XCTAssertFalse(_lazyInitModuleNotificationSent);
+
+  __block RCTLazyInitModule *module;
+  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+    module = [_bridge moduleForClass:[RCTLazyInitModule class]];
+  });
+
+  RUN_RUNLOOP_WHILE(!module);
+  XCTAssertTrue(_lazyInitModuleNotificationSent);
+  XCTAssertFalse(_lazyInitModuleNotificationSentOnMainThread);
+  XCTAssertNotNil(module);
+  XCTAssertEqual(module.bridge, _bridge.batchedBridge);
+  XCTAssertNotNil(module.methodQueue);
+}
+
+@end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
index 93ac3b5aa047..f247da4ffc75 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m
@@ -47,10 +47,10 @@ - (void)doFooWithBar:(__unused NSString *)bar { }
 
 - (void)testNonnull
 {
-  NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar";
-  RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                               JSMethodName:nil
-                                                                moduleClass:[self class]];
+  NSString *methodSignature = @"doFooWithBar:(nonnull NSString *)bar";
+  RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                JSMethodName:nil
+                                                                 moduleClass:[self class]];
   XCTAssertFalse(RCTLogsError(^{
     [method invokeWithBridge:nil module:self arguments:@[@"Hello World"]];
   }));
@@ -72,40 +72,40 @@ - (void)testNumbersNonnull
   {
     // Specifying an NSNumber param without nonnull isn't allowed
     XCTAssertTrue(RCTLogsError(^{
-      NSString *methodName = @"doFooWithNumber:(NSNumber *)n";
-      RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                                   JSMethodName:nil
-                                                                    moduleClass:[self class]];
+      NSString *methodSignature = @"doFooWithNumber:(NSNumber *)n";
+      RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                    JSMethodName:nil
+                                                                     moduleClass:[self class]];
       // Invoke method to trigger parsing
       [method invokeWithBridge:nil module:self arguments:@[@1]];
     }));
   }
 
   {
-    NSString *methodName = @"doFooWithNumber:(nonnull NSNumber *)n";
-    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                                 JSMethodName:nil
-                                                                  moduleClass:[self class]];
+    NSString *methodSignature = @"doFooWithNumber:(nonnull NSNumber *)n";
+    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                  JSMethodName:nil
+                                                                   moduleClass:[self class]];
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
     }));
   }
 
   {
-    NSString *methodName = @"doFooWithDouble:(double)n";
-    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                                 JSMethodName:nil
-                                                                  moduleClass:[self class]];
+    NSString *methodSignature = @"doFooWithDouble:(double)n";
+    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                  JSMethodName:nil
+                                                                   moduleClass:[self class]];
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
     }));
   }
 
   {
-    NSString *methodName = @"doFooWithInteger:(NSInteger)n";
-    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                                 JSMethodName:nil
-                                                                  moduleClass:[self class]];
+    NSString *methodSignature = @"doFooWithInteger:(NSInteger)n";
+    RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                  JSMethodName:nil
+                                                                   moduleClass:[self class]];
     XCTAssertTrue(RCTLogsError(^{
       [method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
     }));
@@ -114,10 +114,10 @@ - (void)testNumbersNonnull
 
 - (void)testStructArgument
 {
-  NSString *methodName = @"doFooWithCGRect:(CGRect)s";
-  RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                               JSMethodName:nil
-                                                                moduleClass:[self class]];
+  NSString *methodSignature = @"doFooWithCGRect:(CGRect)s";
+  RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                                JSMethodName:nil
+                                                                 moduleClass:[self class]];
 
   CGRect r = CGRectMake(10, 20, 30, 40);
   [method invokeWithBridge:nil module:self arguments:@[@[@10, @20, @30, @40]]];
@@ -126,13 +126,13 @@ - (void)testStructArgument
 
 - (void)testWhitespaceTolerance
 {
-  NSString *methodName = @"doFoo : \t (NSString *)foo";
+  NSString *methodSignature = @"doFoo : \t (NSString *)foo";
 
   __block RCTModuleMethod *method;
   XCTAssertFalse(RCTLogsError(^{
-    method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
-                                                JSMethodName:nil
-                                                 moduleClass:[self class]];
+    method = [[RCTModuleMethod alloc] initWithMethodSignature:methodSignature
+                                                 JSMethodName:nil
+                                                  moduleClass:[self class]];
   }));
 
   XCTAssertEqualObjects(method.JSMethodName, @"doFoo");
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
index f2715947a4ea..cfe3959bc756 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
@@ -17,11 +17,23 @@
 #import "RCTShadowView.h"
 
 @interface RCTShadowViewTests : XCTestCase
-
+@property (nonatomic, strong) RCTShadowView *parentView;
 @end
 
 @implementation RCTShadowViewTests
 
+- (void)setUp
+{
+  [super setUp];
+  
+  self.parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
+    style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
+    style->dimensions[0] = 440;
+    style->dimensions[1] = 440;
+  }];
+  self.parentView.reactTag = @1; // must be valid rootView tag
+}
+
 // Just a basic sanity test to ensure css-layout is applied correctly in the context of our shadow view hierarchy.
 //
 // ====================================
@@ -69,33 +81,87 @@ - (void)testApplyingLayoutRecursivelyToShadowView
     style->flex = 1;
   }];
 
-  RCTShadowView *parentView = [self _shadowViewWithStyle:^(css_style_t *style) {
-    style->flex_direction = CSS_FLEX_DIRECTION_COLUMN;
-    style->padding[0] = 10;
-    style->padding[1] = 10;
-    style->padding[2] = 10;
-    style->padding[3] = 10;
-    style->dimensions[0] = 440;
-    style->dimensions[1] = 440;
-  }];
+  self.parentView.cssNode->style.padding[0] = 10;
+  self.parentView.cssNode->style.padding[1] = 10;
+  self.parentView.cssNode->style.padding[2] = 10;
+  self.parentView.cssNode->style.padding[3] = 10;
+
+  [self.parentView insertReactSubview:headerView atIndex:0];
+  [self.parentView insertReactSubview:mainView atIndex:1];
+  [self.parentView insertReactSubview:footerView atIndex:2];
 
-  [parentView insertReactSubview:headerView atIndex:0];
-  [parentView insertReactSubview:mainView atIndex:1];
-  [parentView insertReactSubview:footerView atIndex:2];
+  [self.parentView collectRootUpdatedFrames];
 
-  parentView.reactTag = @1; // must be valid rootView tag
-  [parentView collectRootUpdatedFrames];
+  XCTAssertTrue(CGRectEqualToRect([self.parentView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(0, 0, 440, 440)));
+  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([self.parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
 
-  XCTAssertTrue(CGRectEqualToRect([parentView measureLayoutRelativeToAncestor:parentView], CGRectMake(0, 0, 440, 440)));
-  XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets([parentView paddingAsInsets], UIEdgeInsetsMake(10, 10, 10, 10)));
+  XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 10, 420, 100)));
+  XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 420, 200)));
+  XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 330, 420, 100)));
 
-  XCTAssertTrue(CGRectEqualToRect([headerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 10, 420, 100)));
-  XCTAssertTrue(CGRectEqualToRect([mainView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 420, 200)));
-  XCTAssertTrue(CGRectEqualToRect([footerView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 330, 420, 100)));
+  XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(10, 120, 100, 200)));
+  XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(120, 120, 200, 200)));
+  XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:self.parentView], CGRectMake(330, 120, 100, 200)));
+}
 
-  XCTAssertTrue(CGRectEqualToRect([leftView measureLayoutRelativeToAncestor:parentView], CGRectMake(10, 120, 100, 200)));
-  XCTAssertTrue(CGRectEqualToRect([centerView measureLayoutRelativeToAncestor:parentView], CGRectMake(120, 120, 200, 200)));
-  XCTAssertTrue(CGRectEqualToRect([rightView measureLayoutRelativeToAncestor:parentView], CGRectMake(330, 120, 100, 200)));
+- (void)testAssignsSuggestedWidthDimension
+{
+  [self _withShadowViewWithStyle:^(css_style_t *style) {
+                                   style->position[CSS_LEFT] = 0;
+                                   style->position[CSS_TOP] = 0;
+                                   style->dimensions[CSS_HEIGHT] = 10;
+                                 }
+            assertRelativeLayout:CGRectMake(0, 0, 3, 10)
+        withIntrinsicContentSize:CGSizeMake(3, UIViewNoIntrinsicMetric)];
+}
+
+- (void)testAssignsSuggestedHeightDimension
+{
+  [self _withShadowViewWithStyle:^(css_style_t *style) {
+                                   style->position[CSS_LEFT] = 0;
+                                   style->position[CSS_TOP] = 0;
+                                   style->dimensions[CSS_WIDTH] = 10;
+                                 }
+            assertRelativeLayout:CGRectMake(0, 0, 10, 4)
+        withIntrinsicContentSize:CGSizeMake(UIViewNoIntrinsicMetric, 4)];
+}
+
+- (void)testDoesNotOverrideDimensionStyleWithSuggestedDimensions
+{
+  [self _withShadowViewWithStyle:^(css_style_t *style) {
+                                   style->position[CSS_LEFT] = 0;
+                                   style->position[CSS_TOP] = 0;
+                                   style->dimensions[CSS_WIDTH] = 10;
+                                   style->dimensions[CSS_HEIGHT] = 10;
+                                 }
+          assertRelativeLayout:CGRectMake(0, 0, 10, 10)
+      withIntrinsicContentSize:CGSizeMake(3, 4)];
+}
+
+- (void)testDoesNotAssignSuggestedDimensionsWhenStyledWithFlexAttribute
+{
+  float parentWidth = self.parentView.cssNode->style.dimensions[CSS_WIDTH];
+  float parentHeight = self.parentView.cssNode->style.dimensions[CSS_HEIGHT];
+  [self _withShadowViewWithStyle:^(css_style_t *style) {
+                                   style->flex = 1;
+                                 }
+            assertRelativeLayout:CGRectMake(0, 0, parentWidth, parentHeight)
+        withIntrinsicContentSize:CGSizeMake(3, 4)];
+}
+
+- (void)_withShadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
+            assertRelativeLayout:(CGRect)expectedRect
+        withIntrinsicContentSize:(CGSize)contentSize
+{
+  RCTShadowView *view = [self _shadowViewWithStyle:styleBlock];
+  [self.parentView insertReactSubview:view atIndex:0];
+  view.intrinsicContentSize = contentSize;
+  [self.parentView collectRootUpdatedFrames];
+  CGRect actualRect = [view measureLayoutRelativeToAncestor:self.parentView];
+  XCTAssertTrue(CGRectEqualToRect(expectedRect, actualRect),
+                @"Expected layout to be %@, got %@",
+                NSStringFromCGRect(expectedRect),
+                NSStringFromCGRect(actualRect));
 }
 
 - (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m
new file mode 100644
index 000000000000..c2f6db568765
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTURLUtilsTests.m
@@ -0,0 +1,96 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#import <XCTest/XCTest.h>
+#import "RCTUtils.h"
+
+@interface RCTURLUtilsTests : XCTestCase
+
+@end
+
+@implementation RCTURLUtilsTests
+
+- (void)testGetQueryParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&bar=foo"];
+  NSString *foo = RCTGetURLQueryParam(URL, @"foo");
+  NSString *bar = RCTGetURLQueryParam(URL, @"bar");
+  XCTAssertEqualObjects(foo, @"bar");
+  XCTAssertEqualObjects(bar, @"foo");
+}
+
+- (void)testGetEncodedParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"];
+  NSString *foo = RCTGetURLQueryParam(URL, @"foo");
+  XCTAssertEqualObjects(foo, @"You & Me");
+}
+
+- (void)testQueryParamNotFound
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar"];
+  NSString *bar = RCTGetURLQueryParam(URL, @"bar");
+  XCTAssertNil(bar);
+}
+
+- (void)testDuplicateParamTakesLatter
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&foo=foo"];
+  NSString *foo = RCTGetURLQueryParam(URL, @"foo");
+  XCTAssertEqualObjects(foo, @"foo");
+}
+
+- (void)testNilURLGetQueryParam
+{
+  NSURL *URL = nil;
+  NSString *foo = RCTGetURLQueryParam(URL, @"foo");
+  XCTAssertNil(foo);
+}
+
+- (void)testReplaceParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=bar&bar=foo"];
+  NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"foo");
+  XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=foo&bar=foo");
+}
+
+- (void)testReplaceEncodedParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?foo=You%20%26%20Me"];
+  NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"Me & You");
+  XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=Me%20%26%20You");
+}
+
+- (void)testAppendParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?bar=foo"];
+  NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"bar");
+  XCTAssertEqualObjects(result.absoluteString, @"http://example.com?bar=foo&foo=bar");
+}
+
+- (void)testRemoveParam
+{
+  NSURL *URL = [NSURL URLWithString:@"http://example.com?bar=foo&foo=bar"];
+  NSURL *result = RCTURLByReplacingQueryParam(URL, @"bar", nil);
+  XCTAssertEqualObjects(result.absoluteString, @"http://example.com?foo=bar");
+}
+
+- (void)testNilURLAppendQueryParam
+{
+  NSURL *URL = nil;
+  NSURL *result = RCTURLByReplacingQueryParam(URL, @"foo", @"bar");
+  XCTAssertNil(result);
+}
+
+@end
diff --git a/Examples/UIExplorer/VibrationExample.js b/Examples/UIExplorer/VibrationExample.js
new file mode 100644
index 000000000000..11f17d368b86
--- /dev/null
+++ b/Examples/UIExplorer/VibrationExample.js
@@ -0,0 +1,54 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  StyleSheet,
+  View,
+  Text,
+  TouchableHighlight,
+  Vibration,
+} = React;
+
+exports.framework = 'React';
+exports.title = 'Vibration';
+exports.description = 'Vibration API';
+exports.examples = [{
+  title: 'Vibration.vibrate()',
+  render() {
+    return (
+      <TouchableHighlight
+        style={styles.wrapper}
+        onPress={() => Vibration.vibrate()}>
+        <View style={styles.button}>
+          <Text>Vibrate</Text>
+        </View>
+      </TouchableHighlight>
+    );
+  },
+}];
+
+var styles = StyleSheet.create({
+  wrapper: {
+    borderRadius: 5,
+    marginBottom: 5,
+  },
+  button: {
+    backgroundColor: '#eeeeee',
+    padding: 10,
+  },
+});
diff --git a/Examples/UIExplorer/ViewExample.js b/Examples/UIExplorer/ViewExample.js
index c20d7e11996a..deed55067e20 100644
--- a/Examples/UIExplorer/ViewExample.js
+++ b/Examples/UIExplorer/ViewExample.js
@@ -40,16 +40,6 @@ var ViewBorderStyleExample = React.createClass({
   },
 
   render() {
-    if (Platform.OS !== 'android') {
-      return (
-        <View style={{backgroundColor: 'red'}}>
-          <Text style={{color: 'white'}}>
-            borderStyle is only supported on android for now.
-          </Text>
-        </View>
-      );
-    }
-
     return (
       <TouchableWithoutFeedback onPress={this._handlePress}>
         <View>
diff --git a/Examples/UIExplorer/ViewPagerAndroidExample.android.js b/Examples/UIExplorer/ViewPagerAndroidExample.android.js
index e4f33458e95b..0e672ee8cf99 100644
--- a/Examples/UIExplorer/ViewPagerAndroidExample.android.js
+++ b/Examples/UIExplorer/ViewPagerAndroidExample.android.js
@@ -25,6 +25,8 @@ var {
   ViewPagerAndroid,
 } = React;
 
+import type { ViewPagerScrollState } from 'ViewPagerAndroid';
+
 var PAGES = 5;
 var BGCOLOR = ['#fdc08e', '#fff6b9', '#99d1b7', '#dde5fe', '#f79273'];
 var IMAGE_URIS = [
@@ -114,6 +116,10 @@ var ViewPagerAndroidExample = React.createClass({
     this.setState({progress: e.nativeEvent});
   },
 
+  onPageScrollStateChanged: function(state : ViewPagerScrollState) {
+    this.setState({scrollState: state});
+  },
+
   move: function(delta) {
     var page = this.state.page + delta;
     this.go(page);
@@ -155,6 +161,7 @@ var ViewPagerAndroidExample = React.createClass({
           initialPage={0}
           onPageScroll={this.onPageScroll}
           onPageSelected={this.onPageSelected}
+          onPageScrollStateChanged={this.onPageScrollStateChanged}
           ref={viewPager => { this.viewPager = viewPager; }}>
           {pages}
         </ViewPagerAndroid>
@@ -170,6 +177,7 @@ var ViewPagerAndroidExample = React.createClass({
               enabled={true}
               onPress={() => this.setState({animationsAreEnabled: true})}
             /> }
+          <Text style={styles.scrollStateText}>ScrollState[ {this.state.scrollState} ]</Text>
         </View>
         <View style={styles.buttons}>
           <Button text="Start" enabled={page > 0} onPress={() => this.go(0)}/>
@@ -207,6 +215,9 @@ var styles = StyleSheet.create({
   buttonText: {
     color: 'white',
   },
+  scrollStateText: {
+    color: '#99d1b7',
+  },
   container: {
     flex: 1,
     backgroundColor: 'white',
diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js
index f8183b12b29b..1b5f836df601 100644
--- a/Examples/UIExplorer/WebViewExample.js
+++ b/Examples/UIExplorer/WebViewExample.js
@@ -20,6 +20,7 @@ var {
   StyleSheet,
   Text,
   TextInput,
+  TouchableWithoutFeedback,
   TouchableOpacity,
   View,
   WebView
@@ -49,7 +50,11 @@ var WebViewExample = React.createClass({
   inputText: '',
 
   handleTextInputChange: function(event) {
-    this.inputText = event.nativeEvent.text;
+    var url = event.nativeEvent.text;
+    if (!/^[a-zA-Z-_]+:/.test(url)) {
+      url = 'http://' + url;
+    }
+    this.inputText = url;
   },
 
   render: function() {
@@ -93,8 +98,10 @@ var WebViewExample = React.createClass({
           ref={WEBVIEW_REF}
           automaticallyAdjustContentInsets={false}
           style={styles.webView}
-          url={this.state.url}
-          javaScriptEnabledAndroid={true}
+          source={{uri: this.state.url}}
+          javaScriptEnabled={true}
+          domStorageEnabled={true}
+          decelerationRate="normal"
           onNavigationStateChange={this.onNavigationStateChange}
           onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest}
           startInLoadingState={true}
@@ -148,12 +155,66 @@ var WebViewExample = React.createClass({
         url: url,
       });
     }
-    // dismiss keyoard
+    // dismiss keyboard
     this.refs[TEXT_INPUT_REF].blur();
   },
 
 });
 
+var Button = React.createClass({
+  _handlePress: function() {
+    if (this.props.enabled !== false && this.props.onPress) {
+      this.props.onPress();
+    }
+  },
+  render: function() {
+    return (
+      <TouchableWithoutFeedback onPress={this._handlePress}>
+        <View style={[styles.button, this.props.enabled ? {} : styles.buttonDisabled]}>
+          <Text style={styles.buttonText}>{this.props.text}</Text>
+        </View>
+      </TouchableWithoutFeedback>
+    );
+  }
+});
+
+var ScaledWebView = React.createClass({
+
+  getInitialState: function() {
+    return {
+      scalingEnabled: true,
+    }
+  },
+
+  render: function() {
+    return (
+      <View>
+        <WebView
+          style={{
+            backgroundColor: BGWASH,
+            height: 200,
+          }}
+          source={{uri: 'https://facebook.github.io/react/'}}
+          scalesPageToFit={this.state.scalingEnabled}
+        />
+        <View style={styles.buttons}>
+        { this.state.scalingEnabled ?
+          <Button
+            text="Scaling:ON"
+            enabled={true}
+            onPress={() => this.setState({scalingEnabled: false})}
+          /> :
+          <Button
+            text="Scaling:OFF"
+            enabled={true}
+            onPress={() => this.setState({scalingEnabled: true})}
+          /> }
+        </View>
+      </View>
+    );
+  },
+})
+
 var styles = StyleSheet.create({
   container: {
     flex: 1,
@@ -223,14 +284,110 @@ var styles = StyleSheet.create({
     width: 20,
     marginRight: 6,
   },
+  buttons: {
+    flexDirection: 'row',
+    height: 30,
+    backgroundColor: 'black',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+  },
+  button: {
+    flex: 0.5,
+    width: 0,
+    margin: 5,
+    borderColor: 'gray',
+    borderWidth: 1,
+    backgroundColor: 'gray',
+  },
 });
 
+const HTML = `
+<!DOCTYPE html>\n
+<html>
+  <head>
+    <title>Hello Static World</title>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <meta name="viewport" content="width=320, user-scalable=no">
+    <style type="text/css">
+      body {
+        margin: 0;
+        padding: 0;
+        font: 62.5% arial, sans-serif;
+        background: #ccc;
+      }
+      h1 {
+        padding: 45px;
+        margin: 0;
+        text-align: center;
+        color: #33f;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Hello Static World</h1>
+  </body>
+</html>
+`;
+
 exports.displayName = (undefined: ?string);
 exports.title = '<WebView>';
 exports.description = 'Base component to display web content';
 exports.examples = [
   {
-    title: 'WebView',
+    title: 'Simple Browser',
     render(): ReactElement { return <WebViewExample />; }
+  },
+  {
+    title: 'Scale Page to Fit',
+    render(): ReactElement { return <ScaledWebView/>; }
+  },
+  {
+    title: 'Bundled HTML',
+    render(): ReactElement {
+      return (
+        <WebView
+          style={{
+            backgroundColor: BGWASH,
+            height: 100,
+          }}
+          source={require('./helloworld.html')}
+          scalesPageToFit={true}
+        />
+      );
+    }
+  },
+  {
+    title: 'Static HTML',
+    render(): ReactElement {
+      return (
+        <WebView
+          style={{
+            backgroundColor: BGWASH,
+            height: 100,
+          }}
+          source={{html: HTML}}
+          scalesPageToFit={true}
+        />
+      );
+    }
+  },
+  {
+    title: 'POST Test',
+    render(): ReactElement {
+      return (
+        <WebView
+          style={{
+            backgroundColor: BGWASH,
+            height: 100,
+          }}
+          source={{
+            uri: 'http://www.posttestserver.com/post.php',
+            method: 'POST',
+            body: 'foo=bar&bar=foo'
+          }}
+          scalesPageToFit={false}
+        />
+      );
+    }
   }
 ];
diff --git a/Examples/UIExplorer/XHRExample.android.js b/Examples/UIExplorer/XHRExample.android.js
index ab272d133da2..55b1a51787c8 100644
--- a/Examples/UIExplorer/XHRExample.android.js
+++ b/Examples/UIExplorer/XHRExample.android.js
@@ -17,7 +17,6 @@
 
 var React = require('react-native');
 var {
-  PixelRatio,
   ProgressBarAndroid,
   StyleSheet,
   Text,
@@ -27,8 +26,11 @@ var {
 } = React;
 
 var XHRExampleHeaders = require('./XHRExampleHeaders');
+var XHRExampleCookies = require('./XHRExampleCookies');
+var XHRExampleFetch = require('./XHRExampleFetch');
 
-// TODO t7093728 This is a simlified XHRExample.ios.js.
+
+// TODO t7093728 This is a simplified XHRExample.ios.js.
 // Once we have Camera roll, Toast, Intent (for opening URLs)
 // we should make this consistent with iOS.
 
@@ -279,11 +281,21 @@ exports.examples = [{
   render() {
     return <FormUploader/>;
   }
+}, {
+  title: 'Fetch Test',
+  render() {
+    return <XHRExampleFetch/>;
+  }
 }, {
   title: 'Headers',
   render() {
     return <XHRExampleHeaders/>;
   }
+}, {
+  title: 'Cookies',
+  render() {
+    return <XHRExampleCookies/>;
+  }
 }];
 
 var styles = StyleSheet.create({
@@ -299,7 +311,7 @@ var styles = StyleSheet.create({
     flexDirection: 'row',
     paddingVertical: 8,
     alignItems: 'center',
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: 'grey',
   },
   textButton: {
@@ -313,7 +325,6 @@ var styles = StyleSheet.create({
     borderRadius: 3,
     borderColor: 'grey',
     borderWidth: 1,
-    height: 30,
     paddingLeft: 8,
   },
   equalSign: {
diff --git a/Examples/UIExplorer/XHRExample.ios.js b/Examples/UIExplorer/XHRExample.ios.js
index cc83ccf5837c..a9de630e2c2d 100644
--- a/Examples/UIExplorer/XHRExample.ios.js
+++ b/Examples/UIExplorer/XHRExample.ios.js
@@ -21,7 +21,6 @@ var {
   CameraRoll,
   Image,
   LinkingIOS,
-  PixelRatio,
   ProgressViewIOS,
   StyleSheet,
   Text,
@@ -31,8 +30,10 @@ var {
 } = React;
 
 var XHRExampleHeaders = require('./XHRExampleHeaders');
+var XHRExampleFetch = require('./XHRExampleFetch');
 
 class Downloader extends React.Component {
+  state: any;
 
   xhr: XMLHttpRequest;
   cancelled: boolean;
@@ -120,6 +121,7 @@ class Downloader extends React.Component {
 var PAGE_SIZE = 20;
 
 class FormUploader extends React.Component {
+  state: any;
 
   _isMounted: boolean;
   _fetchRandomPhoto: () => void;
@@ -304,54 +306,6 @@ class FormUploader extends React.Component {
   }
 }
 
-class FetchTest extends React.Component {
-
-  constructor(props) {
-    super(props);
-    this.state = {
-     responseText: null,
-    };
-  }
-
-  submit(uri: String) {
-    fetch(uri).then((response) => {
-      return response.text();
-    }).then((body) => {
-      this.setState({responseText: body});
-    });
-  }
-
-  render() {
-
-    var response = this.state.responseText ? (
-      <View style={{marginTop: 10}}>
-        <Text style={styles.label}>Server response:</Text>
-        <TextInput
-          editable={false}
-          multiline={true}
-          defaultValue={this.state.responseText}
-          style={styles.textOutput}
-        />
-      </View>
-    ) : null;
-
-    return (
-      <View>
-        <Text style={styles.label}>Edit URL to submit:</Text>
-        <TextInput
-          returnKeyType="go"
-          defaultValue="http://www.posttestserver.com/post.php"
-          onSubmitEditing={(event)=> {
-            this.submit(event.nativeEvent.text);
-          }}
-          style={styles.textInput}
-        />
-        {response}
-      </View>
-    );
-  }
-}
-
 exports.framework = 'React';
 exports.title = 'XMLHttpRequest';
 exports.description = 'XMLHttpRequest';
@@ -366,9 +320,9 @@ exports.examples = [{
     return <FormUploader/>;
   }
 }, {
-  title: 'fetch test',
+  title: 'Fetch Test',
   render() {
-    return <FetchTest/>;
+    return <XHRExampleFetch/>;
   }
 }, {
   title: 'Headers',
@@ -390,7 +344,7 @@ var styles = StyleSheet.create({
     flexDirection: 'row',
     paddingVertical: 8,
     alignItems: 'center',
-    borderBottomWidth: 1 / PixelRatio.get(),
+    borderBottomWidth: StyleSheet.hairlineWidth,
     borderBottomColor: 'grey',
   },
   photoLabel: {
@@ -432,19 +386,4 @@ var styles = StyleSheet.create({
     fontSize: 16,
     fontWeight: '500',
   },
-  label: {
-    flex: 1,
-    color: '#aaa',
-    fontWeight: '500',
-    height: 20,
-  },
-  textOutput: {
-    flex: 1,
-    fontSize: 17,
-    borderRadius: 3,
-    borderColor: 'grey',
-    borderWidth: 1,
-    height: 200,
-    paddingLeft: 8,
-  },
 });
diff --git a/Examples/UIExplorer/XHRExampleCookies.js b/Examples/UIExplorer/XHRExampleCookies.js
new file mode 100644
index 000000000000..d4cfad0e5f14
--- /dev/null
+++ b/Examples/UIExplorer/XHRExampleCookies.js
@@ -0,0 +1,131 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  StyleSheet,
+  Text,
+  TouchableHighlight,
+  View,
+} = React;
+
+var RCTNetworking = require('RCTNetworking');
+
+class XHRExampleCookies extends React.Component {
+  state: any;
+  cancelled: boolean;
+
+  constructor(props: any) {
+    super(props);
+    this.cancelled = false;
+    this.state = {
+      status: '',
+      a: 1,
+      b: 2,
+    };
+  }
+
+  setCookie(domain: string) {
+    var {a, b} = this.state;
+    var url = `https://${domain}/cookies/set?a=${a}&b=${b}`;
+    fetch(url).then((response) => {
+      this.setStatus(`Cookies a=${a}, b=${b} set`);
+    });
+
+    this.setState({
+      status: 'Setting cookies...',
+      a: a + 1,
+      b: b + 2,
+    });
+  }
+
+  getCookies(domain: string) {
+    fetch(`https://${domain}/cookies`).then((response) => {
+      return response.json();
+    }).then((data) => {
+      this.setStatus(`Got cookies ${JSON.stringify(data.cookies)} from server`);
+    });
+
+    this.setStatus('Getting cookies...');
+  }
+
+  clearCookies() {
+    RCTNetworking.clearCookies((cleared) => {
+      this.setStatus('Cookies cleared, had cookies=' + cleared);
+    });
+  }
+
+  setStatus(status: string) {
+    this.setState({status});
+  }
+
+  render() {
+    return (
+      <View>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={this.setCookie.bind(this, 'httpbin.org')}>
+          <View style={styles.button}>
+            <Text>Set cookie</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={this.setCookie.bind(this, 'eu.httpbin.org')}>
+          <View style={styles.button}>
+            <Text>Set cookie (EU)</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={this.getCookies.bind(this, 'httpbin.org')}>
+          <View style={styles.button}>
+            <Text>Get cookies</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={this.getCookies.bind(this, 'eu.httpbin.org')}>
+          <View style={styles.button}>
+            <Text>Get cookies (EU)</Text>
+          </View>
+        </TouchableHighlight>
+        <TouchableHighlight
+          style={styles.wrapper}
+          onPress={this.clearCookies.bind(this)}>
+          <View style={styles.button}>
+            <Text>Clear cookies</Text>
+          </View>
+        </TouchableHighlight>
+        <Text>{this.state.status}</Text>
+      </View>
+    );
+  }
+}
+
+var styles = StyleSheet.create({
+  wrapper: {
+    borderRadius: 5,
+    marginBottom: 5,
+  },
+  button: {
+    backgroundColor: '#eeeeee',
+    padding: 8,
+  },
+});
+
+module.exports = XHRExampleCookies;
diff --git a/Examples/UIExplorer/XHRExampleFetch.js b/Examples/UIExplorer/XHRExampleFetch.js
new file mode 100644
index 000000000000..87e8e1bb6465
--- /dev/null
+++ b/Examples/UIExplorer/XHRExampleFetch.js
@@ -0,0 +1,114 @@
+/**
+ * The examples provided by Facebook are for non-commercial testing and
+ * evaluation purposes only.
+ *
+ * Facebook reserves all rights not expressly granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
+ * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @flow
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  StyleSheet,
+  Text,
+  TextInput,
+  View,
+  Platform,
+} = React;
+
+
+class XHRExampleFetch extends React.Component {
+  state: any;
+  responseURL: ?string;
+
+  constructor(props: any) {
+    super(props);
+    this.state = {
+     responseText: null,
+    };
+    this.responseURL = null;
+  }
+
+  submit(uri: String) {
+    fetch(uri).then((response) => {
+      this.responseURL = response.url;
+      return response.text();
+    }).then((body) => {
+      this.setState({responseText: body});
+    });
+  }
+
+  render() {
+
+    var responseURL = this.responseURL ? (
+      <View style={{marginTop: 10}}>
+        <Text style={styles.label}>Server response URL:</Text>
+        <Text>{this.responseURL}</Text>
+      </View>
+    ) : null;
+
+    var response = this.state.responseText ? (
+      <View style={{marginTop: 10}}>
+        <Text style={styles.label}>Server response:</Text>
+        <TextInput
+          editable={false}
+          multiline={true}
+          defaultValue={this.state.responseText}
+          style={styles.textOutput}
+        />
+      </View>
+    ) : null;
+
+    return (
+      <View>
+        <Text style={styles.label}>Edit URL to submit:</Text>
+        <TextInput
+          returnKeyType="go"
+          defaultValue="http://www.posttestserver.com/post.php"
+          onSubmitEditing={(event)=> {
+            this.submit(event.nativeEvent.text);
+          }}
+          style={styles.textInput}
+        />
+        {responseURL}
+        {response}
+      </View>
+    );
+  }
+}
+
+var styles = StyleSheet.create({
+  textInput: {
+    flex: 1,
+    borderRadius: 3,
+    borderColor: 'grey',
+    borderWidth: 1,
+    height: Platform.OS === 'android' ? 44 : 30,
+    paddingLeft: 8,
+  },
+  label: {
+    flex: 1,
+    color: '#aaa',
+    fontWeight: '500',
+    height: 20,
+  },
+  textOutput: {
+    flex: 1,
+    fontSize: 17,
+    borderRadius: 3,
+    borderColor: 'grey',
+    borderWidth: 1,
+    height: 200,
+    paddingLeft: 8,
+  },
+});
+
+module.exports = XHRExampleFetch;
diff --git a/Examples/UIExplorer/XHRExampleHeaders.js b/Examples/UIExplorer/XHRExampleHeaders.js
index e9f5e2c4c974..e83c1b071d95 100644
--- a/Examples/UIExplorer/XHRExampleHeaders.js
+++ b/Examples/UIExplorer/XHRExampleHeaders.js
@@ -11,6 +11,7 @@
  * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  *
+ * @noflow
  */
 'use strict';
 
@@ -113,4 +114,4 @@ var styles = StyleSheet.create({
   },
 });
 
-module.exports = XHRExampleHeaders;
\ No newline at end of file
+module.exports = XHRExampleHeaders;
diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
index b69776f9a5d4..c1e8475153ee 100644
--- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
+++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,10 @@
           package="com.facebook.react.uiapp" >
 
   <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+  <uses-permission android:name="android.permission.VIBRATE"/>
 
   <application
       android:allowBackup="true"
diff --git a/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java b/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
index c1dd73f6d524..f9a50e93f44b 100644
--- a/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
+++ b/Examples/UIExplorer/android/app/src/main/java/UIExplorerActivity.java
@@ -14,76 +14,40 @@
 
 package com.facebook.react.uiapp;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.KeyEvent;
-
-import com.facebook.react.LifecycleState;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.ReactRootView;
-import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactPackage;
 import com.facebook.react.shell.MainReactPackage;
+import com.facebook.react.uiapp.R;
 
-public class UIExplorerActivity extends Activity implements DefaultHardwareBackBtnHandler {
-
-  private ReactInstanceManager mReactInstanceManager;
-
-  @Override
-  protected void onCreate(Bundle savedInstanceState) {
-    super.onCreate(savedInstanceState);
-    setContentView(R.layout.activity_main);
-
-    mReactInstanceManager = ReactInstanceManager.builder()
-        .setApplication(getApplication())
-        .setBundleAssetName("UIExplorerApp.android.bundle")
-        .setJSMainModuleName("Examples/UIExplorer/UIExplorerApp.android")
-        .addPackage(new MainReactPackage())
-        .setUseDeveloperSupport(true)
-        .setInitialLifecycleState(LifecycleState.RESUMED)
-        .build();
-
-    ((ReactRootView) findViewById(R.id.react_root_view))
-        .startReactApplication(mReactInstanceManager, "UIExplorerApp", null);
-  }
+import java.util.Arrays;
+import java.util.List;
+import javax.annotation.Nullable;
 
+public class UIExplorerActivity extends ReactActivity {
   @Override
-  public boolean onKeyUp(int keyCode, KeyEvent event) {
-    if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
-      mReactInstanceManager.showDevOptionsDialog();
-      return true;
-    }
-    return super.onKeyUp(keyCode, event);
+  protected String getMainComponentName() {
+      return "UIExplorerApp";
   }
 
   @Override
-  protected void onPause() {
-    super.onPause();
-
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onPause();
-    }
-  }
+  protected @Nullable String getBundleAssetName() {
+    return "UIExplorerApp.android.bundle";
+  };
 
   @Override
-  protected void onResume() {
-    super.onResume();
-
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onResume(this, this);
-    }
+  protected String getJSMainModuleName() {
+    return "Examples/UIExplorer/UIExplorerApp.android";
   }
 
   @Override
-  public void onBackPressed() {
-    if (mReactInstanceManager != null) {
-      mReactInstanceManager.onBackPressed();
-    } else {
-      super.onBackPressed();
-    }
+  protected boolean getUseDeveloperSupport() {
+      return true;
   }
 
   @Override
-  public void invokeDefaultOnBackPressed() {
-    super.onBackPressed();
+  protected List<ReactPackage> getPackages() {
+    return Arrays.<ReactPackage>asList(
+      new MainReactPackage()
+    );
   }
 }
diff --git a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_highlighted.png b/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_highlighted.png
deleted file mode 100644
index b33726757ed4..000000000000
Binary files a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_highlighted.png and /dev/null differ
diff --git a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_normal.png b/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_normal.png
deleted file mode 100644
index 6491689fbbc8..000000000000
Binary files a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_comment_normal.png and /dev/null differ
diff --git a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_normal.png b/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_normal.png
deleted file mode 100644
index 72683dfac123..000000000000
Binary files a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_normal.png and /dev/null differ
diff --git a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_selected.png b/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_selected.png
deleted file mode 100644
index 79eb69cf92b3..000000000000
Binary files a/Examples/UIExplorer/android/app/src/main/res/drawable/uie_thumb_selected.png and /dev/null differ
diff --git a/Examples/UIExplorer/createExamplePage.js b/Examples/UIExplorer/createExamplePage.js
index 41068816096d..3d722029516c 100644
--- a/Examples/UIExplorer/createExamplePage.js
+++ b/Examples/UIExplorer/createExamplePage.js
@@ -24,7 +24,7 @@ var ReactNative = require('ReactNative');
 var UIExplorerBlock = require('./UIExplorerBlock');
 var UIExplorerPage = require('./UIExplorerPage');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 import type { Example, ExampleModule } from 'ExampleTypes';
 
@@ -39,15 +39,18 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
     },
 
     getBlock: function(example: Example, i) {
-      if (example.platform) {
-        if (Platform.OS !== example.platform) {
-          return;
+      // Filter platform-specific examples
+      var {title, description, platform} = example;
+      if (platform) {
+        if (Platform.OS !== platform) {
+          return null;
         }
-        example.title += ' (' + example.platform + ' only)';
+        title += ' (' + platform + ' only)';
       }
       // Hack warning: This is a hack because the www UI explorer requires
       // renderComponent to be called.
       var originalRender = React.render;
+      // $FlowFixMe React.renderComponent was deprecated in 0.12, should this be React.render?
       var originalRenderComponent = React.renderComponent;
       var originalIOSRender = ReactNative.render;
       var originalIOSRenderComponent = ReactNative.renderComponent;
@@ -74,8 +77,8 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
       return (
         <UIExplorerBlock
           key={i}
-          title={example.title}
-          description={example.description}>
+          title={title}
+          description={description}>
           {renderedComponent}
         </UIExplorerBlock>
       );
diff --git a/Examples/UIExplorer/helloworld.html b/Examples/UIExplorer/helloworld.html
new file mode 100644
index 000000000000..7289b0f87944
--- /dev/null
+++ b/Examples/UIExplorer/helloworld.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Hello Bundled World</title>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8">
+    <meta name="viewport" content="width=320, user-scalable=no">
+    <style type="text/css">
+      body {
+        margin: 0;
+        padding: 0;
+        font: 62.5% arial, sans-serif;
+        background: #ccc;
+      }
+      h1 {
+        padding: 45px;
+        margin: 0;
+        text-align: center;
+        color: #f33;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Hello Bundled World</h1>
+  </body>
+</html>
diff --git a/Examples/UIExplorer/slider-left.png b/Examples/UIExplorer/slider-left.png
new file mode 100644
index 000000000000..dff4e11e19d6
Binary files /dev/null and b/Examples/UIExplorer/slider-left.png differ
diff --git a/Examples/UIExplorer/slider-left@2x.png b/Examples/UIExplorer/slider-left@2x.png
new file mode 100644
index 000000000000..83c6dd8204eb
Binary files /dev/null and b/Examples/UIExplorer/slider-left@2x.png differ
diff --git a/Examples/UIExplorer/slider-right.png b/Examples/UIExplorer/slider-right.png
new file mode 100644
index 000000000000..57e8c7d3fe56
Binary files /dev/null and b/Examples/UIExplorer/slider-right.png differ
diff --git a/Examples/UIExplorer/slider-right@2x.png b/Examples/UIExplorer/slider-right@2x.png
new file mode 100644
index 000000000000..8546ba8c9446
Binary files /dev/null and b/Examples/UIExplorer/slider-right@2x.png differ
diff --git a/Examples/UIExplorer/slider.png b/Examples/UIExplorer/slider.png
new file mode 100644
index 000000000000..9141991010c9
Binary files /dev/null and b/Examples/UIExplorer/slider.png differ
diff --git a/Examples/UIExplorer/slider@2x.png b/Examples/UIExplorer/slider@2x.png
new file mode 100644
index 000000000000..396614fa9b9b
Binary files /dev/null and b/Examples/UIExplorer/slider@2x.png differ
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/uie_comment_highlighted@2x.png b/Examples/UIExplorer/uie_comment_highlighted@2x.png
similarity index 100%
rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_highlighted.imageset/uie_comment_highlighted@2x.png
rename to Examples/UIExplorer/uie_comment_highlighted@2x.png
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/uie_comment_normal@2x.png b/Examples/UIExplorer/uie_comment_normal@2x.png
similarity index 100%
rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_comment_normal.imageset/uie_comment_normal@2x.png
rename to Examples/UIExplorer/uie_comment_normal@2x.png
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png b/Examples/UIExplorer/uie_thumb_big.png
similarity index 100%
rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_big.imageset/uie_thumb_big.png
rename to Examples/UIExplorer/uie_thumb_big.png
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/uie_thumb_normal@2x.png b/Examples/UIExplorer/uie_thumb_normal@2x.png
similarity index 100%
rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_normal.imageset/uie_thumb_normal@2x.png
rename to Examples/UIExplorer/uie_thumb_normal@2x.png
diff --git a/Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/uie_thumb_selected@2x.png b/Examples/UIExplorer/uie_thumb_selected@2x.png
similarity index 100%
rename from Examples/UIExplorer/UIExplorer/Images.xcassets/uie_thumb_selected.imageset/uie_thumb_selected@2x.png
rename to Examples/UIExplorer/uie_thumb_selected@2x.png
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000000..8183779c30ac
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,31 @@
+Hey there and thank you for using React Native!
+
+React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly.
+
+Please read the following tips before filing an issue:
+
+Is this something you can debug and fix?
+-------------------------------------------------------
+
+Can you see anything in the logs? Can you debug why this happens? **Send a pull request**! Bug fixes and documentation fixes are very welcome.
+
+Have a question or not sure you've found a bug?
+-----------------------------------------------------------------
+Please ask your question on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native`. StackOverflow is amazing for Q&A. Because of the reputation system it is likely the community will see and answer your question there.
+
+Make sure you **include a short code snippet to demonstrate the problem**.
+
+Have an idea for a feature?
+------------------------------------
+Post the feature request on [Product Pains](https://productpains.com/product/react-native/). It has a voting system that lets us focus on the most important issues the community is experiencing. The higher number of votes the more likely it is a feature will get implemented. You could always implement it yourself, too.
+
+None of the above, want to create a GitHub issue
+------------------------------------------------------------------
+
+Make sure to add **all the information needed to understand the issue** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information.
+
+- If this is a bug, provide a **minimal code snippet** that reproduces it
+- Provide **screenshots** where appropriate
+- What's the **version** of React Native you're using?
+- Does this occur on iOS, Android or both?
+- Are you using Mac, Linux or Windows?
diff --git a/IntegrationTests/AppEventsTest.js b/IntegrationTests/AppEventsTest.js
index 7362072ffd48..c003b014d1ea 100644
--- a/IntegrationTests/AppEventsTest.js
+++ b/IntegrationTests/AppEventsTest.js
@@ -24,8 +24,15 @@ var deepDiffer = require('deepDiffer');
 
 var TEST_PAYLOAD = {foo: 'bar'};
 
+type AppEvent = { data: Object, ts: number, };
+type State = {
+  sent: 'none' | AppEvent,
+  received: 'none' | AppEvent,
+  elapsed?: string,
+};
+
 var AppEventsTest = React.createClass({
-  getInitialState: function() {
+  getInitialState(): State {
     return {sent: 'none', received: 'none'};
   },
   componentDidMount: function() {
diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js
index 3bd313a7fd0e..06ea818e4511 100644
--- a/IntegrationTests/AsyncStorageTest.js
+++ b/IntegrationTests/AsyncStorageTest.js
@@ -142,12 +142,30 @@ function testMerge() {
         expectAsyncNoError('testMerge/setItem', err3);
         expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge');
         updateMessage('objects deeply merged\nDone!');
+        runTestCase('multi set and get', testOptimizedMultiGet);
+      });
+    });
+  });
+}
+
+function testOptimizedMultiGet() {
+  let batch = [[KEY_1, VAL_1], [KEY_2, VAL_2]];
+  let keys = batch.map(([key, value]) => key);
+  AsyncStorage.multiSet(batch, (err1) => {
+    // yes, twice on purpose
+    ;[1, 2].forEach((i) => {
+      expectAsyncNoError(`${i} testOptimizedMultiGet/multiSet`, err1);
+      AsyncStorage.multiGet(keys, (err2, result) => {
+        expectAsyncNoError(`${i} testOptimizedMultiGet/multiGet`, err2);
+        expectEqual(result, batch, `${i} testOptimizedMultiGet multiGet`);
+        updateMessage('multiGet([key_1, key_2]) correctly returned ' + JSON.stringify(result));
         done();
       });
     });
   });
 }
 
+
 var AsyncStorageTest = React.createClass({
   getInitialState() {
     return {
diff --git a/IntegrationTests/IntegrationTestHarnessTest.js b/IntegrationTests/IntegrationTestHarnessTest.js
index 27ef7428dc4b..bfc9c5d88bae 100644
--- a/IntegrationTests/IntegrationTestHarnessTest.js
+++ b/IntegrationTests/IntegrationTestHarnessTest.js
@@ -10,7 +10,7 @@
  */
 'use strict';
 
-var requestAnimationFrame = require('requestAnimationFrame');
+var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
 var React = require('react-native');
 var {
   Text,
diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js
index 95c2697c17ab..54fca9a4bd49 100644
--- a/IntegrationTests/IntegrationTestsApp.js
+++ b/IntegrationTests/IntegrationTestsApp.js
@@ -36,10 +36,15 @@ TESTS.forEach(
   (test) => AppRegistry.registerComponent(test.displayName, () => test)
 );
 
+// Modules required for integration tests
+require('LoggingTestModule');
+
+type Test = any;
+
 var IntegrationTestsApp = React.createClass({
   getInitialState: function() {
     return {
-      test: null,
+      test: (null: ?Test),
     };
   },
   render: function() {
diff --git a/IntegrationTests/LayoutEventsTest.js b/IntegrationTests/LayoutEventsTest.js
index 9f7eb7d54b35..2cfe13f9b9dd 100644
--- a/IntegrationTests/LayoutEventsTest.js
+++ b/IntegrationTests/LayoutEventsTest.js
@@ -27,19 +27,38 @@ function debug() {
   // console.log.apply(null, arguments);
 }
 
+type Layout = {
+  x: number;
+  y: number;
+  width: number;
+  height: number;
+};
 type LayoutEvent = {
   nativeEvent: {
-    layout: {
-      x: number;
-      y: number;
-      width: number;
-      height: number;
-    };
+    layout: Layout;
   };
 };
+type Style = {
+  margin?: number,
+  padding?: number,
+  borderColor?: string,
+  borderWidth?: number,
+  backgroundColor?: string,
+  width?: number,
+};
+
+type State = {
+  didAnimation: boolean,
+  extraText?: string,
+  imageLayout?: Layout,
+  textLayout?: Layout,
+  viewLayout?: Layout,
+  viewStyle?: Style,
+  containerStyle?: Style,
+};
 
 var LayoutEventsTest = React.createClass({
-  getInitialState: function() {
+  getInitialState(): State {
     return {
       didAnimation: false,
     };
diff --git a/IntegrationTests/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js
new file mode 100644
index 000000000000..cb801a0af088
--- /dev/null
+++ b/IntegrationTests/LoggingTestModule.js
@@ -0,0 +1,41 @@
+/**
+ * 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 LoggingTestModule
+ */
+'use strict';
+
+var BatchedBridge = require('BatchedBridge');
+
+var warning = require('fbjs/lib/warning');
+var invariant = require('fbjs/lib/invariant');
+
+var LoggingTestModule = {
+  logToConsole: function(str) {
+    console.log(str);
+  },
+  warning: function(str) {
+    warning(false, str);
+  },
+  invariant: function(str) {
+    invariant(false, str);
+  },
+  logErrorToConsole: function(str) {
+    console.error(str);
+  },
+  throwError: function(str) {
+    throw new Error(str);
+  }
+};
+
+BatchedBridge.registerCallableModule(
+  'LoggingTestModule',
+  LoggingTestModule
+);
+
+module.exports = LoggingTestModule;
diff --git a/IntegrationTests/PropertiesUpdateTest.js b/IntegrationTests/PropertiesUpdateTest.js
new file mode 100644
index 000000000000..da563ec94a55
--- /dev/null
+++ b/IntegrationTests/PropertiesUpdateTest.js
@@ -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.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+  View,
+} = React;
+
+var { TestModule } = React.addons;
+
+var PropertiesUpdateTest = React.createClass({
+
+  render() {
+    if (this.props.markTestPassed) {
+      TestModule.markTestPassed(true);
+    }
+    return (
+      <View/>
+    );
+  }
+});
+
+PropertiesUpdateTest.displayName = 'PropertiesUpdateTest';
+
+module.exports = PropertiesUpdateTest;
diff --git a/IntegrationTests/RCTRootViewIntegrationTestApp.js b/IntegrationTests/RCTRootViewIntegrationTestApp.js
new file mode 100644
index 000000000000..5a126a601920
--- /dev/null
+++ b/IntegrationTests/RCTRootViewIntegrationTestApp.js
@@ -0,0 +1,94 @@
+/**
+ * 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 RCTRootViewIntegrationTestsApp
+ */
+'use strict';
+
+require('regenerator/runtime');
+
+var React = require('react-native');
+
+var {
+  AppRegistry,
+  ScrollView,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  View,
+} = React;
+
+/* Keep this list in sync with RCTRootViewIntegrationTests.m */
+var TESTS = [
+  require('./PropertiesUpdateTest'),
+  require('./ReactContentSizeUpdateTest'),
+  require('./SizeFlexibilityUpdateTest'),
+];
+
+TESTS.forEach(
+  (test) => AppRegistry.registerComponent(test.displayName, () => test)
+);
+
+var RCTRootViewIntegrationTestsApp = React.createClass({
+  getInitialState: function() {
+    return {
+      test: null,
+    };
+  },
+  render: function() {
+    if (this.state.test) {
+      return (
+        <ScrollView>
+          <this.state.test />
+        </ScrollView>
+      );
+    }
+    return (
+      <View style={styles.container}>
+        <Text style={styles.row}>
+          Click on a test to run it in this shell for easier debugging and
+          development.  Run all tests in the testing environment with cmd+U in
+          Xcode.
+        </Text>
+        <View style={styles.separator} />
+        <ScrollView>
+          {TESTS.map((test) => [
+            <TouchableOpacity
+              onPress={() => this.setState({test})}
+              style={styles.row}>
+              <Text style={styles.testName}>
+                {test.displayName}
+              </Text>
+            </TouchableOpacity>,
+            <View style={styles.separator} />
+          ])}
+        </ScrollView>
+      </View>
+    );
+  }
+});
+
+var styles = StyleSheet.create({
+  container: {
+    backgroundColor: 'white',
+    marginTop: 40,
+    margin: 15,
+  },
+  row: {
+    padding: 10,
+  },
+  testName: {
+    fontWeight: '500',
+  },
+  separator: {
+    height: 1,
+    backgroundColor: '#bbbbbb',
+  },
+});
+
+AppRegistry.registerComponent('RCTRootViewIntegrationTestsApp', () => RCTRootViewIntegrationTestsApp);
diff --git a/IntegrationTests/ReactContentSizeUpdateTest.js b/IntegrationTests/ReactContentSizeUpdateTest.js
new file mode 100644
index 000000000000..73e1c5f5d3d3
--- /dev/null
+++ b/IntegrationTests/ReactContentSizeUpdateTest.js
@@ -0,0 +1,73 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var React = require('react-native');
+var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter');
+var Subscribable = require('Subscribable');
+var TimerMixin = require('react-timer-mixin');
+
+var { View } = React;
+
+var { TestModule } = React.addons;
+
+var reactViewWidth = 101;
+var reactViewHeight = 102;
+var newReactViewWidth = 201;
+var newReactViewHeight = 202;
+
+var ReactContentSizeUpdateTest = React.createClass({
+  mixins: [Subscribable.Mixin,
+           TimerMixin],
+
+  componentWillMount: function() {
+    this.addListenerOn(
+      RCTNativeAppEventEmitter,
+      'rootViewDidChangeIntrinsicSize',
+      this.rootViewDidChangeIntrinsicSize
+    );
+  },
+
+  getInitialState: function() {
+    return {
+      height: reactViewHeight,
+      width: reactViewWidth,
+    };
+  },
+
+  updateViewSize: function() {
+    this.setState({
+      height: newReactViewHeight,
+      width: newReactViewWidth,
+    });
+  },
+
+  componentDidMount: function() {
+    this.setTimeout(
+      () => { this.updateViewSize(); },
+      1000
+    );
+  },
+
+  rootViewDidChangeIntrinsicSize: function(intrinsicSize) {
+    if (intrinsicSize.height === newReactViewHeight && intrinsicSize.width === newReactViewWidth) {
+      TestModule.markTestPassed(true);
+    }
+  },
+
+  render() {
+    return (
+      <View style={{'height':this.state.height, 'width':this.state.width}}/>
+    );
+  }
+});
+
+ReactContentSizeUpdateTest.displayName = 'ReactContentSizeUpdateTest';
+
+module.exports = ReactContentSizeUpdateTest;
diff --git a/IntegrationTests/SimpleSnapshotTest.js b/IntegrationTests/SimpleSnapshotTest.js
index 2f0e93358a57..181077acd175 100644
--- a/IntegrationTests/SimpleSnapshotTest.js
+++ b/IntegrationTests/SimpleSnapshotTest.js
@@ -11,7 +11,7 @@
 'use strict';
 
 var React = require('react-native');
-var requestAnimationFrame = require('requestAnimationFrame');
+var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
 
 var {
   StyleSheet,
diff --git a/IntegrationTests/SizeFlexibilityUpdateTest.js b/IntegrationTests/SizeFlexibilityUpdateTest.js
new file mode 100644
index 000000000000..ddc016b70ac6
--- /dev/null
+++ b/IntegrationTests/SizeFlexibilityUpdateTest.js
@@ -0,0 +1,82 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var React = require('react-native');
+var RCTNativeAppEventEmitter = require('RCTNativeAppEventEmitter');
+var Subscribable = require('Subscribable');
+var { View } = React;
+
+var { TestModule } = React.addons;
+
+var reactViewWidth = 111;
+var reactViewHeight = 222;
+
+var finalState = false;
+
+var SizeFlexibilityUpdateTest = React.createClass({
+  mixins: [Subscribable.Mixin],
+
+  componentWillMount: function() {
+    this.addListenerOn(
+      RCTNativeAppEventEmitter,
+      'rootViewDidChangeIntrinsicSize',
+      this.rootViewDidChangeIntrinsicSize
+    );
+  },
+
+  markPassed: function() {
+    TestModule.markTestPassed(true);
+    finalState = true;
+  },
+
+  rootViewDidChangeIntrinsicSize: function(intrinsicSize) {
+
+    if (finalState) {
+      // If a test reaches its final state, it is not expected to do anything more
+      TestModule.markTestPassed(false);
+      return;
+    }
+
+    if (this.props.both) {
+      if (intrinsicSize.width === reactViewWidth && intrinsicSize.height === reactViewHeight) {
+        this.markPassed();
+        return;
+      }
+    }
+    if (this.props.height) {
+      if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height === reactViewHeight) {
+        this.markPassed();
+        return;
+      }
+    }
+    if (this.props.width) {
+      if (intrinsicSize.width === reactViewWidth && intrinsicSize.height !== reactViewHeight) {
+        this.markPassed();
+        return;
+      }
+    }
+    if (this.props.none) {
+      if (intrinsicSize.width !== reactViewWidth && intrinsicSize.height !== reactViewHeight) {
+        this.markPassed();
+        return;
+      }
+    }
+  },
+
+  render() {
+    return (
+      <View style={{'height':reactViewHeight, 'width':reactViewWidth}}/>
+    );
+  }
+});
+
+SizeFlexibilityUpdateTest.displayName = 'SizeFlexibilityUpdateTest';
+
+module.exports = SizeFlexibilityUpdateTest;
diff --git a/Libraries/ART/ARTSurfaceView.m b/Libraries/ART/ARTSurfaceView.m
index 8949e43c349d..f5c13651bf02 100644
--- a/Libraries/ART/ARTSurfaceView.m
+++ b/Libraries/ART/ARTSurfaceView.m
@@ -27,4 +27,9 @@ - (void)drawRect:(CGRect)rect
   }
 }
 
+- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
+{
+  self.backgroundColor = inheritedBackgroundColor;
+}
+
 @end
diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js
index 155b7d74e2f4..a6f5dc2342af 100644
--- a/Libraries/ActionSheetIOS/ActionSheetIOS.js
+++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js
@@ -13,7 +13,8 @@
 
 var RCTActionSheetManager = require('NativeModules').ActionSheetManager;
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
+var processColor = require('processColor');
 
 var ActionSheetIOS = {
   showActionSheetWithOptions(options: Object, callback: Function) {
@@ -26,11 +27,22 @@ var ActionSheetIOS = {
       'Must provide a valid callback'
     );
     RCTActionSheetManager.showActionSheetWithOptions(
-      options,
+      {...options, tintColor: processColor(options.tintColor)},
       callback
     );
   },
-
+  
+  /**
+   * Display the iOS share sheet. The `options` object should contain
+   * one or both of:
+   * 
+   * - `message` (string) - a message to share
+   * - `url` (string) - a URL to share
+   *
+   * NOTE: if `url` points to a local file, or is a base64-encoded
+   * uri, the file it points to will be loaded and shared directly.
+   * In this way, you can share images, videos, PDF files, etc.
+   */
   showShareActionSheetWithOptions(
     options: Object,
     failureCallback: Function,
@@ -49,7 +61,7 @@ var ActionSheetIOS = {
       'Must provide a valid successCallback'
     );
     RCTActionSheetManager.showShareActionSheetWithOptions(
-      options,
+      {...options, tintColor: processColor(options.tintColor)},
       failureCallback,
       successCallback
     );
diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
index 626da966ceac..18fc8596b115 100644
--- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m
+++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
@@ -16,7 +16,6 @@
 #import "RCTUIManager.h"
 
 @interface RCTActionSheetManager () <UIActionSheetDelegate>
-
 @end
 
 @implementation RCTActionSheetManager
@@ -67,6 +66,10 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
   NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
 
   UIViewController *controller = RCTKeyWindow().rootViewController;
+  while (controller.presentedViewController) {
+    controller = controller.presentedViewController;
+  }
+    
   if (controller == nil) {
     RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
     return;
@@ -139,6 +142,8 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
       alertController.popoverPresentationController.permittedArrowDirections = 0;
     }
     [controller presentViewController:alertController animated:YES completion:nil];
+
+    alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
   }
 }
 
@@ -151,14 +156,26 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
     return;
   }
 
-  NSMutableArray<id /* NSString or NSURL */> *items = [NSMutableArray array];
+  NSMutableArray<id> *items = [NSMutableArray array];
   NSString *message = [RCTConvert NSString:options[@"message"]];
   if (message) {
     [items addObject:message];
   }
   NSURL *URL = [RCTConvert NSURL:options[@"url"]];
   if (URL) {
-    [items addObject:URL];
+    if (URL.fileURL || [URL.scheme.lowercaseString isEqualToString:@"data"]) {
+      NSError *error;
+      NSData *data = [NSData dataWithContentsOfURL:URL
+                                           options:(NSDataReadingOptions)0
+                                             error:&error];
+      if (!data) {
+        failureCallback(error);
+        return;
+      }
+      [items addObject:data];
+    } else {
+      [items addObject:URL];
+    }
   }
   if (items.count == 0) {
     RCTLogError(@"No `url` or `message` to share");
@@ -166,6 +183,17 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
   }
 
   UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
+
+  NSString *subject = [RCTConvert NSString:options[@"subject"]];
+  if (subject) {
+    [shareController setValue:subject forKey:@"subject"];
+  }
+
+  NSArray *excludedActivityTypes = [RCTConvert NSStringArray:options[@"excludedActivityTypes"]];
+  if (excludedActivityTypes) {
+    shareController.excludedActivityTypes = excludedActivityTypes;
+  }
+
   UIViewController *controller = RCTKeyWindow().rootViewController;
 
 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
@@ -199,6 +227,8 @@ - (CGRect)sourceRectInView:(UIView *)sourceView
   }
 
   [controller presentViewController:shareController animated:YES completion:nil];
+
+  shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
 }
 
 #pragma mark UIActionSheetDelegate Methods
diff --git a/Libraries/Animated/examples/demo.html b/Libraries/Animated/examples/demo.html
index bc4c85e1f462..4f95e114fa86 100644
--- a/Libraries/Animated/examples/demo.html
+++ b/Libraries/Animated/examples/demo.html
@@ -326,7 +326,7 @@ <h2>stopAnimation</h2>
 
 <p><code>Animated</code> can offload the animation to a different thread (CoreAnimation, CSS transitions, main thread...) and we don't have a good way to know the real value. If you try to query the value then modify it, you are going to be out of sync and the result will look terrible.</p>
 
-<p>There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in <code>stopAnimation</code>. It will not suffer from beign out of sync since the animation is no longer running.</p>
+<p>There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in <code>stopAnimation</code>. It will not suffer from being out of sync since the animation is no longer running.</p>
 
 <script type="text/jsx;harmony=true;stripTypes=true">
 examplify(React.createClass({
diff --git a/Libraries/Animated/examples/pic1.jpg b/Libraries/Animated/examples/pic1.jpg
new file mode 100644
index 000000000000..a9f2e824fe69
Binary files /dev/null and b/Libraries/Animated/examples/pic1.jpg differ
diff --git a/Libraries/Animated/examples/pic2.jpg b/Libraries/Animated/examples/pic2.jpg
new file mode 100644
index 000000000000..34d30966ae98
Binary files /dev/null and b/Libraries/Animated/examples/pic2.jpg differ
diff --git a/Libraries/Animated/examples/pic3.jpg b/Libraries/Animated/examples/pic3.jpg
new file mode 100644
index 000000000000..47a97c801804
Binary files /dev/null and b/Libraries/Animated/examples/pic3.jpg differ
diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js
index b8b3aee77b96..8cfacf29860b 100644
--- a/Libraries/Animated/src/AnimatedImplementation.js
+++ b/Libraries/Animated/src/AnimatedImplementation.js
@@ -20,8 +20,8 @@ var SpringConfig = require('SpringConfig');
 var ViewStylePropTypes = require('ViewStylePropTypes');
 
 var flattenStyle = require('flattenStyle');
-var invariant = require('invariant');
-var requestAnimationFrame = require('requestAnimationFrame');
+var invariant = require('fbjs/lib/invariant');
+var requestAnimationFrame = require('fbjs/lib/requestAnimationFrame');
 
 import type { InterpolationConfigType } from 'Interpolation';
 
@@ -165,9 +165,9 @@ class TimingAnimation extends Animation {
   ) {
     super();
     this._toValue = config.toValue;
-    this._easing = config.easing || easeInOut;
+    this._easing = config.easing !== undefined ? config.easing : easeInOut;
     this._duration = config.duration !== undefined ? config.duration : 500;
-    this._delay = config.delay || 0;
+    this._delay = config.delay !== undefined ? config.delay : 0;
     this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
   }
 
@@ -252,7 +252,7 @@ class DecayAnimation extends Animation {
     config: DecayAnimationConfigSingle,
   ) {
     super();
-    this._deceleration = config.deceleration || 0.998;
+    this._deceleration = config.deceleration !== undefined ? config.deceleration : 0.998;
     this._velocity = config.velocity;
     this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true;
   }
@@ -577,8 +577,8 @@ class AnimatedValue extends AnimatedWithChildren {
 
   /**
    * Adds an asynchronous listener to the value so you can observe updates from
-   * animations or whathaveyou.  This is useful because there is no way to
-   * syncronously read the value because it might be driven natively.
+   * animations.  This is useful because there is no way to
+   * synchronously read the value because it might be driven natively.
    */
   addListener(callback: ValueListenerCallback): string {
     var id = String(_uniqueId++);
@@ -840,6 +840,91 @@ class AnimatedInterpolation extends AnimatedWithChildren {
   }
 }
 
+class AnimatedAddition extends AnimatedWithChildren {
+  _a: Animated;
+  _b: Animated;
+
+  constructor(a: Animated | number, b: Animated | number) {
+    super();
+    this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
+    this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
+  }
+
+  __getValue(): number {
+    return this._a.__getValue() + this._b.__getValue();
+  }
+
+  interpolate(config: InterpolationConfigType): AnimatedInterpolation {
+    return new AnimatedInterpolation(this, Interpolation.create(config));
+  }
+
+  __attach(): void {
+    this._a.__addChild(this);
+    this._b.__addChild(this);
+  }
+
+  __detach(): void {
+    this._a.__removeChild(this);
+    this._b.__removeChild(this);
+  }
+}
+
+class AnimatedMultiplication extends AnimatedWithChildren {
+  _a: Animated;
+  _b: Animated;
+
+  constructor(a: Animated | number, b: Animated | number) {
+    super();
+    this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
+    this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
+  }
+
+  __getValue(): number {
+    return this._a.__getValue() * this._b.__getValue();
+  }
+
+  interpolate(config: InterpolationConfigType): AnimatedInterpolation {
+    return new AnimatedInterpolation(this, Interpolation.create(config));
+  }
+
+  __attach(): void {
+    this._a.__addChild(this);
+    this._b.__addChild(this);
+  }
+
+  __detach(): void {
+    this._a.__removeChild(this);
+    this._b.__removeChild(this);
+  }
+}
+
+class AnimatedModulo extends AnimatedWithChildren {
+  _a: Animated;
+  _modulus: number;
+
+  constructor(a: Animated, modulus: number) {
+    super();
+    this._a = a;
+    this._modulus = modulus;
+  }
+
+  __getValue(): number {
+    return (this._a.__getValue() % this._modulus + this._modulus) % this._modulus;
+  }
+
+  interpolate(config: InterpolationConfigType): AnimatedInterpolation {
+    return new AnimatedInterpolation(this, Interpolation.create(config));
+  }
+
+  __attach(): void {
+    this._a.__addChild(this);
+  }
+
+  __detach(): void {
+    this._a.__removeChild(this);
+  }
+}
+
 class AnimatedTransform extends AnimatedWithChildren {
   _transforms: Array<Object>;
 
@@ -1094,6 +1179,10 @@ function createAnimatedComponent(Component: any): any {
   }
   AnimatedComponent.propTypes = {
     style: function(props, propName, componentName) {
+      if (!Component.propTypes) {
+        return;
+      }
+
       for (var key in ViewStylePropTypes) {
         if (!Component.propTypes[key] && props[key] !== undefined) {
           console.error(
@@ -1157,6 +1246,28 @@ type CompositeAnimation = {
   stop: () => void;
 };
 
+var add = function(
+  a: Animated,
+  b: Animated
+): AnimatedAddition {
+  return new AnimatedAddition(a, b);
+};
+
+var multiply = function(
+  a: Animated,
+  b: Animated
+): AnimatedMultiplication {
+  return new AnimatedMultiplication(a, b);
+};
+
+var modulo = function(
+  a: Animated,
+  modulus: number
+): AnimatedModulo {
+  return new AnimatedModulo(a, modulus);
+};
+
+
 var maybeVectorAnim = function(
   value: AnimatedValue | AnimatedValueXY,
   config: Object,
@@ -1409,7 +1520,7 @@ var event = function(
  * The simplest workflow is to create an `Animated.Value`, hook it up to one or
  * more style attributes of an animated component, and then drive updates either
  * via animations, such as `Animated.timing`, or by hooking into gestures like
- * panning or scolling via `Animated.event`.  `Animated.Value` can also bind to
+ * panning or scrolling via `Animated.event`.  `Animated.Value` can also bind to
  * props other than style, and can be interpolated as well.  Here is a basic
  * example of a container view that will fade in when it's mounted:
  *
@@ -1441,7 +1552,7 @@ var event = function(
  * Note that only animatable components can be animated.  `View`, `Text`, and
  * `Image` are already provided, and you can create custom ones with
  * `createAnimatedComponent`.  These special components do the magic of binding
- * the animated values to the properties, and do targetted native updates to
+ * the animated values to the properties, and do targeted native updates to
  * avoid the cost of the react render and reconciliation process on every frame.
  * They also handle cleanup on unmount so they are safe by default.
  *
@@ -1480,10 +1591,10 @@ var event = function(
  * interaction patterns, like drag-and-drop.
  *
  * You can see more example usage in `AnimationExample.js`, the Gratuitous
- * Animation App, and [Animations documentation guide](http://facebook.github.io/react-native/docs/animations.html).
+ * Animation App, and [Animations documentation guide](docs/animations.html).
  *
  * Note that `Animated` is designed to be fully serializable so that animations
- * can be run in a high performace way, independent of the normal JavaScript
+ * can be run in a high performance way, independent of the normal JavaScript
  * event loop. This does influence the API, so keep that in mind when it seems a
  * little trickier to do something compared to a fully synchronous system.
  * Checkout `Animated.Value.addListener` as a way to work around some of these
@@ -1517,6 +1628,23 @@ module.exports = {
    */
   spring,
 
+  /**
+   * Creates a new Animated value composed from two Animated values added
+   * together.
+   */
+  add,
+  /**
+   * Creates a new Animated value composed from two Animated values multiplied
+   * together.
+   */
+  multiply,
+
+  /**
+   * Creates a new Animated value that is the (non-negative) modulo of the
+   * provided Animated value
+   */
+  modulo,
+
   /**
    * Starts an animation after the given delay.
    */
@@ -1544,12 +1672,12 @@ module.exports = {
    *  then calls `setValue` on the mapped outputs.  e.g.
    *
    *```javascript
-   *  onScroll={this.AnimatedEvent(
+   *  onScroll={Animated.event(
    *    [{nativeEvent: {contentOffset: {x: this._scrollX}}}]
    *    {listener},          // Optional async listener
    *  )
    *  ...
-   *  onPanResponderMove: this.AnimatedEvent([
+   *  onPanResponderMove: Animated.event([
    *    null,                // raw event arg ignored
    *    {dx: this._panX},    // gestureState arg
    *  ]),
diff --git a/Libraries/Animated/src/Easing.js b/Libraries/Animated/src/Easing.js
index 46c30aed0646..a8685a7182d6 100644
--- a/Libraries/Animated/src/Easing.js
+++ b/Libraries/Animated/src/Easing.js
@@ -72,14 +72,14 @@ class Easing {
   static elastic(bounciness: number = 1): (t: number) => number {
     var p = bounciness * Math.PI;
     return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
-  };
+  }
 
   static back(s: number): (t: number) => number {
     if (s === undefined) {
       s = 1.70158;
     }
     return (t) => t * t * ((s + 1) * t - s);
-  };
+  }
 
   static bounce(t: number): number {
     if (t < 1 / 2.75) {
@@ -98,7 +98,7 @@ class Easing {
 
     t -= 2.625 / 2.75;
     return 7.5625 * t * t + 0.984375;
-  };
+  }
 
   static bezier(
     x1: number,
diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js
index da9671316064..50f77ed7102d 100644
--- a/Libraries/Animated/src/Interpolation.js
+++ b/Libraries/Animated/src/Interpolation.js
@@ -9,18 +9,11 @@
  * @providesModule Interpolation
  * @flow
  */
+/* eslint no-bitwise: 0 */
 'use strict';
 
-var tinycolor = require('tinycolor');
-
-// TODO(#7644673): fix this hack once github jest actually checks invariants
-var invariant = function(condition, message) {
-  if (!condition) {
-    var error = new Error(message);
-    (error: any).framesToPop = 1; // $FlowIssue
-    throw error;
-  }
-};
+var invariant = require('fbjs/lib/invariant');
+var normalizeColor = require('normalizeColor');
 
 type ExtrapolateType = 'extend' | 'identity' | 'clamp';
 
@@ -164,16 +157,20 @@ function interpolate(
   return result;
 }
 
-function colorToRgba(
-  input: string
-): string {
-  var color = tinycolor(input);
-  if (color.isValid()) {
-    var {r, g, b, a} = color.toRgb();
-    return `rgba(${r}, ${g}, ${b}, ${a === undefined ? 1 : a})`;
-  } else {
+function colorToRgba(input: string): string {
+  var int32Color = normalizeColor(input);
+  if (int32Color === null) {
     return input;
   }
+
+  int32Color = int32Color || 0; // $FlowIssue
+
+  var r = (int32Color & 0xff000000) >>> 24;
+  var g = (int32Color & 0x00ff0000) >>> 16;
+  var b = (int32Color & 0x0000ff00) >>> 8;
+  var a = (int32Color & 0x000000ff) / 255;
+
+  return `rgba(${r}, ${g}, ${b}, ${a})`;
 }
 
 var stringShapeRegex = /[0-9\.-]+/g;
diff --git a/Libraries/Animated/src/__tests__/Interpolation-test.js b/Libraries/Animated/src/__tests__/Interpolation-test.js
index d6625f25074c..afea659ed956 100644
--- a/Libraries/Animated/src/__tests__/Interpolation-test.js
+++ b/Libraries/Animated/src/__tests__/Interpolation-test.js
@@ -11,7 +11,7 @@
 jest
   .dontMock('Interpolation')
   .dontMock('Easing')
-  .dontMock('tinycolor');
+  .dontMock('normalizeColor');
 
 var Interpolation = require('Interpolation');
 var Easing = require('Easing');
@@ -216,12 +216,12 @@ describe('Interpolation', () => {
   it('should work with output ranges as string', () => {
     var interpolation = Interpolation.create({
       inputRange: [0, 1],
-      outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)'],
+      outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'],
     });
 
     expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
-    expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.25)');
-    expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.5)');
+    expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.2)');
+    expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.4)');
   });
 
   it('should work with output ranges as short hex string', () => {
@@ -249,11 +249,11 @@ describe('Interpolation', () => {
   it('should work with output ranges with mixed hex and rgba strings', () => {
     var interpolation = Interpolation.create({
       inputRange: [0, 1],
-      outputRange: ['rgba(100, 120, 140, .5)', '#87FC70'],
+      outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'],
     });
 
-    expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.5)');
-    expect(interpolation(0.5)).toBe('rgba(117.5, 186, 126, 0.75)');
+    expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.4)');
+    expect(interpolation(0.5)).toBe('rgba(117.5, 186, 126, 0.7)');
     expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
   });
 
diff --git a/Libraries/Animated/src/bezier.js b/Libraries/Animated/src/bezier.js
index 11b02f501c89..19cc855a4d95 100644
--- a/Libraries/Animated/src/bezier.js
+++ b/Libraries/Animated/src/bezier.js
@@ -42,7 +42,7 @@ module.exports = function(x1, y1, x2, y2, epsilon){
 
   var derivativeCurveX = function(t){
     var v = 1 - t;
-    return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (- t * t * t + 2 * v * t) * x2;
+    return 3 * (2 * (t - 1) * t + v * v) * x1 + 3 * (-t * t * t + 2 * v * t) * x2;
   };
 
   return function(t){
@@ -52,24 +52,26 @@ module.exports = function(x1, y1, x2, y2, epsilon){
     // First try a few iterations of Newton's method -- normally very fast.
     for (t2 = x, i = 0; i < 8; i++){
       x2 = curveX(t2) - x;
-      if (Math.abs(x2) < epsilon) return curveY(t2);
+      if (Math.abs(x2) < epsilon) { return curveY(t2); }
       d2 = derivativeCurveX(t2);
-      if (Math.abs(d2) < 1e-6) break;
+      if (Math.abs(d2) < 1e-6) { break; }
       t2 = t2 - x2 / d2;
     }
 
-    t0 = 0, t1 = 1, t2 = x;
+    t0 = 0;
+    t1 = 1;
+    t2 = x;
 
-    if (t2 < t0) return curveY(t0);
-    if (t2 > t1) return curveY(t1);
+    if (t2 < t0) { return curveY(t0); }
+    if (t2 > t1) { return curveY(t1); }
 
     // Fallback to the bisection method for reliability.
     while (t0 < t1){
       x2 = curveX(t2);
-      if (Math.abs(x2 - x) < epsilon) return curveY(t2);
-      if (x > x2) t0 = t2;
-      else t1 = t2;
-      t2 = (t1 - t0) * .5 + t0;
+      if (Math.abs(x2 - x) < epsilon) { return curveY(t2); }
+      if (x > x2) { t0 = t2; }
+      else { t1 = t2; }
+      t2 = (t1 - t0) * 0.5 + t0;
     }
 
     // Failure
diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js
index 3cf7f3ab04c8..6077d8663085 100644
--- a/Libraries/AppRegistry/AppRegistry.js
+++ b/Libraries/AppRegistry/AppRegistry.js
@@ -11,7 +11,10 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var BatchedBridge = require('BatchedBridge');
+var ReactNative = require('ReactNative');
+
+var invariant = require('fbjs/lib/invariant');
 var renderApplication = require('renderApplication');
 
 if (__DEV__) {
@@ -37,6 +40,10 @@ type AppConfig = {
  * for the app and then actually run the app when it's ready by invoking
  * `AppRegistry.runApplication`.
  *
+ * To "stop" an application when a view should be destroyed, call
+ * `AppRegistry.unmountApplicationComponentAtRootTag` with the tag that was
+ * pass into `runApplication`. These should always be used as a pair.
+ *
  * `AppRegistry` should be `require`d early in the `require` sequence to make
  * sure the JS execution environment is setup before other modules are
  * `require`d.
@@ -87,6 +94,16 @@ var AppRegistry = {
     );
     runnables[appKey].run(appParameters);
   },
+
+  unmountApplicationComponentAtRootTag: function(rootTag : number) {
+    ReactNative.unmountComponentAtNodeAndRemoveContainer(rootTag);
+  },
+
 };
 
+BatchedBridge.registerCallableModule(
+  'AppRegistry',
+  AppRegistry
+);
+
 module.exports = AppRegistry;
diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js
new file mode 100644
index 000000000000..8f9a7cf4be9a
--- /dev/null
+++ b/Libraries/AppState/AppState.js
@@ -0,0 +1,142 @@
+/**
+ * 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 AppState
+ * @flow
+ */
+'use strict';
+
+var Map = require('Map');
+var NativeModules = require('NativeModules');
+var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var RCTAppState = NativeModules.AppState;
+
+var logError = require('logError');
+var invariant = require('fbjs/lib/invariant');
+
+var _eventHandlers = {
+  change: new Map(),
+  memoryWarning: new Map(),
+};
+
+/**
+ * `AppState` can tell you if the app is in the foreground or background,
+ * and notify you when the state changes.
+ *
+ * AppState is frequently used to determine the intent and proper behavior when
+ * handling push notifications.
+ *
+ * ### App States
+ *
+ *  - `active` - The app is running in the foreground
+ *  - `background` - The app is running in the background. The user is either
+ *     in another app or on the home screen
+ *  - `inactive` - This is a transition state that currently never happens for
+ *     typical React Native apps.
+ *
+ * For more information, see
+ * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
+ *
+ * ### Basic Usage
+ *
+ * To see the current state, you can check `AppState.currentState`, which
+ * will be kept up-to-date. However, `currentState` will be null at launch
+ * while `AppState` retrieves it over the bridge.
+ *
+ * ```
+ * getInitialState: function() {
+ *   return {
+ *     currentAppState: AppState.currentState,
+ *   };
+ * },
+ * componentDidMount: function() {
+ *   AppState.addEventListener('change', this._handleAppStateChange);
+ * },
+ * componentWillUnmount: function() {
+ *   AppState.removeEventListener('change', this._handleAppStateChange);
+ * },
+ * _handleAppStateChange: function(currentAppState) {
+ *   this.setState({ currentAppState, });
+ * },
+ * render: function() {
+ *   return (
+ *     <Text>Current state is: {this.state.currentAppState}</Text>
+ *   );
+ * },
+ * ```
+ *
+ * This example will only ever appear to say "Current state is: active" because
+ * the app is only visible to the user when in the `active` state, and the null
+ * state will happen only momentarily.
+ */
+
+var AppState = {
+
+  /**
+   * Add a handler to AppState changes by listening to the `change` event type
+   * and providing the handler
+   */
+  addEventListener: function(
+    type: string,
+    handler: Function
+  ) {
+    invariant(
+      ['change', 'memoryWarning'].indexOf(type) !== -1,
+      'Trying to subscribe to unknown event: "%s"', type
+    );
+    if (type === 'change') {
+      _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+        'appStateDidChange',
+        (appStateData) => {
+          handler(appStateData.app_state);
+        }
+      ));
+    } else if (type === 'memoryWarning') {
+      _eventHandlers[type].set(handler, RCTDeviceEventEmitter.addListener(
+        'memoryWarning',
+        handler
+      ));
+    }
+  },
+
+  /**
+   * Remove a handler by passing the `change` event type and the handler
+   */
+  removeEventListener: function(
+    type: string,
+    handler: Function
+  ) {
+    invariant(
+      ['change', 'memoryWarning'].indexOf(type) !== -1,
+      'Trying to remove listener for unknown event: "%s"', type
+    );
+    if (!_eventHandlers[type].has(handler)) {
+      return;
+    }
+    _eventHandlers[type].get(handler).remove();
+    _eventHandlers[type].delete(handler);
+  },
+
+  currentState: ('active' : ?string),
+};
+
+RCTDeviceEventEmitter.addListener(
+  'appStateDidChange',
+  (appStateData) => {
+    AppState.currentState = appStateData.app_state;
+  }
+);
+
+RCTAppState.getCurrentAppState(
+  (appStateData) => {
+    AppState.currentState = appStateData.app_state;
+  },
+  logError
+);
+
+module.exports = AppState;
diff --git a/Libraries/AppStateIOS/AppStateIOS.android.js b/Libraries/AppStateIOS/AppStateIOS.android.js
index 0f59cbea06cf..511174138562 100644
--- a/Libraries/AppStateIOS/AppStateIOS.android.js
+++ b/Libraries/AppStateIOS/AppStateIOS.android.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 class AppStateIOS {
 
diff --git a/Libraries/AppStateIOS/AppStateIOS.ios.js b/Libraries/AppStateIOS/AppStateIOS.ios.js
index f7dddcd5787c..82d968ce9afe 100644
--- a/Libraries/AppStateIOS/AppStateIOS.ios.js
+++ b/Libraries/AppStateIOS/AppStateIOS.ios.js
@@ -11,13 +11,12 @@
  */
 'use strict';
 
-var Map = require('Map');
 var NativeModules = require('NativeModules');
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 var RCTAppState = NativeModules.AppState;
 
 var logError = require('logError');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var _eventHandlers = {
   change: new Map(),
diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js
index ea49b202fc01..5b582be4036d 100644
--- a/Libraries/BatchedBridge/BatchedBridge.js
+++ b/Libraries/BatchedBridge/BatchedBridge.js
@@ -10,11 +10,31 @@
  */
 'use strict';
 
-let MessageQueue = require('MessageQueue');
+const MessageQueue = require('MessageQueue');
 
-let BatchedBridge = new MessageQueue(
+const BatchedBridge = new MessageQueue(
   __fbBatchedBridgeConfig.remoteModuleConfig,
   __fbBatchedBridgeConfig.localModulesConfig,
 );
 
+// TODO: Move these around to solve the cycle in a cleaner way.
+
+const Systrace = require('Systrace');
+const JSTimersExecution = require('JSTimersExecution');
+
+BatchedBridge.registerCallableModule('Systrace', Systrace);
+BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);
+
+if (__DEV__) {
+  BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
+}
+
+// Wire up the batched bridge on the global object so that we can call into it.
+// Ideally, this would be the inverse relationship. I.e. the native environment
+// provides this global directly with its script embedded. Then this module
+// would export it. A possible fix would be to trim the dependencies in
+// MessageQueue to its minimal features and embed that in the native runtime.
+
+Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
+
 module.exports = BatchedBridge;
diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js
index e3a179887b6c..5d9cf603aac5 100644
--- a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js
+++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js
@@ -11,10 +11,102 @@
  */
 'use strict';
 
-var NativeModules = require('BatchedBridge').RemoteModules;
+const BatchedBridge = require('BatchedBridge');
+const RemoteModules = BatchedBridge.RemoteModules;
 
-var nativeModulePrefixNormalizer = require('nativeModulePrefixNormalizer');
+function normalizePrefix(moduleName: string): string {
+  return moduleName.replace(/^(RCT|RK)/, '');
+}
 
-nativeModulePrefixNormalizer(NativeModules);
+/**
+ * Dirty hack to support old (RK) and new (RCT) native module name conventions.
+ */
+Object.keys(RemoteModules).forEach((moduleName) => {
+  const strippedName = normalizePrefix(moduleName);
+  if (RemoteModules['RCT' + strippedName] && RemoteModules['RK' + strippedName]) {
+    throw new Error(
+      'Module cannot be registered as both RCT and RK: ' + moduleName
+    );
+  }
+  if (strippedName !== moduleName) {
+    RemoteModules[strippedName] = RemoteModules[moduleName];
+    delete RemoteModules[moduleName];
+  }
+});
+
+/**
+ * Define lazy getters for each module.
+ * These will return the module if already loaded, or load it if not.
+ */
+const NativeModules = {};
+Object.keys(RemoteModules).forEach((moduleName) => {
+  Object.defineProperty(NativeModules, moduleName, {
+    enumerable: true,
+    get: () => {
+      let module = RemoteModules[moduleName];
+      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
+        const json = global.nativeRequireModuleConfig(moduleName);
+        const config = json && JSON.parse(json);
+        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
+        RemoteModules[moduleName] = module;
+      }
+      return module;
+    },
+  });
+});
+
+/**
+ * Copies the ViewManager constants and commands into UIManager. This is
+ * only needed for iOS, which puts the constants in the ViewManager
+ * namespace instead of UIManager, unlike Android.
+ *
+ * We'll eventually move this logic to UIManager.js, once all
+ * the call sites accessing NativeModules.UIManager directly have
+ * been removed #9344445
+ */
+const UIManager = NativeModules.UIManager;
+UIManager && Object.keys(UIManager).forEach(viewName => {
+  const viewConfig = UIManager[viewName];
+  if (viewConfig.Manager) {
+    let constants;
+    /* $FlowFixMe - nice try. Flow doesn't like getters */
+    Object.defineProperty(viewConfig, 'Constants', {
+      enumerable: true,
+      get: () => {
+        if (constants) {
+          return constants;
+        }
+        constants = {};
+        const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
+        viewManager && Object.keys(viewManager).forEach(key => {
+          const value = viewManager[key];
+          if (typeof value !== 'function') {
+            constants[key] = value;
+          }
+        });
+        return constants;
+      },
+    });
+    let commands;
+    /* $FlowFixMe - nice try. Flow doesn't like getters */
+    Object.defineProperty(viewConfig, 'Commands', {
+      enumerable: true,
+      get: () => {
+        if (commands) {
+          return commands;
+        }
+        commands = {};
+        const viewManager = NativeModules[normalizePrefix(viewConfig.Manager)];
+        viewManager && Object.keys(viewManager).forEach((key, index) => {
+          const value = viewManager[key];
+          if (typeof value === 'function') {
+            commands[key] = index;
+          }
+        });
+        return commands;
+      },
+    });
+  }
+});
 
 module.exports = NativeModules;
diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow
new file mode 100644
index 000000000000..f2181b965b08
--- /dev/null
+++ b/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js.flow
@@ -0,0 +1,17 @@
+/**
+ * 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 NativeModules
+ * @flow
+ */
+'use strict';
+
+const BatchedBridge = require('BatchedBridge');
+const RemoteModules = BatchedBridge.RemoteModules;
+
+module.exports = RemoteModules;
diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js
index 8c66aac9aa7b..2b8f578e701a 100644
--- a/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js
+++ b/Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js
@@ -11,7 +11,13 @@
  */
 'use strict';
 
+var BatchedBridge = require('BatchedBridge');
 var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
 
+BatchedBridge.registerCallableModule(
+  'RCTEventEmitter',
+  ReactNativeEventEmitter
+);
+
 // Completely locally implemented - no native hooks.
 module.exports = ReactNativeEventEmitter;
diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js
index 113a09262699..164508ddd038 100644
--- a/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js
+++ b/Libraries/BatchedBridge/BatchedBridgedModules/__mocks__/NativeModules.js
@@ -54,8 +54,8 @@ var NativeModules = {
   AlertManager: {
     alertWithArgs: jest.genMockFunction(),
   },
-  Pasteboard: {
-    setPasteboardString: jest.genMockFunction(),
+  Clipboard: {
+    setString: jest.genMockFunction(),
   },
 };
 
diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js
index 1eee46a3edbf..8f9cc1bc6186 100644
--- a/Libraries/CameraRoll/CameraRoll.js
+++ b/Libraries/CameraRoll/CameraRoll.js
@@ -17,7 +17,7 @@ var RCTCameraRollManager = require('NativeModules').CameraRollManager;
 var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
 var deepFreezeAndThrowOnMutationInDev =
   require('deepFreezeAndThrowOnMutationInDev');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var GROUP_TYPES_OPTIONS = [
   'Album',
@@ -35,7 +35,6 @@ var ASSET_TYPE_OPTIONS = [
   'Photos', // default
 ];
 
-
 // Flow treats Object and Array as disjoint types, currently.
 deepFreezeAndThrowOnMutationInDev((GROUP_TYPES_OPTIONS: any));
 deepFreezeAndThrowOnMutationInDev((ASSET_TYPE_OPTIONS: any));
@@ -119,69 +118,63 @@ class CameraRoll {
   /**
    * Saves the image to the camera roll / gallery.
    *
-   * The CameraRoll API is not yet implemented for Android.
-   * 
-   * @param {string} tag On Android, this is a local URI, such
-   * as `"file:///sdcard/img.png"`.
+   * On Android, the tag is a local URI, such as `"file:///sdcard/img.png"`.
    *
    * On iOS, the tag can be one of the following:
    *
    *   - local URI
    *   - assets-library tag
-   *   - a tag not maching any of the above, which means the image data will
+   *   - a tag not matching any of the above, which means the image data will
    * be stored in memory (and consume memory as long as the process is alive)
    *
-   * @param successCallback Invoked with the value of `tag` on success.
-   * @param errorCallback Invoked with error message on error.
+   * Returns a Promise which when resolved will be passed the new URI.
    */
-  static saveImageWithTag(tag, successCallback, errorCallback) {
+  static saveImageWithTag(tag) {
     invariant(
       typeof tag === 'string',
       'CameraRoll.saveImageWithTag tag must be a valid string.'
     );
-    RCTCameraRollManager.saveImageWithTag(
-      tag,
-      (imageTag) => {
-        successCallback && successCallback(imageTag);
-      },
-      (errorMessage) => {
-        errorCallback && errorCallback(errorMessage);
-      });
+    if (arguments.length > 1) {
+      console.warn("CameraRoll.saveImageWithTag(tag, success, error) is deprecated.  Use the returned Promise instead");
+      let successCallback = arguments[1];
+      let errorCallback = arguments[2] || ( () => {} );
+      RCTCameraRollManager.saveImageWithTag(tag).then(successCallback, errorCallback);
+      return;
+    }
+    return RCTCameraRollManager.saveImageWithTag(tag);
   }
 
   /**
-   *  Invokes `callback` with photo identifier objects from the local camera
-   *  roll of the device matching shape defined by `getPhotosReturnChecker`.
+   * Returns a Promise with photo identifier objects from the local camera
+   * roll of the device matching shape defined by `getPhotosReturnChecker`.
    *
-   *  @param {object} params See `getPhotosParamChecker`.
-   *  @param {function} callback Invoked with arg of shape defined by
-   *  `getPhotosReturnChecker` on success.
-   *  @param {function} errorCallback Invoked with error message on error.
+   * @param {object} params See `getPhotosParamChecker`.
+   *
+   * Returns a Promise which when resolved will be of shape `getPhotosReturnChecker`.
    */
-  static getPhotos(params, callback, errorCallback) {
-    var metaCallback = callback;
+  static getPhotos(params) {
     if (__DEV__) {
       getPhotosParamChecker({params}, 'params', 'CameraRoll.getPhotos');
-      invariant(
-        typeof callback === 'function',
-        'CameraRoll.getPhotos callback must be a valid function.'
-      );
-      invariant(
-        typeof errorCallback === 'function',
-        'CameraRoll.getPhotos errorCallback must be a valid function.'
-      );
     }
-    if (__DEV__) {
-      metaCallback = (response) => {
-        getPhotosReturnChecker(
-          {response},
-          'response',
-          'CameraRoll.getPhotos callback'
-        );
-        callback(response);
-      };
+    if (arguments.length > 1) {
+      console.warn("CameraRoll.getPhotos(tag, success, error) is deprecated.  Use the returned Promise instead");
+      let successCallback = arguments[1];
+      if (__DEV__) {
+        let callback = arguments[1];
+        successCallback = (response) => {
+          getPhotosReturnChecker(
+            {response},
+            'response',
+            'CameraRoll.getPhotos callback'
+          );
+          callback(response);
+        };
+      }
+      let errorCallback = arguments[2] || ( () => {} );
+      RCTCameraRollManager.getPhotos(params).then(successCallback, errorCallback);
     }
-    RCTCameraRollManager.getPhotos(params, metaCallback, errorCallback);
+    // TODO: Add the __DEV__ check back in to verify the Promise result
+    return RCTCameraRollManager.getPhotos(params);
   }
 }
 
diff --git a/Libraries/CameraRoll/ImagePickerIOS.js b/Libraries/CameraRoll/ImagePickerIOS.js
index 9b2f75e5b92d..a67884dc8435 100644
--- a/Libraries/CameraRoll/ImagePickerIOS.js
+++ b/Libraries/CameraRoll/ImagePickerIOS.js
@@ -24,7 +24,7 @@ var ImagePickerIOS = {
     config = {
       videoMode: false,
       ...config,
-    }
+    };
     return RCTImagePicker.openCameraDialog(config, successCallback, cancelCallback);
   },
   openSelectDialog: function(config: Object, successCallback: Function, cancelCallback: Function) {
@@ -32,7 +32,7 @@ var ImagePickerIOS = {
       showImages: true,
       showVideos: false,
       ...config,
-    }
+    };
     return RCTImagePicker.openSelectDialog(config, successCallback, cancelCallback);
   },
 };
diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m b/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
deleted file mode 100644
index 9b49742ace43..000000000000
--- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.m
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTAssetsLibraryImageLoader.h"
-
-#import <AssetsLibrary/AssetsLibrary.h>
-#import <ImageIO/ImageIO.h>
-#import <libkern/OSAtomic.h>
-#import <UIKit/UIKit.h>
-
-#import "RCTBridge.h"
-#import "RCTConvert.h"
-#import "RCTImageLoader.h"
-#import "RCTImageUtils.h"
-#import "RCTUtils.h"
-
-static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
-
-@implementation RCTAssetsLibraryImageLoader
-{
-  ALAssetsLibrary *_assetsLibrary;
-}
-
-RCT_EXPORT_MODULE()
-
-@synthesize bridge = _bridge;
-
-- (ALAssetsLibrary *)assetsLibrary
-{
-  return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
-}
-
-#pragma mark - RCTImageLoader
-
-- (BOOL)canLoadImageURL:(NSURL *)requestURL
-{
-  return [requestURL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
-}
-
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
-                                              size:(CGSize)size
-                                             scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
-                                   progressHandler:(RCTImageLoaderProgressBlock)progressHandler
-                                 completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
-{
-  __block volatile uint32_t cancelled = 0;
-
-  [[self assetsLibrary] assetForURL:imageURL resultBlock:^(ALAsset *asset) {
-    if (cancelled) {
-      return;
-    }
-
-    if (asset) {
-      // ALAssetLibrary API is async and will be multi-threaded. Loading a few full
-      // resolution images at once will spike the memory up to store the image data,
-      // and might trigger memory warnings and/or OOM crashes.
-      // To improve this, process the loaded asset in a serial queue.
-      dispatch_async(RCTAssetsLibraryImageLoaderQueue(), ^{
-        if (cancelled) {
-          return;
-        }
-
-        // Also make sure the image is released immediately after it's used so it
-        // doesn't spike the memory up during the process.
-        @autoreleasepool {
-          BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
-          ALAssetRepresentation *representation = [asset defaultRepresentation];
-
-#if RCT_DEV
-
-          CGSize sizeBeingLoaded = size;
-          if (useMaximumSize) {
-            CGSize pointSize = representation.dimensions;
-            sizeBeingLoaded = CGSizeMake(pointSize.width * representation.scale, pointSize.height * representation.scale);
-          }
-
-          CGSize screenSize;
-          if ([[[UIDevice currentDevice] systemVersion] compare:@"8.0" options:NSNumericSearch] == NSOrderedDescending) {
-            screenSize = [UIScreen mainScreen].nativeBounds.size;
-          } else {
-            CGSize mainScreenSize = [UIScreen mainScreen].bounds.size;
-            CGFloat mainScreenScale = [[UIScreen mainScreen] scale];
-            screenSize = CGSizeMake(mainScreenSize.width * mainScreenScale, mainScreenSize.height * mainScreenScale);
-          }
-          CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
-
-          if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
-            RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@",
-                       representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
-          }
-
-#endif
-
-          UIImage *image = nil;
-          NSError *error = nil;
-          if (useMaximumSize) {
-
-            image = [UIImage imageWithCGImage:representation.fullResolutionImage
-                                        scale:scale
-                                  orientation:(UIImageOrientation)representation.orientation];
-          } else {
-
-            NSUInteger length = (NSUInteger)representation.size;
-            uint8_t *buffer = (uint8_t *)malloc((size_t)length);
-            if ([representation getBytes:buffer
-                                fromOffset:0
-                                    length:length
-                                      error:&error]) {
-
-              NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer
-                                                          length:length
-                                                    freeWhenDone:YES];
-
-              image = RCTDecodeImageWithData(data, size, scale, resizeMode);
-            } else {
-              free(buffer);
-            }
-          }
-
-          completionHandler(error, image);
-        }
-      });
-    } else {
-      NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
-      completionHandler(RCTErrorWithMessage(errorText), nil);
-    }
-  } failureBlock:^(NSError *loadError) {
-    if (cancelled) {
-      return;
-    }
-
-    NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
-    completionHandler(RCTErrorWithMessage(errorText), nil);
-  }];
-
-  return ^{
-    OSAtomicOr32Barrier(1, &cancelled);
-  };
-}
-
-@end
-
-@implementation RCTBridge (RCTAssetsLibraryImageLoader)
-
-- (ALAssetsLibrary *)assetsLibrary
-{
-  return [self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])] assetsLibrary];
-}
-
-@end
-
-static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
-{
-  static dispatch_queue_t queue;
-  static dispatch_once_t onceToken;
-  dispatch_once(&onceToken, ^{
-    queue = dispatch_queue_create("com.facebook.RCTAssetsLibraryImageLoader", DISPATCH_QUEUE_SERIAL);
-  });
-
-  return queue;
-}
diff --git a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h
similarity index 75%
rename from Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h
rename to Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h
index 0cc1c5353088..4ce68bc3b312 100644
--- a/Libraries/CameraRoll/RCTAssetsLibraryImageLoader.h
+++ b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.h
@@ -7,9 +7,12 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTImageLoader.h"
+#import "RCTBridge.h"
+#import "RCTURLRequestHandler.h"
 
-@interface RCTAssetsLibraryImageLoader : NSObject <RCTImageURLLoader>
+@class ALAssetsLibrary;
+
+@interface RCTAssetsLibraryRequestHandler : NSObject <RCTURLRequestHandler>
 
 @end
 
diff --git a/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m
new file mode 100644
index 000000000000..d818f336dbc3
--- /dev/null
+++ b/Libraries/CameraRoll/RCTAssetsLibraryRequestHandler.m
@@ -0,0 +1,114 @@
+/**
+ * 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.
+ */
+
+#import "RCTAssetsLibraryRequestHandler.h"
+
+#import <AssetsLibrary/AssetsLibrary.h>
+#import <libkern/OSAtomic.h>
+
+#import "RCTBridge.h"
+#import "RCTUtils.h"
+
+@implementation RCTAssetsLibraryRequestHandler
+{
+  ALAssetsLibrary *_assetsLibrary;
+}
+
+RCT_EXPORT_MODULE()
+
+@synthesize bridge = _bridge;
+
+- (ALAssetsLibrary *)assetsLibrary
+{
+  return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
+}
+
+#pragma mark - RCTURLRequestHandler
+
+- (BOOL)canHandleRequest:(NSURLRequest *)request
+{
+  return [request.URL.scheme caseInsensitiveCompare:@"assets-library"] == NSOrderedSame;
+}
+
+- (id)sendRequest:(NSURLRequest *)request
+     withDelegate:(id<RCTURLRequestDelegate>)delegate
+{
+  __block volatile uint32_t cancelled = 0;
+  void (^cancellationBlock)(void) = ^{
+    OSAtomicOr32Barrier(1, &cancelled);
+  };
+
+  [[self assetsLibrary] assetForURL:request.URL resultBlock:^(ALAsset *asset) {
+    if (cancelled) {
+      return;
+    }
+
+    if (asset) {
+
+      ALAssetRepresentation *representation = [asset defaultRepresentation];
+      NSInteger length = (NSInteger)representation.size;
+
+      NSURLResponse *response =
+      [[NSURLResponse alloc] initWithURL:request.URL
+                                MIMEType:representation.UTI
+                   expectedContentLength:length
+                        textEncodingName:nil];
+
+      [delegate URLRequest:cancellationBlock didReceiveResponse:response];
+
+      NSError *error = nil;
+      uint8_t *buffer = (uint8_t *)malloc((size_t)length);
+      if ([representation getBytes:buffer
+                        fromOffset:0
+                            length:length
+                             error:&error]) {
+
+        NSData *data = [[NSData alloc] initWithBytesNoCopy:buffer
+                                                    length:length
+                                              freeWhenDone:YES];
+
+        [delegate URLRequest:cancellationBlock didReceiveData:data];
+        [delegate URLRequest:cancellationBlock didCompleteWithError:nil];
+
+      } else {
+        free(buffer);
+        [delegate URLRequest:cancellationBlock didCompleteWithError:error];
+      }
+
+    } else {
+      NSString *errorMessage = [NSString stringWithFormat:@"Failed to load asset"
+                                " at URL %@ with no error message.", request.URL];
+      NSError *error = RCTErrorWithMessage(errorMessage);
+      [delegate URLRequest:cancellationBlock didCompleteWithError:error];
+    }
+  } failureBlock:^(NSError *loadError) {
+    if (cancelled) {
+      return;
+    }
+    [delegate URLRequest:cancellationBlock didCompleteWithError:loadError];
+  }];
+
+  return cancellationBlock;
+}
+
+- (void)cancelRequest:(id)requestToken
+{
+  ((void (^)(void))requestToken)();
+}
+
+@end
+
+@implementation RCTBridge (RCTAssetsLibraryImageLoader)
+
+- (ALAssetsLibrary *)assetsLibrary
+{
+  return [[self moduleForClass:[RCTAssetsLibraryRequestHandler class]] assetsLibrary];
+}
+
+@end
diff --git a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj b/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj
index 56cced2147f1..a8bb79d92aa9 100644
--- a/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj
+++ b/Libraries/CameraRoll/RCTCameraRoll.xcodeproj/project.pbxproj
@@ -9,7 +9,7 @@
 /* Begin PBXBuildFile section */
 		137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
 		143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
-		8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
+		8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */; };
 		8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
 /* End PBXBuildFile section */
 
@@ -31,8 +31,8 @@
 		143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = "<group>"; };
 		143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = "<group>"; };
 		58B5115D1A9E6B3D00147676 /* libRCTCameraRoll.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTCameraRoll.a; sourceTree = BUILT_PRODUCTS_DIR; };
-		8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
-		8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
+		8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryRequestHandler.h; sourceTree = "<group>"; };
+		8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryRequestHandler.m; sourceTree = "<group>"; };
 		8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
 		8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -51,8 +51,8 @@
 		58B511541A9E6B3D00147676 = {
 			isa = PBXGroup;
 			children = (
-				8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
-				8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
+				8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.h */,
+				8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m */,
 				143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
 				143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
 				137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
@@ -129,7 +129,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
+				8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryRequestHandler.m in Sources */,
 				8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
 				137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
 				143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m
index b1c285186b1e..d060224ccc7a 100644
--- a/Libraries/CameraRoll/RCTCameraRollManager.m
+++ b/Libraries/CameraRoll/RCTCameraRollManager.m
@@ -13,7 +13,7 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
-#import "RCTAssetsLibraryImageLoader.h"
+#import "RCTAssetsLibraryRequestHandler.h"
 #import "RCTBridge.h"
 #import "RCTConvert.h"
 #import "RCTImageLoader.h"
@@ -79,13 +79,16 @@ @implementation RCTCameraRollManager
 
 @synthesize bridge = _bridge;
 
+NSString *const RCTErrorUnableToLoad = @"E_UNABLE_TO_LOAD";
+NSString *const RCTErrorUnableToSave = @"E_UNABLE_TO_SAVE";
+
 RCT_EXPORT_METHOD(saveImageWithTag:(NSString *)imageTag
-                  successCallback:(RCTResponseSenderBlock)successCallback
-                  errorCallback:(RCTResponseErrorBlock)errorCallback)
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(RCTPromiseRejectBlock)reject)
 {
   [_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *loadError, UIImage *loadedImage) {
     if (loadError) {
-      errorCallback(loadError);
+      reject(RCTErrorUnableToLoad, nil, loadError);
       return;
     }
     // It's unclear if writeImageToSavedPhotosAlbum is thread-safe
@@ -93,41 +96,41 @@ @implementation RCTCameraRollManager
       [_bridge.assetsLibrary writeImageToSavedPhotosAlbum:loadedImage.CGImage metadata:nil completionBlock:^(NSURL *assetURL, NSError *saveError) {
         if (saveError) {
           RCTLogWarn(@"Error saving cropped image: %@", saveError);
-          errorCallback(saveError);
+          reject(RCTErrorUnableToSave, nil, saveError);
         } else {
-          successCallback(@[assetURL.absoluteString]);
+          resolve(assetURL.absoluteString);
         }
       }];
     });
   }];
 }
 
-static void RCTCallCallback(RCTResponseSenderBlock callback,
-                            NSArray<NSDictionary<NSString *, id> *> *assets,
-                            BOOL hasNextPage)
+static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
+                              NSArray<NSDictionary<NSString *, id> *> *assets,
+                              BOOL hasNextPage)
 {
   if (!assets.count) {
-    callback(@[@{
+    resolve(@{
       @"edges": assets,
       @"page_info": @{
         @"has_next_page": @NO,
       }
-    }]);
+    });
     return;
   }
-  callback(@[@{
+  resolve(@{
     @"edges": assets,
     @"page_info": @{
       @"start_cursor": assets[0][@"node"][@"image"][@"uri"],
       @"end_cursor": assets[assets.count - 1][@"node"][@"image"][@"uri"],
       @"has_next_page": @(hasNextPage),
     }
-  }]);
+  });
 }
 
 RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params
-                  successCallback:(RCTResponseSenderBlock)successCallback
-                  errorCallback:(RCTResponseErrorBlock)errorCallback)
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(RCTPromiseRejectBlock)reject)
 {
   NSUInteger first = [RCTConvert NSInteger:params[@"first"]];
   NSString *afterCursor = [RCTConvert NSString:params[@"after"]];
@@ -137,7 +140,7 @@ static void RCTCallCallback(RCTResponseSenderBlock callback,
 
   BOOL __block foundAfter = NO;
   BOOL __block hasNextPage = NO;
-  BOOL __block calledCallback = NO;
+  BOOL __block resolvedPromise = NO;
   NSMutableArray<NSDictionary<NSString *, id> *> *assets = [NSMutableArray new];
 
   [_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) {
@@ -157,9 +160,9 @@ static void RCTCallCallback(RCTResponseSenderBlock callback,
             *stopAssets = YES;
             *stopGroups = YES;
             hasNextPage = YES;
-            RCTAssert(calledCallback == NO, @"Called the callback before we finished processing the results.");
-            RCTCallCallback(successCallback, assets, hasNextPage);
-            calledCallback = YES;
+            RCTAssert(resolvedPromise == NO, @"Resolved the promise before we finished processing the results.");
+            RCTResolvePromise(resolve, assets, hasNextPage);
+            resolvedPromise = YES;
             return;
           }
           CGSize dimensions = [result defaultRepresentation].dimensions;
@@ -188,18 +191,18 @@ static void RCTCallCallback(RCTResponseSenderBlock callback,
         }
       }];
     } else {
-      // Sometimes the enumeration continues even if we set stop above, so we guard against calling the callback
+      // Sometimes the enumeration continues even if we set stop above, so we guard against resolving the promise
       // multiple times here.
-      if (!calledCallback) {
-        RCTCallCallback(successCallback, assets, hasNextPage);
-        calledCallback = YES;
+      if (!resolvedPromise) {
+        RCTResolvePromise(resolve, assets, hasNextPage);
+        resolvedPromise = YES;
       }
     }
   } failureBlock:^(NSError *error) {
     if (error.code != ALAssetsLibraryAccessUserDeniedError) {
       RCTLogError(@"Failure while iterating through asset groups %@", error);
     }
-    errorCallback(error);
+    reject(RCTErrorUnableToLoad, nil, error);
   }];
 }
 
diff --git a/Libraries/CameraRoll/RCTImagePickerManager.m b/Libraries/CameraRoll/RCTImagePickerManager.m
index 87071863b8b5..e772ce0816db 100644
--- a/Libraries/CameraRoll/RCTImagePickerManager.m
+++ b/Libraries/CameraRoll/RCTImagePickerManager.m
@@ -9,6 +9,7 @@
  */
 
 #import "RCTImagePickerManager.h"
+#import "RCTImageStoreManager.h"
 
 #import "RCTConvert.h"
 #import "RCTRootView.h"
@@ -32,15 +33,7 @@ @implementation RCTImagePickerManager
 
 RCT_EXPORT_MODULE(ImagePickerIOS);
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _pickers = [NSMutableArray new];
-    _pickerCallbacks = [NSMutableArray new];
-    _pickerCancelCallbacks = [NSMutableArray new];
-  }
-  return self;
-}
+@synthesize bridge = _bridge;
 
 - (dispatch_queue_t)methodQueue
 {
@@ -67,8 +60,6 @@ - (dispatch_queue_t)methodQueue
     return;
   }
 
-  UIViewController *rootViewController = RCTKeyWindow().rootViewController;
-
   UIImagePickerController *imagePicker = [UIImagePickerController new];
   imagePicker.delegate = self;
   imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
@@ -77,11 +68,9 @@ - (dispatch_queue_t)methodQueue
     imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
   }
 
-  [_pickers addObject:imagePicker];
-  [_pickerCallbacks addObject:callback];
-  [_pickerCancelCallbacks addObject:cancelCallback];
-
-  [rootViewController presentViewController:imagePicker animated:YES completion:nil];
+  [self _presentPicker:imagePicker
+       successCallback:callback
+        cancelCallback:cancelCallback];
 }
 
 RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config
@@ -93,8 +82,6 @@ - (dispatch_queue_t)methodQueue
     return;
   }
 
-  UIViewController *rootViewController = RCTKeyWindow().rootViewController;
-
   UIImagePickerController *imagePicker = [UIImagePickerController new];
   imagePicker.delegate = self;
   imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
@@ -109,33 +96,62 @@ - (dispatch_queue_t)methodQueue
 
   imagePicker.mediaTypes = allowedTypes;
 
-  [_pickers addObject:imagePicker];
-  [_pickerCallbacks addObject:callback];
-  [_pickerCancelCallbacks addObject:cancelCallback];
-
-  [rootViewController presentViewController:imagePicker animated:YES completion:nil];
+  [self _presentPicker:imagePicker
+       successCallback:callback
+        cancelCallback:cancelCallback];
 }
 
 - (void)imagePickerController:(UIImagePickerController *)picker
 didFinishPickingMediaWithInfo:(NSDictionary<NSString *, id> *)info
 {
-  NSUInteger index = [_pickers indexOfObject:picker];
-  RCTResponseSenderBlock callback = _pickerCallbacks[index];
-
-  [_pickers removeObjectAtIndex:index];
-  [_pickerCallbacks removeObjectAtIndex:index];
-  [_pickerCancelCallbacks removeObjectAtIndex:index];
+  NSString *mediaType = info[UIImagePickerControllerMediaType];
+  BOOL isMovie = [mediaType isEqualToString:(NSString *)kUTTypeMovie];
+  NSString *key = isMovie ? UIImagePickerControllerMediaURL : UIImagePickerControllerReferenceURL;
+  NSURL *imageURL = info[key];
+  if (imageURL) {
+    [self _dismissPicker:picker args:@[imageURL.absoluteString]];
+    return;
+  }
 
-  UIViewController *rootViewController = RCTKeyWindow().rootViewController;
-  [rootViewController dismissViewControllerAnimated:YES completion:nil];
+  // This is a newly taken image, and doesn't have a URL yet.
+  // We need to save it to the image store first.
+  UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
 
-  callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]);
+  // WARNING: Using ImageStoreManager may cause a memory leak because the
+  // image isn't automatically removed from store once we're done using it.
+  [_bridge.imageStoreManager storeImage:originalImage withBlock:^(NSString *tempImageTag) {
+    [self _dismissPicker:picker args:tempImageTag ? @[tempImageTag] : nil];
+  }];
 }
 
 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
+{
+  [self _dismissPicker:picker args:nil];
+}
+
+- (void)_presentPicker:(UIImagePickerController *)imagePicker
+       successCallback:(RCTResponseSenderBlock)callback
+        cancelCallback:(RCTResponseSenderBlock)cancelCallback
+{
+  if (!_pickers) {
+    _pickers = [NSMutableArray new];
+    _pickerCallbacks = [NSMutableArray new];
+    _pickerCancelCallbacks = [NSMutableArray new];
+  }
+
+  [_pickers addObject:imagePicker];
+  [_pickerCallbacks addObject:callback];
+  [_pickerCancelCallbacks addObject:cancelCallback];
+
+  UIViewController *rootViewController = RCTKeyWindow().rootViewController;
+  [rootViewController presentViewController:imagePicker animated:YES completion:nil];
+}
+
+- (void)_dismissPicker:(UIImagePickerController *)picker args:(NSArray *)args
 {
   NSUInteger index = [_pickers indexOfObject:picker];
-  RCTResponseSenderBlock callback = _pickerCancelCallbacks[index];
+  RCTResponseSenderBlock successCallback = _pickerCallbacks[index];
+  RCTResponseSenderBlock cancelCallback = _pickerCancelCallbacks[index];
 
   [_pickers removeObjectAtIndex:index];
   [_pickerCallbacks removeObjectAtIndex:index];
@@ -144,7 +160,11 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
   UIViewController *rootViewController = RCTKeyWindow().rootViewController;
   [rootViewController dismissViewControllerAnimated:YES completion:nil];
 
-  callback(@[]);
+  if (args) {
+    successCallback(args);
+  } else {
+    cancelCallback(@[]);
+  }
 }
 
 @end
diff --git a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
index 33a9cb31bebe..3cb4d1c3b4d5 100644
--- a/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
+++ b/Libraries/CameraRoll/RCTPhotoLibraryImageLoader.m
@@ -30,7 +30,7 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL
 - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
+                                        resizeMode:(RCTResizeMode)resizeMode
                                    progressHandler:(RCTImageLoaderProgressBlock)progressHandler
                                  completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
 {
@@ -71,7 +71,7 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
   }
 
   PHImageContentMode contentMode = PHImageContentModeAspectFill;
-  if (resizeMode == UIViewContentModeScaleAspectFit) {
+  if (resizeMode == RCTResizeModeContain) {
     contentMode = PHImageContentModeAspectFit;
   }
 
diff --git a/Libraries/Components/Clipboard/Clipboard.js b/Libraries/Components/Clipboard/Clipboard.js
new file mode 100644
index 000000000000..1a1e157238a8
--- /dev/null
+++ b/Libraries/Components/Clipboard/Clipboard.js
@@ -0,0 +1,49 @@
+/**
+ * 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 Clipboard
+ * @flow
+ */
+'use strict';
+
+const Clipboard = require('NativeModules').Clipboard;
+const deprecatedCallback = require('deprecatedCallback');
+
+/**
+ * `Clipboard` gives you an interface for setting and getting content from Clipboard on both iOS and Android
+ */
+module.exports = {
+  /**
+   * Get content of string type, this method returns a `Promise`, so you can use following code to get clipboard content
+   * ```javascript
+   * async _getContent() {
+   *   var content = await Clipboard.getString();
+   * }
+   * ```
+   */
+  getString(): Promise<string> {
+    return deprecatedCallback(
+      Clipboard.getString(),
+      Array.prototype.slice.call(arguments),
+      'success-first',
+      'Clipboard.getString(callback) is deprecated. Use the returned Promise instead'
+    );
+  },
+  /**
+   * Set content of string type. You can use following code to set clipboard content
+   * ```javascript
+   * _setContent() {
+   *   Clipboard.setString('hello world');
+   * }
+   * ```
+   * @param the content to be stored in the clipboard.
+   */
+  setString(content: string) {
+    Clipboard.setString(content);
+  }
+};
diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js
index ede2500ee5c2..c436abbbb362 100644
--- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js
+++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js
@@ -16,14 +16,12 @@
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var PropTypes = require('ReactPropTypes');
 var React = require('React');
-var RCTDatePickerIOSConsts = require('NativeModules').UIManager.RCTDatePicker.Constants;
+var RCTDatePickerIOSConsts = require('UIManager').RCTDatePicker.Constants;
 var StyleSheet = require('StyleSheet');
 var View = require('View');
 
 var requireNativeComponent = require('requireNativeComponent');
 
-var DATEPICKER = 'datepicker';
-
 type DefaultProps = {
   mode: 'date' | 'time' | 'datetime';
 };
@@ -38,6 +36,9 @@ type Event = Object;
  * source of truth.
  */
 var DatePickerIOS = React.createClass({
+  // TOOD: Put a better type for _picker
+  _picker: (undefined: ?$FlowFixMe),
+
   mixins: [NativeMethodsMixin],
 
   propTypes: {
@@ -108,8 +109,8 @@ var DatePickerIOS = React.createClass({
     // certain values. In other words, the embedder of this component should
     // be the source of truth, not the native component.
     var propsTimeStamp = this.props.date.getTime();
-    if (nativeTimeStamp !== propsTimeStamp) {
-      this.refs[DATEPICKER].setNativeProps({
+    if (this._picker && nativeTimeStamp !== propsTimeStamp) {
+      this._picker.setNativeProps({
         date: propsTimeStamp,
       });
     }
@@ -120,7 +121,7 @@ var DatePickerIOS = React.createClass({
     return (
       <View style={props.style}>
         <RCTDatePickerIOS
-          ref={DATEPICKER}
+          ref={ picker => this._picker = picker }
           style={styles.datePickerIOS}
           date={props.date.getTime()}
           maximumDate={
diff --git a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js
new file mode 100644
index 000000000000..64ae4a506a75
--- /dev/null
+++ b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js
@@ -0,0 +1,84 @@
+/**
+ * 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 DatePickerAndroid
+ * @flow
+ */
+'use strict';
+
+const DatePickerModule = require('NativeModules').DatePickerAndroid;
+
+/**
+ * Convert a Date to a timestamp.
+ */
+function _toMillis(options: Object, key: string) {
+  const dateVal = options[key];
+  // Is it a Date object?
+  if (typeof dateVal === 'object' && typeof dateVal.getMonth === 'function') {
+    options[key] = dateVal.getTime();
+  }
+}
+
+/**
+ * Opens the standard Android date picker dialog.
+ *
+ * ### Example
+ *
+ * ```
+ * try {
+ *   const {action, year, month, day} = await DatePickerAndroid.open({
+ *     // Use `new Date()` for current date.
+ *     // May 25 2020. Month 0 is January.
+ *     date: new Date(2020, 4, 25)
+ *   });
+ *   if (action !== DatePickerAndroid.dismissedAction) {
+ *     // Selected year, month (0-11), day
+ *   }
+ * } catch ({code, message}) {
+ *   console.warn('Cannot open date picker', message);
+ * }
+ * ```
+ */
+class DatePickerAndroid {
+  /**
+   * Opens the standard Android date picker dialog.
+   *
+   * The available keys for the `options` object are:
+   *   * `date` (`Date` object or timestamp in milliseconds) - date to show by default
+   *   * `minDate` (`Date` or timestamp in milliseconds) - minimum date that can be selected
+   *   * `maxDate` (`Date` object or timestamp in milliseconds) - minimum date that can be selected
+   *
+   * Returns a Promise which will be invoked an object containing `action`, `year`, `month` (0-11),
+   * `day` if the user picked a date. If the user dismissed the dialog, the Promise will
+   * still be resolved with action being `DatePickerAndroid.dismissedAction` and all the other keys
+   * being undefined. **Always** check whether the `action` before reading the values.
+   *
+   * Note the native date picker dialog has some UI glitches on Android 4 and lower
+   * when using the `minDate` and `maxDate` options.
+   */
+  static async open(options: Object): Promise<Object> {
+    let optionsMs = options;
+    if (optionsMs) {
+      _toMillis(options, 'date');
+      _toMillis(options, 'minDate');
+      _toMillis(options, 'maxDate');
+    }
+    return DatePickerModule.open(options);
+  }
+
+  /**
+   * A date has been selected.
+   */
+  static get dateSetAction() { return 'dateSetAction'; }
+  /**
+   * The dialog has been dismissed.
+   */
+  static get dismissedAction() { return 'dismissedAction'; }
+}
+
+module.exports = DatePickerAndroid;
diff --git a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js
new file mode 100644
index 000000000000..4a6d99423f3d
--- /dev/null
+++ b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js
@@ -0,0 +1,24 @@
+/**
+ * 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 DatePickerAndroid
+ * @flow
+ */
+'use strict';
+
+var warning = require('fbjs/lib/warning');
+
+const DatePickerAndroid = {
+  async open(options: Object): Promise<Object> {
+    return Promise.reject({
+      message: 'DatePickerAndroid is not supported on this platform.'
+    });
+  },
+};
+
+module.exports = DatePickerAndroid;
diff --git a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js
index 407f237e5052..8bb5e12ed7c2 100644
--- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js
+++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js
@@ -10,17 +10,16 @@
  */
 'use strict';
 
-var DrawerConsts = require('NativeModules').UIManager.AndroidDrawerLayout.Constants;
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var React = require('React');
 var ReactPropTypes = require('ReactPropTypes');
-var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var RCTUIManager = require('NativeModules').UIManager;
 var StyleSheet = require('StyleSheet');
+var UIManager = require('UIManager');
 var View = require('View');
 
+var DrawerConsts = UIManager.AndroidDrawerLayout.Constants;
+
 var dismissKeyboard = require('dismissKeyboard');
-var merge = require('merge');
 var requireNativeComponent = require('requireNativeComponent');
 
 var RK_DRAWER_REF = 'drawerlayout';
@@ -29,6 +28,7 @@ var INNERVIEW_REF = 'innerView';
 var DrawerLayoutValidAttributes = {
   drawerWidth: true,
   drawerPosition: true,
+  drawerLockMode: true
 };
 
 var DRAWER_STATES = [
@@ -96,6 +96,18 @@ var DrawerLayoutAndroid = React.createClass({
      * from the edge of the window.
      */
     drawerWidth: ReactPropTypes.number,
+    /**
+     * Specifies the lock mode of the drawer. The drawer can be locked in 3 states:
+     * - unlocked (default), meaning that the drawer will respond (open/close) to touch gestures.
+     * - locked closed, meaning that the drawer will stay closed and not respond to gestures.
+     * - locked open, meaning that the drawer will stay opened and not respond to gestures.
+     * The drawer may still be opened and closed programmatically (`openDrawer`/`closeDrawer`).
+     */
+    drawerLockMode: ReactPropTypes.oneOf([
+      'unlocked',
+      'locked-closed',
+      'locked-open'
+    ]),
     /**
      * Function called whenever there is an interaction with the navigation view.
      */
@@ -103,7 +115,7 @@ var DrawerLayoutAndroid = React.createClass({
     /**
      * Function called when the drawer state has changed. The drawer can be in 3 states:
      * - idle, meaning there is no interaction with the navigation view happening at the time
-     * - dragging, meaning there is currently an interation with the navigation view
+     * - dragging, meaning there is currently an interaction with the navigation view
      * - settling, meaning that there was an interaction with the navigation view, and the
      * navigation view is now finishing it's closing or opening animation
      */
@@ -143,6 +155,7 @@ var DrawerLayoutAndroid = React.createClass({
         ref={RK_DRAWER_REF}
         drawerWidth={this.props.drawerWidth}
         drawerPosition={this.props.drawerPosition}
+        drawerLockMode={this.props.drawerLockMode}
         style={styles.base}
         onDrawerSlide={this._onDrawerSlide}
         onDrawerOpen={this._onDrawerOpen}
@@ -182,17 +195,17 @@ var DrawerLayoutAndroid = React.createClass({
   },
 
   openDrawer: function() {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       this._getDrawerLayoutHandle(),
-      RCTUIManager.AndroidDrawerLayout.Commands.openDrawer,
+      UIManager.AndroidDrawerLayout.Commands.openDrawer,
       null
     );
   },
 
   closeDrawer: function() {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       this._getDrawerLayoutHandle(),
-      RCTUIManager.AndroidDrawerLayout.Commands.closeDrawer,
+      UIManager.AndroidDrawerLayout.Commands.closeDrawer,
       null
     );
   },
diff --git a/Libraries/Components/Intent/IntentAndroid.android.js b/Libraries/Components/Intent/IntentAndroid.android.js
index 75c620ee99fa..0e563d2860d6 100644
--- a/Libraries/Components/Intent/IntentAndroid.android.js
+++ b/Libraries/Components/Intent/IntentAndroid.android.js
@@ -7,15 +7,57 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule IntentAndroid
+ * @flow
  */
 'use strict';
 
-var IntentAndroidModule = require('NativeModules').IntentAndroid;
-var invariant = require('invariant');
+var Linking = require('Linking');
+var invariant = require('fbjs/lib/invariant');
 
 /**
+ * NOTE: `IntentAndroid` is being deprecated. Use `Linking` instead.
+ *
  * `IntentAndroid` gives you a general interface to handle external links.
  *
+ * ### Basic Usage
+ *
+ * #### Handling deep links
+ *
+ * If your app was launched from an external url registered to your app you can
+ * access and handle it from any component you want with
+ *
+ * ```
+ * componentDidMount() {
+ *   var url = IntentAndroid.getInitialURL(url => {
+ *     if (url) {
+ *       console.log('Initial url is: ' + url);
+ *     }
+ *   });
+ * }
+ * ```
+ *
+ * Example to add support for deep linking inside your React Native app.
+ * More Info: [Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters).
+ *
+ * Edit in `android/app/src/main/AndroidManifest.xml`
+ *
+ * ```
+ *  <intent-filter>
+ *    <action android:name="android.intent.action.VIEW" />
+ *    <category android:name="android.intent.category.DEFAULT" />
+ *    <category android:name="android.intent.category.BROWSABLE" />
+ *
+ *    <!-- Accepts URIs that begin with "http://www.facebook.com/react -->
+ *    <data android:scheme="http"
+ *       android:host="www.facebook.com"
+ *       android:pathPrefix="/react" />
+ *    <!-- note that the leading "/" is required for pathPrefix-->
+ *
+ *    <!-- Accepts URIs that begin with "facebook://react -->
+ *    <!-- <data android:scheme="facebook" android:host="react" /> -->
+ *  </intent-filter>
+ * ```
+ *
  * #### Opening external links
  *
  * To start the corresponding activity for a link (web URL, email, contact etc.), call
@@ -50,10 +92,12 @@ class IntentAndroid {
    * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first.
    *
    * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
+   *
+   * @deprecated
    */
   static openURL(url: string) {
-    this._validateURL(url);
-    IntentAndroidModule.openURL(url);
+    console.warn('"IntentAndroid.openURL" is deprecated. Use the promise based "Linking.openURL" instead.');
+    Linking.openURL(url);
   }
 
   /**
@@ -65,25 +109,33 @@ class IntentAndroid {
    * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
    *
    * @param URL the URL to open
+   *
+   * @deprecated
    */
   static canOpenURL(url: string, callback: Function) {
-    this._validateURL(url);
+    console.warn('"IntentAndroid.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.');
     invariant(
       typeof callback === 'function',
       'A valid callback function is required'
     );
-    IntentAndroidModule.canOpenURL(url, callback);
+    Linking.canOpenURL(url).then(callback);
   }
 
-  static _validateURL(url: string) {
-    invariant(
-      typeof url === 'string',
-      'Invalid URL: should be a string. Was: ' + url
-    );
+  /**
+   * If the app launch was triggered by an app link with {@code Intent.ACTION_VIEW},
+   * it will give the link url, otherwise it will give `null`
+   *
+   * Refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents
+   *
+   * @deprecated
+   */
+  static getInitialURL(callback: Function) {
+    console.warn('"IntentAndroid.getInitialURL" is deprecated. Use the promise based "Linking.getInitialURL" instead.');
     invariant(
-      url,
-      'Invalid URL: cannot be empty'
+      typeof callback === 'function',
+      'A valid callback function is required'
     );
+    Linking.getInitialURL().then(callback);
   }
 }
 
diff --git a/Libraries/Components/Intent/IntentAndroid.ios.js b/Libraries/Components/Intent/IntentAndroid.ios.js
index 7ae19934d682..8b4af29ae6e3 100644
--- a/Libraries/Components/Intent/IntentAndroid.ios.js
+++ b/Libraries/Components/Intent/IntentAndroid.ios.js
@@ -11,7 +11,7 @@
 'use strict';
 
 module.exports = {
-  openURI: function(url) {
+  openURL: function(url) {
     console.error('IntentAndroid is not supported on iOS');
   },
 };
diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js
index de8aaa9f729a..13ee06ee8618 100644
--- a/Libraries/Components/MapView/MapView.js
+++ b/Libraries/Components/MapView/MapView.js
@@ -11,56 +11,35 @@
  */
 'use strict';
 
-var EdgeInsetsPropType = require('EdgeInsetsPropType');
-var NativeMethodsMixin = require('NativeMethodsMixin');
-var Platform = require('Platform');
-var React = require('React');
-var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var View = require('View');
-
-var deepDiffer = require('deepDiffer');
-var insetsDiffer = require('insetsDiffer');
-var merge = require('merge');
-var requireNativeComponent = require('requireNativeComponent');
+const ColorPropType = require('ColorPropType');
+const EdgeInsetsPropType = require('EdgeInsetsPropType');
+const Image = require('Image');
+const NativeMethodsMixin = require('NativeMethodsMixin');
+const Platform = require('Platform');
+const RCTMapConfig = require('UIManager').RCTMap;
+const RCTMapConstants = RCTMapConfig && RCTMapConfig.Constants;
+const React = require('React');
+const StyleSheet = require('StyleSheet');
+const View = require('View');
+
+const deprecatedPropType = require('deprecatedPropType');
+const processColor = require('processColor');
+const resolveAssetSource = require('resolveAssetSource');
+const requireNativeComponent = require('requireNativeComponent');
 
 type Event = Object;
-type MapRegion = {
-  latitude: number;
-  longitude: number;
-  latitudeDelta: number;
-  longitudeDelta: number;
-};
-
-var MapView = React.createClass({
-  mixins: [NativeMethodsMixin],
 
-  checkAnnotationIds: function (annotations: Array<Object>) {
+export type AnnotationDragState = $Enum<{
+  idle: string;
+  starting: string;
+  dragging: string;
+  canceling: string;
+  ending: string;
+}>;
 
-    var newAnnotations = annotations.map(function (annotation) {
-      if (!annotation.id) {
-        // TODO: add a base64 (or similar) encoder here
-        annotation.id = encodeURIComponent(JSON.stringify(annotation));
-      }
+const MapView = React.createClass({
 
-      return annotation;
-    });
-
-    this.setState({
-      annotations: newAnnotations
-    });
-  },
-
-  componentWillMount: function() {
-    if (this.props.annotations) {
-      this.checkAnnotationIds(this.props.annotations);
-    }
-  },
-
-  componentWillReceiveProps: function(nextProps: Object) {
-    if (nextProps.annotations) {
-      this.checkAnnotationIds(nextProps.annotations);
-    }
-  },
+  mixins: [NativeMethodsMixin],
 
   propTypes: {
     ...View.propTypes,
@@ -71,15 +50,22 @@ var MapView = React.createClass({
     style: View.propTypes.style,
 
     /**
-     * If `true` the app will ask for the user's location and focus on it.
-     * Default value is `false`.
+     * If `true` the app will ask for the user's location and display it on
+     * the map. Default value is `false`.
      *
-     * **NOTE**: You need to add NSLocationWhenInUseUsageDescription key in
-     * Info.plist to enable geolocation, otherwise it is going
-     * to *fail silently*!
+     * **NOTE**: on iOS, you need to add the `NSLocationWhenInUseUsageDescription`
+     * key in Info.plist to enable geolocation, otherwise it will fail silently.
      */
     showsUserLocation: React.PropTypes.bool,
 
+    /**
+     * If `true` the map will follow the user's location whenever it changes.
+     * Note that this has no effect unless `showsUserLocation` is enabled.
+     * Default value is `true`.
+     * @platform ios
+     */
+    followUserLocation: React.PropTypes.bool,
+
     /**
      * If `false` points of interest won't be displayed on the map.
      * Default value is `true`.
@@ -87,6 +73,13 @@ var MapView = React.createClass({
      */
     showsPointsOfInterest: React.PropTypes.bool,
 
+    /**
+     * If `false` compass won't be displayed on the map.
+     * Default value is `true`.
+     * @platform ios
+     */
+    showsCompass: React.PropTypes.bool,
+
     /**
      * If `false` the user won't be able to pinch/zoom the map.
      * Default value is `true`.
@@ -122,7 +115,9 @@ var MapView = React.createClass({
      *
      * - standard: standard road map (default)
      * - satellite: satellite view
-     * - hybrid: satellite view with roads and points of interest overlayed
+     * - hybrid: satellite view with roads and points of interest overlaid
+     *
+     * @platform ios
      */
     mapType: React.PropTypes.oneOf([
       'standard',
@@ -144,15 +139,16 @@ var MapView = React.createClass({
       longitude: React.PropTypes.number.isRequired,
 
       /**
-       * Distance between the minimun and the maximum latitude/longitude
+       * Distance between the minimum and the maximum latitude/longitude
        * to be displayed.
        */
-      latitudeDelta: React.PropTypes.number.isRequired,
-      longitudeDelta: React.PropTypes.number.isRequired,
+      latitudeDelta: React.PropTypes.number,
+      longitudeDelta: React.PropTypes.number,
     }),
 
     /**
      * Map annotations with title/subtitle.
+     * @platform ios
      */
     annotations: React.PropTypes.arrayOf(React.PropTypes.shape({
       /**
@@ -166,6 +162,28 @@ var MapView = React.createClass({
        */
       animateDrop: React.PropTypes.bool,
 
+      /**
+       * Whether the pin should be draggable or not
+       */
+      draggable: React.PropTypes.bool,
+
+      /**
+       * Event that fires when the annotation drag state changes.
+       */
+      onDragStateChange: React.PropTypes.func,
+
+      /**
+       * Event that fires when the annotation gets was tapped by the user
+       * and the callout view was displayed.
+       */
+      onFocus: React.PropTypes.func,
+
+      /**
+       * Event that fires when another annotation or the mapview itself
+       * was tapped and a previously shown annotation will be closed.
+       */
+      onBlur: React.PropTypes.func,
+
       /**
        * Annotation title/subtile.
        */
@@ -173,37 +191,101 @@ var MapView = React.createClass({
       subtitle: React.PropTypes.string,
 
       /**
-       * Whether the Annotation has callout buttons.
+       * Callout views.
+       */
+      leftCalloutView: React.PropTypes.element,
+      rightCalloutView: React.PropTypes.element,
+      detailCalloutView: React.PropTypes.element,
+
+      /**
+       * The pin color. This can be any valid color string, or you can use one
+       * of the predefined PinColors constants. Applies to both standard pins
+       * and custom pin images.
+       *
+       * Note that on iOS 8 and earlier, only the standard PinColor constants
+       * are supported for regular pins. For custom pin images, any tintColor
+       * value is supported on all iOS versions.
        */
-      hasLeftCallout: React.PropTypes.bool,
-      hasRightCallout: React.PropTypes.bool,
+      tintColor: ColorPropType,
 
       /**
-       * Event handlers for callout buttons.
+       * Custom pin image. This must be a static image resource inside the app.
        */
-      onLeftCalloutPress: React.PropTypes.func,
-      onRightCalloutPress: React.PropTypes.func,
+      image: Image.propTypes.source,
+
+      /**
+       * Custom pin view. If set, this replaces the pin or custom pin image.
+       */
+      view: React.PropTypes.element,
 
       /**
        * annotation id
        */
-      id: React.PropTypes.string
+      id: React.PropTypes.string,
 
+      /**
+       * Deprecated. Use the left/right/detailsCalloutView props instead.
+       */
+      hasLeftCallout: deprecatedPropType(
+        React.PropTypes.bool,
+        'Use `leftCalloutView` instead.'
+      ),
+      hasRightCallout: deprecatedPropType(
+        React.PropTypes.bool,
+        'Use `rightCalloutView` instead.'
+      ),
+      onLeftCalloutPress: deprecatedPropType(
+        React.PropTypes.func,
+        'Use `leftCalloutView` instead.'
+      ),
+      onRightCalloutPress: deprecatedPropType(
+        React.PropTypes.func,
+        'Use `rightCalloutView` instead.'
+      ),
+    })),
+
+    /**
+     * Map overlays
+     * @platform ios
+     */
+    overlays: React.PropTypes.arrayOf(React.PropTypes.shape({
+      /**
+       * Polyline coordinates
+       */
+      coordinates: React.PropTypes.arrayOf(React.PropTypes.shape({
+        latitude: React.PropTypes.number.isRequired,
+        longitude: React.PropTypes.number.isRequired
+      })),
+
+      /**
+       * Line attributes
+       */
+      lineWidth: React.PropTypes.number,
+      strokeColor: ColorPropType,
+      fillColor: ColorPropType,
+
+      /**
+       * Overlay id
+       */
+      id: React.PropTypes.string
     })),
 
     /**
      * Maximum size of area that can be displayed.
+     * @platform ios
      */
     maxDelta: React.PropTypes.number,
 
     /**
      * Minimum size of area that can be displayed.
+     * @platform ios
      */
     minDelta: React.PropTypes.number,
 
     /**
      * Insets for the map's legal label, originally at bottom left of the map.
      * See `EdgeInsetsPropType.js` for more information.
+     * @platform ios
      */
     legalLabelInsets: EdgeInsetsPropType,
 
@@ -218,7 +300,7 @@ var MapView = React.createClass({
     onRegionChangeComplete: React.PropTypes.func,
 
     /**
-     * Callback that is called once, when the user taps an annotation.
+     * Deprecated. Use annotation onFocus and onBlur instead.
      */
     onAnnotationPress: React.PropTypes.func,
 
@@ -228,49 +310,209 @@ var MapView = React.createClass({
     active: React.PropTypes.bool,
   },
 
-  _onChange: function(event: Event) {
-    if (event.nativeEvent.continuous) {
-      this.props.onRegionChange &&
-        this.props.onRegionChange(event.nativeEvent.region);
-    } else {
-      this.props.onRegionChangeComplete &&
-        this.props.onRegionChangeComplete(event.nativeEvent.region);
-    }
-  },
+  render: function() {
+    let children = [], {annotations, overlays, followUserLocation} = this.props;
+    annotations = annotations && annotations.map((annotation: Object) => {
+      let {
+        id,
+        image,
+        tintColor,
+        view,
+        leftCalloutView,
+        rightCalloutView,
+        detailCalloutView,
+      } = annotation;
+
+      if (!view && image && tintColor) {
+        view = <Image
+          style={{
+            tintColor: processColor(tintColor),
+          }}
+          source={image}
+        />;
+        image = undefined;
+      }
+      if (view) {
+        if (image) {
+          console.warn('`image` and `view` both set on annotation. Image will be ignored.');
+        }
+        var viewIndex = children.length;
+        children.push(React.cloneElement(view, {
+          style: [styles.annotationView, view.props.style || {}]
+        }));
+      }
+      if (leftCalloutView) {
+        var leftCalloutViewIndex = children.length;
+        children.push(React.cloneElement(leftCalloutView, {
+          style: [styles.calloutView, leftCalloutView.props.style || {}]
+        }));
+      }
+      if (rightCalloutView) {
+        var rightCalloutViewIndex = children.length;
+        children.push(React.cloneElement(rightCalloutView, {
+          style: [styles.calloutView, rightCalloutView.props.style || {}]
+        }));
+      }
+      if (detailCalloutView) {
+        var detailCalloutViewIndex = children.length;
+        children.push(React.cloneElement(detailCalloutView, {
+          style: [styles.calloutView, detailCalloutView.props.style || {}]
+        }));
+      }
 
-  _onPress: function(event: Event) {
-    if (event.nativeEvent.action === 'annotation-click') {
-      this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation);
-    }
+      let result = {
+        ...annotation,
+        tintColor: tintColor && processColor(tintColor),
+        image,
+        viewIndex,
+        leftCalloutViewIndex,
+        rightCalloutViewIndex,
+        detailCalloutViewIndex,
+        view: undefined,
+        leftCalloutView: undefined,
+        rightCalloutView: undefined,
+        detailCalloutView: undefined,
+      };
+      result.id = id || encodeURIComponent(JSON.stringify(result));
+      result.image = image && resolveAssetSource(image);
+      return result;
+    });
+    overlays = overlays && overlays.map((overlay: Object) => {
+      let {id, fillColor, strokeColor} = overlay;
+      let result = {
+        ...overlay,
+        strokeColor: strokeColor && processColor(strokeColor),
+        fillColor: fillColor && processColor(fillColor),
+      };
+      result.id = id || encodeURIComponent(JSON.stringify(result));
+      return result;
+    });
 
-    if (event.nativeEvent.action === 'callout-click') {
-      if (!this.props.annotations) {
-        return;
+    const findByAnnotationId = (annotationId: string) => {
+      if (!annotations) {
+        return null;
       }
-
-      // Find the annotation with the id of what has been pressed
-      for (var i = 0; i < this.props.annotations.length; i++) {
-        var annotation = this.props.annotations[i];
-        if (annotation.id === event.nativeEvent.annotationId) {
-          // Pass the right function
-          if (event.nativeEvent.side === 'left') {
-            annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent);
-          } else if (event.nativeEvent.side === 'right') {
-            annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent);
-          }
+      for (let i = 0, l = annotations.length; i < l; i++) {
+        if (annotations[i].id === annotationId) {
+          return annotations[i];
         }
       }
+      return null;
+    };
+
+    // TODO: these should be separate events, to reduce bridge traffic
+    let onPress, onAnnotationDragStateChange, onAnnotationFocus, onAnnotationBlur;
+    if (annotations) {
+      onPress = (event: Event) => {
+        if (event.nativeEvent.action === 'annotation-click') {
+          // TODO: Remove deprecated onAnnotationPress API call later.
+          this.props.onAnnotationPress &&
+            this.props.onAnnotationPress(event.nativeEvent.annotation);
+        } else if (event.nativeEvent.action === 'callout-click') {
+          const annotation = findByAnnotationId(event.nativeEvent.annotationId);
+          if (annotation) {
+            // Pass the right function
+            if (event.nativeEvent.side === 'left' && annotation.onLeftCalloutPress) {
+              annotation.onLeftCalloutPress(event.nativeEvent);
+            } else if (event.nativeEvent.side === 'right' && annotation.onRightCalloutPress) {
+              annotation.onRightCalloutPress(event.nativeEvent);
+            }
+          }
+        }
+      };
+      onAnnotationDragStateChange = (event: Event) => {
+        const annotation = findByAnnotationId(event.nativeEvent.annotationId);
+        if (annotation) {
+          // Update location
+          annotation.latitude = event.nativeEvent.latitude;
+          annotation.longitude = event.nativeEvent.longitude;
+          // Call callback
+          annotation.onDragStateChange &&
+            annotation.onDragStateChange(event.nativeEvent);
+        }
+      };
+      onAnnotationFocus = (event: Event) => {
+        const annotation = findByAnnotationId(event.nativeEvent.annotationId);
+        if (annotation && annotation.onFocus) {
+          annotation.onFocus(event.nativeEvent);
+        }
+      };
+      onAnnotationBlur = (event: Event) => {
+        const annotation = findByAnnotationId(event.nativeEvent.annotationId);
+        if (annotation && annotation.onBlur) {
+          annotation.onBlur(event.nativeEvent);
+        }
+      };
+    }
+
+    // TODO: these should be separate events, to reduce bridge traffic
+    if (this.props.onRegionChange || this.props.onRegionChangeComplete) {
+      var onChange = (event: Event) => {
+        if (event.nativeEvent.continuous) {
+          this.props.onRegionChange &&
+            this.props.onRegionChange(event.nativeEvent.region);
+        } else {
+          this.props.onRegionChangeComplete &&
+            this.props.onRegionChangeComplete(event.nativeEvent.region);
+        }
+      };
+    }
 
+    // followUserLocation defaults to true if showUserLocation is set
+    if (followUserLocation === undefined) {
+      followUserLocation = this.props.showUserLocation;
     }
+
+    return (
+      <RCTMap
+        {...this.props}
+        annotations={annotations}
+        children={children}
+        followUserLocation={followUserLocation}
+        overlays={overlays}
+        onPress={onPress}
+        onChange={onChange}
+        onAnnotationDragStateChange={onAnnotationDragStateChange}
+        onAnnotationFocus={onAnnotationFocus}
+        onAnnotationBlur={onAnnotationBlur}
+      />
+    );
   },
+});
 
-  render: function() {
-    return <RCTMap {...this.props} onPress={this._onPress} onChange={this._onChange} />;
+const styles = StyleSheet.create({
+  annotationView: {
+    position: 'absolute',
+    backgroundColor: 'transparent',
+  },
+  calloutView: {
+    position: 'absolute',
+    backgroundColor: 'white',
   },
 });
 
-var RCTMap = requireNativeComponent('RCTMap', MapView, {
-  nativeOnly: {onChange: true, onPress: true}
+/**
+ * Standard iOS MapView pin color constants, to be used with the
+ * `annotation.tintColor` property. On iOS 8 and earlier these are the
+ * only supported values when using regular pins. On iOS 9 and later
+ * you are not obliged to use these, but they are useful for matching
+ * the standard iOS look and feel.
+ */
+const PinColors = RCTMapConstants && RCTMapConstants.PinColors;
+MapView.PinColors = PinColors && {
+  RED: PinColors.RED,
+  GREEN: PinColors.GREEN,
+  PURPLE: PinColors.PURPLE,
+};
+
+const RCTMap = requireNativeComponent('RCTMap', MapView, {
+  nativeOnly: {
+    onAnnotationDragStateChange: true,
+    onAnnotationFocus: true,
+    onAnnotationBlur: true,
+    onChange: true,
+    onPress: true
+  }
 });
 
 module.exports = MapView;
diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js
index a0f2199b295d..c4a2405258df 100644
--- a/Libraries/Components/Navigation/NavigatorIOS.ios.js
+++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js
@@ -20,7 +20,7 @@ var StaticContainer = require('StaticContainer.react');
 var StyleSheet = require('StyleSheet');
 var View = require('View');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var logError = require('logError');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
@@ -74,7 +74,7 @@ type State = {
   fromIndex: number;
   toIndex: number;
   makingNavigatorRequest: boolean;
-  updatingAllIndicesAtOrBeyond: number;
+  updatingAllIndicesAtOrBeyond: ?number;
 }
 
 type Event = Object;
@@ -95,6 +95,12 @@ type Event = Object;
  * NavigatorIOS wraps UIKit navigation and allows you to add back-swipe
  * functionality across your app.
  *
+ * > **NOTE**: This Component is not maintained by Facebook
+ * >
+ * > This component is under community responsibility.
+ * > If a pure JavaScript solution fits your needs you may try the `Navigator`
+ * > component instead.
+ *
  * #### Routes
  * A route is an object used to describe each page in the navigator. The first
  * route is provided to NavigatorIOS as `initialRoute`:
@@ -113,7 +119,7 @@ type Event = Object;
  * },
  * ```
  *
- * Now MyView will be rendered by the navigator. It will recieve the route
+ * Now MyView will be rendered by the navigator. It will receive the route
  * object in the `route` prop, a navigator, and all of the props specified in
  * `passProps`.
  *
@@ -166,6 +172,11 @@ type Event = Object;
  * });
  * ```
  *
+ * Props passed to the NavigatorIOS component will set the default configuration
+ * for the navigation bar. Props passed as properties to a route object will set
+ * the configuration for that route's navigation bar, overriding any props
+ * passed to the NavigatorIOS component.
+ *
  */
 var NavigatorIOS = React.createClass({
 
@@ -242,15 +253,45 @@ var NavigatorIOS = React.createClass({
        */
       wrapperStyle: View.propTypes.style,
 
+      /**
+       * A Boolean value that indicates whether the navigation bar is hidden
+       */
+      navigationBarHidden: PropTypes.bool,
+
+      /**
+       * A Boolean value that indicates whether to hide the 1px hairline shadow
+       */
+      shadowHidden: PropTypes.bool,
+
+      /**
+       * The color used for buttons in the navigation bar
+       */
+      tintColor: PropTypes.string,
+
+      /**
+       * The background color of the navigation bar
+       */
+      barTintColor: PropTypes.string,
+
+       /**
+       * The text color of the navigation bar title
+       */
+      titleTextColor: PropTypes.string,
+
+       /**
+       * A Boolean value that indicates whether the navigation bar is translucent
+       */
+      translucent: PropTypes.bool,
+
     }).isRequired,
 
     /**
-     * A Boolean value that indicates whether the navigation bar is hidden
+     * A Boolean value that indicates whether the navigation bar is hidden by default
      */
     navigationBarHidden: PropTypes.bool,
 
     /**
-     * A Boolean value that indicates whether to hide the 1px hairline shadow
+     * A Boolean value that indicates whether to hide the 1px hairline shadow by default
      */
     shadowHidden: PropTypes.bool,
 
@@ -261,22 +302,22 @@ var NavigatorIOS = React.createClass({
     itemWrapperStyle: View.propTypes.style,
 
     /**
-     * The color used for buttons in the navigation bar
+     * The default color used for buttons in the navigation bar
      */
     tintColor: PropTypes.string,
 
     /**
-     * The background color of the navigation bar
+     * The default background color of the navigation bar
      */
     barTintColor: PropTypes.string,
 
     /**
-     * The text color of the navigation bar title
+     * The default text color of the navigation bar title
      */
     titleTextColor: PropTypes.string,
 
     /**
-     * A Boolean value that indicates whether the navigation bar is translucent
+     * A Boolean value that indicates whether the navigation bar is translucent by default
      */
     translucent: PropTypes.bool,
 
@@ -312,6 +353,12 @@ var NavigatorIOS = React.createClass({
     this.navigationContext = new NavigationContext();
   },
 
+  getDefaultProps: function(): Object {
+    return {
+      translucent: true,
+    };
+  },
+
   getInitialState: function(): State {
     return {
       idStack: [getuid()],
@@ -480,9 +527,7 @@ var NavigatorIOS = React.createClass({
           this.setState({
             requestedTopOfStack: newRequestedTopOfStack,
             makingNavigatorRequest: true,
-            // Not actually updating the indices yet until we get the native
-            // `onNavigationComplete`.
-            updatingAllIndicesAtOrBeyond: null,
+            updatingAllIndicesAtOrBeyond: this.state.requestedTopOfStack - n,
           });
         });
       }
@@ -590,38 +635,27 @@ var NavigatorIOS = React.createClass({
     this._handleNavigatorStackChanged(e);
   },
 
-  _routeToStackItem: function(route: Route, i: number) {
-    var Component = route.component;
-    var shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond !== null &&
+  _routeToStackItem: function(routeArg: Route, i: number) {
+    var {component, wrapperStyle, passProps, ...route} = routeArg;
+    var {itemWrapperStyle, ...props} = this.props;
+    var shouldUpdateChild =
+      this.state.updatingAllIndicesAtOrBeyond != null &&
       this.state.updatingAllIndicesAtOrBeyond >= i;
-
+    var Component = component;
     return (
       <StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
         <RCTNavigatorItem
-          title={route.title}
+          {...props}
+          {...route}
           style={[
             styles.stackItem,
-            this.props.itemWrapperStyle,
-            route.wrapperStyle
-          ]}
-          backButtonIcon={resolveAssetSource(route.backButtonIcon)}
-          backButtonTitle={route.backButtonTitle}
-          leftButtonIcon={resolveAssetSource(route.leftButtonIcon)}
-          leftButtonTitle={route.leftButtonTitle}
-          onNavLeftButtonTap={route.onLeftButtonPress}
-          rightButtonIcon={resolveAssetSource(route.rightButtonIcon)}
-          rightButtonTitle={route.rightButtonTitle}
-          onNavRightButtonTap={route.onRightButtonPress}
-          navigationBarHidden={this.props.navigationBarHidden}
-          shadowHidden={this.props.shadowHidden}
-          tintColor={this.props.tintColor}
-          barTintColor={this.props.barTintColor}
-          translucent={this.props.translucent !== false}
-          titleTextColor={this.props.titleTextColor}>
+            itemWrapperStyle,
+            wrapperStyle
+          ]}>
           <Component
             navigator={this.navigator}
             route={route}
-            {...route.passProps}
+            {...passProps}
           />
         </RCTNavigatorItem>
       </StaticContainer>
diff --git a/Libraries/Components/Picker/Picker.js b/Libraries/Components/Picker/Picker.js
new file mode 100644
index 000000000000..5fa3ba7e185e
--- /dev/null
+++ b/Libraries/Components/Picker/Picker.js
@@ -0,0 +1,153 @@
+/**
+ * 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 Picker
+ * @flow
+ */
+
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var PickerIOS = require('PickerIOS');
+var PickerAndroid = require('PickerAndroid');
+var Platform = require('Platform');
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var StyleSheetPropType = require('StyleSheetPropType');
+var TextStylePropTypes = require('TextStylePropTypes');
+var UnimplementedView = require('UnimplementedView');
+var View = require('View');
+var ViewStylePropTypes = require('ViewStylePropTypes');
+
+var itemStylePropType = StyleSheetPropType(TextStylePropTypes);
+
+var pickerStyleType = StyleSheetPropType({
+  ...ViewStylePropTypes,
+  color: ColorPropType,
+});
+
+var MODE_DIALOG = 'dialog';
+var MODE_DROPDOWN = 'dropdown';
+
+/**
+ * Renders the native picker component on iOS and Android. Example:
+ *
+ *     <Picker
+ *       selectedValue={this.state.language}
+ *       onValueChange={(lang) => this.setState({language: lang})}>
+ *       <Picker.Item label="Java" value="java" />
+ *       <Picker.Item label="JavaScript" value="js" />
+ *     </Picker>
+ */
+var Picker = React.createClass({
+
+  statics: {
+    /**
+     * On Android, display the options in a dialog.
+     */
+    MODE_DIALOG: MODE_DIALOG,
+    /**
+     * On Android, display the options in a dropdown (this is the default).
+     */
+    MODE_DROPDOWN: MODE_DROPDOWN,
+  },
+
+  getDefaultProps: function() {
+    return {
+      mode: MODE_DIALOG,
+    };
+  },
+
+  propTypes: {
+    ...View.propTypes,
+    style: pickerStyleType,
+    /**
+     * Value matching value of one of the items. Can be a string or an integer.
+     */
+    selectedValue: React.PropTypes.any,
+    /**
+     * Callback for when an item is selected. This is called with the following parameters:
+     *   - `itemValue`: the `value` prop of the item that was selected
+     *   - `itemPosition`: the index of the selected item in this picker
+     */
+    onValueChange: React.PropTypes.func,
+    /**
+     * If set to false, the picker will be disabled, i.e. the user will not be able to make a
+     * selection.
+     * @platform android
+     */
+    enabled: React.PropTypes.bool,
+    /**
+     * On Android, specifies how to display the selection items when the user taps on the picker:
+     *
+     *   - 'dialog': Show a modal dialog. This is the default.
+     *   - 'dropdown': Shows a dropdown anchored to the picker view
+     *
+     * @platform android
+     */
+    mode: React.PropTypes.oneOf(['dialog', 'dropdown']),
+    /**
+     * Style to apply to each of the item labels.
+     * @platform ios
+     */
+    itemStyle: itemStylePropType,
+    /**
+     * Prompt string for this picker, used on Android in dialog mode as the title of the dialog.
+     * @platform android
+     */
+    prompt: React.PropTypes.string,
+    /**
+     * Used to locate this view in end-to-end tests.
+     */
+    testID: React.PropTypes.string,
+  },
+
+  render: function() {
+      if (Platform.OS === 'ios') {
+        return <PickerIOS {...this.props}>{this.props.children}</PickerIOS>;
+      } else if (Platform.OS === 'android') {
+        return <PickerAndroid {...this.props}>{this.props.children}</PickerAndroid>;
+      } else {
+        return <UnimplementedView />;
+      }
+  },
+});
+
+/**
+ * Individual selectable item in a Picker.
+ */
+Picker.Item = React.createClass({
+
+  propTypes: {
+    /**
+     * Text to display for this item.
+     */
+    label: React.PropTypes.string.isRequired,
+    /**
+     * The value to be passed to picker's `onValueChange` callback when
+     * this item is selected. Can be a string or an integer.
+     */
+    value: React.PropTypes.any,
+    /**
+     * Color of this item's text.
+     * @platform android
+     */
+    color: ColorPropType,
+    /**
+     * Used to locate the item in end-to-end tests.
+     */
+    testID: React.PropTypes.string,
+  },
+
+  render: function() {
+    // The items are not rendered directly
+    throw null;
+  },
+});
+
+module.exports = Picker;
diff --git a/Libraries/Components/Picker/PickerAndroid.android.js b/Libraries/Components/Picker/PickerAndroid.android.js
new file mode 100644
index 000000000000..797653d001df
--- /dev/null
+++ b/Libraries/Components/Picker/PickerAndroid.android.js
@@ -0,0 +1,142 @@
+/**
+ * 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 PickerAndroid
+ * @flow
+ */
+
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var React = require('React');
+var ReactChildren = require('ReactChildren');
+var ReactPropTypes = require('ReactPropTypes');
+var StyleSheet = require('StyleSheet');
+var StyleSheetPropType = require('StyleSheetPropType');
+var View = require('View');
+var ViewStylePropTypes = require('ViewStylePropTypes');
+
+var processColor = require('processColor');
+var requireNativeComponent = require('requireNativeComponent');
+
+var REF_PICKER = 'picker';
+var MODE_DIALOG = 'dialog';
+var MODE_DROPDOWN = 'dropdown';
+
+var pickerStyleType = StyleSheetPropType({
+  ...ViewStylePropTypes,
+  color: ColorPropType,
+});
+
+type Event = Object;
+
+/**
+ * Not exposed as a public API - use <Picker> instead.
+ */
+var PickerAndroid = React.createClass({
+
+  propTypes: {
+    ...View.propTypes,
+    style: pickerStyleType,
+    selectedValue: React.PropTypes.any,
+    enabled: ReactPropTypes.bool,
+    mode: ReactPropTypes.oneOf(['dialog', 'dropdown']),
+    onValueChange: ReactPropTypes.func,
+    prompt: ReactPropTypes.string,
+    testID: ReactPropTypes.string,
+  },
+
+  getInitialState: function() {
+    return this._stateFromProps(this.props);
+  },
+
+  componentWillReceiveProps: function(nextProps) {
+    this.setState(this._stateFromProps(nextProps));
+  },
+
+  // Translate prop and children into stuff that the native picker understands.
+  _stateFromProps: function(props) {
+    var selectedIndex = 0;
+    let items = ReactChildren.map(props.children, (child, index) => {
+      if (child.props.value === props.selectedValue) {
+        selectedIndex = index;
+      }
+      let childProps = {
+        value: child.props.value,
+        label: child.props.label,
+      };
+      if (child.props.color) {
+        childProps.color = processColor(child.props.color);
+      }
+      return childProps;
+    });
+    return {selectedIndex, items};
+  },
+
+  render: function() {
+    var Picker = this.props.mode === MODE_DROPDOWN ? DropdownPicker : DialogPicker;
+
+    var nativeProps = {
+      enabled: this.props.enabled,
+      items: this.state.items,
+      mode: this.props.mode,
+      onSelect: this._onChange,
+      prompt: this.props.prompt,
+      selected: this.state.selectedIndex,
+      testID: this.props.testID,
+      style: [styles.pickerAndroid, this.props.style],
+    };
+
+    return <Picker ref={REF_PICKER} {...nativeProps} />;
+  },
+
+  _onChange: function(event: Event) {
+    if (this.props.onValueChange) {
+      var position = event.nativeEvent.position;
+      if (position >= 0) {
+        var value = this.props.children[position].props.value;
+        this.props.onValueChange(value, position);
+      } else {
+        this.props.onValueChange(null, position);
+      }
+    }
+
+    // The picker is a controlled component. This means we expect the
+    // on*Change handlers to be in charge of updating our
+    // `selectedValue` prop. That way they can also
+    // disallow/undo/mutate the selection of certain values. In other
+    // words, the embedder of this component should be the source of
+    // truth, not the native component.
+    if (this.refs[REF_PICKER] && this.state.selectedIndex !== event.nativeEvent.position) {
+      this.refs[REF_PICKER].setNativeProps({selected: this.state.selectedIndex});
+    }
+  },
+});
+
+var styles = StyleSheet.create({
+  pickerAndroid: {
+    // The picker will conform to whatever width is given, but we do
+    // have to set the component's height explicitly on the
+    // surrounding view to ensure it gets rendered.
+    // TODO would be better to export a native constant for this,
+    // like in iOS the RCTDatePickerManager.m
+    height: 50,
+  },
+});
+
+var cfg = {
+  nativeOnly: {
+    items: true,
+    selected: true,
+  }
+};
+
+var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg);
+var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg);
+
+module.exports = PickerAndroid;
diff --git a/packager/react-packager/src/FileWatcher/__mocks__/sane.js b/Libraries/Components/Picker/PickerAndroid.ios.js
similarity index 77%
rename from packager/react-packager/src/FileWatcher/__mocks__/sane.js
rename to Libraries/Components/Picker/PickerAndroid.ios.js
index 9823a930d128..fc589882864a 100644
--- a/packager/react-packager/src/FileWatcher/__mocks__/sane.js
+++ b/Libraries/Components/Picker/PickerAndroid.ios.js
@@ -5,9 +5,9 @@
  * 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 PickerAndroid
  */
 'use strict';
 
-module.exports = {
-  WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher')
-};
+module.exports = require('UnimplementedView');
diff --git a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
index fb2175b9962e..95e5504a4499 100644
--- a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
+++ b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js
@@ -15,11 +15,13 @@ var React = require('React');
 var ReactPropTypes = require('ReactPropTypes');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var View = require('View');
+var ColorPropType = require('ColorPropType');
 
 var requireNativeComponent = require('requireNativeComponent');
 
 var STYLE_ATTRIBUTES = [
   'Horizontal',
+  'Normal',
   'Small',
   'Large',
   'Inverse',
@@ -69,6 +71,7 @@ var ProgressBarAndroid = React.createClass({
      * Style of the ProgressBar. One of:
      *
      * - Horizontal
+     * - Normal (default)
      * - Small
      * - Large
      * - Inverse
@@ -88,7 +91,7 @@ var ProgressBarAndroid = React.createClass({
     /**
      * Color of the progress bar.
      */
-    color: ReactPropTypes.string,
+    color: ColorPropType,
     /**
      * Used to locate this view in end-to-end tests.
      */
@@ -97,7 +100,7 @@ var ProgressBarAndroid = React.createClass({
 
   getDefaultProps: function() {
     return {
-      styleAttr: 'Large',
+      styleAttr: 'Normal',
       indeterminate: true
     };
   },
diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js
new file mode 100644
index 000000000000..ee285a3c752b
--- /dev/null
+++ b/Libraries/Components/RefreshControl/RefreshControl.js
@@ -0,0 +1,96 @@
+/**
+ * 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 RefreshControl
+ * @flow
+ */
+'use strict';
+
+const React = require('React');
+const Platform = require('Platform');
+const ColorPropType = require('ColorPropType');
+const View = require('View');
+
+const requireNativeComponent = require('requireNativeComponent');
+
+if (Platform.OS === 'android') {
+  var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants;
+} else {
+  var RefreshLayoutConsts = {SIZE: {}};
+}
+
+/**
+ * This component is used inside a ScrollView to add pull to refresh
+ * functionality. When the ScrollView is at `scrollY: 0`, swiping down
+ * triggers an `onRefresh` event.
+ */
+const RefreshControl = React.createClass({
+  statics: {
+    SIZE: RefreshLayoutConsts.SIZE,
+  },
+
+  propTypes: {
+    ...View.propTypes,
+    /**
+     * Called when the view starts refreshing.
+     */
+    onRefresh: React.PropTypes.func,
+    /**
+     * Whether the view should be indicating an active refresh.
+     */
+    refreshing: React.PropTypes.bool,
+    /**
+     * The color of the refresh indicator.
+     * @platform ios
+     */
+    tintColor: ColorPropType,
+    /**
+     * The title displayed under the refresh indicator.
+     * @platform ios
+     */
+    title: React.PropTypes.string,
+    /**
+     * Whether the pull to refresh functionality is enabled.
+     * @platform android
+     */
+    enabled: React.PropTypes.bool,
+    /**
+     * The colors (at least one) that will be used to draw the refresh indicator.
+     * @platform android
+     */
+    colors: React.PropTypes.arrayOf(ColorPropType),
+    /**
+     * The background color of the refresh indicator.
+     * @platform android
+     */
+    progressBackgroundColor: ColorPropType,
+    /**
+     * Size of the refresh indicator, see RefreshControl.SIZE.
+     * @platform android
+     */
+    size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE),
+  },
+
+  render() {
+    return <NativeRefreshControl {...this.props} />;
+  },
+});
+
+if (Platform.OS === 'ios') {
+  var NativeRefreshControl = requireNativeComponent(
+    'RCTRefreshControl',
+    RefreshControl
+  );
+} else if (Platform.OS === 'android') {
+  var NativeRefreshControl = requireNativeComponent(
+    'AndroidSwipeRefreshLayout',
+    RefreshControl
+  );
+}
+
+module.exports = RefreshControl;
diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js
index 75ceb561d522..f2372fdccb08 100644
--- a/Libraries/Components/ScrollResponder.js
+++ b/Libraries/Components/ScrollResponder.js
@@ -12,17 +12,19 @@
 'use strict';
 
 var Dimensions = require('Dimensions');
-var NativeModules = require('NativeModules');
 var Platform = require('Platform');
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 var React = require('React');
 var Subscribable = require('Subscribable');
 var TextInputState = require('TextInputState');
+var UIManager = require('UIManager');
 
-var RCTUIManager = NativeModules.UIManager;
-var RCTScrollViewConsts = RCTUIManager.RCTScrollView.Constants;
+var { ScrollViewManager } = require('NativeModules');
 
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
+
+import type ReactComponent from 'ReactComponent';
 
 /**
  * Mixin that can be integrated in order to handle scrolling that plays well
@@ -115,7 +117,6 @@ type Event = Object;
 
 var ScrollResponderMixin = {
   mixins: [Subscribable.Mixin],
-  statics: RCTScrollViewConsts,
   scrollResponderMixinGetInitialState: function(): State {
     return {
       isTouching: false,
@@ -346,53 +347,73 @@ var ScrollResponderMixin = {
     return isAnimating;
   },
 
+  /**
+   * Returns the node that represents native view that can be scrolled.
+   * Components can pass what node to use by defining a `getScrollableNode`
+   * function otherwise `this` is used.
+   */
+  scrollResponderGetScrollableNode: function(): any {
+    return this.getScrollableNode ?
+      this.getScrollableNode() :
+      React.findNodeHandle(this);
+  },
+
   /**
    * A helper function to scroll to a specific point  in the scrollview.
-   * This is currently used to help focus on child textview's, but this
-   * can also be used to quickly scroll to any element we want to focus
+   * This is currently used to help focus on child textviews, but can also
+   * be used to quickly scroll to any element we want to focus. Syntax:
+   *
+   * scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})
+   *
+   * Note: The weird argument signature is due to the fact that, for historical reasons,
+   * the function also accepts separate arguments as as alternative to the options object.
+   * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
    */
-  scrollResponderScrollTo: function(offsetX: number, offsetY: number) {
-    if (Platform.OS === 'android') {
-      RCTUIManager.dispatchViewManagerCommand(
-        React.findNodeHandle(this),
-        RCTUIManager.RCTScrollView.Commands.scrollTo,
-        [Math.round(offsetX), Math.round(offsetY)],
-      );
+  scrollResponderScrollTo: function(
+    x?: number | { x?: number; y?: number; animated?: boolean },
+    y?: number,
+    animated?: boolean
+  ) {
+    if (typeof x === 'number') {
+      console.warn('`scrollResponderScrollTo(x, y, animated)` is deprecated. Use `scrollResponderScrollTo({x: 5, y: 5, animated: true})` instead.');
     } else {
-      RCTUIManager.scrollTo(
-        React.findNodeHandle(this),
-        offsetX,
-        offsetY,
-      );
+      ({x, y, animated} = x || {});
     }
+    UIManager.dispatchViewManagerCommand(
+      this.scrollResponderGetScrollableNode(),
+      UIManager.RCTScrollView.Commands.scrollTo,
+      [x || 0, y || 0, animated !== false],
+    );
   },
 
   /**
-   * Like `scrollResponderScrollTo` but immediately scrolls to the given
-   * position
+   * Deprecated, do not use.
    */
-  scrollResponderScrollWithouthAnimationTo: function(offsetX: number, offsetY: number) {
-    if (Platform.OS === 'android') {
-      RCTUIManager.dispatchViewManagerCommand(
-        React.findNodeHandle(this),
-        RCTUIManager.RCTScrollView.Commands.scrollWithoutAnimationTo,
-        [offsetX, offsetY],
-      );
-    } else {
-      RCTUIManager.scrollWithoutAnimationTo(
-        React.findNodeHandle(this),
-        offsetX,
-        offsetY
-      );
-    }
+  scrollResponderScrollWithoutAnimationTo: function(offsetX: number, offsetY: number) {
+    console.warn('`scrollResponderScrollWithoutAnimationTo` is deprecated. Use `scrollResponderScrollTo` instead');
+    this.scrollResponderScrollTo({x: offsetX, y: offsetY, animated: false});
   },
 
   /**
-   * A helper function to zoom to a specific rect in the scrollview.
-   * @param {object} rect Should have shape {x, y, width, height}
+   * A helper function to zoom to a specific rect in the scrollview. The argument has the shape
+   * {x: number; y: number; width: number; height: number; animated: boolean = true}
+   *
+   * @platform ios
    */
-  scrollResponderZoomTo: function(rect: { x: number; y: number; width: number; height: number; }) {
-    RCTUIManager.zoomToRect(React.findNodeHandle(this), rect);
+  scrollResponderZoomTo: function(
+    rect: { x: number; y: number; width: number; height: number; animated?: boolean },
+    animated?: boolean // deprecated, put this inside the rect argument instead
+  ) {
+    if (Platform.OS === 'android') {
+      invariant('zoomToRect is not implemented');
+    } else {
+      if ('animated' in rect) {
+        var { animated, ...rect } = rect;
+      } else if (typeof animated !== 'undefined') {
+        console.warn('`scrollResponderZoomTo` `animated` argument is deprecated. Use `options.animated` instead');
+      }
+      ScrollViewManager.zoomToRect(this.scrollResponderGetScrollableNode(), rect, animated !== false);
+    }
   },
 
   /**
@@ -408,7 +429,7 @@ var ScrollResponderMixin = {
   scrollResponderScrollNativeHandleToKeyboard: function(nodeHandle: any, additionalOffset?: number, preventNegativeScrollOffset?: bool) {
     this.additionalScrollOffset = additionalOffset || 0;
     this.preventNegativeScrollOffset = !!preventNegativeScrollOffset;
-    RCTUIManager.measureLayout(
+    UIManager.measureLayout(
       nodeHandle,
       React.findNodeHandle(this.getInnerViewNode()),
       this.scrollResponderTextInputFocusError,
@@ -440,7 +461,7 @@ var ScrollResponderMixin = {
     if (this.preventNegativeScrollOffset) {
       scrollOffsetY = Math.max(0, scrollOffsetY);
     }
-    this.scrollResponderScrollTo(0, scrollOffsetY);
+    this.scrollResponderScrollTo({x: 0, y: scrollOffsetY, animated: true});
 
     this.additionalOffset = 0;
     this.preventNegativeScrollOffset = false;
diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js
index c7b244a6323e..f8fecfdc3506 100644
--- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js
+++ b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js
@@ -20,9 +20,9 @@ var INNERVIEW = 'InnerView';
  * Wrapper around android native recycler view.
  *
  * It simply renders rows passed as children in a separate recycler view cells
- * similarily to how `ScrollView` is doing it. Thanks to the fact that it uses
+ * similarly to how `ScrollView` is doing it. Thanks to the fact that it uses
  * native `RecyclerView` though, rows that are out of sight are going to be
- * automatically detached (similarily on how this would work with
+ * automatically detached (similarly on how this would work with
  * `removeClippedSubviews = true` on a `ScrollView.js`).
  *
  * CAUTION: This is an experimental component and should only be used together
@@ -71,6 +71,11 @@ var RecyclerViewBackedScrollView = React.createClass({
     this.refs[INNERVIEW].setNativeProps(props);
   },
 
+  _handleContentSizeChange: function(event) {
+    var {width, height} = event.nativeEvent;
+    this.props.onContentSizeChange(width, height);
+  },
+
   render: function() {
     var props = {
       ...this.props,
@@ -92,6 +97,10 @@ var RecyclerViewBackedScrollView = React.createClass({
       ref: INNERVIEW,
     };
 
+    if (this.props.onContentSizeChange) {
+      props.onContentSizeChange = this._handleContentSizeChange;
+    }
+
     var wrappedChildren = React.Children.map(this.props.children, (child) => {
       if (!child) {
         return null;
diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js
index 651fac1deb12..a84377e54d72 100644
--- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js
+++ b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js
@@ -5,4 +5,4 @@
  */
 'use strict';
 
-module.exports = require('UnimplementedView');
+module.exports = require('ScrollView');
diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index c373b52c5a3f..dfb9e2b61c30 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -15,9 +15,9 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
 var Platform = require('Platform');
 var PointPropType = require('PointPropType');
 var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
+var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var RCTUIManager = require('NativeModules').UIManager;
 var ScrollResponder = require('ScrollResponder');
 var StyleSheet = require('StyleSheet');
 var StyleSheetPropType = require('StyleSheetPropType');
@@ -25,13 +25,14 @@ var View = require('View');
 var ViewStylePropTypes = require('ViewStylePropTypes');
 
 var deepDiffer = require('deepDiffer');
+var deprecatedPropType = require('deprecatedPropType');
 var dismissKeyboard = require('dismissKeyboard');
 var flattenStyle = require('flattenStyle');
 var insetsDiffer = require('insetsDiffer');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var pointsDiffer = require('pointsDiffer');
 var requireNativeComponent = require('requireNativeComponent');
-
+var processDecelerationRate = require('processDecelerationRate');
 var PropTypes = React.PropTypes;
 
 var SCROLLVIEW = 'ScrollView';
@@ -129,17 +130,35 @@ var ScrollView = React.createClass({
     contentContainerStyle: StyleSheetPropType(ViewStylePropTypes),
     /**
      * A floating-point number that determines how quickly the scroll view
-     * decelerates after the user lifts their finger. Reasonable choices include
+     * decelerates after the user lifts their finger. You may also use string
+     * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
+     * for `UIScrollViewDecelerationRateNormal` and
+     * `UIScrollViewDecelerationRateFast` respectively.
      *   - Normal: 0.998 (the default)
      *   - Fast: 0.9
      * @platform ios
      */
-    decelerationRate: PropTypes.number,
+    decelerationRate: PropTypes.oneOfType([
+      PropTypes.oneOf(['fast', 'normal']),
+      PropTypes.number,
+    ]),
     /**
      * When true, the scroll view's children are arranged horizontally in a row
      * instead of vertically in a column. The default value is false.
      */
     horizontal: PropTypes.bool,
+    /**
+     * The style of the scroll indicators.
+     *   - `default` (the default), same as `black`.
+     *   - `black`, scroll indicator is black. This style is good against a white content background.
+     *   - `white`, scroll indicator is white. This style is good against a black content background.
+     * @platform ios
+     */
+    indicatorStyle: PropTypes.oneOf([
+      'default', // default
+      'black',
+      'white',
+    ]),
     /**
      * When true, the ScrollView will try to lock to only vertical or horizontal
      * scrolling while dragging.  The default value is false.
@@ -184,7 +203,7 @@ var ScrollView = React.createClass({
     minimumZoomScale: PropTypes.number,
     /**
      * Fires at most once per frame during scrolling. The frequency of the
-     * events can be contolled using the `scrollEventThrottle` prop.
+     * events can be controlled using the `scrollEventThrottle` prop.
      */
     onScroll: PropTypes.func,
     /**
@@ -192,6 +211,12 @@ var ScrollView = React.createClass({
      * @platform ios
      */
     onScrollAnimationEnd: PropTypes.func,
+    /**
+     * Called when scrollable content view of the ScrollView changes. It's
+     * implemented using onLayout handler attached to the content container
+     * which this ScrollView renders.
+     */
+    onContentSizeChange: PropTypes.func,
     /**
      * When true, the scroll view stops on multiples of the scroll view's size
      * when scrolling. This can be used for horizontal pagination. The default
@@ -202,7 +227,6 @@ var ScrollView = React.createClass({
     /**
      * When false, the content does not scroll.
      * The default value is true.
-     * @platform ios
      */
     scrollEnabled: PropTypes.bool,
     /**
@@ -228,6 +252,13 @@ var ScrollView = React.createClass({
      * @platform ios
      */
     scrollsToTop: PropTypes.bool,
+    /**
+     * When true, momentum events will be sent from Android
+     * This is internal and set automatically by the framework if you have
+     * onMomentumScrollBegin or onMomentumScrollEnd set on your ScrollView
+     * @platform android
+     */
+    sendMomentumEvents: PropTypes.bool,
     /**
      * When true, shows a horizontal scroll indicator.
      */
@@ -256,7 +287,7 @@ var ScrollView = React.createClass({
     snapToInterval: PropTypes.number,
     /**
      * When `snapToInterval` is set, `snapToAlignment` will define the relationship
-     * of the the snapping to the scroll view.
+     * of the snapping to the scroll view.
      *   - `start` (the default) will align the snap at the left (horizontal) or top (vertical)
      *   - `center` will align the snap in the center
      *   - `end` will align the snap at the right (horizontal) or bottom (vertical)
@@ -271,7 +302,7 @@ var ScrollView = React.createClass({
      * Experimental: When true, offscreen child views (whose `overflow` value is
      * `hidden`) are removed from their native backing superview when offscreen.
      * This can improve scrolling performance on long lists. The default value is
-     * false.
+     * true.
      */
     removeClippedSubviews: PropTypes.bool,
     /**
@@ -279,6 +310,22 @@ var ScrollView = React.createClass({
      * @platform ios
      */
     zoomScale: PropTypes.number,
+
+    /**
+     * A RefreshControl component, used to provide pull-to-refresh
+     * functionality for the ScrollView.
+     *
+     * See [RefreshControl](docs/refreshcontrol.html).
+     */
+    refreshControl: PropTypes.element,
+
+    /**
+     * @platform ios
+     */
+    onRefreshStart: deprecatedPropType(
+      PropTypes.func,
+      'Use the `refreshControl` prop instead.'
+    ),
   },
 
   mixins: [ScrollResponder.Mixin],
@@ -291,6 +338,12 @@ var ScrollView = React.createClass({
     this.refs[SCROLLVIEW].setNativeProps(props);
   },
 
+  endRefreshing: function() {
+    RCTScrollViewManager.endRefreshing(
+      React.findNodeHandle(this)
+    );
+  },
+
   /**
    * Returns a reference to the underlying scroll responder, which supports
    * operations like `scrollTo`. All ScrollView-like components should
@@ -301,24 +354,47 @@ var ScrollView = React.createClass({
     return this;
   },
 
+  getScrollableNode: function(): any {
+    return React.findNodeHandle(this.refs[SCROLLVIEW]);
+  },
+
   getInnerViewNode: function(): any {
     return React.findNodeHandle(this.refs[INNERVIEW]);
   },
 
-  scrollTo: function(destY?: number, destX?: number) {
+  /**
+   * Scrolls to a given x, y offset, either immediately or with a smooth animation.
+   * Syntax:
+   *
+   * scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})
+   *
+   * Note: The weird argument signature is due to the fact that, for historical reasons,
+   * the function also accepts separate arguments as as alternative to the options object.
+   * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
+   */
+  scrollTo: function(
+    y?: number | { x?: number, y?: number, animated?: boolean },
+    x?: number,
+    animated?: boolean
+  ) {
+    if (typeof y === 'number') {
+      console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.');
+    } else {
+      ({x, y, animated} = y || {});
+    }
     // $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now
-    this.getScrollResponder().scrollResponderScrollTo(destX || 0, destY || 0);
+    this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false});
   },
 
-  scrollWithoutAnimationTo: function(destY?: number, destX?: number) {
-    // $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now
-    this.getScrollResponder().scrollResponderScrollWithouthAnimationTo(
-      destX || 0,
-      destY || 0,
-    );
+  /**
+   * Deprecated, do not use.
+   */
+  scrollWithoutAnimationTo: function(y: number = 0, x: number = 0) {
+    console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead');
+    this.scrollTo({x, y, animated: false});
   },
 
-  handleScroll: function(e: Event) {
+  handleScroll: function(e: Object) {
     if (__DEV__) {
       if (this.props.onScroll && !this.props.scrollEventThrottle) {
         console.log(
@@ -338,6 +414,11 @@ var ScrollView = React.createClass({
     this.scrollResponderHandleScroll(e);
   },
 
+  _handleContentOnLayout: function(e: Object) {
+    var {width, height} = e.nativeEvent.layout;
+    this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
+  },
+
   render: function() {
     var contentContainerStyle = [
       this.props.horizontal && styles.contentContainerHorizontal,
@@ -354,8 +435,16 @@ var ScrollView = React.createClass({
       );
     }
 
+    var contentSizeChangeProps = {};
+    if (this.props.onContentSizeChange) {
+      contentSizeChangeProps = {
+        onLayout: this._handleContentOnLayout,
+      };
+    }
+
     var contentContainer =
       <View
+        {...contentSizeChangeProps}
         ref={INNERVIEW}
         style={contentContainerStyle}
         removeClippedSubviews={this.props.removeClippedSubviews}
@@ -394,8 +483,22 @@ var ScrollView = React.createClass({
       onResponderTerminate: this.scrollResponderHandleTerminate,
       onResponderRelease: this.scrollResponderHandleResponderRelease,
       onResponderReject: this.scrollResponderHandleResponderReject,
+      sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? true : false,
     };
 
+    var onRefreshStart = this.props.onRefreshStart;
+    if (onRefreshStart) {
+      // this is necessary because if we set it on props, even when empty,
+      // it'll trigger the default pull-to-refresh behavior on native.
+      props.onRefreshStart =
+        function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this);
+    }
+
+    var { decelerationRate } = this.props;
+    if (decelerationRate) {
+      props.decelerationRate = processDecelerationRate(decelerationRate);
+    }
+
     var ScrollViewClass;
     if (Platform.OS === 'ios') {
       ScrollViewClass = RCTScrollView;
@@ -411,6 +514,29 @@ var ScrollView = React.createClass({
       'ScrollViewClass must not be undefined'
     );
 
+    var refreshControl = this.props.refreshControl;
+    if (refreshControl) {
+      if (Platform.OS === 'ios') {
+        // On iOS the RefreshControl is a child of the ScrollView.
+        return (
+          <ScrollViewClass {...props} ref={SCROLLVIEW}>
+            {refreshControl}
+            {contentContainer}
+          </ScrollViewClass>
+        );
+      } else if (Platform.OS === 'android') {
+        // On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
+        // Since the ScrollView is wrapped add the style props to the
+        // AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
+        return React.cloneElement(
+          refreshControl,
+          {style: props.style},
+          <ScrollViewClass {...props} style={styles.base} ref={SCROLLVIEW}>
+            {contentContainer}
+          </ScrollViewClass>
+        );
+      }
+    }
     return (
       <ScrollViewClass {...props} ref={SCROLLVIEW}>
         {contentContainer}
@@ -440,6 +566,7 @@ var validAttributes = {
   contentOffset: {diff: pointsDiffer},
   decelerationRate: true,
   horizontal: true,
+  indicatorStyle: true,
   keyboardDismissMode: true,
   keyboardShouldPersistTaps: true,
   maximumZoomScale: true,
diff --git a/Libraries/Components/ScrollView/processDecelerationRate.js b/Libraries/Components/ScrollView/processDecelerationRate.js
new file mode 100644
index 000000000000..82be66cef4a6
--- /dev/null
+++ b/Libraries/Components/ScrollView/processDecelerationRate.js
@@ -0,0 +1,29 @@
+/**
+ * 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 processDecelerationRate
+ */
+'use strict';
+
+var ScrollViewConsts = require('UIManager').RCTScrollView.Constants;
+
+function processDecelerationRate(decelerationRate) {
+  var ScrollViewDecelerationRateNormal = ScrollViewConsts && ScrollViewConsts.DecelerationRate.normal;
+  var ScrollViewDecelerationRateFast = ScrollViewConsts && ScrollViewConsts.DecelerationRate.fast;
+
+  if (typeof decelerationRate === 'string') {
+    if (decelerationRate === 'fast') {
+      return ScrollViewDecelerationRateFast;
+    } else if (decelerationRate === 'normal') {
+      return ScrollViewDecelerationRateNormal;
+    }
+  }
+  return decelerationRate;
+}
+
+module.exports = processDecelerationRate;
diff --git a/Libraries/Components/SliderIOS/SliderIOS.ios.js b/Libraries/Components/SliderIOS/SliderIOS.ios.js
index f151a29b7c67..fa1ed2246502 100644
--- a/Libraries/Components/SliderIOS/SliderIOS.ios.js
+++ b/Libraries/Components/SliderIOS/SliderIOS.ios.js
@@ -39,7 +39,7 @@ var SliderIOS = React.createClass({
      * Default value is 0.
      *
      * *This is not a controlled component*, e.g. if you don't update
-     * the value, the component won't be reset to its inital value.
+     * the value, the component won't be reset to its initial value.
      */
     value: PropTypes.number,
 
@@ -78,11 +78,29 @@ var SliderIOS = React.createClass({
      */
     disabled: PropTypes.bool,
 
-   /**
-     * Sets an image for the track. It only supports images that are included as assets
+    /**
+     * Assigns a single image for the track. Only static images are supported.
+     * The center pixel of the image will be stretched to fill the track.
      */
     trackImage: Image.propTypes.source,
 
+    /**
+     * Assigns a minimum track image. Only static images are supported. The
+     * rightmost pixel of the image will be stretched to fill the track.
+     */
+    minimumTrackImage: Image.propTypes.source,
+
+    /**
+     * Assigns a maximum track image. Only static images are supported. The
+     * leftmost pixel of the image will be stretched to fill the track.
+     */
+    maximumTrackImage: Image.propTypes.source,
+
+    /**
+     * Sets an image for the thumb. It only supports static images.
+     */
+    thumbImage: Image.propTypes.source,
+
     /**
      * Callback continuously called while the user is dragging the slider.
      */
@@ -102,28 +120,18 @@ var SliderIOS = React.createClass({
   },
 
   render: function() {
+    let {style, onValueChange, onSlidingComplete, ...props} = this.props;
+    props.style = [styles.slider, style];
 
-    let onValueChange = this.props.onValueChange && ((event: Event) => {
-      this.props.onValueChange &&
-        this.props.onValueChange(event.nativeEvent.value);
+    props.onValueChange = onValueChange && ((event: Event) => {
+      onValueChange && onValueChange(event.nativeEvent.value);
     });
 
-    let onSlidingComplete = this.props.onSlidingComplete && ((event: Event) => {
-      this.props.onSlidingComplete &&
-        this.props.onSlidingComplete(event.nativeEvent.value);
+    props.onSlidingComplete = onSlidingComplete && ((event: Event) => {
+      onSlidingComplete && onSlidingComplete(event.nativeEvent.value);
     });
 
-    let {style, ...props} = this.props;
-    style = [styles.slider, this.props.style];
-
-    return (
-      <RCTSlider
-        {...props}
-        style={style}
-        onValueChange={onValueChange}
-        onSlidingComplete={onSlidingComplete}
-      />
-    );
+    return <RCTSlider {...props}/>;
   }
 });
 
diff --git a/Libraries/Components/StatusBar/StatusBar.js b/Libraries/Components/StatusBar/StatusBar.js
new file mode 100644
index 000000000000..159577b585f1
--- /dev/null
+++ b/Libraries/Components/StatusBar/StatusBar.js
@@ -0,0 +1,264 @@
+/**
+ * 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 StatusBar
+ * @flow
+ */
+'use strict';
+
+const React = require('React');
+const ColorPropType = require('ColorPropType');
+const Platform = require('Platform');
+
+const processColor = require('processColor');
+
+const StatusBarManager = require('NativeModules').StatusBarManager;
+
+export type StatusBarStyle = $Enum<{
+  'default': string,
+  'light-content': string,
+}>;
+
+export type StatusBarAnimation = $Enum<{
+  'none': string,
+  'fade': string,
+  'slide': string,
+}>;
+
+type DefaultProps = {
+  animated: boolean;
+};
+
+/**
+ * Merges the prop stack with the default values.
+ */
+function mergePropsStack(propsStack: Array<Object>, defaultValues: Object): Object {
+  return propsStack.reduce((prev, cur) => {
+    return Object.assign(prev, cur);
+  }, defaultValues);
+}
+
+/**
+ * Component to control the app status bar.
+ *
+ * ### Usage with Navigator
+ *
+ * It is possible to have multiple `StatusBar` components mounted at the same
+ * time. The props will be merged in the order the `StatusBar` components were
+ * mounted. One use case is to specify status bar styles per route using `Navigator`.
+ *
+ * ```
+ *  <View>
+ *    <StatusBar
+ *      backgroundColor="blue"
+ *      barStyle="light-content"
+ *    />
+ *    <Navigator
+ *      initialRoute={{statusBarHidden: true}}
+ *      renderScene={(route, navigator) =>
+ *        <View>
+ *          <StatusBar hidden={route.statusBarHidden} />
+ *          ...
+ *        </View>
+ *      }
+ *    />
+ *  </View>
+ * ```
+ *
+ * ### Imperative API
+ *
+ * For cases where using a component is not ideal, there is also an imperative
+ * API exposed as static functions on the component. It is however not recommended
+ * to use the static API and the compoment for the same prop because any value
+ * set by the static API will get overriden by the one set by the component in
+ * the next render.
+ */
+const StatusBar = React.createClass({
+  statics: {
+    _propsStack: [],
+    _defaultProps: {
+      backgroundColor: 'black',
+      barStyle: 'default',
+      translucent: false,
+      hidden: false,
+      networkActivityIndicatorVisible: false,
+    },
+
+    // Provide an imperative API as static functions of the component.
+    // See the corresponding prop for more detail.
+    setHidden(hidden: boolean, animation?: StatusBarAnimation) {
+      animation = animation || 'none';
+      StatusBar._defaultProps.hidden = hidden;
+      if (Platform.OS === 'ios') {
+        StatusBarManager.setHidden(hidden, animation);
+      } else if (Platform.OS === 'android') {
+        StatusBarManager.setHidden(hidden);
+      }
+    },
+
+    setBarStyle(style: StatusBarStyle, animated?: boolean) {
+      if (Platform.OS !== 'ios') {
+        console.warn('`setBarStyle` is only available on iOS');
+        return;
+      }
+      animated = animated || false;
+      StatusBar._defaultProps.barStyle = style;
+      StatusBarManager.setStyle(style, animated);
+    },
+
+    setNetworkActivityIndicatorVisible(visible: boolean) {
+      if (Platform.OS !== 'ios') {
+        console.warn('`setNetworkActivityIndicatorVisible` is only available on iOS');
+        return;
+      }
+      StatusBar._defaultProps.networkActivityIndicatorVisible = visible;
+      StatusBarManager.setNetworkActivityIndicatorVisible(visible);
+    },
+
+    setBackgroundColor(color, animated?: boolean) {
+      if (Platform.OS !== 'android') {
+        console.warn('`setBackgroundColor` is only available on Android');
+        return;
+      }
+      animated = animated || false;
+      StatusBar._defaultProps.backgroundColor = color;
+      StatusBarManager.setColor(processColor(color), animated);
+    },
+
+    setTranslucent(translucent: boolean) {
+      if (Platform.OS !== 'android') {
+        console.warn('`setTranslucent` is only available on Android');
+        return;
+      }
+      StatusBar._defaultProps.translucent = translucent;
+      StatusBarManager.setTranslucent(translucent);
+    },
+  },
+
+  propTypes: {
+    /**
+     * If the status bar is hidden.
+     */
+    hidden: React.PropTypes.bool,
+    /**
+     * If the transition between status bar property changes should be animated.
+     * Supported for backgroundColor, barStyle and hidden.
+     */
+    animated: React.PropTypes.bool,
+    /**
+     * The background color of the status bar.
+     * @platform android
+     */
+    backgroundColor: ColorPropType,
+    /**
+     * If the status bar is translucent.
+     * When translucent is set to true, the app will draw under the status bar.
+     * This is useful when using a semi transparent status bar color.
+     *
+     * @platform android
+     */
+    translucent: React.PropTypes.bool,
+    /**
+     * Sets the color of the status bar text.
+     *
+     * @platform ios
+     */
+    barStyle: React.PropTypes.oneOf([
+      'default',
+      'light-content',
+    ]),
+    /**
+     * If the network activity indicator should be visible.
+     *
+     * @platform ios
+     */
+    networkActivityIndicatorVisible: React.PropTypes.bool,
+    /**
+     * The transition effect when showing and hiding the status bar using the `hidden`
+     * prop. Defaults to 'fade'.
+     *
+     * @platform ios
+     */
+    showHideTransition: React.PropTypes.oneOf([
+      'fade',
+      'slide',
+    ]),
+  },
+
+  getDefaultProps(): DefaultProps {
+    return {
+      animated: false,
+      showHideTransition: 'fade',
+    };
+  },
+
+  componentDidMount() {
+    // Every time a StatusBar component is mounted, we push it's prop to a stack
+    // and always update the native status bar with the props from the top of then
+    // stack. This allows having multiple StatusBar components and the one that is
+    // added last or is deeper in the view hierachy will have priority.
+    StatusBar._propsStack.push(this.props);
+    this._updatePropsStack();
+  },
+
+  componentWillUnmount() {
+    // When a StatusBar is unmounted, remove itself from the stack and update
+    // the native bar with the next props.
+    const index = StatusBar._propsStack.indexOf(this.props);
+    StatusBar._propsStack.splice(index, 1);
+
+    this._updatePropsStack();
+  },
+
+  componentDidUpdate(oldProps: Object) {
+    const index = StatusBar._propsStack.indexOf(oldProps);
+    StatusBar._propsStack[index] = this.props;
+
+    this._updatePropsStack();
+  },
+
+  /**
+   * Updates the native status bar with the props from the stack.
+   */
+  _updatePropsStack() {
+    const mergedProps = mergePropsStack(StatusBar._propsStack, StatusBar._defaultProps);
+
+    if (Platform.OS === 'ios') {
+      if (mergedProps.barStyle !== undefined) {
+        StatusBarManager.setStyle(mergedProps.barStyle, this.props.animated);
+      }
+      if (mergedProps.hidden !== undefined) {
+        StatusBarManager.setHidden(
+          mergedProps.hidden,
+          this.props.animated ? this.props.showHideTransition : 'none'
+        );
+      }
+      if (mergedProps.networkActivityIndicatorVisible !== undefined) {
+        StatusBarManager.setNetworkActivityIndicatorVisible(
+          mergedProps.networkActivityIndicatorVisible
+        );
+      }
+    } else if (Platform.OS === 'android') {
+      if (mergedProps.backgroundColor !== undefined) {
+        StatusBarManager.setColor(processColor(mergedProps.backgroundColor), this.props.animated);
+      }
+      if (mergedProps.hidden !== undefined) {
+        StatusBarManager.setHidden(mergedProps.hidden);
+      }
+      if (mergedProps.translucent !== undefined) {
+        StatusBarManager.setTranslucent(mergedProps.translucent);
+      }
+    }
+  },
+
+  render(): ?ReactElement {
+    return null;
+  },
+});
+
+module.exports = StatusBar;
diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js
index 59369ef143dd..a4c0357b0cf0 100644
--- a/Libraries/Components/StatusBar/StatusBarIOS.ios.js
+++ b/Libraries/Components/StatusBar/StatusBarIOS.ios.js
@@ -11,33 +11,31 @@
  */
 'use strict';
 
-var RCTStatusBarManager = require('NativeModules').StatusBarManager;
+const StatusBar = require('StatusBar');
 
-type StatusBarStyle = $Enum<{
-  'default': string,
-  'light-content': string,
-}>;
+import type {StatusBarStyle, StatusBarAnimation} from 'StatusBar';
 
-type StatusBarAnimation = $Enum<{
-  'none': string,
-  'fade': string,
-  'slide': string,
-}>;
-
-var StatusBarIOS = {
+/**
+ * Deprecated. Use `StatusBar` instead.
+ */
+const StatusBarIOS = {
 
   setStyle(style: StatusBarStyle, animated?: boolean) {
-    animated = animated || false;
-    RCTStatusBarManager.setStyle(style, animated);
+    console.warn('`StatusBarIOS.setStyle` is deprecated. Use `StatusBar.setBarStyle` instead.');
+    StatusBar.setBarStyle(style, animated);
   },
 
   setHidden(hidden: boolean, animation?: StatusBarAnimation) {
-    animation = animation || 'none';
-    RCTStatusBarManager.setHidden(hidden, animation);
+    console.warn('`StatusBarIOS.setHidden` is deprecated. Use `StatusBar.setHidden` instead.');
+    StatusBar.setHidden(hidden, animation);
   },
 
   setNetworkActivityIndicatorVisible(visible: boolean) {
-    RCTStatusBarManager.setNetworkActivityIndicatorVisible(visible);
+    console.warn(
+      '`StatusBarIOS.setNetworkActivityIndicatorVisible` is deprecated. ' +
+      'Use `StatusBar.setNetworkActivityIndicatorVisible` instead.'
+    );
+    StatusBar.setNetworkActivityIndicatorVisible(visible);
   },
 };
 
diff --git a/Libraries/Components/Switch/Switch.js b/Libraries/Components/Switch/Switch.js
index 450c0ff4fa08..273139e7160f 100644
--- a/Libraries/Components/Switch/Switch.js
+++ b/Libraries/Components/Switch/Switch.js
@@ -6,6 +6,7 @@
  */
 'use strict';
 
+var ColorPropType = require('ColorPropType');
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var Platform = require('Platform');
 var React = require('React');
@@ -36,7 +37,7 @@ var Switch = React.createClass({
      */
     disabled: React.PropTypes.bool,
     /**
-     * Invoked with the new value when the value chages.
+     * Invoked with the new value when the value changes.
      */
     onValueChange: React.PropTypes.func,
     /**
@@ -48,17 +49,17 @@ var Switch = React.createClass({
      * Background color when the switch is turned off.
      * @platform ios
      */
-    tintColor: React.PropTypes.string,
+    tintColor: ColorPropType,
     /**
      * Background color when the switch is turned on.
      * @platform ios
      */
-    onTintColor: React.PropTypes.string,
+    onTintColor: ColorPropType,
     /**
      * Color of the foreground switch grip.
      * @platform ios
      */
-    thumbTintColor: React.PropTypes.string,
+    thumbTintColor: ColorPropType,
   },
 
   getDefaultProps: function(): DefaultProps {
@@ -91,7 +92,7 @@ var Switch = React.createClass({
     if (Platform.OS === 'android') {
       props.enabled = !this.props.disabled;
       props.on = this.props.value;
-      props.style = [styles.rctSwitchAndroid, this.props.style];
+      props.style = this.props.style;
     } else if (Platform.OS === 'ios') {
       props.style = [styles.rctSwitchIOS, this.props.style];
     }
@@ -109,11 +110,7 @@ var styles = StyleSheet.create({
   rctSwitchIOS: {
     height: 31,
     width: 51,
-  },
-  rctSwitchAndroid: {
-    height: 27,
-    width: 40,
-  },
+  }
 });
 
 if (Platform.OS === 'android') {
diff --git a/Libraries/Components/SwitchAndroid/SwitchAndroid.android.js b/Libraries/Components/SwitchAndroid/SwitchAndroid.android.js
index feba96b8d9c3..2cbfb87ddeff 100644
--- a/Libraries/Components/SwitchAndroid/SwitchAndroid.android.js
+++ b/Libraries/Components/SwitchAndroid/SwitchAndroid.android.js
@@ -36,7 +36,7 @@ var SwitchAndroid = React.createClass({
      */
     disabled: PropTypes.bool,
     /**
-     * Invoked with the new value when the value chages.
+     * Invoked with the new value when the value changes.
      */
     onValueChange: PropTypes.func,
     /**
diff --git a/Libraries/Components/SwitchIOS/SwitchIOS.ios.js b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js
index 35514f66fe11..798529c58535 100644
--- a/Libraries/Components/SwitchIOS/SwitchIOS.ios.js
+++ b/Libraries/Components/SwitchIOS/SwitchIOS.ios.js
@@ -13,6 +13,7 @@
  */
 'use strict';
 
+var ColorPropType = require('ColorPropType');
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var PropTypes = require('ReactPropTypes');
 var React = require('React');
@@ -62,17 +63,17 @@ var SwitchIOS = React.createClass({
     /**
      * Background color when the switch is turned on.
      */
-    onTintColor: PropTypes.string,
+    onTintColor: ColorPropType,
 
     /**
      * Background color for the switch round button.
      */
-    thumbTintColor: PropTypes.string,
+    thumbTintColor: ColorPropType,
 
     /**
      * Background color when the switch is turned off.
      */
-    tintColor: PropTypes.string,
+    tintColor: ColorPropType,
   },
 
   getDefaultProps: function(): DefaultProps {
diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
index 660c8c62a137..3d168509fd60 100644
--- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
@@ -11,6 +11,7 @@
  */
 'use strict';
 
+var ColorPropType = require('ColorPropType');
 var React = require('React');
 var StyleSheet = require('StyleSheet');
 var TabBarItemIOS = require('TabBarItemIOS');
@@ -29,11 +30,11 @@ var TabBarIOS = React.createClass({
     /**
      * Color of the currently selected tab icon
      */
-    tintColor: React.PropTypes.string,
+    tintColor: ColorPropType,
     /**
      * Background color of the tab bar
      */
-    barTintColor: React.PropTypes.string,
+    barTintColor: ColorPropType,
     /**
      * A Boolean value that indicates whether the tab bar is translucent
      */
diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
index a4c883bc900e..bfdae5433320 100644
--- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
@@ -7,6 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule TabBarItemIOS
+ * @noflow
  */
 'use strict';
 
@@ -15,7 +16,6 @@ var React = require('React');
 var StaticContainer = require('StaticContainer.react');
 var StyleSheet = require('StyleSheet');
 var View = require('View');
-var resolveAssetSource = require('resolveAssetSource');
 
 var requireNativeComponent = require('requireNativeComponent');
 
@@ -31,7 +31,7 @@ var TabBarItemIOS = React.createClass({
     ]),
     /**
      * Items comes with a few predefined system icons. Note that if you are
-     * using them, the title and selectedIcon will be overriden with the
+     * using them, the title and selectedIcon will be overridden with the
      * system ones.
      */
     systemIcon: React.PropTypes.oneOf([
@@ -97,29 +97,23 @@ var TabBarItemIOS = React.createClass({
   },
 
   render: function() {
-    var tabContents = null;
+    var {style, children, ...props} = this.props;
+
     // if the tab has already been shown once, always continue to show it so we
     // preserve state between tab transitions
     if (this.state.hasBeenSelected) {
-      tabContents =
+      var tabContents =
         <StaticContainer shouldUpdate={this.props.selected}>
-          {this.props.children}
+          {children}
         </StaticContainer>;
     } else {
-      tabContents = <View />;
+      var tabContents = <View />;
     }
 
-    var badge = typeof this.props.badge === 'number' ?
-      '' + this.props.badge :
-      this.props.badge;
-
     return (
       <RCTTabBarItem
-        {...this.props}
-        icon={this.props.systemIcon || resolveAssetSource(this.props.icon)}
-        selectedIcon={resolveAssetSource(this.props.selectedIcon)}
-        badge={badge}
-        style={[styles.tab, this.props.style]}>
+        {...props}
+        style={[styles.tab, style]}>
         {tabContents}
       </RCTTabBarItem>
     );
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index c9728d96ab9d..2243ee4b454c 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -16,7 +16,6 @@ var EventEmitter = require('EventEmitter');
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var Platform = require('Platform');
 var PropTypes = require('ReactPropTypes');
-var RCTUIManager = require('NativeModules').UIManager;
 var React = require('React');
 var ReactChildren = require('ReactChildren');
 var StyleSheet = require('StyleSheet');
@@ -24,11 +23,12 @@ var Text = require('Text');
 var TextInputState = require('TextInputState');
 var TimerMixin = require('react-timer-mixin');
 var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
+var UIManager = require('UIManager');
 var View = require('View');
 
 var createReactNativeComponentClass = require('createReactNativeComponentClass');
-var emptyFunction = require('emptyFunction');
-var invariant = require('invariant');
+var emptyFunction = require('fbjs/lib/emptyFunction');
+var invariant = require('fbjs/lib/invariant');
 var requireNativeComponent = require('requireNativeComponent');
 
 var onlyMultiline = {
@@ -37,7 +37,7 @@ var onlyMultiline = {
 };
 
 var notMultiline = {
-  onSubmitEditing: true,
+  // nothing yet
 };
 
 if (Platform.OS === 'android') {
@@ -47,10 +47,6 @@ if (Platform.OS === 'android') {
   var RCTTextField = requireNativeComponent('RCTTextField', null);
 }
 
-type DefaultProps = {
-  blurOnSubmit: boolean;
-};
-
 type Event = Object;
 
 /**
@@ -73,17 +69,6 @@ type Event = Object;
  * ```
  *
  * Note that some props are only available with `multiline={true/false}`:
- * ```
- *   var onlyMultiline = {
- *     onSelectionChange: true, // not supported in Open Source yet
- *     onTextInput: true, // not supported in Open Source yet
- *     children: true,
- *   };
- *
- *   var notMultiline = {
- *     onSubmitEditing: true,
- *   };
- * ```
  */
 var TextInput = React.createClass({
   statics: {
@@ -116,24 +101,6 @@ var TextInput = React.createClass({
      * The default value is false.
      */
     autoFocus: PropTypes.bool,
-    /**
-     * Set the position of the cursor from where editing will begin.
-     * @platform android
-     */
-    textAlign: PropTypes.oneOf([
-      'start',
-      'center',
-      'end',
-    ]),
-    /**
-     * Aligns text vertically within the TextInput.
-     * @platform android
-     */
-    textAlignVertical: PropTypes.oneOf([
-      'top',
-      'center',
-      'bottom',
-    ]),
     /**
      * If false, text is not editable. The default value is true.
      */
@@ -149,14 +116,14 @@ var TextInput = React.createClass({
     keyboardType: PropTypes.oneOf([
       // Cross-platform
       'default',
-      'numeric',
       'email-address',
+      'numeric',
+      'phone-pad',
       // iOS-only
       'ascii-capable',
       'numbers-and-punctuation',
       'url',
       'number-pad',
-      'phone-pad',
       'name-phone-pad',
       'decimal-pad',
       'twitter',
@@ -231,6 +198,10 @@ var TextInput = React.createClass({
      * Callback that is called when text input ends.
      */
     onEndEditing: PropTypes.func,
+    /**
+     * Callback that is called when the text input selection is changed
+     */
+    onSelectionChange: PropTypes.func,
     /**
      * Callback that is called when the text input's submit button is pressed.
      * Invalid if multiline={true} is specified.
@@ -260,6 +231,10 @@ var TextInput = React.createClass({
      * like passwords stay secure. The default value is false.
      */
     secureTextEntry: PropTypes.bool,
+    /**
+    * The highlight (and cursor on ios) color of the text input
+    */
+    selectionColor: PropTypes.string,
     /**
      * See DocumentSelectionState.js, some state that is responsible for
      * maintaining selection information for a document
@@ -304,18 +279,16 @@ var TextInput = React.createClass({
     selectTextOnFocus: PropTypes.bool,
     /**
      * If true, the text field will blur when submitted.
-     * The default value is true.
-     * @platform ios
+     * The default value is true for single-line fields and false for
+     * multiline fields. Note that for multiline fields, setting blurOnSubmit
+     * to true means that pressing return will blur the field and trigger the
+     * onSubmitEditing event instead of inserting a newline into the field.
      */
     blurOnSubmit: PropTypes.bool,
     /**
      * Styles
      */
     style: Text.propTypes.style,
-    /**
-     * Used to locate this view in end-to-end tests
-     */
-    testID: PropTypes.string,
     /**
      * The color of the textInput underline.
      * @platform android
@@ -323,32 +296,24 @@ var TextInput = React.createClass({
     underlineColorAndroid: PropTypes.string,
   },
 
-  getDefaultProps: function(): DefaultProps {
-    return {
-      blurOnSubmit: true,
-    };
-  },
-
   /**
    * `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
    * make `this` look like an actual native component class.
    */
   mixins: [NativeMethodsMixin, TimerMixin],
 
-  viewConfig: ((Platform.OS === 'ios' ? RCTTextField.viewConfig :
-    (Platform.OS === 'android' ? AndroidTextInput.viewConfig : {})) : Object),
+  viewConfig:
+    ((Platform.OS === 'ios' && RCTTextField ?
+      RCTTextField.viewConfig :
+      (Platform.OS === 'android' && AndroidTextInput ?
+        AndroidTextInput.viewConfig :
+        {})) : Object),
 
   isFocused: function(): boolean {
     return TextInputState.currentlyFocusedField() ===
       React.findNodeHandle(this.refs.input);
   },
 
-  getInitialState: function() {
-    return {
-      mostRecentEventCount: 0,
-    };
-  },
-
   contextTypes: {
     onFocusRequested: React.PropTypes.func,
     focusEmitter: React.PropTypes.instanceOf(EventEmitter),
@@ -445,7 +410,6 @@ var TextInput = React.createClass({
           onSelectionChange={onSelectionChange}
           onSelectionChangeShouldSetResponder={emptyFunction.thatReturnsTrue}
           text={this._getText()}
-          mostRecentEventCount={this.state.mostRecentEventCount}
         />;
     } else {
       for (var propKey in notMultiline) {
@@ -474,7 +438,6 @@ var TextInput = React.createClass({
           ref="input"
           {...props}
           children={children}
-          mostRecentEventCount={this.state.mostRecentEventCount}
           onFocus={this._onFocus}
           onBlur={this._onBlur}
           onChange={this._onChange}
@@ -489,6 +452,9 @@ var TextInput = React.createClass({
       <TouchableWithoutFeedback
         onPress={this._onPress}
         rejectResponderTermination={true}
+        accessible={props.accessible}
+        accessibilityLabel={props.accessibilityLabel}
+        accessibilityTraits={props.accessibilityTraits}
         testID={props.testID}>
         {textContainer}
       </TouchableWithoutFeedback>
@@ -496,11 +462,19 @@ var TextInput = React.createClass({
   },
 
   _renderAndroid: function() {
-    var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize];
-    var textAlign =
-      RCTUIManager.AndroidTextInput.Constants.TextAlign[this.props.textAlign];
-    var textAlignVertical =
-      RCTUIManager.AndroidTextInput.Constants.TextAlignVertical[this.props.textAlignVertical];
+    var onSelectionChange;
+    if (this.props.selectionState || this.props.onSelectionChange) {
+      onSelectionChange = (event: Event) => {
+        if (this.props.selectionState) {
+          var selection = event.nativeEvent.selection;
+          this.props.selectionState.update(selection.start, selection.end);
+        }
+        this.props.onSelectionChange && this.props.onSelectionChange(event);
+      };
+    }
+
+    var autoCapitalize =
+      UIManager.AndroidTextInput.Constants.AutoCapitalizationType[this.props.autoCapitalize];
     var children = this.props.children;
     var childCount = 0;
     ReactChildren.forEach(children, () => ++childCount);
@@ -511,29 +485,31 @@ var TextInput = React.createClass({
     if (childCount > 1) {
       children = <Text>{children}</Text>;
     }
+
     var textContainer =
       <AndroidTextInput
         ref="input"
         style={[this.props.style]}
         autoCapitalize={autoCapitalize}
         autoCorrect={this.props.autoCorrect}
-        textAlign={textAlign}
-        textAlignVertical={textAlignVertical}
         keyboardType={this.props.keyboardType}
-        mostRecentEventCount={this.state.mostRecentEventCount}
+        mostRecentEventCount={0}
         multiline={this.props.multiline}
         numberOfLines={this.props.numberOfLines}
         maxLength={this.props.maxLength}
         onFocus={this._onFocus}
         onBlur={this._onBlur}
         onChange={this._onChange}
+        onSelectionChange={onSelectionChange}
         onTextInput={this._onTextInput}
         onEndEditing={this.props.onEndEditing}
         onSubmitEditing={this.props.onSubmitEditing}
+        blurOnSubmit={this.props.blurOnSubmit}
         onLayout={this.props.onLayout}
         password={this.props.password || this.props.secureTextEntry}
         placeholder={this.props.placeholder}
         placeholderTextColor={this.props.placeholderTextColor}
+        selectionColor={this.props.selectionColor}
         text={this._getText()}
         underlineColorAndroid={this.props.underlineColorAndroid}
         children={children}
@@ -543,6 +519,9 @@ var TextInput = React.createClass({
     return (
       <TouchableWithoutFeedback
         onPress={this._onPress}
+        accessible={this.props.accessible}
+        accessibilityLabel={this.props.accessibilityLabel}
+        accessibilityComponentType={this.props.accessibilityComponentType}
         testID={this.props.testID}>
         {textContainer}
       </TouchableWithoutFeedback>
@@ -553,6 +532,10 @@ var TextInput = React.createClass({
     if (this.props.onFocus) {
       this.props.onFocus(event);
     }
+
+    if (this.props.selectionState) {
+      this.props.selectionState.focus();
+    }
   },
 
   _onPress: function(event: Event) {
@@ -562,29 +545,30 @@ var TextInput = React.createClass({
   },
 
   _onChange: function(event: Event) {
-    if (Platform.OS === 'android') {
-      // Android expects the event count to be updated as soon as possible.
-      this.refs.input.setNativeProps({
-        mostRecentEventCount: event.nativeEvent.eventCount,
-      });
-    }
+    // Make sure to fire the mostRecentEventCount first so it is already set on
+    // native when the text value is set.
+    this.refs.input.setNativeProps({
+      mostRecentEventCount: event.nativeEvent.eventCount,
+    });
+
     var text = event.nativeEvent.text;
-    var eventCount = event.nativeEvent.eventCount;
     this.props.onChange && this.props.onChange(event);
     this.props.onChangeText && this.props.onChangeText(text);
-    this.setState({mostRecentEventCount: eventCount}, () => {
-      // NOTE: this doesn't seem to be needed on iOS - keeping for now in case it's required on Android
-      if (Platform.OS === 'android') {
-        // This is a controlled component, so make sure to force the native value
-        // to match.  Most usage shouldn't need this, but if it does this will be
-        // more correct but might flicker a bit and/or cause the cursor to jump.
-        if (text !== this.props.value && typeof this.props.value === 'string') {
-          this.refs.input.setNativeProps({
-            text: this.props.value,
-          });
-        }
-      }
-    });
+
+    if (!this.refs.input) {
+      // calling `this.props.onChange` or `this.props.onChangeText`
+      // may clean up the input itself. Exits here.
+      return;
+    }
+
+    // This is necessary in case native updates the text and JS decides
+    // that the update should be ignored and we should stick with the value
+    // that we have in JS.
+    if (text !== this.props.value && typeof this.props.value === 'string') {
+      this.refs.input.setNativeProps({
+        text: this.props.value,
+      });
+    }
   },
 
   _onBlur: function(event: Event) {
@@ -592,6 +576,10 @@ var TextInput = React.createClass({
     if (this.props.onBlur) {
       this.props.onBlur(event);
     }
+
+    if (this.props.selectionState) {
+      this.props.selectionState.blur();
+    }
   },
 
   _onTextInput: function(event: Event) {
diff --git a/Libraries/Components/TextInput/TextInputState.js b/Libraries/Components/TextInput/TextInputState.js
index f3e35b78c2f0..276bba0d781b 100644
--- a/Libraries/Components/TextInput/TextInputState.js
+++ b/Libraries/Components/TextInput/TextInputState.js
@@ -16,7 +16,7 @@
 'use strict';
 
 var Platform = require('Platform');
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 var TextInputState = {
    /**
@@ -41,11 +41,11 @@ var TextInputState = {
     if (this._currentlyFocusedID !== textFieldID && textFieldID !== null) {
       this._currentlyFocusedID = textFieldID;
       if (Platform.OS === 'ios') {
-        RCTUIManager.focus(textFieldID);
+        UIManager.focus(textFieldID);
       } else if (Platform.OS === 'android') {
-        RCTUIManager.dispatchViewManagerCommand(
+        UIManager.dispatchViewManagerCommand(
           textFieldID,
-          RCTUIManager.AndroidTextInput.Commands.focusTextInput,
+          UIManager.AndroidTextInput.Commands.focusTextInput,
           null
         );
       }
@@ -61,11 +61,11 @@ var TextInputState = {
     if (this._currentlyFocusedID === textFieldID && textFieldID !== null) {
       this._currentlyFocusedID = null;
       if (Platform.OS === 'ios') {
-        RCTUIManager.blur(textFieldID);
+        UIManager.blur(textFieldID);
       } else if (Platform.OS === 'android') {
-        RCTUIManager.dispatchViewManagerCommand(
+        UIManager.dispatchViewManagerCommand(
           textFieldID,
-          RCTUIManager.AndroidTextInput.Commands.blurTextInput,
+          UIManager.AndroidTextInput.Commands.blurTextInput,
           null
         );
       }
diff --git a/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js
new file mode 100644
index 000000000000..14480040ca54
--- /dev/null
+++ b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js
@@ -0,0 +1,67 @@
+/**
+ * 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 TimePickerAndroid
+ * @flow
+ */
+'use strict';
+
+const TimePickerModule = require('NativeModules').TimePickerAndroid;
+
+/**
+ * Opens the standard Android time picker dialog.
+ *
+ * ### Example
+ *
+ * ```
+ * try {
+ *   const {action, hour, minute} = await TimePickerAndroid.open({
+ *     hour: 14,
+ *     minute: 0,
+ *     is24Hour: false, // Will display '2 PM'
+ *   });
+ *   if (action !== DatePickerAndroid.dismissedAction) {
+ *     // Selected hour (0-23), minute (0-59)
+ *   }
+ * } catch ({code, message}) {
+ *   console.warn('Cannot open time picker', message);
+ * }
+ * ```
+ */
+class TimePickerAndroid {
+
+  /**
+   * Opens the standard Android time picker dialog.
+   *
+   * The available keys for the `options` object are:
+   *   * `hour` (0-23) - the hour to show, defaults to the current time
+   *   * `minute` (0-59) - the minute to show, defaults to the current time
+   *   * `is24Hour` (boolean) - If `true`, the picker uses the 24-hour format. If `false`,
+   *     the picker shows an AM/PM chooser. If undefined, the default for the current locale
+   *     is used.
+   *
+   * Returns a Promise which will be invoked an object containing `action`, `hour` (0-23),
+   * `minute` (0-59) if the user picked a time. If the user dismissed the dialog, the Promise will
+   * still be resolved with action being `TimePickerAndroid.dismissedAction` and all the other keys
+   * being undefined. **Always** check whether the `action` before reading the values.
+   */
+  static async open(options: Object): Promise<Object> {
+    return TimePickerModule.open(options);
+  }
+
+  /**
+   * A time has been selected.
+   */
+  static get timeSetAction() { return 'timeSetAction'; }
+  /**
+   * The dialog has been dismissed.
+   */
+  static get dismissedAction() { return 'dismissedAction'; }
+}
+
+module.exports = TimePickerAndroid;
diff --git a/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js
new file mode 100644
index 000000000000..0d7ceee4b64b
--- /dev/null
+++ b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js
@@ -0,0 +1,24 @@
+/**
+ * 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 TimePickerAndroid
+ * @flow
+ */
+'use strict';
+
+var warning = require('fbjs/lib/warning');
+
+const TimePickerAndroid = {
+  async open(options: Object): Promise<Object> {
+    return Promise.reject({
+      message: 'TimePickerAndroid is not supported on this platform.'
+    });
+  },
+};
+
+module.exports = TimePickerAndroid;
diff --git a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js
index bf42aae640e8..4fc912777abb 100644
--- a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js
+++ b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js
@@ -7,10 +7,11 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule ToastAndroid
+ * @noflow
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var ToastAndroid = {
 
@@ -18,7 +19,7 @@ var ToastAndroid = {
     message: string,
     duration: number
   ): void {
-    warning(false, 'Cannot use ToastAndroid on iOS.');
+    warning(false, 'ToastAndroid is not supported on this platform.');
   },
 
 };
diff --git a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
index 774e602bee68..060c6a1d0e48 100644
--- a/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
+++ b/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js
@@ -13,11 +13,12 @@
 
 var Image = require('Image');
 var NativeMethodsMixin = require('NativeMethodsMixin');
-var RCTUIManager = require('NativeModules').UIManager;
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var ReactPropTypes = require('ReactPropTypes');
+var UIManager = require('UIManager');
 var View = require('View');
+var ColorPropType = require('ColorPropType');
 
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
@@ -49,9 +50,9 @@ var optionalImageSource = ReactPropTypes.oneOfType([
  * render: function() {
  *   return (
  *     <ToolbarAndroid
- *       logo={require('image!app_logo')}
+ *       logo={require('./app_logo.png')}
  *       title="AwesomeApp"
- *       actions={[{title: 'Settings', icon: require('image!icon_settings'), show: 'always'}]}
+ *       actions={[{title: 'Settings', icon: require('./icon_settings.png'), show: 'always'}]}
  *       onActionSelected={this.onActionSelected} />
  *   )
  * },
@@ -77,7 +78,7 @@ var ToolbarAndroid = React.createClass({
      * This property takes an array of objects, where each object has the following keys:
      *
      * * `title`: **required**, the title of this action
-     * * `icon`: the icon for this action, e.g. `require('image!some_icon')`
+     * * `icon`: the icon for this action, e.g. `require('./some_icon.png')`
      * * `show`: when to show this action as an icon or hide it in the overflow menu: `always`,
      * `ifRoom` or `never`
      * * `showWithText`: boolean, whether to show text alongside the icon or not
@@ -97,7 +98,7 @@ var ToolbarAndroid = React.createClass({
      */
     navIcon: optionalImageSource,
     /**
-     * Callback that is called when an action is selected. The only argument that is passeed to the
+     * Callback that is called when an action is selected. The only argument that is passed to the
      * callback is the position of the action in the actions array.
      */
     onActionSelected: ReactPropTypes.func,
@@ -116,7 +117,7 @@ var ToolbarAndroid = React.createClass({
     /**
      * Sets the toolbar subtitle color.
      */
-    subtitleColor: ReactPropTypes.string,
+    subtitleColor: ColorPropType,
     /**
      * Sets the toolbar title.
      */
@@ -124,7 +125,36 @@ var ToolbarAndroid = React.createClass({
     /**
      * Sets the toolbar title color.
      */
-    titleColor: ReactPropTypes.string,
+    titleColor: ColorPropType,
+    /**
+     * Sets the content inset for the toolbar starting edge.
+     *
+     * The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for
+     * these components and can be used to effectively align Toolbar content
+     * along well-known gridlines.
+     */
+    contentInsetStart: ReactPropTypes.number,
+    /**
+     * Sets the content inset for the toolbar ending edge.
+     *
+     * The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for
+     * these components and can be used to effectively align Toolbar content
+     * along well-known gridlines.
+     */
+    contentInsetEnd: ReactPropTypes.number,
+    /**
+     * Used to set the toolbar direction to RTL.
+     * In addition to this property you need to add
+     *
+     *   android:supportsRtl="true"
+     *
+     * to your application AndroidManifest.xml and then call
+     * `setLayoutDirection(LayoutDirection.RTL)` in your MainActivity
+     * `onCreate` method.
+     */
+    rtl: ReactPropTypes.bool,
     /**
      * Used to locate this view in end-to-end tests.
      */
@@ -154,7 +184,7 @@ var ToolbarAndroid = React.createClass({
           action.icon = resolveAssetSource(action.icon);
         }
         if (action.show) {
-          action.show = RCTUIManager.ToolbarAndroid.Constants.ShowAsAction[action.show];
+          action.show = UIManager.ToolbarAndroid.Constants.ShowAsAction[action.show];
         }
         nativeActions.push(action);
       }
@@ -180,6 +210,7 @@ var toolbarAttributes = {
   logo: true,
   navIcon: true,
   overflowIcon: true,
+  rtl: true,
   subtitle: true,
   subtitleColor: true,
   title: true,
diff --git a/Libraries/Components/Touchable/BoundingDimensions.js b/Libraries/Components/Touchable/BoundingDimensions.js
index f4acffc0237e..ca63fd14b08e 100644
--- a/Libraries/Components/Touchable/BoundingDimensions.js
+++ b/Libraries/Components/Touchable/BoundingDimensions.js
@@ -2,7 +2,7 @@
  * @providesModule BoundingDimensions
  */
 
-"use strict";
+'use strict';
 
 var PooledClass = require('PooledClass');
 
diff --git a/Libraries/Components/Touchable/Position.js b/Libraries/Components/Touchable/Position.js
index 6175b6b9ea8c..5287e929afb5 100644
--- a/Libraries/Components/Touchable/Position.js
+++ b/Libraries/Components/Touchable/Position.js
@@ -2,7 +2,7 @@
  * @providesModule Position
  */
 
-"use strict";
+'use strict';
 
 var PooledClass = require('PooledClass');
 
@@ -11,7 +11,7 @@ var twoArgumentPooler = PooledClass.twoArgumentPooler;
 /**
  * Position does not expose methods for construction via an `HTMLDOMElement`,
  * because it isn't meaningful to construct such a thing without first defining
- * a frame of refrence.
+ * a frame of reference.
  *
  * @param {number} windowStartKey Key that window starts at.
  * @param {number} windowEndKey Key that window ends at.
@@ -29,4 +29,3 @@ Position.prototype.destructor = function() {
 PooledClass.addPoolingTo(Position, twoArgumentPooler);
 
 module.exports = Position;
-
diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js
index 7b1f56a551e5..4f5000c5ce88 100644
--- a/Libraries/Components/Touchable/Touchable.js
+++ b/Libraries/Components/Touchable/Touchable.js
@@ -6,9 +6,9 @@
 
 var BoundingDimensions = require('BoundingDimensions');
 var Position = require('Position');
-var TouchEventUtils = require('TouchEventUtils');
+var TouchEventUtils = require('fbjs/lib/TouchEventUtils');
 
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 var queryLayoutByID = require('queryLayoutByID');
 
 /**
@@ -31,7 +31,7 @@ var queryLayoutByID = require('queryLayoutByID');
  *
  * A good tap interaction isn't as simple as you might think. There should be a
  * slight delay before showing a highlight when starting a touch. If a
- * subsequent touch move exceeds the boundary of the elemement, it should
+ * subsequent touch move exceeds the boundary of the element, it should
  * unhighlight, but if that same touch is brought back within the boundary, it
  * should rehighlight again. A touch can move in and out of that boundary
  * several times, each time toggling highlighting, but a "press" is only
@@ -49,24 +49,24 @@ var queryLayoutByID = require('queryLayoutByID');
  *
  * - Choose the rendered component who's touches should start the interactive
  *   sequence. On that rendered node, forward all `Touchable` responder
- *   handlers. You can choose any rendered node you like. Choose a node who's
+ *   handlers. You can choose any rendered node you like. Choose a node whose
  *   hit target you'd like to instigate the interaction sequence:
  *
  *   // In render function:
  *   return (
- *     <div
+ *     <View
  *       onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
  *       onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
  *       onResponderGrant={this.touchableHandleResponderGrant}
  *       onResponderMove={this.touchableHandleResponderMove}
  *       onResponderRelease={this.touchableHandleResponderRelease}
  *       onResponderTerminate={this.touchableHandleResponderTerminate}>
- *       <div>
+ *       <View>
  *         Even though the hit detection/interactions are triggered by the
  *         wrapping (typically larger) node, we usually end up implementing
  *         custom logic that highlights this inner one.
- *       </div>
- *     </div>
+ *       </View>
+ *     </View>
  *   );
  *
  * - You may set up your own handlers for each of these events, so long as you
@@ -337,7 +337,7 @@ var TouchableMixin = {
    * Must return true to start the process of `Touchable`.
    */
   touchableHandleStartShouldSetResponder: function() {
-    return true;
+    return !this.props.disabled;
   },
 
   /**
@@ -426,12 +426,22 @@ var TouchableMixin = {
         top: PRESS_EXPAND_PX,
         bottom: PRESS_EXPAND_PX
       };
-      
+
     var pressExpandLeft = pressRectOffset.left;
     var pressExpandTop = pressRectOffset.top;
     var pressExpandRight = pressRectOffset.right;
     var pressExpandBottom = pressRectOffset.bottom;
-    
+
+    var hitSlop = this.touchableGetHitSlop ?
+      this.touchableGetHitSlop() : null;
+
+    if (hitSlop) {
+      pressExpandLeft += hitSlop.left;
+      pressExpandTop += hitSlop.top;
+      pressExpandRight += hitSlop.right;
+      pressExpandBottom += hitSlop.bottom;
+    }
+
     var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
     var pageX = touch && touch.pageX;
     var pageY = touch && touch.pageY;
@@ -456,6 +466,11 @@ var TouchableMixin = {
           pressExpandBottom;
     if (isTouchWithinActive) {
       this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
+      var curState = this.state.touchable.touchState;
+      if (curState === States.RESPONDER_INACTIVE_PRESS_IN) {
+        // fix for t7967420
+        this._cancelLongPressDelayTimeout();
+      }
     } else {
       this._cancelLongPressDelayTimeout();
       this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
@@ -564,7 +579,15 @@ var TouchableMixin = {
 
   _handleLongDelay: function(e) {
     this.longPressDelayTimeout = null;
-    this._receiveSignal(Signals.LONG_PRESS_DETECTED, e);
+    var curState = this.state.touchable.touchState;
+    if (curState !== States.RESPONDER_ACTIVE_PRESS_IN &&
+        curState !== States.RESPONDER_ACTIVE_LONG_PRESS_IN) {
+      console.error('Attempted to transition from state `' + curState + '` to `' +
+        States.RESPONDER_ACTIVE_LONG_PRESS_IN + '`, which is not supported. This is ' +
+        'most likely due to `Touchable.longPressDelayTimeout` not being cancelled.');
+    } else {
+      this._receiveSignal(Signals.LONG_PRESS_DETECTED, e);
+    }
   },
 
   /**
@@ -577,13 +600,13 @@ var TouchableMixin = {
    */
   _receiveSignal: function(signal, e) {
     var curState = this.state.touchable.touchState;
-    if (!(Transitions[curState] && Transitions[curState][signal])) {
+    var nextState = Transitions[curState] && Transitions[curState][signal];
+    if (!nextState) {
       throw new Error(
         'Unrecognized signal `' + signal + '` or state `' + curState +
         '` for Touchable responder `' + this.state.touchable.responderID + '`'
       );
     }
-    var nextState = Transitions[curState][signal];
     if (nextState === States.ERROR) {
       throw new Error(
         'Touchable cannot transition from `' + curState + '` to `' + signal +
@@ -610,7 +633,9 @@ var TouchableMixin = {
     var touch = TouchEventUtils.extractSingleTouch(e.nativeEvent);
     var pageX = touch && touch.pageX;
     var pageY = touch && touch.pageY;
-    this.pressInLocation = {pageX: pageX, pageY: pageY};
+    var locationX = touch && touch.locationX;
+    var locationY = touch && touch.locationY;
+    this.pressInLocation = {pageX, pageY, locationX, locationY};
   },
 
   _getDistanceBetweenPoints: function (aX, aY, bX, bY) {
@@ -655,7 +680,7 @@ var TouchableMixin = {
       this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(e);
     } else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) {
       if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) {
-        this.pressOutDelayTimeout = this.setTimeout(function() {
+        this.pressOutDelayTimeout = setTimeout(() => {
           this.touchableHandleActivePressOut(e);
         }, this.touchableGetPressOutDelayMS());
       } else {
diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js
index 0b7d91152c76..5ea367ceba59 100644
--- a/Libraries/Components/Touchable/TouchableBounce.js
+++ b/Libraries/Components/Touchable/TouchableBounce.js
@@ -21,6 +21,7 @@ type Event = Object;
 
 type State = {
   animationID: ?number;
+  scale: Animated.Value;
 };
 
 var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
@@ -53,6 +54,15 @@ var TouchableBounce = React.createClass({
      * is disabled. Ensure you pass in a constant to reduce memory allocations.
      */
     pressRetentionOffset: EdgeInsetsPropType,
+    /**
+     * This defines how far your touch can start away from the button. This is
+     * added to `pressRetentionOffset` when moving off of the button.
+     * ** NOTE **
+     * The touch area never extends past the parent view bounds and the Z-index
+     * of sibling views always takes precedence if a touch hits two overlapping
+     * views.
+     */
+    hitSlop: EdgeInsetsPropType,
   },
 
   getInitialState: function(): State {
@@ -107,6 +117,10 @@ var TouchableBounce = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function(): ?Object {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function(): number {
     return 0;
   },
@@ -116,7 +130,11 @@ var TouchableBounce = React.createClass({
       <Animated.View
         style={[{transform: [{scale: this.state.scale}]}, this.props.style]}
         accessible={true}
+        accessibilityLabel={this.props.accessibilityLabel}
+        accessibilityComponentType={this.props.accessibilityComponentType}
+        accessibilityTraits={this.props.accessibilityTraits}
         testID={this.props.testID}
+        hitSlop={this.props.hitSlop}
         onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
         onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
         onResponderGrant={this.touchableHandleResponderGrant}
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index b7dc706eb6a0..a15501bebba8 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -7,11 +7,13 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule TouchableHighlight
+ * @noflow
  */
 'use strict';
 
 // Note (avik): add @flow when Flow supports spread properties in propTypes
 
+var ColorPropType = require('ColorPropType');
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
@@ -23,7 +25,7 @@ var View = require('View');
 
 var ensureComponentIsNative = require('ensureComponentIsNative');
 var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
-var keyOf = require('keyOf');
+var keyOf = require('fbjs/lib/keyOf');
 var merge = require('merge');
 var onlyChild = require('onlyChild');
 
@@ -75,7 +77,7 @@ var TouchableHighlight = React.createClass({
      * The color of the underlay that will show through when the touch is
      * active.
      */
-    underlayColor: React.PropTypes.string,
+    underlayColor: ColorPropType,
     style: View.propTypes.style,
     /**
      * Called immediately after the underlay is shown
@@ -174,6 +176,10 @@ var TouchableHighlight = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function() {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function() {
     return this.props.delayPressIn;
   },
@@ -187,7 +193,7 @@ var TouchableHighlight = React.createClass({
   },
 
   _showUnderlay: function() {
-    if (!this.isMounted()) {
+    if (!this.isMounted() || !this._hasPressHandler()) {
       return;
     }
 
@@ -199,7 +205,7 @@ var TouchableHighlight = React.createClass({
   _hideUnderlay: function() {
     this.clearTimeout(this._hideTimeout);
     this._hideTimeout = null;
-    if (this.refs[UNDERLAY_REF]) {
+    if (this._hasPressHandler() && this.refs[UNDERLAY_REF]) {
       this.refs[CHILD_REF].setNativeProps(INACTIVE_CHILD_PROPS);
       this.refs[UNDERLAY_REF].setNativeProps({
         ...INACTIVE_UNDERLAY_PROPS,
@@ -209,15 +215,26 @@ var TouchableHighlight = React.createClass({
     }
   },
 
+  _hasPressHandler: function() {
+    return !!(
+      this.props.onPress ||
+      this.props.onPressIn ||
+      this.props.onPressOut ||
+      this.props.onLongPress
+    );
+  },
+
   render: function() {
     return (
       <View
         accessible={true}
+        accessibilityLabel={this.props.accessibilityLabel}
         accessibilityComponentType={this.props.accessibilityComponentType}
         accessibilityTraits={this.props.accessibilityTraits}
         ref={UNDERLAY_REF}
         style={this.state.underlayStyle}
         onLayout={this.props.onLayout}
+        hitSlop={this.props.hitSlop}
         onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
         onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
         onResponderGrant={this.touchableHandleResponderGrant}
diff --git a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
index dd479bdfefbb..6d18182b8531 100644
--- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
+++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
@@ -11,25 +11,24 @@
 'use strict';
 
 var PropTypes = require('ReactPropTypes');
-var RCTUIManager = require('NativeModules').UIManager;
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var Touchable = require('Touchable');
 var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
+var UIManager = require('UIManager');
 
-var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
 var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
 var onlyChild = require('onlyChild');
 var processColor = require('processColor');
 var requireNativeComponent = require('requireNativeComponent');
 
-var rippleBackgroundPropType = createStrictShapeTypeChecker({
+var rippleBackgroundPropType = PropTypes.shape({
   type: React.PropTypes.oneOf(['RippleAndroid']),
   color: PropTypes.number,
   borderless: PropTypes.bool,
 });
 
-var themeAttributeBackgroundPropType = createStrictShapeTypeChecker({
+var themeAttributeBackgroundPropType = PropTypes.shape({
   type: React.PropTypes.oneOf(['ThemeAttrAndroid']),
   attribute: PropTypes.string.isRequired,
 });
@@ -142,7 +141,7 @@ var TouchableNativeFeedback = React.createClass({
   touchableHandleActivePressIn: function() {
     this.props.onPressIn && this.props.onPressIn();
     this._dispatchPressedStateChange(true);
-    this._dispatchHotspotUpdate(this.pressInLocation.pageX, this.pressInLocation.pageY);
+    this._dispatchHotspotUpdate(this.pressInLocation.locationX, this.pressInLocation.locationY);
   },
 
   touchableHandleActivePressOut: function() {
@@ -163,6 +162,10 @@ var TouchableNativeFeedback = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function() {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function() {
     return this.props.delayPressIn;
   },
@@ -181,17 +184,17 @@ var TouchableNativeFeedback = React.createClass({
   },
 
   _dispatchHotspotUpdate: function(destX, destY) {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       React.findNodeHandle(this),
-      RCTUIManager.RCTView.Commands.hotspotUpdate,
+      UIManager.RCTView.Commands.hotspotUpdate,
       [destX || 0, destY || 0]
     );
   },
 
   _dispatchPressedStateChange: function(pressed) {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       React.findNodeHandle(this),
-      RCTUIManager.RCTView.Commands.setPressed,
+      UIManager.RCTView.Commands.setPressed,
       [pressed]
     );
   },
@@ -201,10 +204,12 @@ var TouchableNativeFeedback = React.createClass({
       ...onlyChild(this.props.children).props,
       nativeBackgroundAndroid: this.props.background,
       accessible: this.props.accessible !== false,
+      accessibilityLabel: this.props.accessibilityLabel,
       accessibilityComponentType: this.props.accessibilityComponentType,
       accessibilityTraits: this.props.accessibilityTraits,
       testID: this.props.testID,
       onLayout: this.props.onLayout,
+      hitSlop: this.props.hitSlop,
       onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
       onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
       onResponderGrant: this.touchableHandleResponderGrant,
diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js
index 5d7d8f3fbb28..29ceda28947e 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -7,6 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule TouchableOpacity
+ * @noflow
  */
 'use strict';
 
@@ -123,6 +124,10 @@ var TouchableOpacity = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function() {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function() {
     return this.props.delayPressIn || 0;
   },
@@ -153,11 +158,13 @@ var TouchableOpacity = React.createClass({
     return (
       <Animated.View
         accessible={true}
+        accessibilityLabel={this.props.accessibilityLabel}
         accessibilityComponentType={this.props.accessibilityComponentType}
         accessibilityTraits={this.props.accessibilityTraits}
         style={[this.props.style, {opacity: this.state.anim}]}
         testID={this.props.testID}
         onLayout={this.props.onLayout}
+        hitSlop={this.props.hitSlop}
         onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
         onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
         onResponderGrant={this.touchableHandleResponderGrant}
diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
index 8c04c945709c..d48e15856303 100755
--- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js
+++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js
@@ -18,7 +18,7 @@ var TimerMixin = require('react-timer-mixin');
 var Touchable = require('Touchable');
 var View = require('View');
 var ensurePositiveDelayProps = require('ensurePositiveDelayProps');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var onlyChild = require('onlyChild');
 
 type Event = Object;
@@ -29,6 +29,10 @@ var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
  * Do not use unless you have a very good reason. All the elements that
  * respond to press should have a visual feedback when touched. This is
  * one of the primary reason a "web" app doesn't feel "native".
+ *
+ * > **NOTE**: TouchableWithoutFeedback supports only one child
+ * >
+ * > If you wish to have several child components, wrap them in a View.
  */
 var TouchableWithoutFeedback = React.createClass({
   mixins: [TimerMixin, Touchable.Mixin],
@@ -40,6 +44,10 @@ var TouchableWithoutFeedback = React.createClass({
       React.PropTypes.oneOf(View.AccessibilityTraits),
       React.PropTypes.arrayOf(React.PropTypes.oneOf(View.AccessibilityTraits)),
     ]),
+    /**
+     * If true, disable all interactions for this component.
+     */
+    disabled: React.PropTypes.bool,
     /**
      * Called when the touch is released, but not if cancelled (e.g. by a scroll
      * that steals the responder lock).
@@ -76,6 +84,15 @@ var TouchableWithoutFeedback = React.createClass({
      * is disabled. Ensure you pass in a constant to reduce memory allocations.
      */
     pressRetentionOffset: EdgeInsetsPropType,
+    /**
+     * This defines how far your touch can start away from the button. This is
+     * added to `pressRetentionOffset` when moving off of the button.
+     * ** NOTE **
+     * The touch area never extends past the parent view bounds and the Z-index
+     * of sibling views always takes precedence if a touch hits two overlapping
+     * views.
+     */
+    hitSlop: EdgeInsetsPropType,
   },
 
   getInitialState: function() {
@@ -114,6 +131,10 @@ var TouchableWithoutFeedback = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function(): ?Object {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function(): number {
     return this.props.delayPressIn || 0;
   },
@@ -131,10 +152,12 @@ var TouchableWithoutFeedback = React.createClass({
     // Note(avik): remove dynamic typecast once Flow has been upgraded
     return (React: any).cloneElement(onlyChild(this.props.children), {
       accessible: this.props.accessible !== false,
+      accessibilityLabel: this.props.accessibilityLabel,
       accessibilityComponentType: this.props.accessibilityComponentType,
       accessibilityTraits: this.props.accessibilityTraits,
       testID: this.props.testID,
       onLayout: this.props.onLayout,
+      hitSlop: this.props.hitSlop,
       onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
       onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
       onResponderGrant: this.touchableHandleResponderGrant,
diff --git a/Libraries/Components/Touchable/ensureComponentIsNative.js b/Libraries/Components/Touchable/ensureComponentIsNative.js
index 944093f78e13..c66735b508b1 100644
--- a/Libraries/Components/Touchable/ensureComponentIsNative.js
+++ b/Libraries/Components/Touchable/ensureComponentIsNative.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var ensureComponentIsNative = function(component: any) {
   invariant(
diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js
index 4c6525a54130..d2f6a09e8c89 100644
--- a/Libraries/Components/Touchable/ensurePositiveDelayProps.js
+++ b/Libraries/Components/Touchable/ensurePositiveDelayProps.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var ensurePositiveDelayProps = function(props: any) {
   invariant(
diff --git a/Libraries/Components/UnimplementedViews/UnimplementedView.js b/Libraries/Components/UnimplementedViews/UnimplementedView.js
index 6aebef38b16b..1870f495bcc7 100644
--- a/Libraries/Components/UnimplementedViews/UnimplementedView.js
+++ b/Libraries/Components/UnimplementedViews/UnimplementedView.js
@@ -9,7 +9,6 @@
 
 var React = require('React');
 var StyleSheet = require('StyleSheet');
-var View = require('View');
 
 var UnimplementedView = React.createClass({
   setNativeProps: function() {
@@ -18,6 +17,8 @@ var UnimplementedView = React.createClass({
     // See ensureComponentIsNative.js for more info
   },
   render: function() {
+    // Workaround require cycle from requireNativeComponent
+    var View = require('View');
     return (
       <View style={[styles.unimplementedView, this.props.style]}>
         {this.props.children}
diff --git a/Libraries/Components/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js
index ffeff5420d2e..1e91b1ef265f 100644
--- a/Libraries/Components/View/ReactNativeStyleAttributes.js
+++ b/Libraries/Components/View/ReactNativeStyleAttributes.js
@@ -16,7 +16,7 @@ var ImageStylePropTypes = require('ImageStylePropTypes');
 var TextStylePropTypes = require('TextStylePropTypes');
 var ViewStylePropTypes = require('ViewStylePropTypes');
 
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 var matricesDiffer = require('matricesDiffer');
 var processColor = require('processColor');
 var processTransform = require('processTransform');
@@ -46,5 +46,7 @@ ReactNativeStyleAttributes.color = colorAttributes;
 ReactNativeStyleAttributes.shadowColor = colorAttributes;
 ReactNativeStyleAttributes.textDecorationColor = colorAttributes;
 ReactNativeStyleAttributes.tintColor = colorAttributes;
+ReactNativeStyleAttributes.textShadowColor = colorAttributes;
+ReactNativeStyleAttributes.overlayColor = colorAttributes;
 
 module.exports = ReactNativeStyleAttributes;
diff --git a/Libraries/Components/View/ShadowPropTypesIOS.js b/Libraries/Components/View/ShadowPropTypesIOS.js
new file mode 100644
index 000000000000..c770ca9ed267
--- /dev/null
+++ b/Libraries/Components/View/ShadowPropTypesIOS.js
@@ -0,0 +1,42 @@
+/**
+ * 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 ShadowPropTypesIOS
+ * @flow
+ */
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var ReactPropTypes = require('ReactPropTypes');
+
+var ShadowPropTypesIOS = {
+  /**
+   * Sets the drop shadow color
+   * @platform ios
+   */
+  shadowColor: ColorPropType,
+  /**
+   * Sets the drop shadow offset
+   * @platform ios
+   */
+  shadowOffset: ReactPropTypes.shape(
+    {width: ReactPropTypes.number, height: ReactPropTypes.number}
+  ),
+  /**
+   * Sets the drop shadow opacity (multiplied by the color's alpha component)
+   * @platform ios
+   */
+  shadowOpacity: ReactPropTypes.number,
+  /**
+   * Sets the drop shadow blur radius
+   * @platform ios
+   */
+  shadowRadius: ReactPropTypes.number,
+};
+
+module.exports = ShadowPropTypesIOS;
diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index 3f62f027c66b..20b9d9288344 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -11,20 +11,21 @@
  */
 'use strict';
 
-var NativeMethodsMixin = require('NativeMethodsMixin');
-var PropTypes = require('ReactPropTypes');
-var RCTUIManager = require('NativeModules').UIManager;
-var React = require('React');
-var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
-var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var StyleSheetPropType = require('StyleSheetPropType');
-var ViewStylePropTypes = require('ViewStylePropTypes');
+const EdgeInsetsPropType = require('EdgeInsetsPropType');
+const NativeMethodsMixin = require('NativeMethodsMixin');
+const PropTypes = require('ReactPropTypes');
+const React = require('React');
+const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
+const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
+const StyleSheetPropType = require('StyleSheetPropType');
+const UIManager = require('UIManager');
+const ViewStylePropTypes = require('ViewStylePropTypes');
 
-var createReactNativeComponentClass = require('createReactNativeComponentClass');
+const requireNativeComponent = require('requireNativeComponent');
 
-var stylePropType = StyleSheetPropType(ViewStylePropTypes);
+const stylePropType = StyleSheetPropType(ViewStylePropTypes);
 
-var AccessibilityTraits = [
+const AccessibilityTraits = [
   'none',
   'button',
   'link',
@@ -44,13 +45,26 @@ var AccessibilityTraits = [
   'pageTurn',
 ];
 
-var AccessibilityComponentType = [
+const AccessibilityComponentType = [
   'none',
   'button',
   'radiobutton_checked',
   'radiobutton_unchecked',
 ];
 
+const forceTouchAvailable = (UIManager.RCTView.Constants &&
+  UIManager.RCTView.Constants.forceTouchAvailable) || false;
+
+const statics = {
+  AccessibilityTraits,
+  AccessibilityComponentType,
+  /**
+   * Is 3D Touch / Force Touch available (i.e. will touch events include `force`)
+   * @platform ios
+   */
+  forceTouchAvailable,
+};
+
 /**
  * The most fundamental component for building UI, `View` is a
  * container that supports layout with flexbox, style, some touch handling, and
@@ -71,7 +85,7 @@ var AccessibilityComponentType = [
  * `View`s are designed to be used with `StyleSheet`s for clarity and
  * performance, although inline styles are also supported.
  */
-var View = React.createClass({
+const View = React.createClass({
   mixins: [NativeMethodsMixin],
 
   /**
@@ -84,8 +98,7 @@ var View = React.createClass({
   },
 
   statics: {
-    AccessibilityTraits,
-    AccessibilityComponentType,
+    ...statics,
   },
 
   propTypes: {
@@ -189,6 +202,19 @@ var View = React.createClass({
     onMoveShouldSetResponder: PropTypes.func,
     onMoveShouldSetResponderCapture: PropTypes.func,
 
+    /**
+     * This defines how far a touch event can start away from the view.
+     * Typical interface guidelines recommend touch targets that are at least
+     * 30 - 40 points/density-independent pixels. If a Touchable view has a
+     * height of 20 the touchable height can be extended to 40 with
+     * `hitSlop={{top: 10, bottom: 10, left: 0, right: 0}}`
+     * ** NOTE **
+     * The touch area never extends past the parent view bounds and the Z-index
+     * of sibling views always takes precedence if a touch hits two overlapping
+     * views.
+     */
+    hitSlop: EdgeInsetsPropType,
+
     /**
      * Invoked on mount and layout changes with
      *
@@ -312,19 +338,24 @@ var View = React.createClass({
   },
 
   render: function() {
+    // WARNING: This method will not be used in production mode as in that mode we
+    // replace wrapper component View with generated native wrapper RCTView. Avoid
+    // adding functionality this component that you'd want to be available in both
+    // dev and prod modes.
     return <RCTView {...this.props} />;
   },
 });
 
-var RCTView = createReactNativeComponentClass({
-  validAttributes: ReactNativeViewAttributes.RCTView,
-  uiViewClassName: 'RCTView',
+const RCTView = requireNativeComponent('RCTView', View, {
+  nativeOnly: {
+    nativeBackgroundAndroid: true,
+  }
 });
-RCTView.propTypes = View.propTypes;
+
 if (__DEV__) {
-  var viewConfig = RCTUIManager.viewConfigs && RCTUIManager.viewConfigs.RCTView || {};
-  for (var prop in viewConfig.nativeProps) {
-    var viewAny: any = View; // Appease flow
+  const viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {};
+  for (const prop in viewConfig.nativeProps) {
+    const viewAny: any = View; // Appease flow
     if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) {
       throw new Error(
         'View is missing propType for native prop `' + prop + '`'
@@ -333,9 +364,11 @@ if (__DEV__) {
   }
 }
 
-var ViewToExport = RCTView;
+let ViewToExport = RCTView;
 if (__DEV__) {
   ViewToExport = View;
+} else {
+  Object.assign(RCTView, statics);
 }
 
 module.exports = ViewToExport;
diff --git a/Libraries/Components/View/ViewStylePropTypes.js b/Libraries/Components/View/ViewStylePropTypes.js
index e36b143a1e4b..619df34c8d96 100644
--- a/Libraries/Components/View/ViewStylePropTypes.js
+++ b/Libraries/Components/View/ViewStylePropTypes.js
@@ -13,6 +13,8 @@
 
 var LayoutPropTypes = require('LayoutPropTypes');
 var ReactPropTypes = require('ReactPropTypes');
+var ColorPropType = require('ColorPropType');
+var ShadowPropTypesIOS = require('ShadowPropTypesIOS');
 var TransformPropTypes = require('TransformPropTypes');
 
 /**
@@ -20,14 +22,15 @@ var TransformPropTypes = require('TransformPropTypes');
  */
 var ViewStylePropTypes = {
   ...LayoutPropTypes,
+  ...ShadowPropTypesIOS,
   ...TransformPropTypes,
   backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
-  backgroundColor: ReactPropTypes.string,
-  borderColor: ReactPropTypes.string,
-  borderTopColor: ReactPropTypes.string,
-  borderRightColor: ReactPropTypes.string,
-  borderBottomColor: ReactPropTypes.string,
-  borderLeftColor: ReactPropTypes.string,
+  backgroundColor: ColorPropType,
+  borderColor: ColorPropType,
+  borderTopColor: ColorPropType,
+  borderRightColor: ColorPropType,
+  borderBottomColor: ColorPropType,
+  borderLeftColor: ColorPropType,
   borderRadius: ReactPropTypes.number,
   borderTopLeftRadius: ReactPropTypes.number,
   borderTopRightRadius: ReactPropTypes.number,
@@ -41,12 +44,14 @@ var ViewStylePropTypes = {
   borderLeftWidth: ReactPropTypes.number,
   opacity: ReactPropTypes.number,
   overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
-  shadowColor: ReactPropTypes.string,
-  shadowOffset: ReactPropTypes.shape(
-    {width: ReactPropTypes.number, height: ReactPropTypes.number}
-  ),
-  shadowOpacity: ReactPropTypes.number,
-  shadowRadius: ReactPropTypes.number,
+  /**
+   * (Android-only) Sets the elevation of a view, using Android's underlying
+   * [elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation).
+   * This adds a drop shadow to the item and affects z-order for overlapping views.
+   * Only supported on Android 5.0+, has no effect on earlier versions.
+   * @platform android
+   */
+  elevation: ReactPropTypes.number,
 };
 
 module.exports = ViewStylePropTypes;
diff --git a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
index 7151917ff637..a8af449746c9 100644
--- a/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
+++ b/Libraries/Components/ViewPager/ViewPagerAndroid.android.js
@@ -7,24 +7,30 @@
 'use strict';
 
 var NativeMethodsMixin = require('NativeMethodsMixin');
-var NativeModules = require('NativeModules');
 var React = require('React');
 var ReactElement = require('ReactElement');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var ReactPropTypes = require('ReactPropTypes');
+var UIManager = require('UIManager');
 var View = require('View');
 
-var RCTUIManager = NativeModules.UIManager;
-
 var dismissKeyboard = require('dismissKeyboard');
 var requireNativeComponent = require('requireNativeComponent');
 
 var VIEWPAGER_REF = 'viewPager';
 
+type Event = Object;
+
+export type ViewPagerScrollState = $Enum<{
+  idle: string;
+  dragging: string;
+  settling: string;
+}>;
+
 /**
  * Container that allows to flip left and right between child views. Each
  * child view of the `ViewPagerAndroid` will be treated as a separate page
- * and will be streched to fill the `ViewPagerAndroid`.
+ * and will be stretched to fill the `ViewPagerAndroid`.
  *
  * It is important all children are `<View>`s and not composite components.
  * You can set style properties like `padding` or `backgroundColor` for each
@@ -81,7 +87,17 @@ var ViewPagerAndroid = React.createClass({
     onPageScroll: ReactPropTypes.func,
 
     /**
-     * This callback will be caleld once ViewPager finish navigating to selected page
+     * Function called when the page scrolling state has changed.
+     * The page scrolling state can be in 3 states:
+     * - idle, meaning there is no interaction with the page scroller happening at the time
+     * - dragging, meaning there is currently an interaction with the page scroller
+     * - settling, meaning that there was an interaction with the page scroller, and the
+     *   page scroller is now finishing it's closing or opening animation
+     */
+    onPageScrollStateChanged: ReactPropTypes.func,
+
+    /**
+     * This callback will be called once ViewPager finish navigating to selected page
      * (when user swipes between pages). The `event.nativeEvent` object passed to this
      * callback will have following fields:
      *  - position - index of page that has been selected
@@ -114,6 +130,9 @@ var ViewPagerAndroid = React.createClass({
     // will handle positioning of elements, so it's not important to offset
     // them correctly.
     return React.Children.map(this.props.children, function(child) {
+      if (!child) {
+        return null;
+      }
       var newProps = {
         ...child.props,
         style: [child.props.style, {
@@ -146,6 +165,12 @@ var ViewPagerAndroid = React.createClass({
     }
   },
 
+  _onPageScrollStateChanged: function(e: Event) {
+    if (this.props.onPageScrollStateChanged) {
+      this.props.onPageScrollStateChanged(e.nativeEvent.pageScrollState);
+    }
+  },
+
   _onPageSelected: function(e: Event) {
     if (this.props.onPageSelected) {
       this.props.onPageSelected(e);
@@ -157,9 +182,9 @@ var ViewPagerAndroid = React.createClass({
    * The transition between pages will be animated.
    */
   setPage: function(selectedPage: number) {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       React.findNodeHandle(this),
-      RCTUIManager.AndroidViewPager.Commands.setPage,
+      UIManager.AndroidViewPager.Commands.setPage,
       [selectedPage],
     );
   },
@@ -169,9 +194,9 @@ var ViewPagerAndroid = React.createClass({
    * The transition between pages will be *not* be animated.
    */
   setPageWithoutAnimation: function(selectedPage: number) {
-    RCTUIManager.dispatchViewManagerCommand(
+    UIManager.dispatchViewManagerCommand(
       React.findNodeHandle(this),
-      RCTUIManager.AndroidViewPager.Commands.setPageWithoutAnimation,
+      UIManager.AndroidViewPager.Commands.setPageWithoutAnimation,
       [selectedPage],
     );
   },
@@ -182,6 +207,7 @@ var ViewPagerAndroid = React.createClass({
         ref={VIEWPAGER_REF}
         style={this.props.style}
         onPageScroll={this._onPageScroll}
+        onPageScrollStateChanged={this._onPageScrollStateChanged}
         onPageSelected={this._onPageSelected}
         children={this._childrenWithOverridenStyle()}
       />
diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js
index f3ce5956d80e..ebf06456671d 100644
--- a/Libraries/Components/WebView/WebView.android.js
+++ b/Libraries/Components/WebView/WebView.android.js
@@ -14,14 +14,16 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var StyleSheet = require('StyleSheet');
+var UIManager = require('UIManager');
 var View = require('View');
 
-var keyMirror = require('keyMirror');
+var deprecatedPropType = require('deprecatedPropType');
+var keyMirror = require('fbjs/lib/keyMirror');
 var merge = require('merge');
 var requireNativeComponent = require('requireNativeComponent');
+var resolveAssetSource = require('resolveAssetSource');
 
 var PropTypes = React.PropTypes;
-var RCTUIManager = require('NativeModules').UIManager;
 
 var RCT_WEBVIEW_REF = 'webview';
 
@@ -32,23 +34,88 @@ var WebViewState = keyMirror({
 });
 
 /**
- * Note that WebView is only supported on iOS for now,
- * see https://facebook.github.io/react-native/docs/known-issues.html
+ * Renders a native WebView.
  */
 var WebView = React.createClass({
 
   propTypes: {
     ...View.propTypes,
-    renderError: PropTypes.func, // view to show if there's an error
-    renderLoading: PropTypes.func, // loading indicator to show
-    url: PropTypes.string,
-    html: PropTypes.string,
+    renderError: PropTypes.func,
+    renderLoading: PropTypes.func,
+    onLoad: PropTypes.func,
+    onLoadEnd: PropTypes.func,
+    onLoadStart: PropTypes.func,
+    onError: PropTypes.func,
     automaticallyAdjustContentInsets: PropTypes.bool,
     contentInset: EdgeInsetsPropType,
     onNavigationStateChange: PropTypes.func,
     startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
     style: View.propTypes.style,
-    javaScriptEnabledAndroid: PropTypes.bool,
+
+    html: deprecatedPropType(
+      PropTypes.string,
+      'Use the `source` prop instead.'
+    ),
+
+    url: deprecatedPropType(
+      PropTypes.string,
+      'Use the `source` prop instead.'
+    ),
+
+    /**
+     * Loads static html or a uri (with optional headers) in the WebView.
+     */
+    source: PropTypes.oneOfType([
+      PropTypes.shape({
+        /*
+         * The URI to load in the WebView. Can be a local or remote file.
+         */
+        uri: PropTypes.string,
+        /*
+         * The HTTP Method to use. Defaults to GET if not specified.
+         * NOTE: On Android, only GET and POST are supported.
+         */
+        method: PropTypes.oneOf(['GET', 'POST']),
+        /*
+         * Additional HTTP headers to send with the request.
+         * NOTE: On Android, this can only be used with GET requests.
+         */
+        headers: PropTypes.object,
+        /*
+         * The HTTP body to send with the request. This must be a valid
+         * UTF-8 string, and will be sent exactly as specified, with no
+         * additional encoding (e.g. URL-escaping or base64) applied.
+         * NOTE: On Android, this can only be used with POST requests.
+         */
+        body: PropTypes.string,
+      }),
+      PropTypes.shape({
+        /*
+         * A static HTML page to display in the WebView.
+         */
+        html: PropTypes.string,
+        /*
+         * The base URL to be used for any relative links in the HTML.
+         */
+        baseUrl: PropTypes.string,
+      }),
+      /*
+       * Used internally by packager.
+       */
+      PropTypes.number,
+    ]),
+
+    /**
+     * Used on Android only, JS is enabled by default for WebView on iOS
+     * @platform android
+     */
+    javaScriptEnabled: PropTypes.bool,
+
+    /**
+     * Used on Android only, controls whether DOM Storage is enabled or not
+     * @platform android
+     */
+    domStorageEnabled: PropTypes.bool,
 
     /**
      * Sets the JS to be injected when the webpage loads.
@@ -56,10 +123,16 @@ var WebView = React.createClass({
     injectedJavaScript: PropTypes.string,
 
     /**
-     * Sets the user-agent for this WebView. The user-agent can also be set in native through
-     * WebViewConfig, but this can and will overwrite that config.
+     * Sets whether the webpage scales to fit the view and the user can change the scale.
+     */
+    scalesPageToFit: PropTypes.bool,
+
+    /**
+     * Sets the user-agent for this WebView. The user-agent can also be set in native using
+     * WebViewConfig. This prop will overwrite that config.
      */
     userAgent: PropTypes.string,
+
     /**
      * Used to locate this view in end-to-end tests.
      */
@@ -74,6 +147,13 @@ var WebView = React.createClass({
     };
   },
 
+  getDefaultProps: function() {
+    return {
+      javaScriptEnabled : true,
+      scalesPageToFit: true,
+    };
+  },
+
   componentWillMount: function() {
     if (this.props.startInLoadingState) {
       this.setState({viewState: WebViewState.LOADING});
@@ -102,16 +182,30 @@ var WebView = React.createClass({
       webViewStyles.push(styles.hidden);
     }
 
+    var source = this.props.source || {};
+    if (this.props.html) {
+      source.html = this.props.html;
+    } else if (this.props.url) {
+      source.uri = this.props.url;
+    }
+
+    if (source.method === 'POST' && source.headers) {
+      console.warn('WebView: `source.headers` is not supported when using POST.');
+    } else if (source.method === 'GET' && source.body) {
+      console.warn('WebView: `source.body` is not supported when using GET.');
+    }
+
     var webView =
       <RCTWebView
         ref={RCT_WEBVIEW_REF}
         key="webViewKey"
         style={webViewStyles}
-        url={this.props.url}
-        html={this.props.html}
+        source={resolveAssetSource(source)}
+        scalesPageToFit={this.props.scalesPageToFit}
         injectedJavaScript={this.props.injectedJavaScript}
         userAgent={this.props.userAgent}
-        javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid}
+        javaScriptEnabled={this.props.javaScriptEnabled}
+        domStorageEnabled={this.props.domStorageEnabled}
         contentInset={this.props.contentInset}
         automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
         onLoadingStart={this.onLoadingStart}
@@ -129,25 +223,25 @@ var WebView = React.createClass({
   },
 
   goForward: function() {
-    RCTUIManager.dispatchViewManagerCommand(
-      this.getWebWiewHandle(),
-      RCTUIManager.RCTWebView.Commands.goForward,
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.goForward,
       null
     );
   },
 
   goBack: function() {
-    RCTUIManager.dispatchViewManagerCommand(
-      this.getWebWiewHandle(),
-      RCTUIManager.RCTWebView.Commands.goBack,
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.goBack,
       null
     );
   },
 
   reload: function() {
-    RCTUIManager.dispatchViewManagerCommand(
-      this.getWebWiewHandle(),
-      RCTUIManager.RCTWebView.Commands.reload,
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.reload,
       null
     );
   },
@@ -162,16 +256,21 @@ var WebView = React.createClass({
     }
   },
 
-  getWebWiewHandle: function() {
+  getWebViewHandle: function() {
     return React.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
   },
 
   onLoadingStart: function(event) {
+    var onLoadStart = this.props.onLoadStart;
+    onLoadStart && onLoadStart(event);
     this.updateNavigationState(event);
   },
 
   onLoadingError: function(event) {
     event.persist(); // persist this event because we need to store it
+    var {onError, onLoadEnd} = this.props;
+    onError && onError(event);
+    onLoadEnd && onLoadEnd(event);
     console.error('Encountered an error loading page', event.nativeEvent);
 
     this.setState({
@@ -181,6 +280,9 @@ var WebView = React.createClass({
   },
 
   onLoadingFinish: function(event) {
+    var {onLoad, onLoadEnd} = this.props;
+    onLoad && onLoad(event);
+    onLoadEnd && onLoadEnd(event);
     this.setState({
       viewState: WebViewState.IDLE,
     });
diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js
index 5e303ff00b01..9fc11a5c50d5 100644
--- a/Libraries/Components/WebView/WebView.ios.js
+++ b/Libraries/Components/WebView/WebView.ios.js
@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule WebView
- * @flow
+ * @noflow
  */
 'use strict';
 
@@ -16,11 +16,16 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
 var React = require('React');
 var StyleSheet = require('StyleSheet');
 var Text = require('Text');
+var UIManager = require('UIManager');
 var View = require('View');
+var ScrollView = require('ScrollView');
 
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
+var deprecatedPropType = require('deprecatedPropType');
+var invariant = require('fbjs/lib/invariant');
+var keyMirror = require('fbjs/lib/keyMirror');
+var processDecelerationRate = require('processDecelerationRate');
 var requireNativeComponent = require('requireNativeComponent');
+var resolveAssetSource = require('resolveAssetSource');
 
 var PropTypes = React.PropTypes;
 var RCTWebViewManager = require('NativeModules').WebViewManager;
@@ -77,9 +82,6 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
 
 /**
  * Renders a native WebView.
- *
- * Note that WebView is only supported on iOS for now,
- * see https://facebook.github.io/react-native/docs/known-issues.html
  */
 var WebView = React.createClass({
   statics: {
@@ -89,37 +91,148 @@ var WebView = React.createClass({
 
   propTypes: {
     ...View.propTypes,
-    url: PropTypes.string,
-    html: PropTypes.string,
+
+    html: deprecatedPropType(
+      PropTypes.string,
+      'Use the `source` prop instead.'
+    ),
+
+    url: deprecatedPropType(
+      PropTypes.string,
+      'Use the `source` prop instead.'
+    ),
+
+    /**
+     * Loads static html or a uri (with optional headers) in the WebView.
+     */
+    source: PropTypes.oneOfType([
+      PropTypes.shape({
+        /*
+         * The URI to load in the WebView. Can be a local or remote file.
+         */
+        uri: PropTypes.string,
+        /*
+         * The HTTP Method to use. Defaults to GET if not specified.
+         * NOTE: On Android, only GET and POST are supported.
+         */
+        method: PropTypes.string,
+        /*
+         * Additional HTTP headers to send with the request.
+         * NOTE: On Android, this can only be used with GET requests.
+         */
+        headers: PropTypes.object,
+        /*
+         * The HTTP body to send with the request. This must be a valid
+         * UTF-8 string, and will be sent exactly as specified, with no
+         * additional encoding (e.g. URL-escaping or base64) applied.
+         * NOTE: On Android, this can only be used with POST requests.
+         */
+        body: PropTypes.string,
+      }),
+      PropTypes.shape({
+        /*
+         * A static HTML page to display in the WebView.
+         */
+        html: PropTypes.string,
+        /*
+         * The base URL to be used for any relative links in the HTML.
+         */
+        baseUrl: PropTypes.string,
+      }),
+      /*
+       * Used internally by packager.
+       */
+      PropTypes.number,
+    ]),
+
+    /**
+     * Function that returns a view to show if there's an error.
+     */
     renderError: PropTypes.func, // view to show if there's an error
-    renderLoading: PropTypes.func, // loading indicator to show
+    /**
+     * Function that returns a loading indicator.
+     */
+    renderLoading: PropTypes.func,
+    /**
+     * Invoked when load finish
+     */
+    onLoad: PropTypes.func,
+    /**
+     * Invoked when load either succeeds or fails
+     */
+    onLoadEnd: PropTypes.func,
+    /**
+     * Invoked on load start
+     */
+    onLoadStart: PropTypes.func,
+    /**
+     * Invoked when load fails
+     */
+    onError: PropTypes.func,
+    /**
+     * @platform ios
+     */
     bounces: PropTypes.bool,
+    /**
+     * A floating-point number that determines how quickly the scroll view
+     * decelerates after the user lifts their finger. You may also use string
+     * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
+     * for `UIScrollViewDecelerationRateNormal` and
+     * `UIScrollViewDecelerationRateFast` respectively.
+     *   - Normal: 0.998
+     *   - Fast: 0.9 (the default for iOS WebView)
+     * @platform ios
+     */
+    decelerationRate: ScrollView.propTypes.decelerationRate,
+    /**
+     * @platform ios
+     */
     scrollEnabled: PropTypes.bool,
     automaticallyAdjustContentInsets: PropTypes.bool,
     contentInset: EdgeInsetsPropType,
     onNavigationStateChange: PropTypes.func,
     startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
     style: View.propTypes.style,
+
     /**
-     * Used for android only, JS is enabled by default for WebView on iOS
+     * Used on Android only, JS is enabled by default for WebView on iOS
+     * @platform android
      */
-    javaScriptEnabledAndroid: PropTypes.bool,
+    javaScriptEnabled: PropTypes.bool,
+
+    /**
+     * Used on Android only, controls whether DOM Storage is enabled or not
+     * @platform android
+     */
+    domStorageEnabled: PropTypes.bool,
+
     /**
      * Sets the JS to be injected when the webpage loads.
      */
     injectedJavaScript: PropTypes.string,
 
     /**
-     * Used for iOS only, sets whether the webpage scales to fit the view and the
-     * user can change the scale
+     * Sets whether the webpage scales to fit the view and the user can change the scale.
      */
     scalesPageToFit: PropTypes.bool,
 
     /**
      * Allows custom handling of any webview requests by a JS handler. Return true
      * or false from this method to continue loading the request.
+     * @platform ios
      */
     onShouldStartLoadWithRequest: PropTypes.func,
+
+    /**
+     * Determines whether HTML5 videos play inline or use the native full-screen
+     * controller.
+     * default value `false`
+     * **NOTE** : "In order for video to play inline, not only does this
+     * property need to be set to true, but the video element in the HTML
+     * document must also include the webkit-playsinline attribute."
+     * @platform ios
+     */
+    allowsInlineMediaPlayback: PropTypes.bool,
   },
 
   getInitialState: function() {
@@ -171,16 +284,25 @@ var WebView = React.createClass({
       RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
     });
 
+    var decelerationRate = processDecelerationRate(this.props.decelerationRate);
+
+    var source = this.props.source || {};
+    if (this.props.html) {
+      source.html = this.props.html;
+    } else if (this.props.url) {
+      source.uri = this.props.url;
+    }
+
     var webView =
       <RCTWebView
         ref={RCT_WEBVIEW_REF}
         key="webViewKey"
         style={webViewStyles}
-        url={this.props.url}
-        html={this.props.html}
+        source={resolveAssetSource(source)}
         injectedJavaScript={this.props.injectedJavaScript}
         bounces={this.props.bounces}
         scrollEnabled={this.props.scrollEnabled}
+        decelerationRate={decelerationRate}
         contentInset={this.props.contentInset}
         automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
         onLoadingStart={this.onLoadingStart}
@@ -188,6 +310,7 @@ var WebView = React.createClass({
         onLoadingError={this.onLoadingError}
         onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
         scalesPageToFit={this.props.scalesPageToFit}
+        allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
       />;
 
     return (
@@ -199,15 +322,27 @@ var WebView = React.createClass({
   },
 
   goForward: function() {
-    RCTWebViewManager.goForward(this.getWebViewHandle());
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.goForward,
+      null
+    );
   },
 
   goBack: function() {
-    RCTWebViewManager.goBack(this.getWebViewHandle());
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.goBack,
+      null
+    );
   },
 
   reload: function() {
-    RCTWebViewManager.reload(this.getWebViewHandle());
+    UIManager.dispatchViewManagerCommand(
+      this.getWebViewHandle(),
+      UIManager.RCTWebView.Commands.reload,
+      null
+    );
   },
 
   /**
@@ -225,11 +360,16 @@ var WebView = React.createClass({
   },
 
   onLoadingStart: function(event: Event) {
+    var onLoadStart = this.props.onLoadStart;
+    onLoadStart && onLoadStart(event);
     this.updateNavigationState(event);
   },
 
   onLoadingError: function(event: Event) {
     event.persist(); // persist this event because we need to store it
+    var {onError, onLoadEnd} = this.props;
+    onError && onError(event);
+    onLoadEnd && onLoadEnd(event);
     console.warn('Encountered an error loading page', event.nativeEvent);
 
     this.setState({
@@ -239,6 +379,9 @@ var WebView = React.createClass({
   },
 
   onLoadingFinish: function(event: Event) {
+    var {onLoad, onLoadEnd} = this.props;
+    onLoad && onLoad(event);
+    onLoadEnd && onLoadEnd(event);
     this.setState({
       viewState: WebViewState.IDLE,
     });
@@ -283,6 +426,7 @@ var styles = StyleSheet.create({
     flex: 1,
     justifyContent: 'center',
     alignItems: 'center',
+    height: 100,
   },
   webView: {
     backgroundColor: '#ffffff',
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 6ea615ad530b..eb642d73fbec 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -28,7 +28,6 @@
 
 var ListViewDataSource = require('ListViewDataSource');
 var React = require('React');
-var RCTUIManager = require('NativeModules').UIManager;
 var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
 var ScrollView = require('ScrollView');
 var ScrollResponder = require('ScrollResponder');
@@ -36,7 +35,6 @@ var StaticRenderer = require('StaticRenderer');
 var TimerMixin = require('react-timer-mixin');
 
 var isEmpty = require('isEmpty');
-var logError = require('logError');
 var merge = require('merge');
 
 var PropTypes = React.PropTypes;
@@ -117,6 +115,7 @@ var ListView = React.createClass({
     dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired,
     /**
      * (sectionID, rowID, adjacentRowHighlighted) => renderable
+     *
      * If provided, a renderable component to be rendered as the separator
      * below each row but not the last row if there is a section header below.
      * Take a sectionID and rowID of the row above and whether its adjacent row
@@ -125,6 +124,7 @@ var ListView = React.createClass({
     renderSeparator: PropTypes.func,
     /**
      * (rowData, sectionID, rowID, highlightRow) => renderable
+     *
      * Takes a data entry from the data source and its ids and should return
      * a renderable component to be rendered as the row.  By default the data
      * is exactly what was put into the data source, but it's also possible to
@@ -136,7 +136,7 @@ var ListView = React.createClass({
     renderRow: PropTypes.func.isRequired,
     /**
      * How many rows to render on initial component mount.  Use this to make
-     * it so that the first screen worth of data apears at one time instead of
+     * it so that the first screen worth of data appears at one time instead of
      * over the course of multiple frames.
      */
     initialListSize: PropTypes.number,
@@ -147,11 +147,15 @@ var ListView = React.createClass({
      */
     onEndReached: PropTypes.func,
     /**
-     * Threshold in pixels for onEndReached.
+     * Threshold in pixels (virtual, not physical) for calling onEndReached.
      */
     onEndReachedThreshold: PropTypes.number,
     /**
-     * Number of rows to render per event loop.
+     * Number of rows to render per event loop. Note: if your 'rows' are actually
+     * cells, i.e. they don't span the full width of your view (as in the
+     * ListViewGridLayoutExample), you should set the pageSize to be a multiple
+     * of the number of cells per row, otherwise you're likely to see gaps at
+     * the edge of the ListView as new pages are loaded.
      */
     pageSize: PropTypes.number,
     /**
@@ -197,11 +201,20 @@ var ListView = React.createClass({
      */
     onChangeVisibleRows: React.PropTypes.func,
     /**
-     * An experimental performance optimization for improving scroll perf of
+     * A performance optimization for improving scroll perf of
      * large lists, used in conjunction with overflow: 'hidden' on the row
-     * containers.  Use at your own risk.
+     * containers.  This is enabled by default.
      */
     removeClippedSubviews: React.PropTypes.bool,
+    /**
+     * An array of child indices determining which children get docked to the
+     * top of the screen when scrolling. For example, passing
+     * `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
+     * top of the scroll view. This property is not supported in conjunction
+     * with `horizontal={true}`.
+     * @platform ios
+     */
+    stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
   },
 
   /**
@@ -217,8 +230,9 @@ var ListView = React.createClass({
   },
 
   /**
-   * Provides a handle to the underlying scroll responder to support operations
-   * such as scrollTo.
+   * Provides a handle to the underlying scroll responder.
+   * Note that the view in `SCROLLVIEW_REF` may not be a `ScrollView`, so we
+   * need to check that it responds to `getScrollResponder` before calling it.
    */
   getScrollResponder: function() {
     return this.refs[SCROLLVIEW_REF] &&
@@ -226,8 +240,15 @@ var ListView = React.createClass({
       this.refs[SCROLLVIEW_REF].getScrollResponder();
   },
 
+  scrollTo: function(...args) {
+    this.refs[SCROLLVIEW_REF] &&
+      this.refs[SCROLLVIEW_REF].scrollTo &&
+      this.refs[SCROLLVIEW_REF].scrollTo(...args);
+  },
+
   setNativeProps: function(props) {
-    this.refs[SCROLLVIEW_REF].setNativeProps(props);
+    this.refs[SCROLLVIEW_REF] &&
+      this.refs[SCROLLVIEW_REF].setNativeProps(props);
   },
 
   /**
@@ -241,13 +262,13 @@ var ListView = React.createClass({
       renderScrollComponent: props => <ScrollView {...props} />,
       scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD,
       onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD,
+      stickyHeaderIndices: [],
     };
   },
 
   getInitialState: function() {
     return {
       curRenderedRowsCount: this.props.initialListSize,
-      prevRenderedRowsCount: 0,
       highlightedRow: {},
     };
   },
@@ -265,6 +286,8 @@ var ListView = React.createClass({
     };
     this._childFrames = [];
     this._visibleRows = {};
+    this._prevRenderedRowsCount = 0;
+    this._sentEndForContentLength = null;
   },
 
   componentDidMount: function() {
@@ -276,17 +299,20 @@ var ListView = React.createClass({
   },
 
   componentWillReceiveProps: function(nextProps) {
-    if (this.props.dataSource !== nextProps.dataSource) {
+    if (this.props.dataSource !== nextProps.dataSource ||
+        this.props.initialListSize !== nextProps.initialListSize) {
       this.setState((state, props) => {
-        var rowsToRender = Math.min(
-          state.curRenderedRowsCount + props.pageSize,
-          props.dataSource.getRowCount()
-        );
+        this._prevRenderedRowsCount = 0;
         return {
-          prevRenderedRowsCount: 0,
-          curRenderedRowsCount: rowsToRender,
+          curRenderedRowsCount: Math.min(
+            Math.max(
+              state.curRenderedRowsCount,
+              props.initialListSize
+            ),
+            props.dataSource.getRowCount()
+          ),
         };
-      });
+      }, () => this._renderMoreRowsIfNeeded());
     }
   },
 
@@ -320,7 +346,7 @@ var ListView = React.createClass({
       }
 
       if (this.props.renderSectionHeader) {
-        var shouldUpdateHeader = rowCount >= this.state.prevRenderedRowsCount &&
+        var shouldUpdateHeader = rowCount >= this._prevRenderedRowsCount &&
           dataSource.sectionHeaderShouldUpdate(sectionIdx);
         bodyComponents.push(
           <StaticRenderer
@@ -339,7 +365,7 @@ var ListView = React.createClass({
       for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) {
         var rowID = rowIDs[rowIdx];
         var comboID = sectionID + '_' + rowID;
-        var shouldUpdateRow = rowCount >= this.state.prevRenderedRowsCount &&
+        var shouldUpdateRow = rowCount >= this._prevRenderedRowsCount &&
           dataSource.rowShouldUpdate(sectionIdx, rowIdx);
         var row =
           <StaticRenderer
@@ -368,8 +394,10 @@ var ListView = React.createClass({
             rowID,
             adjacentRowHighlighted
           );
-          bodyComponents.push(separator);
-          totalIndex++;
+          if (separator) {
+            bodyComponents.push(separator);
+            totalIndex++;
+          }
         }
         if (++rowCount === this.state.curRenderedRowsCount) {
           break;
@@ -392,7 +420,7 @@ var ListView = React.createClass({
     }
     Object.assign(props, {
       onScroll: this._onScroll,
-      stickyHeaderIndices: sectionHeaderIndices,
+      stickyHeaderIndices: this.props.stickyHeaderIndices.concat(sectionHeaderIndices),
 
       // Do not pass these events downstream to ScrollView since they will be
       // registered in ListView's own ScrollResponder.Mixin
@@ -406,6 +434,8 @@ var ListView = React.createClass({
     // component's original ref instead of clobbering it
     return React.cloneElement(renderScrollComponent(props), {
       ref: SCROLLVIEW_REF,
+      onContentSizeChange: this._onContentSizeChange,
+      onLayout: this._onLayout,
     }, header, bodyComponents, footer);
   },
 
@@ -418,41 +448,35 @@ var ListView = React.createClass({
     if (!scrollComponent || !scrollComponent.getInnerViewNode) {
       return;
     }
-    RCTUIManager.measureLayout(
-      scrollComponent.getInnerViewNode(),
-      React.findNodeHandle(scrollComponent),
-      logError,
-      this._setScrollContentLength
-    );
-    RCTUIManager.measureLayoutRelativeToParent(
-      React.findNodeHandle(scrollComponent),
-      logError,
-      this._setScrollVisibleLength
-    );
 
     // RCTScrollViewManager.calculateChildFrames is not available on
     // every platform
     RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
       RCTScrollViewManager.calculateChildFrames(
         React.findNodeHandle(scrollComponent),
-        this._updateChildFrames,
+        this._updateVisibleRows,
       );
   },
 
-  _setScrollContentLength: function(left, top, width, height) {
-    this.scrollProperties.contentLength = !this.props.horizontal ?
-      height : width;
-  },
-
-  _setScrollVisibleLength: function(left, top, width, height) {
-    this.scrollProperties.visibleLength = !this.props.horizontal ?
-      height : width;
-    this._updateVisibleRows();
-    this._renderMoreRowsIfNeeded();
+  _onContentSizeChange: function(width, height) {
+    var contentLength = !this.props.horizontal ? height : width;
+    if (contentLength !== this.scrollProperties.contentLength) {
+      this.scrollProperties.contentLength = contentLength;
+      this._updateVisibleRows();
+      this._renderMoreRowsIfNeeded();
+    }
+    this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
   },
 
-  _updateChildFrames: function(childFrames) {
-    this._updateVisibleRows(childFrames);
+  _onLayout: function(event) {
+    var {width, height} = event.nativeEvent.layout;
+    var visibleLength = !this.props.horizontal ? height : width;
+    if (visibleLength !== this.scrollProperties.visibleLength) {
+      this.scrollProperties.visibleLength = visibleLength;
+      this._updateVisibleRows();
+      this._renderMoreRowsIfNeeded();
+    }
+    this.props.onLayout && this.props.onLayout(event);
   },
 
   _maybeCallOnEndReached: function(event) {
@@ -487,24 +511,18 @@ var ListView = React.createClass({
         state.curRenderedRowsCount + props.pageSize,
         props.dataSource.getRowCount()
       );
+      this._prevRenderedRowsCount = state.curRenderedRowsCount;
       return {
-        prevRenderedRowsCount: state.curRenderedRowsCount,
         curRenderedRowsCount: rowsToRender
       };
     }, () => {
       this._measureAndUpdateScrollProps();
-      this.setState(state => ({
-        prevRenderedRowsCount: state.curRenderedRowsCount,
-      }));
+      this._prevRenderedRowsCount = this.state.curRenderedRowsCount;
     });
   },
 
   _getDistanceFromEnd: function(scrollProperties) {
-    var maxLength = Math.max(
-      scrollProperties.contentLength,
-      scrollProperties.visibleLength
-    );
-    return maxLength - scrollProperties.visibleLength - scrollProperties.offset;
+    return scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset;
   },
 
   _updateVisibleRows: function(updatedFrames) {
diff --git a/Libraries/CustomComponents/ListView/ListViewDataSource.js b/Libraries/CustomComponents/ListView/ListViewDataSource.js
index 0b1f0ecd426a..1d14e50f5b74 100644
--- a/Libraries/CustomComponents/ListView/ListViewDataSource.js
+++ b/Libraries/CustomComponents/ListView/ListViewDataSource.js
@@ -28,9 +28,9 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var isEmpty = require('isEmpty');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 function defaultGetRowData(
   dataBlob: any,
@@ -141,7 +141,7 @@ class ListViewDataSource {
 
   /**
    * Clones this `ListViewDataSource` with the specified `dataBlob` and
-   * `rowIdentities`. The `dataBlob` is just an aribitrary blob of data. At
+   * `rowIdentities`. The `dataBlob` is just an arbitrary blob of data. At
    * construction an extractor to get the interesting information was defined
    * (or the default was used).
    *
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js
new file mode 100644
index 000000000000..7021e73abcce
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc.  All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software").  Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software").  Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationCard
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+const NavigationRootContainer = require('NavigationRootContainer');
+const NavigationContainer = require('NavigationContainer');
+const PanResponder = require('PanResponder');
+const Platform = require('Platform');
+const React = require('React');
+const StyleSheet = require('StyleSheet');
+const View = require('View');
+
+const ENABLE_GESTURES = Platform.OS !== 'android';
+
+import type {
+  NavigationParentState
+} from 'NavigationStateUtils';
+
+type Layout = {
+  initWidth: number,
+  initHeight: number,
+  width: Animated.Value;
+  height: Animated.Value;
+};
+
+type Props = {
+  navigationState: NavigationParentState;
+  index: number;
+  position: Animated.Value;
+  layout: Layout;
+  onNavigate: Function;
+  children: Object;
+};
+
+class NavigationCard extends React.Component {
+  _responder: ?Object;
+  _lastHeight: number;
+  _lastWidth: number;
+  _widthListener: string;
+  _heightListener: string;
+  props: Props;
+  componentWillMount() {
+    if (ENABLE_GESTURES) {
+      this._enableGestures();
+    }
+  }
+  _enableGestures() {
+    this._responder = PanResponder.create({
+      onMoveShouldSetPanResponder: (e, {dx, dy, moveX, moveY, x0, y0}) => {
+        if (this.props.navigationState.index === 0) {
+          return false;
+        }
+        if (moveX > 30) {
+          return false;
+        }
+        if (dx > 5 && Math.abs(dy) < 4) {
+          return true;
+        }
+        return false;
+      },
+      onPanResponderGrant: (e, {dx, dy, moveX, moveY, x0, y0}) => {
+      },
+      onPanResponderMove: (e, {dx}) => {
+        const a = (-dx / this._lastWidth) + this.props.navigationState.index;
+        this.props.position.setValue(a);
+      },
+      onPanResponderRelease: (e, {vx, dx}) => {
+        const xRatio = dx / this._lastWidth;
+        const doesPop = (xRatio + vx) > 0.45;
+        if (doesPop) {
+          // todo: add an action which accepts velocity of the pop action/gesture, which is caught and used by NavigationAnimatedView
+          this.props.onNavigate(NavigationRootContainer.getBackAction());
+          return;
+        }
+        Animated.spring(this.props.position, {
+          toValue: this.props.navigationState.index,
+        }).start();
+      },
+      onPanResponderTerminate: (e, {vx, dx}) => {
+        Animated.spring(this.props.position, {
+          toValue: this.props.navigationState.index,
+        }).start();
+      },
+    });
+  }
+  componentDidMount() {
+    this._lastHeight = this.props.layout.initHeight;
+    this._lastWidth = this.props.layout.initWidth;
+    this._widthListener = this.props.layout.width.addListener(({value}) => {
+      this._lastWidth = value;
+    });
+    this._heightListener = this.props.layout.height.addListener(({value}) => {
+      this._lastHeight = value;
+    });
+    // todo: fix listener and last layout dimentsions when props change. potential bugs here
+  }
+  componentWillUnmount() {
+    this.props.layout.width.removeListener(this._widthListener);
+    this.props.layout.height.removeListener(this._heightListener);
+  }
+  render() {
+    const cardPosition = Animated.add(this.props.position, new Animated.Value(-this.props.index));
+    const gestureValue = Animated.multiply(cardPosition, this.props.layout.width);
+    const touchResponderHandlers = this._responder ? this._responder.panHandlers : null;
+    return (
+      <Animated.View
+        {...touchResponderHandlers}
+        style={[
+          styles.card,
+          {
+            right: gestureValue,
+            left: gestureValue.interpolate({
+              inputRange: [0, 1],
+              outputRange: [0, -1],
+            }),
+            opacity: cardPosition.interpolate({
+              inputRange: [-1,0,1],
+              outputRange: [0,1,1],
+            }),
+          }
+        ]}>
+        {this.props.children}
+      </Animated.View>
+    );
+  }
+}
+
+NavigationCard = NavigationContainer.create(NavigationCard);
+
+const styles = StyleSheet.create({
+  card: {
+    backgroundColor: '#E9E9EF',
+    shadowColor: 'black',
+    shadowOpacity: 0.4,
+    shadowOffset: {width: 0, height: 0},
+    shadowRadius: 10,
+    top: 0,
+    bottom: 0,
+    position: 'absolute',
+  },
+});
+
+module.exports = NavigationCard;
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
new file mode 100644
index 000000000000..f36fbc0fe325
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
@@ -0,0 +1,147 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc.  All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software").  Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software").  Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationCardStack
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+const NavigationAnimatedView = require('NavigationAnimatedView');
+const NavigationCardStackItem = require('NavigationCardStackItem');
+const NavigationContainer = require('NavigationContainer');
+const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
+const React = require('React');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
+const StyleSheet = require('StyleSheet');
+
+const emptyFunction = require('fbjs/lib/emptyFunction');
+
+const {PropTypes} = React;
+const {Directions} = NavigationLinearPanResponder;
+
+import type {
+  NavigationParentState,
+} from 'NavigationStateUtils';
+
+import type {
+  NavigationStateRenderer,
+  NavigationStateRendererProps,
+  Position,
+  TimingSetter,
+} from 'NavigationAnimatedView';
+
+type Props = {
+  direction: string,
+  navigationState: NavigationParentState,
+  renderOverlay: ?NavigationStateRenderer,
+  renderScene: NavigationStateRenderer,
+};
+
+/**
+ * A controlled navigation view that renders a list of cards.
+ */
+class NavigationCardStack extends React.Component {
+  _renderScene : NavigationStateRenderer;
+  _setTiming: TimingSetter;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+    this._renderScene = this._renderScene.bind(this);
+    this._setTiming = this._setTiming.bind(this);
+  }
+
+  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+      this,
+      nextProps,
+      nextState
+    );
+  }
+
+  render(): ReactElement {
+    return (
+      <NavigationAnimatedView
+        navigationState={this.props.navigationState}
+        renderOverlay={this.props.renderOverlay}
+        renderScene={this._renderScene}
+        setTiming={this._setTiming}
+        style={[styles.animatedView, this.props.style]}
+      />
+    );
+  }
+
+  _renderScene(props: NavigationStateRendererProps): ReactElement {
+    const {
+      index,
+      layout,
+      navigationState,
+      position,
+      navigationParentState,
+    } = props;
+
+    return (
+      <NavigationCardStackItem
+        direction={this.props.direction}
+        index={index}
+        key={navigationState.key}
+        layout={layout}
+        navigationParentState={navigationParentState}
+        navigationState={navigationState}
+        position={position}
+        renderScene={this.props.renderScene}
+      />
+    );
+  }
+
+  _setTiming(position: Position, navigationState: NavigationParentState): void {
+    Animated.timing(
+      position,
+      {
+        duration: 500,
+        toValue: navigationState.index,
+      }
+    ).start();
+  }
+}
+
+NavigationCardStack.propTypes = {
+  direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
+  navigationState: PropTypes.object.isRequired,
+  renderOverlay: PropTypes.func,
+  renderScene: PropTypes.func.isRequired,
+};
+
+NavigationCardStack.defaultProps = {
+  direction: Directions.HORIZONTAL,
+  renderOverlay: emptyFunction.thatReturnsNull,
+};
+
+const styles = StyleSheet.create({
+  animatedView: {
+    flex: 1,
+  },
+});
+
+module.exports = NavigationContainer.create(NavigationCardStack);
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js
new file mode 100644
index 000000000000..4fe0abf4990a
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStackItem.js
@@ -0,0 +1,280 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc.  All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software").  Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software").  Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationCardStackItem
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+const NavigationContainer = require('NavigationContainer');
+const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
+const React = require('React');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
+const StyleSheet = require('StyleSheet');
+const View = require('View');
+
+const {PropTypes} = React;
+const {Directions} = NavigationLinearPanResponder;
+
+import type {
+  NavigationParentState,
+} from 'NavigationStateUtils';
+
+import type {
+  Layout,
+  Position,
+  NavigationStateRenderer,
+} from 'NavigationAnimatedView';
+
+import type {
+  Direction,
+  OnNavigateHandler,
+} from 'NavigationLinearPanResponder';
+
+type AnimatedValue = Animated.Value;
+
+type Props = {
+  direction: Direction,
+  index: number;
+  layout: Layout;
+  navigationParentState: NavigationParentState,
+  navigationState: NavigationParentState,
+  position: Position,
+  onNavigate: ?OnNavigateHandler,
+  renderScene: NavigationStateRenderer,
+};
+
+type State = {
+  hash: string,
+  height: number,
+  width: number,
+};
+
+class AmimatedValueSubscription {
+  _value: AnimatedValue;
+  _token: string;
+
+  constructor(value: AnimatedValue, callback: Function) {
+    this._value = value;
+    this._token = value.addListener(callback);
+  }
+
+  remove() {
+    this._value.removeListener(this._token);
+  }
+}
+
+/**
+ * Class that provides the required information for the
+ * `NavigationLinearPanResponder`. This class must implement
+ * the interface `NavigationLinearPanResponderDelegate`.
+ */
+class PanResponderDelegate {
+  _props : Props;
+
+  constructor(props: Props) {
+    this._props = props;
+  }
+
+  getDirection(): Direction {
+    return this._props.direction;
+  }
+
+  getIndex(): number {
+    return this._props.navigationParentState.index;
+  }
+
+  getLayout(): Layout {
+    return this._props.layout;
+  }
+
+  getPosition(): Position {
+    return this._props.position;
+  }
+
+  onNavigate(action: {type: string}): void {
+    this._props.onNavigate && this._props.onNavigate(action);
+  }
+}
+
+/**
+ * Component that renders the scene as card for the <NavigationCardStack />.
+ */
+class NavigationCardStackItem extends React.Component {
+  props: Props;
+  state: State;
+  _calculateState: (t: Layout) => State;
+  _layoutListeners: Array<AmimatedValueSubscription>;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+
+    this._calculateState = this._calculateState.bind(this);
+    this.state = this._calculateState(props.layout);
+    this._layoutListeners = [];
+  }
+
+  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+      this,
+      nextProps,
+      nextState
+    );
+  }
+
+  componentDidMount(): void {
+    this._applyLayout(this.props.layout);
+  }
+
+  componentWillUnmount(): void {
+    this._layoutListeners.forEach(subscription => subscription.remove);
+  }
+
+  componentWillReceiveProps(nextProps: Props): void {
+    this._applyLayout(nextProps.layout);
+  }
+
+  render(): ReactElement {
+    const {
+      direction,
+      index,
+      navigationParentState,
+      position,
+    } = this.props;
+    const {
+      height,
+      width,
+    } = this.state;
+
+    const isVertical = direction === 'vertical';
+    const inputRange = [index - 1, index, index + 1];
+    const animatedStyle = {
+
+      opacity: position.interpolate({
+        inputRange,
+        outputRange: [1, 1, 0.3],
+      }),
+
+      transform: [
+        {
+          scale: position.interpolate({
+            inputRange,
+            outputRange: [1, 1, 0.95],
+          }),
+        },
+        {
+          translateX: isVertical ? 0 :
+            position.interpolate({
+              inputRange,
+              outputRange: [width, 0, -10],
+            }),
+        },
+        {
+          translateY: !isVertical ? 0 :
+            position.interpolate({
+              inputRange,
+              outputRange: [height, 0, -10],
+            }),
+        },
+      ],
+    };
+
+    let panHandlers = null;
+    if (navigationParentState.index === index) {
+      const delegate = new PanResponderDelegate(this.props);
+      const panResponder = new NavigationLinearPanResponder(delegate);
+      panHandlers = panResponder.panHandlers;
+    }
+
+    return (
+      <Animated.View
+        {...panHandlers}
+        style={[styles.main, animatedStyle]}>
+        {this.props.renderScene(this.props)}
+      </Animated.View>
+    );
+  }
+
+  _calculateState(layout: Layout): State {
+    const width = layout.width.__getValue();
+    const height = layout.height.__getValue();
+    const hash = 'layout-' + width + '-' + height;
+    const state = {
+      height,
+      width,
+      hash,
+    };
+    return state;
+  }
+
+  _applyLayout(layout: Layout) {
+    this._layoutListeners.forEach(subscription => subscription.remove);
+
+    this._layoutListeners.length = 0;
+
+    const callback = this._applyLayout.bind(this, layout);
+
+    this._layoutListeners.push(
+      new AmimatedValueSubscription(layout.width, callback),
+      new AmimatedValueSubscription(layout.height, callback),
+    );
+
+    const nextState = this._calculateState(layout);
+    if (nextState.hash !== this.state.hash) {
+      this.setState(nextState);
+    }
+  }
+}
+
+NavigationCardStackItem.propTypes = {
+  direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
+  index: PropTypes.number.isRequired,
+  layout: PropTypes.object.isRequired,
+  navigationState: PropTypes.object.isRequired,
+  navigationParentState: PropTypes.object.isRequired,
+  position: PropTypes.object.isRequired,
+  renderScene: PropTypes.func.isRequired,
+};
+
+NavigationCardStackItem.defaultProps = {
+  direction: Directions.HORIZONTAL,
+};
+
+const styles = StyleSheet.create({
+  main: {
+    backgroundColor: '#E9E9EF',
+    bottom: 0,
+    left: 0,
+    position: 'absolute',
+    right: 0,
+    shadowColor: 'black',
+    shadowOffset: {width: 0, height: 0},
+    shadowOpacity: 0.4,
+    shadowRadius: 10,
+    top: 0,
+  },
+});
+
+module.exports = NavigationContainer.create(NavigationCardStackItem);
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js
new file mode 100644
index 000000000000..82a807e2f27d
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc.  All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software").  Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software").  Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationHeader
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+const Image = require('Image');
+const NavigationContainer = require('NavigationContainer');
+const NavigationRootContainer = require('NavigationRootContainer');
+const React = require('react-native');
+const StyleSheet = require('StyleSheet');
+const Text = require('Text');
+const TouchableOpacity = require('TouchableOpacity');
+const View = require('View');
+
+import type {
+  NavigationState,
+  NavigationParentState
+} from 'NavigationStateUtils';
+
+type Props = {
+  navigationState: NavigationParentState,
+  onNavigate: Function,
+  position: Animated.Value,
+  getTitle: (navState: NavigationState) => string,
+};
+
+class NavigationHeader extends React.Component {
+  _handleBackPress: Function;
+  props: Props;
+  componentWillMount() {
+    this._handleBackPress = this._handleBackPress.bind(this);
+  }
+  render() {
+    var state = this.props.navigationState;
+    return (
+      <Animated.View
+        style={[
+          styles.header,
+        ]}>
+        {state.children.map(this._renderTitle, this)}
+        {this._renderBackButton()}
+      </Animated.View>
+    );
+  }
+  _renderBackButton() {
+    if (this.props.navigationState.index === 0) {
+      return null;
+    }
+    return (
+      <TouchableOpacity style={styles.backButton} onPress={this._handleBackPress}>
+        <Image source={require('./back_chevron.png')} style={styles.backButtonImage} />
+      </TouchableOpacity>
+    );
+  }
+  _renderTitle(childState, index) {
+    return (
+      <Animated.Text
+        key={childState.key}
+        style={[
+          styles.title,
+          {
+            opacity: this.props.position.interpolate({
+              inputRange: [index - 1, index, index + 1],
+              outputRange: [0, 1, 0],
+            }),
+            left: this.props.position.interpolate({
+              inputRange: [index - 1, index + 1],
+              outputRange: [200, -200],
+            }),
+            right: this.props.position.interpolate({
+              inputRange: [index - 1, index + 1],
+              outputRange: [-200, 200],
+            }),
+          },
+        ]}>
+        {this.props.getTitle(childState)}
+      </Animated.Text>
+    );
+  }
+  _handleBackPress() {
+    this.props.onNavigate(NavigationRootContainer.getBackAction());
+  }
+}
+
+NavigationHeader = NavigationContainer.create(NavigationHeader);
+
+const styles = StyleSheet.create({
+  title: {
+    textAlign: 'center',
+    marginTop: 10,
+    fontSize: 18,
+    fontWeight: '500',
+    color: '#0A0A0A',
+    position: 'absolute',
+    top: 20,
+    left: 0,
+    right: 0,
+  },
+  header: {
+    backgroundColor: '#EFEFF2',
+    paddingTop: 20,
+    top: 0,
+    height: 64,
+    right: 0,
+    left: 0,
+    borderBottomWidth: 0.5,
+    borderBottomColor: '#828287',
+    position: 'absolute',
+  },
+  backButton: {
+    width: 29,
+    height: 37,
+    position: 'absolute',
+    bottom: 4,
+    left: 2,
+    padding: 8,
+  },
+  backButtonImage: {
+    width: 13,
+    height: 21,
+  },
+});
+
+module.exports = NavigationHeader;
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js
new file mode 100644
index 000000000000..a03bd9f4a797
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2015, Facebook, Inc.  All rights reserved.
+ *
+ * Facebook, Inc. ("Facebook") owns all right, title and interest, including
+ * all intellectual property and other proprietary rights, in and to the React
+ * Native CustomComponents software (the "Software").  Subject to your
+ * compliance with these terms, you are hereby granted a non-exclusive,
+ * worldwide, royalty-free copyright license to (1) use and copy the Software;
+ * and (2) reproduce and distribute the Software as part of your own software
+ * ("Your Software").  Facebook reserves all rights not expressly granted to
+ * you in this license agreement.
+ *
+ * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
+ * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
+ * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @providesModule NavigationLegacyNavigator
+ * @flow
+ */
+'use strict';
+
+/**
+ * NavigationLegacyNavigator is meant to replace Navigator seemlessly without
+ * API changes. While the APIs remain compatible with Navigator, it should
+ * be built with the new Navigation API such as `NavigationAnimatedView`...etc.
+ */
+const NavigationLegacyNavigator = require('Navigator');
+
+module.exports = NavigationLegacyNavigator;
diff --git a/Libraries/CustomComponents/NavigationExperimental/back_chevron.png b/Libraries/CustomComponents/NavigationExperimental/back_chevron.png
new file mode 100644
index 000000000000..af21cf9d5718
Binary files /dev/null and b/Libraries/CustomComponents/NavigationExperimental/back_chevron.png differ
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
index 5a0c35f37e07..406e8be2fc63 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
@@ -23,6 +23,7 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  * @providesModule NavigationContext
+ * @noflow
  */
 'use strict';
 
@@ -32,10 +33,10 @@ var NavigationTreeNode = require('NavigationTreeNode');
 
 var Set = require('Set');
 
-var emptyFunction = require('emptyFunction');
-var invariant = require('invariant');
+var emptyFunction = require('fbjs/lib/emptyFunction');
+var invariant = require('fbjs/lib/invariant');
 
-import type * as EventSubscription from 'EventSubscription';
+import type EventSubscription from 'EventSubscription';
 
 var {
   AT_TARGET,
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
index 6e27f5d5e708..5307e1f869c7 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
@@ -27,7 +27,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 class NavigationEventPool {
   _list: Array<any>;
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
index a2abd3d55bdf..339c95734038 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js
@@ -59,7 +59,7 @@ class NavigationEventEmitter extends EventEmitter {
       // An event cycle that was previously created hasn't finished yet.
       // Put this event cycle into the queue and will finish them later.
       var args: any = Array.prototype.slice.call(arguments);
-      this._emitQueue.unshift(args);
+      this._emitQueue.push(args);
       return;
     }
 
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
index 16a95b55f9b7..fd0d93222ab7 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationRouteStack.js
@@ -7,7 +7,7 @@
 'use strict';
 
 var immutable = require('immutable');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 type IterationCallback = (route: any, index: number, key: string) => void;
 
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js
index b100ab5e43e3..5fdb573f223f 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationTreeNode.js
@@ -8,7 +8,7 @@
 
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var immutable = require('immutable');
 
 var {List} = immutable;
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js
index c3fc0f5ef6cc..81fb1695ef13 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEvent-test.js
@@ -27,7 +27,7 @@
 
 jest
   .dontMock('NavigationEvent')
-  .dontMock('invariant');
+  .dontMock('fbjs/lib/invariant');
 
 var NavigationEvent = require('NavigationEvent');
 
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
index 361892084e53..9e9aef476807 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js
@@ -26,6 +26,7 @@
 
 jest
   .dontMock('EmitterSubscription')
+  .dontMock('EventSubscription')
   .dontMock('EventEmitter')
   .dontMock('EventSubscriptionVendor')
   .dontMock('NavigationEvent')
@@ -99,6 +100,32 @@ describe('NavigationEventEmitter', () => {
     expect(logs).toEqual([1, 2, 3, 4, 5]);
   });
 
+  it('puts nested emit call in a queue should be in sequence order', () => {
+    var context = {};
+    var emitter = new NavigationEventEmitter(context);
+    var logs = [];
+
+    emitter.addListener('one', () => {
+      logs.push(1);
+      emitter.emit('two');
+      emitter.emit('three');
+      logs.push(2);
+    });
+
+    emitter.addListener('two', () => {
+      logs.push(3);
+      logs.push(4);
+    });
+
+    emitter.addListener('three', () => {
+      logs.push(5);
+    });
+
+    emitter.emit('one');
+
+    expect(logs).toEqual([1, 2, 3, 4, 5]);
+  });
+
   it('calls callback after emitting', () => {
     var context = {};
     var emitter = new NavigationEventEmitter(context);
diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js
index e89da6a80d29..68de370caf75 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js
@@ -5,9 +5,8 @@
 'use strict';
 
 jest
-  .dontMock('Map')
   .dontMock('NavigationTreeNode')
-  .dontMock('invariant')
+  .dontMock('fbjs/lib/invariant')
   .dontMock('immutable');
 
 var NavigationTreeNode = require('NavigationTreeNode');
diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js
index 8bfd55457a3d..e5a8a3a846b4 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -30,7 +30,6 @@
 var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
 var Dimensions = require('Dimensions');
 var InteractionMixin = require('InteractionMixin');
-var Map = require('Map');
 var NavigationContext = require('NavigationContext');
 var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
 var NavigatorNavigationBar = require('NavigatorNavigationBar');
@@ -43,14 +42,15 @@ var TimerMixin = require('react-timer-mixin');
 var View = require('View');
 
 var clamp = require('clamp');
+var deprecatedPropType = require('deprecatedPropType');
 var flattenStyle = require('flattenStyle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var rebound = require('rebound');
 
 var PropTypes = React.PropTypes;
 
 // TODO: this is not ideal because there is no guarantee that the navigator
-// is full screen, hwoever we don't have a good way to measure the actual
+// is full screen, however we don't have a good way to measure the actual
 // size of the navigator right now, so this is the next best thing.
 var SCREEN_WIDTH = Dimensions.get('window').width;
 var SCREEN_HEIGHT = Dimensions.get('window').height;
@@ -175,6 +175,7 @@ var GESTURE_ACTIONS = [
  *  - `replace(route)` - Replace the current scene with a new route
  *  - `replaceAtIndex(route, index)` - Replace a scene as specified by an index
  *  - `replacePrevious(route)` - Replace the previous scene
+ *  - `resetTo(route)` - Navigate to a new scene and reset route stack
  *  - `immediatelyResetRouteStack(routeStack)` - Reset every scene with an
  *     array of routes
  *  - `popToRoute(route)` - Pop to a particular scene, as specified by its
@@ -188,11 +189,11 @@ var Navigator = React.createClass({
   propTypes: {
     /**
      * Optional function that allows configuration about scene animations and
-     * gestures. Will be invoked with the route and should return a scene
-     * configuration object
+     * gestures. Will be invoked with the route and the routeStack and should
+     * return a scene configuration object
      *
      * ```
-     * (route) => Navigator.SceneConfigs.FloatFromRight
+     * (route, routeStack) => Navigator.SceneConfigs.FloatFromRight
      * ```
      */
     configureScene: PropTypes.func,
@@ -203,7 +204,7 @@ var Navigator = React.createClass({
      *
      * ```
      * (route, navigator) =>
-     *   <MySceneComponent title={route.title} />
+     *   <MySceneComponent title={route.title} navigator={navigator} />
      * ```
      */
     renderScene: PropTypes.func.isRequired,
@@ -224,17 +225,11 @@ var Navigator = React.createClass({
     initialRouteStack: PropTypes.arrayOf(PropTypes.object),
 
     /**
-     * @deprecated
-     * Use `navigationContext.addListener('willfocus', callback)` instead.
-     *
      * Will emit the target route upon mounting and before each nav transition
      */
     onWillFocus: PropTypes.func,
 
     /**
-     * @deprecated
-     * Use `navigationContext.addListener('didfocus', callback)` instead.
-     *
      * Will be called with the new route of each scene after the transition is
      * complete or after the initial mounting
      */
@@ -292,7 +287,7 @@ var Navigator = React.createClass({
     }
     return {
       sceneConfigStack: routeStack.map(
-        (route) => this.props.configureScene(route)
+        (route) => this.props.configureScene(route, routeStack)
       ),
       routeStack,
       presentedIndex: initialRouteIndex,
@@ -329,7 +324,6 @@ var Navigator = React.createClass({
     });
     this.panGesture = PanResponder.create({
       onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
-      onPanResponderGrant: this._handlePanResponderGrant,
       onPanResponderRelease: this._handlePanResponderRelease,
       onPanResponderMove: this._handlePanResponderMove,
       onPanResponderTerminate: this._handlePanResponderTerminate,
@@ -368,7 +362,7 @@ var Navigator = React.createClass({
     this.setState({
       routeStack: nextRouteStack,
       sceneConfigStack: nextRouteStack.map(
-        this.props.configureScene
+        route => this.props.configureScene(route, nextRouteStack)
       ),
       presentedIndex: destIndex,
       activeGesture: null,
@@ -376,6 +370,7 @@ var Navigator = React.createClass({
       transitionQueue: [],
     }, () => {
       this._handleSpringUpdate();
+      this._navBar && this._navBar.immediatelyRefresh();
     });
   },
 
@@ -609,7 +604,8 @@ var Navigator = React.createClass({
     if (!sceneConfig) {
       return false;
     }
-    this._expectingGestureGrant = this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState);
+    this._expectingGestureGrant =
+      this._matchGestureAction(this._eligibleGestures, sceneConfig.gestures, gestureState);
     return !!this._expectingGestureGrant;
   },
 
@@ -621,16 +617,6 @@ var Navigator = React.createClass({
     return wouldOverswipeForward || wouldOverswipeBack;
   },
 
-  _handlePanResponderGrant: function(e, gestureState) {
-    invariant(
-      this._expectingGestureGrant,
-      'Responder granted unexpectedly.'
-    );
-    this._attachGesture(this._expectingGestureGrant);
-    this._onAnimationStart();
-    this._expectingGestureGrant = null;
-  },
-
   _deltaForGestureAction: function(gestureAction) {
     switch (gestureAction) {
       case 'pop':
@@ -685,7 +671,7 @@ var Navigator = React.createClass({
         this.state.presentedIndex = destIndex;
         this._transitionTo(
           transitionBackToPresentedIndex,
-          - transitionVelocity,
+          -transitionVelocity,
           1 - this.spring.getCurrentValue()
         );
       }
@@ -735,6 +721,16 @@ var Navigator = React.createClass({
   },
 
   _handlePanResponderMove: function(e, gestureState) {
+    if (this._isMoveGestureAttached !== undefined) {
+      invariant(
+        this._expectingGestureGrant,
+        'Responder granted unexpectedly.'
+      );
+      this._attachGesture(this._expectingGestureGrant);
+      this._onAnimationStart();
+      this._expectingGestureGrant = undefined;
+    }
+
     var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
     if (this.state.activeGesture) {
       var gesture = sceneConfig.gestures[this.state.activeGesture];
@@ -750,7 +746,7 @@ var Navigator = React.createClass({
     var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
     var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
     var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
-    distance = isTravelInverted ? - distance : distance;
+    distance = isTravelInverted ? -distance : distance;
     var gestureDetectMovement = gesture.gestureDetectMovement;
     var nextProgress = (distance - gestureDetectMovement) /
       (gesture.fullDistance - gestureDetectMovement);
@@ -780,7 +776,7 @@ var Navigator = React.createClass({
   },
 
   _matchGestureAction: function(eligibleGestures, gestures, gestureState) {
-    if (!gestures) {
+    if (!gestures || !eligibleGestures || !eligibleGestures.some) {
       return null;
     }
     var matchedGesture = null;
@@ -825,7 +821,7 @@ var Navigator = React.createClass({
         this._eligibleGestures = this._eligibleGestures.slice().splice(gestureIndex, 1);
       }
     });
-    return matchedGesture;
+    return matchedGesture || null;
   },
 
   _transitionSceneStyle: function(fromIndex, toIndex, progress, index) {
@@ -911,7 +907,7 @@ var Navigator = React.createClass({
     var nextStack = activeStack.concat([route]);
     var destIndex = nextStack.length - 1;
     var nextAnimationConfigStack = activeAnimationConfigStack.concat([
-      this.props.configureScene(route),
+      this.props.configureScene(route, nextStack),
     ]);
     this._emitWillFocus(nextStack[destIndex]);
     this.setState({
@@ -979,7 +975,7 @@ var Navigator = React.createClass({
     var nextRouteStack = this.state.routeStack.slice();
     var nextAnimationModeStack = this.state.sceneConfigStack.slice();
     nextRouteStack[index] = route;
-    nextAnimationModeStack[index] = this.props.configureScene(route);
+    nextAnimationModeStack[index] = this.props.configureScene(route, nextRouteStack);
 
     if (index === this.state.presentedIndex) {
       this._emitWillFocus(route);
@@ -1083,11 +1079,17 @@ var Navigator = React.createClass({
   },
 
   _renderNavigationBar: function() {
-    if (!this.props.navigationBar) {
+    let { navigationBar } = this.props;
+    if (!navigationBar) {
       return null;
     }
-    return React.cloneElement(this.props.navigationBar, {
-      ref: (navBar) => { this._navBar = navBar; },
+    return React.cloneElement(navigationBar, {
+      ref: (navBar) => {
+        this._navBar = navBar;
+        if (navigationBar && typeof navigationBar.ref === 'function') {
+          navigationBar.ref(navBar);
+        }
+      },
       navigator: this._navigationBarNavigator,
       navState: this.state,
     });
diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
index b205327afa18..6ec0a546d7be 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
@@ -36,7 +36,8 @@ var View = require('View');
 
 var { Map } = require('immutable');
 
-var invariant = require('invariant');
+var guid = require('guid');
+var invariant = require('fbjs/lib/invariant');
 
 var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
 var NavigatorNavigationBarStyles = Platform.OS === 'android' ?
@@ -46,11 +47,11 @@ var PropTypes = React.PropTypes;
 /**
  * Reusable props objects.
  */
-var CRUMB_PROPS = Interpolators.map(() => {return {style: {}};});
-var ICON_PROPS = Interpolators.map(() => {return {style: {}};});
-var SEPARATOR_PROPS = Interpolators.map(() => {return {style: {}};});
-var TITLE_PROPS = Interpolators.map(() => {return {style: {}};});
-var RIGHT_BUTTON_PROPS = Interpolators.map(() => {return {style: {}};});
+var CRUMB_PROPS = Interpolators.map(() => ({style: {}}));
+var ICON_PROPS = Interpolators.map(() => ({style: {}}));
+var SEPARATOR_PROPS = Interpolators.map(() => ({style: {}}));
+var TITLE_PROPS = Interpolators.map(() => ({style: {}}));
+var RIGHT_BUTTON_PROPS = Interpolators.map(() => ({style: {}}));
 
 
 var navStatePresentedIndex = function(navState) {
@@ -134,9 +135,13 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
       this._setPropsIfExists('title_' + index, TITLE_PROPS[index]);
     }
     var right = this.refs['right_' + index];
-    if (right &&
-        interpolate.RightItem(RIGHT_BUTTON_PROPS[index].style, amount)) {
-      right.setNativeProps(RIGHT_BUTTON_PROPS[index]);
+
+    const rightButtonStyle = RIGHT_BUTTON_PROPS[index].style;
+    if (right && interpolate.RightItem(rightButtonStyle, amount)) {
+      right.setNativeProps({
+        style: rightButtonStyle,
+        pointerEvents: rightButtonStyle.opacity === 0 ? 'none' : 'auto',
+      });
     }
   },
 
@@ -175,11 +180,7 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
   },
 
   componentWillMount: function() {
-    this._descriptors = {
-      crumb: new Map(),
-      title: new Map(),
-      right: new Map(),
-    };
+    this._reset();
   },
 
   render: function() {
@@ -188,7 +189,9 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     var titles = navState.routeStack.map(this._getTitle);
     var buttons = navState.routeStack.map(this._getRightButton);
     return (
-      <View style={[styles.breadCrumbContainer, this.props.style]}>
+      <View
+        key={this._key}
+        style={[styles.breadCrumbContainer, this.props.style]}>
         {titles}
         {icons}
         {buttons}
@@ -196,6 +199,20 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     );
   },
 
+  immediatelyRefresh: function() {
+    this._reset();
+    this.forceUpdate();
+  },
+
+  _reset() {
+    this._key = guid();
+    this._descriptors = {
+      crumb: new Map(),
+      title: new Map(),
+      right: new Map(),
+    };
+  },
+
   _getBreadcrumb: function(route, index) {
     if (this._descriptors.crumb.has(route)) {
       return this._descriptors.crumb.get(route);
@@ -205,7 +222,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
 
     var breadcrumbDescriptor = (
-      <View ref={'crumb_' + index} style={firstStyles.Crumb}>
+      <View
+        key={'crumb_' + index}
+        ref={'crumb_' + index}
+        style={firstStyles.Crumb}>
         <View ref={'icon_' + index} style={firstStyles.Icon}>
           {navBarRouteMapper.iconForRoute(route, this.props.navigator)}
         </View>
@@ -231,7 +251,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
 
     var titleDescriptor = (
-      <View ref={'title_' + index} style={firstStyles.Title}>
+      <View
+        key={'title_' + index}
+        ref={'title_' + index}
+        style={firstStyles.Title}>
         {titleContent}
       </View>
     );
@@ -253,7 +276,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
     }
     var firstStyles = initStyle(index, navStatePresentedIndex(this.props.navState));
     var rightButtonDescriptor = (
-      <View ref={'right_' + index} style={firstStyles.RightItem}>
+      <View
+        key={'right_' + index}
+        ref={'right_' + index}
+        style={firstStyles.RightItem}>
         {rightContent}
       </View>
     );
diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js
index d73cb5661661..eea15eee9bec 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBarStyles.ios.js
@@ -136,7 +136,7 @@ CENTER[0] = {
   Title: merge(FIRST_TITLE_BASE, {opacity: 1}),
   RightItem: CENTER[0].RightItem,
 };
-LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: - SCREEN_WIDTH / 4, opacity: 0});
+LEFT[0].Title = merge(FIRST_TITLE_BASE, {left: -SCREEN_WIDTH / 4, opacity: 0});
 RIGHT[0].Title = merge(FIRST_TITLE_BASE, {opacity: 0});
 
 
diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
index 2b6c79def612..dd4a0e10b8ab 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBar.js
@@ -30,10 +30,11 @@ var React = require('React');
 var NavigatorNavigationBarStylesAndroid = require('NavigatorNavigationBarStylesAndroid');
 var NavigatorNavigationBarStylesIOS = require('NavigatorNavigationBarStylesIOS');
 var Platform = require('Platform');
-var StaticContainer = require('StaticContainer.react');
 var StyleSheet = require('StyleSheet');
 var View = require('View');
 
+var guid = require('guid');
+
 var { Map } = require('immutable');
 
 var COMPONENT_NAMES = ['Title', 'LeftButton', 'RightButton'];
@@ -79,6 +80,21 @@ var NavigatorNavigationBar = React.createClass({
   },
 
   componentWillMount: function() {
+    this._reset();
+  },
+
+  /**
+   * Stop transtion, immediately resets the cached state and re-render the
+   * whole view.
+   */
+  immediatelyRefresh() {
+    this._reset();
+    this.forceUpdate();
+  },
+
+  _reset() {
+    this._key = guid();
+    this._reusableProps = {};
     this._components = {};
     this._descriptors = {};
 
@@ -92,9 +108,6 @@ var NavigatorNavigationBar = React.createClass({
     /*string*/componentName,
     /*number*/index
   ) /*object*/ {
-    if (!this._reusableProps) {
-      this._reusableProps = {};
-    }
     var propStack = this._reusableProps[componentName];
     if (!propStack) {
       propStack = this._reusableProps[componentName] = [];
@@ -154,14 +167,16 @@ var NavigatorNavigationBar = React.createClass({
       height: this.props.navigationStyles.General.TotalNavHeight,
     };
     var navState = this.props.navState;
-    var components = COMPONENT_NAMES.map(function (componentName) {
-      return navState.routeStack.map(
-        this._getComponent.bind(this, componentName)
-      );
-    }, this);
+    var components = navState.routeStack.map((route, index) =>
+      COMPONENT_NAMES.map(componentName =>
+        this._getComponent(componentName, route, index)
+      )
+    );
 
     return (
-      <View style={[styles.navBarContainer, navBarStyle, this.props.style]}>
+      <View
+        key={this._key}
+        style={[styles.navBarContainer, navBarStyle, this.props.style]}>
         {components}
       </View>
     );
@@ -196,6 +211,7 @@ var NavigatorNavigationBar = React.createClass({
         ref={(ref) => {
           this._components[componentName] = this._components[componentName].set(route, ref);
         }}
+        pointerEvents="box-none"
         style={initialStage[componentName]}>
         {content}
       </View>
diff --git a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js
index fdad315adc81..1ae2a94eaa0e 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorNavigationBarStylesIOS.js
@@ -76,19 +76,19 @@ var BASE_STYLES = {
 // center-to-left transition on the current navigation item.
 var Stages = {
   Left: {
-    Title: merge(BASE_STYLES.Title, { left: - SCREEN_WIDTH / 2, opacity: 0 }),
-    LeftButton: merge(BASE_STYLES.LeftButton, { left: - SCREEN_WIDTH / 3, opacity: 0 }),
-    RightButton: merge(BASE_STYLES.RightButton, { left: SCREEN_WIDTH / 3, opacity: 0 }),
+    Title: merge(BASE_STYLES.Title, { left: -SCREEN_WIDTH / 2, opacity: 0 }),
+    LeftButton: merge(BASE_STYLES.LeftButton, { left: -SCREEN_WIDTH / 3, opacity: 0 }),
+    RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }),
   },
   Center: {
     Title: merge(BASE_STYLES.Title, { left: 0, opacity: 1 }),
     LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 1 }),
-    RightButton: merge(BASE_STYLES.RightButton, { left: 2 * SCREEN_WIDTH / 3 - 0, opacity: 1 }),
+    RightButton: merge(BASE_STYLES.RightButton, { opacity: 1 }),
   },
   Right: {
     Title: merge(BASE_STYLES.Title, { left: SCREEN_WIDTH / 2, opacity: 0 }),
     LeftButton: merge(BASE_STYLES.LeftButton, { left: 0, opacity: 0 }),
-    RightButton: merge(BASE_STYLES.RightButton, { left: SCREEN_WIDTH, opacity: 0 }),
+    RightButton: merge(BASE_STYLES.RightButton, { opacity: 0 }),
   },
 };
 
diff --git a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
index 32cd3363a2d3..4281fb194ae8 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js
@@ -515,6 +515,9 @@ var NavigatorSceneConfigs = {
   },
   FloatFromLeft: {
     ...BaseConfig,
+    gestures: {
+      pop: BaseRightToLeftGesture,
+    },
     animationInterpolators: {
       into: buildStyleInterpolator(FromTheLeft),
       out: buildStyleInterpolator(FadeToTheRight),
@@ -574,6 +577,28 @@ var NavigatorSceneConfigs = {
       out: buildStyleInterpolator(ToTheLeft),
     },
   },
+  HorizontalSwipeJumpFromRight: {
+    ...BaseConfig,
+    gestures: {
+      jumpBack: {
+        ...BaseRightToLeftGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+      jumpForward: {
+        ...BaseLeftToRightGesture,
+        overswipe: BaseOverswipeConfig,
+        edgeHitWidth: null,
+        isDetachable: true,
+      },
+      pop: BaseRightToLeftGesture,
+    },
+    animationInterpolators: {
+      into: buildStyleInterpolator(FromTheLeft),
+      out: buildStyleInterpolator(FadeToTheRight),
+    },
+  },
   VerticalUpSwipeJump: {
     ...BaseConfig,
     gestures: {
diff --git a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js
index 4ee5a1f03976..d7462bf23b62 100644
--- a/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js
+++ b/Libraries/DebugComponentHierarchy/RCTDebugComponentOwnership.js
@@ -15,6 +15,7 @@
 
 'use strict';
 
+var BatchedBridge = require('BatchedBridge');
 var DebugComponentOwnershipModule = require('NativeModules').DebugComponentOwnershipModule;
 var InspectorUtils = require('InspectorUtils');
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
@@ -35,7 +36,7 @@ function getRootTagForTag(tag: number): ?number {
   return ReactNativeTagHandles.rootNodeIDToTag[rootID];
 }
 
-module.exports = {
+var RCTDebugComponentOwnership = {
 
   /**
    * Asynchronously returns the owner hierarchy as an array of strings. Request id is
@@ -53,3 +54,10 @@ module.exports = {
     DebugComponentOwnershipModule.receiveOwnershipHierarchy(requestID, tag, ownerHierarchy);
   },
 };
+
+BatchedBridge.registerCallableModule(
+  'RCTDebugComponentOwnership',
+  RCTDebugComponentOwnership
+);
+
+module.exports = RCTDebugComponentOwnership;
diff --git a/Libraries/Device/RCTDeviceEventEmitter.js b/Libraries/Device/RCTDeviceEventEmitter.js
index 586663e343a7..41e4d408f4f2 100644
--- a/Libraries/Device/RCTDeviceEventEmitter.js
+++ b/Libraries/Device/RCTDeviceEventEmitter.js
@@ -12,7 +12,13 @@
 'use strict';
 
 var EventEmitter = require('EventEmitter');
+var BatchedBridge = require('BatchedBridge');
 
 var RCTDeviceEventEmitter = new EventEmitter();
 
+BatchedBridge.registerCallableModule(
+  'RCTDeviceEventEmitter',
+  RCTDeviceEventEmitter
+);
+
 module.exports = RCTDeviceEventEmitter;
diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js
index b6752e3f08c2..3ed401a5d37c 100644
--- a/Libraries/Geolocation/Geolocation.js
+++ b/Libraries/Geolocation/Geolocation.js
@@ -14,9 +14,9 @@
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 var RCTLocationObserver = require('NativeModules').LocationObserver;
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var logError = require('logError');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var subscriptions = [];
 
@@ -26,6 +26,7 @@ type GeoOptions = {
   timeout: number;
   maximumAge: number;
   enableHighAccuracy: bool;
+  distanceFilter: number;
 }
 
 /**
@@ -42,6 +43,7 @@ type GeoOptions = {
  * app's `AndroidManifest.xml`:
  *
  * `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
+ *
  */
 var Geolocation = {
 
@@ -67,7 +69,7 @@ var Geolocation = {
 
   /*
    * Invokes the success callback whenever the location changes.  Supported
-   * options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
+   * options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool), distanceFilter(m)
    */
   watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
     if (!updatesEnabled) {
diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m
index ab02bd5824fd..080cc9f4eab3 100644
--- a/Libraries/Geolocation/RCTLocationObserver.m
+++ b/Libraries/Geolocation/RCTLocationObserver.m
@@ -31,6 +31,7 @@ typedef NS_ENUM(NSInteger, RCTPositionErrorCode) {
   double timeout;
   double maximumAge;
   double accuracy;
+  double distanceFilter;
 } RCTLocationOptions;
 
 @implementation RCTConvert (RCTLocationOptions)
@@ -38,10 +39,15 @@ @implementation RCTConvert (RCTLocationOptions)
 + (RCTLocationOptions)RCTLocationOptions:(id)json
 {
   NSDictionary<NSString *, id> *options = [RCTConvert NSDictionary:json];
+
+  double distanceFilter = options[@"distanceFilter"] == NULL ? RCT_DEFAULT_LOCATION_ACCURACY
+    : [RCTConvert double:options[@"distanceFilter"]] ?: kCLDistanceFilterNone;
+
   return (RCTLocationOptions){
     .timeout = [RCTConvert NSTimeInterval:options[@"timeout"]] ?: INFINITY,
     .maximumAge = [RCTConvert NSTimeInterval:options[@"maximumAge"]] ?: INFINITY,
-    .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY
+    .accuracy = [RCTConvert BOOL:options[@"enableHighAccuracy"]] ? kCLLocationAccuracyBest : RCT_DEFAULT_LOCATION_ACCURACY,
+    .distanceFilter = distanceFilter
   };
 }
 
@@ -111,19 +117,6 @@ @implementation RCTLocationObserver
 
 #pragma mark - Lifecycle
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-
-    _locationManager = [CLLocationManager new];
-    _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY;
-    _locationManager.delegate = self;
-
-    _pendingRequests = [NSMutableArray new];
-  }
-  return self;
-}
-
 - (void)dealloc
 {
   [_locationManager stopUpdatingLocation];
@@ -139,8 +132,18 @@ - (dispatch_queue_t)methodQueue
 
 - (void)beginLocationUpdates
 {
+  if (!_locationManager) {
+    _locationManager = [CLLocationManager new];
+    _locationManager.distanceFilter = _observerOptions.distanceFilter;
+    _locationManager.delegate = self;
+  }
+
   // Request location access permission
-  if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
+  if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"] &&
+    [_locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
+    [_locationManager requestAlwaysAuthorization];
+  } else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] &&
+    [_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
     [_locationManager requestWhenInUseAuthorization];
   }
 
@@ -240,6 +243,9 @@ - (void)timeout:(NSTimer *)timer
                                                         selector:@selector(timeout:)
                                                         userInfo:request
                                                          repeats:NO];
+  if (!_pendingRequests) {
+    _pendingRequests = [NSMutableArray new];
+  }
   [_pendingRequests addObject:request];
 
   // Configure location manager and begin updating location
@@ -264,7 +270,7 @@ - (void)locationManager:(CLLocationManager *)manager
       @"heading": @(location.course),
       @"speed": @(location.speed),
     },
-    @"timestamp": @(CFAbsoluteTimeGetCurrent() * 1000.0) // in ms
+    @"timestamp": @([location.timestamp timeIntervalSince1970] * 1000) // in ms
   };
 
   // Send event
@@ -331,8 +337,9 @@ - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *
 
 - (void)checkLocationConfig
 {
-  if (![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) {
-    RCTLogError(@"NSLocationWhenInUseUsageDescription key must be present in Info.plist to use geolocation.");
+  if (!([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"] ||
+    [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"])) {
+    RCTLogError(@"Either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription key must be present in Info.plist to use geolocation.");
   }
 }
 
diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js
index 66308f682c0b..7adbe13c206c 100644
--- a/Libraries/Image/Image.android.js
+++ b/Libraries/Image/Image.android.js
@@ -23,7 +23,7 @@ var StyleSheetPropType = require('StyleSheetPropType');
 var View = require('View');
 
 var flattenStyle = require('flattenStyle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var merge = require('merge');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
@@ -38,7 +38,7 @@ var resolveAssetSource = require('resolveAssetSource');
  *       <View>
  *         <Image
  *           style={styles.icon}
- *           source={require('image!myIcon')}
+ *           source={require('./myIcon.png')}
  *         />
  *         <Image
  *           style={styles.logo}
@@ -53,18 +53,21 @@ var resolveAssetSource = require('resolveAssetSource');
 
 var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, {
   src: true,
+  loadingIndicatorSrc: true,
   resizeMode: true,
   progressiveRenderingEnabled: true,
   fadeDuration: true,
+  shouldNotifyLoadEvents: true,
 });
 
 var Image = React.createClass({
   propTypes: {
     ...View.propTypes,
-    /**
+    style: StyleSheetPropType(ImageStylePropTypes),
+   /**
      * `uri` is a string representing the resource identifier for the image, which
-     * could be an http address, a local file path, or the name of a static image
-     * resource (which should be wrapped in the `require('image!name')` function).
+     * could be an http address, a local file path, or a static image
+     * resource (which should be wrapped in the `require('./path/to/image.png')` function).
      */
     source: PropTypes.oneOfType([
       PropTypes.shape({
@@ -72,10 +75,33 @@ var Image = React.createClass({
       }),
       // Opaque type returned by require('./image.jpg')
       PropTypes.number,
-    ]).isRequired,
+    ]),
+    /**
+     * similarly to `source`, this property represents the resource used to render
+     * the loading indicator for the image, displayed until image is ready to be
+     * displayed, typically after when it got downloaded from network.
+     */
+    loadingIndicatorSource: PropTypes.oneOfType([
+      PropTypes.shape({
+        uri: PropTypes.string,
+      }),
+      // Opaque type returned by require('./image.jpg')
+      PropTypes.number,
+    ]),
     progressiveRenderingEnabled: PropTypes.bool,
     fadeDuration: PropTypes.number,
-    style: StyleSheetPropType(ImageStylePropTypes),
+    /**
+     * Invoked on load start
+     */
+    onLoadStart: PropTypes.func,
+    /**
+     * Invoked when load completes successfully
+     */
+    onLoad: PropTypes.func,
+    /**
+     * Invoked when load either succeeds or fails
+     */
+    onLoadEnd: PropTypes.func,
     /**
      * Used to locate this view in end-to-end tests.
      */
@@ -95,14 +121,14 @@ var Image = React.createClass({
    */
   viewConfig: {
     uiViewClassName: 'RCTView',
-    validAttributes: ReactNativeViewAttributes.RKView
+    validAttributes: ReactNativeViewAttributes.RCTView,
   },
 
   _updateViewConfig: function(props) {
     if (props.children) {
       this.viewConfig = {
         uiViewClassName: 'RCTView',
-        validAttributes: ReactNativeViewAttributes.RKView,
+        validAttributes: ReactNativeViewAttributes.RCTView,
       };
     } else {
       this.viewConfig = {
@@ -126,6 +152,7 @@ var Image = React.createClass({
 
   render: function() {
     var source = resolveAssetSource(this.props.source);
+    var loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource);
 
     // As opposed to the ios version, here it render `null`
     // when no source or source.uri... so let's not break that.
@@ -137,10 +164,13 @@ var Image = React.createClass({
     if (source && source.uri) {
       var {width, height} = source;
       var style = flattenStyle([{width, height}, styles.base, this.props.style]);
+      var {onLoadStart, onLoad, onLoadEnd} = this.props;
 
       var nativeProps = merge(this.props, {
         style,
+        shouldNotifyLoadEvents: !!(onLoadStart || onLoad || onLoadEnd),
         src: source.uri,
+        loadingIndicatorSrc: loadingIndicatorSource ? loadingIndicatorSource.uri : null,
       });
 
       if (nativeProps.children) {
@@ -183,9 +213,11 @@ var styles = StyleSheet.create({
 var cfg = {
   nativeOnly: {
     src: true,
+    loadingIndicatorSrc: true,
     defaultImageSrc: true,
     imageTag: true,
     progressHandlerRegistered: true,
+    shouldNotifyLoadEvents: true,
   },
 };
 var RKImage = requireNativeComponent('RCTImageView', Image, cfg);
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 4da2d53e5096..43f694b43ecc 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -15,7 +15,6 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
 var ImageResizeMode = require('ImageResizeMode');
 var ImageStylePropTypes = require('ImageStylePropTypes');
 var NativeMethodsMixin = require('NativeMethodsMixin');
-var NativeModules = require('NativeModules');
 var PropTypes = require('ReactPropTypes');
 var React = require('React');
 var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
@@ -24,10 +23,15 @@ var StyleSheet = require('StyleSheet');
 var StyleSheetPropType = require('StyleSheetPropType');
 
 var flattenStyle = require('flattenStyle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
+
+var {
+  ImageViewManager,
+  NetworkImageViewManager,
+} = require('NativeModules');
 
 /**
  * A React component for displaying different types of images,
@@ -42,7 +46,7 @@ var warning = require('warning');
  *     <View>
  *       <Image
  *         style={styles.icon}
- *         source={require('image!myIcon')}
+ *         source={require('./myIcon.png')}
  *       />
  *       <Image
  *         style={styles.logo}
@@ -59,7 +63,7 @@ var Image = React.createClass({
     /**
      * `uri` is a string representing the resource identifier for the image, which
      * could be an http address, a local file path, or the name of a static image
-     * resource (which should be wrapped in the `require('image!name')` function).
+     * resource (which should be wrapped in the `require('./path/to/image.png')` function).
      */
     source: PropTypes.oneOfType([
       PropTypes.shape({
@@ -69,13 +73,16 @@ var Image = React.createClass({
       PropTypes.number,
     ]),
     /**
-     * A static image to display while downloading the final image off the
-     * network.
+     * A static image to display while loading the image source.
      * @platform ios
      */
-    defaultSource: PropTypes.shape({
-      uri: PropTypes.string,
-    }),
+    defaultSource: PropTypes.oneOfType([
+      PropTypes.shape({
+        uri: PropTypes.string,
+      }),
+      // Opaque type returned by require('./image.jpg')
+      PropTypes.number,
+    ]),
     /**
      * When true, indicates the image is an accessibility element.
      * @platform ios
@@ -99,6 +106,17 @@ var Image = React.createClass({
     /**
      * Determines how to resize the image when the frame doesn't match the raw
      * image dimensions.
+     *
+     * 'cover': Scale the image uniformly (maintain the image's aspect ratio)
+     * so that both dimensions (width and height) of the image will be equal
+     * to or larger than the corresponding dimension of the view (minus padding).
+     *
+     * 'contain': Scale the image uniformly (maintain the image's aspect ratio)
+     * so that both dimensions (width and height) of the image will be equal to
+     * or less than the corresponding dimension of the view (minus padding).
+     *
+     * 'stretch': Scale width and height independently, This may change the
+     * aspect ratio of the src.
      */
     resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch']),
     /**
@@ -113,7 +131,6 @@ var Image = React.createClass({
     onLayout: PropTypes.func,
     /**
      * Invoked on load start
-     * @platform ios
      */
     onLoadStart: PropTypes.func,
     /**
@@ -128,18 +145,38 @@ var Image = React.createClass({
     onError: PropTypes.func,
     /**
      * Invoked when load completes successfully
-     * @platform ios
      */
     onLoad: PropTypes.func,
     /**
      * Invoked when load either succeeds or fails
-     * @platform ios
      */
     onLoadEnd: PropTypes.func,
   },
 
   statics: {
     resizeMode: ImageResizeMode,
+    /**
+     * Retrieve the width and height (in pixels) of an image prior to displaying it.
+     * This method can fail if the image cannot be found, or fails to download.
+     *
+     * In order to retrieve the image dimensions, the image may first need to be
+     * loaded or downloaded, after which it will be cached. This means that in
+     * principle you could use this method to preload images, however it is not
+     * optimized for that purpose, and may in future be implemented in a way that
+     * does not fully load/download the image data. A proper, supported way to
+     * preload images will be provided as a separate API.
+     *
+     * @platform ios
+     */
+    getSize: function(
+      uri: string,
+      success: (width: number, height: number) => void,
+      failure: (error: any) => void,
+    ) {
+      ImageViewManager.getSize(uri, success, failure || function() {
+        console.warn('Failed to get size for image: ' + uri);
+      });
+    }
   },
 
   mixins: [NativeMethodsMixin],
@@ -158,24 +195,11 @@ var Image = React.createClass({
   },
 
   render: function() {
-    for (var prop in cfg.nativeOnly) {
-      if (this.props[prop] !== undefined) {
-        console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
-          'not be set directly on Image.');
-      }
-    }
     var source = resolveAssetSource(this.props.source) || {};
-    var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {};
-
-    var {width, height} = source;
+    var {width, height, uri} = source;
     var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
 
-    if (source.uri === '') {
-      console.warn('source.uri should not be an empty string');
-      return <View {...this.props} style={style} />;
-    }
-
-    var isNetwork = source.uri && source.uri.match(/^https?:/);
+    var isNetwork = uri && uri.match(/^https?:/);
     var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
     var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
     var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
@@ -187,20 +211,22 @@ var Image = React.createClass({
     }
 
     if (this.context.isInAParentText) {
-      return <RCTVirtualImage source={source}/>;
-    } else {
-      return (
-        <RawImage
-          {...this.props}
-          style={style}
-          resizeMode={resizeMode}
-          tintColor={tintColor}
-          src={source.uri}
-          defaultImageSrc={defaultSource.uri}
-        />
-      );
+      RawImage = RCTVirtualImage;
+      if (!width || !height) {
+        console.warn('You must specify a width and height for the image %s', uri);
+      }
     }
-  }
+
+    return (
+      <RawImage
+        {...this.props}
+        style={style}
+        resizeMode={resizeMode}
+        tintColor={tintColor}
+        source={source}
+      />
+    );
+  },
 });
 
 var styles = StyleSheet.create({
@@ -209,16 +235,9 @@ var styles = StyleSheet.create({
   },
 });
 
-var cfg = {
-  nativeOnly: {
-    src: true,
-    defaultImageSrc: true,
-    imageTag: true,
-    progressHandlerRegistered: true,
-  },
-};
-var RCTImageView = requireNativeComponent('RCTImageView', Image, cfg);
-var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image, cfg) : RCTImageView;
+var RCTImageView = requireNativeComponent('RCTImageView', Image);
+var RCTNetworkImageView = NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView;
 var RCTVirtualImage = requireNativeComponent('RCTVirtualImage', Image);
 
+
 module.exports = Image;
diff --git a/Libraries/Image/ImageEditor.js b/Libraries/Image/ImageEditor.js
new file mode 100644
index 000000000000..4ea15349dd54
--- /dev/null
+++ b/Libraries/Image/ImageEditor.js
@@ -0,0 +1,72 @@
+/**
+ * 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 ImageEditor
+ * @flow
+ */
+'use strict';
+
+const RCTImageEditingManager = require('NativeModules').ImageEditingManager;
+
+type ImageCropData = {
+  /**
+   * The top-left corner of the cropped image, specified in the original
+   * image's coordinate space.
+   */
+  offset: {
+    x: number;
+    y: number;
+  };
+  /**
+   * The size (dimensions) of the cropped image, specified in the original
+   * image's coordinate space.
+   */
+  size: {
+    width: number;
+    height: number; 
+  };
+  /**
+   * (Optional) size to scale the cropped image to.
+   */
+  displaySize?: ?{
+    width: number;
+    height: number;
+  };
+  /**
+   * (Optional) the resizing mode to use when scaling the image. If the
+   * `displaySize` param is not specified, this has no effect.
+   */
+  resizeMode?: ?$Enum<{
+    contain: string;
+    cover: string;
+    stretch: string;
+  }>;
+};
+
+class ImageEditor {
+  /**
+   * Crop the image specified by the URI param. If URI points to a remote
+   * image, it will be downloaded automatically. If the image cannot be
+   * loaded/downloaded, the failure callback will be called. 
+   *
+   * If the cropping process is successful, the resultant cropped image
+   * will be stored in the ImageStore, and the URI returned in the success
+   * callback will point to the image in the store. Remember to delete the
+   * cropped image from the ImageStore when you are done with it.
+   */
+  static cropImage(
+    uri: string,
+    cropData: ImageCropData,
+    success: (uri: string) => void,
+    failure: (error: Object) => void
+  ) {
+    RCTImageEditingManager.cropImage(uri, cropData, success, failure);
+  }
+}
+
+module.exports = ImageEditor;
diff --git a/Libraries/Image/ImageResizeMode.js b/Libraries/Image/ImageResizeMode.js
index b947b95258f3..531212d86794 100644
--- a/Libraries/Image/ImageResizeMode.js
+++ b/Libraries/Image/ImageResizeMode.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 
 /**
  * ImageResizeMode - Enum for different image resizing modes, set via
@@ -31,7 +31,7 @@ var ImageResizeMode = keyMirror({
   /**
    * stretch - The image will be stretched to fill the entire frame of the
    * view without clipping. This may change the aspect ratio of the image,
-   * distoring it.
+   * distorting it.
    */
   stretch: null,
 });
diff --git a/Libraries/Image/ImageSource.js b/Libraries/Image/ImageSource.js
index 66ae67c72ead..963192ea212a 100644
--- a/Libraries/Image/ImageSource.js
+++ b/Libraries/Image/ImageSource.js
@@ -11,6 +11,6 @@
  */
 'use strict';
 
-type ImageSource = {
+export type ImageSource = {
   uri: string;
 };
diff --git a/Libraries/Image/ImageStore.js b/Libraries/Image/ImageStore.js
new file mode 100644
index 000000000000..f03262d6d08f
--- /dev/null
+++ b/Libraries/Image/ImageStore.js
@@ -0,0 +1,84 @@
+/**
+ * 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 ImageStore
+ * @flow
+ */
+'use strict';
+
+const RCTImageStoreManager = require('NativeModules').ImageStoreManager;
+
+class ImageStore {
+  /**
+   * Check if the ImageStore contains image data for the specified URI.
+   * @platform ios
+   */
+  static hasImageForTag(uri: string, callback: (hasImage: bool) => void) {
+    if (RCTImageStoreManager.hasImageForTag) {
+      RCTImageStoreManager.hasImageForTag(uri, callback);
+    } else {
+      console.warn('hasImageForTag() not implemented');
+    }
+  }
+
+  /**
+   * Delete an image from the ImageStore. Images are stored in memory and
+   * must be manually removed when you are finished with them, otherwise they
+   * will continue to use up RAM until the app is terminated. It is safe to
+   * call `removeImageForTag()` without first calling `hasImageForTag()`, it
+   * will simply fail silently.
+   * @platform ios
+   */
+  static removeImageForTag(uri: string) {
+    if (RCTImageStoreManager.removeImageForTag) {
+      RCTImageStoreManager.removeImageForTag(uri);
+    } else {
+      console.warn('removeImageForTag() not implemented');
+    }
+  }
+
+  /**
+   * Stores a base64-encoded image in the ImageStore, and returns a URI that
+   * can be used to access or display the image later. Images are stored in
+   * memory only, and must be manually deleted when you are finished with
+   * them by calling `removeImageForTag()`.
+   *
+   * Note that it is very inefficient to transfer large quantities of binary
+   * data between JS and native code, so you should avoid calling this more
+   * than necessary.
+   * @platform ios
+   */
+  static addImageFromBase64(
+    base64ImageData: string,
+    success: (uri: string) => void,
+    failure: (error: any) => void
+  ) {
+    RCTImageStoreManager.addImageFromBase64(base64ImageData, success, failure);
+  }
+
+  /**
+   * Retrieves the base64-encoded data for an image in the ImageStore. If the
+   * specified URI does not match an image in the store, the failure callback
+   * will be called.
+   *
+   * Note that it is very inefficient to transfer large quantities of binary
+   * data between JS and native code, so you should avoid calling this more
+   * than necessary. To display an image in the ImageStore, you can just pass
+   * the URI to an `<Image/>` component; there is no need to retrieve the
+   * base64 data.
+   */
+  static getBase64ForTag(
+    uri: string,
+    success: (base64ImageData: string) => void,
+    failure: (error: any) => void
+  ) {
+    RCTImageStoreManager.getBase64ForTag(uri, success, failure);
+  }
+}
+
+module.exports = ImageStore;
diff --git a/Libraries/Image/ImageStylePropTypes.js b/Libraries/Image/ImageStylePropTypes.js
index c4ccfb57860a..b1c5e12b1124 100644
--- a/Libraries/Image/ImageStylePropTypes.js
+++ b/Libraries/Image/ImageStylePropTypes.js
@@ -14,22 +14,47 @@
 var ImageResizeMode = require('ImageResizeMode');
 var LayoutPropTypes = require('LayoutPropTypes');
 var ReactPropTypes = require('ReactPropTypes');
+var ColorPropType = require('ColorPropType');
+var ShadowPropTypesIOS = require('ShadowPropTypesIOS');
 var TransformPropTypes = require('TransformPropTypes');
 
 var ImageStylePropTypes = {
   ...LayoutPropTypes,
+  ...ShadowPropTypesIOS,
   ...TransformPropTypes,
   resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)),
-  backgroundColor: ReactPropTypes.string,
-  borderColor: ReactPropTypes.string,
+  backfaceVisibility: ReactPropTypes.oneOf(['visible', 'hidden']),
+  backgroundColor: ColorPropType,
+  borderColor: ColorPropType,
   borderWidth: ReactPropTypes.number,
   borderRadius: ReactPropTypes.number,
   overflow: ReactPropTypes.oneOf(['visible', 'hidden']),
 
-  // iOS-Specific style to "tint" an image.
-  // It changes the color of all the non-transparent pixels to the tintColor
-  tintColor: ReactPropTypes.string,
+  /** 
+   * iOS-Specific style to "tint" an image.
+   * Changes the color of all the non-transparent pixels to the tintColor.
+   * @platform ios
+   */
+  tintColor: ColorPropType,
   opacity: ReactPropTypes.number,
+  /**
+   * When the image has rounded corners, specifying an overlayColor will
+   * cause the remaining space in the corners to be filled with a solid color.
+   * This is useful in cases which are not supported by the Android
+   * implementation of rounded corners:
+   *   - Certain resize modes, such as 'contain'
+   *   - Animated GIFs
+   * 
+   * A typical way to use this prop is with images displayed on a solid
+   * background and setting the `overlayColor` to the same color
+   * as the background.
+   *
+   * For details of how this works under the hood, see
+   * http://frescolib.org/docs/rounded-corners-and-circles.html
+   *
+   * @platform android
+  */
+  overlayColor: ReactPropTypes.string,
 };
 
 module.exports = ImageStylePropTypes;
diff --git a/Libraries/Image/RCTGIFImageDecoder.m b/Libraries/Image/RCTGIFImageDecoder.m
index b90375856bf3..e578421a7e2f 100644
--- a/Libraries/Image/RCTGIFImageDecoder.m
+++ b/Libraries/Image/RCTGIFImageDecoder.m
@@ -30,7 +30,7 @@ - (BOOL)canDecodeImageData:(NSData *)imageData
 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
+                                        resizeMode:(RCTResizeMode)resizeMode
                                  completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
 {
   CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
index 0c404f31c6a2..8c9c61861a31 100644
--- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
+++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
@@ -11,9 +11,10 @@
 		1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
 		1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; };
 		134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
-		13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */; settings = {ASSET_TAGS = (); }; };
-		13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */; settings = {ASSET_TAGS = (); }; };
-		13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */; settings = {ASSET_TAGS = (); }; };
+		139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 139A38831C4D587C00862840 /* RCTResizeMode.m */; };
+		13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */; };
+		13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F0A1BC42D4E003F47DD /* RCTVirtualImageManager.m */; };
+		13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */; };
 		143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
 		35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
 		354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
@@ -40,6 +41,8 @@
 		1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; };
 		134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
 		134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
+		139A38821C4D57AD00862840 /* RCTResizeMode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTResizeMode.h; sourceTree = "<group>"; };
+		139A38831C4D587C00862840 /* RCTResizeMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTResizeMode.m; sourceTree = "<group>"; };
 		13EF7F071BC42D4E003F47DD /* RCTShadowVirtualImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowVirtualImage.h; sourceTree = "<group>"; };
 		13EF7F081BC42D4E003F47DD /* RCTShadowVirtualImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowVirtualImage.m; sourceTree = "<group>"; };
 		13EF7F091BC42D4E003F47DD /* RCTVirtualImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVirtualImageManager.h; sourceTree = "<group>"; };
@@ -69,6 +72,8 @@
 		58B511541A9E6B3D00147676 = {
 			isa = PBXGroup;
 			children = (
+				139A38821C4D57AD00862840 /* RCTResizeMode.h */,
+				139A38831C4D587C00862840 /* RCTResizeMode.m */,
 				13EF7F7D1BC825B1003F47DD /* RCTXCAssetImageLoader.h */,
 				13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */,
 				1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
@@ -165,6 +170,7 @@
 				1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
 				143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
 				354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
+				139A38841C4D587C00862840 /* RCTResizeMode.m in Sources */,
 				1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
 				13EF7F0B1BC42D4E003F47DD /* RCTShadowVirtualImage.m in Sources */,
 				13EF7F7F1BC825B1003F47DD /* RCTXCAssetImageLoader.m in Sources */,
diff --git a/Libraries/Image/RCTImageEditingManager.m b/Libraries/Image/RCTImageEditingManager.m
index cd81bbfbeed7..9ff231d64f7f 100644
--- a/Libraries/Image/RCTImageEditingManager.m
+++ b/Libraries/Image/RCTImageEditingManager.m
@@ -14,6 +14,7 @@
 #import "RCTConvert.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
+#import "RCTImageUtils.h"
 
 #import "RCTImageStoreManager.h"
 #import "RCTImageLoader.h"
@@ -39,40 +40,33 @@ @implementation RCTImageEditingManager
                   successCallback:(RCTResponseSenderBlock)successCallback
                   errorCallback:(RCTResponseErrorBlock)errorCallback)
 {
-  NSDictionary *offset = cropData[@"offset"];
-  NSDictionary *size = cropData[@"size"];
-  NSDictionary *displaySize = cropData[@"displaySize"];
-  NSString *resizeMode = cropData[@"resizeMode"] ?: @"contain";
-
-  if (!offset[@"x"] || !offset[@"y"] || !size[@"width"] || !size[@"height"]) {
-    NSString *errorMessage = [NSString stringWithFormat:@"Invalid cropData: %@", cropData];
-    RCTLogError(@"%@", errorMessage);
-    errorCallback(RCTErrorWithMessage(errorMessage));
-    return;
-  }
+  CGRect rect = {
+    [RCTConvert CGPoint:cropData[@"offset"]],
+    [RCTConvert CGSize:cropData[@"size"]]
+  };
 
   [_bridge.imageLoader loadImageWithTag:imageTag callback:^(NSError *error, UIImage *image) {
     if (error) {
       errorCallback(error);
       return;
     }
-    CGRect rect = (CGRect){
-      [RCTConvert CGPoint:offset],
-      [RCTConvert CGSize:size]
-    };
 
     // Crop image
-    CGRect rectToDrawIn = {{-rect.origin.x, -rect.origin.y}, image.size};
-    UIGraphicsBeginImageContextWithOptions(rect.size, !RCTImageHasAlpha(image.CGImage), image.scale);
-    [image drawInRect:rectToDrawIn];
-    UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
-    UIGraphicsEndImageContext();
-
-    if (displaySize && displaySize[@"width"] && displaySize[@"height"]) {
-      CGSize targetSize = [RCTConvert CGSize:displaySize];
-      croppedImage = [self scaleImage:croppedImage targetSize:targetSize resizeMode:resizeMode];
+    CGSize targetSize = rect.size;
+    CGRect targetRect = {{-rect.origin.x, -rect.origin.y}, image.size};
+    CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetRect);
+    UIImage *croppedImage = RCTTransformImage(image, targetSize, image.scale, transform);
+
+    // Scale image
+    if (cropData[@"displaySize"]) {
+      targetSize = [RCTConvert CGSize:cropData[@"displaySize"]]; // in pixels
+      RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:cropData[@"resizeMode"] ?: @"contain"];
+      targetRect = RCTTargetRect(croppedImage.size, targetSize, 1, resizeMode);
+      transform = RCTTransformFromTargetRect(croppedImage.size, targetRect);
+      croppedImage = RCTTransformImage(croppedImage, targetSize, image.scale, transform);
     }
 
+    // Store image
     [_bridge.imageStoreManager storeImage:croppedImage withBlock:^(NSString *croppedImageTag) {
       if (!croppedImageTag) {
         NSString *errorMessage = @"Error storing cropped image in RCTImageStoreManager";
@@ -85,49 +79,4 @@ @implementation RCTImageEditingManager
   }];
 }
 
-- (UIImage *)scaleImage:(UIImage *)image targetSize:(CGSize)targetSize resizeMode:(NSString *)resizeMode
-{
-  if (CGSizeEqualToSize(image.size, targetSize)) {
-    return image;
-  }
-
-  CGFloat imageRatio = image.size.width / image.size.height;
-  CGFloat targetRatio = targetSize.width / targetSize.height;
-
-  CGFloat newWidth = targetSize.width;
-  CGFloat newHeight = targetSize.height;
-
-  // contain vs cover
-  // http://blog.vjeux.com/2013/image/css-container-and-cover.html
-  if ([resizeMode isEqualToString:@"contain"]) {
-    if (imageRatio <= targetRatio) {
-      newWidth = targetSize.height * imageRatio;
-      newHeight = targetSize.height;
-    } else {
-      newWidth = targetSize.width;
-      newHeight = targetSize.width / imageRatio;
-    }
-  } else if ([resizeMode isEqualToString:@"cover"]) {
-    if (imageRatio <= targetRatio) {
-      newWidth = targetSize.width;
-      newHeight = targetSize.width / imageRatio;
-    } else {
-      newWidth = targetSize.height * imageRatio;
-      newHeight = targetSize.height;
-    }
-  } // else assume we're stretching the image
-
-  // prevent upscaling
-  newWidth = MIN(newWidth, image.size.width);
-  newHeight = MIN(newHeight, image.size.height);
-
-  // perform the scaling @1x because targetSize is in actual pixel width/height
-  UIGraphicsBeginImageContextWithOptions(targetSize, NO, 1.0f);
-  [image drawInRect:CGRectMake(0.f, 0.f, newWidth, newHeight)];
-  UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
-  UIGraphicsEndImageContext();
-
-  return scaledImage;
-}
-
 @end
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index 770747452608..d11c424103d3 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -11,6 +11,7 @@
 
 #import "RCTBridge.h"
 #import "RCTURLRequestHandler.h"
+#import "RCTResizeMode.h"
 
 @class ALAssetsLibrary;
 
@@ -26,6 +27,31 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
 
 @interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
 
+/**
+ * The maximum number of concurrent image loading tasks. Loading and decoding
+ * images can consume a lot of memory, so setting this to a higher value may
+ * cause memory to spike. If you are seeing out-of-memory crashes, try reducing
+ * this value.
+ */
+@property (nonatomic, assign) NSUInteger maxConcurrentLoadingTasks;
+
+/**
+ * The maximum number of concurrent image decoding tasks. Decoding large
+ * images can be especially CPU and memory intensive, so if your are decoding a
+ * lot of large images in your app, you may wish to adjust this value.
+ */
+@property (nonatomic, assign) NSUInteger maxConcurrentDecodingTasks;
+
+/**
+ * Decoding large images can use a lot of memory, and potentially cause the app
+ * to crash. This value allows you to throttle the amount of memory used by the
+ * decoder independently of the number of concurrent threads. This means you can
+ * still decode a lot of small images in parallel, without allowing the decoder
+ * to try to decompress multiple huge images at once. Note that this value is
+ * only a hint, and not an indicator of the total memory used by the app.
+ */
+@property (nonatomic, assign) NSUInteger maxConcurrentDecodingBytes;
+
 /**
  * Loads the specified image at the highest available resolution.
  * Can be called from any thread, will call back on an unspecified thread.
@@ -40,10 +66,20 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
 - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
                                                size:(CGSize)size
                                               scale:(CGFloat)scale
-                                         resizeMode:(UIViewContentMode)resizeMode
+                                         resizeMode:(RCTResizeMode)resizeMode
                                       progressBlock:(RCTImageLoaderProgressBlock)progressBlock
                                     completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
 
+/**
+ * Loads an image without clipping the result to fit - used by RCTImageView.
+ */
+- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
+                                                       size:(CGSize)size
+                                                      scale:(CGFloat)scale
+                                                 resizeMode:(RCTResizeMode)resizeMode
+                                              progressBlock:(RCTImageLoaderProgressBlock)progressBlock
+                                            completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
+
 /**
  * Finds an appropriate image decoder and passes the target size, scale and
  * resizeMode for optimal image decoding. Can be called from any thread,
@@ -52,9 +88,25 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
+                                        resizeMode:(RCTResizeMode)resizeMode
                                    completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
 
+/**
+ * Decodes an image without clipping the result to fit.
+ */
+- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
+                                                             size:(CGSize)size
+                                                            scale:(CGFloat)scale
+                                                       resizeMode:(RCTResizeMode)resizeMode
+                                                  completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
+
+/**
+ * Get image size, in pixels. This method will do the least work possible to get
+ * the information, and won't decode the image if it doesn't have to.
+ */
+- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag
+                                          block:(void(^)(NSError *error, CGSize size))completionBlock;
+
 @end
 
 @interface RCTBridge (RCTImageLoader)
@@ -89,7 +141,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
 - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
+                                        resizeMode:(RCTResizeMode)resizeMode
                                    progressHandler:(RCTImageLoaderProgressBlock)progressHandler
                                  completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
 
@@ -127,7 +179,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
+                                        resizeMode:(RCTResizeMode)resizeMode
                                  completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
 
 @optional
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index b345591a395e..207ecf7024b2 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -11,6 +11,7 @@
 
 #import <libkern/OSAtomic.h>
 #import <UIKit/UIKit.h>
+#import <ImageIO/ImageIO.h>
 
 #import "RCTConvert.h"
 #import "RCTDefines.h"
@@ -37,63 +38,52 @@ @implementation RCTImageLoader
 {
   NSArray<id<RCTImageURLLoader>> *_loaders;
   NSArray<id<RCTImageDataDecoder>> *_decoders;
-  NSURLCache *_cache;
+  NSOperationQueue *_imageDecodeQueue;
+  dispatch_queue_t _URLCacheQueue;
+  NSURLCache *_URLCache;
+  NSMutableArray *_pendingTasks;
+  NSInteger _activeTasks;
+  NSMutableArray *_pendingDecodes;
+  NSInteger _scheduledDecodes;
+  NSUInteger _activeBytes;
 }
 
 @synthesize bridge = _bridge;
 
 RCT_EXPORT_MODULE()
 
-- (void)setBridge:(RCTBridge *)bridge
+- (void)setUp
 {
-  // Get image loaders and decoders
-  NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
-  NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
-  for (id<RCTBridgeModule> module in bridge.modules.allValues) {
-    if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
-      [loaders addObject:(id<RCTImageURLLoader>)module];
-    }
-    if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
-      [decoders addObject:(id<RCTImageDataDecoder>)module];
-    }
-  }
-
-  // Sort loaders in reverse priority order (highest priority first)
-  [loaders sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
-    float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
-    float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
-    if (priorityA > priorityB) {
-      return NSOrderedAscending;
-    } else if (priorityA < priorityB) {
-      return NSOrderedDescending;
-    } else {
-      return NSOrderedSame;
-    }
-  }];
-
-  // Sort decoders in reverse priority order (highest priority first)
-  [decoders sortUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
-    float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
-    float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
-    if (priorityA > priorityB) {
-      return NSOrderedAscending;
-    } else if (priorityA < priorityB) {
-      return NSOrderedDescending;
-    } else {
-      return NSOrderedSame;
-    }
-  }];
+  // Set defaults
+  _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
+  _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
+  _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 *1024; // 30MB
 
-  _bridge = bridge;
-  _loaders = loaders;
-  _decoders = decoders;
-  _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
-                                         diskCapacity:200 * 1024 * 1024 // 200MB
-                                             diskPath:@"React/RCTImageDownloader"];
+  _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
 }
 
 - (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
 {
+  if (!_maxConcurrentLoadingTasks) {
+    [self setUp];
+  }
+
+  if (!_loaders) {
+    // Get loaders, sorted in reverse priority order (highest priority first)
+    RCTAssert(_bridge, @"Bridge not set");
+    _loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
+      float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
+      float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
+      if (priorityA > priorityB) {
+        return NSOrderedAscending;
+      } else if (priorityA < priorityB) {
+        return NSOrderedDescending;
+      } else {
+        return NSOrderedSame;
+      }
+    }];
+  }
+
   if (RCT_DEBUG) {
     // Check for handler conflicts
     float previousPriority = 0;
@@ -131,6 +121,26 @@ - (void)setBridge:(RCTBridge *)bridge
 
 - (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
 {
+  if (!_maxConcurrentLoadingTasks) {
+    [self setUp];
+  }
+
+  if (!_decoders) {
+    // Get decoders, sorted in reverse priority order (highest priority first)
+    RCTAssert(_bridge, @"Bridge not set");
+    _decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
+      float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
+      float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
+      if (priorityA > priorityB) {
+        return NSOrderedAscending;
+      } else if (priorityA < priorityB) {
+        return NSOrderedDescending;
+      } else {
+        return NSOrderedSame;
+      }
+    }];
+  }
+
   if (RCT_DEBUG) {
     // Check for handler conflicts
     float previousPriority = 0;
@@ -167,150 +177,306 @@ - (void)setBridge:(RCTBridge *)bridge
   return nil;
 }
 
+static UIImage *RCTResizeImageIfNeeded(UIImage *image,
+                                       CGSize size,
+                                       CGFloat scale,
+                                       RCTResizeMode resizeMode)
+{
+  if (CGSizeEqualToSize(size, CGSizeZero) ||
+      CGSizeEqualToSize(image.size, CGSizeZero) ||
+      CGSizeEqualToSize(image.size, size)) {
+    return image;
+  }
+  CAKeyframeAnimation *animation = image.reactKeyframeAnimation;
+  CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
+  CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
+  image = RCTTransformImage(image, size, scale, transform);
+  image.reactKeyframeAnimation = animation;
+  return image;
+}
+
 - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
                                            callback:(RCTImageLoaderCompletionBlock)callback
 {
   return [self loadImageWithTag:imageTag
                            size:CGSizeZero
                           scale:1
-                     resizeMode:UIViewContentModeScaleToFill
+                     resizeMode:RCTResizeModeStretch
                   progressBlock:nil
                 completionBlock:callback];
 }
 
-- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
-                                               size:(CGSize)size
-                                              scale:(CGFloat)scale
-                                         resizeMode:(UIViewContentMode)resizeMode
-                                      progressBlock:(RCTImageLoaderProgressBlock)progressHandler
-                                    completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
+- (void)dequeueTasks
 {
-  if (imageTag.length == 0) {
-    RCTLogWarn(@"source.uri should not be an empty string <Native>");
-    return ^{};
-  }
+  dispatch_async(_URLCacheQueue, ^{
+
+    // Remove completed tasks
+    for (RCTNetworkTask *task in _pendingTasks.reverseObjectEnumerator) {
+      switch (task.status) {
+        case RCTNetworkTaskFinished:
+          [_pendingTasks removeObject:task];
+          _activeTasks--;
+          break;
+        case RCTNetworkTaskPending:
+        case RCTNetworkTaskInProgress:
+          // Do nothing
+          break;
+      }
+    }
 
+    // Start queued decode
+    NSInteger activeDecodes = _scheduledDecodes - _pendingDecodes.count;
+    while (activeDecodes == 0 || (_activeBytes <= _maxConcurrentDecodingBytes &&
+                                  activeDecodes <= _maxConcurrentDecodingTasks)) {
+      dispatch_block_t decodeBlock = _pendingDecodes.firstObject;
+      if (decodeBlock) {
+        [_pendingDecodes removeObjectAtIndex:0];
+        decodeBlock();
+      } else {
+        break;
+      }
+    }
+
+    // Start queued tasks
+    for (RCTNetworkTask *task in _pendingTasks) {
+      if (MAX(_activeTasks, _scheduledDecodes) >= _maxConcurrentLoadingTasks) {
+        break;
+      }
+      if (task.status == RCTNetworkTaskPending) {
+        [task start];
+        _activeTasks++;
+      }
+    }
+  });
+}
+
+/**
+ * This returns either an image, or raw image data, depending on the loading
+ * path taken. This is useful if you want to skip decoding, e.g. when preloading
+ * the image, or retrieving metadata.
+ */
+- (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
+                                                     size:(CGSize)size
+                                                    scale:(CGFloat)scale
+                                               resizeMode:(RCTResizeMode)resizeMode
+                                            progressBlock:(RCTImageLoaderProgressBlock)progressHandler
+                                          completionBlock:(void (^)(NSError *error, id imageOrData))completionBlock
+{
   __block volatile uint32_t cancelled = 0;
-  RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
+  __block void(^cancelLoad)(void) = nil;
+  __weak RCTImageLoader *weakSelf = self;
+
+  void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) {
     if ([NSThread isMainThread]) {
 
       // Most loaders do not return on the main thread, so caller is probably not
       // expecting it, and may do expensive post-processing in the callback
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         if (!cancelled) {
-          completionBlock(error, image);
+          completionBlock(error, imageOrData);
         }
       });
     } else if (!cancelled) {
-      completionBlock(error, image);
+      completionBlock(error, imageOrData);
     }
   };
 
-  // Find suitable image URL loader
-  NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
-  id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
-  if (loadHandler) {
-    return [loadHandler loadImageForURL:request.URL
-                                   size:size
-                                  scale:scale
-                             resizeMode:resizeMode
-                        progressHandler:progressHandler
-                      completionHandler:completionHandler] ?: ^{};
-  }
-
-  // Check if networking module is available
-  if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
-    RCTLogError(@"No suitable image URL loader found for %@. You may need to "
-                " import the RCTNetworking library in order to load images.",
-                imageTag);
+  if (imageTag.length == 0) {
+    completionHandler(RCTErrorWithMessage(@"source.uri should not be an empty string"), nil);
     return ^{};
   }
 
-  // Check if networking module can load image
-  if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) {
-    RCTLogError(@"No suitable image URL loader found for %@", imageTag);
-    return ^{};
+  // All access to URL cache must be serialized
+  if (!_URLCacheQueue) {
+    [self setUp];
   }
+  dispatch_async(_URLCacheQueue, ^{
 
-  // Use networking module to load image
-  __weak RCTImageLoader *weakSelf = self;
-  __block RCTImageLoaderCancellationBlock decodeCancel = nil;
-  RCTURLRequestCompletionBlock processResponse =
-  ^(NSURLResponse *response, NSData *data, NSError *error) {
+    if (!_URLCache) {
+      _URLCache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
+                                                diskCapacity:200 * 1024 * 1024 // 200MB
+                                                    diskPath:@"React/RCTImageDownloader"];
+    }
 
-    // Check for system errors
-    if (error) {
-      completionHandler(error, nil);
+    RCTImageLoader *strongSelf = weakSelf;
+    if (cancelled || !strongSelf) {
       return;
-    } else if (!data) {
-      completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
+    }
+
+    // Find suitable image URL loader
+    NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
+    id<RCTImageURLLoader> loadHandler = [strongSelf imageURLLoaderForURL:request.URL];
+    if (loadHandler) {
+      cancelLoad = [loadHandler loadImageForURL:request.URL
+                                           size:size
+                                          scale:scale
+                                     resizeMode:resizeMode
+                                progressHandler:progressHandler
+                              completionHandler:completionHandler] ?: ^{};
       return;
     }
 
-    // Check for http errors
-    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
-      NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
-      if (statusCode != 200) {
-        completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
-                                                     code:statusCode
-                                                 userInfo:nil], nil);
+    // Check if networking module is available
+    if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
+      RCTLogError(@"No suitable image URL loader found for %@. You may need to "
+                  " import the RCTNetworking library in order to load images.",
+                  imageTag);
+      return;
+    }
+
+    // Check if networking module can load image
+    if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) {
+      RCTLogError(@"No suitable image URL loader found for %@", imageTag);
+      return;
+    }
+
+    // Use networking module to load image
+    RCTURLRequestCompletionBlock processResponse =
+    ^(NSURLResponse *response, NSData *data, NSError *error) {
+
+      // Check for system errors
+      if (error) {
+        completionHandler(error, nil);
+        return;
+      } else if (!data) {
+        completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
         return;
       }
-    }
 
-    // Decode image
-    decodeCancel = [weakSelf decodeImageData:data
-                                        size:size
-                                       scale:scale
-                                  resizeMode:resizeMode
-                             completionBlock:completionHandler];
-  };
+      // Check for http errors
+      if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
+        NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
+        if (statusCode != 200) {
+          completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
+                                                       code:statusCode
+                                                   userInfo:nil], nil);
+          return;
+        }
+      }
 
-  // Check for cached response before reloading
-  // TODO: move URL cache out of RCTImageLoader into its own module
-  NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
-  if (cachedResponse) {
-    processResponse(cachedResponse.response, cachedResponse.data, nil);
-    return ^{};
-  }
+      // Call handler
+      completionHandler(nil, data);
+    };
 
-  // Add missing png extension
-  if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
-    NSMutableURLRequest *mutableRequest = [request mutableCopy];
-    mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
-    request = mutableRequest;
-  }
+    // Add missing png extension
+    if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
+      NSMutableURLRequest *mutableRequest = [request mutableCopy];
+      mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
+      request = mutableRequest;
+    }
 
-  // Download image
-  RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
-                          ^(NSURLResponse *response, NSData *data, NSError *error) {
-    if (error) {
-      completionHandler(error, nil);
+    // Check for cached response before reloading
+    // TODO: move URL cache out of RCTImageLoader into its own module
+    NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest:request];
+    if (cachedResponse) {
+      processResponse(cachedResponse.response, cachedResponse.data, nil);
       return;
     }
 
-    // Cache the response
-    // TODO: move URL cache out of RCTImageLoader into its own module
-    RCTImageLoader *strongSelf = weakSelf;
-    BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
-    [strongSelf->_cache storeCachedResponse:
-     [[NSCachedURLResponse alloc] initWithResponse:response
-                                              data:data
-                                          userInfo:nil
-                                     storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
-                                 forRequest:request];
+    // Download image
+    RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
+      if (error) {
+        completionHandler(error, nil);
+        return;
+      }
 
-    // Process image data
-    processResponse(response, data, nil);
+      dispatch_async(_URLCacheQueue, ^{
 
-  }];
-  task.downloadProgressBlock = progressHandler;
-  [task start];
+        // Cache the response
+        // TODO: move URL cache out of RCTImageLoader into its own module
+        BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
+        [strongSelf->_URLCache storeCachedResponse:
+         [[NSCachedURLResponse alloc] initWithResponse:response
+                                                  data:data
+                                              userInfo:nil
+                                         storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
+                                        forRequest:request];
+        // Process image data
+        processResponse(response, data, nil);
+
+        //clean up
+        [weakSelf dequeueTasks];
+
+      });
+
+    }];
+    task.downloadProgressBlock = progressHandler;
+
+    if (!_pendingTasks) {
+      _pendingTasks = [NSMutableArray new];
+    }
+    [_pendingTasks addObject:task];
+    if (MAX(_activeTasks, _scheduledDecodes) < _maxConcurrentLoadingTasks) {
+      [task start];
+      _activeTasks++;
+    }
+
+    cancelLoad = ^{
+      [task cancel];
+      [weakSelf dequeueTasks];
+    };
+
+  });
+
+  return ^{
+    if (cancelLoad) {
+      cancelLoad();
+    }
+    OSAtomicOr32Barrier(1, &cancelled);
+  };
+}
+
+- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
+                                               size:(CGSize)size
+                                              scale:(CGFloat)scale
+                                         resizeMode:(RCTResizeMode)resizeMode
+                                      progressBlock:(RCTImageLoaderProgressBlock)progressHandler
+                                    completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
+{
+  return [self loadImageWithoutClipping:imageTag
+                                   size:size
+                                  scale:scale
+                             resizeMode:resizeMode
+                          progressBlock:progressHandler
+                        completionBlock:^(NSError *error, UIImage *image) {
+                          completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode));
+                        }];
+}
+
+- (RCTImageLoaderCancellationBlock)loadImageWithoutClipping:(NSString *)imageTag
+                                                       size:(CGSize)size
+                                                      scale:(CGFloat)scale
+                                                 resizeMode:(RCTResizeMode)resizeMode
+                                              progressBlock:(RCTImageLoaderProgressBlock)progressHandler
+                                            completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
+{
+  __block volatile uint32_t cancelled = 0;
+  __block void(^cancelLoad)(void) = nil;
+  __weak RCTImageLoader *weakSelf = self;
 
+  void (^completionHandler)(NSError *error, id imageOrData) = ^(NSError *error, id imageOrData) {
+    if (!cancelled) {
+      if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
+        completionBlock(error, imageOrData);
+      } else {
+        cancelLoad = [weakSelf decodeImageDataWithoutClipping:imageOrData
+                                                         size:size
+                                                        scale:scale
+                                                   resizeMode:resizeMode
+                                              completionBlock:completionBlock] ?: ^{};
+      }
+    }
+  };
+
+  cancelLoad = [self loadImageOrDataWithTag:imageTag
+                                       size:size
+                                      scale:scale
+                                 resizeMode:resizeMode
+                              progressBlock:progressHandler
+                            completionBlock:completionHandler] ?: ^{};
   return ^{
-    [task cancel];
-    if (decodeCancel) {
-      decodeCancel();
+    if (cancelLoad) {
+      cancelLoad();
     }
     OSAtomicOr32Barrier(1, &cancelled);
   };
@@ -319,17 +485,46 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
                                               size:(CGSize)size
                                              scale:(CGFloat)scale
-                                        resizeMode:(UIViewContentMode)resizeMode
-                                   completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
+                                        resizeMode:(RCTResizeMode)resizeMode
+                                   completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
+{
+  return [self decodeImageDataWithoutClipping:data
+                                         size:size
+                                        scale:scale
+                                   resizeMode:resizeMode
+                              completionBlock:^(NSError *error, UIImage *image) {
+                                completionBlock(error, RCTResizeImageIfNeeded(image, size, scale, resizeMode));
+                              }];
+}
+
+- (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
+                                                             size:(CGSize)size
+                                                            scale:(CGFloat)scale
+                                                       resizeMode:(RCTResizeMode)resizeMode
+                                                  completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
 {
   if (data.length == 0) {
-    completionHandler(RCTErrorWithMessage(@"No image data"), nil);
+    completionBlock(RCTErrorWithMessage(@"No image data"), nil);
     return ^{};
   }
 
+  __block volatile uint32_t cancelled = 0;
+  void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
+    if ([NSThread isMainThread]) {
+      // Most loaders do not return on the main thread, so caller is probably not
+      // expecting it, and may do expensive post-processing in the callback
+      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        if (!cancelled) {
+          completionBlock(error, image);
+        }
+      });
+    } else if (!cancelled) {
+      completionBlock(error, image);
+    }
+  };
+
   id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
   if (imageDecoder) {
-
     return [imageDecoder decodeImageData:data
                                     size:size
                                    scale:scale
@@ -337,19 +532,73 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
                        completionHandler:completionHandler];
   } else {
 
-    __block volatile uint32_t cancelled = 0;
-    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-      if (cancelled) {
-        return;
+    if (!_URLCacheQueue) {
+      [self setUp];
+    }
+    dispatch_async(_URLCacheQueue, ^{
+      dispatch_block_t decodeBlock = ^{
+
+        // Calculate the size, in bytes, that the decompressed image will require
+        NSInteger decodedImageBytes = (size.width * scale) * (size.height * scale) * 4;
+
+        // Mark these bytes as in-use
+        _activeBytes += decodedImageBytes;
+
+        // Do actual decompression on a concurrent background queue
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+          if (!cancelled) {
+
+            // Decompress the image data (this may be CPU and memory intensive)
+            UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
+
+#if RCT_DEV
+
+            CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale);
+            CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale());
+            if (imagePixelSize.width * imagePixelSize.height >
+                screenPixelSize.width * screenPixelSize.height) {
+              RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger "
+                         "than the screen size %@", NSStringFromCGSize(imagePixelSize),
+                         NSStringFromCGSize(screenPixelSize));
+            }
+
+#endif
+
+            if (image) {
+              completionHandler(nil, image);
+            } else {
+              NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
+              NSError *finalError = RCTErrorWithMessage(errorMessage);
+              completionHandler(finalError, nil);
+            }
+          }
+
+          // We're no longer retaining the uncompressed data, so now we'll mark
+          // the decoding as complete so that the loading task queue can resume.
+          dispatch_async(_URLCacheQueue, ^{
+            _scheduledDecodes--;
+            _activeBytes -= decodedImageBytes;
+            [self dequeueTasks];
+          });
+        });
+      };
+
+      // The decode operation retains the compressed image data until it's
+      // complete, so we'll mark it as having started, in order to block
+      // further image loads from happening until we're done with the data.
+      _scheduledDecodes++;
+
+      if (!_pendingDecodes) {
+        _pendingDecodes = [NSMutableArray new];
       }
-      UIImage *image = [UIImage imageWithData:data scale:scale];
-      if (image) {
-        completionHandler(nil, image);
+      NSInteger activeDecodes = _scheduledDecodes - _pendingDecodes.count - 1;
+      if (activeDecodes == 0 || (_activeBytes <= _maxConcurrentDecodingBytes &&
+                                 activeDecodes <= _maxConcurrentDecodingTasks)) {
+        decodeBlock();
       } else {
-        NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
-        NSError *finalError = RCTErrorWithMessage(errorMessage);
-        completionHandler(finalError, nil);
+        [_pendingDecodes addObject:decodeBlock];
       }
+
     });
 
     return ^{
@@ -358,14 +607,44 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
   }
 }
 
+- (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag
+                                          block:(void(^)(NSError *error, CGSize size))completionBlock
+{
+  return [self loadImageOrDataWithTag:imageTag
+                                 size:CGSizeZero
+                                scale:1
+                           resizeMode:RCTResizeModeStretch
+                        progressBlock:nil
+                      completionBlock:^(NSError *error, id imageOrData) {
+                        CGSize size;
+                        if ([imageOrData isKindOfClass:[NSData class]]) {
+                          NSDictionary *meta = RCTGetImageMetadata(imageOrData);
+                          size = (CGSize){
+                            [meta[(id)kCGImagePropertyPixelWidth] doubleValue],
+                            [meta[(id)kCGImagePropertyPixelHeight] doubleValue],
+                          };
+                        } else {
+                          UIImage *image = imageOrData;
+                          size = (CGSize){
+                            image.size.width * image.scale,
+                            image.size.height * image.scale,
+                          };
+                        }
+                        completionBlock(error, size);
+                      }];
+}
+
 #pragma mark - RCTURLRequestHandler
 
 - (BOOL)canHandleRequest:(NSURLRequest *)request
 {
   NSURL *requestURL = request.URL;
-  for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
-    if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
-        [(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
+  for (id<RCTImageURLLoader> loader in _loaders) {
+    // Don't use RCTImageURLLoader protocol for modules that already conform to
+    // RCTURLRequestHandler as it's inefficient to decode an image and then
+    // convert it back into data
+    if (![loader conformsToProtocol:@protocol(RCTURLRequestHandler)] &&
+        [loader canLoadImageURL:requestURL]) {
       return YES;
     }
   }
@@ -417,7 +696,7 @@ @implementation RCTBridge (RCTImageLoader)
 
 - (RCTImageLoader *)imageLoader
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
+  return [self moduleForClass:[RCTImageLoader class]];
 }
 
 @end
diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m
index f34a13e8d653..4c6441a64cda 100644
--- a/Libraries/Image/RCTImageStoreManager.m
+++ b/Libraries/Image/RCTImageStoreManager.m
@@ -30,15 +30,6 @@ @implementation RCTImageStoreManager
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _store = [NSMutableDictionary new];
-    _id = 0;
-  }
-  return self;
-}
-
 - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
 {
   dispatch_async(_methodQueue, ^{
@@ -52,6 +43,12 @@ - (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
 - (NSString *)_storeImageData:(NSData *)imageData
 {
   RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread");
+
+  if (!_store) {
+    _store = [NSMutableDictionary new];
+    _id = 0;
+  }
+
   NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++];
   _store[imageTag] = imageData;
   return imageTag;
@@ -89,6 +86,12 @@ - (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))bloc
   [_store removeObjectForKey:imageTag];
 }
 
+RCT_EXPORT_METHOD(hasImageForTag:(NSString *)imageTag
+                  callback:(RCTResponseSenderBlock)callback)
+{
+  callback(@[@(_store[imageTag] != nil)]);
+}
+
 // TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag:?
 RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag
                   successCallback:(RCTResponseSenderBlock)successCallback
@@ -213,9 +216,9 @@ - (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))
   RCTAssertParam(block);
   dispatch_async(_methodQueue, ^{
     NSData *imageData = _store[imageTag];
-    UIImage *image = [UIImage imageWithData:imageData];
     dispatch_async(dispatch_get_main_queue(), ^{
-      block(image);
+      // imageWithData: is not thread-safe, so we can't do this on methodQueue
+      block([UIImage imageWithData:imageData]);
     });
   });
 }
@@ -226,7 +229,7 @@ @implementation RCTBridge (RCTImageStoreManager)
 
 - (RCTImageStoreManager *)imageStoreManager
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTImageStoreManager class])];
+  return [self moduleForClass:[RCTImageStoreManager class]];
 }
 
 @end
diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h
index 901a876ab027..0c855790e400 100644
--- a/Libraries/Image/RCTImageUtils.h
+++ b/Libraries/Image/RCTImageUtils.h
@@ -11,26 +11,36 @@
 #import <UIKit/UIKit.h>
 
 #import "RCTDefines.h"
+#import "RCTResizeMode.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 /**
- * This function takes an input content size (typically from an image), a target
- * size and scale that it will be drawn at (typically in a CGContext) and then
+ * This function takes an source size (typically from an image), a target size
+ * and scale that it will be drawn at (typically in a CGContext) and then
  * calculates the rectangle to draw the image into so that it will be sized and
- * positioned correctly if drawn using the specified content mode.
+ * positioned correctly according to the specified resizeMode.
  */
 RCT_EXTERN CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
-                                CGFloat destScale, UIViewContentMode resizeMode);
+                                CGFloat destScale, RCTResizeMode resizeMode);
+
+/**
+ * This function takes a source size (typically from an image), a target rect
+ * that it will be drawn into (typically relative to a CGContext), and works out
+ * the transform needed to draw the image at the correct scale and position.
+ */
+RCT_EXTERN CGAffineTransform RCTTransformFromTargetRect(CGSize sourceSize,
+                                                        CGRect targetRect);
 
 /**
  * This function takes an input content size & scale (typically from an image),
  * a target size & scale at which it will be displayed (typically in a
  * UIImageView) and then calculates the optimal size at which to redraw the
- * image so that it will be displayed correctly with the specified content mode.
+ * image so that it will be displayed correctly with the specified resizeMode.
  */
 RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
                                 CGSize destSize, CGFloat destScale,
-                                UIViewContentMode resizeMode,
-                                BOOL allowUpscaling);
+                                RCTResizeMode resizeMode, BOOL allowUpscaling);
 
 /**
  * This function takes an input content size & scale (typically from an image),
@@ -39,12 +49,7 @@ RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
  */
 RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
                                      CGSize destSize, CGFloat destScale,
-                                     UIViewContentMode resizeMode);
-/**
- * This function takes a source size and scale and returns the size in pixels.
- * Note that the pixel width/height is rounded up to the nearest integral size.
- */
-RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
+                                     RCTResizeMode resizeMode);
 
 /**
  * This function takes the source data for an image and decodes it at the
@@ -53,10 +58,16 @@ RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
  * width/height of the returned image is guaranteed to be >= destSize.
  * Pass a destSize of CGSizeZero to decode the image at its original size.
  */
-RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
-                                           CGSize destSize,
-                                           CGFloat destScale,
-                                           UIViewContentMode resizeMode);
+RCT_EXTERN UIImage *__nullable RCTDecodeImageWithData(NSData *data,
+                                                      CGSize destSize,
+                                                      CGFloat destScale,
+                                                      RCTResizeMode resizeMode);
+
+/**
+ * This function takes the source data for an image and decodes just the
+ * metadata, without decompressing the image itself.
+ */
+RCT_EXTERN NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *data);
 
 /**
  * Convert an image back into data. Images with an alpha channel will be
@@ -65,4 +76,28 @@ RCT_EXTERN UIImage *RCTDecodeImageWithData(NSData *data,
  * conversion, with 1.0 being maximum quality. It has no effect for images
  * using PNG compression.
  */
-RCT_EXTERN NSData *RCTGetImageData(CGImageRef image, float quality);
+RCT_EXTERN NSData *__nullable RCTGetImageData(CGImageRef image, float quality);
+
+/**
+ * This function transforms an image. `destSize` is the size of the final image,
+ * and `destScale` is its scale. The `transform` argument controls how the
+ * source image will be mapped to the destination image.
+ */
+RCT_EXTERN UIImage *__nullable RCTTransformImage(UIImage *image,
+                                                 CGSize destSize,
+                                                 CGFloat destScale,
+                                                 CGAffineTransform transform);
+
+/*
+ * Return YES if image has an alpha component
+ */
+RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
+
+/**
+ * Create a solid placeholder image of the specified size and color to display
+ * while loading an image. If color is not specified, image will be transparent.
+ */
+RCT_EXTERN UIImage *__nullable RCTGetPlaceholderImage(CGSize size,
+                                                      UIColor *__nullable color);
+
+NS_ASSUME_NONNULL_END
diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m
index c05bf526b96c..5aeefad1247d 100644
--- a/Libraries/Image/RCTImageUtils.m
+++ b/Libraries/Image/RCTImageUtils.m
@@ -16,6 +16,8 @@
 #import "RCTLog.h"
 #import "RCTUtils.h"
 
+static const CGFloat RCTThresholdValue = 0.0001;
+
 static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
 {
   return ceil(value * scale) / scale;
@@ -35,7 +37,7 @@ static CGSize RCTCeilSize(CGSize size, CGFloat scale)
 }
 
 CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
-                     CGFloat destScale, UIViewContentMode resizeMode)
+                     CGFloat destScale, RCTResizeMode resizeMode)
 {
   if (CGSizeEqualToSize(destSize, CGSizeZero)) {
     // Assume we require the largest size available
@@ -58,16 +60,16 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
   if (resizeMode != UIViewContentModeScaleToFill) {
     targetAspect = destSize.width / destSize.height;
     if (aspect == targetAspect) {
-      resizeMode = UIViewContentModeScaleToFill;
+      resizeMode = RCTResizeModeStretch;
     }
   }
 
   switch (resizeMode) {
-    case UIViewContentModeScaleToFill: // stretch
+    case RCTResizeModeStretch:
 
       return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
 
-    case UIViewContentModeScaleAspectFit: // contain
+    case RCTResizeModeContain:
 
       if (targetAspect <= aspect) { // target is taller than content
 
@@ -79,9 +81,15 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
         sourceSize.height = destSize.height = destSize.height;
         sourceSize.width = sourceSize.height * aspect;
       }
-      return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)};
+      return (CGRect){
+        {
+          RCTFloorValue((destSize.width - sourceSize.width) / 2, destScale),
+          RCTFloorValue((destSize.height - sourceSize.height) / 2, destScale),
+        },
+        RCTCeilSize(sourceSize, destScale)
+      };
 
-    case UIViewContentModeScaleAspectFill: // cover
+    case RCTResizeModeCover:
 
       if (targetAspect <= aspect) { // target is taller than content
 
@@ -103,21 +111,28 @@ CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize,
           RCTCeilSize(sourceSize, destScale)
         };
       }
-
-    default:
-
-      RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
-      return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)};
   }
 }
 
+CGAffineTransform RCTTransformFromTargetRect(CGSize sourceSize, CGRect targetRect)
+{
+  CGAffineTransform transform = CGAffineTransformIdentity;
+  transform = CGAffineTransformTranslate(transform,
+                                         targetRect.origin.x,
+                                         targetRect.origin.y);
+  transform = CGAffineTransformScale(transform,
+                                     targetRect.size.width / sourceSize.width,
+                                     targetRect.size.height / sourceSize.height);
+  return transform;
+}
+
 CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
                      CGSize destSize, CGFloat destScale,
-                     UIViewContentMode resizeMode,
+                     RCTResizeMode resizeMode,
                      BOOL allowUpscaling)
 {
   switch (resizeMode) {
-    case UIViewContentModeScaleToFill: // stretch
+    case RCTResizeModeStretch:
 
       if (!allowUpscaling) {
         CGFloat scale = sourceScale / destScale;
@@ -143,7 +158,7 @@ CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale,
 
 BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
                           CGSize destSize, CGFloat destScale,
-                          UIViewContentMode resizeMode)
+                          RCTResizeMode resizeMode)
 {
   if (CGSizeEqualToSize(destSize, CGSizeZero)) {
     // Assume we require the largest size available
@@ -161,16 +176,16 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
     aspect = sourceSize.width / sourceSize.height;
     targetAspect = destSize.width / destSize.height;
     if (aspect == targetAspect) {
-      resizeMode = UIViewContentModeScaleToFill;
+      resizeMode = RCTResizeModeStretch;
     }
   }
 
   switch (resizeMode) {
-    case UIViewContentModeScaleToFill: // stretch
+    case RCTResizeModeStretch:
 
       return destSize.width > sourceSize.width || destSize.height > sourceSize.height;
 
-    case UIViewContentModeScaleAspectFit: // contain
+    case RCTResizeModeContain:
 
       if (targetAspect <= aspect) { // target is taller than content
 
@@ -181,7 +196,7 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
         return destSize.height > sourceSize.height;
       }
 
-    case UIViewContentModeScaleAspectFill: // cover
+    case RCTResizeModeCover:
 
       if (targetAspect <= aspect) { // target is taller than content
 
@@ -191,34 +206,20 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
 
         return destSize.width > sourceSize.width;
       }
-
-    default:
-
-      RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
-      return NO;
   }
 }
 
-CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
-{
-  return (CGSize){
-    ceil(pointSize.width * scale),
-    ceil(pointSize.height * scale),
-  };
-}
-
-UIImage *RCTDecodeImageWithData(NSData *data,
-                                CGSize destSize,
-                                CGFloat destScale,
-                                UIViewContentMode resizeMode)
+UIImage *__nullable RCTDecodeImageWithData(NSData *data,
+                                           CGSize destSize,
+                                           CGFloat destScale,
+                                           RCTResizeMode resizeMode)
 {
   CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
   if (!sourceRef) {
     return nil;
   }
 
-  // get original image size
-  CGSize sourceSize;
+  // Get original image size
   CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
   if (!imageProperties) {
     CFRelease(sourceRef);
@@ -226,15 +227,26 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
   }
   NSNumber *width = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
   NSNumber *height = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
-  sourceSize = (CGSize){width.doubleValue, height.doubleValue};
+  CGSize sourceSize = {width.doubleValue, height.doubleValue};
   CFRelease(imageProperties);
 
   if (CGSizeEqualToSize(destSize, CGSizeZero)) {
     destSize = sourceSize;
+    if (!destScale) {
+      destScale = 1;
+    }
+  } else if (!destScale) {
+    destScale = RCTScreenScale();
   }
 
-  // calculate target size
-  CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, YES);
+  if (resizeMode == UIViewContentModeScaleToFill) {
+    // Decoder cannot change aspect ratio, so RCTResizeModeStretch is equivalent
+    // to RCTResizeModeCover for our purposes
+    resizeMode = RCTResizeModeCover;
+  }
+
+  // Calculate target size
+  CGSize targetSize = RCTTargetSize(sourceSize, 1, destSize, destScale, resizeMode, NO);
   CGSize targetPixelSize = RCTSizeInPixels(targetSize, destScale);
   CGFloat maxPixelSize = fmax(fmin(sourceSize.width, targetPixelSize.width),
                               fmin(sourceSize.height, targetPixelSize.height));
@@ -246,25 +258,33 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
     (id)kCGImageSourceThumbnailMaxPixelSize: @(maxPixelSize),
   };
 
-  // get thumbnail
+  // Get thumbnail
   CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
   CFRelease(sourceRef);
   if (!imageRef) {
     return nil;
   }
 
-  // adjust scale
-  size_t actualWidth = CGImageGetWidth(imageRef);
-  CGFloat scale = actualWidth / targetSize.width;
-  // return image
+  // Return image
   UIImage *image = [UIImage imageWithCGImage:imageRef
-                                       scale:scale
+                                       scale:destScale
                                  orientation:UIImageOrientationUp];
   CGImageRelease(imageRef);
   return image;
 }
 
-NSData *RCTGetImageData(CGImageRef image, float quality)
+NSDictionary<NSString *, id> *__nullable RCTGetImageMetadata(NSData *data)
+{
+  CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
+  if (!sourceRef) {
+    return nil;
+  }
+  CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, NULL);
+  CFRelease(sourceRef);
+  return (__bridge_transfer id)imageProperties;
+}
+
+NSData *__nullable RCTGetImageData(CGImageRef image, float quality)
 {
   NSDictionary *properties;
   CGImageDestinationRef destination;
@@ -286,3 +306,77 @@ CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
   CFRelease(destination);
   return (__bridge_transfer NSData *)imageData;
 }
+
+UIImage *__nullable RCTTransformImage(UIImage *image,
+                                      CGSize destSize,
+                                      CGFloat destScale,
+                                      CGAffineTransform transform)
+{
+  if (destSize.width <= 0 | destSize.height <= 0 || destScale <= 0) {
+    return nil;
+  }
+
+  BOOL opaque = !RCTImageHasAlpha(image.CGImage);
+  UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
+  CGContextRef currentContext = UIGraphicsGetCurrentContext();
+  CGContextConcatCTM(currentContext, transform);
+  [image drawAtPoint:CGPointZero];
+  UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return result;
+}
+
+BOOL RCTImageHasAlpha(CGImageRef image)
+{
+  switch (CGImageGetAlphaInfo(image)) {
+    case kCGImageAlphaNone:
+    case kCGImageAlphaNoneSkipLast:
+    case kCGImageAlphaNoneSkipFirst:
+      return NO;
+    default:
+      return YES;
+  }
+}
+
+UIImage *__nullable RCTGetPlaceholderImage(CGSize size,
+                                           UIColor *__nullable color)
+{
+  if (size.width <= 0 || size.height <= 0) {
+    return nil;
+  }
+
+  // If dimensions are nonintegral, increase scale
+  CGFloat scale = 1;
+  if (size.width - floor(size.width) > RCTThresholdValue) {
+    scale *= round(1.0 / (size.width - floor(size.width)));
+  }
+  if (size.height - floor(size.height) > RCTThresholdValue) {
+    scale *= round(1.0 / (size.height - floor(size.height)));
+  }
+
+  // Use Euclid's algorithm to find the greatest common divisor
+  // between the specified placeholder width and height;
+  NSInteger a = size.width * scale;
+  NSInteger b = size.height * scale;
+  while (a != 0) {
+    NSInteger c = a;
+    a = b % a;
+    b = c;
+  }
+
+  // Divide the placeholder image scale by the GCD we found above. This allows
+  // us to save memory by creating the smallest possible placeholder image
+  // with the correct aspect ratio, then scaling it up at display time.
+  scale /= b;
+
+  // Fill image with specified color
+  CGFloat alpha = CGColorGetAlpha(color.CGColor);
+  UIGraphicsBeginImageContextWithOptions(size, ABS(1.0 - alpha) < RCTThresholdValue, scale);
+  if (alpha > 0) {
+    [color setFill];
+    UIRectFill((CGRect){CGPointZero, size});
+  }
+  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+  return image;
+}
diff --git a/Libraries/Image/RCTImageView.h b/Libraries/Image/RCTImageView.h
index a561c0f7f938..769f9c965d1e 100644
--- a/Libraries/Image/RCTImageView.h
+++ b/Libraries/Image/RCTImageView.h
@@ -9,8 +9,10 @@
 
 #import <UIKit/UIKit.h>
 #import "RCTImageComponent.h"
+#import "RCTResizeMode.h"
 
 @class RCTBridge;
+@class RCTImageSource;
 
 @interface RCTImageView : UIImageView <RCTImageComponent>
 
@@ -19,6 +21,6 @@
 @property (nonatomic, assign) UIEdgeInsets capInsets;
 @property (nonatomic, strong) UIImage *defaultImage;
 @property (nonatomic, assign) UIImageRenderingMode renderingMode;
-@property (nonatomic, copy) NSString *src;
+@property (nonatomic, strong) RCTImageSource *source;
 
 @end
diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m
index 246220840614..36506d348d45 100644
--- a/Libraries/Image/RCTImageView.m
+++ b/Libraries/Image/RCTImageView.m
@@ -13,6 +13,7 @@
 #import "RCTConvert.h"
 #import "RCTEventDispatcher.h"
 #import "RCTImageLoader.h"
+#import "RCTImageSource.h"
 #import "RCTImageUtils.h"
 #import "RCTUtils.h"
 
@@ -102,8 +103,15 @@ - (void)setImage:(UIImage *)image
 - (void)setCapInsets:(UIEdgeInsets)capInsets
 {
   if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
-    _capInsets = capInsets;
-    [self updateImage];
+    if (UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero) ||
+        UIEdgeInsetsEqualToEdgeInsets(capInsets, UIEdgeInsetsZero)) {
+      _capInsets = capInsets;
+      // Need to reload image when enabling or disabling capInsets
+      [self reloadImage];
+    } else {
+      _capInsets = capInsets;
+      [self updateImage];
+    }
   }
 }
 
@@ -115,28 +123,25 @@ - (void)setRenderingMode:(UIImageRenderingMode)renderingMode
   }
 }
 
-- (void)setSrc:(NSString *)src
+- (void)setSource:(RCTImageSource *)source
 {
-  if (![src isEqual:_src]) {
-    _src = [src copy];
+  if (![source isEqual:_source]) {
+    _source = source;
     [self reloadImage];
   }
 }
 
-+ (BOOL)srcNeedsReload:(NSString *)src
+- (BOOL)sourceNeedsReload
 {
-  return
-    [src hasPrefix:@"http://"] ||
-    [src hasPrefix:@"https://"] ||
-    [src hasPrefix:@"assets-library://"] ||
-    [src hasPrefix:@"ph://"];
+  // If capInsets are set, image doesn't need reloading when resized
+  return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
 }
 
 - (void)setContentMode:(UIViewContentMode)contentMode
 {
   if (self.contentMode != contentMode) {
     super.contentMode = contentMode;
-    if ([RCTImageView srcNeedsReload:_src]) {
+    if ([self sourceNeedsReload]) {
       [self reloadImage];
     }
   }
@@ -162,7 +167,7 @@ - (void)reloadImage
 {
   [self cancelImageLoad];
 
-  if (_src && self.frame.size.width > 0 && self.frame.size.height > 0) {
+  if (_source && self.frame.size.width > 0 && self.frame.size.height > 0) {
     if (_onLoadStart) {
       _onLoadStart(nil);
     }
@@ -177,31 +182,45 @@ - (void)reloadImage
       };
     }
 
-    _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_src
-                                                                     size:self.bounds.size
-                                                                    scale:RCTScreenScale()
-                                                               resizeMode:self.contentMode
-                                                            progressBlock:progressHandler
-                                                          completionBlock:^(NSError *error, UIImage *image) {
-      if (error) {
-        if (_onError) {
-          _onError(@{ @"error": error.localizedDescription });
-        }
-      } else {
-        if (_onLoad) {
-          _onLoad(nil);
-        }
-      }
-      if (_onLoadEnd) {
-         _onLoadEnd(nil);
-      }
+    CGSize imageSize = self.bounds.size;
+    CGFloat imageScale = RCTScreenScale();
+    if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
+      // Don't resize images that use capInsets
+      imageSize = CGSizeZero;
+      imageScale = _source.scale;
+    }
 
+    RCTImageSource *source = _source;
+    __weak RCTImageView *weakSelf = self;
+    _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithoutClipping:_source.imageURL.absoluteString
+                                                                             size:imageSize
+                                                                            scale:imageScale
+                                                                       resizeMode:(RCTResizeMode)self.contentMode
+                                                                    progressBlock:progressHandler
+                                                                  completionBlock:^(NSError *error, UIImage *image) {
       dispatch_async(dispatch_get_main_queue(), ^{
+        RCTImageView *strongSelf = weakSelf;
+        if (![source isEqual:strongSelf.source]) {
+          // Bail out if source has changed since we started loading
+          return;
+        }
         if (image.reactKeyframeAnimation) {
-          [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
+          [strongSelf.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
         } else {
-          [self.layer removeAnimationForKey:@"contents"];
-          self.image = image;
+          [strongSelf.layer removeAnimationForKey:@"contents"];
+          strongSelf.image = image;
+        }
+        if (error) {
+          if (strongSelf->_onError) {
+            strongSelf->_onError(@{ @"error": error.localizedDescription });
+          }
+        } else {
+          if (strongSelf->_onLoad) {
+            strongSelf->_onLoad(nil);
+          }
+        }
+        if (strongSelf->_onLoadEnd) {
+           strongSelf->_onLoadEnd(nil);
         }
       });
     }];
@@ -217,16 +236,17 @@ - (void)reactSetFrame:(CGRect)frame
   if (!self.image || self.image == _defaultImage) {
     _targetSize = frame.size;
     [self reloadImage];
-  } else if ([RCTImageView srcNeedsReload:_src]) {
+  } else if ([self sourceNeedsReload]) {
     CGSize imageSize = self.image.size;
-    CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, RCTScreenScale(), self.contentMode, YES);
+    CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size,
+                                     RCTScreenScale(), (RCTResizeMode)self.contentMode, YES);
 
     if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) {
       if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
-        RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _src, NSStringFromCGSize(idealSize));
+        RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.imageURL, NSStringFromCGSize(idealSize));
 
-        // If the existing image or an image being loaded are not the right size, reload the asset in case there is a
-        // better size available.
+        // If the existing image or an image being loaded are not the right
+        // size, reload the asset in case there is a better size available.
         _targetSize = idealSize;
         [self reloadImage];
       }
@@ -243,8 +263,8 @@ - (void)didMoveToWindow
   [super didMoveToWindow];
 
   if (!self.window) {
-    // Don't keep self alive through the asynchronous dispatch, if the intention was to remove the view so it would
-    // deallocate.
+    // Don't keep self alive through the asynchronous dispatch, if the intention
+    // was to remove the view so it would deallocate.
     __weak typeof(self) weakSelf = self;
 
     dispatch_async(dispatch_get_main_queue(), ^{
@@ -253,7 +273,8 @@ - (void)didMoveToWindow
         return;
       }
 
-      // If we haven't been re-added to a window by this run loop iteration, clear out the image to save memory.
+      // If we haven't been re-added to a window by this run loop iteration,
+      // clear out the image to save memory.
       if (!strongSelf.window) {
         [strongSelf clearImage];
       }
diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m
index bcf2f0bdb4d0..65e18b5a2a9c 100644
--- a/Libraries/Image/RCTImageViewManager.m
+++ b/Libraries/Image/RCTImageViewManager.m
@@ -12,6 +12,8 @@
 #import <UIKit/UIKit.h>
 
 #import "RCTConvert.h"
+#import "RCTImageLoader.h"
+#import "RCTImageSource.h"
 #import "RCTImageView.h"
 
 @implementation RCTImageViewManager
@@ -24,23 +26,35 @@ - (UIView *)view
 }
 
 RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
-RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
-RCT_EXPORT_VIEW_PROPERTY(src, NSString)
+RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage)
 RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
+RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, RCTResizeMode)
+RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource)
 RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
 {
-  if (json) {
-    view.renderingMode = UIImageRenderingModeAlwaysTemplate;
-    view.tintColor = [RCTConvert UIColor:json];
-  } else {
-    view.renderingMode = defaultView.renderingMode;
-    view.tintColor = defaultView.tintColor;
-  }
+  // Default tintColor isn't nil - it's inherited from the superView - but we
+  // want to treat a null json value for `tintColor` as meaning 'disable tint',
+  // so we toggle `renderingMode` here instead of in `-[RCTImageView setTintColor:]`
+  view.tintColor = [RCTConvert UIColor:json] ?: defaultView.tintColor;
+  view.renderingMode = json ? UIImageRenderingModeAlwaysTemplate : defaultView.renderingMode;
+}
+
+RCT_EXPORT_METHOD(getSize:(NSURL *)imageURL
+                  successBlock:(RCTResponseSenderBlock)successBlock
+                  errorBlock:(RCTResponseErrorBlock)errorBlock)
+{
+  [self.bridge.imageLoader getImageSize:imageURL.absoluteString
+                                  block:^(NSError *error, CGSize size) {
+                                    if (error) {
+                                      errorBlock(error);
+                                    } else {
+                                      successBlock(@[@(size.width), @(size.height)]);
+                                    }
+                                  }];
 }
 
 @end
diff --git a/React/Modules/RCTPasteboard.m b/Libraries/Image/RCTResizeMode.h
similarity index 50%
rename from React/Modules/RCTPasteboard.m
rename to Libraries/Image/RCTResizeMode.h
index 41ad7c044753..81e9d925fa9d 100644
--- a/React/Modules/RCTPasteboard.m
+++ b/Libraries/Image/RCTResizeMode.h
@@ -7,22 +7,16 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTPasteboard.h"
+#import "RCTConvert.h"
 
-#import <UIKit/UIKit.h>
+typedef NS_ENUM(NSInteger, RCTResizeMode) {
+  RCTResizeModeCover = UIViewContentModeScaleAspectFill,
+  RCTResizeModeContain = UIViewContentModeScaleAspectFit,
+  RCTResizeModeStretch = UIViewContentModeScaleToFill,
+};
 
-@implementation RCTPasteboard
+@interface RCTConvert(RCTResizeMode)
 
-RCT_EXPORT_MODULE()
-
-- (dispatch_queue_t)methodQueue
-{
-  return dispatch_get_main_queue();
-}
-
-RCT_EXPORT_METHOD(setPasteboardString:(NSString *)string)
-{
-  [[UIPasteboard generalPasteboard] setString:string];
-}
++ (RCTResizeMode)RCTResizeMode:(id)json;
 
 @end
diff --git a/Libraries/Image/RCTResizeMode.m b/Libraries/Image/RCTResizeMode.m
new file mode 100644
index 000000000000..b80839bb4426
--- /dev/null
+++ b/Libraries/Image/RCTResizeMode.m
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+#import "RCTResizeMode.h"
+
+@implementation RCTConvert(RCTResizeMode)
+
+RCT_ENUM_CONVERTER(RCTResizeMode, (@{
+  @"cover": @(RCTResizeModeCover),
+  @"contain": @(RCTResizeModeContain),
+  @"stretch": @(RCTResizeModeStretch),
+}), RCTResizeModeStretch, integerValue)
+
+@end
diff --git a/Libraries/Image/RCTShadowVirtualImage.h b/Libraries/Image/RCTShadowVirtualImage.h
index 841c215e2b4f..b9623f736f23 100644
--- a/Libraries/Image/RCTShadowVirtualImage.h
+++ b/Libraries/Image/RCTShadowVirtualImage.h
@@ -9,6 +9,8 @@
 
 #import "RCTShadowView.h"
 #import "RCTImageComponent.h"
+#import "RCTImageSource.h"
+#import "RCTResizeMode.h"
 
 @class RCTBridge;
 
@@ -20,6 +22,7 @@
 
 - (instancetype)initWithBridge:(RCTBridge *)bridge;
 
-@property (nonatomic, copy) NSDictionary *source;
+@property (nonatomic, strong) RCTImageSource *source;
+@property (nonatomic, assign) RCTResizeMode resizeMode;
 
 @end
diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m
index 431fbac07afe..f950e63774a5 100644
--- a/Libraries/Image/RCTShadowVirtualImage.m
+++ b/Libraries/Image/RCTShadowVirtualImage.m
@@ -9,13 +9,16 @@
 
 #import "RCTShadowVirtualImage.h"
 #import "RCTImageLoader.h"
+#import "RCTImageUtils.h"
 #import "RCTBridge.h"
 #import "RCTConvert.h"
 #import "RCTUIManager.h"
+#import "RCTUtils.h"
 
 @implementation RCTShadowVirtualImage
 {
   RCTBridge *_bridge;
+  RCTImageLoaderCancellationBlock _cancellationBlock;
 }
 
 @synthesize image = _image;
@@ -30,27 +33,53 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
 
 RCT_NOT_IMPLEMENTED(-(instancetype)init)
 
-- (void)setSource:(NSDictionary *)source
+- (void)didSetProps:(NSArray<NSString *> *)changedProps
 {
-  if (![source isEqual:_source]) {
-    _source = [source copy];
-    NSString *imageTag = [RCTConvert NSString:_source[@"uri"]];
-    CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1;
-
-    __weak RCTShadowVirtualImage *weakSelf = self;
-    [_bridge.imageLoader loadImageWithTag:imageTag
-                                     size:CGSizeZero
-                                    scale:scale
-                               resizeMode:UIViewContentModeScaleToFill
-                            progressBlock:nil
-                          completionBlock:^(NSError *error, UIImage *image) {
-
-      dispatch_async(_bridge.uiManager.methodQueue, ^{
-        RCTShadowVirtualImage *strongSelf = weakSelf;
-        strongSelf->_image = image;
-        [strongSelf dirtyText];
-      });
-    }];
+  [super didSetProps:changedProps];
+
+  if (changedProps.count == 0) {
+    // No need to reload image
+    return;
+  }
+
+  // Cancel previous request
+  if (_cancellationBlock) {
+    _cancellationBlock();
+  }
+
+  CGSize imageSize = {
+    RCTZeroIfNaN(self.width),
+    RCTZeroIfNaN(self.height),
+  };
+
+  if (!_image) {
+    _image = RCTGetPlaceholderImage(imageSize, nil);
+  }
+
+  __weak RCTShadowVirtualImage *weakSelf = self;
+  _cancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
+                                                        size:imageSize
+                                                       scale:RCTScreenScale()
+                                                  resizeMode:_resizeMode
+                                               progressBlock:nil
+                                             completionBlock:^(NSError *error, UIImage *image) {
+
+    dispatch_async(_bridge.uiManager.methodQueue, ^{
+      RCTShadowVirtualImage *strongSelf = weakSelf;
+      if (![_source isEqual:strongSelf.source]) {
+        // Bail out if source has changed since we started loading
+        return;
+      }
+      strongSelf->_image = image;
+      [strongSelf dirtyText];
+    });
+  }];
+}
+
+- (void)dealloc
+{
+  if (_cancellationBlock) {
+    _cancellationBlock();
   }
 }
 
diff --git a/Libraries/Image/RCTVirtualImageManager.m b/Libraries/Image/RCTVirtualImageManager.m
index 2800a92b0d8b..6311010f4ce2 100644
--- a/Libraries/Image/RCTVirtualImageManager.m
+++ b/Libraries/Image/RCTVirtualImageManager.m
@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView
   return [[RCTShadowVirtualImage alloc] initWithBridge:self.bridge];
 }
 
-RCT_EXPORT_SHADOW_PROPERTY(source, NSDictionary)
+RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource)
+RCT_EXPORT_SHADOW_PROPERTY(resizeMode, UIViewContentMode)
 
 @end
diff --git a/Libraries/Image/RCTXCAssetImageLoader.m b/Libraries/Image/RCTXCAssetImageLoader.m
index 799798a36629..750a1f848ed1 100644
--- a/Libraries/Image/RCTXCAssetImageLoader.m
+++ b/Libraries/Image/RCTXCAssetImageLoader.m
@@ -25,7 +25,7 @@ - (BOOL)canLoadImageURL:(NSURL *)requestURL
  - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
                                                size:(CGSize)size
                                               scale:(CGFloat)scale
-                                         resizeMode:(UIViewContentMode)resizeMode
+                                         resizeMode:(RCTResizeMode)resizeMode
                                     progressHandler:(RCTImageLoaderProgressBlock)progressHandler
                                   completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
 {
diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js
index 81fdb2b08dc9..b406316a1a90 100644
--- a/Libraries/Image/__tests__/resolveAssetSource-test.js
+++ b/Libraries/Image/__tests__/resolveAssetSource-test.js
@@ -10,7 +10,8 @@
 
 jest
   .dontMock('AssetRegistry')
-  .dontMock('../resolveAssetSource');
+  .dontMock('../resolveAssetSource')
+  .dontMock('../../../local-cli/bundle/assetPathUtils');
 
 var AssetRegistry = require('AssetRegistry');
 var Platform = require('Platform');
@@ -132,10 +133,10 @@ describe('resolveAssetSource', () => {
     });
   });
 
-  describe('bundle was loaded from file on Android', () => {
+  describe('bundle was loaded from assets on Android', () => {
     beforeEach(() => {
       NativeModules.SourceCode.scriptURL =
-        'file:///Path/To/Simulator/main.bundle';
+        'assets://Path/To/Simulator/main.bundle';
       Platform.OS = 'android';
     });
 
@@ -159,6 +160,34 @@ describe('resolveAssetSource', () => {
       });
     });
   });
+  
+  describe('bundle was loaded from file on Android', () => {
+    beforeEach(() => {
+      NativeModules.SourceCode.scriptURL =
+        'file:///sdcard/Path/To/Simulator/main.bundle';
+      Platform.OS = 'android';
+    });
+
+    it('uses pre-packed image', () => {
+      expectResolvesAsset({
+        __packager_asset: true,
+        fileSystemLocation: '/root/app/module/a',
+        httpServerLocation: '/assets/AwesomeModule/Subdir',
+        width: 100,
+        height: 200,
+        scales: [1],
+        hash: '5b6f00f',
+        name: '!@Logo#1_€',
+        type: 'png',
+      }, {
+        __packager_asset: true,
+        width: 100,
+        height: 200,
+        uri: 'file:///sdcard/Path/To/Simulator/drawable-mdpi/awesomemodule_subdir_logo1_.png',
+        scale: 1,
+      });
+    });
+  });
 
 });
 
diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js
index 638b72ce06e4..6527e91df096 100644
--- a/Libraries/Image/resolveAssetSource.js
+++ b/Libraries/Image/resolveAssetSource.js
@@ -25,6 +25,7 @@ var AssetRegistry = require('AssetRegistry');
 var PixelRatio = require('PixelRatio');
 var Platform = require('Platform');
 var SourceCode = require('NativeModules').SourceCode;
+var assetPathUtils = require('../../local-cli/bundle/assetPathUtils');
 
 var _serverURL, _offlinePath;
 
@@ -62,18 +63,18 @@ function getOfflinePath() {
  * Returns the path at which the asset can be found in the archive
  */
 function getPathInArchive(asset) {
+  var offlinePath = getOfflinePath();
   if (Platform.OS === 'android') {
-    var assetDir = getBasePath(asset);
+    if (offlinePath) {
+      // E.g. 'file:///sdcard/AwesomeModule/drawable-mdpi/icon.png'
+      return 'file://' + offlinePath + getAssetPathInDrawableFolder(asset);
+    }
     // E.g. 'assets_awesomemodule_icon'
     // The Android resource system picks the correct scale.
-    return (assetDir + '/' + asset.name)
-      .toLowerCase()
-      .replace(/\//g, '_')           // Encode folder structure in file name
-      .replace(/([^a-z0-9_])/g, '')  // Remove illegal chars
-      .replace(/^assets_/, '');      // Remove "assets_" prefix
+    return assetPathUtils.getAndroidResourceIdentifier(asset);
   } else {
-    // E.g. 'assets/AwesomeModule/icon@2x.png'
-    return getOfflinePath() + getScaledAssetPath(asset);
+    // E.g. '/assets/AwesomeModule/icon@2x.png'
+    return offlinePath + getScaledAssetPath(asset);
   }
 }
 
@@ -86,29 +87,26 @@ function getPathOnDevserver(devServerUrl, asset) {
     '&hash=' + asset.hash;
 }
 
-/**
- * Returns a path like 'assets/AwesomeModule'
- */
-function getBasePath(asset) {
-  // TODO(frantic): currently httpServerLocation is used both as
-  // path in http URL and path within IPA. Should we have zipArchiveLocation?
-  var path = asset.httpServerLocation;
-  if (path[0] === '/') {
-    path = path.substr(1);
-  }
-  return path;
-}
-
 /**
  * Returns a path like 'assets/AwesomeModule/icon@2x.png'
  */
 function getScaledAssetPath(asset) {
   var scale = pickScale(asset.scales, PixelRatio.get());
   var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
-  var assetDir = getBasePath(asset);
+  var assetDir = assetPathUtils.getBasePath(asset);
   return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
 }
 
+/**
+ * Returns a path like 'drawable-mdpi/icon.png'
+ */
+function getAssetPathInDrawableFolder(asset) {
+  var scale = pickScale(asset.scales, PixelRatio.get());
+  var drawbleFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
+  var fileName =  assetPathUtils.getAndroidResourceIdentifier(asset);
+  return drawbleFolder + '/' + fileName + '.' + asset.type;
+}
+
 function pickScale(scales: Array<number>, deviceScale: number): number {
   // Packager guarantees that `scales` array is sorted
   for (var i = 0; i < scales.length; i++) {
diff --git a/Libraries/Inspector/ElementProperties.js b/Libraries/Inspector/ElementProperties.js
index 222da109fec8..1e808d0d1729 100644
--- a/Libraries/Inspector/ElementProperties.js
+++ b/Libraries/Inspector/ElementProperties.js
@@ -47,6 +47,7 @@ var ElementProperties = React.createClass({
               this.props.hierarchy,
               (item, i) => (
                 <TouchableHighlight
+                  key={'item-' + i}
                   style={[styles.breadItem, i === selection && styles.selected]}
                   onPress={() => this.props.setSelection(i)}>
                   <Text style={styles.breadItemText}>
@@ -54,7 +55,11 @@ var ElementProperties = React.createClass({
                   </Text>
                 </TouchableHighlight>
               ),
-              () => <Text style={styles.breadSep}>&#9656;</Text>
+              (i) => (
+                <Text key={'sep-' + i} style={styles.breadSep}>
+                  &#9656;
+                </Text>
+              )
             )}
           </View>
           <View style={styles.row}>
diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js
index 7dbec7443ace..c72da8e7e765 100644
--- a/Libraries/Inspector/Inspector.js
+++ b/Libraries/Inspector/Inspector.js
@@ -98,7 +98,9 @@ class Inspector extends React.Component {
 
   setSelection(i: number) {
     var instance = this.state.hierarchy[i];
-    var publicInstance = instance.getPublicInstance();
+    // if we inspect a stateless component we can't use the getPublicInstance method
+    // therefore we use the internal _instance property directly.
+    var publicInstance = instance._instance || {};
     UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => {
       this.setState({
         inspected: {
@@ -115,7 +117,9 @@ class Inspector extends React.Component {
       this.state.devtoolsAgent.selectFromReactInstance(instance, true);
     }
     var hierarchy = InspectorUtils.getOwnerHierarchy(instance);
-    var publicInstance = instance.getPublicInstance();
+    // if we inspect a stateless component we can't use the getPublicInstance method
+    // therefore we use the internal _instance property directly.
+    var publicInstance = instance._instance || {};
     var props = publicInstance.props || {};
     this.setState({
       panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
diff --git a/Libraries/Inspector/PerformanceOverlay.js b/Libraries/Inspector/PerformanceOverlay.js
index c0be512421a8..3329dc341d37 100644
--- a/Libraries/Inspector/PerformanceOverlay.js
+++ b/Libraries/Inspector/PerformanceOverlay.js
@@ -24,11 +24,12 @@ var PerformanceOverlay = React.createClass({
 
     for (var key in perfLogs) {
       if (perfLogs[key].totalTime) {
+        var unit = (key === 'BundleSize') ? 'b' : 'ms';
         items.push(
           <View style={styles.row}>
             <Text style={[styles.text, styles.label]}>{key}</Text>
             <Text style={[styles.text, styles.totalTime]}>
-              {perfLogs[key].totalTime + 'ms'}
+              {perfLogs[key].totalTime + unit}
             </Text>
           </View>
         );
diff --git a/Libraries/Inspector/StyleInspector.js b/Libraries/Inspector/StyleInspector.js
index 702d01e1da35..96db90e8a076 100644
--- a/Libraries/Inspector/StyleInspector.js
+++ b/Libraries/Inspector/StyleInspector.js
@@ -25,10 +25,14 @@ class StyleInspector extends React.Component {
     return (
       <View style={styles.container}>
         <View>
-          {names.map(name => <Text style={styles.attr}>{name}:</Text>)}
+          {names.map(name => <Text key={name} style={styles.attr}>{name}:</Text>)}
         </View>
+
         <View>
-          {names.map(name => <Text style={styles.value}>{this.props.style[name]}</Text>)}
+          {names.map(name => {
+            var value = typeof this.props.style[name] === 'object' ? JSON.stringify(this.props.style[name]) : this.props.style[name];
+            return <Text key={name} style={styles.value}>{value}</Text>;
+          } ) }
         </View>
       </View>
     );
diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js
index fd4ab67c39b3..9d0547251a21 100644
--- a/Libraries/Interaction/InteractionManager.js
+++ b/Libraries/Interaction/InteractionManager.js
@@ -11,23 +11,19 @@
  */
 'use strict';
 
-var ErrorUtils = require('ErrorUtils');
-var EventEmitter = require('EventEmitter');
-var Set = require('Set');
+const BatchedBridge = require('BatchedBridge');
+const EventEmitter = require('EventEmitter');
+const Set = require('Set');
+const TaskQueue = require('TaskQueue');
 
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
-var setImmediate = require('setImmediate');
+const invariant = require('fbjs/lib/invariant');
+const keyMirror = require('fbjs/lib/keyMirror');
+const setImmediate = require('setImmediate');
 
 type Handle = number;
+import type {Task} from 'TaskQueue';
 
-var _emitter = new EventEmitter();
-var _interactionSet = new Set();
-var _addInteractionSet = new Set();
-var _deleteInteractionSet = new Set();
-var _nextUpdateHandle = null;
-var _queue = [];
-var _inc = 0;
+const _emitter = new EventEmitter();
 
 /**
  * InteractionManager allows long-running work to be scheduled after any
@@ -63,6 +59,20 @@ var _inc = 0;
  * InteractionManager.clearInteractionHandle(handle);
  * // queued tasks run if all handles were cleared
  * ```
+ *
+ * `runAfterInteractions` takes either a plain callback function, or a
+ * `PromiseTask` object with a `gen` method that returns a `Promise`.  If a
+ * `PromiseTask` is supplied, then it is fully resolved (including asynchronous
+ * dependencies that also schedule more tasks via `runAfterInteractions`) before
+ * starting on the next task that might have been queued up synchronously
+ * earlier.
+ *
+ * By default, queued tasks are executed together in a loop in one
+ * `setImmediate` batch. If `setDeadline` is called with a positive number, then
+ * tasks will only be executed until the deadline (in terms of js event loop run
+ * time) approaches, at which point execution will yield via setTimeout,
+ * allowing events such as touches to start interactions and block queued tasks
+ * from executing, making apps more responsive.
  */
 var InteractionManager = {
   Events: keyMirror({
@@ -73,13 +83,14 @@ var InteractionManager = {
   /**
    * Schedule a function to run after all interactions have completed.
    */
-  runAfterInteractions(callback: ?Function): Promise {
+  runAfterInteractions(task: ?Task): Promise {
     return new Promise(resolve => {
-      scheduleUpdate();
-      if (callback) {
-        _queue.push(callback);
+      _scheduleUpdate();
+      if (task) {
+        _taskQueue.enqueue(task);
       }
-      _queue.push(resolve);
+      const name = task && task.name || '?';
+      _taskQueue.enqueue({run: resolve, name: 'resolve ' + name});
     });
   },
 
@@ -87,7 +98,7 @@ var InteractionManager = {
    * Notify manager that an interaction has started.
    */
   createInteractionHandle(): Handle {
-    scheduleUpdate();
+    _scheduleUpdate();
     var handle = ++_inc;
     _addInteractionSet.add(handle);
     return handle;
@@ -101,28 +112,49 @@ var InteractionManager = {
       !!handle,
       'Must provide a handle to clear.'
     );
-    scheduleUpdate();
+    _scheduleUpdate();
     _addInteractionSet.delete(handle);
     _deleteInteractionSet.add(handle);
   },
 
   addListener: _emitter.addListener.bind(_emitter),
+
+  /**
+   * A positive number will use setTimeout to schedule any tasks after the
+   * eventLoopRunningTime hits the deadline value, otherwise all tasks will be
+   * executed in one setImmediate batch (default).
+   */
+  setDeadline(deadline: number) {
+    _deadline = deadline;
+  },
 };
 
+const _interactionSet = new Set();
+const _addInteractionSet = new Set();
+const _deleteInteractionSet = new Set();
+const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
+let _nextUpdateHandle = 0;
+let _inc = 0;
+let _deadline = -1;
+
 /**
  * Schedule an asynchronous update to the interaction state.
  */
-function scheduleUpdate() {
+function _scheduleUpdate() {
   if (!_nextUpdateHandle) {
-    _nextUpdateHandle = setImmediate(processUpdate);
+    if (_deadline > 0) {
+      _nextUpdateHandle = setTimeout(_processUpdate, 0);
+    } else {
+      _nextUpdateHandle = setImmediate(_processUpdate);
+    }
   }
 }
 
 /**
  * Notify listeners, process queue, etc
  */
-function processUpdate() {
-  _nextUpdateHandle = null;
+function _processUpdate() {
+  _nextUpdateHandle = 0;
 
   var interactionCount = _interactionSet.size;
   _addInteractionSet.forEach(handle =>
@@ -143,13 +175,16 @@ function processUpdate() {
 
   // process the queue regardless of a transition
   if (nextInteractionCount === 0) {
-    var queue = _queue;
-    _queue = [];
-    queue.forEach(callback => {
-      ErrorUtils.applyWithGuard(callback);
-    });
+    while (_taskQueue.hasTasksToProcess()) {
+      _taskQueue.processNext();
+      if (_deadline > 0 &&
+          BatchedBridge.getEventLoopRunningTime() >= _deadline) {
+        // Hit deadline before processing all tasks, so process more later.
+        _scheduleUpdate();
+        break;
+      }
+    }
   }
-
   _addInteractionSet.clear();
   _deleteInteractionSet.clear();
 }
diff --git a/Libraries/Interaction/TaskQueue.js b/Libraries/Interaction/TaskQueue.js
new file mode 100644
index 000000000000..f623f7a3ad9d
--- /dev/null
+++ b/Libraries/Interaction/TaskQueue.js
@@ -0,0 +1,147 @@
+/**
+ * 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 TaskQueue
+ * @flow
+ */
+'use strict';
+
+const ErrorUtils = require('ErrorUtils');
+
+const invariant = require('fbjs/lib/invariant');
+
+type SimpleTask = {
+  name: string;
+  run: () => void;
+};
+type PromiseTask = {
+  name: string;
+  gen: () => Promise;
+};
+export type Task = Function | SimpleTask | PromiseTask;
+
+/**
+ * TaskQueue - A system for queueing and executing a mix of simple callbacks and
+ * trees of dependent tasks based on Promises. No tasks are executed unless
+ * `processNext` is called.
+ *
+ * `enqueue` takes a Task object with either a simple `run` callback, or a
+ * `gen` function that returns a `Promise` and puts it in the queue.  If a gen
+ * function is supplied, then the promise it returns will block execution of
+ * tasks already in the queue until it resolves. This can be used to make sure
+ * the first task is fully resolved (including asynchronous dependencies that
+ * also schedule more tasks via `enqueue`) before starting on the next task.
+ * The `onMoreTasks` constructor argument is used to inform the owner that an
+ * async task has resolved and that the queue should be processed again.
+ *
+ * Note: Tasks are only actually executed with explicit calls to `processNext`.
+ */
+class TaskQueue {
+  /**
+   * TaskQueue instances are self contained and independent, so multiple tasks
+   * of varying semantics and priority can operate together.
+   *
+   * `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
+   * tasks to process.
+   */
+  constructor({onMoreTasks}: {onMoreTasks: () => void}) {
+    this._onMoreTasks = onMoreTasks;
+    this._queueStack = [{tasks: [], popable: false}];
+  }
+
+  /**
+   * Add a task to the queue.  It is recommended to name your tasks for easier
+   * async debugging. Tasks will not be executed until `processNext` is called
+   * explicitly.
+   */
+  enqueue(task: Task): void {
+    this._getCurrentQueue().push(task);
+  }
+
+  /**
+   * Check to see if `processNext` should be called.
+   *
+   * @returns {boolean} Returns true if there are tasks that are ready to be
+   * processed with `processNext`, or returns false if there are no more tasks
+   * to be processed right now, although there may be tasks in the queue that
+   * are blocked by earlier `PromiseTask`s that haven't resolved yet.
+   * `onMoreTasks` will be called after each `PromiseTask` resolves if there are
+   * tasks ready to run at that point.
+   */
+  hasTasksToProcess(): bool {
+    return this._getCurrentQueue().length > 0;
+  }
+
+  /**
+   * Executes the next task in the queue.
+   */
+  processNext(): void {
+    let queue = this._getCurrentQueue();
+    if (queue.length) {
+      const task = queue.shift();
+      try {
+        if (task.gen) {
+          this._genPromise((task: any)); // Rather than annoying tagged union
+        } else if (task.run) {
+          task.run();
+        } else {
+          invariant(
+            typeof task === 'function',
+            'Expected Function, SimpleTask, or PromiseTask, but got: ' +
+              JSON.stringify(task)
+          );
+          task();
+        }
+      } catch (e) {
+        e.message = 'TaskQueue: Error with task' + (task.name || ' ') + ': ' +
+          e.message;
+        ErrorUtils.reportError(e);
+      }
+    }
+  }
+
+  _queueStack: Array<{tasks: Array<Task>, popable: bool}>;
+  _onMoreTasks: () => void;
+
+  _getCurrentQueue(): Array<Task> {
+    const stackIdx = this._queueStack.length - 1;
+    const queue = this._queueStack[stackIdx];
+    if (queue.popable &&
+        queue.tasks.length === 0 &&
+        this._queueStack.length > 1) {
+      this._queueStack.pop();
+      return this._getCurrentQueue();
+    } else {
+      return queue.tasks;
+    }
+  }
+
+  _genPromise(task: PromiseTask) {
+    // Each async task pushes it's own queue onto the queue stack. This
+    // effectively defers execution of previously queued tasks until the promise
+    // resolves, at which point we allow the new queue to be popped, which
+    // happens once it is fully processed.
+    this._queueStack.push({tasks: [], popable: false});
+    const stackIdx = this._queueStack.length - 1;
+    ErrorUtils.applyWithGuard(task.gen)
+      .then(() => {
+        this._queueStack[stackIdx].popable = true;
+        this.hasTasksToProcess() && this._onMoreTasks();
+      })
+      .catch((ex) => {
+        console.warn(
+          'TaskQueue: Error resolving Promise in task ' + task.name,
+          ex
+        );
+        throw ex;
+      });
+  }
+}
+
+
+module.exports = TaskQueue;
diff --git a/Libraries/Interaction/__tests__/InteractionManager-test.js b/Libraries/Interaction/__tests__/InteractionManager-test.js
new file mode 100644
index 000000000000..032acf255546
--- /dev/null
+++ b/Libraries/Interaction/__tests__/InteractionManager-test.js
@@ -0,0 +1,288 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+
+'use strict';
+
+jest
+  .autoMockOff()
+  .mock('ErrorUtils')
+  .mock('BatchedBridge');
+
+function expectToBeCalledOnce(fn) {
+  expect(fn.mock.calls.length).toBe(1);
+}
+
+describe('InteractionManager', () => {
+  let InteractionManager;
+  let interactionStart;
+  let interactionComplete;
+
+  beforeEach(() => {
+    jest.resetModuleRegistry();
+    InteractionManager = require('InteractionManager');
+
+    interactionStart = jest.genMockFunction();
+    interactionComplete = jest.genMockFunction();
+
+    InteractionManager.addListener(
+      InteractionManager.Events.interactionStart,
+      interactionStart
+    );
+    InteractionManager.addListener(
+      InteractionManager.Events.interactionComplete,
+      interactionComplete
+    );
+  });
+
+  it('throws when clearing an undefined handle', () => {
+    expect(() => InteractionManager.clearInteractionHandle()).toThrow();
+  });
+
+  it('notifies asynchronously when interaction starts', () => {
+    InteractionManager.createInteractionHandle();
+    expect(interactionStart).not.toBeCalled();
+
+    jest.runAllTimers();
+    expect(interactionStart).toBeCalled();
+    expect(interactionComplete).not.toBeCalled();
+  });
+
+  it('notifies asynchronously when interaction stops', () => {
+    var handle = InteractionManager.createInteractionHandle();
+    jest.runAllTimers();
+    interactionStart.mockClear();
+    InteractionManager.clearInteractionHandle(handle);
+    expect(interactionComplete).not.toBeCalled();
+
+    jest.runAllTimers();
+    expect(interactionStart).not.toBeCalled();
+    expect(interactionComplete).toBeCalled();
+  });
+
+  it('does not notify when started & stopped in same event loop', () => {
+    var handle = InteractionManager.createInteractionHandle();
+    InteractionManager.clearInteractionHandle(handle);
+
+    jest.runAllTimers();
+    expect(interactionStart).not.toBeCalled();
+    expect(interactionComplete).not.toBeCalled();
+  });
+
+  it('does not notify when going from two -> one active interactions', () => {
+    InteractionManager.createInteractionHandle();
+    var handle = InteractionManager.createInteractionHandle();
+    jest.runAllTimers();
+
+    interactionStart.mockClear();
+    interactionComplete.mockClear();
+
+    InteractionManager.clearInteractionHandle(handle);
+    jest.runAllTimers();
+    expect(interactionStart).not.toBeCalled();
+    expect(interactionComplete).not.toBeCalled();
+  });
+
+  it('runs tasks asynchronously when there are interactions', () => {
+    var task = jest.genMockFunction();
+    InteractionManager.runAfterInteractions(task);
+    expect(task).not.toBeCalled();
+
+    jest.runAllTimers();
+    expect(task).toBeCalled();
+  });
+
+  it('runs tasks when interactions complete', () => {
+    var task = jest.genMockFunction();
+    var handle = InteractionManager.createInteractionHandle();
+    InteractionManager.runAfterInteractions(task);
+
+    jest.runAllTimers();
+    InteractionManager.clearInteractionHandle(handle);
+    expect(task).not.toBeCalled();
+
+    jest.runAllTimers();
+    expect(task).toBeCalled();
+  });
+
+  it('does not run tasks twice', () => {
+    var task1 = jest.genMockFunction();
+    var task2 = jest.genMockFunction();
+    InteractionManager.runAfterInteractions(task1);
+    jest.runAllTimers();
+
+    InteractionManager.runAfterInteractions(task2);
+    jest.runAllTimers();
+
+    expectToBeCalledOnce(task1);
+  });
+
+  it('runs tasks added while processing previous tasks', () => {
+    var task1 = jest.genMockFunction().mockImplementation(() => {
+      InteractionManager.runAfterInteractions(task2);
+    });
+    var task2 = jest.genMockFunction();
+
+    InteractionManager.runAfterInteractions(task1);
+    expect(task2).not.toBeCalled();
+
+    jest.runAllTimers();
+
+    expect(task1).toBeCalled();
+    expect(task2).toBeCalled();
+  });
+});
+
+describe('promise tasks', () => {
+  let InteractionManager;
+  let BatchedBridge;
+  let sequenceId;
+  function createSequenceTask(expectedSequenceId) {
+    return jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(expectedSequenceId);
+    });
+  }
+  beforeEach(() => {
+    jest.resetModuleRegistry();
+    InteractionManager = require('InteractionManager');
+    BatchedBridge = require('BatchedBridge');
+    sequenceId = 0;
+  });
+
+
+  it('should run a basic promise task', () => {
+    const task1 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(1);
+      return new Promise(resolve => resolve());
+    });
+    InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
+    jest.runAllTimers();
+    expectToBeCalledOnce(task1);
+  });
+
+  it('should handle nested promises', () => {
+    const task1 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(1);
+      return new Promise(resolve => {
+        InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'})
+          .then(resolve);
+      });
+    });
+    const task2 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(2);
+      return new Promise(resolve => resolve());
+    });
+    InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
+    jest.runAllTimers();
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+  });
+
+  it('should pause promise tasks during interactions then resume', () => {
+    const task1 = createSequenceTask(1);
+    const task2 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(2);
+      return new Promise(resolve => {
+        setTimeout(() => {
+          InteractionManager.runAfterInteractions(task3).then(resolve);
+        }, 1);
+      });
+    });
+    const task3 = createSequenceTask(3);
+    InteractionManager.runAfterInteractions(task1);
+    InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
+    jest.runOnlyPendingTimers();
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+    const handle = InteractionManager.createInteractionHandle();
+    jest.runAllTimers();
+    jest.runAllTimers(); // Just to be sure...
+    expect(task3).not.toBeCalled();
+    InteractionManager.clearInteractionHandle(handle);
+    jest.runAllTimers();
+    expectToBeCalledOnce(task3);
+  });
+
+  it('should execute tasks in loop within deadline', () => {
+    InteractionManager.setDeadline(100);
+    BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
+    const task1 = createSequenceTask(1);
+    const task2 = createSequenceTask(2);
+    InteractionManager.runAfterInteractions(task1);
+    InteractionManager.runAfterInteractions(task2);
+
+    jest.runOnlyPendingTimers();
+
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+  });
+
+  it('should execute tasks one at a time if deadline exceeded', () => {
+    InteractionManager.setDeadline(100);
+    BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
+    const task1 = createSequenceTask(1);
+    const task2 = createSequenceTask(2);
+    InteractionManager.runAfterInteractions(task1);
+    InteractionManager.runAfterInteractions(task2);
+
+    jest.runOnlyPendingTimers();
+
+    expectToBeCalledOnce(task1);
+    expect(task2).not.toBeCalled();
+
+    jest.runOnlyPendingTimers(); // resolve1
+    jest.runOnlyPendingTimers(); // task2
+
+    expectToBeCalledOnce(task2);
+  });
+
+  const bigAsyncTest = () => {
+    const task1 = createSequenceTask(1);
+    const task2 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(2);
+      return new Promise(resolve => {
+        InteractionManager.runAfterInteractions(task3);
+        setTimeout(() => {
+          InteractionManager.runAfterInteractions({gen: task4, name: 'gen4'})
+            .then(resolve);
+        }, 1);
+      });
+    });
+    const task3 = createSequenceTask(3);
+    const task4 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(4);
+      return new Promise(resolve => {
+        InteractionManager.runAfterInteractions(task5).then(resolve);
+      });
+    });
+    const task5 = createSequenceTask(5);
+    const task6 = createSequenceTask(6);
+
+    InteractionManager.runAfterInteractions(task1);
+    InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
+    InteractionManager.runAfterInteractions(task6);
+
+    jest.runAllTimers();
+    // runAllTimers doesn't actually run all timers with nested timer functions
+    // inside Promises, so we have to call it extra times.
+    jest.runAllTimers();
+    jest.runAllTimers();
+
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+    expectToBeCalledOnce(task3);
+    expectToBeCalledOnce(task4);
+    expectToBeCalledOnce(task5);
+    expectToBeCalledOnce(task6);
+  };
+
+  it('resolves async tasks recusively before other queued tasks', () => {
+    bigAsyncTest();
+  });
+
+  it('should also work with a deadline', () => {
+    InteractionManager.setDeadline(100);
+    BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
+    bigAsyncTest();
+  });
+});
diff --git a/Libraries/Interaction/__tests__/InteractionMixin-test.js b/Libraries/Interaction/__tests__/InteractionMixin-test.js
new file mode 100644
index 000000000000..c1cc7f54da4d
--- /dev/null
+++ b/Libraries/Interaction/__tests__/InteractionMixin-test.js
@@ -0,0 +1,44 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+'use strict';
+
+jest.dontMock('InteractionMixin');
+
+describe('InteractionMixin', () => {
+  var InteractionManager;
+  var InteractionMixin;
+  var component;
+
+  beforeEach(() => {
+    jest.resetModuleRegistry();
+    InteractionManager = require('InteractionManager');
+    InteractionMixin = require('InteractionMixin');
+
+    component = Object.create(InteractionMixin);
+  });
+
+  it('should start interactions', () => {
+    var timeout = 123;
+    component.createInteractionHandle(timeout);
+    expect(InteractionManager.createInteractionHandle).toBeCalled(timeout);
+  });
+
+  it('should end interactions', () => {
+    var handle = {};
+    component.clearInteractionHandle(handle);
+    expect(InteractionManager.clearInteractionHandle).toBeCalledWith(handle);
+  });
+
+  it('should schedule tasks', () => {
+    var task = jest.genMockFunction();
+    component.runAfterInteractions(task);
+    expect(InteractionManager.runAfterInteractions).toBeCalledWith(task);
+  });
+
+  it('should end unfinished interactions in componentWillUnmount', () => {
+    var handle = component.createInteractionHandle();
+    component.componentWillUnmount();
+    expect(InteractionManager.clearInteractionHandle).toBeCalledWith(handle);
+  });
+});
diff --git a/Libraries/Interaction/__tests__/TaskQueue-test.js b/Libraries/Interaction/__tests__/TaskQueue-test.js
new file mode 100644
index 000000000000..9b36783db552
--- /dev/null
+++ b/Libraries/Interaction/__tests__/TaskQueue-test.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+
+'use strict';
+
+jest.dontMock('TaskQueue');
+
+function expectToBeCalledOnce(fn) {
+  expect(fn.mock.calls.length).toBe(1);
+}
+
+function clearTaskQueue(taskQueue) {
+  do {
+    jest.runAllTimers();
+    taskQueue.processNext();
+    jest.runAllTimers();
+  } while (taskQueue.hasTasksToProcess())
+}
+
+describe('TaskQueue', () => {
+  let taskQueue;
+  let onMoreTasks;
+  let sequenceId;
+  function createSequenceTask(expectedSequenceId) {
+    return jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(expectedSequenceId);
+    });
+  }
+  beforeEach(() => {
+    jest.resetModuleRegistry();
+    onMoreTasks = jest.genMockFunction();
+    const TaskQueue = require('TaskQueue');
+    taskQueue = new TaskQueue({onMoreTasks});
+    sequenceId = 0;
+  });
+
+
+  it('should run a basic task', () => {
+    const task1 = createSequenceTask(1);
+    taskQueue.enqueue({run: task1, name: 'run1'});
+    expect(taskQueue.hasTasksToProcess()).toBe(true);
+    taskQueue.processNext();
+    expectToBeCalledOnce(task1);
+  });
+
+  it('should handle blocking promise task', () => {
+    const task1 = jest.genMockFunction().mockImplementation(() => {
+      return new Promise(resolve => {
+        setTimeout(() => {
+          expect(++sequenceId).toBe(1);
+          resolve();
+        }, 1);
+      });
+    });
+    const task2 = createSequenceTask(2);
+    taskQueue.enqueue({gen: task1, name: 'gen1'});
+    taskQueue.enqueue({run: task2, name: 'run2'});
+
+    taskQueue.processNext();
+
+    expectToBeCalledOnce(task1);
+    expect(task2).not.toBeCalled();
+    expect(onMoreTasks).not.toBeCalled();
+    expect(taskQueue.hasTasksToProcess()).toBe(false);
+
+    clearTaskQueue(taskQueue);
+
+    expectToBeCalledOnce(onMoreTasks);
+    expectToBeCalledOnce(task2);
+  });
+
+  it('should handle nested simple tasks', () => {
+    const task1 = jest.genMockFunction().mockImplementation(() => {
+      expect(++sequenceId).toBe(1);
+      taskQueue.enqueue({run: task3, name: 'run3'});
+    });
+    const task2 = createSequenceTask(2);
+    const task3 = createSequenceTask(3);
+    taskQueue.enqueue({run: task1, name: 'run1'});
+    taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
+
+    clearTaskQueue(taskQueue);
+
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+    expectToBeCalledOnce(task3);
+  });
+
+  it('should handle nested promises', () => {
+    const task1 = jest.genMockFunction().mockImplementation(() => {
+      return new Promise(resolve => {
+        setTimeout(() => {
+          expect(++sequenceId).toBe(1);
+          taskQueue.enqueue({gen: task2, name: 'gen2'});
+          taskQueue.enqueue({run: resolve, name: 'resolve1'});
+        }, 1);
+      });
+    });
+    const task2 = jest.genMockFunction().mockImplementation(() => {
+      return new Promise(resolve => {
+        setTimeout(() => {
+          expect(++sequenceId).toBe(2);
+          taskQueue.enqueue({run: task3, name: 'run3'});
+          taskQueue.enqueue({run: resolve, name: 'resolve2'});
+        }, 1);
+      });
+    });
+    const task3 = createSequenceTask(3);
+    const task4 = createSequenceTask(4);
+    taskQueue.enqueue({gen: task1, name: 'gen1'});
+    taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
+
+    clearTaskQueue(taskQueue);
+
+    expectToBeCalledOnce(task1);
+    expectToBeCalledOnce(task2);
+    expectToBeCalledOnce(task3);
+    expectToBeCalledOnce(task4);
+  });
+});
diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
index 8e652caf0df3..a75b166e29d8 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js
@@ -11,39 +11,37 @@
  */
 'use strict';
 
-var RCTExceptionsManager = require('NativeModules').ExceptionsManager;
-
-var loadSourceMap = require('loadSourceMap');
-var parseErrorStack = require('parseErrorStack');
-var stringifySafe = require('stringifySafe');
-
-var sourceMapPromise;
-
-var exceptionID = 0;
+let exceptionID = 0;
 
 /**
  * Handles the developer-visible aspect of errors and exceptions
  */
 function reportException(e: Error, isFatal: bool) {
-  var currentExceptionID = ++exceptionID;
+  const parseErrorStack = require('parseErrorStack');
+  const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
+
+  const currentExceptionID = ++exceptionID;
   if (RCTExceptionsManager) {
-    var stack = parseErrorStack(e);
+    const stack = parseErrorStack(e);
     if (isFatal) {
       RCTExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
     } else {
       RCTExceptionsManager.reportSoftException(e.message, stack, currentExceptionID);
     }
     if (__DEV__) {
-      (sourceMapPromise = sourceMapPromise || loadSourceMap())
-        .then(map => {
-          var prettyStack = parseErrorStack(e, map);
-          RCTExceptionsManager.updateExceptionMessage(e.message, prettyStack, currentExceptionID);
-        })
-        .catch(error => {
-          // This can happen in a variety of normal situations, such as
-          // Network module not being available, or when running locally
-          console.warn('Unable to load source map: ' + error.message);
-        });
+      require('SourceMapsCache').getSourceMaps().then(sourceMaps => {
+        const prettyStack = parseErrorStack(e, sourceMaps);
+        RCTExceptionsManager.updateExceptionMessage(
+          e.message,
+          prettyStack,
+          currentExceptionID,
+        );
+      })
+      .catch(error => {
+        // This can happen in a variety of normal situations, such as
+        // Network module not being available, or when running locally
+        console.warn('Unable to load source map: ' + error.message);
+      });
     }
   }
 }
@@ -83,14 +81,15 @@ function installConsoleErrorReporter() {
     if (arguments[0] && arguments[0].stack) {
       reportException(arguments[0], /* isFatal */ false);
     } else {
-      var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
+      const stringifySafe = require('stringifySafe');
+      const str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
       if (str.slice(0, 10) === '"Warning: ') {
         // React warnings use console.error so that a stack trace is shown, but
         // we don't (currently) want these to show a redbox
         // (Note: Logic duplicated in polyfills/console.js.)
         return;
       }
-      var error : any = new Error('console.error: ' + str);
+      const error : any = new Error('console.error: ' + str);
       error.framesToPop = 1;
       reportException(error, /* isFatal */ false);
     }
diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
index 501d4c9cc329..3a5fd5234300 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js
@@ -25,11 +25,11 @@
 require('regenerator/runtime');
 
 if (typeof GLOBAL === 'undefined') {
-  GLOBAL = this;
+  global.GLOBAL = this;
 }
 
 if (typeof window === 'undefined') {
-  window = GLOBAL;
+  global.window = GLOBAL;
 }
 
 function setUpConsole() {
@@ -53,7 +53,7 @@ function setUpConsole() {
  * For more info on that particular case, see:
  * https://github.com/facebook/react-native/issues/934
  */
-function polyfillGlobal(name, newValue, scope=GLOBAL) {
+function polyfillGlobal(name, newValue, scope = GLOBAL) {
   var descriptor = Object.getOwnPropertyDescriptor(scope, name) || {
     // jest for some bad reasons runs the polyfill code multiple times. In jest
     // environment, XmlHttpRequest doesn't exist so getOwnPropertyDescriptor
@@ -70,6 +70,15 @@ function polyfillGlobal(name, newValue, scope=GLOBAL) {
   Object.defineProperty(scope, name, {...descriptor, value: newValue});
 }
 
+/**
+ * Polyfill a module if it is not already defined in `scope`.
+ */
+function polyfillIfNeeded(name, polyfill, scope = GLOBAL, descriptor = {}) {
+  if (scope[name] === undefined) {
+    Object.defineProperty(scope, name, {...descriptor, value: polyfill});
+  }
+}
+
 function setUpErrorHandler() {
   if (global.__fbDisableExceptionsManager) {
     return;
@@ -78,7 +87,7 @@ function setUpErrorHandler() {
   function handleError(e, isFatal) {
     try {
       require('ExceptionsManager').handleException(e, isFatal);
-    } catch(ee) {
+    } catch (ee) {
       console.log('Failed to print error: ', ee.message);
     }
   }
@@ -87,13 +96,6 @@ function setUpErrorHandler() {
   ErrorUtils.setGlobalHandler(handleError);
 }
 
-function setUpFlowChecker() {
-  if (__DEV__) {
-    var checkFlowAtRuntime = require('checkFlowAtRuntime');
-    checkFlowAtRuntime();
-  }
-}
-
 /**
  * Sets up a set of window environment wrappers that ensure that the
  * BatchedBridge is flushed after each tick. In both the case of the
@@ -117,15 +119,11 @@ function setUpTimers() {
 }
 
 function setUpAlert() {
-  var RCTAlertManager = require('NativeModules').AlertManager;
   if (!GLOBAL.alert) {
     GLOBAL.alert = function(text) {
-      var alertOpts = {
-        title: 'Alert',
-        message: '' + text,
-        buttons: [{'cancel': 'OK'}],
-      };
-      RCTAlertManager.alertWithArgs(alertOpts, function () {});
+      // Require Alert on demand. Requiring it too early can lead to issues
+      // with things like Platform not being fully initialized.
+      require('Alert').alert('Alert', '' + text);
     };
   }
 }
@@ -150,17 +148,31 @@ function setUpXHR() {
 }
 
 function setUpGeolocation() {
-  GLOBAL.navigator = GLOBAL.navigator || {};
+  polyfillIfNeeded('navigator', {}, GLOBAL, {
+    writable: true,
+    enumerable: true,
+    configurable: true,
+  });
   polyfillGlobal('geolocation', require('Geolocation'), GLOBAL.navigator);
 }
 
+function setUpMapAndSet() {
+  polyfillGlobal('Map', require('Map'));
+  polyfillGlobal('Set', require('Set'));
+}
+
+function setUpProduct() {
+  Object.defineProperty(GLOBAL.navigator, 'product', {value: 'ReactNative'});
+}
+
 function setUpWebSockets() {
   polyfillGlobal('WebSocket', require('WebSocket'));
 }
 
 function setUpProfile() {
   if (__DEV__) {
-    require('BridgeProfiling').swizzleReactPerf();
+    var Systrace = require('Systrace');
+    Systrace.swizzleReactPerf();
   }
 }
 
@@ -173,9 +185,9 @@ function setUpProcessEnv() {
 }
 
 function setUpNumber() {
-  Number.EPSILON = Number.EPSILON || Math.pow(2, -52);
-  Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;
-  Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(Math.pow(2, 53) - 1);
+  polyfillIfNeeded('EPSILON', Math.pow(2, -52), Number);
+  polyfillIfNeeded('MAX_SAFE_INTEGER', Math.pow(2, 53) - 1, Number);
+  polyfillIfNeeded('MIN_SAFE_INTEGER', -(Math.pow(2, 53) - 1), Number);
 }
 
 function setUpDevTools() {
@@ -196,9 +208,10 @@ setUpPromise();
 setUpErrorHandler();
 setUpXHR();
 setUpGeolocation();
+setUpMapAndSet();
+setUpProduct();
 setUpWebSockets();
 setUpProfile();
-setUpFlowChecker();
 setUpNumber();
 setUpDevTools();
 
@@ -209,3 +222,8 @@ if (__DEV__) {
 }
 require('RCTDeviceEventEmitter');
 require('PerformanceLogger');
+
+if (__DEV__) {
+  // include this transform and it's dependencies on the bundle on dev mode
+  require('react-transform-hmr');
+}
diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js
index 53d08f8299d1..ce42bb6ced30 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js
@@ -327,7 +327,7 @@ define('source-map/source-map-generator', ['require', 'exports', 'module' ,  'so
    * Applies the mappings of a sub-source-map for a specific source file to the
    * source map being generated. Each mapping to the supplied source file is
    * rewritten using the supplied source map. Note: The resolution for the
-   * resulting mappings is the minimium of this map and the supplied map.
+   * resulting mappings is the minimum of this map and the supplied map.
    *
    * @param aSourceMapConsumer The source map to be applied.
    * @param aSourceFile Optional. The filename of the source file.
@@ -1058,7 +1058,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' ,  'sou
    *
    *   - version: Which version of the source map spec this map is following.
    *   - sources: An array of URLs to the original source files.
-   *   - names: An array of identifiers which can be referrenced by individual mappings.
+   *   - names: An array of identifiers which can be referenced by individual mappings.
    *   - sourceRoot: Optional. The URL root from which all sources are relative.
    *   - sourcesContent: Optional. An array of contents of the original source files.
    *   - mappings: A string of base64 VLQs which contain the actual mappings.
@@ -1369,7 +1369,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' ,  'sou
   /**
    * Returns the original source content. The only argument is the url of the
    * original source file. Returns null if no original source content is
-   * availible.
+   * available.
    */
   SourceMapConsumer.prototype.sourceContentFor =
     function SourceMapConsumer_sourceContentFor(aSource) {
diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMapsCache.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsCache.js
new file mode 100644
index 000000000000..63ab9c642c11
--- /dev/null
+++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsCache.js
@@ -0,0 +1,44 @@
+/**
+ * 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 SourceMapsCache
+ */
+'use strict';
+
+const getObjectValues = require('getObjectValues');
+const SourceMapsUtils = require('SourceMapsUtils');
+
+const sourceMapsCache = {};
+
+const SourceMapsCache = {
+  mainSourceMapID: 'main',
+
+  fetch({text, url, fullSourceMappingURL}) {
+    const sourceMappingURL = fullSourceMappingURL
+      ? fullSourceMappingURL
+      : SourceMapsUtils.extractSourceMapURL({text, url});
+
+    sourceMapsCache[sourceMappingURL] = SourceMapsUtils.fetchSourceMap(
+      sourceMappingURL
+    );
+  },
+
+  getSourceMaps() {
+    fetchMainSourceMap();
+    return Promise.all(getObjectValues(sourceMapsCache));
+  },
+};
+
+function fetchMainSourceMap() {
+  if (!sourceMapsCache[SourceMapsCache.mainSourceMapID]) {
+    sourceMapsCache[SourceMapsCache.mainSourceMapID] =
+      SourceMapsUtils.fetchMainSourceMap();
+  }
+}
+
+module.exports = SourceMapsCache;
diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js
new file mode 100644
index 000000000000..ecd6d8272597
--- /dev/null
+++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js
@@ -0,0 +1,83 @@
+/**
+ * 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 SourceMapsUtils
+ * @flow
+ */
+
+'use strict';
+
+var HMRClient = require('../../Utilities/HMRClient');
+var Promise = require('Promise');
+var NativeModules = require('NativeModules');
+var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
+var SourceMapURL = require('./source-map-url');
+
+var RCTSourceCode = NativeModules.SourceCode;
+var RCTNetworking = NativeModules.Networking;
+
+var SourceMapsUtils = {
+  fetchMainSourceMap(): Promise {
+    return SourceMapsUtils._getMainSourceMapURL().then(url =>
+      SourceMapsUtils.fetchSourceMap(url)
+    );
+  },
+
+  fetchSourceMap(sourceMappingURL: string): Promise {
+    return fetch(sourceMappingURL)
+      .then(response => response.text())
+      .then(map => new SourceMapConsumer(map));
+  },
+
+  extractSourceMapURL(data: ({url?:string, text?:string, fullSourceMappingURL?:string})): ?string {
+    const url = data.url;
+    const text = data.text;
+    const fullSourceMappingURL = data.fullSourceMappingURL;
+    if (fullSourceMappingURL) {
+      return fullSourceMappingURL;
+    }
+    var mapURL = SourceMapURL.getFrom(text);
+    if (!mapURL) {
+      return null;
+    }
+    if (!url) {
+      return null;
+    }
+    var baseURLs = url.match(/(.+:\/\/.*?)\//);
+    if (!baseURLs || baseURLs.length < 2) {
+      return null;
+    }
+    return baseURLs[1] + mapURL;
+  },
+
+  _getMainSourceMapURL(): Promise {
+    if (global.RAW_SOURCE_MAP) {
+      return Promise.resolve(global.RAW_SOURCE_MAP);
+    }
+
+    if (!RCTSourceCode) {
+      return Promise.reject(new Error('RCTSourceCode module is not available'));
+    }
+
+    if (!RCTNetworking) {
+      // Used internally by fetch
+      return Promise.reject(new Error('RCTNetworking module is not available'));
+    }
+
+    return RCTSourceCode.getScriptText()
+      .then(SourceMapsUtils.extractSourceMapURL)
+      .then((url) => {
+        if (url === null) {
+          return Promise.reject(new Error('No source map URL found. May be running from bundled file.'));
+        }
+        return Promise.resolve(url);
+      });
+  },
+};
+
+module.exports = SourceMapsUtils;
diff --git a/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js b/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js
deleted file mode 100644
index 8165ae6673d7..000000000000
--- a/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * 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 checkFlowAtRuntime
- *
- */
-'use strict';
-
-function checkFlowAtRuntime() {
-  var url = getPackagerURL();
-  if (!url) {
-    return;
-  }
-  fetch(url + 'flow/')
-    .then(response => response.json())
-    .then(response => {
-      if (response.silentError) {
-        return;
-      }
-      throw {
-        message: response.message,
-        stack: response.errors.map(err => {
-          return {
-            ...err,
-            methodName: err.description,
-            file: err.filename,
-          };
-        }),
-      };
-    },
-    () => {
-    //if fetch fails, silently give up
-    })
-    .done();
-}
-
-function getPackagerURL() {
-  var NativeModules = require('NativeModules');
-  var scriptURL = (NativeModules
-    && NativeModules.SourceCode
-    && NativeModules.SourceCode.scriptURL)
-    || '';
-
-  // extract the url of the packager from the whole scriptURL
-  // we match until the first / after http(s)://
-  // i.e. http://www.mypackger.com/debug/my/bundle -> http://www.mypackger.com/
-  return getFirstOrNull(scriptURL.match(/^https?:\/\/[^/]+\//));
-}
-
-function getFirstOrNull(ar) {
-  return ar ? ar[0] : null;
-}
-
-module.exports = checkFlowAtRuntime;
diff --git a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js
deleted file mode 100644
index de96ad2be08d..000000000000
--- a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/**
- * 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 loadSourceMap
- * @flow
- */
-
-'use strict';
-
-var Promise = require('Promise');
-var NativeModules = require('NativeModules');
-var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
-var SourceMapURL = require('./source-map-url');
-
-var RCTSourceCode = NativeModules.SourceCode;
-var RCTNetworking = NativeModules.Networking;
-
-function loadSourceMap(): Promise {
-  return fetchSourceMap()
-    .then(map => new SourceMapConsumer(map));
-}
-
-function fetchSourceMap(): Promise {
-  if (global.RAW_SOURCE_MAP) {
-    return Promise.resolve(global.RAW_SOURCE_MAP);
-  }
-
-  if (!RCTSourceCode) {
-    return Promise.reject(new Error('RCTSourceCode module is not available'));
-  }
-
-  if (!RCTNetworking) {
-    // Used internally by fetch
-    return Promise.reject(new Error('RCTNetworking module is not available'));
-  }
-
-  return new Promise(RCTSourceCode.getScriptText)
-    .then(extractSourceMapURL)
-    .then((url) => {
-      if (url === null) {
-        return Promise.reject(new Error('No source map URL found. May be running from bundled file.'));
-      }
-      return Promise.resolve(url);
-    })
-    .then(fetch)
-    .then(response => response.text());
-}
-
-function extractSourceMapURL({url, text, fullSourceMappingURL}): ?string {
-  if (fullSourceMappingURL) {
-    return fullSourceMappingURL;
-  }
-  var mapURL = SourceMapURL.getFrom(text);
-  if (!mapURL) {
-    return null;
-  }
-  var baseURL = url.match(/(.+:\/\/.*?)\//)[1];
-  return baseURL + mapURL;
-}
-
-module.exports = loadSourceMap;
diff --git a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js
index 63076c7b8413..5fec943152b4 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js
@@ -19,7 +19,11 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) {
       column: stackFrame.column,
     });
     if (orig) {
-      stackFrame.file = orig.source;
+      // remove query string if any
+      const queryStringStartIndex = orig.source.indexOf('?');
+      stackFrame.file = queryStringStartIndex === -1
+        ? orig.source
+        : orig.source.substring(0, queryStringStartIndex);
       stackFrame.lineNumber = orig.line;
       stackFrame.column = orig.column;
     }
@@ -27,7 +31,7 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) {
   }
 }
 
-function parseErrorStack(e, sourceMapInstance) {
+function parseErrorStack(e, sourceMaps) {
   if (!e || !e.stack) {
     return [];
   }
@@ -39,8 +43,17 @@ function parseErrorStack(e, sourceMapInstance) {
     stack.shift();
   }
 
-  if (sourceMapInstance) {
-    stack.forEach(resolveSourceMaps.bind(null, sourceMapInstance));
+  if (sourceMaps) {
+    sourceMaps.forEach((sourceMap, index) => {
+      stack.forEach(frame => {
+        if (frame.file.indexOf(sourceMap.file) !== -1 ||
+            frame.file.replace('.map', '.bundle').indexOf(
+              sourceMap.file
+            ) !== -1) {
+          resolveSourceMaps(sourceMap, frame);
+        }
+      });
+    });
   }
 
   return stack;
diff --git a/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js b/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js
index 8342a5a90ccf..f44740031842 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/source-map-url.js
@@ -29,7 +29,7 @@ void (function(root, factory) {
   }
 }(this, function() {
 
-  var innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/
+  var innerRegex = /[#@] source(?:Mapping)?URL=([^\s'"]*)/
 
   var regex = RegExp(
     "(?:" +
diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js
index f6a8bddc7a43..0ec0a4c08c28 100644
--- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js
+++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js
@@ -106,10 +106,10 @@ var JSTimers = {
 
   clearImmediate: function(timerID) {
     JSTimers._clearTimerID(timerID);
-    JSTimersExecution.immediates.splice(
-      JSTimersExecution.immediates.indexOf(timerID),
-      1
-    );
+    var index = JSTimersExecution.immediates.indexOf(timerID);
+    if (index !== -1) {
+      JSTimersExecution.immediates.splice(index, 1);
+    }
   },
 
   cancelAnimationFrame: function(timerID) {
diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js
index 6f56d835796a..df1b609723e6 100644
--- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js
+++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js
@@ -10,10 +10,11 @@
  */
 'use strict';
 
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
-var performanceNow = require('performanceNow');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var keyMirror = require('fbjs/lib/keyMirror');
+var performanceNow = require('fbjs/lib/performanceNow');
+var warning = require('fbjs/lib/warning');
+var Systrace = require('Systrace');
 
 /**
  * JS implementation of timer functions. Must be completely driven by an
@@ -107,15 +108,38 @@ var JSTimersExecution = {
     }
   },
 
+  /**
+   * Performs a single pass over the enqueued immediates. Returns whether
+   * more immediates are queued up (can be used as a condition a while loop).
+   */
+  callImmediatesPass: function() {
+    Systrace.beginEvent('JSTimersExecution.callImmediatesPass()');
+
+    // The main reason to extract a single pass is so that we can track
+    // in the system trace
+    if (JSTimersExecution.immediates.length > 0) {
+      var passImmediates = JSTimersExecution.immediates.slice();
+      JSTimersExecution.immediates = [];
+
+      // Use for loop rather than forEach as per @vjeux's advice
+      // https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
+      for (var i = 0; i < passImmediates.length; ++i) {
+        JSTimersExecution.callTimer(passImmediates[i]);
+      }
+    }
+
+    Systrace.endEvent();
+
+    return JSTimersExecution.immediates.length > 0;
+  },
+
   /**
    * This is called after we execute any command we receive from native but
    * before we hand control back to native.
    */
   callImmediates: function() {
     JSTimersExecution.errors = null;
-    while (JSTimersExecution.immediates.length !== 0) {
-      JSTimersExecution.callTimer(JSTimersExecution.immediates.shift());
-    }
+    while (JSTimersExecution.callImmediatesPass()) {}
     if (JSTimersExecution.errors) {
       JSTimersExecution.errors.forEach((error) =>
         require('JSTimers').setTimeout(() => { throw error; }, 0)
diff --git a/Libraries/JavaScriptAppEngine/polyfills/document.js b/Libraries/JavaScriptAppEngine/polyfills/document.js
deleted file mode 100644
index 4e76db957259..000000000000
--- a/Libraries/JavaScriptAppEngine/polyfills/document.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint global-strict: 0 */
-(function(GLOBAL) {
-  /**
-   * The document must be shimmed before anything else that might define the
-   * `ExecutionEnvironment` module (which checks for `document.createElement`).
-   */
-
-  // The browser defines Text and Image globals by default. If you forget to
-  // require them, then the error message is very confusing.
-  function getInvalidGlobalUseError(name) {
-    return new Error(
-      'You are trying to render the global ' + name + ' variable as a ' +
-      'React element. You probably forgot to require ' + name + '.'
-    );
-  }
-  GLOBAL.Text = {
-    get defaultProps() {
-      throw getInvalidGlobalUseError('Text');
-    }
-  };
-  GLOBAL.Image = {
-    get defaultProps() {
-      throw getInvalidGlobalUseError('Image');
-    }
-  };
-  // Force `ExecutionEnvironment.canUseDOM` to be false.
-  if (GLOBAL.document) {
-    GLOBAL.document.createElement = null;
-  }
-
-  // There is no DOM so MutationObserver doesn't make sense. It is used
-  // as feature detection in Bluebird Promise implementation
-  GLOBAL.MutationObserver = undefined;
-})(this);
diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js
index 881b499f2ba3..108c4d5428e5 100644
--- a/Libraries/LayoutAnimation/LayoutAnimation.js
+++ b/Libraries/LayoutAnimation/LayoutAnimation.js
@@ -12,10 +12,10 @@
 'use strict';
 
 var PropTypes = require('ReactPropTypes');
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 
 var TypesEnum = {
   spring: true,
@@ -71,7 +71,7 @@ type Config = {
 
 function configureNext(config: Config, onAnimationDidEnd?: Function) {
   configChecker({config}, 'config', 'LayoutAnimation.configureNext');
-  RCTUIManager.configureNextLayoutAnimation(
+  UIManager.configureNextLayoutAnimation(
     config, onAnimationDidEnd || function() {}, function() { /* unused */ }
   );
 }
diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js
new file mode 100644
index 000000000000..280b48ec8a85
--- /dev/null
+++ b/Libraries/Linking/Linking.js
@@ -0,0 +1,213 @@
+/**
+ * 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 Linking
+ * @flow
+ */
+'use strict';
+
+const Platform = require('Platform');
+const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+const {
+  IntentAndroid,
+  LinkingManager: LinkingManagerIOS
+} = require('NativeModules');
+const LinkingManager = Platform.OS === 'android' ? IntentAndroid : LinkingManagerIOS;
+const invariant = require('fbjs/lib/invariant');
+const Map = require('Map');
+
+const _notifHandlers = new Map();
+
+const DEVICE_NOTIF_EVENT = 'openURL';
+
+/**
+ * `Linking` gives you a general interface to interact with both incoming
+ * and outgoing app links.
+ *
+ * ### Basic Usage
+ *
+ * #### Handling deep links
+ *
+ * If your app was launched from an external url registered to your app you can
+ * access and handle it from any component you want with
+ *
+ * ```
+ * componentDidMount() {
+ *   var url = Linking.getInitialURL().then((url) => {
+ *     if (url) {
+ *       console.log('Initial url is: ' + url);
+ *     }
+ *   }).catch(err => console.error('An error occurred', err));
+ * }
+ * ```
+ *
+ * NOTE: For instructions on how to add support for deep linking on Android,
+ * refer [Enabling Deep Links for App Content - Add Intent Filters for Your Deep Links](http://developer.android.com/training/app-indexing/deep-linking.html#adding-filters).
+ *
+ * NOTE: For iOS, in case you also want to listen to incoming app links during your app's
+ * execution you'll need to add the following lines to you `*AppDelegate.m`:
+ *
+ * ```
+ *#import "RCTLinkingManager.h"
+ *
+ * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
+ *   sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
+ * {
+ *   return [RCTLinkingManager application:application openURL:url
+ *                       sourceApplication:sourceApplication annotation:annotation];
+ * }
+ *
+ * // Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
+ * - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
+ *  restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
+ * {
+ *  return [RCTLinkingManager application:application
+ *                   continueUserActivity:userActivity
+ *                     restorationHandler:restorationHandler];
+ * }
+ *
+ * ```
+ *
+ * And then on your React component you'll be able to listen to the events on
+ * `Linking` as follows
+ *
+ * ```
+ * componentDidMount() {
+ *   Linking.addEventListener('url', this._handleOpenURL);
+ * },
+ * componentWillUnmount() {
+ *   Linking.removeEventListener('url', this._handleOpenURL);
+ * },
+ * _handleOpenURL(event) {
+ *   console.log(event.url);
+ * }
+ * ```
+ * Note that this is only supported on iOS.
+ *
+ * #### Opening external links
+ *
+ * To start the corresponding activity for a link (web URL, email, contact etc.), call
+ *
+ * ```
+ * Linking.openURL(url).catch(err => console.error('An error occurred', err));
+ * ```
+ *
+ * If you want to check if any installed app can handle a given URL beforehand you can call
+ * ```
+ * Linking.canOpenURL(url).then(supported => {
+ *   if (!supported) {
+ *     console.log('Can\'t handle url: ' + url);
+ *   } else {
+ *     return Linking.openURL(url);
+ *   }
+ * }).catch(err => console.error('An error occurred', err));
+ * ```
+ */
+class Linking {
+  /**
+   * Add a handler to Linking changes by listening to the `url` event type
+   * and providing the handler
+   *
+   * @platform ios
+   */
+  static addEventListener(type: string, handler: Function) {
+    if (Platform.OS === 'android') {
+        console.warn('Linking.addEventListener is not supported on Android');
+    } else {
+      invariant(
+        type === 'url',
+        'Linking only supports `url` events'
+      );
+      var listener = RCTDeviceEventEmitter.addListener(
+        DEVICE_NOTIF_EVENT,
+        handler
+      );
+      _notifHandlers.set(handler, listener);
+    }
+  }
+
+  /**
+   * Remove a handler by passing the `url` event type and the handler
+   *
+   * @platform ios
+   */
+  static removeEventListener(type: string, handler: Function ) {
+    if (Platform.OS === 'android') {
+        console.warn('Linking.removeEventListener is not supported on Android');
+    } else {
+      invariant(
+        type === 'url',
+        'Linking only supports `url` events'
+      );
+      var listener = _notifHandlers.get(handler);
+      if (!listener) {
+        return;
+      }
+      listener.remove();
+      _notifHandlers.delete(handler);
+    }
+  }
+
+  /**
+   * Try to open the given `url` with any of the installed apps.
+   *
+   * You can use other URLs, like a location (e.g. "geo:37.484847,-122.148386"), a contact,
+   * or any other URL that can be opened with the installed apps.
+   *
+   * NOTE: This method will fail if the system doesn't know how to open the specified URL.
+   * If you're passing in a non-http(s) URL, it's best to check {@code canOpenURL} first.
+   *
+   * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
+   */
+  static openURL(url: string): Promise<boolean> {
+    this._validateURL(url);
+    return LinkingManager.openURL(url);
+  }
+
+  /**
+   * Determine whether or not an installed app can handle a given URL.
+   *
+   * NOTE: For web URLs, the protocol ("http://", "https://") must be set accordingly!
+   *
+   * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key
+   * inside `Info.plist`.
+   *
+   * @param URL the URL to open
+   */
+  static canOpenURL(url: string): Promise<boolean> {
+    this._validateURL(url);
+    return LinkingManager.canOpenURL(url);
+  }
+
+  /**
+   * If the app launch was triggered by an app link with,
+   * it will give the link url, otherwise it will give `null`
+   *
+   * NOTE: To support deep linking on Android, refer http://developer.android.com/training/app-indexing/deep-linking.html#handling-intents
+   */
+  static getInitialURL(): Promise<?string> {
+    if (Platform.OS === 'android') {
+      return IntentAndroid.getInitialURL();
+    } else {
+      return Promise.resolve(LinkingManagerIOS.initialURL);
+    }
+  }
+
+  static _validateURL(url: string) {
+    invariant(
+      typeof url === 'string',
+      'Invalid URL: should be a string. Was: ' + url
+    );
+    invariant(
+      url,
+      'Invalid URL: cannot be empty'
+    );
+  }
+}
+
+module.exports = Linking;
diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js
index 0ee8367df073..b5ec9f7a6c60 100644
--- a/Libraries/LinkingIOS/LinkingIOS.js
+++ b/Libraries/LinkingIOS/LinkingIOS.js
@@ -11,18 +11,15 @@
  */
 'use strict';
 
-var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var Linking = require('Linking');
 var RCTLinkingManager = require('NativeModules').LinkingManager;
-var Map = require('Map');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
-var _notifHandlers = new Map();
-var _initialURL = RCTLinkingManager &&
-  RCTLinkingManager.initialURL;
-
-var DEVICE_NOTIF_EVENT = 'openURL';
+var _initialURL = RCTLinkingManager && RCTLinkingManager.initialURL;
 
 /**
+ * NOTE: `LinkingIOS` is being deprecated. Use `Linking` instead.
+ *
  * `LinkingIOS` gives you a general interface to interact with both incoming
  * and outgoing app links.
  *
@@ -43,9 +40,22 @@ var DEVICE_NOTIF_EVENT = 'openURL';
  * execution you'll need to add the following lines to you `*AppDelegate.m`:
  *
  * ```
- * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
- *   return [RCTLinkingManager application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
+ * - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
+ *   sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
+ * {
+ *   return [RCTLinkingManager application:application openURL:url
+ *                       sourceApplication:sourceApplication annotation:annotation];
  * }
+ *
+ * // Only if your app is using [Universal Links](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
+ * - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
+ *  restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
+ * {
+ *  return [RCTLinkingManager application:application
+ *                   continueUserActivity:userActivity
+ *                     restorationHandler:restorationHandler];
+ * }
+ *
  * ```
  *
  * And then on your React component you'll be able to listen to the events on
@@ -71,7 +81,7 @@ var DEVICE_NOTIF_EVENT = 'openURL';
  * LinkingIOS.openURL(url)
  * ```
  *
- * If you want to check if any installed app can handle a given URL beforehand you can call
+ * If you want to check if any installed app can handle a given URL beforehand, call
  * ```
  * LinkingIOS.canOpenURL(url, (supported) => {
  *   if (!supported) {
@@ -86,70 +96,60 @@ class LinkingIOS {
   /**
    * Add a handler to LinkingIOS changes by listening to the `url` event type
    * and providing the handler
+   *
+   * @deprecated
    */
   static addEventListener(type: string, handler: Function) {
-    invariant(
-      type === 'url',
-      'LinkingIOS only supports `url` events'
-    );
-    var listener = RCTDeviceEventEmitter.addListener(
-      DEVICE_NOTIF_EVENT,
-      handler
-    );
-    _notifHandlers.set(handler, listener);
+    console.warn('"LinkingIOS.addEventListener" is deprecated. Use "Linking.addEventListener" instead.');
+    Linking.addEventListener(type, handler);
   }
 
   /**
    * Remove a handler by passing the `url` event type and the handler
+   *
+   * @deprecated
    */
   static removeEventListener(type: string, handler: Function ) {
-    invariant(
-      type === 'url',
-      'LinkingIOS only supports `url` events'
-    );
-    var listener = _notifHandlers.get(handler);
-    if (!listener) {
-      return;
-    }
-    listener.remove();
-    _notifHandlers.delete(handler);
+    console.warn('"LinkingIOS.removeEventListener" is deprecated. Use "Linking.removeEventListener" instead.');
+    Linking.removeEventListener(type, handler);
   }
 
   /**
    * Try to open the given `url` with any of the installed apps.
+   *
+   * @deprecated
    */
   static openURL(url: string) {
-    invariant(
-      typeof url === 'string',
-      'Invalid url: should be a string'
-    );
-    RCTLinkingManager.openURL(url);
+    console.warn('"LinkingIOS.openURL" is deprecated. Use the promise based "Linking.openURL" instead.');
+    Linking.openURL(url);
   }
 
   /**
    * Determine whether or not an installed app can handle a given URL.
    * The callback function will be called with `bool supported` as the only argument
    *
-   * NOTE: As of iOS 9, your app needs to provide a `LSApplicationQueriesSchemes` key 
+   * NOTE: As of iOS 9, your app needs to provide the `LSApplicationQueriesSchemes` key
    * inside `Info.plist`.
+   *
+   * @deprecated
    */
   static canOpenURL(url: string, callback: Function) {
-    invariant(
-      typeof url === 'string',
-      'Invalid url: should be a string'
-    );
+    console.warn('"LinkingIOS.canOpenURL" is deprecated. Use the promise based "Linking.canOpenURL" instead.');
     invariant(
       typeof callback === 'function',
       'A valid callback function is required'
     );
-    RCTLinkingManager.canOpenURL(url, callback);
+    Linking.canOpenURL(url).then(callback);
   }
 
   /**
    * If the app launch was triggered by an app link, it will pop the link url,
    * otherwise it will return `null`
+   *
+   * @deprecated
    */
   static popInitialURL(): ?string {
+    console.warn('"LinkingIOS.popInitialURL" is deprecated. Use the promise based "Linking.getInitialURL" instead.');
     var initialURL = _initialURL;
     _initialURL = null;
     return initialURL;
diff --git a/Libraries/LinkingIOS/RCTLinkingManager.h b/Libraries/LinkingIOS/RCTLinkingManager.h
index caa3aa2a3a92..092ca990b8a1 100644
--- a/Libraries/LinkingIOS/RCTLinkingManager.h
+++ b/Libraries/LinkingIOS/RCTLinkingManager.h
@@ -18,4 +18,8 @@
   sourceApplication:(NSString *)sourceApplication
          annotation:(id)annotation;
 
++ (BOOL)application:(UIApplication *)application
+continueUserActivity:(NSUserActivity *)userActivity
+  restorationHandler:(void (^)(NSArray *))restorationHandler;
+
 @end
diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m
index 39fff4eb9658..9538981298a0 100644
--- a/Libraries/LinkingIOS/RCTLinkingManager.m
+++ b/Libraries/LinkingIOS/RCTLinkingManager.m
@@ -21,15 +21,32 @@ @implementation RCTLinkingManager
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(handleOpenURLNotification:)
-                                                 name:RCTOpenURLNotification
-                                               object:nil];
+  _bridge = bridge;
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(handleOpenURLNotification:)
+                                               name:RCTOpenURLNotification
+                                             object:nil];
+}
+
+- (NSDictionary<NSString *, id> *)constantsToExport
+{
+  NSURL *initialURL;
+
+  if (_bridge.launchOptions[UIApplicationLaunchOptionsURLKey]) {
+    initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
+  } else if (&UIApplicationLaunchOptionsUserActivityDictionaryKey &&
+      _bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey]) {
+    NSDictionary *userActivityDictionary = _bridge.launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey];
+
+    if ([userActivityDictionary[UIApplicationLaunchOptionsUserActivityTypeKey] isEqual:NSUserActivityTypeBrowsingWeb]) {
+      initialURL = ((NSUserActivity *)userActivityDictionary[@"UIApplicationLaunchOptionsUserActivityKey"]).webpageURL;
+    }
   }
-  return self;
+
+  return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
 }
 
 - (void)dealloc
@@ -49,36 +66,47 @@ + (BOOL)application:(UIApplication *)application
   return YES;
 }
 
++ (BOOL)application:(UIApplication *)application
+continueUserActivity:(NSUserActivity *)userActivity
+  restorationHandler:(void (^)(NSArray *))restorationHandler
+{
+  if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
+    NSDictionary *payload = @{@"url": userActivity.webpageURL.absoluteString};
+    [[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
+                                                        object:self
+                                                      userInfo:payload];
+  }
+  return YES;
+}
+
 - (void)handleOpenURLNotification:(NSNotification *)notification
 {
   [_bridge.eventDispatcher sendDeviceEventWithName:@"openURL"
                                               body:notification.userInfo];
 }
 
-RCT_EXPORT_METHOD(openURL:(NSURL *)URL)
+RCT_EXPORT_METHOD(openURL:(NSURL *)URL
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(__unused RCTPromiseRejectBlock)reject)
 {
-  // Doesn't really matter what thread we call this on since it exits the app
-  [RCTSharedApplication() openURL:URL];
+  BOOL opened = [RCTSharedApplication() openURL:URL];
+  resolve(@(opened));
 }
 
 RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
-                  callback:(RCTResponseSenderBlock)callback)
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(__unused RCTPromiseRejectBlock)reject)
 {
   if (RCTRunningInAppExtension()) {
     // Technically Today widgets can open urls, but supporting that would require
     // a reference to the NSExtensionContext
-    callback(@[@(NO)]);
+    resolve(@NO);
+    return;
   }
 
   // This can be expensive, so we deliberately don't call on main thread
   BOOL canOpen = [RCTSharedApplication() canOpenURL:URL];
-  callback(@[@(canOpen)]);
-}
-
-- (NSDictionary<NSString *, id> *)constantsToExport
-{
-  NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
-  return @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
+  resolve(@(canOpen));
 }
 
 @end
diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js
index dfbfdb685542..318171e3de09 100644
--- a/Libraries/Modal/Modal.js
+++ b/Libraries/Modal/Modal.js
@@ -31,6 +31,8 @@ var RCTModalHostView = requireNativeComponent('RCTModalHostView', null);
  * Navigator instead of Modal. With a top-level Navigator, you have more control
  * over how to present the modal scene over the rest of your app by using the
  * configureScene property.
+ *
+ * This component is only available in iOS at this time.
  */
 class Modal extends React.Component {
   render(): ?ReactElement {
@@ -47,6 +49,7 @@ class Modal extends React.Component {
         animated={this.props.animated}
         transparent={this.props.transparent}
         onDismiss={this.props.onDismiss}
+        onShow={this.props.onShow}
         style={styles.modal}>
         <View style={[styles.container, containerBackgroundColor]}>
           {this.props.children}
@@ -61,6 +64,7 @@ Modal.propTypes = {
   transparent: PropTypes.bool,
   visible: PropTypes.bool,
   onDismiss: PropTypes.func,
+  onShow: PropTypes.func,
 };
 
 Modal.defaultProps = {
diff --git a/Libraries/NativeApp/RCTNativeAppEventEmitter.js b/Libraries/NativeApp/RCTNativeAppEventEmitter.js
index 38ccb0dcfdce..9d4de85f39e8 100644
--- a/Libraries/NativeApp/RCTNativeAppEventEmitter.js
+++ b/Libraries/NativeApp/RCTNativeAppEventEmitter.js
@@ -11,8 +11,14 @@
  */
 'use strict';
 
+var BatchedBridge = require('BatchedBridge');
 var EventEmitter = require('EventEmitter');
 
 var RCTNativeAppEventEmitter = new EventEmitter();
 
+BatchedBridge.registerCallableModule(
+  'RCTNativeAppEventEmitter',
+  RCTNativeAppEventEmitter
+);
+
 module.exports = RCTNativeAppEventEmitter;
diff --git a/Libraries/NavigationExperimental/NavigationAbstractPanResponder.js b/Libraries/NavigationExperimental/NavigationAbstractPanResponder.js
new file mode 100644
index 000000000000..98fdbf32d005
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationAbstractPanResponder.js
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule NavigationAbstractPanResponder
+ * @flow
+ */
+'use strict';
+
+const PanResponder = require('PanResponder');
+
+const invariant = require('fbjs/lib/invariant');
+
+const EmptyPanHandlers = {
+  onMoveShouldSetPanResponder: null,
+  onPanResponderGrant: null,
+  onPanResponderMove: null,
+  onPanResponderRelease: null,
+  onPanResponderTerminate: null,
+};
+
+/**
+ * Abstract class that defines the common interface of PanResponder that handles
+ * the gesture actions.
+ */
+class NavigationAbstractPanResponder {
+
+  panHandlers: Object;
+
+  constructor() {
+    const config = {};
+    Object.keys(EmptyPanHandlers).forEach(name => {
+      const fn: any = (this: any)[name];
+
+      invariant(
+        typeof fn === 'function',
+        'subclass of `NavigationAbstractPanResponder` must implement method %s',
+        name
+      );
+
+      config[name] = fn.bind(this);
+    }, this);
+
+    this.panHandlers = PanResponder.create(config).panHandlers;
+  }
+}
+
+module.exports = NavigationAbstractPanResponder;
diff --git a/Libraries/NavigationExperimental/NavigationAnimatedView.js b/Libraries/NavigationExperimental/NavigationAnimatedView.js
new file mode 100644
index 000000000000..4d1f5b68f16e
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationAnimatedView.js
@@ -0,0 +1,270 @@
+/**
+ * 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 NavigationAnimatedView
+ * @flow
+ */
+'use strict';
+
+var Animated = require('Animated');
+var Map = require('Map');
+var NavigationStateUtils = require('NavigationStateUtils');
+var NavigationContainer = require('NavigationContainer');
+var React = require('React');
+var View = require('View');
+
+import type {
+  NavigationState,
+  NavigationParentState,
+} from 'NavigationStateUtils';
+
+type NavigationScene = {
+  index: number,
+  state: NavigationState,
+  isStale: boolean,
+};
+
+/**
+ * Helper function to compare route keys (e.g. "9", "11").
+ */
+function compareKey(one: string, two: string): number {
+  var delta = one.length - two.length;
+  if (delta > 0) {
+    return 1;
+  }
+  if (delta < 0) {
+    return -1;
+  }
+  return one > two ? 1 : -1;
+}
+
+/**
+ * Helper function to sort scenes based on their index and view key.
+ */
+function compareScenes(
+  one: NavigationScene,
+  two: NavigationScene
+): number {
+  if (one.index > two.index) {
+    return 1;
+  }
+  if (one.index < two.index) {
+    return -1;
+  }
+
+  return compareKey(
+    one.state.key,
+    two.state.key
+  );
+}
+
+type Layout = {
+  initWidth: number,
+  initHeight: number,
+  width: Animated.Value;
+  height: Animated.Value;
+};
+
+type Position = Animated.Value;
+
+/**
+ * Definition of the props object that is passed to the functions
+ * that render the overlay and the scene.
+ */
+type NavigationStateRendererProps = {
+  // The state of the child view.
+  navigationState: NavigationState,
+  // The index of the child view.
+  index: number,
+  // The "progressive index" of the containing navigation state.
+  position: Position,
+  // The layout of the the containing navigation view.
+  layout: Layout,
+  // The state of the the containing navigation view.
+  navigationParentState: NavigationParentState,
+
+  onNavigate: (action: any) => void,
+};
+
+type NavigationStateRenderer = (
+  props: NavigationStateRendererProps,
+) => ReactElement;
+
+type TimingSetter = (
+  position: Animated.Value,
+  newState: NavigationParentState,
+  lastState: NavigationParentState,
+) => void;
+
+type Props = {
+  navigationState: NavigationParentState,
+  onNavigate: (action: any) => void,
+  renderScene: NavigationStateRenderer,
+  renderOverlay: ?NavigationStateRenderer,
+  style: any,
+  setTiming: ?TimingSetter,
+};
+
+class NavigationAnimatedView extends React.Component {
+  _animatedHeight: Animated.Value;
+  _animatedWidth: Animated.Value;
+  _lastHeight: number;
+  _lastWidth: number;
+  props: Props;
+  constructor(props) {
+    super(props);
+    this._lastWidth = 0;
+    this._lastHeight = 0;
+    this._animatedHeight = new Animated.Value(this._lastHeight);
+    this._animatedWidth = new Animated.Value(this._lastWidth);
+
+    this.state = {
+      position: new Animated.Value(this.props.navigationState.index),
+      scenes: new Map(),
+    };
+  }
+  componentWillMount() {
+    this.setState({
+      scenes: this._reduceScenes(this.state.scenes, this.props.navigationState),
+    });
+  }
+  componentDidMount() {
+    this.postionListener = this.state.position.addListener(this._onProgressChange.bind(this));
+  }
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.navigationState !== this.props.navigationState) {
+      this.setState({
+        scenes: this._reduceScenes(this.state.scenes, nextProps.navigationState, this.props.navigationState),
+      });
+    }
+  }
+  componentDidUpdate(lastProps) {
+    if (lastProps.navigationState.index !== this.props.navigationState.index && this.props.setTiming) {
+      this.props.setTiming(this.state.position, this.props.navigationState, lastProps.navigationState);
+    }
+  }
+  componentWillUnmount() {
+    if (this.postionListener) {
+      this.state.position.removeListener(this.postionListener);
+      this.postionListener = null;
+    }
+  }
+  _onProgressChange(data: Object): void {
+    if (Math.abs(data.value - this.props.navigationState.index) > Number.EPSILON) {
+      return;
+    }
+    this.state.scenes.forEach((scene, index) => {
+      if (scene.isStale) {
+        const scenes = this.state.scenes.slice();
+        scenes.splice(index, 1);
+        this.setState({ scenes, });
+      }
+    });
+  }
+  _reduceScenes(
+    scenes: Array<NavigationScene>,
+    nextState: NavigationParentState,
+    lastState: ?NavigationParentState
+  ): Array<NavigationScene> {
+    let nextScenes = nextState.children.map((child, index) => {
+      return {
+        index,
+        state: child,
+        isStale: false,
+      };
+    });
+
+    if (lastState) {
+      lastState.children.forEach((child, index) => {
+        if (!NavigationStateUtils.get(nextState, child.key) && index !== nextState.index) {
+          nextScenes.push({
+            index,
+            state: child,
+            isStale: true,
+          });
+        }
+      });
+    }
+
+    nextScenes = nextScenes.sort(compareScenes);
+
+    return nextScenes;
+  }
+  render() {
+    return (
+      <View
+        onLayout={(e) => {
+          const {height, width} = e.nativeEvent.layout;
+          this._animatedHeight &&
+            this._animatedHeight.setValue(height);
+          this._animatedWidth &&
+            this._animatedWidth.setValue(width);
+          this._lastHeight = height;
+          this._lastWidth = width;
+        }}
+        style={this.props.style}>
+        {this.state.scenes.map(this._renderScene, this)}
+        {this._renderOverlay()}
+      </View>
+    );
+  }
+  _getLayout() {
+    return {
+      height: this._animatedHeight,
+      width: this._animatedWidth,
+      initWidth: this._lastWidth,
+      initHeight: this._lastHeight,
+    };
+  }
+  _renderScene(scene: NavigationScene) {
+    return this.props.renderScene({
+      index: scene.index,
+      layout: this._getLayout(),
+      navigationParentState: this.props.navigationState,
+      navigationState: scene.state,
+      onNavigate: this.props.onNavigate,
+      position: this.state.position,
+    });
+  }
+  _renderOverlay() {
+    const {
+      onNavigate,
+      renderOverlay,
+      navigationState,
+    } = this.props;
+    if (renderOverlay) {
+      return renderOverlay({
+        index: navigationState.index,
+        layout: this._getLayout(),
+        navigationParentState: navigationState,
+        navigationState: navigationState.children[navigationState.index],
+        onNavigate: onNavigate,
+        position: this.state.position,
+      });
+    }
+    return null;
+  }
+}
+
+function setDefaultTiming(position, navigationState) {
+  Animated.spring(
+    position,
+    {
+      bounciness: 0,
+      toValue: navigationState.index,
+    }
+  ).start();
+}
+
+NavigationAnimatedView.defaultProps = {
+  setTiming: setDefaultTiming,
+};
+
+NavigationAnimatedView = NavigationContainer.create(NavigationAnimatedView);
+
+module.exports = NavigationAnimatedView;
diff --git a/Libraries/NavigationExperimental/NavigationContainer.js b/Libraries/NavigationExperimental/NavigationContainer.js
new file mode 100644
index 000000000000..133b498cb07a
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationContainer.js
@@ -0,0 +1,51 @@
+/**
+ * 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 NavigationContainer
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var NavigationRootContainer = require('NavigationRootContainer');
+
+function createNavigationContainer(Component: React.Component): React.Component {
+  class NavigationComponent extends React.Component {
+    render() {
+      return (
+        <Component
+          onNavigate={this.getNavigationHandler()}
+          {...this.props}
+        />
+      );
+    }
+    getNavigationHandler() {
+      return this.props.onNavigate || this.context.onNavigate;
+    }
+    getChildContext() {
+      return {
+        onNavigate: this.getNavigationHandler(),
+      };
+    }
+  }
+  NavigationComponent.contextTypes = {
+    onNavigate: React.PropTypes.func,
+  };
+  NavigationComponent.childContextTypes = {
+    onNavigate: React.PropTypes.func,
+  };
+  return NavigationComponent;
+}
+
+var NavigationContainer = {
+  create: createNavigationContainer,
+  RootContainer: NavigationRootContainer,
+};
+
+
+module.exports = NavigationContainer;
diff --git a/Libraries/NavigationExperimental/NavigationExperimental.js b/Libraries/NavigationExperimental/NavigationExperimental.js
new file mode 100644
index 000000000000..ca6bb627d559
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationExperimental.js
@@ -0,0 +1,45 @@
+/**
+ * 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 NavigationExperimental
+ * @flow
+ */
+'use strict';
+
+const NavigationAnimatedView = require('NavigationAnimatedView');
+const NavigationCard = require('NavigationCard');
+const NavigationCardStack = require('NavigationCardStack');
+const NavigationContainer = require('NavigationContainer');
+const NavigationHeader = require('NavigationHeader');
+const NavigationLegacyNavigator = require('NavigationLegacyNavigator');
+const NavigationReducer = require('NavigationReducer');
+const NavigationRootContainer = require('NavigationRootContainer');
+const NavigationStateUtils = require('NavigationStateUtils');
+const NavigationView = require('NavigationView');
+
+const NavigationExperimental = {
+  // Core
+  StateUtils: NavigationStateUtils,
+  Reducer: NavigationReducer,
+
+  // Containers
+  Container: NavigationContainer,
+  RootContainer: NavigationRootContainer,
+
+  // Views
+  View: NavigationView,
+  AnimatedView: NavigationAnimatedView,
+
+  // CustomComponents:
+  Card: NavigationCard,
+  CardStack: NavigationCardStack,
+  Header: NavigationHeader,
+  LegacyNavigator: NavigationLegacyNavigator,
+};
+
+module.exports = NavigationExperimental;
diff --git a/Libraries/NavigationExperimental/NavigationLinearPanResponder.js b/Libraries/NavigationExperimental/NavigationLinearPanResponder.js
new file mode 100644
index 000000000000..704f305c7704
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationLinearPanResponder.js
@@ -0,0 +1,183 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule NavigationLinearPanResponder
+ * @flow
+ * @typechecks
+ */
+'use strict';
+
+const Animated = require('Animated');
+const NavigationAbstractPanResponder = require('NavigationAbstractPanResponder');
+
+const clamp = require('clamp');
+
+/**
+ * The duration of the card animation in milliseconds.
+ */
+const ANIMATION_DURATION = 250;
+
+/**
+ * The threshold to invoke the `onNavigate` action.
+ * For instance, `1 / 3` means that moving greater than 1 / 3 of the width of
+ * the view will navigate.
+ */
+const POSITION_THRESHOLD = 1 / 3;
+
+/**
+ * The threshold (in pixels) to start the gesture action.
+ */
+const RESPOND_THRESHOLD = 15;
+
+/**
+ * The threshold (in speed) to finish the gesture action.
+ */
+const VELOCITY_THRESHOLD = 100;
+
+/**
+ * Primitive gesture directions.
+ */
+const Directions = {
+  'HORIZONTAL': 'horizontal',
+  'VERTICAL': 'vertical',
+};
+
+/**
+ * Primitive gesture actions.
+ */
+const Actions = {
+  // The gesture to navigate backward.
+  // This is done by swiping from the left to the right or from the top to the
+  // bottom.
+  BACK: {type: 'back'},
+};
+
+import type {
+  Layout,
+  Position,
+} from 'NavigationAnimatedView';
+
+export type OnNavigateHandler = (action: {type: string}) => void;
+
+export type Direction = $Enum<typeof Directions>;
+
+/**
+ * The type interface of the object that provides the information required by
+ * NavigationLinearPanResponder.
+ */
+export type NavigationLinearPanResponderDelegate = {
+  getDirection: () => Direction;
+  getIndex: () => number,
+  getLayout: () => Layout,
+  getPosition: () => Position,
+  onNavigate: OnNavigateHandler,
+};
+
+/**
+ * Pan responder that handles the One-dimensional gesture (horizontal or
+ * vertical).
+ */
+class NavigationLinearPanResponder extends NavigationAbstractPanResponder {
+  static Actions: Object;
+  static Directions: Object;
+
+  _isResponding: boolean;
+  _startValue: number;
+  _delegate: NavigationLinearPanResponderDelegate;
+
+  constructor(delegate: NavigationLinearPanResponderDelegate) {
+    super();
+    this._isResponding = false;
+    this._startValue = 0;
+    this._delegate = delegate;
+  }
+
+  onMoveShouldSetPanResponder(event: any, gesture: any): boolean {
+    const delegate = this._delegate;
+    const layout = delegate.getLayout();
+    const isVertical = delegate.getDirection() === Directions.VERTICAL;
+    const axis = isVertical ? 'dy' : 'dx';
+    const index = delegate.getIndex();
+    const distance = isVertical ?
+      layout.height.__getValue() :
+      layout.width.__getValue();
+
+    return (
+      Math.abs(gesture[axis]) > RESPOND_THRESHOLD &&
+      distance > 0 &&
+      index > 0
+    );
+  }
+
+  onPanResponderGrant(): void {
+    this._isResponding = false;
+    this._delegate.getPosition().stopAnimation((value: number) => {
+      this._isResponding = true;
+      this._startValue = value;
+    });
+  }
+
+  onPanResponderMove(event: any, gesture: any): void {
+    if (!this._isResponding) {
+      return;
+    }
+
+    const delegate = this._delegate;
+    const layout = delegate.getLayout();
+    const isVertical = delegate.getDirection() === Directions.VERTICAL;
+    const axis = isVertical ? 'dy' : 'dx';
+    const index = delegate.getIndex();
+    const distance = isVertical ?
+      layout.height.__getValue() :
+      layout.width.__getValue();
+
+    const value = clamp(
+      index - 1,
+      this._startValue - (gesture[axis] / distance),
+      index
+    );
+
+    this._delegate.getPosition().setValue(value);
+  }
+
+  onPanResponderRelease(event: any, gesture: any): void {
+    if (!this._isResponding) {
+      return;
+    }
+
+    this._isResponding = false;
+
+    const delegate = this._delegate;
+    const isVertical = delegate.getDirection() === Directions.VERTICAL;
+    const axis = isVertical ? 'dy' : 'dx';
+    const index = delegate.getIndex();
+    const velocity = gesture[axis];
+
+    delegate.getPosition().stopAnimation((value: number) => {
+      this._reset();
+       if (velocity > VELOCITY_THRESHOLD  || value <= index - POSITION_THRESHOLD) {
+        delegate.onNavigate(Actions.BACK);
+      }
+    });
+  }
+
+  onPanResponderTerminate(): void {
+    this._isResponding = false;
+    this._reset();
+  }
+
+  _reset(): void {
+    Animated.timing(
+      this._delegate.getPosition(),
+      {
+        toValue: this._delegate.getIndex(),
+        duration: ANIMATION_DURATION,
+      }
+    ).start();
+  }
+}
+
+NavigationLinearPanResponder.Actions = Actions;
+NavigationLinearPanResponder.Directions = Directions;
+
+module.exports = NavigationLinearPanResponder;
diff --git a/Libraries/NavigationExperimental/NavigationRootContainer.js b/Libraries/NavigationExperimental/NavigationRootContainer.js
new file mode 100644
index 000000000000..18d7689e0760
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationRootContainer.js
@@ -0,0 +1,157 @@
+/**
+ * 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 NavigationRootContainer
+ * @flow
+ */
+'use strict';
+
+const AsyncStorage = require('AsyncStorage');
+const Linking = require('Linking');
+const React = require('React');
+const BackAndroid = require('BackAndroid');
+const Platform = require('Platform');
+
+import type {
+  NavigationAction,
+  NavigationState,
+  NavigationReducer
+} from 'NavigationStateUtils';
+
+export type NavigationRenderer = (
+  navigationState: NavigationState,
+  onNavigate: Function
+) => ReactElement;
+
+export type BackAction = {
+  type: 'BackAction';
+};
+
+function getBackAction(): BackAction {
+  return { type: 'BackAction' };
+}
+
+type Props = {
+  /*
+   * Set up the rendering of the app for a given navigation state
+   */
+  renderNavigation: NavigationRenderer;
+
+  /*
+   * A function that will output the latest navigation state as a function of
+   * the (optional) previous state, and an action
+   */
+  reducer: NavigationReducer;
+
+  /*
+   * Provide this key, and the container will store the navigation state in
+   * AsyncStorage through refreshes, with the provided key
+   */
+  persistenceKey: ?string;
+
+  /*
+   * The default action to be passed into the reducer when getting the first
+   * state. Defaults to {type: 'RootContainerInitialAction'}
+   */
+  initialAction: NavigationAction;
+
+  /*
+   * Provide linkingActionMap to instruct the container to subscribe to linking
+   * events, and use this mapper to convert URIs into actions that your app can
+   * handle
+   */
+  linkingActionMap: (uri: string) => NavigationAction;
+};
+
+class NavigationRootContainer extends React.Component {
+  _handleOpenURLEvent: Function;
+  props: Props;
+  constructor(props: Props) {
+    super(props);
+    this.handleNavigation = this.handleNavigation.bind(this);
+    this._handleOpenURLEvent = this._handleOpenURLEvent.bind(this);
+    let navState = null;
+    if (!this.props.persistenceKey) {
+      navState = this.props.reducer(null, props.initialAction);
+    }
+    this.state = { navState };
+  }
+  componentDidMount() {
+    if (this.props.LinkingActionMap) {
+      Linking.getInitialURL().then(this._handleOpenURL.bind(this));
+      Platform.OS === 'ios' && Linking.addEventListener('url', this._handleOpenURLEvent);    
+    }
+    if (this.props.persistenceKey) {
+      AsyncStorage.getItem(this.props.persistenceKey, (err, storedString) => {
+        if (err || !storedString) {
+          this.setState({
+            navState: this.props.reducer(null, this.props.initialAction),
+          });
+          return;
+        }
+        this.setState({
+          navState: JSON.parse(storedString),
+        });
+      });
+    }
+  }
+  componentWillUnmount() {
+    Platform.OS === 'ios' && Linking.removeEventListener('url', this._handleOpenURLEvent);
+  }
+  _handleOpenURLEvent(event: {url: string}) {
+    this._handleOpenURL(event.url);
+  }
+  _handleOpenURL(url: ?string) {
+    if (!this.props.LinkingActionMap) {
+      return;
+    }
+    const action = this.props.LinkingActionMap(url);
+    if (action) {
+      this.handleNavigation(action);
+    }
+  }
+  getChildContext(): Object {
+    return {
+      onNavigate: this.handleNavigation,
+    };
+  }
+  handleNavigation(action: Object): boolean {
+    const navState = this.props.reducer(this.state.navState, action);
+    if (navState === this.state.navState) {
+      return false;
+    }
+    this.setState({
+      navState,
+    });
+    if (this.props.persistenceKey) {
+      AsyncStorage.setItem(this.props.persistenceKey, JSON.stringify(navState));
+    }
+    return true;
+  }
+  render(): ReactElement {
+    const navigation = this.props.renderNavigation(
+      this.state.navState,
+      this.handleNavigation
+    );
+    return navigation;
+  }
+}
+
+NavigationRootContainer.childContextTypes = {
+  onNavigate: React.PropTypes.func,
+};
+
+NavigationRootContainer.defaultProps = {
+  initialAction: {
+    type: 'RootContainerInitialAction',
+  },
+};
+
+NavigationRootContainer.getBackAction = getBackAction;
+
+module.exports = NavigationRootContainer;
diff --git a/Libraries/NavigationExperimental/NavigationStateUtils.js b/Libraries/NavigationExperimental/NavigationStateUtils.js
new file mode 100644
index 000000000000..d457ebea8ada
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationStateUtils.js
@@ -0,0 +1,206 @@
+/**
+ * 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 NavigationStateUtils
+ * @flow
+ */
+'use strict';
+
+const invariant = require('fbjs/lib/invariant');
+
+export type NavigationState = {
+  key: string;
+};
+
+export type NavigationParentState = {
+  key: string;
+  index: number;
+  children: Array<NavigationState>;
+};
+
+export type NavigationAction = {
+  type: string;
+};
+
+export type NavigationReducer = (
+  state: ?NavigationState,
+  action: ?NavigationAction
+) => NavigationState;
+
+function getParent(state: NavigationState): ?NavigationParentState {
+  if (
+    (state instanceof Object) &&
+    (state.children instanceof Array) &&
+    (state.children[0] !== undefined) &&
+    (typeof state.index === 'number') &&
+    (state.children[state.index] !== undefined)
+  ) {
+    return state;
+  }
+  return null;
+}
+
+function get(state: NavigationState, key: string): ?NavigationState {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return null;
+  }
+  const childState = parentState.children.find(child => child.key === key);
+  return childState || null;
+}
+
+function indexOf(state: NavigationState, key: string): ?number {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return null;
+  }
+  const index = parentState.children.map(child => child.key).indexOf(key);
+  if (index === -1) {
+    return null;
+  }
+  return index;
+}
+
+function push(state: NavigationParentState, newChildState: NavigationState): NavigationParentState {
+  var lastChildren: Array<NavigationState> = state.children;
+  return {
+    ...state,
+    children: [
+      ...lastChildren,
+      newChildState,
+    ],
+    index: lastChildren.length,
+  };
+}
+
+function pop(state: NavigationParentState): NavigationParentState {
+  const lastChildren = state.children;
+  return {
+    ...state,
+    children: lastChildren.slice(0, lastChildren.length - 1),
+    index: lastChildren.length - 2,
+  };
+}
+
+function reset(state: NavigationState, nextChildren: ?Array<NavigationState>, nextIndex: ?number): NavigationState {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return state;
+  }
+  const children = nextChildren || parentState.children;
+  const index = nextIndex == null ? parentState.index : nextIndex;
+  if (children === parentState.children && index === parentState.index) {
+    return state;
+  }
+  return {
+    ...parentState,
+    children,
+    index,
+  };
+}
+
+function set(state: ?NavigationState, key: string, nextChildren: Array<NavigationState>, nextIndex: number): NavigationState {
+  if (!state) {
+    return {
+      children: nextChildren,
+      index: nextIndex,
+      key,
+    };
+  }
+  const parentState = getParent(state);
+  if (!parentState) {
+    return {
+      children: nextChildren,
+      index: nextIndex,
+      key,
+    };
+  }
+  if (nextChildren === parentState.children && nextIndex === parentState.index && key === parentState.key) {
+    return parentState;
+  }
+  return {
+    ...parentState,
+    children: nextChildren,
+    index: nextIndex,
+    key,
+  };
+}
+
+function jumpToIndex(state: NavigationState, index: number): NavigationState {
+  const parentState = getParent(state);
+  if (parentState && parentState.index === index) {
+    return parentState;
+  }
+  return {
+    ...parentState,
+    index,
+  };
+}
+
+function jumpTo(state: NavigationState, key: string): NavigationState {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return state;
+  }
+  const index = parentState.children.indexOf(parentState.children.find(child => child.key === key));
+  invariant(
+    index !== -1,
+    'Cannot find child with matching key in this NavigationState'
+  );
+  return {
+    ...parentState,
+    index,
+  };
+}
+
+function replaceAt(state: NavigationState, key: string, newState: NavigationState): NavigationState {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return state;
+  }
+  const children = [...parentState.children];
+  const index = parentState.children.indexOf(parentState.children.find(child => child.key === key));
+  invariant(
+    index !== -1,
+    'Cannot find child with matching key in this NavigationState'
+  );
+  children[index] = newState;
+  return {
+    ...parentState,
+    children,
+  };
+}
+
+function replaceAtIndex(state: NavigationState, index: number, newState: NavigationState): NavigationState {
+  const parentState = getParent(state);
+  if (!parentState) {
+    return state;
+  }
+  const children = [...parentState.children];
+  children[index] = newState;
+  return {
+    ...parentState,
+    children,
+  };
+}
+
+const NavigationStateUtils = {
+  getParent,
+  get: get,
+  indexOf,
+  push,
+  pop,
+  reset,
+  set: set,
+  jumpToIndex,
+  jumpTo,
+  replaceAt,
+  replaceAtIndex,
+};
+
+module.exports = NavigationStateUtils;
diff --git a/Libraries/NavigationExperimental/NavigationView.js b/Libraries/NavigationExperimental/NavigationView.js
new file mode 100644
index 000000000000..5789cfbf3a9e
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationView.js
@@ -0,0 +1,61 @@
+/**
+ * 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 NavigationView
+ * @flow
+ */
+'use strict';
+
+var React = require('React');
+var NavigationContainer = require('NavigationContainer');
+var StyleSheet = require('StyleSheet');
+var View = require('View');
+
+var NavigationView = React.createClass({
+  propTypes: {
+    // todo, figure out a propType for getK
+    navigationState: React.PropTypes.object.isRequired,
+    renderScene: React.PropTypes.func.isRequired,
+  },
+  render: function() {
+    return (
+      <View
+        style={this.props.style}>
+        {this.props.navigationState.children.map(this._renderScene)}
+      </View>
+    );
+  },
+  _renderScene: function(route, index) {
+    var isSelected = index === this.props.navigationState.index;
+    return (
+      <View
+        key={route.key}
+        pointerEvents={isSelected ? 'auto' : 'none'}
+        style={[
+          styles.navView,
+          {opacity: isSelected ? 1 : 0},
+        ]}>
+        {this.props.renderScene(route, index)}
+      </View>
+    );
+  },
+});
+
+NavigationView = NavigationContainer.create(NavigationView);
+
+var styles = StyleSheet.create({
+  navView: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    top: 0,
+    bottom: 0,
+  },
+});
+
+module.exports = NavigationView;
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js
new file mode 100644
index 000000000000..bca336ee6358
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js
@@ -0,0 +1,38 @@
+/**
+ * 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 NavigationFindReducer
+ * @flow
+ */
+'use strict';
+
+/*
+ * NavigationFindReducer takes an array of reducers, and returns a reducer that
+ * iterates through all of the reducers and the result of the first reducer
+ * that modifies the input
+ */
+
+import type {
+  NavigationState,
+  NavigationReducer
+} from 'NavigationStateUtils';
+
+function NavigationFindReducer(reducers: Array<NavigationReducer>, defaultState: NavigationState): NavigationReducer {
+  return function(lastState: ?NavigationState, action: ?any): NavigationState {
+    for (let i = 0; i < reducers.length; i++) {
+      let reducer = reducers[i];
+      let newState = reducer(lastState, action);
+      if (newState !== lastState) {
+        return newState || defaultState;
+      }
+    }
+    return lastState || defaultState;
+  };
+}
+
+module.exports = NavigationFindReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationReducer.js
new file mode 100644
index 000000000000..4fb52297daaa
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/NavigationReducer.js
@@ -0,0 +1,24 @@
+/**
+ * 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 NavigationReducer
+ * @flow
+ */
+'use strict';
+
+var NavigationFindReducer = require('NavigationFindReducer');
+var NavigationStackReducer = require('NavigationStackReducer');
+var NavigationTabsReducer = require('NavigationTabsReducer');
+
+const NavigationReducer = {
+  FindReducer: NavigationFindReducer,
+  StackReducer: NavigationStackReducer,
+  TabsReducer: NavigationTabsReducer,
+};
+
+module.exports = NavigationReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js
new file mode 100644
index 000000000000..89d96da097b0
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js
@@ -0,0 +1,107 @@
+/**
+ * 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 NavigationStackReducer
+ * @flow
+ */
+'use strict';
+
+var NavigationStateUtils = require('NavigationStateUtils');
+
+import type {
+  NavigationState,
+  NavigationParentState,
+  NavigationReducer,
+} from 'NavigationStateUtils';
+
+import type {
+  BackAction,
+} from 'NavigationRootContainer';
+
+export type NavigationStackReducerAction = BackAction | {
+  type: string,
+};
+
+export type ReducerForStateHandler = (state: NavigationState) => NavigationReducer;
+
+export type PushedReducerForActionHandler = (action: any) => ?NavigationReducer;
+
+export type StackReducerConfig = {
+  /*
+   * The initialState is that the reducer will use when there is no previous state.
+   * Must be a NavigationParentState:
+   *
+   * {
+   *   children: [
+   *     {key: 'subState0'},
+   *     {key: 'subState1'},
+   *   ],
+   *   index: 0,
+   *   key: 'navStackKey'
+   * }
+   */
+  initialState: NavigationParentState;
+
+  /*
+   * Returns the sub-reducer for a particular state to handle. This will be called
+   * when we need to handle an action on a sub-state. If no reducer is returned,
+   * no action will be taken
+   */
+  getReducerForState?: ReducerForStateHandler;
+
+  /*
+   * Returns a sub-reducer that will be used when pushing a new route. If a reducer
+   * is returned, it be called to get the new state that will be pushed
+   */
+  getPushedReducerForAction: PushedReducerForActionHandler;
+};
+
+const defaultGetReducerForState = (initialState) => (state) => state || initialState;
+
+function NavigationStackReducer({initialState, getReducerForState, getPushedReducerForAction}: StackReducerConfig): NavigationReducer {
+  const getReducerForStateWithDefault = getReducerForState || defaultGetReducerForState;
+  return function (lastState: ?NavigationState, action: any): NavigationState {
+    if (!lastState) {
+      return initialState;
+    }
+    const lastParentState = NavigationStateUtils.getParent(lastState);
+    if (!lastParentState) {
+      return lastState;
+    }
+    switch (action.type) {
+      case 'BackAction':
+        if (lastParentState.index === 0 || lastParentState.children.length === 1) {
+          return lastParentState;
+        }
+        return NavigationStateUtils.pop(lastParentState);
+    }
+
+    const activeSubState = lastParentState.children[lastParentState.index];
+    const activeSubReducer = getReducerForStateWithDefault(activeSubState);
+    const nextActiveState = activeSubReducer(activeSubState, action);
+    if (nextActiveState !== activeSubState) {
+      const nextChildren = [...lastParentState.children];
+      nextChildren[lastParentState.index] = nextActiveState;
+      return {
+        ...lastParentState,
+        children: nextChildren,
+      };
+    }
+
+    const subReducerToPush = getPushedReducerForAction(action);
+    if (subReducerToPush) {
+      return NavigationStateUtils.push(
+        lastParentState,
+        subReducerToPush(null, action)
+      );
+    }
+    return lastParentState;
+  };
+}
+
+module.exports = NavigationStackReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js
new file mode 100644
index 000000000000..eaf9ef83b2b2
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js
@@ -0,0 +1,104 @@
+/**
+ * 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 NavigationTabsReducer
+ * @flow
+ */
+'use strict';
+
+const NavigationFindReducer = require('NavigationFindReducer');
+const NavigationStateUtils = require('NavigationStateUtils');
+
+import type {
+  NavigationReducer,
+  NavigationState,
+  NavigationParentState
+} from 'NavigationStateUtils';
+
+const ActionTypes = {
+  JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo',
+};
+
+export type JumpToAction = {
+  type: typeof ActionTypes.JUMP_TO,
+  index: number,
+};
+function NavigationTabsJumpToAction(index: number): JumpToAction {
+  return {
+    type: ActionTypes.JUMP_TO,
+    index,
+  };
+}
+
+type TabsReducerConfig = {
+  key: string;
+  initialIndex: number;
+  tabReducers: Array<NavigationReducer>;
+};
+
+function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer {
+  return function(lastNavState: ?NavigationState, action: ?any): NavigationState {
+    if (!lastNavState) {
+      lastNavState = {
+        children: tabReducers.map(reducer => reducer(null, null)),
+        index: initialIndex || 0,
+        key,
+      };
+    }
+    const lastParentNavState = NavigationStateUtils.getParent(lastNavState);
+    if (!action || !lastParentNavState) {
+      return lastNavState;
+    }
+    if (
+      action.type === ActionTypes.JUMP_TO &&
+      action.index !== lastParentNavState.index
+    ) {
+      return NavigationStateUtils.jumpToIndex(
+        lastParentNavState,
+        action.index,
+      );
+    }
+    const subReducers = tabReducers.map((tabReducer, tabIndex) => {
+      return function(navState: ?NavigationState, tabAction: any): NavigationState {
+        if (!navState) {
+          return lastParentNavState;
+        }
+        const parentState = NavigationStateUtils.getParent(navState);
+        const tabState = parentState && parentState.children[tabIndex];
+        const nextTabState = tabReducer(tabState, tabAction);
+        if (nextTabState && tabState !== nextTabState) {
+          const tabs = parentState && parentState.children || [];
+          tabs[tabIndex] = nextTabState;
+          return {
+            ...lastParentNavState,
+            tabs,
+            index: tabIndex,
+          };
+        }
+        return lastParentNavState;
+      };
+    });
+    let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0];
+    subReducers.unshift(function(navState: ?NavigationState, action: any): NavigationState {
+      if (navState && action.type === 'BackAction') {
+        return NavigationStateUtils.jumpToIndex(
+          lastParentNavState,
+          initialIndex || 0
+        );
+      }
+      return lastParentNavState;
+    });
+    subReducers.unshift(selectedTabReducer);
+    const findReducer = NavigationFindReducer(subReducers, lastParentNavState);
+    return findReducer(lastParentNavState, action);
+  };
+}
+
+NavigationTabsReducer.JumpToAction = NavigationTabsJumpToAction;
+
+module.exports = NavigationTabsReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js
new file mode 100644
index 000000000000..660e1c03ef94
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ *
+ * @flow-broken
+ */
+'use strict';
+
+jest
+ .dontMock('NavigationFindReducer');
+
+const NavigationFindReducer = require('NavigationFindReducer');
+
+describe('NavigationFindReducer', () => {
+
+  it('handles basic find reducing with strings', () => {
+    let reducer = NavigationFindReducer([
+      s => s,
+      s => s + '_yes',
+      s => 'nope',
+    ]);
+    let route = reducer('input');
+    expect(route).toBe('input_yes');
+
+    reducer = NavigationFindReducer([
+      (s, action) => s,
+      (s, action) => 'origRoute',
+      (s, action) => 'firstChangedState',
+    ]);
+    route = reducer('origRoute', 'action1');
+    expect(route).toBe('firstChangedState');
+
+    reducer = NavigationFindReducer([
+      (s, action) => s,
+      (s, action) => action,
+    ]);
+    route = reducer('inputState', 'action2');
+    expect(route).toBe('action2');
+  });
+
+});
diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js
new file mode 100644
index 000000000000..9a0d50326c8e
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js
@@ -0,0 +1,107 @@
+/**
+ * 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.
+ *
+ * @flow-broken
+ */
+'use strict';
+
+jest
+ .dontMock('NavigationRootContainer')
+ .dontMock('NavigationStackReducer')
+ .dontMock('NavigationStateUtils');
+
+jest.setMock('React', {Component() {}, PropTypes: {}});
+
+const NavigationStackReducer = require('NavigationStackReducer');
+const NavigationRootContainer = require('NavigationRootContainer');
+
+describe('NavigationStackReducer', () => {
+
+  it('provides default/initial state', () => {
+    const initialState = {
+      children: [
+        {key: 'a'},
+      ],
+      index: 0,
+      key: 'myStack',
+    };
+    const reducer = NavigationStackReducer({
+      getPushedReducerForAction: (action) => null,
+      getReducerForState: (state) => () => state,
+      initialState,
+    });
+    const dummyAction = {type: 'dummyAction'};
+    expect(reducer(null, dummyAction)).toBe(initialState);
+  });
+
+  it('handles basic reducer pushing', () => {
+    const reducer = NavigationStackReducer({
+      getPushedReducerForAction: (action) => {
+        if (action.type === 'TestPushAction') {
+          return (state) => state || {key: action.testValue};
+        }
+        return null;
+      },
+      getReducerForState: (state) => () => state,
+      initialState: {
+        children: [
+          {key: 'first'},
+        ],
+        index: 0,
+        key: 'myStack'
+      }
+    });
+    const state1 = reducer(null, {type: 'default'});
+    expect(state1.children.length).toBe(1);
+    expect(state1.children[0].key).toBe('first');
+    expect(state1.index).toBe(0);
+
+    const action = {type: 'TestPushAction', testValue: 'second'};
+    const state2 = reducer(state1, action);
+    expect(state2.children.length).toBe(2);
+    expect(state2.children[0].key).toBe('first');
+    expect(state2.children[1].key).toBe('second');
+    expect(state2.index).toBe(1);
+  });
+
+  it('handles BackAction', () => {
+    const reducer = NavigationStackReducer({
+      getPushedReducerForAction: (action) => {
+        if (action.type === 'TestPushAction') {
+          return (state) => state || {key: action.testValue};
+        }
+        return null;
+      },
+      getReducerForState: (state) => () => state,
+      initialState: {
+        children: [
+          {key: 'a'},
+          {key: 'b'},
+        ],
+        index: 1,
+        key: 'myStack',
+      },
+    });
+
+    const state1 = reducer(null, {type: 'MyDefaultAction'});
+    expect(state1.children[0].key).toBe('a');
+    expect(state1.children[1].key).toBe('b');
+    expect(state1.children.length).toBe(2);
+    expect(state1.index).toBe(1);
+    expect(state1.key).toBe('myStack');
+
+    const state2 = reducer(state1, NavigationRootContainer.getBackAction());
+    expect(state2.children[0].key).toBe('a');
+    expect(state2.children.length).toBe(1);
+    expect(state2.index).toBe(0);
+
+    const state3 = reducer(state2, NavigationRootContainer.getBackAction());
+    expect(state3).toBe(state2);
+  });
+
+});
\ No newline at end of file
diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js
new file mode 100644
index 000000000000..9228d0c7b976
--- /dev/null
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ *
+ * @flow-broken
+ */
+'use strict';
+
+jest
+ .dontMock('NavigationTabsReducer')
+ .dontMock('NavigationFindReducer')
+ .dontMock('NavigationStateUtils');
+
+const NavigationTabsReducer = require('NavigationTabsReducer');
+
+const {
+  JumpToAction,
+} = NavigationTabsReducer;
+
+describe('NavigationTabsReducer', () => {
+
+  it('handles JumpTo with index', () => {
+    let reducer = NavigationTabsReducer({
+      tabReducers: [
+        (tabState, action) => tabState || 'a',
+        (tabState, action) => tabState || 'b',
+        (tabState, action) => tabState || 'c',
+      ],
+      initialIndex: 1,
+    });
+
+    let navState = reducer();
+
+    expect(navState.children[0]).toBe('a');
+    expect(navState.children[1]).toBe('b');
+    expect(navState.children[2]).toBe('c');
+    expect(navState.children.length).toBe(3);
+    expect(navState.index).toBe(1);
+
+    navState = reducer(
+      navState,
+      JumpToAction(2)
+    );
+
+    expect(navState.children[0]).toEqual('a');
+    expect(navState.children[1]).toEqual('b');
+    expect(navState.children[2]).toEqual('c');
+    expect(navState.children.length).toBe(3);
+    expect(navState.index).toBe(2);
+  });
+
+});
diff --git a/Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js b/Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js
new file mode 100644
index 000000000000..5b8cf98b5ba4
--- /dev/null
+++ b/Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ *
+ * @flow-broken
+ */
+'use strict';
+
+jest
+ .dontMock('NavigationStateUtils');
+
+var NavigationStateUtils = require('NavigationStateUtils');
+
+var VALID_PARENT_STATES = [
+  {children: ['a','b'], index: 0},
+  {children: [{key: 'a'},{key: 'b', foo: 123}], index: 1},
+  {children: [{key: 'a'},{key: 'b'}], index: 0},
+  {children: [{key: 'a'},{key: 'b'}], index: 2},
+];
+var INVALID_PARENT_STATES = [
+  'foo',
+  {},
+  {children: [{key: 'a'}], index: 4},
+  {children: [{key: 'a'}], index: -1},
+  {children: [{key: 'a'}]},
+  {children: {key: 'foo'}},
+  12,
+  null,
+  undefined,
+  [],
+];
+
+describe('NavigationStateUtils', () => {
+
+  it('identifies parents correctly with getParent', () => {
+    for (var i = 0; i <= VALID_PARENT_STATES.length; i++) {
+      var navState = VALID_PARENT_STATES[0];
+      expect(NavigationStateUtils.getParent(navState)).toBe(navState);
+    }
+    for (var i = 0; i <= INVALID_PARENT_STATES.length; i++) {
+      var navState = INVALID_PARENT_STATES[0];
+      expect(NavigationStateUtils.getParent(navState)).toBe(null);
+    }
+  });
+
+  it('can get children', () => {
+    var fooState = {key: 'foo'};
+    var navState = {children: [{key: 'foobar'}, fooState], index: 0};
+    expect(NavigationStateUtils.get(navState, 'foo')).toBe(fooState);
+    expect(NavigationStateUtils.get(navState, 'missing')).toBe(null);
+  });
+});
diff --git a/Libraries/Network/FormData.js b/Libraries/Network/FormData.js
index b2c4eda5a076..f3b261365865 100644
--- a/Libraries/Network/FormData.js
+++ b/Libraries/Network/FormData.js
@@ -64,22 +64,29 @@ class FormData {
   getParts(): Array<FormDataPart> {
     return this._parts.map(([name, value]) => {
       var contentDisposition = 'form-data; name="' + name + '"';
-      var headers: Headers = {'content-disposition': contentDisposition};
-      if (typeof value === 'string') {
-        return {string: value, headers, fieldName: name};
+      // Convert non-object values to strings as per FormData.append() spec
+      if (typeof value !== 'object') {
+        value = '' + value;
       }
 
+      /* $FlowIssue(>=0.20.1) #9463928 */
+      var headers: Headers = {'content-disposition': contentDisposition};
+
       // The body part is a "blob", which in React Native just means
       // an object with a `uri` attribute. Optionally, it can also
       // have a `name` and `type` attribute to specify filename and
       // content type (cf. web Blob interface.)
-      if (typeof value.name === 'string') {
-        headers['content-disposition'] += '; filename="' + value.name + '"';
-      }
-      if (typeof value.type === 'string') {
-        headers['content-type'] = value.type;
+      if (typeof value === 'object') {
+        if (typeof value.name === 'string') {
+          headers['content-disposition'] += '; filename="' + value.name + '"';
+        }
+        if (typeof value.type === 'string') {
+          headers['content-type'] = value.type;
+        }
+        return {...value, headers, fieldName: name};
       }
-      return {...value, headers, fieldName: name};
+      // Cast to string all other values
+      return {string: String(value), headers, fieldName: name};
     });
   }
 }
diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js
index 6c811f9256c4..5399be0250c8 100644
--- a/Libraries/Network/NetInfo.js
+++ b/Libraries/Network/NetInfo.js
@@ -11,13 +11,14 @@
  */
 'use strict';
 
-var Map = require('Map');
-var NativeModules = require('NativeModules');
-var Platform = require('Platform');
-var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
-var RCTNetInfo = NativeModules.NetInfo;
+const Map = require('Map');
+const NativeModules = require('NativeModules');
+const Platform = require('Platform');
+const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+const RCTNetInfo = NativeModules.NetInfo;
+const deprecatedCallback = require('deprecatedCallback');
 
-var DEVICE_REACHABILITY_EVENT = 'networkDidChange';
+const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange';
 
 type ChangeEventName = $Enum<{
   change: string;
@@ -53,6 +54,26 @@ type ConnectivityStateAndroid = $Enum<{
   UNKNOWN: string;
 }>;
 
+
+const _subscriptions = new Map();
+
+let _isConnected;
+if (Platform.OS === 'ios') {
+  _isConnected = function(
+    reachability: ReachabilityStateIOS,
+  ): bool {
+    return reachability !== 'none' && reachability !== 'unknown';
+  };
+} else if (Platform.OS === 'android') {
+  _isConnected = function(
+      connectionType: ConnectivityStateAndroid,
+    ): bool {
+    return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
+  };
+}
+
+const _isConnectedSubscriptions = new Map();
+
 /**
  * NetInfo exposes info about online/offline status
  *
@@ -84,9 +105,14 @@ type ConnectivityStateAndroid = $Enum<{
  *
  * ### Android
  *
+ * To request network info, you need to add the following line to your
+ * app's `AndroidManifest.xml`:
+ *
+ * `<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />`
  * Asynchronously determine if the device is connected and details about that connection.
  *
- * Android Connectivity Types
+ * Android Connectivity Types.
+ *
  * - `NONE` - device is offline
  * - `BLUETOOTH` - The Bluetooth data connection.
  * - `DUMMY` -  Dummy data connection.
@@ -100,16 +126,22 @@ type ConnectivityStateAndroid = $Enum<{
  * - `WIFI` - The WIFI data connection.
  * - `WIMAX` -  The WiMAX data connection.
  * - `UNKNOWN` - Unknown data connection.
+ *
  * The rest ConnectivityStates are hidden by the Android API, but can be used if necessary.
  *
- * ### isConnectionMetered
+ * ### isConnectionExpensive
  *
  * Available on Android. Detect if the current active connection is metered or not. A network is
  * classified as metered when the user is sensitive to heavy data usage on that connection due to
  * monetary costs, data limitations or battery/performance issues.
  *
- * NetInfo.isConnectionMetered((isConnectionMetered) => {
- *   console.log('Connection is ' + (isConnectionMetered ? 'Metered' : 'Not Metered'));
+ * ```
+ * NetInfo.isConnectionExpensive()
+ * .then(isConnectionExpensive => {
+ *   console.log('Connection is ' + (isConnectionExpensive ? 'Expensive' : 'Not Expensive'));
+ * })
+ * .catch(error => {
+ *   console.error(error);
  * });
  * ```
  *
@@ -119,7 +151,7 @@ type ConnectivityStateAndroid = $Enum<{
  * internet connectivity.
  *
  * ```
- * NetInfo.isConnected.fetch().done((isConnected) => {
+ * NetInfo.isConnected.fetch().then(isConnected => {
  *   console.log('First, is ' + (isConnected ? 'online' : 'offline'));
  * });
  * function handleFirstConnectivityChange(isConnected) {
@@ -135,45 +167,28 @@ type ConnectivityStateAndroid = $Enum<{
  * );
  * ```
  */
-
-var _subscriptions = new Map();
-
-if (Platform.OS === 'ios') {
-  var _isConnected = function(
-    reachability: ReachabilityStateIOS
-  ): bool {
-    return reachability !== 'none' &&
-      reachability !== 'unknown';
-  };
-} else if (Platform.OS === 'android') {
-  var _isConnected = function(
-      connectionType: ConnectivityStateAndroid
-    ): bool {
-    return connectionType !== 'NONE' && connectionType !== 'UNKNOWN';
-  };
-}
-
-var _isConnectedSubscriptions = new Map();
-
-var NetInfo = {
-  addEventListener: function (
+const NetInfo = {
+  addEventListener(
     eventName: ChangeEventName,
     handler: Function
-  ): void {
-    var listener = RCTDeviceEventEmitter.addListener(
-      DEVICE_REACHABILITY_EVENT,
+  ): {remove: () => void} {
+    const listener = RCTDeviceEventEmitter.addListener(
+      DEVICE_CONNECTIVITY_EVENT,
       (appStateData) => {
         handler(appStateData.network_info);
       }
     );
     _subscriptions.set(handler, listener);
+    return {
+      remove: () => NetInfo.removeEventListener(eventName, handler)
+    };
   },
 
-  removeEventListener: function(
+  removeEventListener(
     eventName: ChangeEventName,
     handler: Function
   ): void {
-    var listener = _subscriptions.get(handler);
+    const listener = _subscriptions.get(handler);
     if (!listener) {
       return;
     }
@@ -181,23 +196,16 @@ var NetInfo = {
     _subscriptions.delete(handler);
   },
 
-  fetch: function(): Promise {
-    return new Promise((resolve, reject) => {
-      RCTNetInfo.getCurrentReachability(
-        function(resp) {
-          resolve(resp.network_info);
-        },
-        reject
-      );
-    });
+  fetch(): Promise {
+    return RCTNetInfo.getCurrentConnectivity().then(resp => resp.network_info);
   },
 
   isConnected: {
-    addEventListener: function (
+    addEventListener(
       eventName: ChangeEventName,
       handler: Function
-    ): void {
-      var listener = (connection) => {
+    ): {remove: () => void} {
+      const listener = (connection) => {
         handler(_isConnected(connection));
       };
       _isConnectedSubscriptions.set(handler, listener);
@@ -205,13 +213,16 @@ var NetInfo = {
         eventName,
         listener
       );
+      return {
+        remove: () => NetInfo.isConnected.removeEventListener(eventName, handler)
+      };
     },
 
-    removeEventListener: function(
+    removeEventListener(
       eventName: ChangeEventName,
       handler: Function
     ): void {
-      var listener = _isConnectedSubscriptions.get(handler);
+      const listener = _isConnectedSubscriptions.get(handler);
       NetInfo.removeEventListener(
         eventName,
         listener
@@ -219,22 +230,21 @@ var NetInfo = {
       _isConnectedSubscriptions.delete(handler);
     },
 
-    fetch: function(): Promise {
+    fetch(): Promise {
       return NetInfo.fetch().then(
         (connection) => _isConnected(connection)
       );
     },
   },
 
-  isConnectionMetered: ({}: {} | (callback:Function) => void),
+  isConnectionExpensive(): Promise {
+    return deprecatedCallback(
+      Platform.OS === 'android' ? RCTNetInfo.isConnectionMetered() : Promise.reject(new Error('Currently not supported on iOS')),
+      Array.prototype.slice.call(arguments),
+      'single-callback-value-first',
+      'NetInfo.isConnectionMetered(callback) is deprecated. Use the returned Promise instead.'
+    );
+  },
 };
 
-if (Platform.OS === 'android') {
-  NetInfo.isConnectionMetered = function(callback): void {
-    RCTNetInfo.isConnectionMetered((_isMetered) => {
-      callback(_isMetered);
-    });
-  };
-}
-
 module.exports = NetInfo;
diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m
index f52a20426ceb..1def6d4c7801 100644
--- a/Libraries/Network/RCTHTTPRequestHandler.m
+++ b/Libraries/Network/RCTHTTPRequestHandler.m
@@ -21,26 +21,16 @@ @implementation RCTHTTPRequestHandler
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
-                                           valueOptions:NSPointerFunctionsStrongMemory
-                                               capacity:0];
-  }
-  return self;
-}
-
 - (void)invalidate
 {
   [_session invalidateAndCancel];
   _session = nil;
-  _delegates = nil;
 }
 
 - (BOOL)isValid
 {
-  return _delegates != nil;
+  // if session == nil and delegates != nil, we've been invalidated
+  return _session || !_delegates;
 }
 
 #pragma mark - NSURLRequestHandler
@@ -62,12 +52,17 @@ - (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
 {
   // Lazy setup
   if (!_session && [self isValid]) {
+
     NSOperationQueue *callbackQueue = [NSOperationQueue new];
     callbackQueue.maxConcurrentOperationCount = 1;
     NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
     _session = [NSURLSession sessionWithConfiguration:configuration
                                              delegate:self
                                         delegateQueue:callbackQueue];
+
+    _delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
+                                           valueOptions:NSPointerFunctionsStrongMemory
+                                               capacity:0];
   }
 
   NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
diff --git a/Libraries/Network/RCTNetInfo.m b/Libraries/Network/RCTNetInfo.m
index 5aab64432488..067996b6c353 100644
--- a/Libraries/Network/RCTNetInfo.m
+++ b/Libraries/Network/RCTNetInfo.m
@@ -51,7 +51,7 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC
 
   if (![status isEqualToString:self->_status]) {
     self->_status = status;
-    [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkDidChange"
+    [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkStatusDidChange"
                                                       body:@{@"network_info": status}];
   }
 }
@@ -87,10 +87,10 @@ - (void)dealloc
 #pragma mark - Public API
 
 // TODO: remove error callback - not needed except by Subscribable interface
-RCT_EXPORT_METHOD(getCurrentReachability:(RCTResponseSenderBlock)getSuccess
-                  withErrorCallback:(__unused RCTResponseSenderBlock)getError)
+RCT_EXPORT_METHOD(getCurrentConnectivity:(RCTPromiseResolveBlock)resolve
+                  reject:(__unused RCTPromiseRejectBlock)reject)
 {
-  getSuccess(@[@{@"network_info": _status}]);
+  resolve(@[@{@"network_info": _status}]);
 }
 
 @end
diff --git a/Libraries/Network/RCTNetworkTask.h b/Libraries/Network/RCTNetworkTask.h
index 5265d8ef30c2..3f2de225ff8c 100644
--- a/Libraries/Network/RCTNetworkTask.h
+++ b/Libraries/Network/RCTNetworkTask.h
@@ -18,6 +18,12 @@ typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
 typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
 typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
 
+typedef NS_ENUM(NSInteger, RCTNetworkTaskStatus) {
+  RCTNetworkTaskPending = 0,
+  RCTNetworkTaskInProgress,
+  RCTNetworkTaskFinished,
+};
+
 @interface RCTNetworkTask : NSObject <RCTURLRequestDelegate>
 
 @property (nonatomic, readonly) NSURLRequest *request;
@@ -31,6 +37,8 @@ typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
 @property (nonatomic, copy) RCTURLRequestResponseBlock responseBlock;
 @property (nonatomic, copy) RCTURLRequestProgressBlock uploadProgressBlock;
 
+@property (nonatomic, readonly) RCTNetworkTaskStatus status;
+
 - (instancetype)initWithRequest:(NSURLRequest *)request
                         handler:(id<RCTURLRequestHandler>)handler
                 completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER;
diff --git a/Libraries/Network/RCTNetworkTask.m b/Libraries/Network/RCTNetworkTask.m
index 13aba182b9c3..ead015bccc27 100644
--- a/Libraries/Network/RCTNetworkTask.m
+++ b/Libraries/Network/RCTNetworkTask.m
@@ -33,6 +33,7 @@ - (instancetype)initWithRequest:(NSURLRequest *)request
     _request = request;
     _handler = handler;
     _completionBlock = completionBlock;
+    _status = RCTNetworkTaskPending;
   }
   return self;
 }
@@ -55,6 +56,7 @@ - (void)start
     if ([self validateRequestToken:[_handler sendRequest:_request
                                             withDelegate:self]]) {
       _selfReference = self;
+      _status = RCTNetworkTaskInProgress;
     }
   }
 }
@@ -66,6 +68,7 @@ - (void)cancel
     [_handler cancelRequest:strongToken];
   }
   [self invalidate];
+  _status = RCTNetworkTaskFinished;
 }
 
 - (BOOL)validateRequestToken:(id)requestToken
@@ -83,8 +86,9 @@ - (BOOL)validateRequestToken:(id)requestToken
     if (_completionBlock) {
       _completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0
         userInfo:@{NSLocalizedDescriptionKey: @"Unrecognized request token."}]);
-      [self invalidate];
     }
+    [self invalidate];
+    _status = RCTNetworkTaskFinished;
     return NO;
   }
   return YES;
@@ -130,8 +134,9 @@ - (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
   if ([self validateRequestToken:requestToken]) {
     if (_completionBlock) {
       _completionBlock(_response, _data, error);
-      [self invalidate];
     }
+    [self invalidate];
+    _status = RCTNetworkTaskFinished;
   }
 }
 
diff --git a/Libraries/Network/RCTNetworking.android.js b/Libraries/Network/RCTNetworking.android.js
index e8c4be0bd109..38f694d5452f 100644
--- a/Libraries/Network/RCTNetworking.android.js
+++ b/Libraries/Network/RCTNetworking.android.js
@@ -25,7 +25,7 @@ var generateRequestId = function() {
  */
 class RCTNetworking {
 
-  static sendRequest(method, url, headers, data, useIncrementalUpdates) {
+  static sendRequest(method, url, headers, data, useIncrementalUpdates, timeout) {
     var requestId = generateRequestId();
     RCTNetworkingNative.sendRequest(
       method,
@@ -33,13 +33,18 @@ class RCTNetworking {
       requestId,
       headers,
       data,
-      useIncrementalUpdates);
+      useIncrementalUpdates,
+      timeout);
     return requestId;
   }
 
   static abortRequest(requestId) {
     RCTNetworkingNative.abortRequest(requestId);
   }
+
+  static clearCookies(callback) {
+    RCTNetworkingNative.clearCookies(callback);
+  }
 }
 
 module.exports = RCTNetworking;
diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m
index 749c9e0b6020..88c2de0e06fe 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -56,9 +56,11 @@ @implementation RCTHTTPFormDataHelper
   return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
 }
 
-- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
+- (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
                                  callback:(RCTHTTPQueryResult)callback
 {
+  RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue");
+
   if (formData.count == 0) {
     return callback(nil, nil);
   }
@@ -76,6 +78,8 @@ - (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
 - (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result
                                          error:(NSError *)error
 {
+  RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on method queue");
+
   if (error) {
     return _callback(error, nil);
   }
@@ -130,36 +134,27 @@ @implementation RCTNetworking
 
 RCT_EXPORT_MODULE()
 
-- (void)setBridge:(RCTBridge *)bridge
+- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
 {
-  // get handlers
-  NSMutableArray<id<RCTURLRequestHandler>> *handlers = [NSMutableArray array];
-  for (id<RCTBridgeModule> module in bridge.modules.allValues) {
-    if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
-      [handlers addObject:(id<RCTURLRequestHandler>)module];
-    }
+  if (!request.URL) {
+    return nil;
   }
 
-  // Sort handlers in reverse priority order (highest priority first)
-  [handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
-    float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
-    float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
-    if (priorityA > priorityB) {
-      return NSOrderedAscending;
-    } else if (priorityA < priorityB) {
-      return NSOrderedDescending;
-    } else {
-      return NSOrderedSame;
-    }
-  }];
-
-  _bridge = bridge;
-  _handlers = handlers;
-  _tasksByRequestID = [NSMutableDictionary new];
-}
+  if (!_handlers) {
+    // Get handlers, sorted in reverse priority order (highest priority first)
+    _handlers = [[_bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
+      float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
+      float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
+      if (priorityA > priorityB) {
+        return NSOrderedAscending;
+      } else if (priorityA < priorityB) {
+        return NSOrderedDescending;
+      } else {
+        return NSOrderedSame;
+      }
+    }];
+  }
 
-- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
-{
   if (RCT_DEBUG) {
     // Check for handler conflicts
     float previousPriority = 0;
@@ -198,11 +193,13 @@ - (void)setBridge:(RCTBridge *)bridge
 - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query
                                  completionBlock:(void (^)(NSURLRequest *request))block
 {
+  RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue");
+
   NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null
   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
   request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
   request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]];
-
+  request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
   NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
   return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
     if (error) {
@@ -222,7 +219,10 @@ - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)q
       [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"];
     }
 
-    block(request);
+    dispatch_async(_methodQueue, ^{
+      block(request);
+    });
+
     return (RCTURLRequestCancellationBlock)nil;
   }];
 }
@@ -253,6 +253,8 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request
 - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query callback:
 (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))callback
 {
+  RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on method queue");
+
   if (!query) {
     return callback(nil, nil);
   }
@@ -265,7 +267,9 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary
 
     __block RCTURLRequestCancellationBlock cancellationBlock = nil;
     RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
-      cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
+      dispatch_async(_methodQueue, ^{
+        cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
+      });
     }];
 
     [task start];
@@ -278,7 +282,7 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary
       }
     };
   }
-  NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
+  NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
   if (formData) {
     RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new];
     formDataHelper.networker = self;
@@ -291,6 +295,8 @@ - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary
 
 - (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task
 {
+  RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
+
   if (data.length == 0) {
     return;
   }
@@ -335,6 +341,8 @@ - (void)sendRequest:(NSURLRequest *)request
  incrementalUpdates:(BOOL)incrementalUpdates
      responseSender:(RCTResponseSenderBlock)responseSender
 {
+  RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue");
+
   __block RCTNetworkTask *task;
 
   RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
@@ -356,7 +364,8 @@ - (void)sendRequest:(NSURLRequest *)request
         headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
         status = 200;
       }
-      NSArray<id> *responseJSON = @[task.requestID, @(status), headers];
+      id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
+      NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
       [_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
                                                   body:responseJSON];
     });
@@ -391,6 +400,9 @@ - (void)sendRequest:(NSURLRequest *)request
   task.uploadProgressBlock = uploadProgressBlock;
 
   if (task.requestID) {
+    if (!_tasksByRequestID) {
+      _tasksByRequestID = [NSMutableDictionary new];
+    }
     _tasksByRequestID[task.requestID] = task;
     responseSender(@[task.requestID]);
   }
@@ -443,7 +455,7 @@ @implementation RCTBridge (RCTNetworking)
 
 - (RCTNetworking *)networking
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTNetworking class])];
+  return [self moduleForClass:[RCTNetworking class]];
 }
 
 @end
diff --git a/Libraries/Network/XMLHttpRequest.android.js b/Libraries/Network/XMLHttpRequest.android.js
index e0ebc77d0afd..165b6d316e29 100644
--- a/Libraries/Network/XMLHttpRequest.android.js
+++ b/Libraries/Network/XMLHttpRequest.android.js
@@ -26,7 +26,7 @@ function convertHeadersMapToArray(headers: Object): Array<Header> {
 }
 
 class XMLHttpRequest extends XMLHttpRequestBase {
-  sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
+  sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
     var body;
     if (typeof data === 'string') {
       body = {string: data};
@@ -40,14 +40,14 @@ class XMLHttpRequest extends XMLHttpRequestBase {
     } else {
       body = data;
     }
-
     var useIncrementalUpdates = this.onreadystatechange ? true : false;
     var requestId = RCTNetworking.sendRequest(
       method,
       url,
       convertHeadersMapToArray(headers),
       body,
-      useIncrementalUpdates
+      useIncrementalUpdates,
+      timeout
     );
     this.didCreateRequest(requestId);
   }
diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js
index 56b319a254d8..d79e38019f48 100644
--- a/Libraries/Network/XMLHttpRequest.ios.js
+++ b/Libraries/Network/XMLHttpRequest.ios.js
@@ -13,7 +13,6 @@
 
 var FormData = require('FormData');
 var RCTNetworking = require('RCTNetworking');
-var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 
 var XMLHttpRequestBase = require('XMLHttpRequestBase');
 
@@ -24,7 +23,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
     this.upload = {};
   }
 
-  sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
+  sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
     if (typeof data === 'string') {
       data = {string: data};
     } else if (data instanceof FormData) {
@@ -37,6 +36,7 @@ class XMLHttpRequest extends XMLHttpRequestBase {
         data,
         headers,
         incrementalUpdates: this.onreadystatechange ? true : false,
+        timeout
       },
       this.didCreateRequest.bind(this)
     );
diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js
index 6c7bb8b48631..da96cc13f098 100644
--- a/Libraries/Network/XMLHttpRequestBase.js
+++ b/Libraries/Network/XMLHttpRequestBase.js
@@ -14,11 +14,23 @@
 var RCTNetworking = require('RCTNetworking');
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 
+const UNSENT = 0;
+const OPENED = 1;
+const HEADERS_RECEIVED = 2;
+const LOADING = 3;
+const DONE = 4;
+
 /**
  * Shared base for platform-specific XMLHttpRequest implementations.
  */
 class XMLHttpRequestBase {
 
+  static UNSENT: number;
+  static OPENED: number;
+  static HEADERS_RECEIVED: number;
+  static LOADING: number;
+  static DONE: number;
+
   UNSENT: number;
   OPENED: number;
   HEADERS_RECEIVED: number;
@@ -31,7 +43,11 @@ class XMLHttpRequestBase {
   readyState: number;
   responseHeaders: ?Object;
   responseText: ?string;
+  response: ?string;
+  responseType: '' | 'text';
   status: number;
+  timeout: number;
+  responseURL: ?string;
 
   upload: ?{
     onprogress?: (event: Object) => void;
@@ -48,15 +64,16 @@ class XMLHttpRequestBase {
   _lowerCaseResponseHeaders: Object;
 
   constructor() {
-    this.UNSENT = 0;
-    this.OPENED = 1;
-    this.HEADERS_RECEIVED = 2;
-    this.LOADING = 3;
-    this.DONE = 4;
+    this.UNSENT = UNSENT;
+    this.OPENED = OPENED;
+    this.HEADERS_RECEIVED = HEADERS_RECEIVED;
+    this.LOADING = LOADING;
+    this.DONE = DONE;
 
     this.onreadystatechange = null;
     this.onload = null;
     this.upload = undefined; /* Upload not supported yet */
+    this.timeout = 0;
 
     this._reset();
     this._method = null;
@@ -68,7 +85,10 @@ class XMLHttpRequestBase {
     this.readyState = this.UNSENT;
     this.responseHeaders = undefined;
     this.responseText = '';
+    this.response = null;
+    this.responseType = '';
     this.status = 0;
+    delete this.responseURL;
 
     this._requestId = null;
 
@@ -110,11 +130,16 @@ class XMLHttpRequestBase {
     }
   }
 
-  _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object): void {
+  _didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object, responseURL: ?string): void {
     if (requestId === this._requestId) {
       this.status = status;
       this.setResponseHeaders(responseHeaders);
       this.setReadyState(this.HEADERS_RECEIVED);
+      if (responseURL || responseURL === '') {
+        this.responseURL = responseURL;
+      } else {
+        delete this.responseURL;
+      }
     }
   }
 
@@ -125,6 +150,14 @@ class XMLHttpRequestBase {
       } else {
         this.responseText += responseText;
       }
+      switch(this.responseType) {
+      case '':
+      case 'text':
+        this.response = this.responseText;
+        break;
+      default: //TODO: Support other types, eg: document, arraybuffer, json, blob
+        this.response = null;
+      }
       this.setReadyState(this.LOADING);
     }
   }
@@ -179,6 +212,9 @@ class XMLHttpRequestBase {
       // async is default
       throw new Error('Synchronous http requests are not supported');
     }
+    if (!url) {
+      throw new Error('Cannot load an empty url');
+    }
     this._reset();
     this._method = method;
     this._url = url;
@@ -186,7 +222,7 @@ class XMLHttpRequestBase {
     this.setReadyState(this.OPENED);
   }
 
-  sendImpl(method: ?string, url: ?string, headers: Object, data: any): void {
+  sendImpl(method: ?string, url: ?string, headers: Object, data: any, timeout: number): void {
     throw new Error('Subclass must define sendImpl method');
   }
 
@@ -198,7 +234,7 @@ class XMLHttpRequestBase {
       throw new Error('Request has already been sent');
     }
     this._sent = true;
-    this.sendImpl(this._method, this._url, this._headers, data);
+    this.sendImpl(this._method, this._url, this._headers, data, this.timeout);
   }
 
   abort(): void {
@@ -235,7 +271,7 @@ class XMLHttpRequestBase {
     if (onreadystatechange) {
       // We should send an event to handler, but since we don't process that
       // event anywhere, let's leave it empty
-      onreadystatechange(null);
+      onreadystatechange.call(this, null);
     }
     if (newState === this.DONE && !this._aborted) {
       this._sendLoad();
@@ -253,4 +289,10 @@ class XMLHttpRequestBase {
   }
 }
 
+XMLHttpRequestBase.UNSENT = UNSENT;
+XMLHttpRequestBase.OPENED = OPENED;
+XMLHttpRequestBase.HEADERS_RECEIVED = HEADERS_RECEIVED;
+XMLHttpRequestBase.LOADING = LOADING;
+XMLHttpRequestBase.DONE = DONE;
+
 module.exports = XMLHttpRequestBase;
diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js
index 924f13573907..84bb1c0e7f0f 100644
--- a/Libraries/Picker/PickerIOS.ios.js
+++ b/Libraries/Picker/PickerIOS.ios.js
@@ -15,21 +15,21 @@
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var React = require('React');
 var ReactChildren = require('ReactChildren');
-var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
 var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants;
 var StyleSheet = require('StyleSheet');
+var StyleSheetPropType = require('StyleSheetPropType');
+var TextStylePropTypes = require('TextStylePropTypes');
 var View = require('View');
 
+var itemStylePropType = StyleSheetPropType(TextStylePropTypes);
 var requireNativeComponent = require('requireNativeComponent');
-var merge = require('merge');
-
-var PICKER = 'picker';
 
 var PickerIOS = React.createClass({
   mixins: [NativeMethodsMixin],
 
   propTypes: {
     ...View.propTypes,
+    itemStyle: itemStylePropType,
     onValueChange: React.PropTypes.func,
     selectedValue: React.PropTypes.any, // string or integer basically
   },
@@ -59,8 +59,8 @@ var PickerIOS = React.createClass({
     return (
       <View style={this.props.style}>
         <RCTPickerIOS
-          ref={PICKER}
-          style={styles.pickerIOS}
+          ref={picker => this._picker = picker}
+          style={[styles.pickerIOS, this.props.itemStyle]}
           items={this.state.items}
           selectedIndex={this.state.selectedIndex}
           onChange={this._onChange}
@@ -74,7 +74,7 @@ var PickerIOS = React.createClass({
       this.props.onChange(event);
     }
     if (this.props.onValueChange) {
-      this.props.onValueChange(event.nativeEvent.newValue);
+      this.props.onValueChange(event.nativeEvent.newValue, event.nativeEvent.newIndex);
     }
 
     // The picker is a controlled component. This means we expect the
@@ -83,8 +83,8 @@ var PickerIOS = React.createClass({
     // disallow/undo/mutate the selection of certain values. In other
     // words, the embedder of this component should be the source of
     // truth, not the native component.
-    if (this.state.selectedIndex !== event.nativeEvent.newIndex) {
-      this.refs[PICKER].setNativeProps({
+    if (this._picker && this.state.selectedIndex !== event.nativeEvent.newIndex) {
+      this._picker.setNativeProps({
         selectedIndex: this.state.selectedIndex
       });
     }
@@ -112,7 +112,11 @@ var styles = StyleSheet.create({
   },
 });
 
-var RCTPickerIOS = requireNativeComponent('RCTPicker', PickerIOS, {
+var RCTPickerIOS = requireNativeComponent('RCTPicker', {
+  propTypes: {
+    style: itemStylePropType,
+  },
+}, {
   nativeOnly: {
     items: true,
     onChange: true,
diff --git a/Libraries/Portal/Portal.js b/Libraries/Portal/Portal.js
index ac80e04109fe..f6ed4c8f7860 100644
--- a/Libraries/Portal/Portal.js
+++ b/Libraries/Portal/Portal.js
@@ -8,8 +8,8 @@
 
 var Platform = require('Platform');
 var React = require('React');
-var RCTUIManager = require('NativeModules').UIManager;
 var StyleSheet = require('StyleSheet');
+var UIManager = require('UIManager');
 var View = require('View');
 
 var _portalRef: any;
@@ -135,9 +135,9 @@ var Portal = React.createClass({
       // TextViews have no text set at the moment of populating event.
       setTimeout(() => {
         if (this._getOpenModals().length > 0) {
-          RCTUIManager.sendAccessibilityEvent(
+          UIManager.sendAccessibilityEvent(
             React.findNodeHandle(this),
-            RCTUIManager.AccessibilityEventTypes.typeWindowStateChanged);
+            UIManager.AccessibilityEventTypes.typeWindowStateChanged);
         }
       }, 0);
     }
diff --git a/Libraries/Promise.js b/Libraries/Promise.js
index 7cef2042332e..16cf30bf4706 100644
--- a/Libraries/Promise.js
+++ b/Libraries/Promise.js
@@ -1,37 +1,37 @@
 /**
+ * Copyright (c) 2016-present, Facebook, Inc.
+ * All rights reserved.
  *
- * Copyright 2013-2014 Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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 Promise
- *
- * This module wraps and augments the minimally ES6-compliant Promise
- * implementation provided by the promise npm package.
+ * @flow
  */
-
 'use strict';
 
-global.setImmediate = require('setImmediate');
-var Promise = require('promise/setimmediate/es6-extensions');
-require('promise/setimmediate/done');
-
-/**
- * Handle either fulfillment or rejection with the same callback.
- */
-Promise.prototype.finally = function(onSettled) {
-  return this.then(onSettled, onSettled);
-};
+const Promise = require('fbjs/lib/Promise.native');
 
+if (__DEV__) {
+  require('promise/setimmediate/rejection-tracking').enable({
+    allRejections: true,
+    onUnhandled: (id, error) => {
+      const {message, stack} = error;
+      const warning =
+        `Possible Unhandled Promise Rejection (id: ${id}):\n` +
+        (message == null ? '' : `${message}\n`) +
+        (stack == null ? '' : stack);
+      console.warn(warning);
+    },
+    onHandled: (id) => {
+      const warning =
+        `Promise Rejection Handled (id: ${id})\n` +
+        'This means you can ignore any previous messages of the form ' +
+        `"Possible Unhandled Promise Rejection (id: ${id}):"`;
+      console.warn(warning);
+    },
+  });
+}
 
 module.exports = Promise;
diff --git a/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js
new file mode 100644
index 000000000000..e784052c3960
--- /dev/null
+++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js
@@ -0,0 +1,100 @@
+/**
+ * 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 PullToRefreshViewAndroid
+ */
+'use strict';
+
+var ColorPropType = require('ColorPropType');
+var React = require('React');
+var RefreshLayoutConsts = require('UIManager').AndroidSwipeRefreshLayout.Constants;
+var View = require('View');
+
+var onlyChild = require('onlyChild');
+var requireNativeComponent = require('requireNativeComponent');
+
+var NATIVE_REF = 'native_swiperefreshlayout';
+
+/**
+ * Deprecated. Use `RefreshControl` instead.
+ *
+ * React view that supports a single scrollable child view (e.g. `ScrollView`). When this child
+ * view is at `scrollY: 0`, swiping down triggers an `onRefresh` event.
+ *
+ * The style `{flex: 1}` might be required to ensure the expected behavior of the child component
+ * (e.g. when the child is expected to scroll with `ScrollView` or `ListView`).
+ */
+var PullToRefreshViewAndroid = React.createClass({
+  statics: {
+    SIZE: RefreshLayoutConsts.SIZE,
+  },
+
+  propTypes: {
+    ...View.propTypes,
+    /**
+     * Whether the pull to refresh functionality is enabled
+     */
+    enabled: React.PropTypes.bool,
+    /**
+     * The colors (at least one) that will be used to draw the refresh indicator
+     */
+    colors: React.PropTypes.arrayOf(ColorPropType),
+    /**
+     * The background color of the refresh indicator
+     */
+    progressBackgroundColor: ColorPropType,
+    /**
+     * Whether the view should be indicating an active refresh
+     */
+    refreshing: React.PropTypes.bool,
+    /**
+     * Size of the refresh indicator, see PullToRefreshViewAndroid.SIZE
+     */
+    size: React.PropTypes.oneOf(RefreshLayoutConsts.SIZE.DEFAULT, RefreshLayoutConsts.SIZE.LARGE),
+  },
+
+  componentDidMount: function() {
+    console.warn('`PullToRefreshViewAndroid` is deprecated. Use `RefreshControl` instead.');
+  },
+
+  getInnerViewNode: function() {
+    return this.refs[NATIVE_REF];
+  },
+
+  setNativeProps: function(props) {
+    return this.refs[NATIVE_REF].setNativeProps(props);
+  },
+
+  render: function() {
+    return (
+      <NativePullToRefresh
+        colors={this.props.colors}
+        enabled={this.props.enabled}
+        onRefresh={this._onRefresh}
+        progressBackgroundColor={this.props.progressBackgroundColor}
+        ref={NATIVE_REF}
+        refreshing={this.props.refreshing}
+        size={this.props.size}
+        style={this.props.style}>
+        {onlyChild(this.props.children)}
+      </NativePullToRefresh>
+    );
+  },
+
+  _onRefresh: function() {
+    this.props.onRefresh && this.props.onRefresh();
+    this.refs[NATIVE_REF].setNativeProps({refreshing: !!this.props.refreshing});
+  }
+});
+
+var NativePullToRefresh = requireNativeComponent(
+  'AndroidSwipeRefreshLayout',
+  PullToRefreshViewAndroid
+);
+
+module.exports = PullToRefreshViewAndroid;
diff --git a/Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js
new file mode 100644
index 000000000000..2a16f851b827
--- /dev/null
+++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.ios.js
@@ -0,0 +1,13 @@
+/**
+ * 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 PullToRefreshViewAndroid
+ */
+ 'use strict';
+
+ module.exports = require('UnimplementedView');
diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js
index d4735fb417a6..f22beb36501f 100644
--- a/Libraries/PushNotificationIOS/PushNotificationIOS.js
+++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js
@@ -11,10 +11,9 @@
  */
 'use strict';
 
-var Map = require('Map');
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 var RCTPushNotificationManager = require('NativeModules').PushNotificationManager;
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var _notifHandlers = new Map();
 var _initialNotification = RCTPushNotificationManager &&
@@ -22,6 +21,7 @@ var _initialNotification = RCTPushNotificationManager &&
 
 var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived';
 var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
+var DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived';
 
 /**
  * Handle push notifications for your app, including permission handling and
@@ -30,7 +30,13 @@ var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
  * To get up and running, [configure your notifications with Apple](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW6)
  * and your server-side system. To get an idea, [this is the Parse guide](https://parse.com/tutorials/ios-push-notifications).
  *
- * To enable support for `notification` and `register` events you need to augment your AppDelegate.
+ * [Manually link](docs/linking-libraries-ios.html#manual-linking) the PushNotificationIOS library
+ *
+ * - Be sure to add the following to your `Header Search Paths`:
+ * `$(SRCROOT)/../node_modules/react-native/Libraries/PushNotificationIOS`
+ * - Set the search to `recursive`
+ *
+ * Finally, to enable support for `notification` and `register` events you need to augment your AppDelegate.
  *
  * At the top of your `AppDelegate.m`:
  *
@@ -39,15 +45,25 @@ var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered';
  * And then in your AppDelegate implementation add the following:
  *
  *   ```
- *   // Required for the register event.
+ *    // Required to register for notifications
+ *    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
+ *    {
+ *     [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
+ *    }
+ *    // Required for the register event.
  *    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
  *    {
- *     [RCTPushNotificationManager application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
+ *     [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
  *    }
  *    // Required for the notification event.
  *    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
  *    {
- *     [RCTPushNotificationManager application:application didReceiveRemoteNotification:notification];
+ *     [RCTPushNotificationManager didReceiveRemoteNotification:notification];
+ *    }
+ *    // Required for the localNotification event.
+ *    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
+ *    {
+ *     [RCTPushNotificationManager didReceiveLocalNotification:notification];
  *    }
  *   ```
  */
@@ -63,7 +79,10 @@ class PushNotificationIOS {
    * details is an object containing:
    *
    * - `alertBody` : The message displayed in the notification alert.
-   *
+   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
+   * - `soundName` : The sound played when the notification is fired (optional).
+   * - `category`  : The category of this notification, required for actionable notifications (optional).
+   * - `userInfo`  : An optional object containing additional notification data.
    */
   static presentLocalNotification(details: Object) {
     RCTPushNotificationManager.presentLocalNotification(details);
@@ -76,7 +95,10 @@ class PushNotificationIOS {
    *
    * - `fireDate` : The date and time when the system should deliver the notification.
    * - `alertBody` : The message displayed in the notification alert.
-   *
+   * - `alertAction` : The "action" displayed beneath an actionable notification. Defaults to "view";
+   * - `soundName` : The sound played when the notification is fired (optional).
+   * - `category`  : The category of this notification, required for actionable notifications (optional).
+   * - `userInfo` : An optional object containing additional notification data.
    */
   static scheduleLocalNotification(details: Object) {
     RCTPushNotificationManager.scheduleLocalNotification(details);
@@ -103,6 +125,17 @@ class PushNotificationIOS {
     RCTPushNotificationManager.getApplicationIconBadgeNumber(callback);
   }
 
+  /**
+   * Cancel local notifications.
+   *
+   * Optionally restricts the set of canceled notifications to those
+   * notifications whose `userInfo` fields match the corresponding fields
+   * in the `userInfo` argument.
+   */
+  static cancelLocalNotifications(userInfo: Object) {
+    RCTPushNotificationManager.cancelLocalNotifications(userInfo);
+  }
+
   /**
    * Attaches a listener to remote notification events while the app is running
    * in the foreground or the background.
@@ -116,8 +149,8 @@ class PushNotificationIOS {
    */
   static addEventListener(type: string, handler: Function) {
     invariant(
-      type === 'notification' || type === 'register',
-      'PushNotificationIOS only supports `notification` and `register` events'
+      type === 'notification' || type === 'register' || type === 'localNotification',
+      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
     );
     var listener;
     if (type === 'notification') {
@@ -127,6 +160,13 @@ class PushNotificationIOS {
           handler(new PushNotificationIOS(notifData));
         }
       );
+    } else if (type === 'localNotification') {
+      listener = RCTDeviceEventEmitter.addListener(
+        DEVICE_LOCAL_NOTIF_EVENT,
+        (notifData) => {
+          handler(new PushNotificationIOS(notifData));
+        }
+      );
     } else if (type === 'register') {
       listener = RCTDeviceEventEmitter.addListener(
         NOTIF_REGISTER_EVENT,
@@ -208,8 +248,8 @@ class PushNotificationIOS {
    */
   static removeEventListener(type: string, handler: Function) {
     invariant(
-      type === 'notification' || type === 'register',
-      'PushNotificationIOS only supports `notification` and `register` events'
+      type === 'notification' || type === 'register' || type === 'localNotification',
+      'PushNotificationIOS only supports `notification`, `register` and `localNotification` events'
     );
     var listener = _notifHandlers.get(handler);
     if (!listener) {
@@ -235,7 +275,7 @@ class PushNotificationIOS {
   }
 
   /**
-   * You will never need to instansiate `PushNotificationIOS` yourself.
+   * You will never need to instantiate `PushNotificationIOS` yourself.
    * Listening to the `notification` event and invoking
    * `popInitialNotification` is sufficient
    */
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
index 194bbc5ddeed..b7ab540151b5 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h
@@ -13,8 +13,9 @@
 
 @interface RCTPushNotificationManager : NSObject <RCTBridgeModule>
 
-+ (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
-+ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
-+ (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification;
++ (void)didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;
++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
++ (void)didReceiveRemoteNotification:(NSDictionary *)notification;
++ (void)didReceiveLocalNotification:(UILocalNotification *)notification;
 
 @end
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
index 751e8ec349ea..5dd5b4774464 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
@@ -24,6 +24,7 @@
 
 #endif
 
+NSString *const RCTLocalNotificationReceived = @"LocalNotificationReceived";
 NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
 NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
 
@@ -35,35 +36,21 @@ + (UILocalNotification *)UILocalNotification:(id)json
   UILocalNotification *notification = [UILocalNotification new];
   notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date];
   notification.alertBody = [RCTConvert NSString:details[@"alertBody"]];
+  notification.alertAction = [RCTConvert NSString:details[@"alertAction"]];
+  notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName;
+  notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]];
+  notification.category = [RCTConvert NSString:details[@"category"]];
   return notification;
 }
 
 @end
 
 @implementation RCTPushNotificationManager
-{
-  NSDictionary<NSString *, id> *_initialNotification;
-}
 
 RCT_EXPORT_MODULE()
 
 @synthesize bridge = _bridge;
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(handleRemoteNotificationReceived:)
-                                                 name:RCTRemoteNotificationReceived
-                                               object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(handleRemoteNotificationsRegistered:)
-                                                 name:RCTRemoteNotificationsRegistered
-                                               object:nil];
-  }
-  return self;
-}
-
 - (void)dealloc
 {
   [[NSNotificationCenter defaultCenter] removeObserver:self];
@@ -72,17 +59,36 @@ - (void)dealloc
 - (void)setBridge:(RCTBridge *)bridge
 {
   _bridge = bridge;
-  _initialNotification = [bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy];
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(handleLocalNotificationReceived:)
+                                               name:RCTLocalNotificationReceived
+                                             object:nil];
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(handleRemoteNotificationReceived:)
+                                               name:RCTRemoteNotificationReceived
+                                             object:nil];
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(handleRemoteNotificationsRegistered:)
+                                               name:RCTRemoteNotificationsRegistered
+                                             object:nil];
 }
 
-+ (void)application:(__unused UIApplication *)application didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings
+- (NSDictionary<NSString *, id> *)constantsToExport
 {
-  if ([application respondsToSelector:@selector(registerForRemoteNotifications)]) {
-    [application registerForRemoteNotifications];
+  NSDictionary<NSString *, id> *initialNotification =
+    [_bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] copy];
+  return @{@"initialNotification": RCTNullIfNil(initialNotification)};
+}
+
++ (void)didRegisterUserNotificationSettings:(__unused UIUserNotificationSettings *)notificationSettings
+{
+  if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)]) {
+    [[UIApplication sharedApplication] registerForRemoteNotifications];
   }
 }
 
-+ (void)application:(__unused UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
++ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
 {
   NSMutableString *hexString = [NSMutableString string];
   NSUInteger deviceTokenLength = deviceToken.length;
@@ -90,21 +96,38 @@ + (void)application:(__unused UIApplication *)application didRegisterForRemoteNo
   for (NSUInteger i = 0; i < deviceTokenLength; i++) {
     [hexString appendFormat:@"%02x", bytes[i]];
   }
-  NSDictionary<NSString *, id> *userInfo = @{
-    @"deviceToken" : [hexString copy]
-  };
   [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered
                                                       object:self
-                                                    userInfo:userInfo];
+                                                    userInfo:@{@"deviceToken" : [hexString copy]}];
 }
 
-+ (void)application:(__unused UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification
++ (void)didReceiveRemoteNotification:(NSDictionary *)notification
 {
   [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived
                                                       object:self
                                                     userInfo:notification];
 }
 
++ (void)didReceiveLocalNotification:(UILocalNotification *)notification
+{
+  NSMutableDictionary *details = [NSMutableDictionary new];
+  if (notification.alertBody) {
+    details[@"alertBody"] = notification.alertBody;
+  }
+  if (notification.userInfo) {
+    details[@"userInfo"] = RCTJSONClean(notification.userInfo);
+  }
+  [[NSNotificationCenter defaultCenter] postNotificationName:RCTLocalNotificationReceived
+                                                      object:self
+                                                    userInfo:details];
+}
+
+- (void)handleLocalNotificationReceived:(NSNotification *)notification
+{
+  [_bridge.eventDispatcher sendDeviceEventWithName:@"localNotificationReceived"
+                                              body:notification.userInfo];
+}
+
 - (void)handleRemoteNotificationReceived:(NSNotification *)notification
 {
   [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationReceived"
@@ -130,9 +153,7 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
  */
 RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback)
 {
-  callback(@[
-    @(RCTSharedApplication().applicationIconBadgeNumber)
-  ]);
+  callback(@[@(RCTSharedApplication().applicationIconBadgeNumber)]);
 }
 
 RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions)
@@ -143,13 +164,13 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
 
   UIUserNotificationType types = UIUserNotificationTypeNone;
   if (permissions) {
-    if ([permissions[@"alert"] boolValue]) {
+    if ([RCTConvert BOOL:permissions[@"alert"]]) {
       types |= UIUserNotificationTypeAlert;
     }
-    if ([permissions[@"badge"] boolValue]) {
+    if ([RCTConvert BOOL:permissions[@"badge"]]) {
       types |= UIUserNotificationTypeBadge;
     }
-    if ([permissions[@"sound"] boolValue]) {
+    if ([RCTConvert BOOL:permissions[@"sound"]]) {
       types |= UIUserNotificationTypeSound;
     }
   } else {
@@ -158,9 +179,9 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
 
   UIApplication *app = RCTSharedApplication();
   if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) {
-    UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil];
+    UIUserNotificationSettings *notificationSettings =
+      [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil];
     [app registerUserNotificationSettings:notificationSettings];
-    [app registerForRemoteNotifications];
   } else {
     [app registerForRemoteNotificationTypes:(NSUInteger)types];
   }
@@ -174,8 +195,7 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
 RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
 {
   if (RCTRunningInAppExtension()) {
-    NSDictionary<NSString *, NSNumber *> *permissions = @{@"alert": @NO, @"badge": @NO, @"sound": @NO};
-    callback(@[permissions]);
+    callback(@[@{@"alert": @NO, @"badge": @NO, @"sound": @NO}]);
     return;
   }
 
@@ -192,19 +212,11 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
 
   }
 
-  NSMutableDictionary<NSString *, NSNumber *> *permissions = [NSMutableDictionary new];
-  permissions[@"alert"] = @((types & UIUserNotificationTypeAlert) > 0);
-  permissions[@"badge"] = @((types & UIUserNotificationTypeBadge) > 0);
-  permissions[@"sound"] = @((types & UIUserNotificationTypeSound) > 0);
-
-  callback(@[permissions]);
-}
-
-- (NSDictionary<NSString *, id> *)constantsToExport
-{
-  return @{
-    @"initialNotification": RCTNullIfNil(_initialNotification),
-  };
+  callback(@[@{
+    @"alert": @((types & UIUserNotificationTypeAlert) > 0),
+    @"badge": @((types & UIUserNotificationTypeBadge) > 0),
+    @"sound": @((types & UIUserNotificationTypeSound) > 0),
+  }]);
 }
 
 RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification)
@@ -222,4 +234,21 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
   [RCTSharedApplication() cancelAllLocalNotifications];
 }
 
+RCT_EXPORT_METHOD(cancelLocalNotifications:(NSDictionary *)userInfo)
+{
+  for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) {
+    __block BOOL matchesAll = YES;
+    NSDictionary *notificationInfo = notification.userInfo;
+    [userInfo enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
+      if (![notificationInfo[key] isEqual:obj]) {
+        matchesAll = NO;
+        *stop = YES;
+      }
+    }];
+    if (matchesAll) {
+      [[UIApplication sharedApplication] cancelLocalNotification:notification];
+    }
+  }
+}
+
 @end
diff --git a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
index 063e32a48d67..a3ffe02b1755 100644
--- a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
+++ b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestCase.h
@@ -71,7 +71,7 @@ FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
 @property (readwrite, nonatomic, assign) BOOL recordMode;
 
 /**
- Performs the comparisong or records a snapshot of the layer if recordMode is YES.
+ Performs the comparisons or records a snapshot of the layer if recordMode is YES.
  @param layer The Layer to snapshot
  @param referenceImagesDirectory The directory in which reference images are stored.
  @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@@ -84,7 +84,7 @@ FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
                          error:(NSError **)errorPtr;
 
 /**
- Performs the comparisong or records a snapshot of the view if recordMode is YES.
+ Performs the comparisons or records a snapshot of the view if recordMode is YES.
  @param view The view to snapshot
  @param referenceImagesDirectory The directory in which reference images are stored.
  @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
diff --git a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
index 349384d8f8dd..449e3983a7b3 100644
--- a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
+++ b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.h
@@ -97,7 +97,7 @@ extern NSString *const FBReferenceImageFilePathKey;
 
 
 /**
- The directory in which referfence images are stored.
+ The directory in which reference images are stored.
  */
 @property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
 
diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m
index 3976f146ecf5..a889b8522a4e 100644
--- a/Libraries/RCTTest/RCTTestModule.m
+++ b/Libraries/RCTTest/RCTTestModule.m
@@ -29,14 +29,6 @@ - (dispatch_queue_t)methodQueue
   return _bridge.uiManager.methodQueue;
 }
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _snapshotCounter = [NSMutableDictionary new];
-  }
-  return self;
-}
-
 RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback)
 {
   RCTAssert(_controller != nil, @"No snapshot controller configured.");
@@ -44,6 +36,9 @@ - (instancetype)init
   [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
 
     NSString *testName = NSStringFromSelector(_testSelector);
+    if (!_snapshotCounter) {
+      _snapshotCounter = [NSMutableDictionary new];
+    }
     _snapshotCounter[testName] = (@([_snapshotCounter[testName] integerValue] + 1)).stringValue;
 
     NSError *error = nil;
@@ -67,7 +62,7 @@ - (instancetype)init
 
 RCT_REMAP_METHOD(shouldReject, shouldReject_resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
 {
-  reject(nil);
+  reject(nil, nil, nil);
 }
 
 RCT_EXPORT_METHOD(markTestCompleted)
diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m
index 573c7e0a24a8..6203d0f1dbf3 100644
--- a/Libraries/RCTTest/RCTTestRunner.m
+++ b/Libraries/RCTTest/RCTTestRunner.m
@@ -15,7 +15,8 @@
 #import "RCTRootView.h"
 #import "RCTTestModule.h"
 #import "RCTUtils.h"
-#import "RCTContextExecutor.h"
+#import "RCTJSCExecutor.h"
+#import "RCTBridge+Private.h"
 
 static const NSTimeInterval kTestTimeoutSeconds = 60;
 static const NSTimeInterval kTestTeardownTimeoutSeconds = 30;
@@ -112,8 +113,7 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName
     RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps];
     rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices
 
-    NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]);
-    RCTTestModule *testModule = rootView.bridge.modules[testModuleName];
+    RCTTestModule *testModule = [rootView.bridge moduleForClass:[RCTTestModule class]];
     RCTAssert(_testController != nil, @"_testController should not be nil");
     testModule.controller = _testController;
     testModule.testSelector = test;
@@ -135,9 +135,9 @@ - (void)runTest:(SEL)test module:(NSString *)moduleName
 
     // Take a weak reference to the JS context, so we track its deallocation later
     // (we can only do this now, since it's been lazily initialized)
-    id jsExecutor = [bridge valueForKeyPath:@"batchedBridge.javaScriptExecutor"];
-    if ([jsExecutor isKindOfClass:[RCTContextExecutor class]]) {
-      weakJSContext = [jsExecutor valueForKey:@"context"];
+    id jsExecutor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
+    if ([jsExecutor isKindOfClass:[RCTJSCExecutor class]]) {
+      weakJSContext = [jsExecutor valueForKey:@"_context"];
     }
     [rootView removeFromSuperview];
 
diff --git a/Libraries/RCTTest/SnapshotViewIOS.ios.js b/Libraries/RCTTest/SnapshotViewIOS.ios.js
index 7b6294be1ce7..3cd8bf04ae33 100644
--- a/Libraries/RCTTest/SnapshotViewIOS.ios.js
+++ b/Libraries/RCTTest/SnapshotViewIOS.ios.js
@@ -11,10 +11,10 @@
  */
 'use strict';
 
-var Platform = require('Platform');
 var React = require('React');
 var StyleSheet = require('StyleSheet');
-var { TestModule, UIManager } = require('NativeModules');
+var { TestModule } = require('NativeModules');
+var UIManager = require('UIManager');
 var View = require('View');
 
 var requireNativeComponent = require('requireNativeComponent');
diff --git a/Libraries/RKBackendNode/queryLayoutByID.js b/Libraries/RKBackendNode/queryLayoutByID.js
index d6492e6da487..5d038c232bb1 100644
--- a/Libraries/RKBackendNode/queryLayoutByID.js
+++ b/Libraries/RKBackendNode/queryLayoutByID.js
@@ -12,7 +12,7 @@
 'use strict';
 
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 type OnSuccessCallback = (
   left: number,
@@ -51,7 +51,7 @@ var queryLayoutByID = function(
   onSuccess: OnSuccessCallback
 ): void {
   // Native bridge doesn't *yet* surface errors.
-  RCTUIManager.measure(
+  UIManager.measure(
     ReactNativeTagHandles.rootNodeIDToTag[rootNodeID],
     onSuccess
   );
diff --git a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
index 55b22a959934..d1ffc1d402e2 100644
--- a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
+++ b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
@@ -12,16 +12,14 @@
 'use strict';
 
 var EventPropagators = require('EventPropagators');
-var NativeModules = require('NativeModules');
 var SyntheticEvent = require('SyntheticEvent');
+var UIManager = require('UIManager');
 
 var merge = require('merge');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
-var RCTUIManager = NativeModules.UIManager;
-
-var customBubblingEventTypes = RCTUIManager.customBubblingEventTypes;
-var customDirectEventTypes = RCTUIManager.customDirectEventTypes;
+var customBubblingEventTypes = UIManager.customBubblingEventTypes;
+var customDirectEventTypes = UIManager.customDirectEventTypes;
 
 var allTypesByEventName = {};
 
diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js
index b2b5ba41928d..128752d1ac75 100644
--- a/Libraries/ReactIOS/NativeMethodsMixin.js
+++ b/Libraries/ReactIOS/NativeMethodsMixin.js
@@ -11,13 +11,12 @@
  */
 'use strict';
 
-var NativeModules = require('NativeModules');
-var RCTUIManager = NativeModules.UIManager;
 var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
 var TextInputState = require('TextInputState');
+var UIManager = require('UIManager');
 
 var findNodeHandle = require('findNodeHandle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 type MeasureOnSuccessCallback = (
   x: number,
@@ -28,6 +27,13 @@ type MeasureOnSuccessCallback = (
   pageY: number
 ) => void
 
+type MeasureInWindowOnSuccessCallback = (
+  x: number,
+  y: number,
+  width: number,
+  height: number,
+) => void
+
 type MeasureLayoutOnSuccessCallback = (
   left: number,
   top: number,
@@ -57,7 +63,7 @@ function warnForStyleProps(props, validAttributes) {
  * composite components that aren't directly backed by a native view. This will
  * generally include most components that you define in your own app. For more
  * information, see [Direct
- * Manipulation](/react-native/docs/direct-manipulation.html).
+ * Manipulation](docs/direct-manipulation.html).
  */
 var NativeMethodsMixin = {
   /**
@@ -75,10 +81,32 @@ var NativeMethodsMixin = {
    * Note that these measurements are not available until after the rendering
    * has been completed in native. If you need the measurements as soon as
    * possible, consider using the [`onLayout`
-   * prop](/react-native/docs/view.html#onlayout) instead.
+   * prop](docs/view.html#onlayout) instead.
    */
   measure: function(callback: MeasureOnSuccessCallback) {
-    RCTUIManager.measure(
+    UIManager.measure(
+      findNodeHandle(this),
+      mountSafeCallback(this, callback)
+    );
+  },
+
+  /**
+   * Determines the location of the given view in the window and returns the
+   * values via an async callback. If the React root view is embedded in
+   * another native view, this will give you the absolute coordinates. If
+   * successful, the callback will be called be called with the following
+   * arguments:
+   *
+   *  - x
+   *  - y
+   *  - width
+   *  - height
+   *
+   * Note that these measurements are not available until after the rendering
+   * has been completed in native.
+   */
+  measureInWindow: function(callback: MeasureInWindowOnSuccessCallback) {
+    UIManager.measureInWindow(
       findNodeHandle(this),
       mountSafeCallback(this, callback)
     );
@@ -97,7 +125,7 @@ var NativeMethodsMixin = {
     onSuccess: MeasureLayoutOnSuccessCallback,
     onFail: () => void /* currently unused */
   ) {
-    RCTUIManager.measureLayout(
+    UIManager.measureLayout(
       findNodeHandle(this),
       relativeToNativeNode,
       mountSafeCallback(this, onFail),
@@ -109,7 +137,7 @@ var NativeMethodsMixin = {
    * This function sends props straight to native. They will not participate in
    * future diff process - this means that if you do not include them in the
    * next render, they will remain active (see [Direct
-   * Manipulation](/react-native/docs/direct-manipulation.html)).
+   * Manipulation](docs/direct-manipulation.html)).
    */
   setNativeProps: function(nativeProps: Object) {
     if (__DEV__) {
@@ -121,7 +149,7 @@ var NativeMethodsMixin = {
       this.viewConfig.validAttributes
     );
 
-    RCTUIManager.updateView(
+    UIManager.updateView(
       findNodeHandle(this),
       this.viewConfig.uiViewClassName,
       updatePayload
diff --git a/Libraries/ReactIOS/WarningBox.js b/Libraries/ReactIOS/WarningBox.js
deleted file mode 100644
index 09ac954c5502..000000000000
--- a/Libraries/ReactIOS/WarningBox.js
+++ /dev/null
@@ -1,393 +0,0 @@
-/**
- * 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 WarningBox
- */
-'use strict';
-
-var AsyncStorage = require('AsyncStorage');
-var EventEmitter = require('EventEmitter');
-var Map = require('Map');
-var PanResponder = require('PanResponder');
-var React = require('React');
-var StyleSheet = require('StyleSheet');
-var Text = require('Text');
-var TouchableOpacity = require('TouchableOpacity');
-var View = require('View');
-
-var invariant = require('invariant');
-var rebound = require('rebound');
-var stringifySafe = require('stringifySafe');
-
-var SCREEN_WIDTH = require('Dimensions').get('window').width;
-var IGNORED_WARNINGS_KEY = '__DEV_WARNINGS_IGNORED';
-
-var consoleWarn = console.warn.bind(console);
-
-var warningCounts = new Map();
-var ignoredWarnings: Array<string> = [];
-var totalWarningCount = 0;
-var warningCountEvents = new EventEmitter();
-
-/**
- * WarningBox renders warnings on top of the app being developed. Warnings help
- * guard against subtle yet significant issues that can impact the quality of
- * your application, such as accessibility and memory leaks. This "in your
- * face" style of warning allows developers to notice and correct these issues
- * as quickly as possible.
- *
- * The warning box is currently opt-in. Set the following flag to enable it:
- *
- * `console.yellowBoxEnabled = true;`
- *
- * If "ignore" is tapped on a warning, the WarningBox will record that warning
- * and will not display it again. This is useful for hiding errors that already
- * exist or have been introduced elsewhere. To re-enable all of the errors, set
- * the following:
- *
- * `console.yellowBoxResetIgnored = true;`
- *
- * This can also be set permanently, and ignore will only silence the warnings
- * until the next refresh.
- */
-
-if (__DEV__) {
-  console.warn = function() {
-    consoleWarn.apply(null, arguments);
-    if (!console.yellowBoxEnabled) {
-      return;
-    }
-    var warning = Array.prototype.map.call(arguments, stringifySafe).join(' ');
-    if (!console.yellowBoxResetIgnored &&
-        ignoredWarnings.indexOf(warning) !== -1) {
-      return;
-    }
-    var count = warningCounts.has(warning) ? warningCounts.get(warning) + 1 : 1;
-    warningCounts.set(warning, count);
-    totalWarningCount += 1;
-    warningCountEvents.emit('count', totalWarningCount);
-  };
-}
-
-function saveIgnoredWarnings() {
-  AsyncStorage.setItem(
-    IGNORED_WARNINGS_KEY,
-    JSON.stringify(ignoredWarnings),
-    function(err) {
-      if (err) {
-        console.warn('Could not save ignored warnings.', err);
-      }
-    }
-  );
-}
-
-AsyncStorage.getItem(IGNORED_WARNINGS_KEY, function(err, data) {
-  if (!err && data && !console.yellowBoxResetIgnored) {
-    ignoredWarnings = JSON.parse(data);
-  }
-});
-
-var WarningRow = React.createClass({
-  componentWillMount: function() {
-    this.springSystem = new rebound.SpringSystem();
-    this.dismissalSpring = this.springSystem.createSpring();
-    this.dismissalSpring.setRestSpeedThreshold(0.05);
-    this.dismissalSpring.setCurrentValue(0);
-    this.dismissalSpring.addListener({
-      onSpringUpdate: () => {
-        var val = this.dismissalSpring.getCurrentValue();
-        this.text && this.text.setNativeProps({
-          left: SCREEN_WIDTH * val,
-        });
-        this.container && this.container.setNativeProps({
-          opacity: 1 - val,
-        });
-        this.closeButton && this.closeButton.setNativeProps({
-          opacity: 1 - (val * 5),
-        });
-      },
-      onSpringAtRest: () => {
-        if (this.dismissalSpring.getCurrentValue()) {
-          this.collapseSpring.setEndValue(1);
-        }
-      },
-    });
-    this.collapseSpring = this.springSystem.createSpring();
-    this.collapseSpring.setRestSpeedThreshold(0.05);
-    this.collapseSpring.setCurrentValue(0);
-    this.collapseSpring.getSpringConfig().friction = 20;
-    this.collapseSpring.getSpringConfig().tension = 200;
-    this.collapseSpring.addListener({
-      onSpringUpdate: () => {
-        var val = this.collapseSpring.getCurrentValue();
-        this.container && this.container.setNativeProps({
-          height: Math.abs(46 - (val * 46)),
-        });
-      },
-      onSpringAtRest: () => {
-        this.props.onDismissed();
-      },
-    });
-    this.panGesture = PanResponder.create({
-      onStartShouldSetPanResponder: () => {
-        return !!this.dismissalSpring.getCurrentValue();
-      },
-      onMoveShouldSetPanResponder: () => true,
-      onPanResponderGrant: () => {
-        this.isResponderOnlyToBlockTouches =
-          !!this.dismissalSpring.getCurrentValue();
-      },
-      onPanResponderMove: (e, gestureState) => {
-        if (this.isResponderOnlyToBlockTouches) {
-          return;
-        }
-        this.dismissalSpring.setCurrentValue(gestureState.dx / SCREEN_WIDTH);
-      },
-      onPanResponderRelease: (e, gestureState) => {
-        if (this.isResponderOnlyToBlockTouches) {
-          return;
-        }
-        var gestureCompletion = gestureState.dx / SCREEN_WIDTH;
-        var doesGestureRelease = (gestureState.vx + gestureCompletion) > 0.5;
-        this.dismissalSpring.setEndValue(doesGestureRelease ? 1 : 0);
-      }
-    });
-  },
-  render: function() {
-    var countText;
-    if (warningCounts.get(this.props.warning) > 1) {
-      countText = (
-        <Text style={styles.bold}>
-          ({warningCounts.get(this.props.warning)}){" "}
-        </Text>
-      );
-    }
-    return (
-      <View
-        style={styles.warningBox}
-        ref={container => { this.container = container; }}
-        {...this.panGesture.panHandlers}>
-        <TouchableOpacity
-          onPress={this.props.onOpened}>
-          <Text
-            style={styles.warningText}
-            numberOfLines={2}
-            ref={text => { this.text = text; }}>
-            {countText}
-            {this.props.warning}
-          </Text>
-        </TouchableOpacity>
-        <View
-          ref={closeButton => { this.closeButton = closeButton; }}
-          style={styles.closeButton}>
-          <TouchableOpacity
-            onPress={() => {
-              this.dismissalSpring.setEndValue(1);
-            }}>
-            <Text style={styles.closeButtonText}>✕</Text>
-          </TouchableOpacity>
-        </View>
-      </View>
-    );
-  }
-});
-
-var WarningBoxOpened = React.createClass({
-  render: function() {
-    var countText;
-    if (warningCounts.get(this.props.warning) > 1) {
-      countText = (
-        <Text style={styles.bold}>
-          ({warningCounts.get(this.props.warning)}){" "}
-        </Text>
-      );
-    }
-    return (
-      <TouchableOpacity
-        activeOpacity={0.9}
-        onPress={this.props.onClose}
-        style={styles.yellowBox}>
-        <Text style={styles.yellowBoxText}>
-          {countText}
-          {this.props.warning}
-        </Text>
-        <View style={styles.yellowBoxButtons}>
-          <TouchableOpacity
-            onPress={this.props.onDismissed}
-            style={styles.yellowBoxButton}>
-            <Text style={styles.yellowBoxButtonText}>
-              Dismiss
-            </Text>
-          </TouchableOpacity>
-          <TouchableOpacity
-            onPress={this.props.onIgnored}
-            style={styles.yellowBoxButton}>
-            <Text style={styles.yellowBoxButtonText}>
-              Ignore
-            </Text>
-          </TouchableOpacity>
-        </View>
-      </TouchableOpacity>
-    );
-  },
-});
-
-var canMountWarningBox = true;
-
-var WarningBox = React.createClass({
-  getInitialState: function() {
-    return {
-      totalWarningCount,
-      openWarning: null,
-    };
-  },
-  componentWillMount: function() {
-    if (console.yellowBoxResetIgnored) {
-      AsyncStorage.setItem(IGNORED_WARNINGS_KEY, '[]', function(err) {
-        if (err) {
-          console.warn('Could not reset ignored warnings.', err);
-        }
-      });
-      ignoredWarnings = [];
-    }
-  },
-  componentDidMount: function() {
-    invariant(
-      canMountWarningBox,
-      'There can only be one WarningBox'
-    );
-    canMountWarningBox = false;
-    warningCountEvents.addListener(
-      'count',
-      this._onWarningCount
-    );
-  },
-  componentWillUnmount: function() {
-    warningCountEvents.removeAllListeners();
-    canMountWarningBox = true;
-  },
-  _onWarningCount: function(totalWarningCount) {
-    // Must use setImmediate because warnings often happen during render and
-    // state cannot be set while rendering
-    setImmediate(() => {
-      this.setState({ totalWarningCount, });
-    });
-  },
-  _onDismiss: function(warning) {
-    warningCounts.delete(warning);
-    this.setState({
-      openWarning: null,
-    });
-  },
-  render: function() {
-    if (warningCounts.size === 0) {
-      return <View />;
-    }
-    if (this.state.openWarning) {
-      return (
-        <WarningBoxOpened
-          warning={this.state.openWarning}
-          onClose={() => {
-            this.setState({ openWarning: null });
-          }}
-          onDismissed={this._onDismiss.bind(this, this.state.openWarning)}
-          onIgnored={() => {
-            ignoredWarnings.push(this.state.openWarning);
-            saveIgnoredWarnings();
-            this._onDismiss(this.state.openWarning);
-          }}
-        />
-      );
-    }
-    var warningRows = [];
-    warningCounts.forEach((count, warning) => {
-      warningRows.push(
-        <WarningRow
-          key={warning}
-          onOpened={() => {
-            this.setState({ openWarning: warning });
-          }}
-          onDismissed={this._onDismiss.bind(this, warning)}
-          warning={warning}
-        />
-      );
-    });
-    return (
-      <View style={styles.warningContainer}>
-        {warningRows}
-      </View>
-    );
-  },
-});
-
-var styles = StyleSheet.create({
-  bold: {
-    fontWeight: 'bold',
-  },
-  closeButton: {
-    position: 'absolute',
-    right: 0,
-    height: 46,
-    width: 46,
-  },
-  closeButtonText: {
-    color: 'white',
-    fontSize: 32,
-    position: 'relative',
-    left: 8,
-  },
-  warningContainer: {
-    position: 'absolute',
-    left: 0,
-    right: 0,
-    bottom: 0
-  },
-  warningBox: {
-    position: 'relative',
-    backgroundColor: 'rgba(171, 124, 36, 0.9)',
-    flex: 1,
-    height: 46,
-  },
-  warningText: {
-    color: 'white',
-    position: 'absolute',
-    left: 0,
-    marginLeft: 15,
-    marginRight: 46,
-    top: 7,
-  },
-  yellowBox: {
-    backgroundColor: 'rgba(171, 124, 36, 0.9)',
-    position: 'absolute',
-    left: 0,
-    right: 0,
-    top: 0,
-    bottom: 0,
-    padding: 15,
-    paddingTop: 35,
-  },
-  yellowBoxText: {
-    color: 'white',
-    fontSize: 20,
-  },
-  yellowBoxButtons: {
-    flexDirection: 'row',
-    position: 'absolute',
-    bottom: 0,
-  },
-  yellowBoxButton: {
-    flex: 1,
-    padding: 25,
-  },
-  yellowBoxButtonText: {
-    color: 'white',
-    fontSize: 16,
-  }
-});
-
-module.exports = WarningBox;
diff --git a/Libraries/ReactIOS/YellowBox.js b/Libraries/ReactIOS/YellowBox.js
new file mode 100644
index 000000000000..e1dddc14541e
--- /dev/null
+++ b/Libraries/ReactIOS/YellowBox.js
@@ -0,0 +1,364 @@
+/**
+ * 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 YellowBox
+ * @flow
+ */
+
+'use strict';
+
+const EventEmitter = require('EventEmitter');
+import type EmitterSubscription from 'EmitterSubscription';
+const Platform = require('Platform');
+const React = require('React');
+const StyleSheet = require('StyleSheet');
+
+const _warningEmitter = new EventEmitter();
+const _warningMap = new Map();
+
+/**
+ * YellowBox renders warnings at the bottom of the app being developed.
+ *
+ * Warnings help guard against subtle yet significant issues that can impact the
+ * quality of the app. This "in your face" style of warning allows developers to
+ * notice and correct these issues as quickly as possible.
+ *
+ * By default, the warning box is enabled in `__DEV__`. Set the following flag
+ * to disable it (and call `console.warn` to update any rendered <YellowBox>):
+ *
+ *   console.disableYellowBox = true;
+ *   console.warn('YellowBox is disabled.');
+ *
+ * Warnings can be ignored programmatically by setting the array:
+ *
+ *   console.ignoredYellowBox = ['Warning: ...'];
+ *
+ * Strings in `console.ignoredYellowBox` can be a prefix of the warning that
+ * should be ignored.
+ */
+
+if (__DEV__) {
+  const {error, warn} = console;
+  console.error = function() {
+    error.apply(console, arguments);
+    // Show yellow box for the `warning` module.
+    if (typeof arguments[0] === 'string' &&
+        arguments[0].startsWith('Warning: ')) {
+      updateWarningMap.apply(null, arguments);
+    }
+  };
+  console.warn = function() {
+    warn.apply(console, arguments);
+    updateWarningMap.apply(null, arguments);
+  };
+}
+
+/**
+ * Simple function for formatting strings.
+ *
+ * Replaces placeholders with values passed as extra arguments
+ *
+ * @param {string} format the base string
+ * @param ...args the values to insert
+ * @return {string} the replaced string
+ */
+function sprintf(format, ...args) {
+  var index = 0;
+  return format.replace(/%s/g, match => args[index++]);
+}
+
+function updateWarningMap(format, ...args): void {
+  const stringifySafe = require('stringifySafe');
+
+  format = String(format);
+  const argCount = (format.match(/%s/g) || []).length;
+  const warning = [
+    sprintf(format, ...args.slice(0, argCount)),
+    ...args.slice(argCount).map(stringifySafe),
+  ].join(' ');
+
+  const count = _warningMap.has(warning) ? _warningMap.get(warning) : 0;
+  _warningMap.set(warning, count + 1);
+  _warningEmitter.emit('warning', _warningMap);
+}
+
+function isWarningIgnored(warning: string): boolean {
+  return (
+    Array.isArray(console.ignoredYellowBox) &&
+    console.ignoredYellowBox.some(
+      ignorePrefix => warning.startsWith(ignorePrefix)
+    )
+  );
+}
+
+const WarningRow = ({count, warning, onPress}) => {
+  const Text = require('Text');
+  const TouchableHighlight = require('TouchableHighlight');
+  const View = require('View');
+
+  const countText = count > 1 ?
+    <Text style={styles.listRowCount}>{'(' + count + ') '}</Text> :
+    null;
+
+  return (
+    <View style={styles.listRow}>
+      <TouchableHighlight
+        activeOpacity={0.5}
+        onPress={onPress}
+        style={styles.listRowContent}
+        underlayColor="transparent">
+        <Text style={styles.listRowText} numberOfLines={2}>
+          {countText}
+          {warning}
+        </Text>
+      </TouchableHighlight>
+    </View>
+  );
+};
+
+const WarningInspector = ({
+  count,
+  warning,
+  onClose,
+  onDismiss,
+  onDismissAll,
+}) => {
+  const ScrollView = require('ScrollView');
+  const Text = require('Text');
+  const TouchableHighlight = require('TouchableHighlight');
+  const View = require('View');
+
+  const countSentence =
+    'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.';
+
+  return (
+    <TouchableHighlight
+      activeOpacity={0.95}
+      underlayColor={backgroundColor(0.8)}
+      onPress={onClose}
+      style={styles.inspector}>
+      <View style={styles.inspectorContent}>
+        <View style={styles.inspectorCount}>
+          <Text style={styles.inspectorCountText}>{countSentence}</Text>
+        </View>
+        <ScrollView style={styles.inspectorWarning}>
+          <Text style={styles.inspectorWarningText}>{warning}</Text>
+        </ScrollView>
+        <View style={styles.inspectorButtons}>
+          <TouchableHighlight
+            activeOpacity={0.5}
+            onPress={onDismiss}
+            style={styles.inspectorButton}
+            underlayColor="transparent">
+            <Text style={styles.inspectorButtonText}>
+              Dismiss
+            </Text>
+          </TouchableHighlight>
+          <TouchableHighlight
+            activeOpacity={0.5}
+            onPress={onDismissAll}
+            style={styles.inspectorButton}
+            underlayColor="transparent">
+            <Text style={styles.inspectorButtonText}>
+              Dismiss All
+            </Text>
+          </TouchableHighlight>
+        </View>
+      </View>
+    </TouchableHighlight>
+  );
+};
+
+class YellowBox extends React.Component {
+  state: {
+    inspecting: ?string;
+    warningMap: Map;
+  };
+  _listener: ?EmitterSubscription;
+
+  constructor(props: mixed, context: mixed) {
+    super(props, context);
+    this.state = {
+      inspecting: null,
+      warningMap: _warningMap,
+    };
+    this.dismissWarning = warning => {
+      const {inspecting, warningMap} = this.state;
+      if (warning) {
+        warningMap.delete(warning);
+      } else {
+        warningMap.clear();
+      }
+      this.setState({
+        inspecting: (warning && inspecting !== warning) ? inspecting : null,
+        warningMap,
+      });
+    };
+  }
+
+  componentDidMount() {
+    let scheduled = null;
+    this._listener = _warningEmitter.addListener('warning', warningMap => {
+      // Use `setImmediate` because warnings often happen during render, but
+      // state cannot be set while rendering.
+      scheduled = scheduled || setImmediate(() => {
+        scheduled = null;
+        this.setState({
+          warningMap,
+        });
+      });
+    });
+  }
+
+  componentWillUnmount() {
+    if (this._listener) {
+      this._listener.remove();
+    }
+  }
+
+  render() {
+    if (console.disableYellowBox || this.state.warningMap.size === 0) {
+      return null;
+    }
+    const ScrollView = require('ScrollView');
+    const View = require('View');
+
+    const inspecting = this.state.inspecting;
+    const inspector = inspecting !== null ?
+      <WarningInspector
+        count={this.state.warningMap.get(inspecting)}
+        warning={inspecting}
+        onClose={() => this.setState({inspecting: null})}
+        onDismiss={() => this.dismissWarning(inspecting)}
+        onDismissAll={() => this.dismissWarning(null)}
+      /> :
+      null;
+
+    const rows = [];
+    this.state.warningMap.forEach((count, warning) => {
+      if (!isWarningIgnored(warning)) {
+        rows.push(
+          <WarningRow
+            key={warning}
+            count={count}
+            warning={warning}
+            onPress={() => this.setState({inspecting: warning})}
+            onDismiss={() => this.dismissWarning(warning)}
+          />
+        );
+      }
+    });
+
+    const listStyle = [
+      styles.list,
+      // Additional `0.4` so the 5th row can peek into view.
+      {height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)},
+    ];
+    return (
+      <View style={inspector ? styles.fullScreen : listStyle}>
+        <ScrollView style={listStyle} scrollsToTop={false}>
+          {rows}
+        </ScrollView>
+        {inspector}
+      </View>
+    );
+  }
+}
+
+const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')';
+const textColor = 'white';
+const rowGutter = 1;
+const rowHeight = 46;
+
+var styles = StyleSheet.create({
+  fullScreen: {
+    backgroundColor: 'transparent',
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    top: 0,
+    bottom: 0,
+  },
+  inspector: {
+    backgroundColor: backgroundColor(0.95),
+    flex: 1,
+  },
+  inspectorContainer: {
+    flex: 1,
+  },
+  inspectorButtons: {
+    flexDirection: 'row',
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: 0,
+  },
+  inspectorButton: {
+    flex: 1,
+    padding: 22,
+  },
+  inspectorButtonText: {
+    color: textColor,
+    fontSize: 14,
+    opacity: 0.8,
+    textAlign: 'center',
+  },
+  inspectorContent: {
+    flex: 1,
+    paddingTop: 5,
+  },
+  inspectorCount: {
+    padding: 15,
+    paddingBottom: 0,
+  },
+  inspectorCountText: {
+    color: textColor,
+    fontSize: 14,
+  },
+  inspectorWarning: {
+    padding: 15,
+    position: 'absolute',
+    top: 39,
+    bottom: 60,
+  },
+  inspectorWarningText: {
+    color: textColor,
+    fontSize: 16,
+    fontWeight: '600',
+  },
+  list: {
+    backgroundColor: 'transparent',
+    position: 'absolute',
+    left: 0,
+    right: 0,
+    bottom: 0,
+  },
+  listRow: {
+    position: 'relative',
+    backgroundColor: backgroundColor(0.95),
+    flex: 1,
+    height: rowHeight,
+    marginTop: rowGutter,
+  },
+  listRowContent: {
+    flex: 1,
+  },
+  listRowCount: {
+    color: 'rgba(255, 255, 255, 0.5)',
+  },
+  listRowText: {
+    color: textColor,
+    position: 'absolute',
+    left: 0,
+    top: Platform.OS === 'android' ? 5 : 7,
+    marginLeft: 15,
+    marginRight: 15,
+  },
+});
+
+module.exports = YellowBox;
diff --git a/Libraries/ReactIOS/renderApplication.android.js b/Libraries/ReactIOS/renderApplication.android.js
index d299c2eb3842..b809ba08d0df 100644
--- a/Libraries/ReactIOS/renderApplication.android.js
+++ b/Libraries/ReactIOS/renderApplication.android.js
@@ -8,6 +8,7 @@
  *
  * @providesModule renderApplication
  */
+
 'use strict';
 
 var Inspector = require('Inspector');
@@ -18,7 +19,9 @@ var StyleSheet = require('StyleSheet');
 var Subscribable = require('Subscribable');
 var View = require('View');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
+
+var YellowBox = __DEV__ ? require('YellowBox') : null;
 
 // require BackAndroid so it sets the default handler that exits the app if no listeners respond
 require('BackAndroid');
@@ -89,10 +92,14 @@ var AppContainer = React.createClass({
         <Portal
           onModalVisibilityChanged={this.setRootAccessibility}/>
       </View>;
-
+    let yellowBox = null;
+    if (__DEV__) {
+      yellowBox = <YellowBox />;
+    }
     return this.state.enabled ?
       <View style={styles.appContainer}>
         {appView}
+        {yellowBox}
         {this.renderInspector()}
       </View> :
       appView;
diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js
index 01eb1e5d53f9..2e37716cc135 100644
--- a/Libraries/ReactIOS/renderApplication.ios.js
+++ b/Libraries/ReactIOS/renderApplication.ios.js
@@ -7,7 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule renderApplication
+ * @noflow
  */
+
 'use strict';
 
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
@@ -16,10 +18,10 @@ var StyleSheet = require('StyleSheet');
 var Subscribable = require('Subscribable');
 var View = require('View');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var Inspector = __DEV__ ? require('Inspector') : null;
-var WarningBox = __DEV__ ? require('WarningBox') : null;
+var YellowBox = __DEV__ ? require('YellowBox') : null;
 
 var AppContainer = React.createClass({
   mixins: [Subscribable.Mixin],
@@ -47,14 +49,16 @@ var AppContainer = React.createClass({
   },
 
   render: function() {
-    var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
-    var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
+    let yellowBox = null;
+    if (__DEV__) {
+      yellowBox = <YellowBox />;
+    }
     return (
       <View style={styles.appContainer}>
         <View collapsible={false} style={styles.appContainer} ref="main">
           {this.props.children}
         </View>
-        {warningBox}
+        {yellowBox}
         {this.state.inspector}
       </View>
     );
@@ -70,6 +74,7 @@ function renderApplication<D, P, S>(
     rootTag,
     'Expect to have a valid rootTag, instead got ', rootTag
   );
+  /* eslint-disable jsx-no-undef-with-namespace */
   React.render(
     <AppContainer rootTag={rootTag}>
       <RootComponent
@@ -79,6 +84,7 @@ function renderApplication<D, P, S>(
     </AppContainer>,
     rootTag
   );
+  /* eslint-enable jsx-no-undef-with-namespace */
 }
 
 var styles = StyleSheet.create({
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index 11defc0ada13..61993f799fb9 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -11,23 +11,25 @@
  */
 'use strict';
 
-var RCTUIManager = require('NativeModules').UIManager;
 var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
+var UIManager = require('UIManager');
 var UnimplementedView = require('UnimplementedView');
 
 var createReactNativeComponentClass = require('createReactNativeComponentClass');
+
 var insetsDiffer = require('insetsDiffer');
 var pointsDiffer = require('pointsDiffer');
 var matricesDiffer = require('matricesDiffer');
 var processColor = require('processColor');
+var resolveAssetSource = require('resolveAssetSource');
 var sizesDiffer = require('sizesDiffer');
 var verifyPropTypes = require('verifyPropTypes');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 /**
  * Used to create React components that directly wrap native component
  * implementations.  Config information is extracted from data exported from the
- * RCTUIManager module.  You should also wrap the native component in a
+ * UIManager module.  You should also wrap the native component in a
  * hand-written component with full propTypes definitions and other
  * documentation - pass the hand-written component in as `componentInterface` to
  * verify all the native props are documented via `propTypes`.
@@ -46,13 +48,13 @@ function requireNativeComponent(
   componentInterface?: ?ComponentInterface,
   extraConfig?: ?{nativeOnly?: Object},
 ): Function {
-  var viewConfig = RCTUIManager[viewName];
+  var viewConfig = UIManager[viewName];
   if (!viewConfig || !viewConfig.NativeProps) {
     warning(false, 'Native component for "%s" does not exist', viewName);
     return UnimplementedView;
   }
   var nativeProps = {
-    ...RCTUIManager.RCTView.NativeProps,
+    ...UIManager.RCTView.NativeProps,
     ...viewConfig.NativeProps,
   };
   viewConfig.uiViewClassName = viewName;
@@ -104,14 +106,22 @@ var TypeToDifferMap = {
   // (not yet implemented)
 };
 
+function processColorArray(colors: []): [] {
+  return colors && colors.map(processColor);
+}
+
 var TypeToProcessorMap = {
   // iOS Types
   CGColor: processColor,
-  CGColorArray: processColor,
+  CGColorArray: processColorArray,
   UIColor: processColor,
-  UIColorArray: processColor,
+  UIColorArray: processColorArray,
+  CGImage: resolveAssetSource,
+  UIImage: resolveAssetSource,
+  RCTImageSource: resolveAssetSource,
   // Android Types
   Color: processColor,
+  ColorArray: processColorArray,
 };
 
 module.exports = requireNativeComponent;
diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js
index 02715c080772..07432eacb58b 100644
--- a/Libraries/ReactIOS/verifyPropTypes.js
+++ b/Libraries/ReactIOS/verifyPropTypes.js
@@ -12,7 +12,6 @@
 'use strict';
 
 var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
-var View = require('View');
 
 export type ComponentInterface = ReactClass<any, any, any> | {
   name?: string;
@@ -40,14 +39,21 @@ function verifyPropTypes(
   var nativeProps = viewConfig.NativeProps;
   for (var prop in nativeProps) {
     if (!componentInterface.propTypes[prop] &&
-        !View.propTypes[prop] &&
         !ReactNativeStyleAttributes[prop] &&
         (!nativePropsToIgnore || !nativePropsToIgnore[prop])) {
-      throw new Error(
-        '`' + componentName + '` has no propType for native prop `' +
+      var message;
+      if (componentInterface.propTypes.hasOwnProperty(prop)) {
+        message = '`' + componentName + '` has incorrectly defined propType for native prop `' +
+        viewConfig.uiViewClassName + '.' + prop + '` of native type `' + nativeProps[prop];
+      } else {
+        message = '`' + componentName + '` has no propType for native prop `' +
         viewConfig.uiViewClassName + '.' + prop + '` of native type `' +
-        nativeProps[prop] + '`'
-      );
+        nativeProps[prop] + '`';
+      };
+      message += '\nIf you haven\'t changed this prop yourself, this usually means that ' +
+        'your versions of the native code and JavaScript code are out of sync. Updating both ' +
+        'should make this error go away.';
+      throw new Error(message);
     }
   }
 }
diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js
index a5554c0f4b51..148a366663ef 100644
--- a/Libraries/ReactNative/ReactNative.js
+++ b/Libraries/ReactNative/ReactNative.js
@@ -26,11 +26,10 @@ var ReactNativeMount = require('ReactNativeMount');
 var ReactPropTypes = require('ReactPropTypes');
 var ReactUpdates = require('ReactUpdates');
 
-var deprecated = require('deprecated');
 var findNodeHandle = require('findNodeHandle');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var onlyChild = require('onlyChild');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 ReactNativeDefaultInjection.inject();
 
@@ -85,6 +84,7 @@ var ReactNative = {
     map: ReactChildren.map,
     forEach: ReactChildren.forEach,
     count: ReactChildren.count,
+    toArray: ReactChildren.toArray,
     only: onlyChild
   },
   Component: ReactComponent,
diff --git a/Libraries/ReactNative/ReactNativeAttributePayload.js b/Libraries/ReactNative/ReactNativeAttributePayload.js
index 8ee8378c1054..74aa45cd75bc 100644
--- a/Libraries/ReactNative/ReactNativeAttributePayload.js
+++ b/Libraries/ReactNative/ReactNativeAttributePayload.js
@@ -143,7 +143,7 @@ function diffProperties(
   nextProps : Object,
   validAttributes : AttributeConfiguration
 ): ?Object {
-  var attributeConfig : ?AttributeConfiguration;
+  var attributeConfig : ?(CustomAttributeConfiguration | AttributeConfiguration);
   var nextProp;
   var prevProp;
 
diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js
index cfd3df3e832d..b879b45b6a51 100644
--- a/Libraries/ReactNative/ReactNativeBaseComponent.js
+++ b/Libraries/ReactNative/ReactNativeBaseComponent.js
@@ -14,13 +14,13 @@
 var NativeMethodsMixin = require('NativeMethodsMixin');
 var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
 var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
-var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
 var ReactMultiChild = require('ReactMultiChild');
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
 
 var registrationNames = ReactNativeEventEmitter.registrationNames;
 var putListener = ReactNativeEventEmitter.putListener;
@@ -46,31 +46,6 @@ var ReactNativeBaseComponent = function(
   this.viewConfig = viewConfig;
 };
 
-/**
- * Generates and caches arrays of the form:
- *
- *    [0, 1, 2, 3]
- *    [0, 1, 2, 3, 4]
- *    [0, 1]
- *
- * @param {number} size Size of array to generate.
- * @return {Array<number>} Array with values that mirror the index.
- */
-var cachedIndexArray = function(size) {
-  var cachedResult = cachedIndexArray._cache[size];
-  if (!cachedResult) {
-    var arr = [];
-    for (var i = 0; i < size; i++) {
-      arr[i] = i;
-    }
-    cachedIndexArray._cache[size] = arr;
-    return arr;
-  } else {
-    return cachedResult;
-  }
-};
-cachedIndexArray._cache = {};
-
 /**
  * Mixin for containers that contain UIViews. NOTE: markup is rendered markup
  * which is a `viewID` ... see the return value for `mountComponent` !
@@ -105,11 +80,11 @@ ReactNativeBaseComponent.Mixin = {
     // no children - let's avoid calling out to the native bridge for a large
     // portion of the children.
     if (mountImages.length) {
-      var indexes = cachedIndexArray(mountImages.length);
+
       // TODO: Pool these per platform view class. Reusing the `mountImages`
       // array would likely be a jit deopt.
       var createdTags = [];
-      for (var i = 0; i < mountImages.length; i++) {
+      for (var i = 0, l = mountImages.length; i < l; i++) {
         var mountImage = mountImages[i];
         var childTag = mountImage.tag;
         var childID = mountImage.rootNodeID;
@@ -123,8 +98,7 @@ ReactNativeBaseComponent.Mixin = {
         );
         createdTags[i] = mountImage.tag;
       }
-      RCTUIManager
-        .manageChildren(containerTag, null, null, createdTags, indexes, null);
+      UIManager.setChildren(containerTag, createdTags);
     }
   },
 
@@ -141,7 +115,11 @@ ReactNativeBaseComponent.Mixin = {
     this._currentElement = nextElement;
 
     if (__DEV__) {
-      deepFreezeAndThrowOnMutationInDev(this._currentElement.props);
+      for (var key in this.viewConfig.validAttributes) {
+        if (nextElement.props.hasOwnProperty(key)) {
+          deepFreezeAndThrowOnMutationInDev(nextElement.props[key]);
+        }
+      }
     }
 
     var updatePayload = ReactNativeAttributePayload.diff(
@@ -151,7 +129,7 @@ ReactNativeBaseComponent.Mixin = {
     );
 
     if (updatePayload) {
-      RCTUIManager.updateView(
+      UIManager.updateView(
         ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(this._rootNodeID),
         this.viewConfig.uiViewClassName,
         updatePayload
@@ -207,7 +185,11 @@ ReactNativeBaseComponent.Mixin = {
     var tag = ReactNativeTagHandles.allocateTag();
 
     if (__DEV__) {
-      deepFreezeAndThrowOnMutationInDev(this._currentElement.props);
+      for (var key in this.viewConfig.validAttributes) {
+        if (this._currentElement.props.hasOwnProperty(key)) {
+          deepFreezeAndThrowOnMutationInDev(this._currentElement.props[key]);
+        }
+      }
     }
 
     var updatePayload = ReactNativeAttributePayload.create(
@@ -216,10 +198,16 @@ ReactNativeBaseComponent.Mixin = {
     );
 
     var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
-    RCTUIManager.createView(
+    if (nativeTopRootID == null) {
+      invariant(
+        false,
+        'nativeTopRootID not found for tag ' + tag + ' view type ' +
+          this.viewConfig.uiViewClassName + ' with rootID ' + rootID);
+    }
+    UIManager.createView(
       tag,
       this.viewConfig.uiViewClassName,
-      nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null,
+      ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID],
       updatePayload
     );
 
diff --git a/Libraries/ReactNative/ReactNativeDOMIDOperations.js b/Libraries/ReactNative/ReactNativeDOMIDOperations.js
index af17f9e0742e..fbf90db50337 100644
--- a/Libraries/ReactNative/ReactNativeDOMIDOperations.js
+++ b/Libraries/ReactNative/ReactNativeDOMIDOperations.js
@@ -13,8 +13,8 @@
 
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
 var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
-var RCTUIManager = require('NativeModules').UIManager;
 var ReactPerf = require('ReactPerf');
+var UIManager = require('UIManager');
 
 /**
  * Updates a component's children by processing a series of updates.
@@ -59,7 +59,7 @@ var dangerouslyProcessChildrenUpdates = function(childrenUpdates, markupList) {
   for (var updateParentTagString in byContainerTag) {
     var updateParentTagNumber = +updateParentTagString;
     var childUpdatesToSend = byContainerTag[updateParentTagNumber];
-    RCTUIManager.manageChildren(
+    UIManager.manageChildren(
       updateParentTagNumber,
       childUpdatesToSend.moveFromIndices,
       childUpdatesToSend.moveToIndices,
@@ -93,7 +93,7 @@ var ReactNativeDOMIDOperations = {
     'dangerouslyReplaceNodeWithMarkupByID',
     function(id, mountImage) {
       var oldTag = ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(id);
-      RCTUIManager.replaceExistingNonRootView(oldTag, mountImage.tag);
+      UIManager.replaceExistingNonRootView(oldTag, mountImage.tag);
       ReactNativeTagHandles.associateRootNodeIDWithMountedNodeHandle(id, mountImage.tag);
     }
   ),
diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js
index 5185af953d31..ee97c2560e61 100644
--- a/Libraries/ReactNative/ReactNativeDefaultInjection.js
+++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js
@@ -23,6 +23,7 @@ var EventPluginUtils = require('EventPluginUtils');
 var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder');
 var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin');
 var NodeHandle = require('NodeHandle');
+var ReactElement = require('ReactElement');
 var ReactComponentEnvironment = require('ReactComponentEnvironment');
 var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
 var ReactEmptyComponent = require('ReactEmptyComponent');
@@ -37,8 +38,7 @@ var ReactUpdates = require('ReactUpdates');
 var ResponderEventPlugin = require('ResponderEventPlugin');
 var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle');
 
-var createReactNativeComponentClass = require('createReactNativeComponentClass');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 // Just to ensure this gets packaged, since its only caller is from Native.
 require('RCTEventEmitter');
@@ -81,12 +81,15 @@ function inject() {
     ReactNativeComponentEnvironment
   );
 
-  // Can't import View here because it depends on React to make its composite
-  var RCTView = createReactNativeComponentClass({
-    validAttributes: {},
-    uiViewClassName: 'RCTView',
-  });
-  ReactEmptyComponent.injection.injectEmptyComponent(RCTView);
+  var EmptyComponent = () => {
+    // Can't import View at the top because it depends on React to make its composite
+    var View = require('View');
+    return ReactElement.createElement(View, {
+      collapsable: true,
+      style: { position: 'absolute' }
+    });
+  };
+  ReactEmptyComponent.injection.injectEmptyComponent(EmptyComponent);
 
   EventPluginUtils.injection.injectMount(ReactNativeMount);
 
diff --git a/Libraries/ReactNative/ReactNativeEventEmitter.js b/Libraries/ReactNative/ReactNativeEventEmitter.js
index b0bb5659809b..c7680257aff5 100644
--- a/Libraries/ReactNative/ReactNativeEventEmitter.js
+++ b/Libraries/ReactNative/ReactNativeEventEmitter.js
@@ -18,7 +18,7 @@ var NodeHandle = require('NodeHandle');
 var EventConstants = require('EventConstants');
 
 var merge = require('merge');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var topLevelTypes = EventConstants.topLevelTypes;
 
@@ -107,7 +107,7 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
    *
    * @see receiveEvent.
    *
-   * @param {rootNodeID} rootNodeID React root node ID that event occured on.
+   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
    * @param {TopLevelType} topLevelType Top level type of event.
    * @param {object} nativeEventParam Object passed from native.
    */
@@ -127,9 +127,9 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
   },
 
   /**
-   * Publically exposed method on module for native objc to invoke when a top
+   * Publicly exposed method on module for native objc to invoke when a top
    * level event is extracted.
-   * @param {rootNodeID} rootNodeID React root node ID that event occured on.
+   * @param {rootNodeID} rootNodeID React root node ID that event occurred on.
    * @param {TopLevelType} topLevelType Top level type of event.
    * @param {object} nativeEventParam Object passed from native.
    */
@@ -183,7 +183,7 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
 
     for (var jj = 0; jj < changedTouches.length; jj++) {
       var touch = changedTouches[jj];
-      // Touch objects can fullfill the role of `DOM` `Event` objects if we set
+      // Touch objects can fulfill the role of `DOM` `Event` objects if we set
       // the `changedTouches`/`touches`. This saves allocations.
       touch.changedTouches = changedTouches;
       touch.touches = touches;
diff --git a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js
index 1f548c3ebcc2..1094e60451b8 100644
--- a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js
+++ b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js
@@ -11,18 +11,18 @@
  */
 'use strict';
 
-var RCTUIManager = require('NativeModules').UIManager;
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
+var UIManager = require('UIManager');
 
 var ReactNativeGlobalResponderHandler = {
   onChange: function(from: string, to: string, blockNativeResponder: boolean) {
     if (to !== null) {
-      RCTUIManager.setJSResponder(
+      UIManager.setJSResponder(
         ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to),
         blockNativeResponder
       );
     } else {
-      RCTUIManager.clearJSResponder();
+      UIManager.clearJSResponder();
     }
   }
 };
diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js
index 02dbcb40fc7e..cf8c5bb336fd 100644
--- a/Libraries/ReactNative/ReactNativeMount.js
+++ b/Libraries/ReactNative/ReactNativeMount.js
@@ -11,16 +11,15 @@
  */
 'use strict';
 
-var RCTUIManager = require('NativeModules').UIManager;
-
 var ReactElement = require('ReactElement');
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
 var ReactPerf = require('ReactPerf');
 var ReactReconciler = require('ReactReconciler');
 var ReactUpdateQueue = require('ReactUpdateQueue');
 var ReactUpdates = require('ReactUpdates');
+var UIManager = require('UIManager');
 
-var emptyObject = require('emptyObject');
+var emptyObject = require('fbjs/lib/emptyObject');
 var instantiateReactComponent = require('instantiateReactComponent');
 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
 
@@ -189,15 +188,9 @@ var ReactNativeMount = {
         mountImage.rootNodeID,
         mountImage.tag
       );
-      var addChildTags = [mountImage.tag];
-      var addAtIndices = [0];
-      RCTUIManager.manageChildren(
+      UIManager.setChildren(
         ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID),
-        null,         // moveFromIndices
-        null,         // moveToIndices
-        addChildTags,
-        addAtIndices,
-        null          // removeAtIndices
+        [mountImage.tag]
       );
     }
   ),
@@ -215,7 +208,7 @@ var ReactNativeMount = {
   ) {
     ReactNativeMount.unmountComponentAtNode(containerTag);
     // call back into native to remove all of the subviews from this container
-    RCTUIManager.removeRootView(containerTag);
+    UIManager.removeRootView(containerTag);
   },
 
   /**
@@ -256,7 +249,7 @@ var ReactNativeMount = {
     ReactReconciler.unmountComponent(instance);
     var containerTag =
       ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(containerID);
-    RCTUIManager.removeSubviewsFromContainerWithID(containerTag);
+    UIManager.removeSubviewsFromContainerWithID(containerTag);
   },
 
   getNode: function(rootNodeID: string): number {
diff --git a/Libraries/ReactNative/ReactNativeReconcileTransaction.js b/Libraries/ReactNative/ReactNativeReconcileTransaction.js
index 309630e3cc36..b6721964988f 100644
--- a/Libraries/ReactNative/ReactNativeReconcileTransaction.js
+++ b/Libraries/ReactNative/ReactNativeReconcileTransaction.js
@@ -66,7 +66,7 @@ var Mixin = {
    * @see Transaction
    * @abstract
    * @final
-   * @return {array<object>} List of operation wrap proceedures.
+   * @return {array<object>} List of operation wrap procedures.
    *   TODO: convert to array<TransactionWrapper>
    */
   getTransactionWrappers: function() {
@@ -83,7 +83,7 @@ var Mixin = {
 
   /**
    * `PooledClass` looks for this, and will invoke this before allowing this
-   * instance to be resused.
+   * instance to be reused.
    */
   destructor: function() {
     CallbackQueue.release(this.reactMountReady);
diff --git a/Libraries/ReactNative/ReactNativeTagHandles.js b/Libraries/ReactNative/ReactNativeTagHandles.js
index ab350817c6e9..5a0de4cda393 100644
--- a/Libraries/ReactNative/ReactNativeTagHandles.js
+++ b/Libraries/ReactNative/ReactNativeTagHandles.js
@@ -11,8 +11,8 @@
  */
 'use strict';
 
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
 
 /**
  * Keeps track of allocating and associating native "tags" which are numeric,
diff --git a/Libraries/ReactNative/ReactNativeTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js
index a7694c1394c9..0eb5e0a6b0d0 100644
--- a/Libraries/ReactNative/ReactNativeTextComponent.js
+++ b/Libraries/ReactNative/ReactNativeTextComponent.js
@@ -12,10 +12,10 @@
 'use strict';
 
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 var assign = require('Object.assign');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var ReactNativeTextComponent = function(props) {
   // This constructor and its argument is currently used by mocks.
@@ -39,7 +39,7 @@ assign(ReactNativeTextComponent.prototype, {
     this._rootNodeID = rootID;
     var tag = ReactNativeTagHandles.allocateTag();
     var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID);
-    RCTUIManager.createView(
+    UIManager.createView(
       tag,
       'RCTRawText',
       nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null,
@@ -57,7 +57,7 @@ assign(ReactNativeTextComponent.prototype, {
       var nextStringText = '' + nextText;
       if (nextStringText !== this._stringText) {
         this._stringText = nextStringText;
-        RCTUIManager.updateView(
+        UIManager.updateView(
           ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(
             this._rootNodeID
           ),
diff --git a/Libraries/ReactNative/UIManagerStatTracker.js b/Libraries/ReactNative/UIManagerStatTracker.js
index ef9c64b2bbb5..a3b8779234f5 100644
--- a/Libraries/ReactNative/UIManagerStatTracker.js
+++ b/Libraries/ReactNative/UIManagerStatTracker.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var RCTUIManager = require('NativeModules').UIManager;
+var UIManager = require('UIManager');
 
 var installed = false;
 var UIManagerStatTracker = {
@@ -32,20 +32,20 @@ var UIManagerStatTracker = {
         statLogHandle = setImmediate(printStats);
       }
     }
-    var createViewOrig = RCTUIManager.createView;
-    RCTUIManager.createView = function(tag, className, rootTag, props) {
+    var createViewOrig = UIManager.createView;
+    UIManager.createView = function(tag, className, rootTag, props) {
       incStat('createView', 1);
       incStat('setProp', Object.keys(props || []).length);
       createViewOrig(tag, className, rootTag, props);
     };
-    var updateViewOrig = RCTUIManager.updateView;
-    RCTUIManager.updateView = function(tag, className, props) {
+    var updateViewOrig = UIManager.updateView;
+    UIManager.updateView = function(tag, className, props) {
       incStat('updateView', 1);
       incStat('setProp', Object.keys(props || []).length);
       updateViewOrig(tag, className, props);
     };
-    var manageChildrenOrig = RCTUIManager.manageChildren;
-    RCTUIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) {
+    var manageChildrenOrig = UIManager.manageChildren;
+    UIManager.manageChildren = function(tag, moveFrom, moveTo, addTags, addIndices, remove) {
       incStat('manageChildren', 1);
       incStat('move', Object.keys(moveFrom || []).length);
       incStat('remove', Object.keys(remove || []).length);
diff --git a/Libraries/ReactNative/findNodeHandle.js b/Libraries/ReactNative/findNodeHandle.js
index 37c772760c50..dd3191b82d92 100644
--- a/Libraries/ReactNative/findNodeHandle.js
+++ b/Libraries/ReactNative/findNodeHandle.js
@@ -16,8 +16,8 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
 var ReactInstanceMap = require('ReactInstanceMap');
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
 
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
 
 /**
  * ReactNative vs ReactWeb
diff --git a/Libraries/Sample/Sample.android.js b/Libraries/Sample/Sample.android.js
index 2024dbdaeafc..1a66fb9bf575 100644
--- a/Libraries/Sample/Sample.android.js
+++ b/Libraries/Sample/Sample.android.js
@@ -6,7 +6,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var Sample = {
   test: function() {
diff --git a/Libraries/Settings/RCTSettingsManager.h b/Libraries/Settings/RCTSettingsManager.h
index 274cc69aed43..e0c2908806a6 100644
--- a/Libraries/Settings/RCTSettingsManager.h
+++ b/Libraries/Settings/RCTSettingsManager.h
@@ -13,6 +13,6 @@
 
 @interface RCTSettingsManager : NSObject <RCTBridgeModule>
 
-- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults;
 
 @end
diff --git a/Libraries/Settings/RCTSettingsManager.m b/Libraries/Settings/RCTSettingsManager.m
index 3e812e2462d7..c0c915df777c 100644
--- a/Libraries/Settings/RCTSettingsManager.m
+++ b/Libraries/Settings/RCTSettingsManager.m
@@ -24,23 +24,31 @@ @implementation RCTSettingsManager
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
+- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults
 {
-  return [self initWithUserDefaults:[NSUserDefaults standardUserDefaults]];
+  if ((self = [self init])) {
+    _defaults = defaults;
+  }
+  return self;
 }
 
-- (instancetype)initWithUserDefaults:(NSUserDefaults *)defaults
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    _defaults = defaults;
+  _bridge = bridge;
 
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(userDefaultsDidChange:)
-                                                 name:NSUserDefaultsDidChangeNotification
-                                               object:_defaults];
+  if (!_defaults) {
+    _defaults = [NSUserDefaults standardUserDefaults];
   }
 
-  return self;
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(userDefaultsDidChange:)
+                                               name:NSUserDefaultsDidChangeNotification
+                                             object:_defaults];
+}
+
+- (NSDictionary<NSString *, id> *)constantsToExport
+{
+  return @{@"settings": RCTJSONClean([_defaults dictionaryRepresentation])};
 }
 
 - (void)dealloc
@@ -59,13 +67,6 @@ - (void)userDefaultsDidChange:(NSNotification *)note
    body:RCTJSONClean([_defaults dictionaryRepresentation])];
 }
 
-- (NSDictionary<NSString *, id> *)constantsToExport
-{
-  return @{
-    @"settings": RCTJSONClean([_defaults dictionaryRepresentation])
-  };
-}
-
 /**
  * Set one or more values in the settings.
  * TODO: would it be useful to have a callback for when this has completed?
@@ -89,7 +90,7 @@ - (void)userDefaultsDidChange:(NSNotification *)note
 /**
  * Remove some values from the settings.
  */
-RCT_EXPORT_METHOD(deleteValues:(NSStringArray *)keys)
+RCT_EXPORT_METHOD(deleteValues:(NSArray<NSString *> *)keys)
 {
   _ignoringUpdates = YES;
   for (NSString *key in keys) {
diff --git a/Libraries/Settings/Settings.ios.js b/Libraries/Settings/Settings.ios.js
index 73d66ca587c3..3d1036fedf49 100644
--- a/Libraries/Settings/Settings.ios.js
+++ b/Libraries/Settings/Settings.ios.js
@@ -14,7 +14,7 @@
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 var RCTSettingsManager = require('NativeModules').SettingsManager;
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var subscriptions: Array<{keys: Array<string>; callback: ?Function}> = [];
 
diff --git a/Libraries/Storage/AsyncStorage.js b/Libraries/Storage/AsyncStorage.js
index c5be992e2aba..f1ed35c3c1c5 100644
--- a/Libraries/Storage/AsyncStorage.js
+++ b/Libraries/Storage/AsyncStorage.js
@@ -7,6 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule AsyncStorage
+ * @noflow
  * @flow-weak
  */
 'use strict';
@@ -32,6 +33,10 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncSQLiteStorage || RCTAsyn
  * method returns a `Promise` object.
  */
 var AsyncStorage = {
+  _getRequests: ([]: Array<any>),
+  _getKeys: ([]: Array<string>),
+  _immediate: (null: ?number),
+
   /**
    * Fetches `key` and passes the result to `callback`, along with an `Error` if
    * there is any. Returns a `Promise` object.
@@ -163,6 +168,34 @@ var AsyncStorage = {
    * indicate which key caused the error.
    */
 
+  /** Flushes any pending requests using a single multiget */
+  flushGetRequests: function(): void {
+    const getRequests = this._getRequests;
+    const getKeys = this._getKeys;
+
+    this._getRequests = [];
+    this._getKeys = [];
+
+    RCTAsyncStorage.multiGet(getKeys, function(errors, result) {
+      // Even though the runtime complexity of this is theoretically worse vs if we used a map,
+      // it's much, much faster in practice for the data sets we deal with (we avoid
+      // allocating result pair arrays). This was heavily benchmarked.
+      //
+      // Is there a way to avoid using the map but fix the bug in this breaking test?
+      // https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
+      let map = {};
+      result.forEach(([key, value]) => map[key] = value);
+      const reqLength = getRequests.length;
+      for (let i = 0; i < reqLength; i++) {
+        const request = getRequests[i];
+        const requestKeys = request.keys;
+        let requestResult = requestKeys.map(key => [key, map[key]]);
+        request.callback && request.callback(null, requestResult);
+        request.resolve && request.resolve(requestResult);
+      }
+    });
+  },
+
   /**
    * multiGet invokes callback with an array of key-value pair arrays that
    * matches the input format of multiSet. Returns a `Promise` object.
@@ -173,17 +206,36 @@ var AsyncStorage = {
     keys: Array<string>,
     callback?: ?(errors: ?Array<Error>, result: ?Array<Array<string>>) => void
   ): Promise {
-    return new Promise((resolve, reject) => {
-      RCTAsyncStorage.multiGet(keys, function(errors, result) {
-        var error = convertErrors(errors);
-        callback && callback(error, result);
-        if (error) {
-          reject(error);
-        } else {
-          resolve(result);
-        }
+    if (!this._immediate) {
+      this._immediate = setImmediate(() => {
+        this._immediate = null;
+        this.flushGetRequests();
       });
+    }
+
+    var getRequest = {
+      keys: keys,
+      callback: callback,
+      // do we need this?
+      keyIndex: this._getKeys.length,
+      resolve: null,
+      reject: null,
+    };
+
+    var promiseResult = new Promise((resolve, reject) => {
+      getRequest.resolve = resolve;
+      getRequest.reject = reject;
+    });
+
+    this._getRequests.push(getRequest);
+    // avoid fetching duplicates
+    keys.forEach(key => {
+      if (this._getKeys.indexOf(key) === -1) {
+        this._getKeys.push(key);
+      }
     });
+
+    return promiseResult;
   },
 
   /**
diff --git a/Libraries/StyleSheet/ColorPropType.js b/Libraries/StyleSheet/ColorPropType.js
new file mode 100644
index 000000000000..ab6cf45243d0
--- /dev/null
+++ b/Libraries/StyleSheet/ColorPropType.js
@@ -0,0 +1,62 @@
+ /**
+  * 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 ColorPropType
+  */
+'use strict';
+
+var ReactPropTypes = require('ReactPropTypes');
+var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
+
+var normalizeColor = require('normalizeColor');
+
+var colorPropType = function(isRequired, props, propName, componentName, location, propFullName) {
+  var color = props[propName];
+  if (color === undefined || color === null) {
+    if (isRequired) {
+      var locationName = ReactPropTypeLocationNames[location];
+      return new Error(
+        'Required ' + locationName + ' `' + (propFullName || propName) +
+        '` was not specified in `' + componentName + '`.'
+      );
+    }
+    return;
+  }
+
+  if (typeof color === 'number') {
+    // Developers should not use a number, but we are using the prop type
+    // both for user provided colors and for transformed ones. This isn't ideal
+    // and should be fixed but will do for now...
+    return;
+  }
+
+  if (normalizeColor(color) === null) {
+    var locationName = ReactPropTypeLocationNames[location];
+    return new Error(
+      'Invalid ' + locationName + ' `' + (propFullName || propName) +
+      '` supplied to `' + componentName + '`: ' + color + '\n' +
+`Valid color formats are
+  - '#f0f' (#rgb)
+  - '#f0fc' (#rgba)
+  - '#ff00ff' (#rrggbb)
+  - '#ff00ff00' (#rrggbbaa)
+  - 'rgb(255, 255, 255)'
+  - 'rgba(255, 255, 255, 1.0)'
+  - 'hsl(360, 100%, 100%)'
+  - 'hsla(360, 100%, 100%, 1.0)'
+  - 'transparent'
+  - 'red'
+  - 0xff00ff00 (0xrrggbbaa)
+`);
+  }
+};
+
+var ColorPropType = colorPropType.bind(null, false /* isRequired */);
+ColorPropType.isRequired = colorPropType.bind(null, true /* isRequired */);
+
+module.exports = ColorPropType;
diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js
index 825db546cafb..95414b339291 100644
--- a/Libraries/StyleSheet/LayoutPropTypes.js
+++ b/Libraries/StyleSheet/LayoutPropTypes.js
@@ -27,11 +27,7 @@ var ReactPropTypes = require('ReactPropTypes');
  */
 var LayoutPropTypes = {
   width: ReactPropTypes.number,
-  maxWidth: ReactPropTypes.number,
-  minWidth: ReactPropTypes.number,
   height: ReactPropTypes.number,
-  maxHeight: ReactPropTypes.number,
-  minHeight: ReactPropTypes.number,
   top: ReactPropTypes.number,
   left: ReactPropTypes.number,
   right: ReactPropTypes.number,
diff --git a/Libraries/StyleSheet/StyleSheet.js b/Libraries/StyleSheet/StyleSheet.js
index aa997b0a4884..5774d2663b85 100644
--- a/Libraries/StyleSheet/StyleSheet.js
+++ b/Libraries/StyleSheet/StyleSheet.js
@@ -11,9 +11,16 @@
  */
 'use strict';
 
+var PixelRatio = require('PixelRatio');
 var StyleSheetRegistry = require('StyleSheetRegistry');
 var StyleSheetValidation = require('StyleSheetValidation');
-var flattenStyle = require('flattenStyle');
+
+var flatten = require('flattenStyle');
+
+var hairlineWidth = PixelRatio.roundToNearestPixel(0.4);
+if (hairlineWidth === 0) {
+  hairlineWidth = 1 / PixelRatio.get();
+}
 
 /**
  * A StyleSheet is an abstraction similar to CSS StyleSheets
@@ -59,10 +66,32 @@ var flattenStyle = require('flattenStyle');
  *  - It also allows to send the style only once through the bridge. All
  * subsequent uses are going to refer an id (not implemented yet).
  */
-class StyleSheet {
-  static flatten: typeof flattenStyle;
+module.exports = {
+  /**
+   * This is defined as the width of a thin line on the platform. It can be
+   * used as the thickness of a border or division between two elements.
+   * Example:
+   * ```
+   *   {
+   *     borderBottomColor: '#bbb',
+   *     borderBottomWidth: StyleSheet.hairlineWidth
+   *   }
+   * ```
+   *
+   * This constant will always be a round number of pixels (so a line defined
+   * by it look crisp) and will try to match the standard width of a thin line
+   * on the underlying platform. However, you should not rely on it being a
+   * constant size, because on different platforms and screen densities its
+   * value may be calculated differently.
+   */
+  hairlineWidth,
+
+  flatten,
 
-  static create(obj: {[key: string]: any}): {[key: string]: number} {
+  /**
+   * Creates a StyleSheet style reference from the given object.
+   */
+  create(obj: {[key: string]: any}): {[key: string]: number} {
     var result = {};
     for (var key in obj) {
       StyleSheetValidation.validateStyle(key, obj);
@@ -70,9 +99,4 @@ class StyleSheet {
     }
     return result;
   }
-}
-
-/* TODO(brentvatne) docs are needed for this */
-StyleSheet.flatten = flattenStyle;
-
-module.exports = StyleSheet;
+};
diff --git a/Libraries/StyleSheet/StyleSheetValidation.js b/Libraries/StyleSheet/StyleSheetValidation.js
index f61decb3cc2c..2be4ca2f2496 100644
--- a/Libraries/StyleSheet/StyleSheetValidation.js
+++ b/Libraries/StyleSheet/StyleSheetValidation.js
@@ -16,7 +16,7 @@ var ReactPropTypeLocations = require('ReactPropTypeLocations');
 var TextStylePropTypes = require('TextStylePropTypes');
 var ViewStylePropTypes = require('ViewStylePropTypes');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 class StyleSheetValidation {
   static validateStyleProp(prop, style, caller) {
diff --git a/Libraries/StyleSheet/TransformPropTypes.js b/Libraries/StyleSheet/TransformPropTypes.js
index e91382caef9b..9988b8216ea2 100644
--- a/Libraries/StyleSheet/TransformPropTypes.js
+++ b/Libraries/StyleSheet/TransformPropTypes.js
@@ -12,6 +12,7 @@
 'use strict';
 
 var ReactPropTypes = require('ReactPropTypes');
+var deprecatedPropType = require('deprecatedPropType');
 
 var ArrayOfNumberPropType = ReactPropTypes.arrayOf(ReactPropTypes.number);
 
@@ -49,11 +50,11 @@ var TransformPropTypes = {
   transformMatrix: TransformMatrixPropType,
 
   /* Deprecated transform props used on Android only */
-  scaleX: ReactPropTypes.number,
-  scaleY: ReactPropTypes.number,
-  rotation: ReactPropTypes.number,
-  translateX: ReactPropTypes.number,
-  translateY: ReactPropTypes.number,
+  scaleX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
+  scaleY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
+  rotation: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
+  translateX: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
+  translateY: deprecatedPropType(ReactPropTypes.number, 'Use the transform prop instead.'),
 };
 
 module.exports = TransformPropTypes;
diff --git a/Libraries/StyleSheet/__tests__/normalizeColor-test.js b/Libraries/StyleSheet/__tests__/normalizeColor-test.js
new file mode 100644
index 000000000000..fd8d7d147460
--- /dev/null
+++ b/Libraries/StyleSheet/__tests__/normalizeColor-test.js
@@ -0,0 +1,131 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.dontMock('normalizeColor');
+
+var normalizeColor = require('normalizeColor');
+
+describe('normalizeColor', function() {
+  it('should accept only spec compliant colors', function() {
+    expect(normalizeColor('#abc')).not.toBe(null);
+    expect(normalizeColor('#abcd')).not.toBe(null);
+    expect(normalizeColor('#abcdef')).not.toBe(null);
+    expect(normalizeColor('#abcdef01')).not.toBe(null);
+    expect(normalizeColor('rgb(1,2,3)')).not.toBe(null);
+    expect(normalizeColor('rgb(1, 2, 3)')).not.toBe(null);
+    expect(normalizeColor('rgb(   1   , 2   , 3   )')).not.toBe(null);
+    expect(normalizeColor('rgb(-1, -2, -3)')).not.toBe(null);
+    expect(normalizeColor('rgba(0, 0, 0, 1)')).not.toBe(null);
+    expect(normalizeColor(0x01234567 + 0.5)).toBe(null);
+    expect(normalizeColor(-1)).toBe(null);
+    expect(normalizeColor(0xffffffff + 1)).toBe(null);
+  });
+
+  it('should temporarly accept floating point values for rgb', function() {
+    expect(normalizeColor('rgb(1.1, 2.1, 3.1)')).toBe(0x010203ff);
+    expect(normalizeColor('rgba(1.1, 2.1, 3.1, 1.0)')).toBe(0x010203ff);
+  });
+
+  it('should refuse non spec compliant colors', function() {
+    expect(normalizeColor('#00gg00')).toBe(null);
+    expect(normalizeColor('rgb(1, 2, 3,)')).toBe(null);
+    expect(normalizeColor('rgb(1, 2, 3')).toBe(null);
+
+    // Used to be accepted by normalizeColor
+    expect(normalizeColor('abc')).toBe(null);
+    expect(normalizeColor(' #abc ')).toBe(null);
+    expect(normalizeColor('##abc')).toBe(null);
+    expect(normalizeColor('rgb 255 0 0')).toBe(null);
+    expect(normalizeColor('RGBA(0, 1, 2)')).toBe(null);
+    expect(normalizeColor('rgb (0, 1, 2)')).toBe(null);
+    expect(normalizeColor('hsv(0, 1, 2)')).toBe(null);
+    expect(normalizeColor({r: 10, g: 10, b: 10})).toBe(null);
+    expect(normalizeColor('hsl(1%, 2, 3)')).toBe(null);
+    expect(normalizeColor('rgb(1%, 2%, 3%)')).toBe(null);
+  });
+
+  it('should handle hex6 properly', function() {
+    expect(normalizeColor('#000000')).toBe(0x000000ff);
+    expect(normalizeColor('#ffffff')).toBe(0xffffffff);
+    expect(normalizeColor('#ff00ff')).toBe(0xff00ffff);
+    expect(normalizeColor('#abcdef')).toBe(0xabcdefff);
+    expect(normalizeColor('#012345')).toBe(0x012345ff);
+  });
+
+  it('should handle hex3 properly', function() {
+    expect(normalizeColor('#000')).toBe(0x000000ff);
+    expect(normalizeColor('#fff')).toBe(0xffffffff);
+    expect(normalizeColor('#f0f')).toBe(0xff00ffff);
+  });
+
+  it('should handle hex8 properly', function() {
+    expect(normalizeColor('#00000000')).toBe(0x00000000);
+    expect(normalizeColor('#ffffffff')).toBe(0xffffffff);
+    expect(normalizeColor('#ffff00ff')).toBe(0xffff00ff);
+    expect(normalizeColor('#abcdef01')).toBe(0xabcdef01);
+    expect(normalizeColor('#01234567')).toBe(0x01234567);
+  });
+
+  it('should handle rgb properly', function() {
+    expect(normalizeColor('rgb(0, 0, 0)')).toBe(0x000000ff);
+    expect(normalizeColor('rgb(-1, -2, -3)')).toBe(0x000000ff);
+    expect(normalizeColor('rgb(0, 0, 255)')).toBe(0x0000ffff);
+    expect(normalizeColor('rgb(100, 15, 69)')).toBe(0x640f45ff);
+    expect(normalizeColor('rgb(255, 255, 255)')).toBe(0xffffffff);
+    expect(normalizeColor('rgb(256, 256, 256)')).toBe(0xffffffff);
+  });
+
+  it('should handle rgba properly', function() {
+    expect(normalizeColor('rgba(0, 0, 0, 0.0)')).toBe(0x00000000);
+    expect(normalizeColor('rgba(0, 0, 0, 0)')).toBe(0x00000000);
+    expect(normalizeColor('rgba(0, 0, 0, -0.5)')).toBe(0x00000000);
+    expect(normalizeColor('rgba(0, 0, 0, 1.0)')).toBe(0x000000ff);
+    expect(normalizeColor('rgba(0, 0, 0, 1)')).toBe(0x000000ff);
+    expect(normalizeColor('rgba(0, 0, 0, 1.5)')).toBe(0x000000ff);
+    expect(normalizeColor('rgba(100, 15, 69, 0.5)')).toBe(0x640f4580);
+  });
+
+  it('should handle hsl properly', function() {
+    expect(normalizeColor('hsl(0, 0%, 0%)')).toBe(0x000000ff);
+    expect(normalizeColor('hsl(360, 100%, 100%)')).toBe(0xffffffff);
+    expect(normalizeColor('hsl(180, 50%, 50%)')).toBe(0x40bfbfff);
+    expect(normalizeColor('hsl(540, 50%, 50%)')).toBe(0x40bfbfff);
+    expect(normalizeColor('hsl(70, 25%, 75%)')).toBe(0xcacfafff);
+    expect(normalizeColor('hsl(70, 100%, 75%)')).toBe(0xeaff80ff);
+    expect(normalizeColor('hsl(70, 110%, 75%)')).toBe(0xeaff80ff);
+    expect(normalizeColor('hsl(70, 0%, 75%)')).toBe(0xbfbfbfff);
+    expect(normalizeColor('hsl(70, -10%, 75%)')).toBe(0xbfbfbfff);
+  });
+
+  it('should handle hsla properly', function() {
+    expect(normalizeColor('hsla(0, 0%, 0%, 0)')).toBe(0x00000000);
+    expect(normalizeColor('hsla(360, 100%, 100%, 1)')).toBe(0xffffffff);
+    expect(normalizeColor('hsla(360, 100%, 100%, 0)')).toBe(0xffffff00);
+    expect(normalizeColor('hsla(180, 50%, 50%, 0.2)')).toBe(0x40bfbf33);
+  });
+
+  it('should handle named colors properly', function() {
+    expect(normalizeColor('red')).toBe(0xff0000ff);
+    expect(normalizeColor('transparent')).toBe(0x00000000);
+    expect(normalizeColor('peachpuff')).toBe(0xffdab9ff);
+  });
+
+  it('should handle number colors properly', function() {
+    expect(normalizeColor(0x00000000)).toBe(0x00000000);
+    expect(normalizeColor(0xff0000ff)).toBe(0xff0000ff);
+    expect(normalizeColor(0xffffffff)).toBe(0xffffffff);
+    expect(normalizeColor(0x01234567)).toBe(0x01234567);
+  });
+
+  it('should return the same color when it\'s already normalized', function() {
+    const normalizedColor = normalizeColor('red') || 0;
+    expect(normalizeColor(normalizedColor)).toBe(normalizedColor);
+  });
+});
diff --git a/Libraries/StyleSheet/__tests__/processColor-test.js b/Libraries/StyleSheet/__tests__/processColor-test.js
index 9416353b79b6..aaf45d5e23e3 100644
--- a/Libraries/StyleSheet/__tests__/processColor-test.js
+++ b/Libraries/StyleSheet/__tests__/processColor-test.js
@@ -49,12 +49,6 @@ describe('processColor', () => {
       expect(colorFromString).toEqual(expectedInt);
     });
 
-    it('should convert rgb x, y, z', () => {
-      var colorFromString = processColor('rgb 10, 20, 30');
-      var expectedInt = 0xFF0A141E;
-      expect(colorFromString).toEqual(expectedInt);
-    });
-
   });
 
   describe('RGBA strings', () => {
@@ -65,12 +59,6 @@ describe('processColor', () => {
       expect(colorFromString).toEqual(expectedInt);
     });
 
-    it('should convert rgba x, y, z, a', () => {
-      var colorFromString = processColor('rgba 10, 20, 30, 0.4');
-      var expectedInt = 0x660A141E;
-      expect(colorFromString).toEqual(expectedInt);
-    });
-
   });
 
   describe('HSL strings', () => {
@@ -81,15 +69,9 @@ describe('processColor', () => {
       expect(colorFromString).toEqual(expectedInt);
     });
 
-    it('should convert hsl x, y%, z%', () => {
-      var colorFromString = processColor('hsl 318, 69%, 55%');
-      var expectedInt = 0xFFDB3DAC;
-      expect(colorFromString).toEqual(expectedInt);
-    });
-
   });
 
-  describe('HSL strings', () => {
+  describe('HSLA strings', () => {
 
     it('should convert hsla(x, y%, z%, a)', () => {
       var colorFromString = processColor('hsla(318, 69%, 55%, 0.25)');
@@ -97,12 +79,6 @@ describe('processColor', () => {
       expect(colorFromString).toEqual(expectedInt);
     });
 
-    it('should convert hsla x, y%, z%, a', () => {
-      var colorFromString = processColor('hsla 318, 69%, 55%, 0.25');
-      var expectedInt = 0x40DB3DAC;
-      expect(colorFromString).toEqual(expectedInt);
-    });
-
   });
 
   describe('hex strings', () => {
diff --git a/Libraries/StyleSheet/__tests__/setNormalizedColorAlpha-test.js b/Libraries/StyleSheet/__tests__/setNormalizedColorAlpha-test.js
new file mode 100644
index 000000000000..598652dbba7b
--- /dev/null
+++ b/Libraries/StyleSheet/__tests__/setNormalizedColorAlpha-test.js
@@ -0,0 +1,36 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.dontMock('setNormalizedColorAlpha');
+jest.dontMock('normalizeColor');
+
+var setNormalizedColorAlpha = require('setNormalizedColorAlpha');
+var normalizeColor = require('normalizeColor');
+
+describe('setNormalizedColorAlpha', function() {
+  it('should adjust the alpha of the color passed in', function() {
+    expect(setNormalizedColorAlpha(0xffffffff, 0.4)).toBe(0xffffff66);
+    expect(setNormalizedColorAlpha(0x204080ff, 0.6)).toBe(0x20408099);
+  });
+
+  it('should clamp invalid input', function() {
+    expect(setNormalizedColorAlpha(0xffffffff, 1.5)).toBe(0xffffffff);
+    expect(setNormalizedColorAlpha(0xffffffff, -1)).toBe(0xffffff00);
+  });
+
+  it('should ignore the color\'s original alpha', function() {
+    expect(setNormalizedColorAlpha(0x204080aa, 0.8)).toBe(0x204080cc);
+  });
+
+  it('should return the original color when alpha is unchanged', function() {
+    var originalColor = normalizeColor('blue');
+    expect(setNormalizedColorAlpha(originalColor, 1)).toBe(originalColor);
+  });
+});
diff --git a/Libraries/StyleSheet/flattenStyle.js b/Libraries/StyleSheet/flattenStyle.js
index 621c614ffd9f..a3af4f46e63d 100644
--- a/Libraries/StyleSheet/flattenStyle.js
+++ b/Libraries/StyleSheet/flattenStyle.js
@@ -12,7 +12,7 @@
 'use strict';
 
 var StyleSheetRegistry = require('StyleSheetRegistry');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 import type { StyleObj } from 'StyleSheetTypes';
 
diff --git a/Libraries/StyleSheet/normalizeColor.js b/Libraries/StyleSheet/normalizeColor.js
new file mode 100755
index 000000000000..f32a09631682
--- /dev/null
+++ b/Libraries/StyleSheet/normalizeColor.js
@@ -0,0 +1,348 @@
+/**
+ * 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 normalizeColor
+ * @flow
+ */
+/* eslint no-bitwise: 0 */
+'use strict';
+
+function normalizeColor(color: string | number): ?number {
+  var match;
+
+  if (typeof color === 'number') {
+    if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) {
+      return color;
+    }
+    return null;
+  }
+
+  // Ordered based on occurrences on Facebook codebase
+  if ((match = matchers.hex6.exec(color))) {
+    return parseInt(match[1] + 'ff', 16) >>> 0;
+  }
+
+  if (names.hasOwnProperty(color)) {
+    return names[color];
+  }
+
+  if ((match = matchers.rgb.exec(color))) {
+    return (
+      parse255(match[1]) << 24 | // r
+      parse255(match[2]) << 16 | // g
+      parse255(match[3]) << 8 | // b
+      0x000000ff // a
+    ) >>> 0;
+  }
+
+  if ((match = matchers.rgba.exec(color))) {
+    return (
+      parse255(match[1]) << 24 | // r
+      parse255(match[2]) << 16 | // g
+      parse255(match[3]) << 8 | // b
+      parse1(match[4]) // a
+    ) >>> 0;
+  }
+
+  if ((match = matchers.hex3.exec(color))) {
+    return parseInt(
+      match[1] + match[1] + // r
+      match[2] + match[2] + // g
+      match[3] + match[3] + // b
+      'ff', // a
+      16
+    ) >>> 0;
+  }
+
+  // https://drafts.csswg.org/css-color-4/#hex-notation
+  if ((match = matchers.hex8.exec(color))) {
+    return parseInt(match[1], 16) >>> 0;
+  }
+
+  if ((match = matchers.hex4.exec(color))) {
+    return parseInt(
+      match[1] + match[1] + // r
+      match[2] + match[2] + // g
+      match[3] + match[3] + // b
+      match[4] + match[4], // a
+      16
+    ) >>> 0;
+  }
+
+  if ((match = matchers.hsl.exec(color))) {
+    return (
+      hslToRgb(
+        parse360(match[1]), // h
+        parsePercentage(match[2]), // s
+        parsePercentage(match[3]) // l
+      ) |
+      0x000000ff // a
+    ) >>> 0;
+  }
+
+  if ((match = matchers.hsla.exec(color))) {
+    return (
+      hslToRgb(
+        parse360(match[1]), // h
+        parsePercentage(match[2]), // s
+        parsePercentage(match[3]) // l
+      ) |
+      parse1(match[4]) // a
+    ) >>> 0;
+  }
+
+  return null;
+}
+
+function hue2rgb(p: number, q: number, t: number): number {
+  if (t < 0) {
+    t += 1;
+  }
+  if (t > 1) {
+    t -= 1;
+  }
+  if (t < 1 / 6) {
+    return p + (q - p) * 6 * t;
+  }
+  if (t < 1 / 2) {
+    return q;
+  }
+  if (t < 2 / 3) {
+    return p + (q - p) * (2 / 3 - t) * 6;
+  }
+  return p;
+}
+
+function hslToRgb(h: number, s: number, l: number): number {
+  var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+  var p = 2 * l - q;
+  var r = hue2rgb(p, q, h + 1 / 3);
+  var g = hue2rgb(p, q, h);
+  var b = hue2rgb(p, q, h - 1 / 3);
+
+  return (
+    Math.round(r * 255) << 24 |
+    Math.round(g * 255) << 16 |
+    Math.round(b * 255) << 8
+  );
+}
+
+// var INTEGER = '[-+]?\\d+';
+var NUMBER = '[-+]?\\d*\\.?\\d+';
+var PERCENTAGE = NUMBER + '%';
+
+function call(...args) {
+  return '\\(\\s*(' + args.join(')\\s*,\\s*(') + ')\\s*\\)';
+}
+
+var matchers = {
+  rgb: new RegExp('rgb' + call(NUMBER, NUMBER, NUMBER)),
+  rgba: new RegExp('rgba' + call(NUMBER, NUMBER, NUMBER, NUMBER)),
+  hsl: new RegExp('hsl' + call(NUMBER, PERCENTAGE, PERCENTAGE)),
+  hsla: new RegExp('hsla' + call(NUMBER, PERCENTAGE, PERCENTAGE, NUMBER)),
+  hex3: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+  hex4: /^#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+  hex6: /^#([0-9a-fA-F]{6})$/,
+  hex8: /^#([0-9a-fA-F]{8})$/,
+};
+
+function parse255(str: string): number {
+  var int = parseInt(str, 10);
+  if (int < 0) {
+    return 0;
+  }
+  if (int > 255) {
+    return 255;
+  }
+  return int;
+}
+
+function parse360(str: string): number {
+  var int = parseFloat(str);
+  return (((int % 360) + 360) % 360) / 360;
+}
+
+function parse1(str: string): number {
+  var num = parseFloat(str);
+  if (num < 0) {
+    return 0;
+  }
+  if (num > 1) {
+    return 255;
+  }
+  return Math.round(num * 255);
+}
+
+function parsePercentage(str: string): number {
+  // parseFloat conveniently ignores the final %
+  var int = parseFloat(str, 10);
+  if (int < 0) {
+    return 0;
+  }
+  if (int > 100) {
+    return 1;
+  }
+  return int / 100;
+}
+
+var names = {
+  transparent: 0x00000000,
+
+  // http://www.w3.org/TR/css3-color/#svg-color
+  aliceblue: 0xf0f8ffff,
+  antiquewhite: 0xfaebd7ff,
+  aqua: 0x00ffffff,
+  aquamarine: 0x7fffd4ff,
+  azure: 0xf0ffffff,
+  beige: 0xf5f5dcff,
+  bisque: 0xffe4c4ff,
+  black: 0x000000ff,
+  blanchedalmond: 0xffebcdff,
+  blue: 0x0000ffff,
+  blueviolet: 0x8a2be2ff,
+  brown: 0xa52a2aff,
+  burlywood: 0xdeb887ff,
+  burntsienna: 0xea7e5dff,
+  cadetblue: 0x5f9ea0ff,
+  chartreuse: 0x7fff00ff,
+  chocolate: 0xd2691eff,
+  coral: 0xff7f50ff,
+  cornflowerblue: 0x6495edff,
+  cornsilk: 0xfff8dcff,
+  crimson: 0xdc143cff,
+  cyan: 0x00ffffff,
+  darkblue: 0x00008bff,
+  darkcyan: 0x008b8bff,
+  darkgoldenrod: 0xb8860bff,
+  darkgray: 0xa9a9a9ff,
+  darkgreen: 0x006400ff,
+  darkgrey: 0xa9a9a9ff,
+  darkkhaki: 0xbdb76bff,
+  darkmagenta: 0x8b008bff,
+  darkolivegreen: 0x556b2fff,
+  darkorange: 0xff8c00ff,
+  darkorchid: 0x9932ccff,
+  darkred: 0x8b0000ff,
+  darksalmon: 0xe9967aff,
+  darkseagreen: 0x8fbc8fff,
+  darkslateblue: 0x483d8bff,
+  darkslategray: 0x2f4f4fff,
+  darkslategrey: 0x2f4f4fff,
+  darkturquoise: 0x00ced1ff,
+  darkviolet: 0x9400d3ff,
+  deeppink: 0xff1493ff,
+  deepskyblue: 0x00bfffff,
+  dimgray: 0x696969ff,
+  dimgrey: 0x696969ff,
+  dodgerblue: 0x1e90ffff,
+  firebrick: 0xb22222ff,
+  floralwhite: 0xfffaf0ff,
+  forestgreen: 0x228b22ff,
+  fuchsia: 0xff00ffff,
+  gainsboro: 0xdcdcdcff,
+  ghostwhite: 0xf8f8ffff,
+  gold: 0xffd700ff,
+  goldenrod: 0xdaa520ff,
+  gray: 0x808080ff,
+  green: 0x008000ff,
+  greenyellow: 0xadff2fff,
+  grey: 0x808080ff,
+  honeydew: 0xf0fff0ff,
+  hotpink: 0xff69b4ff,
+  indianred: 0xcd5c5cff,
+  indigo: 0x4b0082ff,
+  ivory: 0xfffff0ff,
+  khaki: 0xf0e68cff,
+  lavender: 0xe6e6faff,
+  lavenderblush: 0xfff0f5ff,
+  lawngreen: 0x7cfc00ff,
+  lemonchiffon: 0xfffacdff,
+  lightblue: 0xadd8e6ff,
+  lightcoral: 0xf08080ff,
+  lightcyan: 0xe0ffffff,
+  lightgoldenrodyellow: 0xfafad2ff,
+  lightgray: 0xd3d3d3ff,
+  lightgreen: 0x90ee90ff,
+  lightgrey: 0xd3d3d3ff,
+  lightpink: 0xffb6c1ff,
+  lightsalmon: 0xffa07aff,
+  lightseagreen: 0x20b2aaff,
+  lightskyblue: 0x87cefaff,
+  lightslategray: 0x778899ff,
+  lightslategrey: 0x778899ff,
+  lightsteelblue: 0xb0c4deff,
+  lightyellow: 0xffffe0ff,
+  lime: 0x00ff00ff,
+  limegreen: 0x32cd32ff,
+  linen: 0xfaf0e6ff,
+  magenta: 0xff00ffff,
+  maroon: 0x800000ff,
+  mediumaquamarine: 0x66cdaaff,
+  mediumblue: 0x0000cdff,
+  mediumorchid: 0xba55d3ff,
+  mediumpurple: 0x9370dbff,
+  mediumseagreen: 0x3cb371ff,
+  mediumslateblue: 0x7b68eeff,
+  mediumspringgreen: 0x00fa9aff,
+  mediumturquoise: 0x48d1ccff,
+  mediumvioletred: 0xc71585ff,
+  midnightblue: 0x191970ff,
+  mintcream: 0xf5fffaff,
+  mistyrose: 0xffe4e1ff,
+  moccasin: 0xffe4b5ff,
+  navajowhite: 0xffdeadff,
+  navy: 0x000080ff,
+  oldlace: 0xfdf5e6ff,
+  olive: 0x808000ff,
+  olivedrab: 0x6b8e23ff,
+  orange: 0xffa500ff,
+  orangered: 0xff4500ff,
+  orchid: 0xda70d6ff,
+  palegoldenrod: 0xeee8aaff,
+  palegreen: 0x98fb98ff,
+  paleturquoise: 0xafeeeeff,
+  palevioletred: 0xdb7093ff,
+  papayawhip: 0xffefd5ff,
+  peachpuff: 0xffdab9ff,
+  peru: 0xcd853fff,
+  pink: 0xffc0cbff,
+  plum: 0xdda0ddff,
+  powderblue: 0xb0e0e6ff,
+  purple: 0x800080ff,
+  rebeccapurple: 0x663399ff,
+  red: 0xff0000ff,
+  rosybrown: 0xbc8f8fff,
+  royalblue: 0x4169e1ff,
+  saddlebrown: 0x8b4513ff,
+  salmon: 0xfa8072ff,
+  sandybrown: 0xf4a460ff,
+  seagreen: 0x2e8b57ff,
+  seashell: 0xfff5eeff,
+  sienna: 0xa0522dff,
+  silver: 0xc0c0c0ff,
+  skyblue: 0x87ceebff,
+  slateblue: 0x6a5acdff,
+  slategray: 0x708090ff,
+  slategrey: 0x708090ff,
+  snow: 0xfffafaff,
+  springgreen: 0x00ff7fff,
+  steelblue: 0x4682b4ff,
+  tan: 0xd2b48cff,
+  teal: 0x008080ff,
+  thistle: 0xd8bfd8ff,
+  tomato: 0xff6347ff,
+  turquoise: 0x40e0d0ff,
+  violet: 0xee82eeff,
+  wheat: 0xf5deb3ff,
+  white: 0xffffffff,
+  whitesmoke: 0xf5f5f5ff,
+  yellow: 0xffff00ff,
+  yellowgreen: 0x9acd32ff,
+};
+
+module.exports = normalizeColor;
diff --git a/Libraries/StyleSheet/processColor.js b/Libraries/StyleSheet/processColor.js
index e259113e3e80..4cb9b2e5e60d 100644
--- a/Libraries/StyleSheet/processColor.js
+++ b/Libraries/StyleSheet/processColor.js
@@ -10,33 +10,32 @@
  */
 'use strict';
 
-var tinycolor = require('tinycolor');
 var Platform = require('Platform');
 
+var normalizeColor = require('normalizeColor');
+
 /* eslint no-bitwise: 0 */
 function processColor(color) {
-  if (!color || typeof color === 'number') {
+  if (color === undefined || color === null) {
     return color;
-  } else if (color instanceof Array) {
-    return color.map(processColor);
-  } else {
-    var color = tinycolor(color);
-    if (color.isValid()) {
-      var rgb = color.toRgb();
-      // All bitwise operations happen on 32-bit numbers, so we shift the 1 first
-      // then multiply it with the actual value.
-      var colorInt = Math.round(rgb.a * 255) * (1 << 24) + rgb.r * (1 << 16) + rgb.g * (1 << 8) + rgb.b;
-      if (Platform.OS === 'android') {
-        // Android use 32 bit *signed* integer to represent the color
-        // We utilize the fact that bitwise operations in JS also operates on
-        // signed 32 bit integers, so that we can use those to convert from
-        // *unsiged* to *signed* 32bit int that way.
-        colorInt = colorInt | 0x0;
-      }
-      return colorInt;
-    }
-    return 0;
   }
+
+  var int32Color = normalizeColor(color);
+  if (int32Color === null) {
+    return undefined;
+  }
+
+  // Converts 0xrrggbbaa into 0xaarrggbb
+  int32Color = (int32Color << 24 | int32Color >>> 8) >>> 0;
+
+  if (Platform.OS === 'android') {
+    // Android use 32 bit *signed* integer to represent the color
+    // We utilize the fact that bitwise operations in JS also operates on
+    // signed 32 bit integers, so that we can use those to convert from
+    // *unsigned* to *signed* 32bit int that way.
+    int32Color = int32Color | 0x0;
+  }
+  return int32Color;
 }
 
 module.exports = processColor;
diff --git a/Libraries/StyleSheet/processTransform.js b/Libraries/StyleSheet/processTransform.js
index 4666a3055291..6978efdcfbe6 100644
--- a/Libraries/StyleSheet/processTransform.js
+++ b/Libraries/StyleSheet/processTransform.js
@@ -14,7 +14,7 @@
 var MatrixMath = require('MatrixMath');
 var Platform = require('Platform');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var stringifySafe = require('stringifySafe');
 
 /**
@@ -165,6 +165,20 @@ function _validateTransform(key, value, transformation) {
         stringifySafe(transformation),
       );
       break;
+    case 'perspective':
+      invariant(
+        typeof value === 'number',
+        'Transform with key of "%s" must be a number: %s',
+        key,
+        stringifySafe(transformation),
+      );
+      invariant(
+        value !== 0,
+        'Transform with key of "%s" cannot be zero: %s',
+        key,
+        stringifySafe(transformation),
+      );
+      break;
     default:
       invariant(
         typeof value === 'number',
diff --git a/Libraries/StyleSheet/setNormalizedColorAlpha.js b/Libraries/StyleSheet/setNormalizedColorAlpha.js
new file mode 100644
index 000000000000..cd943309922c
--- /dev/null
+++ b/Libraries/StyleSheet/setNormalizedColorAlpha.js
@@ -0,0 +1,31 @@
+/**
+ * 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 setNormalizedColorAlpha
+ * @flow
+ */
+/* eslint no-bitwise: 0 */
+'use strict';
+
+/**
+ * number should be a color processed by `normalizeColor`
+ * alpha should be number between 0 and 1
+ */
+function setNormalizedColorAlpha(input: number, alpha: number): number {
+  if (alpha < 0) {
+    alpha = 0;
+  } else if (alpha > 1) {
+    alpha = 1;
+  }
+
+  alpha = Math.round(alpha * 255);
+  // magic bitshift guarantees we return an unsigned int
+  return ((input & 0xffffff00) | alpha) >>> 0;
+}
+
+module.exports = setNormalizedColorAlpha;
diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h
index 1ded47c93f4f..5d793916bb1d 100644
--- a/Libraries/Text/RCTShadowText.h
+++ b/Libraries/Text/RCTShadowText.h
@@ -33,6 +33,9 @@ extern NSString *const RCTReactTagAttributeName;
 @property (nonatomic, assign) CGFloat fontSizeMultiplier;
 @property (nonatomic, assign) BOOL allowFontScaling;
 @property (nonatomic, assign) CGFloat opacity;
+@property (nonatomic, assign) CGSize textShadowOffset;
+@property (nonatomic, assign) CGFloat textShadowRadius;
+@property (nonatomic, strong) UIColor *textShadowColor;
 
 - (void)recomputeText;
 
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index a8087b1da476..a3286a88b332 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -30,7 +30,7 @@ @implementation RCTShadowText
   CGFloat _effectiveLetterSpacing;
 }
 
-static css_dim_t RCTMeasure(void *context, float width)
+static css_dim_t RCTMeasure(void *context, float width, float height)
 {
   RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
   NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
@@ -213,8 +213,6 @@ - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily
         NSTextAttachment *imageAttachment = [NSTextAttachment new];
         imageAttachment.image = image;
         [attributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:imageAttachment]];
-      } else {
-        //TODO: add placeholder image?
       }
     } else {
       RCTLogError(@"<Text> can't have any children except <Text>, <Image> or raw strings");
@@ -305,22 +303,31 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib
   }
 
   // Text decoration
-  if(_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
-     _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
+  if (_textDecorationLine == RCTTextDecorationLineTypeUnderline ||
+      _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough) {
     [self _addAttribute:NSUnderlineStyleAttributeName withValue:@(_textDecorationStyle)
      toAttributedString:attributedString];
   }
-  if(_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
-     _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
+  if (_textDecorationLine == RCTTextDecorationLineTypeStrikethrough ||
+      _textDecorationLine == RCTTextDecorationLineTypeUnderlineStrikethrough){
     [self _addAttribute:NSStrikethroughStyleAttributeName withValue:@(_textDecorationStyle)
      toAttributedString:attributedString];
   }
-  if(_textDecorationColor) {
+  if (_textDecorationColor) {
     [self _addAttribute:NSStrikethroughColorAttributeName withValue:_textDecorationColor
      toAttributedString:attributedString];
     [self _addAttribute:NSUnderlineColorAttributeName withValue:_textDecorationColor
      toAttributedString:attributedString];
   }
+
+  // Text shadow
+  if (!CGSizeEqualToSize(_textShadowOffset, CGSizeZero)) {
+    NSShadow *shadow = [NSShadow new];
+    shadow.shadowOffset = _textShadowOffset;
+    shadow.shadowBlurRadius = _textShadowRadius;
+    shadow.shadowColor = _textShadowColor;
+    [self _addAttribute:NSShadowAttributeName withValue:shadow toAttributedString:attributedString];
+  }
 }
 
 - (void)fillCSSNode:(css_node_t *)node
@@ -364,13 +371,15 @@ - (void)set##setProp:(type)value;              \
 RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
 RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
 RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
-RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
 RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
 RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *);
 RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType);
 RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle);
 RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
 RCT_TEXT_PROPERTY(Opacity, _opacity, CGFloat)
+RCT_TEXT_PROPERTY(TextShadowOffset, _textShadowOffset, CGSize);
+RCT_TEXT_PROPERTY(TextShadowRadius, _textShadowRadius, CGFloat);
+RCT_TEXT_PROPERTY(TextShadowColor, _textShadowColor, UIColor *);
 
 - (void)setAllowFontScaling:(BOOL)allowFontScaling
 {
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index c45287917db9..a4f110ed63e8 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -52,6 +52,11 @@ - (void)reactSetFrame:(CGRect)frame
   }];
 }
 
+- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
+{
+  self.backgroundColor = inheritedBackgroundColor;
+}
+
 - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
 {
   [_reactSubviews insertObject:subview atIndex:atIndex];
diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m
index 222d280e1576..24b077baff10 100644
--- a/Libraries/Text/RCTTextField.m
+++ b/Libraries/Text/RCTTextField.m
@@ -36,6 +36,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
     [self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
     [self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
     _reactSubviews = [NSMutableArray new];
+    _blurOnSubmit = YES;
   }
   return self;
 }
@@ -57,7 +58,7 @@ - (void)sendKeyValueForString:(NSString *)string
                                eventCount:_nativeEventCount];
 }
 
-// This method is overriden for `onKeyPress`. The manager
+// This method is overridden for `onKeyPress`. The manager
 // will not send a keyPress for text that was pasted.
 - (void)paste:(id)sender
 {
@@ -70,8 +71,18 @@ - (void)setText:(NSString *)text
   NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
   if (eventLag == 0 && ![text isEqualToString:self.text]) {
     UITextRange *selection = self.selectedTextRange;
+    NSInteger oldTextLength = self.text.length;
+
     super.text = text;
-    self.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
+
+    if (selection.empty) {
+      // maintain cursor position relative to the end of the old text
+      NSInteger offsetStart = [self offsetFromPosition:self.beginningOfDocument toPosition:selection.start];
+      NSInteger offsetFromEnd = oldTextLength - offsetStart;
+      NSInteger newOffset = text.length - offsetFromEnd;
+      UITextPosition *position = [self positionFromPosition:self.beginningOfDocument offset:newOffset];
+      self.selectedTextRange = [self textRangeFromPosition:position toPosition:position];
+    }
   } else if (eventLag > RCTTextUpdateLagWarningThreshold) {
     RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
   }
@@ -239,12 +250,19 @@ - (void)sendSelectionEvent
   }
 }
 
-- (BOOL)becomeFirstResponder
+- (BOOL)canBecomeFirstResponder
+{
+  return _jsRequestingFirstResponder;
+}
+
+- (void)reactWillMakeFirstResponder
 {
   _jsRequestingFirstResponder = YES;
-  BOOL result = [super becomeFirstResponder];
+}
+
+- (void)reactDidMakeFirstResponder
+{
   _jsRequestingFirstResponder = NO;
-  return result;
 }
 
 - (BOOL)resignFirstResponder
@@ -261,9 +279,4 @@ - (BOOL)resignFirstResponder
   return result;
 }
 
-- (BOOL)canBecomeFirstResponder
-{
-  return _jsRequestingFirstResponder;
-}
-
 @end
diff --git a/Libraries/Text/RCTTextFieldManager.m b/Libraries/Text/RCTTextFieldManager.m
index f2ccf07f4616..652639a0409f 100644
--- a/Libraries/Text/RCTTextFieldManager.m
+++ b/Libraries/Text/RCTTextFieldManager.m
@@ -94,6 +94,7 @@ - (BOOL)textFieldShouldEndEditing:(RCTTextField *)textField
 RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
 RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
 RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment)
+RCT_REMAP_VIEW_PROPERTY(selectionColor, tintColor, UIColor)
 RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
 {
   view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index 5496b3b80521..2a7e4d98b472 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -51,7 +51,6 @@ - (RCTShadowView *)shadowView
 RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
 RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
 RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
-RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
 RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
 RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle)
 RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
@@ -59,6 +58,9 @@ - (RCTShadowView *)shadowView
 RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
 RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
 RCT_EXPORT_SHADOW_PROPERTY(opacity, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(textShadowOffset, CGSize)
+RCT_EXPORT_SHADOW_PROPERTY(textShadowRadius, CGFloat)
+RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
 
 - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
 {
diff --git a/Libraries/Text/RCTTextView.h b/Libraries/Text/RCTTextView.h
index a6d8bca08917..5279eafb6731 100644
--- a/Libraries/Text/RCTTextView.h
+++ b/Libraries/Text/RCTTextView.h
@@ -17,12 +17,12 @@
 @interface RCTTextView : RCTView <UITextViewDelegate>
 
 @property (nonatomic, assign) BOOL autoCorrect;
+@property (nonatomic, assign) BOOL blurOnSubmit;
 @property (nonatomic, assign) BOOL clearTextOnFocus;
 @property (nonatomic, assign) BOOL selectTextOnFocus;
 @property (nonatomic, assign) UIEdgeInsets contentInset;
 @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
 @property (nonatomic, copy) NSString *text;
-@property (nonatomic, strong) UIColor *textColor;
 @property (nonatomic, strong) UIColor *placeholderTextColor;
 @property (nonatomic, strong) UIFont *font;
 @property (nonatomic, assign) NSInteger mostRecentEventCount;
diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m
index 2af31f39aa65..b9c6a74b22d4 100644
--- a/Libraries/Text/RCTTextView.m
+++ b/Libraries/Text/RCTTextView.m
@@ -22,6 +22,9 @@ @interface RCTUITextView : UITextView
 @end
 
 @implementation RCTUITextView
+{
+  BOOL _jsRequestingFirstResponder;
+}
 
 - (void)paste:(id)sender
 {
@@ -29,12 +32,26 @@ - (void)paste:(id)sender
   [super paste:sender];
 }
 
+- (void)reactWillMakeFirstResponder
+{
+  _jsRequestingFirstResponder = YES;
+}
+
+- (BOOL)canBecomeFirstResponder
+{
+  return _jsRequestingFirstResponder;
+}
+
+- (void)reactDidMakeFirstResponder
+{
+  _jsRequestingFirstResponder = NO;
+}
+
 @end
 
 @implementation RCTTextView
 {
   RCTEventDispatcher *_eventDispatcher;
-  BOOL _jsRequestingFirstResponder;
   NSString *_placeholder;
   UITextView *_placeholderView;
   UITextView *_textView;
@@ -44,6 +61,9 @@ @implementation RCTTextView
   NSMutableArray<UIView *> *_subviews;
   BOOL _blockTextShouldChange;
   UITextRange *_previousSelectionRange;
+  NSUInteger _previousTextLength;
+  CGFloat _previousContentHeight;
+  UIScrollView *_scrollView;
 }
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@@ -54,16 +74,22 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
     _contentInset = UIEdgeInsetsZero;
     _eventDispatcher = eventDispatcher;
     _placeholderTextColor = [self defaultPlaceholderTextColor];
+    _blurOnSubmit = NO;
 
-    _textView = [[RCTUITextView alloc] initWithFrame:self.bounds];
+    _textView = [[RCTUITextView alloc] initWithFrame:CGRectZero];
     _textView.backgroundColor = [UIColor clearColor];
     _textView.scrollsToTop = NO;
+    _textView.scrollEnabled = NO;
     _textView.delegate = self;
 
+    _scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
+    _scrollView.scrollsToTop = NO;
+    [_scrollView addSubview:_textView];
+
     _previousSelectionRange = _textView.selectedTextRange;
 
     _subviews = [NSMutableArray new];
-    [self addSubview:_textView];
+    [self addSubview:_scrollView];
   }
   return self;
 }
@@ -139,16 +165,22 @@ - (void)performPendingTextUpdate
   // we temporarily block all textShouldChange events so they are not applied.
   _blockTextShouldChange = YES;
 
-  // We compute the new selectedRange manually to make sure the cursor is at the
-  // end of the newly inserted/deleted text after update.
-  NSRange range = _textView.selectedRange;
-  CGPoint contentOffset = _textView.contentOffset;
+  UITextRange *selection = _textView.selectedTextRange;
+  NSInteger oldTextLength = _textView.attributedText.length;
 
   _textView.attributedText = _pendingAttributedText;
   _pendingAttributedText = nil;
-  _textView.selectedRange = range;
+
+  if (selection.empty) {
+    // maintain cursor position relative to the end of the old text
+    NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
+    NSInteger offsetFromEnd = oldTextLength - start;
+    NSInteger newOffset = _textView.attributedText.length - offsetFromEnd;
+    UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
+    _textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
+  }
+
   [_textView layoutIfNeeded];
-  _textView.contentOffset = contentOffset;
 
   [self _setPlaceholderVisibility];
 
@@ -174,11 +206,21 @@ - (void)updateFrames
   CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset);
   _textView.frame = frame;
   _placeholderView.frame = frame;
+  _scrollView.frame = frame;
+  [self updateContentSize];
 
   _textView.textContainerInset = adjustedTextContainerInset;
   _placeholderView.textContainerInset = adjustedTextContainerInset;
 }
 
+- (void)updateContentSize
+{
+  CGSize size = (CGSize){_scrollView.frame.size.width, INFINITY};
+  size.height = [_textView sizeThatFits:size].height;
+  _scrollView.contentSize = size;
+  _textView.frame = (CGRect){CGPointZero, size};
+}
+
 - (void)updatePlaceholder
 {
   [_placeholderView removeFromSuperview];
@@ -186,6 +228,8 @@ - (void)updatePlaceholder
 
   if (_placeholder) {
     _placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
+    _placeholderView.editable = NO;
+    _placeholderView.userInteractionEnabled = NO;
     _placeholderView.backgroundColor = [UIColor clearColor];
     _placeholderView.scrollEnabled = false;
     _placeholderView.scrollsToTop = NO;
@@ -211,16 +255,6 @@ - (void)setFont:(UIFont *)font
   [self updatePlaceholder];
 }
 
-- (UIColor *)textColor
-{
-  return _textView.textColor;
-}
-
-- (void)setTextColor:(UIColor *)textColor
-{
-  _textView.textColor = textColor;
-}
-
 - (void)setPlaceholder:(NSString *)placeholder
 {
   _placeholder = placeholder;
@@ -257,11 +291,35 @@ - (BOOL)textView:(RCTUITextView *)textView shouldChangeTextInRange:(NSRange)rang
   if (textView.textWasPasted) {
     textView.textWasPasted = NO;
   } else {
+    
     [_eventDispatcher sendTextEventWithType:RCTTextEventTypeKeyPress
                                    reactTag:self.reactTag
                                        text:nil
                                         key:text
                                  eventCount:_nativeEventCount];
+
+    if (_blurOnSubmit && [text isEqualToString:@"\n"]) {
+
+      // TODO: the purpose of blurOnSubmit on RCTextField is to decide if the
+      // field should lose focus when return is pressed or not. We're cheating a
+      // bit here by using it on RCTextView to decide if return character should
+      // submit the form, or be entered into the field.
+      //
+      // The reason this is cheating is because there's no way to specify that
+      // you want the return key to be swallowed *and* have the field retain
+      // focus (which was what blurOnSubmit was originally for). For the case
+      // where _blurOnSubmit = YES, this is still the correct and expected
+      // behavior though, so we'll leave the don't-blur-or-add-newline problem
+      // to be solved another day.
+
+      [_eventDispatcher sendTextEventWithType:RCTTextEventTypeSubmit
+                                     reactTag:self.reactTag
+                                         text:self.text
+                                          key:nil
+                                   eventCount:_nativeEventCount];
+      [self resignFirstResponder];
+      return NO;
+    }
   }
 
   if (_maxLength == nil) {
@@ -296,8 +354,8 @@ - (void)textViewDidChangeSelection:(RCTUITextView *)textView
     _previousSelectionRange = textView.selectedTextRange;
 
     UITextRange *selection = textView.selectedTextRange;
-    NSInteger start = [textView offsetFromPosition:[textView beginningOfDocument] toPosition:selection.start];
-    NSInteger end = [textView offsetFromPosition:[textView beginningOfDocument] toPosition:selection.end];
+    NSInteger start = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.start];
+    NSInteger end = [textView offsetFromPosition:textView.beginningOfDocument toPosition:selection.end];
     _onSelectionChange(@{
       @"selection": @{
         @"start": @(start),
@@ -305,10 +363,6 @@ - (void)textViewDidChangeSelection:(RCTUITextView *)textView
       },
     });
   }
-
-  if (textView.editable && [textView isFirstResponder]) {
-    [textView scrollRangeToVisible:textView.selectedRange];
-  }
 }
 
 - (void)setText:(NSString *)text
@@ -316,9 +370,22 @@ - (void)setText:(NSString *)text
   NSInteger eventLag = _nativeEventCount - _mostRecentEventCount;
   if (eventLag == 0 && ![text isEqualToString:_textView.text]) {
     UITextRange *selection = _textView.selectedTextRange;
+    NSInteger oldTextLength = _textView.text.length;
+
     _textView.text = text;
+
+    if (selection.empty) {
+      // maintain cursor position relative to the end of the old text
+      NSInteger start = [_textView offsetFromPosition:_textView.beginningOfDocument toPosition:selection.start];
+      NSInteger offsetFromEnd = oldTextLength - start;
+      NSInteger newOffset = text.length - offsetFromEnd;
+      UITextPosition *position = [_textView positionFromPosition:_textView.beginningOfDocument offset:newOffset];
+      _textView.selectedTextRange = [_textView textRangeFromPosition:position toPosition:position];
+    }
+
     [self _setPlaceholderVisibility];
-    _textView.selectedTextRange = selection; // maintain cursor position/selection - this is robust to out of bounds
+    [self updateContentSize]; //keep the text wrapping when the length of
+    //the textline has been extended longer than the length of textinputView
   } else if (eventLag > RCTTextUpdateLagWarningThreshold) {
     RCTLogWarn(@"Native TextInput(%@) is %zd events ahead of JS - try to make your JS faster.", self.text, eventLag);
   }
@@ -362,21 +429,46 @@ - (void)textViewDidBeginEditing:(UITextView *)textView
 
   [_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
                                  reactTag:self.reactTag
-                                     text:textView.text
+                                     text:nil
                                       key:nil
                                eventCount:_nativeEventCount];
 }
 
 - (void)textViewDidChange:(UITextView *)textView
 {
+  [self updateContentSize];
   [self _setPlaceholderVisibility];
   _nativeEventCount++;
-  [_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
-                                 reactTag:self.reactTag
-                                     text:textView.text
-                                      key:nil
-                               eventCount:_nativeEventCount];
 
+  if (!self.reactTag) {
+    return;
+  }
+
+  // When the context size increases, iOS updates the contentSize twice; once
+  // with a lower height, then again with the correct height. To prevent a
+  // spurious event from being sent, we track the previous, and only send the
+  // update event if it matches our expectation that greater text length
+  // should result in increased height. This assumption is, of course, not
+  // necessarily true because shorter text might include more linebreaks, but
+  // in practice this works well enough.
+  NSUInteger textLength = textView.text.length;
+  CGFloat contentHeight = textView.contentSize.height;
+  if (textLength >= _previousTextLength) {
+    contentHeight = MAX(contentHeight, _previousContentHeight);
+  }
+  _previousTextLength = textLength;
+  _previousContentHeight = contentHeight;
+
+  NSDictionary *event = @{
+    @"text": self.text,
+    @"contentSize": @{
+      @"height": @(contentHeight),
+      @"width": @(textView.contentSize.width)
+    },
+    @"target": self.reactTag,
+    @"eventCount": @(_nativeEventCount),
+  };
+  [_eventDispatcher sendInputEventWithName:@"change" body:event];
 }
 
 - (void)textViewDidEndEditing:(UITextView *)textView
@@ -386,28 +478,43 @@ - (void)textViewDidEndEditing:(UITextView *)textView
                                      text:textView.text
                                       key:nil
                                eventCount:_nativeEventCount];
+
+  [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
+                                 reactTag:self.reactTag
+                                     text:nil
+                                      key:nil
+                               eventCount:_nativeEventCount];
+}
+
+- (BOOL)isFirstResponder
+{
+  return [_textView isFirstResponder];
+}
+
+- (BOOL)canBecomeFirstResponder
+{
+  return [_textView canBecomeFirstResponder];
+}
+
+- (void)reactWillMakeFirstResponder
+{
+  [_textView reactWillMakeFirstResponder];
 }
 
 - (BOOL)becomeFirstResponder
 {
-  _jsRequestingFirstResponder = YES;
-  BOOL result = [_textView becomeFirstResponder];
-  _jsRequestingFirstResponder = NO;
-  return result;
+  return [_textView becomeFirstResponder];
+}
+
+- (void)reactDidMakeFirstResponder
+{
+  [_textView reactDidMakeFirstResponder];
 }
 
 - (BOOL)resignFirstResponder
 {
   [super resignFirstResponder];
-  BOOL result = [_textView resignFirstResponder];
-  if (result) {
-    [_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
-                                   reactTag:self.reactTag
-                                       text:_textView.text
-                                        key:nil
-                                 eventCount:_nativeEventCount];
-  }
-  return result;
+  return [_textView resignFirstResponder];
 }
 
 - (void)layoutSubviews
@@ -416,11 +523,6 @@ - (void)layoutSubviews
   [self updateFrames];
 }
 
-- (BOOL)canBecomeFirstResponder
-{
-  return _jsRequestingFirstResponder;
-}
-
 - (UIFont *)defaultPlaceholderFont
 {
   return [UIFont systemFontOfSize:17];
diff --git a/Libraries/Text/RCTTextViewManager.m b/Libraries/Text/RCTTextViewManager.m
index c5c04ee7fb32..f447c905c54a 100644
--- a/Libraries/Text/RCTTextViewManager.m
+++ b/Libraries/Text/RCTTextViewManager.m
@@ -23,21 +23,25 @@ - (UIView *)view
   return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
 }
 
+RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
 RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
-RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
-RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
-RCT_EXPORT_VIEW_PROPERTY(text, NSString)
-RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
+RCT_EXPORT_VIEW_PROPERTY(blurOnSubmit, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
+RCT_REMAP_VIEW_PROPERTY(color, textView.textColor, UIColor)
+RCT_REMAP_VIEW_PROPERTY(textAlign, textView.textAlignment, NSTextAlignment)
+RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
+RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
 RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
 RCT_REMAP_VIEW_PROPERTY(keyboardAppearance, textView.keyboardAppearance, UIKeyboardAppearance)
+RCT_EXPORT_VIEW_PROPERTY(maxLength, NSNumber)
 RCT_EXPORT_VIEW_PROPERTY(onSelectionChange, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
+RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
 RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType)
-RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
-RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
-RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
+RCT_REMAP_VIEW_PROPERTY(secureTextEntry, textView.secureTextEntry, BOOL)
+RCT_REMAP_VIEW_PROPERTY(selectionColor, tintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(text, NSString)
 RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)
 {
   view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js
index c9ca8357d952..eeb88c2dd2d3 100644
--- a/Libraries/Text/Text.js
+++ b/Libraries/Text/Text.js
@@ -11,22 +11,22 @@
  */
 'use strict';
 
-var NativeMethodsMixin = require('NativeMethodsMixin');
-var Platform = require('Platform');
-var React = require('React');
-var ReactInstanceMap = require('ReactInstanceMap');
-var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
-var StyleSheetPropType = require('StyleSheetPropType');
-var TextStylePropTypes = require('TextStylePropTypes');
-var Touchable = require('Touchable');
-
-var createReactNativeComponentClass =
+const NativeMethodsMixin = require('NativeMethodsMixin');
+const Platform = require('Platform');
+const React = require('React');
+const ReactInstanceMap = require('ReactInstanceMap');
+const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
+const StyleSheetPropType = require('StyleSheetPropType');
+const TextStylePropTypes = require('TextStylePropTypes');
+const Touchable = require('Touchable');
+
+const createReactNativeComponentClass =
   require('createReactNativeComponentClass');
-var merge = require('merge');
+const merge = require('merge');
 
-var stylePropType = StyleSheetPropType(TextStylePropTypes);
+const stylePropType = StyleSheetPropType(TextStylePropTypes);
 
-var viewConfig = {
+const viewConfig = {
   validAttributes: merge(ReactNativeViewAttributes.UIView, {
     isHighlighted: true,
     numberOfLines: true,
@@ -68,13 +68,10 @@ var viewConfig = {
  * ```
  */
 
-var Text = React.createClass({
-
-  mixins: [Touchable.Mixin, NativeMethodsMixin],
-
+const Text = React.createClass({
   propTypes: {
     /**
-     * Used to truncate the text with an elipsis after computing the text
+     * Used to truncate the text with an ellipsis after computing the text
      * layout, including line wrapping, such that the total number of lines
      * does not exceed this number.
      */
@@ -102,124 +99,130 @@ var Text = React.createClass({
     testID: React.PropTypes.string,
     /**
      * Specifies should fonts scale to respect Text Size accessibility setting on iOS.
+     * @platform ios
      */
     allowFontScaling: React.PropTypes.bool,
   },
-
-  viewConfig: viewConfig,
-
-  getInitialState: function(): Object {
-    return merge(this.touchableGetInitialState(), {
-      isHighlighted: false,
-    });
-  },
-  getDefaultProps: function(): Object {
+  getDefaultProps(): Object {
     return {
+      accessible: true,
       allowFontScaling: true,
     };
   },
-
-  onStartShouldSetResponder: function(): bool {
-    var shouldSetFromProps = this.props.onStartShouldSetResponder &&
-      this.props.onStartShouldSetResponder();
-    return shouldSetFromProps || !!this.props.onPress;
-  },
-
-  /*
-   * Returns true to allow responder termination
-   */
-  handleResponderTerminationRequest: function(): bool {
-    // Allow touchable or props.onResponderTerminationRequest to deny
-    // the request
-    var allowTermination = this.touchableHandleResponderTerminationRequest();
-    if (allowTermination && this.props.onResponderTerminationRequest) {
-      allowTermination = this.props.onResponderTerminationRequest();
-    }
-    return allowTermination;
-  },
-
-  handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) {
-    this.touchableHandleResponderGrant(e, dispatchID);
-    this.props.onResponderGrant &&
-      this.props.onResponderGrant.apply(this, arguments);
-  },
-
-  handleResponderMove: function(e: SyntheticEvent) {
-    this.touchableHandleResponderMove(e);
-    this.props.onResponderMove &&
-      this.props.onResponderMove.apply(this, arguments);
-  },
-
-  handleResponderRelease: function(e: SyntheticEvent) {
-    this.touchableHandleResponderRelease(e);
-    this.props.onResponderRelease &&
-      this.props.onResponderRelease.apply(this, arguments);
-  },
-
-  handleResponderTerminate: function(e: SyntheticEvent) {
-    this.touchableHandleResponderTerminate(e);
-    this.props.onResponderTerminate &&
-      this.props.onResponderTerminate.apply(this, arguments);
-  },
-
-  touchableHandleActivePressIn: function() {
-    if (this.props.suppressHighlighting || !this.props.onPress) {
-      return;
-    }
-    this.setState({
-      isHighlighted: true,
-    });
-  },
-
-  touchableHandleActivePressOut: function() {
-    if (this.props.suppressHighlighting || !this.props.onPress) {
-      return;
-    }
-    this.setState({
+  getInitialState: function(): Object {
+    return merge(Touchable.Mixin.touchableGetInitialState(), {
       isHighlighted: false,
     });
   },
-
-  touchableHandlePress: function() {
-    this.props.onPress && this.props.onPress();
-  },
-
-  touchableGetPressRectOffset: function(): RectOffset {
-    return PRESS_RECT_OFFSET;
-  },
-
-  getChildContext: function(): Object {
+  mixins: [NativeMethodsMixin],
+  viewConfig: viewConfig,
+  getChildContext(): Object {
     return {isInAParentText: true};
   },
-
   childContextTypes: {
     isInAParentText: React.PropTypes.bool
   },
-
-  render: function() {
-    var props = {};
-    for (var key in this.props) {
-      props[key] = this.props[key];
-    }
-    // Text is accessible by default
-    if (props.accessible !== false) {
-      props.accessible = true;
+  contextTypes: {
+    isInAParentText: React.PropTypes.bool
+  },
+  /**
+   * Only assigned if touch is needed.
+   */
+  _handlers: (null: ?Object),
+  /**
+   * These are assigned lazily the first time the responder is set to make plain
+   * text nodes as cheap as possible.
+   */
+  touchableHandleActivePressIn: (null: ?Function),
+  touchableHandleActivePressOut: (null: ?Function),
+  touchableHandlePress: (null: ?Function),
+  touchableGetPressRectOffset: (null: ?Function),
+  render(): ReactElement {
+    let newProps = this.props;
+    if (this.props.onStartShouldSetResponder || this.props.onPress) {
+      if (!this._handlers) {
+        this._handlers = {
+          onStartShouldSetResponder: (): bool => {
+            const shouldSetFromProps = this.props.onStartShouldSetResponder &&
+                this.props.onStartShouldSetResponder();
+            const setResponder = shouldSetFromProps || !!this.props.onPress;
+            if (setResponder && !this.touchableHandleActivePressIn) {
+              // Attach and bind all the other handlers only the first time a touch
+              // actually happens.
+              for (let key in Touchable.Mixin) {
+                if (typeof Touchable.Mixin[key] === 'function') {
+                  (this: any)[key] = Touchable.Mixin[key].bind(this);
+                }
+              }
+              this.touchableHandleActivePressIn = () => {
+                if (this.props.suppressHighlighting || !this.props.onPress) {
+                  return;
+                }
+                this.setState({
+                  isHighlighted: true,
+                });
+              };
+
+              this.touchableHandleActivePressOut = () => {
+                if (this.props.suppressHighlighting || !this.props.onPress) {
+                  return;
+                }
+                this.setState({
+                  isHighlighted: false,
+                });
+              };
+
+              this.touchableHandlePress = () => {
+                this.props.onPress && this.props.onPress();
+              };
+
+              this.touchableGetPressRectOffset = function(): RectOffset {
+                return PRESS_RECT_OFFSET;
+              };
+            }
+            return setResponder;
+          },
+          onResponderGrant: function(e: SyntheticEvent, dispatchID: string) {
+            this.touchableHandleResponderGrant(e, dispatchID);
+            this.props.onResponderGrant &&
+              this.props.onResponderGrant.apply(this, arguments);
+          }.bind(this),
+          onResponderMove: function(e: SyntheticEvent) {
+            this.touchableHandleResponderMove(e);
+            this.props.onResponderMove &&
+              this.props.onResponderMove.apply(this, arguments);
+          }.bind(this),
+          onResponderRelease: function(e: SyntheticEvent) {
+            this.touchableHandleResponderRelease(e);
+            this.props.onResponderRelease &&
+              this.props.onResponderRelease.apply(this, arguments);
+          }.bind(this),
+          onResponderTerminate: function(e: SyntheticEvent) {
+            this.touchableHandleResponderTerminate(e);
+            this.props.onResponderTerminate &&
+              this.props.onResponderTerminate.apply(this, arguments);
+          }.bind(this),
+          onResponderTerminationRequest: function(): bool {
+            // Allow touchable or props.onResponderTerminationRequest to deny
+            // the request
+            var allowTermination = this.touchableHandleResponderTerminationRequest();
+            if (allowTermination && this.props.onResponderTerminationRequest) {
+              allowTermination = this.props.onResponderTerminationRequest.apply(this, arguments);
+            }
+            return allowTermination;
+          }.bind(this),
+        };
+      }
+      newProps = {
+        ...this.props,
+        ...this._handlers,
+        isHighlighted: this.state.isHighlighted,
+      };
     }
-    props.isHighlighted = this.state.isHighlighted;
-    props.onStartShouldSetResponder = this.onStartShouldSetResponder;
-    props.onResponderTerminationRequest =
-      this.handleResponderTerminationRequest;
-    props.onResponderGrant = this.handleResponderGrant;
-    props.onResponderMove = this.handleResponderMove;
-    props.onResponderRelease = this.handleResponderRelease;
-    props.onResponderTerminate = this.handleResponderTerminate;
-
-    // TODO: Switch to use contextTypes and this.context after React upgrade
-    var context = ReactInstanceMap.get(this)._context;
-    if (context.isInAParentText) {
-      return <RCTVirtualText {...props} />;
+    if (this.context.isInAParentText) {
+      return <RCTVirtualText {...newProps} />;
     } else {
-      return <RCTText {...props} />;
+      return <RCTText {...newProps} />;
     }
   },
 });
diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js
index cbd6249360f9..9c4668174e4f 100644
--- a/Libraries/Text/TextStylePropTypes.js
+++ b/Libraries/Text/TextStylePropTypes.js
@@ -12,11 +12,12 @@
 'use strict';
 
 var ReactPropTypes = require('ReactPropTypes');
+var ColorPropType = require('ColorPropType');
 var ViewStylePropTypes = require('ViewStylePropTypes');
 
 // TODO: use spread instead of Object.assign/create after #6560135 is fixed
 var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
-  color: ReactPropTypes.string,
+  color: ColorPropType,
   fontFamily: ReactPropTypes.string,
   fontSize: ReactPropTypes.number,
   fontStyle: ReactPropTypes.oneOf(['normal', 'italic']),
@@ -29,6 +30,11 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
     ['normal' /*default*/, 'bold',
      '100', '200', '300', '400', '500', '600', '700', '800', '900']
   ),
+  textShadowOffset: ReactPropTypes.shape(
+    {width: ReactPropTypes.number, height: ReactPropTypes.number}
+  ),
+  textShadowRadius: ReactPropTypes.number,
+  textShadowColor: ColorPropType,
   /**
    * @platform ios
    */
@@ -40,6 +46,12 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
   textAlign: ReactPropTypes.oneOf(
     ['auto' /*default*/, 'left', 'right', 'center', 'justify']
   ),
+  /**
+   * @platform android
+   */
+  textAlignVertical: ReactPropTypes.oneOf(
+    ['auto' /*default*/, 'top', 'bottom', 'center']
+  ),
   /**
    * @platform ios
    */
@@ -55,7 +67,7 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
   /**
    * @platform ios
    */
-  textDecorationColor: ReactPropTypes.string,
+  textDecorationColor: ColorPropType,
   /**
    * @platform ios
    */
@@ -64,19 +76,4 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), {
   ),
 });
 
-// Text doesn't support padding correctly (#4841912)
-var unsupportedProps = Object.keys({
-  padding: null,
-  paddingTop: null,
-  paddingLeft: null,
-  paddingRight: null,
-  paddingBottom: null,
-  paddingVertical: null,
-  paddingHorizontal: null,
-});
-
-for (var ii = 0; ii < unsupportedProps.length; ii++) {
-  delete TextStylePropTypes[unsupportedProps[ii]];
-}
-
 module.exports = TextStylePropTypes;
diff --git a/Libraries/Utilities/Alert.js b/Libraries/Utilities/Alert.js
new file mode 100644
index 000000000000..ee733d1ece63
--- /dev/null
+++ b/Libraries/Utilities/Alert.js
@@ -0,0 +1,133 @@
+/**
+ * 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 Alert
+ * @flow
+ */
+'use strict';
+
+var AlertIOS = require('AlertIOS');
+var Platform = require('Platform');
+var DialogModuleAndroid = require('NativeModules').DialogManagerAndroid;
+
+import type { AlertType, AlertButtonStyle } from 'AlertIOS';
+
+type Buttons = Array<{
+  text?: string;
+  onPress?: ?Function;
+  style?: AlertButtonStyle;
+}>;
+
+/**
+ * Launches an alert dialog with the specified title and message.
+ *
+ * Optionally provide a list of buttons. Tapping any button will fire the
+ * respective onPress callback and dismiss the alert. By default, the only
+ * button will be an 'OK' button.
+ *
+ * This is an API that works both on iOS and Android and can show static
+ * alerts. To show an alert that prompts the user to enter some information,
+ * see `AlertIOS`; entering text in an alert is common on iOS only.
+ *
+ * ## iOS
+ *
+ * On iOS you can specify any number of buttons. Each button can optionally
+ * specify a style, which is one of 'default', 'cancel' or 'destructive'.
+ *
+ * ## Android
+ *
+ * On Android at most three buttons can be specified. Android has a concept
+ * of a neutral, negative and a positive button:
+ *
+ *   - If you specify one button, it will be the 'positive' one (such as 'OK')
+ *   - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
+ *   - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
+ *
+ * ```
+ * // Works on both iOS and Android
+ * Alert.alert(
+ *   'Alert Title',
+ *   'My Alert Msg',
+ *   [
+ *     {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
+ *     {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
+ *     {text: 'OK', onPress: () => console.log('OK Pressed')},
+ *   ]
+ * )
+ * ```
+ */
+class Alert {
+
+  static alert(
+    title: ?string,
+    message?: ?string,
+    buttons?: Buttons,
+    type?: AlertType,
+  ): void {
+    if (Platform.OS === 'ios') {
+      if (typeof type !== 'undefined') {
+        console.warn('Alert.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.');
+        AlertIOS.alert(title, message, buttons, type);
+        return;
+      }
+      AlertIOS.alert(title, message, buttons);
+    } else if (Platform.OS === 'android') {
+      AlertAndroid.alert(title, message, buttons);
+    }
+  }
+}
+
+/**
+ * Wrapper around the Android native module.
+ */
+class AlertAndroid {
+
+  static alert(
+    title: ?string,
+    message?: ?string,
+    buttons?: Buttons,
+  ): void {
+    var config = {
+      title: title || '',
+      message: message || '',
+    };
+    // At most three buttons (neutral, negative, positive). Ignore rest.
+    // The text 'OK' should be probably localized. iOS Alert does that in native.
+    var validButtons: Buttons = buttons ? buttons.slice(0, 3) : [{text: 'OK'}];
+    var buttonPositive = validButtons.pop();
+    var buttonNegative = validButtons.pop();
+    var buttonNeutral = validButtons.pop();
+    if (buttonNeutral) {
+      config = {...config, buttonNeutral: buttonNeutral.text || '' }
+    }
+    if (buttonNegative) {
+      config = {...config, buttonNegative: buttonNegative.text || '' }
+    }
+    if (buttonPositive) {
+      config = {...config, buttonPositive: buttonPositive.text || '' }
+    }
+    DialogModuleAndroid.showAlert(
+      config,
+      (errorMessage) => console.warn(message),
+      (action, buttonKey) => {
+        if (action !== DialogModuleAndroid.buttonClicked) {
+          return;
+        }
+        if (buttonKey === DialogModuleAndroid.buttonNeutral) {
+          buttonNeutral.onPress && buttonNeutral.onPress();
+        } else if (buttonKey === DialogModuleAndroid.buttonNegative) {
+          buttonNegative.onPress && buttonNegative.onPress();
+        } else if (buttonKey === DialogModuleAndroid.buttonPositive) {
+          buttonPositive.onPress && buttonPositive.onPress();
+        }
+      }
+    );
+  }
+}
+
+module.exports = Alert;
diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js
index 7fd15d254ae6..1da237b0c23e 100644
--- a/Libraries/Utilities/AlertIOS.js
+++ b/Libraries/Utilities/AlertIOS.js
@@ -12,106 +12,176 @@
 'use strict';
 
 var RCTAlertManager = require('NativeModules').AlertManager;
-var invariant = require('invariant');
 
-var DEFAULT_BUTTON_TEXT = 'OK';
-var DEFAULT_BUTTON = {
-  text: DEFAULT_BUTTON_TEXT,
-  onPress: null,
-};
+export type AlertType = $Enum<{
+  'default': string;
+  'plain-text': string;
+  'secure-text': string;
+  'login-password': string;
+}>;
+
+export type AlertButtonStyle = $Enum<{
+  'default': string;
+  'cancel': string;
+  'destructive': string;
+}>;
+
+type ButtonsArray = Array<{
+  text?: string;
+  onPress?: ?Function;
+  style?: AlertButtonStyle;
+}>;
 
 /**
- * Launches an alert dialog with the specified title and message.
+ * The AlertsIOS utility provides two functions: `alert` and `prompt`. All
+ * functionality available through `AlertIOS.alert` is also available in the
+ * cross-platform `Alert.alert`, which we recommend you use if you don't need
+ * iOS-specific functionality.
  *
- * Optionally provide a list of buttons. Tapping any button will fire the
- * respective onPress callback and dismiss the alert. By default, the only
- * button will be an 'OK' button
+ * `AlertIOS.prompt` allows you to prompt the user for input inside of an
+ * alert popup.
  *
- * The last button in the list will be considered the 'Primary' button and
- * it will appear bold.
- *
- * ```
- * AlertIOS.alert(
- *   'Foo Title',
- *   'My Alert Msg',
- *   [
- *     {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
- *     {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
- *   ]
- * )
- * ```
  */
-
 class AlertIOS {
+  /**
+   * Creates a popup to alert the user. See
+   * [Alert](docs/alert.html).
+   *
+   *  - title: string -- The dialog's title.
+   *  - message: string -- An optional message that appears above the text input.
+   *  - callbackOrButtons -- This optional argument should be either a
+   *    single-argument function or an array of buttons. If passed a function,
+   *    it will be called when the user taps 'OK'.
+   *
+   *    If passed an array of button configurations, each button should include
+   *    a `text` key, as well as optional `onPress` and `style` keys.
+   *    `style` should be one of 'default', 'cancel' or 'destructive'.
+   *  - type -- *deprecated, do not use*
+   *
+   * Example:
+   *
+   * ```
+   * AlertIOS.alert(
+   *  'Sync Complete',
+   *  'All your data are belong to us.'
+   * );
+   * ```
+   */
   static alert(
     title: ?string,
     message?: ?string,
-    buttons?: Array<{
-      text: ?string;
-      onPress?: ?Function;
-    }>,
-    type?: ?string
+    callbackOrButtons?: ?(() => void) | ButtonsArray,
+    type?: AlertType,
   ): void {
-    var callbacks = [];
-    var buttonsSpec = [];
-    title = title || '';
-    message = message || '';
-    buttons = buttons || [DEFAULT_BUTTON];
-    type = type || '';
-
-    buttons.forEach((btn, index) => {
-      callbacks[index] = btn.onPress;
-      var btnDef = {};
-      btnDef[index] = btn.text || DEFAULT_BUTTON_TEXT;
-      buttonsSpec.push(btnDef);
-    });
-    RCTAlertManager.alertWithArgs({
-      title,
-      message,
-      buttons: buttonsSpec,
-      type,
-    }, (id, value) => {
-      var cb = callbacks[id];
-      cb && cb(value);
-    });
+    if (typeof type !== 'undefined') {
+      console.warn('AlertIOS.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.');
+      this.prompt(title, message, callbackOrButtons, type);
+      return;
+    }
+    this.prompt(title, message, callbackOrButtons, 'default');
   }
 
+  /**
+   * Prompt the user to enter some text.
+   *
+   *  - title: string -- The dialog's title.
+   *  - message: string -- An optional message that appears above the text input.
+   *  - callbackOrButtons -- This optional argument should be either a
+   *    single-argument function or an array of buttons. If passed a function,
+   *    it will be called with the prompt's value when the user taps 'OK'.
+   *
+   *    If passed an array of button configurations, each button should include
+   *    a `text` key, as well as optional `onPress` and `style` keys (see example).
+   *    `style` should be one of 'default', 'cancel' or 'destructive'.
+   *  - type: string -- This configures the text input. One of 'plain-text',
+   *    'secure-text' or 'login-password'.
+   *  - defaultValue: string -- the default value for the text field.
+   *
+   * Example with custom buttons:
+   * ```
+   * AlertIOS.prompt(
+   *   'Enter password',
+   *   'Enter your password to claim your $1.5B in lottery winnings',
+   *   [
+   *     {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
+   *     {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)},
+   *   ],
+   *   'secure-text'
+   * );
+   * ```
+   *
+   * Example with the default button and a custom callback:
+   * ```
+   * AlertIOS.prompt(
+   *   'Update username',
+   *   null,
+   *   text => console.log("Your username is "+text),
+   *   null,
+   *   'default'
+   * )
+   * ```
+   */
   static prompt(
-    title: string,
-    value?: string,
-    buttons?: Array<{
-      text: ?string;
-      onPress?: ?Function;
-    }>,
-    callback?: ?Function
+    title: ?string,
+    message?: ?string,
+    callbackOrButtons?: ?((text: string) => void) | ButtonsArray,
+    type?: ?AlertType = 'plain-text',
+    defaultValue?: string,
   ): void {
-    if (arguments.length === 2) {
-      if (typeof value === 'object') {
-        buttons = value;
-        value = undefined;
-      } else if (typeof value === 'function') {
-        callback = value;
-        value = undefined;
-      }
-    } else if (arguments.length === 3 && typeof buttons === 'function') {
-      callback = buttons;
-      buttons = undefined;
-    }
+    if (typeof type === 'function') {
+      console.warn(
+        'You passed a callback function as the "type" argument to AlertIOS.prompt(). React Native is ' +
+        'assuming  you want to use the deprecated AlertIOS.prompt(title, defaultValue, buttons, callback) ' +
+        'signature. The current signature is AlertIOS.prompt(title, message, callbackOrButtons, type, defaultValue) ' +
+        'and the old syntax will be removed in a future version.');
 
-    invariant(
-      !(callback && buttons) && (callback || buttons),
-      'Must provide either a button list or a callback, but not both'
-    );
+      var callback = type;
+      var defaultValue = message;
+      RCTAlertManager.alertWithArgs({
+        title: title || undefined,
+        type: 'plain-text',
+        defaultValue,
+      }, (id, value) => {
+        callback(value);
+      });
+      return;
+    }
 
-    if (!buttons) {
-      buttons = [{
-        text: 'Cancel',
-      }, {
-        text: 'OK',
-        onPress: callback
-      }];
+    var callbacks = [];
+    var buttons = [];
+    var cancelButtonKey;
+    var destructiveButtonKey;
+    if (typeof callbackOrButtons === 'function') {
+      callbacks = [callbackOrButtons];
+    }
+    else if (callbackOrButtons instanceof Array) {
+      callbackOrButtons.forEach((btn, index) => {
+        callbacks[index] = btn.onPress;
+        if (btn.style === 'cancel') {
+          cancelButtonKey = String(index);
+        } else if (btn.style === 'destructive') {
+          destructiveButtonKey = String(index);
+        }
+        if (btn.text || index < (callbackOrButtons || []).length - 1) {
+          var btnDef = {};
+          btnDef[index] = btn.text || '';
+          buttons.push(btnDef);
+        }
+      });
     }
-    this.alert(title, value, buttons, 'plain-text');
+
+    RCTAlertManager.alertWithArgs({
+      title: title || undefined,
+      message: message || undefined,
+      buttons,
+      type: type || undefined,
+      defaultValue,
+      cancelButtonKey,
+      destructiveButtonKey,
+    }, (id, value) => {
+      var cb = callbacks[id];
+      cb && cb(value);
+    });
   }
 }
 
diff --git a/Libraries/Utilities/BackAndroid.android.js b/Libraries/Utilities/BackAndroid.android.js
index d7f3a482be68..795380448d2f 100644
--- a/Libraries/Utilities/BackAndroid.android.js
+++ b/Libraries/Utilities/BackAndroid.android.js
@@ -11,7 +11,6 @@
 
 'use strict';
 
-var Set = require('Set');
 var DeviceEventManager = require('NativeModules').DeviceEventManager;
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 
@@ -24,8 +23,9 @@ type BackPressEventName = $Enum<{
 var _backPressSubscriptions = new Set();
 
 RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() {
+  var backPressSubscriptions = new Set(_backPressSubscriptions);
   var invokeDefault = true;
-  _backPressSubscriptions.forEach((subscription) => {
+  backPressSubscriptions.forEach((subscription) => {
     if (subscription()) {
       invokeDefault = false;
     }
@@ -36,7 +36,7 @@ RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function() {
 });
 
 /**
- * Detect hardware back button presses, and programatically invoke the default back button
+ * Detect hardware back button presses, and programmatically invoke the default back button
  * functionality to exit the app if there are no listeners or if none of the listeners return true.
  *
  * Example:
@@ -60,8 +60,11 @@ var BackAndroid = {
   addEventListener: function (
     eventName: BackPressEventName,
     handler: Function
-  ): void {
+  ): {remove: () => void} {
     _backPressSubscriptions.add(handler);
+    return {
+      remove: () => BackAndroid.removeEventListener(eventName, handler),
+    };
   },
 
   removeEventListener: function(
diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js
deleted file mode 100644
index 16b6a7159339..000000000000
--- a/Libraries/Utilities/BridgeProfiling.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * 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 BridgeProfiling
- * @flow
- */
-'use strict';
-
-var GLOBAL = GLOBAL || this;
-var TRACE_TAG_REACT_APPS = 1 << 17;
-
-var _enabled;
-var _ReactPerf = null;
-function ReactPerf() {
-  if (!_ReactPerf) {
-    _ReactPerf = require('ReactPerf');
-  }
-  return _ReactPerf;
-}
-
-var BridgeProfiling = {
-  setEnabled(enabled: boolean) {
-    _enabled = enabled;
-
-    ReactPerf().enableMeasure = enabled;
-  },
-
-  profile(profileName?: any) {
-    if (_enabled) {
-      profileName = typeof profileName === 'function' ?
-        profileName() : profileName;
-      global.nativeTraceBeginSection(TRACE_TAG_REACT_APPS, profileName);
-    }
-  },
-
-  profileEnd() {
-    if (_enabled) {
-      global.nativeTraceEndSection(TRACE_TAG_REACT_APPS);
-    }
-  },
-
-  reactPerfMeasure(objName: string, fnName: string, func: any): any {
-    return function (component) {
-      if (!_enabled) {
-        return func.apply(this, arguments);
-      }
-
-      var name = objName === 'ReactCompositeComponent' && this.getName() || '';
-      BridgeProfiling.profile(`${objName}.${fnName}(${name})`);
-      var ret = func.apply(this, arguments);
-      BridgeProfiling.profileEnd();
-      return ret;
-    };
-  },
-
-  swizzleReactPerf() {
-    ReactPerf().injection.injectMeasure(BridgeProfiling.reactPerfMeasure);
-  },
-};
-
-BridgeProfiling.setEnabled(global.__RCTProfileIsProfiling || false);
-
-module.exports = BridgeProfiling;
diff --git a/Libraries/Utilities/CPUProfiler.js b/Libraries/Utilities/CPUProfiler.js
new file mode 100644
index 000000000000..445706c7b088
--- /dev/null
+++ b/Libraries/Utilities/CPUProfiler.js
@@ -0,0 +1,43 @@
+/**
+ * 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 CPUProfiler
+ * @flow
+ */
+'use strict';
+
+var _label;
+var _nestingLevel = 0;
+
+var CPUProfiler = {
+
+  start(profileName: string) {
+    if (_nestingLevel === 0) {
+      if (global.nativeProfilerStart) {
+        _label = profileName;
+        global.nativeProfilerStart(profileName);
+      } else if (console.profile) {
+        console.profile(profileName);
+      }
+    }
+    _nestingLevel++;
+  },
+  end() {
+    _nestingLevel--;
+    if (_nestingLevel === 0) {
+      if (global.nativeProfilerEnd) {
+        global.nativeProfilerEnd(_label);
+      } else if (console.profileEnd) {
+        console.profileEnd();
+      }
+      _label = undefined;
+    }
+  }
+};
+
+module.exports = CPUProfiler;
diff --git a/Libraries/Utilities/CSSVarConfig.js b/Libraries/Utilities/CSSVarConfig.js
index cb018be0af59..55c0244c3d7e 100644
--- a/Libraries/Utilities/CSSVarConfig.js
+++ b/Libraries/Utilities/CSSVarConfig.js
@@ -5,35 +5,9 @@
  */
 'use strict';
 
-// this a partial list of the contants in CSSConstants:: from PHP that are applicable to mobile
-
 module.exports = {
   'fbui-accent-blue': '#5890ff',
-  'fbui-blue-90': '#4e69a2',
-  'fbui-blue-80': '#627aad',
-  'fbui-blue-70': '#758ab7',
-  'fbui-blue-60': '#899bc1',
-  'fbui-blue-50': '#9daccb',
-  'fbui-blue-40': '#b1bdd6',
-  'fbui-blue-30': '#c4cde0',
-  'fbui-blue-20': '#d8deea',
-  'fbui-blue-10': '#ebeef4',
-  'fbui-blue-5': '#f5f7fa',
-  'fbui-blue-2': '#fbfcfd',
-  'fbui-blueblack-90': '#06090f',
-  'fbui-blueblack-80': '#0c121e',
-  'fbui-blueblack-70': '#121b2e',
-  'fbui-blueblack-60': '#18243d',
-  'fbui-blueblack-50': '#1e2d4c',
-  'fbui-blueblack-40': '#23355b',
-  'fbui-blueblack-30': '#293e6b',
-  'fbui-blueblack-20': '#2f477a',
-  'fbui-blueblack-10': '#355089',
-  'fbui-blueblack-5': '#385490',
-  'fbui-blueblack-2': '#3a5795',
-  'fbui-bluegray-90': '#080a10',
   'fbui-bluegray-80': '#141823',
-  'fbui-bluegray-70': '#232937',
   'fbui-bluegray-60': '#373e4d',
   'fbui-bluegray-50': '#4e5665',
   'fbui-bluegray-40': '#6a7180',
@@ -42,21 +16,13 @@ module.exports = {
   'fbui-bluegray-10': '#dcdee3',
   'fbui-bluegray-5': '#e9eaed',
   'fbui-bluegray-2': '#f6f7f8',
-  'fbui-gray-90': '#191919',
   'fbui-gray-80': '#333333',
-  'fbui-gray-70': '#4c4c4c',
   'fbui-gray-60': '#666666',
-  'fbui-gray-50': '#7f7f7f',
   'fbui-gray-40': '#999999',
-  'fbui-gray-30': '#b2b2b2',
   'fbui-gray-20': '#cccccc',
   'fbui-gray-10': '#e5e5e5',
   'fbui-gray-5': '#f2f2f2',
   'fbui-gray-2': '#fafafa',
-  'fbui-red': '#da2929',
-  'fbui-error': '#ce0d24',
-  'x-mobile-dark-text': '#4e5665',
+  'fbui-red': '#dc3847',
   'x-mobile-medium-text': '#6a7180',
-  'x-mobile-light-text': '#9197a3',
-  'x-mobile-base-wash': '#dcdee3',
 };
diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js
index 31fa0d4dc4f3..5cf5f21edc21 100644
--- a/Libraries/Utilities/Dimensions.js
+++ b/Libraries/Utilities/Dimensions.js
@@ -11,11 +11,12 @@
  */
 'use strict';
 
-var NativeModules = require('NativeModules');
+var Platform = require('Platform');
+var UIManager = require('UIManager');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
-var dimensions = NativeModules.UIManager.Dimensions;
+var dimensions = UIManager.Dimensions;
 
 // We calculate the window dimensions in JS so that we don't encounter loss of
 // precision in transferring the dimensions (which could be non-integers) over
@@ -31,7 +32,21 @@ if (dimensions && dimensions.windowPhysicalPixels) {
     scale: windowPhysicalPixels.scale,
     fontScale: windowPhysicalPixels.fontScale,
   };
+  if (Platform.OS === 'android') {
+    // Screen and window dimensions are different on android
+    var screenPhysicalPixels = dimensions.screenPhysicalPixels;
+    dimensions.screen = {
+      width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
+      height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
+      scale: screenPhysicalPixels.scale,
+      fontScale: screenPhysicalPixels.fontScale,
+    };
 
+    // delete so no callers rely on this existing
+    delete dimensions.screenPhysicalPixels;
+  } else {
+    dimensions.screen = dimensions.window;
+  }
   // delete so no callers rely on this existing
   delete dimensions.windowPhysicalPixels;
 }
@@ -57,6 +72,8 @@ class Dimensions {
    * than caching the value (for example, using inline styles rather than
    * setting a value in a `StyleSheet`).
    *
+   * Example: `var {height, width} = Dimensions.get('window');`
+   *
    * @param {string} dim Name of dimension as defined when calling `set`.
    * @returns {Object?} Value for the dimension.
    */
diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js
new file mode 100644
index 000000000000..14850f860ba6
--- /dev/null
+++ b/Libraries/Utilities/HMRClient.js
@@ -0,0 +1,131 @@
+/**
+ * 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 HMRClient
+ * @flow
+ */
+'use strict';
+
+const Platform = require('Platform');
+const invariant = require('fbjs/lib/invariant');
+
+/**
+ * HMR Client that receives from the server HMR updates and propagates them
+ * runtime to reflects those changes.
+ */
+const HMRClient = {
+  enable(platform: string, bundleEntry: string, host: string, port: number) {
+    invariant(platform, 'Missing required parameter `platform`');
+    invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
+    invariant(host, 'Missing required paramenter `host`');
+
+    // need to require WebSocket inside of `enable` function because
+    // this module is defined as a `polyfillGlobal`.
+    // See `InitializeJavascriptAppEngine.js`
+    const WebSocket = require('WebSocket');
+
+    const wsHostPort = port !== null && port !== ''
+      ? `${host}:${port}`
+      : 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) => {
+      throw new Error(
+`Hot loading isn't working because it cannot connect to the development server.
+
+Ensure the following:
+- Node server is running and available on the same network
+- run 'npm start' from react-native root
+- Node server URL is correctly set in AppDelegate
+
+URL: ${host}:${port}
+
+Error: ${e.message}`
+      );
+    };
+    activeWS.onmessage = ({data}) => {
+      // Moving to top gives errors due to NativeModules not being initialized
+      const HMRLoadingView = require('HMRLoadingView');
+
+      data = JSON.parse(data);
+
+      switch (data.type) {
+        case 'update-start': {
+          HMRLoadingView.showMessage('Hot Loading...');
+          break;
+        }
+        case 'update': {
+          const {
+            modules,
+            sourceMappingURLs,
+            sourceURLs,
+            inverseDependencies,
+          } = data.body;
+
+          if (Platform.OS === 'ios') {
+            const RCTRedBox = require('NativeModules').RedBox;
+            RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
+          } else {
+            const RCTExceptionsManager = require('NativeModules').ExceptionsManager;
+            RCTExceptionsManager && RCTExceptionsManager.dismissRedbox && RCTExceptionsManager.dismissRedbox();
+          }
+
+          modules.forEach(({name, code}, i) => {
+            code = code + '\n\n' + sourceMappingURLs[i];
+
+            require('SourceMapsCache').fetch({
+              text: code,
+              url: sourceURLs[i],
+              sourceMappingURL: sourceMappingURLs[i],
+            });
+
+            // on JSC we need to inject from native for sourcemaps to work
+            // (Safari doesn't support `sourceMappingURL` nor any variant when
+            // evaluating code) but on Chrome we can simply use eval
+            const injectFunction = typeof global.nativeInjectHMRUpdate === 'function'
+              ? global.nativeInjectHMRUpdate
+              : eval;
+
+            // TODO: (martinb) yellow box if cannot accept module
+            code = `
+              __accept(
+                ${name},
+                function(global, require, module, exports) {
+                  ${code}
+                },
+                ${JSON.stringify(inverseDependencies)}
+              );`;
+
+            injectFunction(code, sourceURLs[i]);
+          });
+
+          HMRLoadingView.hide();
+          break;
+        }
+        case 'update-done': {
+          HMRLoadingView.hide();
+          break;
+        }
+        case 'error': {
+          HMRLoadingView.hide();
+          throw new Error(data.body.type + ' ' + data.body.description);
+        }
+        default: {
+          throw new Error(`Unexpected message: ${data}`);
+        }
+      }
+    };
+  },
+};
+
+module.exports = HMRClient;
diff --git a/Libraries/Utilities/HMRLoadingView.android.js b/Libraries/Utilities/HMRLoadingView.android.js
new file mode 100644
index 000000000000..82075ffa1920
--- /dev/null
+++ b/Libraries/Utilities/HMRLoadingView.android.js
@@ -0,0 +1,38 @@
+/**
+ * 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 HMRLoadingView
+ * @flow
+ */
+
+'use strict';
+
+var ToastAndroid = require('ToastAndroid');
+
+const TOAST_SHORT_DELAY = 2000;
+
+class HMRLoadingView {
+  static _showing: boolean;
+
+  static showMessage(message: string) {
+    if (HMRLoadingView._showing) {
+      return;
+    }
+    ToastAndroid.show(message, ToastAndroid.SHORT);
+    HMRLoadingView._showing = true;
+    setTimeout(() => {
+      HMRLoadingView._showing = false;
+    }, TOAST_SHORT_DELAY);
+  }
+
+  static hide() {
+    // noop
+  }
+}
+
+module.exports = HMRLoadingView;
diff --git a/Libraries/Utilities/HMRLoadingView.ios.js b/Libraries/Utilities/HMRLoadingView.ios.js
new file mode 100644
index 000000000000..ea0559fc41d0
--- /dev/null
+++ b/Libraries/Utilities/HMRLoadingView.ios.js
@@ -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.
+ *
+ * @providesModule HMRLoadingView
+ * @flow
+ */
+
+'use strict';
+
+const processColor = require('processColor');
+const { DevLoadingView } = require('NativeModules');
+
+class HMRLoadingView {
+  static showMessage(message: string) {
+    DevLoadingView.showMessage(
+      message,
+      processColor('#000000'),
+      processColor('#aaaaaa'),
+    );
+  }
+
+  static hide() {
+    DevLoadingView.hide();
+  }
+}
+
+module.exports = HMRLoadingView;
diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js
index 23db73811f18..6900106a6e0f 100755
--- a/Libraries/Utilities/MatrixMath.js
+++ b/Libraries/Utilities/MatrixMath.js
@@ -2,11 +2,12 @@
  * Copyright 2004-present Facebook. All Rights Reserved.
  *
  * @providesModule MatrixMath
+ * @noflow
  */
 /* eslint-disable space-infix-ops */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 /**
  * Memory conservative (mutative) matrix math utilities. Uses "command"
@@ -66,8 +67,14 @@ var MatrixMath = {
     ];
   },
 
+  /**
+   * This create a perspective projection towards negative z
+   * Clipping the z range of [-near, -far]
+   *
+   * @param fovInRadians - field of view in randians
+   */
   createPerspective: function(fovInRadians, aspect, near, far) {
-    var h = 1 / Math.tan(fovInRadians);
+    var h = 1 / Math.tan(fovInRadians / 2);
     var r_depth  = 1 / (near - far);
     var C = (far + near) * r_depth;
     var D = 2 * (far * near * r_depth);
@@ -464,10 +471,10 @@ var MatrixMath = {
 
       // Solve the equation by inverting perspectiveMatrix and multiplying
       // rightHandSide by the inverse.
-      var inversePerspectiveMatrix = MatrixMath.inverse3x3(
+      var inversePerspectiveMatrix = MatrixMath.inverse(
         perspectiveMatrix
       );
-      var transposedInversePerspectiveMatrix = MatrixMath.transpose4x4(
+      var transposedInversePerspectiveMatrix = MatrixMath.transpose(
         inversePerspectiveMatrix
       );
       var perspective = MatrixMath.multiplyVectorByMatrix(
@@ -582,6 +589,8 @@ var MatrixMath = {
       translation,
 
       rotate: rotationDegrees[2],
+      rotateX: rotationDegrees[0],
+      rotateY: rotationDegrees[1],
       scaleX: scale[0],
       scaleY: scale[1],
       translateX: translation[0],
diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js
index 8b336bd7e461..2e542b6c40f3 100644
--- a/Libraries/Utilities/MessageQueue.js
+++ b/Libraries/Utilities/MessageQueue.js
@@ -13,20 +13,23 @@
 
 'use strict';
 
-let BridgeProfiling = require('BridgeProfiling');
+let Systrace = require('Systrace');
 let ErrorUtils = require('ErrorUtils');
 let JSTimersExecution = require('JSTimersExecution');
-let ReactUpdates = require('ReactUpdates');
+let Platform = require('Platform');
 
-let invariant = require('invariant');
-let keyMirror = require('keyMirror');
+let invariant = require('fbjs/lib/invariant');
+let keyMirror = require('fbjs/lib/keyMirror');
 let stringifySafe = require('stringifySafe');
 
 let MODULE_IDS = 0;
 let METHOD_IDS = 1;
 let PARAMS = 2;
+let CALL_IDS = 3;
 let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
 
+let TRACE_TAG_REACT_APPS = 1 << 17;
+
 let SPY_MODE = false;
 
 let MethodTypes = keyMirror({
@@ -44,16 +47,18 @@ var guard = (fn) => {
 
 class MessageQueue {
 
-  constructor(remoteModules, localModules, customRequire) {
+  constructor(remoteModules, localModules) {
     this.RemoteModules = {};
 
-    this._require = customRequire || require;
-    this._queue = [[],[],[]];
+    this._callableModules = {};
+    this._queue = [[], [], [], 0];
     this._moduleTable = {};
     this._methodTable = {};
     this._callbacks = [];
     this._callbackID = 0;
+    this._callID = 0;
     this._lastFlush = 0;
+    this._eventLoopStartTime = new Date().getTime();
 
     [
       'invokeCallbackAndReturnFlushedQueue',
@@ -67,8 +72,6 @@ class MessageQueue {
       this._genModulesConfig(localModules),this._moduleTable, this._methodTable
     );
 
-    this._copyNativeComponentConstants(this.RemoteModules);
-
     this._debugInfo = {};
     this._remoteModuleTable = {};
     this._remoteMethodTable = {};
@@ -102,18 +105,28 @@ class MessageQueue {
     this.__callImmediates();
 
     let queue = this._queue;
-    this._queue = [[],[],[]];
+    this._queue = [[], [], [], this._callID];
     return queue[0].length ? queue : null;
   }
 
+  processModuleConfig(config, moduleID) {
+    const module = this._genModule(config, moduleID);
+    this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
+    return module;
+  }
+
+  getEventLoopRunningTime() {
+    return new Date().getTime() - this._eventLoopStartTime;
+  }
+
   /**
    * "Private" methods
    */
 
   __callImmediates() {
-    BridgeProfiling.profile('JSTimersExecution.callImmediates()');
+    Systrace.beginEvent('JSTimersExecution.callImmediates()');
     guard(() => JSTimersExecution.callImmediates());
-    BridgeProfiling.profileEnd();
+    Systrace.endEvent();
   }
 
   __nativeCall(module, method, params, onFail, onSucc) {
@@ -128,6 +141,11 @@ class MessageQueue {
       onSucc && params.push(this._callbackID);
       this._callbacks[this._callbackID++] = onSucc;
     }
+
+    global.nativeTraceBeginAsyncFlow &&
+      global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native', this._callID);
+    this._callID++;
+
     this._queue[MODULE_IDS].push(module);
     this._queue[METHOD_IDS].push(method);
     this._queue[PARAMS].push(params);
@@ -136,9 +154,10 @@ class MessageQueue {
     if (global.nativeFlushQueueImmediate &&
         now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
       global.nativeFlushQueueImmediate(this._queue);
-      this._queue = [[],[],[]];
+      this._queue = [[], [], [], this._callID];
       this._lastFlush = now;
     }
+    Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
     if (__DEV__ && SPY_MODE && isFinite(module)) {
       console.log('JS->N : ' + this._remoteModuleTable[module] + '.' +
         this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')');
@@ -146,71 +165,53 @@ class MessageQueue {
   }
 
   __callFunction(module, method, args) {
-    BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`);
     this._lastFlush = new Date().getTime();
+    this._eventLoopStartTime = this._lastFlush;
     if (isFinite(module)) {
       method = this._methodTable[module][method];
       module = this._moduleTable[module];
     }
+    Systrace.beginEvent(`${module}.${method}()`);
     if (__DEV__ && SPY_MODE) {
       console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')');
     }
-    module = this._require(module);
-    module[method].apply(module, args);
-    BridgeProfiling.profileEnd();
+    var moduleMethods = this._callableModules[module];
+    invariant(
+      !!moduleMethods,
+      'Module %s is not a registered callable module.',
+      module
+    );
+    moduleMethods[method].apply(moduleMethods, args);
+    Systrace.endEvent();
   }
 
   __invokeCallback(cbID, args) {
-    BridgeProfiling.profile(
-      () => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`);
     this._lastFlush = new Date().getTime();
+    this._eventLoopStartTime = this._lastFlush;
     let callback = this._callbacks[cbID];
-    if (!callback || __DEV__) {
-      let debug = this._debugInfo[cbID >> 1];
-      let module = debug && this._remoteModuleTable[debug[0]];
-      let method = debug && this._remoteMethodTable[debug[0]][debug[1]];
-      invariant(
-        callback,
-        `Callback with id ${cbID}: ${module}.${method}() not found`
-      );
-      if (callback && SPY_MODE) {
-        console.log('N->JS : <callback for ' + module + '.' + method + '>(' + JSON.stringify(args) + ')');
-      }
+    let debug = this._debugInfo[cbID >> 1];
+    let module = debug && this._remoteModuleTable[debug[0]];
+    let method = debug && this._remoteMethodTable[debug[0]][debug[1]];
+    invariant(
+      callback,
+      `Callback with id ${cbID}: ${module}.${method}() not found`
+    );
+    let profileName = debug ? '<callback for ' + module + '.' + method + '>' : cbID;
+    if (callback && SPY_MODE && __DEV__) {
+      console.log('N->JS : ' + profileName + '(' + JSON.stringify(args) + ')');
     }
+    Systrace.beginEvent(
+      `MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`);
     this._callbacks[cbID & ~1] = null;
     this._callbacks[cbID |  1] = null;
     callback.apply(null, args);
-    BridgeProfiling.profileEnd();
+    Systrace.endEvent();
   }
 
   /**
    * Private helper methods
    */
 
-  /**
-   * Copies the ViewManager constants into UIManager. This is only
-   * needed for iOS, which puts the constants in the ViewManager
-   * namespace instead of UIManager, unlike Android.
-   */
-  _copyNativeComponentConstants(remoteModules) {
-    let UIManager = remoteModules.RCTUIManager;
-    UIManager && Object.keys(UIManager).forEach(viewName => {
-      let viewConfig = UIManager[viewName];
-      if (viewConfig.Manager) {
-        const viewManager = remoteModules[viewConfig.Manager];
-        viewManager && Object.keys(viewManager).forEach(key => {
-          const value = viewManager[key];
-          if (typeof value !== 'function') {
-            if (!viewConfig.Constants) {
-              viewConfig.Constants = {};
-            }
-            viewConfig.Constants[key] = value;
-          }
-        });
-      }
-    });
-  }
-
   /**
    * Converts the old, object-based module structure to the new
    * array-based structure. TODO (t8823865) Removed this
@@ -256,52 +257,59 @@ class MessageQueue {
   }
 
   _genLookupTables(modulesConfig, moduleTable, methodTable) {
-    modulesConfig.forEach((module, moduleID) => {
-      if (!module) {
-        return;
-      }
-
-      let moduleName, methods;
-      if (moduleHasConstants(module)) {
-        [moduleName, , methods] = module;
-      } else {
-        [moduleName, methods] = module;
-      }
-
-      moduleTable[moduleID] = moduleName;
-      methodTable[moduleID] = Object.assign({}, methods);
+    modulesConfig.forEach((config, moduleID) => {
+      this._genLookup(config, moduleID, moduleTable, methodTable);
     });
   }
 
-  _genModules(remoteModules) {
-    remoteModules.forEach((module, moduleID) => {
-      if (!module) {
-        return;
-      }
+  _genLookup(config, moduleID, moduleTable, methodTable) {
+    if (!config) {
+      return;
+    }
 
-      let moduleName, constants, methods, asyncMethods;
-      if (moduleHasConstants(module)) {
-        [moduleName, constants, methods, asyncMethods] = module;
-      } else {
-        [moduleName, methods, asyncMethods] = module;
-      }
+    let moduleName, methods;
+    if (moduleHasConstants(config)) {
+      [moduleName, , methods] = config;
+    } else {
+      [moduleName, methods] = config;
+    }
 
-      const moduleConfig = {moduleID, constants, methods, asyncMethods};
-      this.RemoteModules[moduleName] = this._genModule({}, moduleConfig);
+    moduleTable[moduleID] = moduleName;
+    methodTable[moduleID] = Object.assign({}, methods);
+  }
+
+  _genModules(remoteModules) {
+    remoteModules.forEach((config, moduleID) => {
+      this._genModule(config, moduleID);
     });
   }
 
-  _genModule(module, moduleConfig) {
-    const {moduleID, constants, methods = [], asyncMethods = []} = moduleConfig;
+  _genModule(config, moduleID) {
+    if (!config) {
+      return;
+    }
+
+    let moduleName, constants, methods, asyncMethods;
+    if (moduleHasConstants(config)) {
+      [moduleName, constants, methods, asyncMethods] = config;
+    } else {
+      [moduleName, methods, asyncMethods] = config;
+    }
 
-    methods.forEach((methodName, methodID) => {
+    let module = {};
+    methods && methods.forEach((methodName, methodID) => {
       const methodType =
-        arrayContains(asyncMethods, methodID) ?
+        asyncMethods && arrayContains(asyncMethods, methodID) ?
           MethodTypes.remoteAsync : MethodTypes.remote;
       module[methodName] = this._genMethod(moduleID, methodID, methodType);
     });
     Object.assign(module, constants);
 
+    if (!constants && !methods && !asyncMethods) {
+      module.moduleID = moduleID;
+    }
+
+    this.RemoteModules[moduleName] = module;
     return module;
   }
 
@@ -311,10 +319,17 @@ class MessageQueue {
     if (type === MethodTypes.remoteAsync) {
       fn = function(...args) {
         return new Promise((resolve, reject) => {
-          self.__nativeCall(module, method, args, resolve, (errorData) => {
-            var error = createErrorFromErrorData(errorData);
-            reject(error);
-          });
+          self.__nativeCall(
+            module,
+            method,
+            args,
+            (data) => {
+              resolve(data);
+            },
+            (errorData) => {
+              var error = createErrorFromErrorData(errorData);
+              reject(error);
+            });
         });
       };
     } else {
@@ -338,6 +353,10 @@ class MessageQueue {
     return fn;
   }
 
+  registerCallableModule(name, methods) {
+    this._callableModules[name] = methods;
+  }
+
 }
 
 function moduleHasConstants(moduleArray: Array<Object|Array<>>): boolean {
diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js
index 75d8478bc350..03e5df830bbe 100644
--- a/Libraries/Utilities/PerformanceLogger.js
+++ b/Libraries/Utilities/PerformanceLogger.js
@@ -10,8 +10,9 @@
  */
 'use strict';
 
+var BatchedBridge = require('BatchedBridge');
 
-var performanceNow = require('performanceNow');
+var performanceNow = require('fbjs/lib/performanceNow');
 
 var timespans = {};
 var extras = {};
@@ -65,6 +66,15 @@ var PerformanceLogger = {
       }
       return;
     }
+    if (timespans[key].endTime) {
+      if (__DEV__) {
+        console.log(
+          'PerformanceLogger: Attempting to end a timespan that has already ended ',
+          key
+        );
+      }
+      return;
+    }
 
     timespans[key].endTime = performanceNow();
     timespans[key].totalTime =
@@ -131,4 +141,9 @@ var PerformanceLogger = {
   }
 };
 
+BatchedBridge.registerCallableModule(
+  'PerformanceLogger',
+  PerformanceLogger
+);
+
 module.exports = PerformanceLogger;
diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js
index 940890ccec00..e7ab32ca5685 100644
--- a/Libraries/Utilities/PixelRatio.js
+++ b/Libraries/Utilities/PixelRatio.js
@@ -16,20 +16,6 @@ var Dimensions = require('Dimensions');
 /**
  * PixelRatio class gives access to the device pixel density.
  *
- * There are a few use cases for using PixelRatio:
- *
- * ### Displaying a line that's as thin as the device permits
- *
- * A width of 1 is actually pretty thick on devices with high pixel density
- * (such as iPhone 4+ and many Android devices), we can make one that's
- * thinner using a width of `1 / PixelRatio.get()`.
- * It's a technique that works on all the devices independent of their
- * pixel density.
- *
- * ```
- * style={{ borderWidth: 1 / PixelRatio.get() }}
- * ```
- *
  * ### Fetching a correctly sized image
  *
  * You should get a higher resolution image if you are on a high pixel density
@@ -91,6 +77,17 @@ class PixelRatio {
     return Math.round(layoutSize * PixelRatio.get());
   }
 
+  /**
+   * Rounds a layout size (dp) to the nearest layout size that corresponds to
+   * an integer number of pixels. For example, on a device with a PixelRatio
+   * of 3, `PixelRatio.roundToNearestPixel(8.4) = 8.33`, which corresponds to
+   * exactly (8.33 * 3) = 25 pixels.
+   */
+  static roundToNearestPixel(layoutSize: number): number {
+    var ratio = PixelRatio.get();
+    return Math.round(layoutSize * ratio) / ratio;
+  }
+
   // No-op for iOS, but used on the web. Should not be documented.
   static startDetecting() {}
 }
diff --git a/Libraries/Utilities/Platform.android.js b/Libraries/Utilities/Platform.android.js
index 2d061d4148d0..659d9c873a1c 100644
--- a/Libraries/Utilities/Platform.android.js
+++ b/Libraries/Utilities/Platform.android.js
@@ -12,11 +12,9 @@
 
 'use strict';
 
-var AndroidConstants = require('NativeModules').AndroidConstants;
-
 var Platform = {
   OS: 'android',
-  Version: AndroidConstants.Version,
+  get Version() { return require('NativeModules').AndroidConstants.Version; },
 };
 
 module.exports = Platform;
diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js
index 5b280dd66631..32394684cb9d 100644
--- a/Libraries/Utilities/RCTLog.js
+++ b/Libraries/Utilities/RCTLog.js
@@ -11,7 +11,9 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var BatchedBridge = require('BatchedBridge');
+
+var invariant = require('fbjs/lib/invariant');
 
 var levelsMap = {
   log: 'log',
@@ -39,4 +41,9 @@ class RCTLog {
   }
 }
 
+BatchedBridge.registerCallableModule(
+  'RCTLog',
+  RCTLog
+);
+
 module.exports = RCTLog;
diff --git a/Libraries/Utilities/RCTRenderingPerf.js b/Libraries/Utilities/RCTRenderingPerf.js
index 126c8a032852..3979780ba364 100644
--- a/Libraries/Utilities/RCTRenderingPerf.js
+++ b/Libraries/Utilities/RCTRenderingPerf.js
@@ -13,7 +13,7 @@
 
 var ReactDefaultPerf = require('ReactDefaultPerf');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 type perfModule = {
   start: () => void;
diff --git a/Libraries/Utilities/Systrace.js b/Libraries/Utilities/Systrace.js
new file mode 100644
index 000000000000..0e11e8942bf6
--- /dev/null
+++ b/Libraries/Utilities/Systrace.js
@@ -0,0 +1,205 @@
+/**
+ * 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 Systrace
+ * @flow
+ */
+'use strict';
+
+type RelayProfiler = {
+  attachProfileHandler(
+    name: string,
+    handler: (name: string, state?: any) => () => void
+  ): void,
+
+  attachAggregateHandler(
+    name: string,
+    handler: (name: string, callback: () => void) => void
+  ): void,
+};
+
+var GLOBAL = GLOBAL || this;
+var TRACE_TAG_REACT_APPS = 1 << 17;
+var TRACE_TAG_JSC_CALLS = 1 << 27;
+
+var _enabled = false;
+var _asyncCookie = 0;
+var _ReactPerf = null;
+function ReactPerf() {
+  if (!_ReactPerf) {
+    _ReactPerf = require('ReactPerf');
+  }
+  return _ReactPerf;
+}
+
+var Systrace = {
+  setEnabled(enabled: boolean) {
+    if (_enabled !== enabled) {
+      if (enabled) {
+        global.nativeTraceBeginLegacy && global.nativeTraceBeginLegacy(TRACE_TAG_JSC_CALLS);
+      } else {
+        global.nativeTraceEndLegacy && global.nativeTraceEndLegacy(TRACE_TAG_JSC_CALLS);
+      }
+    }
+    _enabled = enabled;
+
+    ReactPerf().enableMeasure = enabled;
+  },
+
+  /**
+   * beginEvent/endEvent for starting and then ending a profile within the same call stack frame
+  **/
+  beginEvent(profileName?: any) {
+    if (_enabled) {
+      profileName = typeof profileName === 'function' ?
+        profileName() : profileName;
+      global.nativeTraceBeginSection(TRACE_TAG_REACT_APPS, profileName);
+    }
+  },
+
+  endEvent() {
+    if (_enabled) {
+      global.nativeTraceEndSection(TRACE_TAG_REACT_APPS);
+    }
+  },
+
+  /**
+   * beginAsyncEvent/endAsyncEvent for starting and then ending a profile where the end can either
+   * occur on another thread or out of the current stack frame, eg await
+   * the returned cookie variable should be used as input into the endAsyncEvent call to end the profile
+  **/
+  beginAsyncEvent(profileName?: any): any {
+    var cookie = _asyncCookie;
+    if (_enabled) {
+      _asyncCookie++;
+      profileName = typeof profileName === 'function' ?
+        profileName() : profileName;
+      global.nativeTraceBeginAsyncSection(TRACE_TAG_REACT_APPS, profileName, cookie, 0);
+    }
+    return cookie;
+  },
+
+  endAsyncEvent(profileName?: any, cookie?: any) {
+    if (_enabled) {
+      profileName = typeof profileName === 'function' ?
+        profileName() : profileName;
+      global.nativeTraceEndAsyncSection(TRACE_TAG_REACT_APPS, profileName, cookie, 0);
+    }
+  },
+
+  /**
+   * counterEvent registers the value to the profileName on the systrace timeline
+  **/
+  counterEvent(profileName?: any, value?: any) {
+    if (_enabled) {
+      profileName = typeof profileName === 'function' ?
+        profileName() : profileName;
+      global.nativeTraceCounter &&
+        global.nativeTraceCounter(TRACE_TAG_REACT_APPS, profileName, value);
+    }
+  },
+
+  reactPerfMeasure(objName: string, fnName: string, func: any): any {
+    return function (component) {
+      if (!_enabled) {
+        return func.apply(this, arguments);
+      }
+
+      var name = objName === 'ReactCompositeComponent' && this.getName() || '';
+      Systrace.beginEvent(`${objName}.${fnName}(${name})`);
+    var ret = func.apply(this, arguments);
+      Systrace.endEvent();
+      return ret;
+    };
+  },
+
+  swizzleReactPerf() {
+    ReactPerf().injection.injectMeasure(Systrace.reactPerfMeasure);
+  },
+
+  /**
+   * Relay profiles use await calls, so likely occur out of current stack frame
+   * therefore async variant of profiling is used
+  **/
+  attachToRelayProfiler(relayProfiler: RelayProfiler) {
+    relayProfiler.attachProfileHandler('*', (name) => {
+      var cookie = Systrace.beginAsyncEvent(name);
+      return () => {
+        Systrace.endAsyncEvent(name, cookie);
+      };
+    });
+
+    relayProfiler.attachAggregateHandler('*', (name, callback) => {
+      Systrace.beginEvent(name);
+      callback();
+      Systrace.endEvent();
+    });
+  },
+
+  /* This is not called by default due to perf overhead but it's useful
+     if you want to find traces which spend too much time in JSON. */
+  swizzleJSON() {
+    Systrace.measureMethods(JSON, 'JSON', [
+      'parse',
+      'stringify'
+    ]);
+  },
+
+ /**
+  * Measures multiple methods of a class. For example, you can do:
+  * Systrace.measureMethods(JSON, 'JSON', ['parse', 'stringify']);
+  *
+  * @param object
+  * @param objectName
+  * @param methodNames Map from method names to method display names.
+  */
+ measureMethods(object: any, objectName: string, methodNames: Array<string>): void {
+   if (!__DEV__) {
+     return;
+   }
+
+   methodNames.forEach(methodName => {
+     object[methodName] = Systrace.measure(
+       objectName,
+       methodName,
+       object[methodName]
+     );
+   });
+ },
+
+ /**
+  * Returns an profiled version of the input function. For example, you can:
+  * JSON.parse = Systrace.measure('JSON', 'parse', JSON.parse);
+  *
+  * @param objName
+  * @param fnName
+  * @param {function} func
+  * @return {function} replacement function
+  */
+ measure(objName: string, fnName: string, func: any): any {
+   if (!__DEV__) {
+     return func;
+   }
+
+   var profileName = `${objName}.${fnName}`;
+   return function() {
+     if (!_enabled) {
+       return func.apply(this, arguments);
+     }
+
+     Systrace.beginEvent(profileName);
+     var ret = func.apply(this, arguments);
+     Systrace.endEvent();
+     return ret;
+   };
+ },
+};
+
+Systrace.setEnabled(global.__RCTProfileIsProfiling || false);
+
+module.exports = Systrace;
diff --git a/Libraries/Utilities/UIManager.js b/Libraries/Utilities/UIManager.js
new file mode 100644
index 000000000000..efd8ce5b0728
--- /dev/null
+++ b/Libraries/Utilities/UIManager.js
@@ -0,0 +1,84 @@
+/**
+ * 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 UIManager
+ * @flow
+ */
+'use strict';
+
+var UIManager = require('NativeModules').UIManager;
+var findNodeHandle = require('findNodeHandle');
+
+if (!UIManager.setChildren) {
+
+  /**
+   * Index cache (used by setChildren())
+   */
+  UIManager._cachedIndexArray = function(size) {
+    var cachedResult = this._cachedIndexArray._cache[size];
+    if (!cachedResult) {
+      var arr = [];
+      for (var i = 0; i < size; i++) {
+        arr[i] = i;
+      }
+      this._cachedIndexArray._cache[size] = arr;
+      return arr;
+    } else {
+      return cachedResult;
+    }
+  };
+  UIManager._cachedIndexArray._cache = {};
+
+  /**
+   * Fallback setChildren() implementation for Android
+   */
+  UIManager.setChildren = function(containerTag, createdTags) {
+    var indexes = this._cachedIndexArray(createdTags.length);
+    UIManager.manageChildren(containerTag, null, null, createdTags, indexes, null);
+  };
+}
+
+const _takeSnapshot = UIManager.takeSnapshot;
+
+/**
+ * Capture an image of the screen, window or an individual view. The image
+ * will be stored in a temporary file that will only exist for as long as the
+ * app is running.
+ * 
+ * The `view` argument can be the literal string `window` if you want to
+ * capture the entire window, or it can be a reference to a specific
+ * React Native component.
+ *
+ * The `options` argument may include:
+ * - width/height (number) - the width and height of the image to capture.
+ * - format (string) - either 'png' or 'jpeg'. Defaults to 'png'.
+ * - quality (number) - the quality when using jpeg. 0.0 - 1.0 (default).
+ *
+ * Returns a Promise.
+ * @platform ios
+ */
+UIManager.takeSnapshot = async function(
+  view ?: 'window' | ReactElement | number,
+  options ?: {
+    width ?: number;
+    height ?: number;
+    format ?: 'png' | 'jpeg';
+    quality ?: number;
+  },
+) {
+  if (!_takeSnapshot) {
+    console.warn('UIManager.takeSnapshot is not available on this platform');
+    return;
+  }
+  if (typeof view !== 'number' && view !== 'window') {
+    view = findNodeHandle(view) || 'window';
+  }
+  return _takeSnapshot(view, options);
+};
+
+module.exports = UIManager;
diff --git a/Libraries/Utilities/__mocks__/ErrorUtils.js b/Libraries/Utilities/__mocks__/ErrorUtils.js
index 59fbbafbc904..832d27e60072 100644
--- a/Libraries/Utilities/__mocks__/ErrorUtils.js
+++ b/Libraries/Utilities/__mocks__/ErrorUtils.js
@@ -15,6 +15,7 @@ function reportError(error) {
 var ErrorUtils = {
   apply: jest.genMockFunction().mockImplementation(execute),
   applyWithGuard: jest.genMockFunction().mockImplementation(execute),
+  guard: jest.genMockFunction().mockImplementation(callback => callback),
   inGuard: jest.genMockFunction().mockReturnValue(true),
   reportError: jest.genMockFunction().mockImplementation(reportError),
   setGlobalHandler: jest.genMockFunction(),
diff --git a/Libraries/Utilities/__mocks__/PixelRatio.js b/Libraries/Utilities/__mocks__/PixelRatio.js
index bcf04d7fe48a..6e16eddf171f 100644
--- a/Libraries/Utilities/__mocks__/PixelRatio.js
+++ b/Libraries/Utilities/__mocks__/PixelRatio.js
@@ -1,20 +1,24 @@
 /**
  * Copyright 2004-present Facebook. All Rights Reserved.
  */
-'use strict';
-
-var PixelRatio = {
-  startDetecting: function () {
-    // noop for our implementation
-  },
 
-  get: function() {
-    return 2;
-  },
+'use strict';
 
-  getPixelSizeForLayoutSize: function (layoutSize) {
-    return Math.round(layoutSize * PixelRatio.get());
-  }
+const PixelRatio = {
+  get: jest.genMockFunction().mockReturnValue(2),
+  getFontScale: jest.genMockFunction().mockImplementation(
+    () => PixelRatio.get()
+  ),
+  getPixelSizeForLayoutSize: jest.genMockFunction().mockImplementation(
+    layoutSize => Math.round(layoutSize * PixelRatio.get())
+  ),
+  roundToNearestPixel: jest.genMockFunction().mockImplementation(
+    layoutSize => {
+      const ratio = PixelRatio.get();
+      return Math.round(layoutSize * ratio) / ratio;
+    }
+  ),
+  startDetecting: jest.genMockFunction(),
 };
 
 module.exports = PixelRatio;
diff --git a/Libraries/Utilities/__tests__/MatrixMath-test.js b/Libraries/Utilities/__tests__/MatrixMath-test.js
index 4ca108e39251..b8c10ac402e2 100644
--- a/Libraries/Utilities/__tests__/MatrixMath-test.js
+++ b/Libraries/Utilities/__tests__/MatrixMath-test.js
@@ -9,7 +9,7 @@
 'use strict';
 
 jest.dontMock('MatrixMath');
-jest.dontMock('invariant');
+jest.dontMock('fbjs/lib/invariant');
 
 var MatrixMath = require('MatrixMath');
 
@@ -17,6 +17,10 @@ function degreesToRadians(degrees) {
   return degrees * Math.PI / 180;
 }
 
+function convertZeroes(degrees) {
+  return degrees.map(value => value === -0 ? 0 : value);
+}
+
 describe('MatrixMath', () => {
 
   it('decomposes a 4x4 matrix to produce accurate Z-axis angles', () => {
@@ -26,11 +30,11 @@ describe('MatrixMath', () => {
 
     [30, 45, 60, 75, 90, 100, 115, 120, 133, 167].forEach(angle => {
       var mat = MatrixMath.createRotateZ(degreesToRadians(angle));
-      expect(MatrixMath.decomposeMatrix(mat).rotationDegrees)
+      expect(convertZeroes(MatrixMath.decomposeMatrix(mat).rotationDegrees))
         .toEqual([0, 0, angle]);
 
       mat = MatrixMath.createRotateZ(degreesToRadians(-angle));
-      expect(MatrixMath.decomposeMatrix(mat).rotationDegrees)
+      expect(convertZeroes(MatrixMath.decomposeMatrix(mat).rotationDegrees))
         .toEqual([0, 0, -angle]);
     });
 
@@ -51,7 +55,7 @@ describe('MatrixMath', () => {
     // 360 is expressed as 0
     expect(MatrixMath.decomposeMatrix(
       MatrixMath.createRotateZ(degreesToRadians(360))
-    ).rotationDegrees).toEqual([0, 0, 0]);
+    ).rotationDegrees).toEqual([0, 0, -0]);
 
     expect(MatrixMath.decomposeMatrix(
       MatrixMath.createRotateZ(degreesToRadians(33.33333333))
@@ -83,12 +87,12 @@ describe('MatrixMath', () => {
     [30, 45, 60, 75, 90, 100, 110, 120, 133, 167].forEach(angle => {
       mat = MatrixMath.createIdentityMatrix();
       MatrixMath.reuseRotateYCommand(mat, degreesToRadians(angle));
-      expect(MatrixMath.decomposeMatrix(mat).rotationDegrees)
+      expect(convertZeroes(MatrixMath.decomposeMatrix(mat).rotationDegrees))
         .toEqual([0, angle, 0]);
 
       mat = MatrixMath.createIdentityMatrix();
       MatrixMath.reuseRotateYCommand(mat, degreesToRadians(-angle));
-      expect(MatrixMath.decomposeMatrix(mat).rotationDegrees)
+      expect(convertZeroes(MatrixMath.decomposeMatrix(mat).rotationDegrees))
         .toEqual([0, -angle, 0]);
     });
 
@@ -115,7 +119,7 @@ describe('MatrixMath', () => {
     [30, 45, 60, 75, 90, 100, 110, 120, 133, 167].forEach(angle => {
       mat = MatrixMath.createIdentityMatrix();
       MatrixMath.reuseRotateXCommand(mat, degreesToRadians(angle));
-      expect(MatrixMath.decomposeMatrix(mat).rotationDegrees)
+      expect(convertZeroes(MatrixMath.decomposeMatrix(mat).rotationDegrees))
         .toEqual([angle, 0, 0]);
     });
 
diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js
index dc0bab73b079..14c50e746d7d 100644
--- a/Libraries/Utilities/__tests__/MessageQueue-test.js
+++ b/Libraries/Utilities/__tests__/MessageQueue-test.js
@@ -8,12 +8,8 @@
  */
 'use strict';
 
-jest.setMock('ReactUpdates', {
-  batchedUpdates: fn => fn()
-});
-
-jest.dontMock('MessageQueue');
-jest.dontMock('keyMirror');
+jest.dontMock('MessageQueue')
+  .dontMock('fbjs/lib/keyMirror');
 var MessageQueue = require('MessageQueue');
 
 let MODULE_IDS = 0;
@@ -24,8 +20,6 @@ let TestModule = {
   testHook1(){}, testHook2(){},
 };
 
-let customRequire = (moduleName) => TestModule;
-
 let assertQueue = (flushedQueue, index, moduleID, methodID, params) => {
   expect(flushedQueue[MODULE_IDS][index]).toEqual(moduleID);
   expect(flushedQueue[METHOD_IDS][index]).toEqual(methodID);
@@ -39,10 +33,11 @@ describe('MessageQueue', () => {
   beforeEach(() => {
     queue = new MessageQueue(
       remoteModulesConfig,
-      localModulesConfig,
-      customRequire,
+      localModulesConfig
     );
 
+    queue.registerCallableModule('one', TestModule);
+
     TestModule.testHook1 = jasmine.createSpy();
     TestModule.testHook2 = jasmine.createSpy();
   });
@@ -54,15 +49,15 @@ describe('MessageQueue', () => {
   });
 
   it('should call a local function with id', () => {
-    expect(TestModule.testHook1.callCount).toEqual(0);
+    expect(TestModule.testHook1.calls.count()).toEqual(0);
     queue.__callFunction(0, 0, [1]);
-    expect(TestModule.testHook1.callCount).toEqual(1);
+    expect(TestModule.testHook1.calls.count()).toEqual(1);
   });
 
   it('should call a local function with the function name', () => {
-    expect(TestModule.testHook2.callCount).toEqual(0);
+    expect(TestModule.testHook2.calls.count()).toEqual(0);
     queue.__callFunction('one', 'testHook2', [2]);
-    expect(TestModule.testHook2.callCount).toEqual(1);
+    expect(TestModule.testHook2.calls.count()).toEqual(1);
   });
 
   it('should generate native modules', () => {
@@ -77,7 +72,7 @@ describe('MessageQueue', () => {
     assertQueue(flushedQueue, 0, 0, 1, ['foo', 0, 1]);
   });
 
-  it('should call the stored callback', (done) => {
+  it('should call the stored callback', () => {
     var done = false;
     queue.RemoteModules.one.remoteMethod1(() => { done = true; });
     queue.__invokeCallback(1);
diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js
index fdb9653e8f01..ff51699b033d 100644
--- a/Libraries/Utilities/buildStyleInterpolator.js
+++ b/Libraries/Utilities/buildStyleInterpolator.js
@@ -9,7 +9,7 @@
  */
 /* eslint-disable global-strict */
 
-var keyOf = require('keyOf');
+var keyOf = require('fbjs/lib/keyOf');
 
 var X_DIM = keyOf({x: null});
 var Y_DIM = keyOf({y: null});
@@ -102,7 +102,7 @@ var ARGUMENT_NAMES_RE = /([^\s,]+)/g;
  *
  *  inline(inlineMe, ['hi', 'bye']);  // "hi = bye + bye;"
  *
- * @param {function} func Any simple function whos arguments can be replaced via a regex.
+ * @param {function} func Any simple function who's arguments can be replaced via a regex.
  * @param {array<string>} replaceWithArgs Corresponding names of variables
  * within an environment, to replace `func` args with.
  * @return {string} Resulting function body string.
@@ -553,8 +553,15 @@ var createFunctionString = function(anims) {
  * object and returns a boolean describing if any update was actually applied.
  */
 var buildStyleInterpolator = function(anims) {
-  return Function(createFunctionString(anims))();
+  // Defer compiling this method until we really need it.
+  var interpolator = null;
+  function lazyStyleInterpolator(result, value) {
+    if (interpolator === null) {
+      interpolator = Function(createFunctionString(anims))();
+    }
+    return interpolator(result, value);
+  }
+  return lazyStyleInterpolator;
 };
 
-
 module.exports = buildStyleInterpolator;
diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js
index cd0d5757a96d..b5fe14a1b950 100644
--- a/Libraries/Utilities/createStrictShapeTypeChecker.js
+++ b/Libraries/Utilities/createStrictShapeTypeChecker.js
@@ -13,7 +13,7 @@
 
 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var merge = require('merge');
 
 function createStrictShapeTypeChecker(
@@ -24,7 +24,7 @@ function createStrictShapeTypeChecker(
       if (isRequired) {
         invariant(
           false,
-          `Required object \`${propName}\` was not specified in `+
+          `Required object \`${propName}\` was not specified in ` +
           `\`${componentName}\`.`
         );
       }
diff --git a/Libraries/Utilities/cssVar.js b/Libraries/Utilities/cssVar.js
index 0ab64e7f9ca5..094689ae25f1 100644
--- a/Libraries/Utilities/cssVar.js
+++ b/Libraries/Utilities/cssVar.js
@@ -6,7 +6,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var CSSVarConfig = require('CSSVarConfig');
 
 var cssVar = function(/*string*/ key) /*string*/ {
diff --git a/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js b/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
index 9263116ed707..b1dac7777dea 100644
--- a/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
+++ b/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
@@ -42,11 +42,17 @@ function deepFreezeAndThrowOnMutationInDev(object: Object) {
       if (object.hasOwnProperty(key)) {
         object.__defineGetter__(key, identity.bind(null, object[key]));
         object.__defineSetter__(key, throwOnImmutableMutation.bind(null, key));
-        deepFreezeAndThrowOnMutationInDev(object[key]);
       }
     }
+
     Object.freeze(object);
     Object.seal(object);
+
+    for (var key in object) {
+      if (object.hasOwnProperty(key)) {
+        deepFreezeAndThrowOnMutationInDev(object[key]);
+      }
+    }
   }
 }
 
diff --git a/Libraries/Utilities/deprecatedCallback.js b/Libraries/Utilities/deprecatedCallback.js
new file mode 100644
index 000000000000..3beefdc483a8
--- /dev/null
+++ b/Libraries/Utilities/deprecatedCallback.js
@@ -0,0 +1,54 @@
+/**
+ * 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.
+ *
+ * Helper for deprecated callback pattern
+ *
+ * @providesModule deprecatedCallback
+ * @flow
+ */
+
+'use strict';
+
+module.exports = function(promise: Promise, callbacks: Array<Function>, type: string, warning: string): Promise {
+  if (callbacks.length === 0) {
+    return promise;
+  }
+
+  let success, error, callback;
+
+  console.warn(warning);
+
+  switch (type) {
+  case 'success-first': // handles func(success, error), func(success)
+    [ success, error ] = callbacks;
+    return promise.then(
+      res => success(res),
+      err => error && error(err)
+    );
+  case 'error-first': // handles func(error, success)
+    [ error, success ] = callbacks;
+    return promise.then(
+      res => success(res),
+      err => error(err)
+    );
+  case 'single-callback-value-first': // handles func(callback(value, err))
+    [ callback ] = callbacks;
+    return promise.then(
+      res => callback(res),
+      err => callback(null, err)
+    );
+  case 'node': // handles func(callback(err, value))
+    [ callback ] = callbacks;
+    return promise.then(
+      res => callback(null, res),
+      err => callback(err)
+    );
+  default:
+    throw new Error(`Type of callbacks not specified. Must be one of 'success-first', 'error-first', 'single-callback-value-first', or 'node'`);
+  }
+};
diff --git a/Libraries/Utilities/deprecatedPropType.js b/Libraries/Utilities/deprecatedPropType.js
new file mode 100644
index 000000000000..63bc61020b4a
--- /dev/null
+++ b/Libraries/Utilities/deprecatedPropType.js
@@ -0,0 +1,33 @@
+/**
+ * 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 deprecatedPropType
+ * @flow
+ */
+'use strict';
+
+const UIManager = require('UIManager');
+
+/**
+ * Adds a deprecation warning when the prop is used.
+ */
+function deprecatedPropType(
+  propType: ReactPropsCheckType,
+  explanation: string
+): ReactPropsCheckType {
+  return function validate(props, propName, componentName) {
+    // Don't warn for native components.
+    if (!UIManager[componentName] && props[propName] !== undefined) {
+      console.warn(`\`${propName}\` supplied to \`${componentName}\` has been deprecated. ${explanation}`);
+    }
+
+    return propType(props, propName, componentName);
+  };
+}
+
+module.exports = deprecatedPropType;
diff --git a/Libraries/Utilities/mergeIntoFast.js b/Libraries/Utilities/mergeIntoFast.js
index 1a97d07b7b17..b7160875a453 100644
--- a/Libraries/Utilities/mergeIntoFast.js
+++ b/Libraries/Utilities/mergeIntoFast.js
@@ -13,7 +13,7 @@
 
 /**
  * Faster version of `mergeInto` that doesn't check its arguments and
- * also copies over prototye inherited properties.
+ * also copies over prototype inherited properties.
  *
  * @param {object} one Object to assign to.
  * @param {object} two Object to assign from.
diff --git a/Libraries/Utilities/nativeModulePrefixNormalizer.js b/Libraries/Utilities/nativeModulePrefixNormalizer.js
deleted file mode 100644
index 151417bc5acf..000000000000
--- a/Libraries/Utilities/nativeModulePrefixNormalizer.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * 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 nativeModulePrefixNormalizer
- * @flow
- */
-'use strict';
-
-// Dirty hack to support old (RK) and new (RCT) native module name conventions
-function nativeModulePrefixNormalizer(
-  modules: {[key: string]: any}
-): void {
-  Object.keys(modules).forEach((moduleName) => {
-    var strippedName = moduleName.replace(/^(RCT|RK)/, '');
-    if (modules['RCT' + strippedName] && modules['RK' + strippedName]) {
-      throw new Error(
-        'Module cannot be registered as both RCT and RK: ' + moduleName
-      );
-    }
-    if (strippedName !== moduleName) {
-      modules[strippedName] = modules[moduleName];
-      delete modules[moduleName];
-    }
-  });
-}
-
-module.exports = nativeModulePrefixNormalizer;
diff --git a/Libraries/Utilities/truncate.js b/Libraries/Utilities/truncate.js
index a15da571bfde..462a023d130a 100644
--- a/Libraries/Utilities/truncate.js
+++ b/Libraries/Utilities/truncate.js
@@ -25,7 +25,7 @@ var defaultOptions = {
   elipsis: '...',
 };
 
-// maxChars (including elipsis)
+// maxChars (including ellipsis)
 var truncate = function(
   str: ?string,
   maxChars: number,
diff --git a/Libraries/Vibration/Vibration.js b/Libraries/Vibration/Vibration.js
new file mode 100644
index 000000000000..9258eadaa2a7
--- /dev/null
+++ b/Libraries/Vibration/Vibration.js
@@ -0,0 +1,39 @@
+/**
+ * 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 Vibration
+ * @flow
+ */
+'use strict';
+
+var RCTVibration = require('NativeModules').Vibration;
+var Platform = require('Platform');
+
+/**
+ * The Vibration API is exposed at `Vibration.vibrate()`.
+ * The vibration is asynchronous so this method will return immediately.
+ *
+ * There will be no effect on devices that do not support Vibration, eg. the simulator.
+ *
+ * Note for android
+ * add `<uses-permission android:name="android.permission.VIBRATE"/>` to `AndroidManifest.xml`
+ *
+ * Vibration patterns are currently unsupported.
+ */
+
+var Vibration = {
+  vibrate: function(duration: number = 400) {
+    if (Platform.OS === 'android') {
+      RCTVibration.vibrate(duration);
+    } else {
+      RCTVibration.vibrate();
+    }
+  }
+};
+
+module.exports = Vibration;
diff --git a/Libraries/Vibration/VibrationIOS.android.js b/Libraries/Vibration/VibrationIOS.android.js
index f52be6b7501b..37cdf859c1eb 100644
--- a/Libraries/Vibration/VibrationIOS.android.js
+++ b/Libraries/Vibration/VibrationIOS.android.js
@@ -12,7 +12,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var VibrationIOS = {
   vibrate: function() {
diff --git a/Libraries/Vibration/VibrationIOS.ios.js b/Libraries/Vibration/VibrationIOS.ios.js
index 2a1dc701c173..56da5a815b12 100644
--- a/Libraries/Vibration/VibrationIOS.ios.js
+++ b/Libraries/Vibration/VibrationIOS.ios.js
@@ -13,9 +13,11 @@
 
 var RCTVibration = require('NativeModules').Vibration;
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 /**
+ * NOTE: `VibrationIOS` is being deprecated. Use `Vibration` instead.
+ *
  * The Vibration API is exposed at `VibrationIOS.vibrate()`. On iOS, calling this
  * function will trigger a one second vibration. The vibration is asynchronous
  * so this method will return immediately.
@@ -27,6 +29,9 @@ var invariant = require('invariant');
  */
 
 var VibrationIOS = {
+  /**
+   * @deprecated
+   */
   vibrate: function() {
     invariant(
       arguments[0] === undefined,
diff --git a/Libraries/WebSocket/RCTSRWebSocket.h b/Libraries/WebSocket/RCTSRWebSocket.h
index 0c209c21cebb..12753043470e 100644
--- a/Libraries/WebSocket/RCTSRWebSocket.h
+++ b/Libraries/WebSocket/RCTSRWebSocket.h
@@ -60,10 +60,12 @@ extern NSString *const RCTSRHTTPResponseErrorKey;
 @property (nonatomic, readonly, copy) NSString *protocol;
 
 // Protocols should be an array of strings that turn into Sec-WebSocket-Protocol.
-- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols NS_DESIGNATED_INITIALIZER;
+// options can contain a custom "origin" NSString
+- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols options:(NSDictionary<NSString *, NSString *> *)options NS_DESIGNATED_INITIALIZER;
 - (instancetype)initWithURLRequest:(NSURLRequest *)request;
 
 // Some helper constructors.
+- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols options:(NSDictionary<NSString *, NSString *> *)options;
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols;
 - (instancetype)initWithURL:(NSURL *)url;
 
diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m
index 5f46bfd757ad..bf67c3b14e37 100644
--- a/Libraries/WebSocket/RCTSRWebSocket.m
+++ b/Libraries/WebSocket/RCTSRWebSocket.m
@@ -234,6 +234,7 @@ @implementation RCTSRWebSocket
   __strong RCTSRWebSocket *_selfRetain;
 
   NSArray<NSString *> *_requestedProtocols;
+  NSDictionary<NSString *, NSString *> *_requestedOptions;
   RCTSRIOConsumerPool *_consumerPool;
 }
 
@@ -244,7 +245,7 @@ + (void)initialize;
   CRLFCRLF = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4];
 }
 
-- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols;
+- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols options:(NSDictionary<NSString *, NSString *> *)options
 {
   RCTAssertParam(request);
 
@@ -253,6 +254,7 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NS
     _urlRequest = request;
 
     _requestedProtocols = [protocols copy];
+    _requestedOptions = [options copy];
 
     [self _RCTSR_commonInit];
   }
@@ -263,18 +265,39 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NS
 
 - (instancetype)initWithURLRequest:(NSURLRequest *)request;
 {
-  return [self initWithURLRequest:request protocols:nil];
+  return [self initWithURLRequest:request protocols:nil options: nil];
 }
 
 - (instancetype)initWithURL:(NSURL *)URL;
 {
-  return [self initWithURL:URL protocols:nil];
+  return [self initWithURL:URL protocols:nil options:nil];
 }
 
 - (instancetype)initWithURL:(NSURL *)URL protocols:(NSArray<NSString *> *)protocols;
 {
-  NSURLRequest *request = URL ? [NSURLRequest requestWithURL:URL] : nil;
-  return [self initWithURLRequest:request protocols:protocols];
+  return [self initWithURL:URL protocols:protocols options:nil];
+}
+
+- (instancetype)initWithURL:(NSURL *)URL protocols:(NSArray<NSString *> *)protocols options:(NSDictionary<NSString *, id> *)options
+{
+  NSMutableURLRequest *request;
+  if (URL) {
+    // Build a mutable request so we can fill the cookie header.
+    request = [NSMutableURLRequest requestWithURL:URL];
+
+    // We load cookies from sharedHTTPCookieStorage (shared with XHR and
+    // fetch). To get HTTPS-only cookies for wss URLs, replace wss with https
+    // in the URL.
+    NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:true];
+    if ([components.scheme isEqualToString:@"wss"]) {
+      components.scheme = @"https";
+    }
+
+    // Load and set the cookie header.
+    NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:components.URL];
+    [request setAllHTTPHeaderFields:[NSHTTPCookie requestHeaderFieldsWithCookies:cookies]];
+  }
+  return [self initWithURLRequest:request protocols:protocols options:options];
 }
 
 - (void)_RCTSR_commonInit;
@@ -465,12 +488,12 @@ - (void)didConnect
   CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey);
   CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]);
 
-  CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.RCTSR_origin);
-
   if (_requestedProtocols) {
     CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]);
   }
 
+  CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)(_requestedOptions[@"origin"] ?: _url.RCTSR_origin));
+
   [_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
     CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
   }];
diff --git a/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj b/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
index 62dbee6a7606..b1a875706918 100644
--- a/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
+++ b/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
@@ -10,6 +10,7 @@
 		1338BBE01B04ACC80064A9C9 /* RCTSRWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */; };
 		1338BBE11B04ACC80064A9C9 /* RCTWebSocketExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */; };
 		3C86DF7C1ADF695F0047B81A /* RCTWebSocketModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */; };
+		3DB9106F1C74B1ED00838BBE /* RCTWebSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
@@ -20,6 +21,8 @@
 		3C86DF461ADF2C930047B81A /* libRCTWebSocket.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWebSocket.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		3C86DF7A1ADF695F0047B81A /* RCTWebSocketModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketModule.h; sourceTree = "<group>"; };
 		3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketModule.m; sourceTree = "<group>"; tabWidth = 2; };
+		3DB9106D1C74B1ED00838BBE /* RCTWebSocketManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketManager.h; sourceTree = "<group>"; };
+		3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebSocketManager.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -40,6 +43,8 @@
 				1338BBDD1B04ACC80064A9C9 /* RCTSRWebSocket.m */,
 				1338BBDE1B04ACC80064A9C9 /* RCTWebSocketExecutor.h */,
 				1338BBDF1B04ACC80064A9C9 /* RCTWebSocketExecutor.m */,
+				3DB9106D1C74B1ED00838BBE /* RCTWebSocketManager.h */,
+				3DB9106E1C74B1ED00838BBE /* RCTWebSocketManager.m */,
 				3C86DF7A1ADF695F0047B81A /* RCTWebSocketModule.h */,
 				3C86DF7B1ADF695F0047B81A /* RCTWebSocketModule.m */,
 				3C86DF471ADF2C930047B81A /* Products */,
@@ -114,6 +119,7 @@
 				1338BBE01B04ACC80064A9C9 /* RCTSRWebSocket.m in Sources */,
 				3C86DF7C1ADF695F0047B81A /* RCTWebSocketModule.m in Sources */,
 				1338BBE11B04ACC80064A9C9 /* RCTWebSocketExecutor.m in Sources */,
+				3DB9106F1C74B1ED00838BBE /* RCTWebSocketManager.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.h b/Libraries/WebSocket/RCTWebSocketExecutor.h
index 2c17541f1e8c..9993cbc5a306 100644
--- a/Libraries/WebSocket/RCTWebSocketExecutor.h
+++ b/Libraries/WebSocket/RCTWebSocketExecutor.h
@@ -15,7 +15,7 @@
 
 @interface RCTWebSocketExecutor : NSObject <RCTJavaScriptExecutor>
 
-- (instancetype)initWithURL:(NSURL *)URL NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithURL:(NSURL *)URL;
 
 @end
 
diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m
index 41ac0baf29f0..de26e8a91857 100644
--- a/Libraries/WebSocket/RCTWebSocketExecutor.m
+++ b/Libraries/WebSocket/RCTWebSocketExecutor.m
@@ -36,19 +36,11 @@ @implementation RCTWebSocketExecutor
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
-{
-  NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
-  NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"] ?: 8081;
-  NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy", port];
-  return [self initWithURL:[RCTConvert NSURL:URLString]];
-}
-
 - (instancetype)initWithURL:(NSURL *)URL
 {
   RCTAssertParam(URL);
 
-  if ((self = [super init])) {
+  if ((self = [self init])) {
     _url = URL;
   }
   return self;
@@ -56,6 +48,13 @@ - (instancetype)initWithURL:(NSURL *)URL
 
 - (void)setUp
 {
+  if (!_url) {
+    NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
+    NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"] ?: 8081;
+    NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy?role=client", port];
+    _url = [RCTConvert NSURL:URLString];
+  }
+
   _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL);
   _socket = [[RCTSRWebSocket alloc] initWithURL:_url];
   _socket.delegate = self;
@@ -67,9 +66,9 @@ - (void)setUp
   [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:startDevToolsURL] delegate:nil];
 
   if (![self connectToProxy]) {
-    RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If \
-                you are running on the device, check if you have the right IP \
-                address in `RCTWebSocketExecutor.m`.", _url);
+    RCTLogError(@"Connection to %@ timed out. Are you running node proxy? If "
+                 "you are running on the device, check if you have the right IP "
+                 "address in `RCTWebSocketExecutor.m`.", _url);
     [self invalidate];
     return;
   }
@@ -81,8 +80,9 @@ - (void)setUp
     retries--;
   }
   if (!runtimeIsReady) {
-    RCTLogError(@"Runtime is not ready. Make sure Chrome is running and not "
-                "paused on a breakpoint or exception and try reloading again.");
+    RCTLogError(@"Runtime is not ready for debugging.\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.");
     [self invalidate];
     return;
   }
@@ -126,7 +126,11 @@ - (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
 
 - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
 {
-  RCTLogError(@"WebSocket connection failed with error %@", error);
+  dispatch_semaphore_signal(_socketOpenSemaphore);
+  dispatch_async(dispatch_get_main_queue(), ^{
+    // Give the setUp method an opportunity to report an error first
+    RCTLogError(@"WebSocket connection failed with error %@", error);
+  });
 }
 
 - (void)sendMessage:(NSDictionary<NSString *, id> *)message waitForReply:(RCTWSMessageCallback)callback
@@ -136,7 +140,7 @@ - (void)sendMessage:(NSDictionary<NSString *, id> *)message waitForReply:(RCTWSM
   dispatch_async(_jsQueue, ^{
     if (!self.valid) {
       NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
-        NSLocalizedDescriptionKey: @"socket closed"
+        NSLocalizedDescriptionKey: @"Runtime is not ready for debugging. Make sure Packager server is running."
       }];
       callback(error, nil);
       return;
@@ -162,13 +166,31 @@ - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)URL onCompl
   }];
 }
 
-- (void)executeJSCall:(NSString *)name method:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
+- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
+{
+  [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete];
+}
+
+- (void)callFunctionOnModule:(NSString *)module
+                      method:(NSString *)method
+                   arguments:(NSArray *)args
+                    callback:(RCTJavaScriptCallback)onComplete
+{
+  [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
+}
+
+- (void)invokeCallbackID:(NSNumber *)cbID
+               arguments:(NSArray *)args
+                callback:(RCTJavaScriptCallback)onComplete
+{
+  [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete];
+}
+
+- (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(RCTJavaScriptCallback)onComplete
 {
   RCTAssert(onComplete != nil, @"callback was missing for exec JS call");
   NSDictionary<NSString *, id> *message = @{
-    @"method": @"executeJSCall",
-    @"moduleName": name,
-    @"moduleMethod": method,
+    @"method": method,
     @"arguments": arguments
   };
   [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary<NSString *, id> *reply) {
@@ -193,11 +215,7 @@ - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)object
 
 - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
 {
-  if ([NSThread isMainThread]) {
-    block();
-  } else {
-    dispatch_async(dispatch_get_main_queue(), block);
-  }
+  RCTExecuteOnMainThread(block, NO);
 }
 
 - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
diff --git a/Libraries/WebSocket/RCTWebSocketManager.h b/Libraries/WebSocket/RCTWebSocketManager.h
new file mode 100644
index 000000000000..debc3f7676f3
--- /dev/null
+++ b/Libraries/WebSocket/RCTWebSocketManager.h
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+#import "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+#import "RCTWebSocketProxy.h"
+
+@interface RCTWebSocketManager : NSObject <RCTWebSocketProxy>
+@end
+
+#endif
diff --git a/Libraries/WebSocket/RCTWebSocketManager.m b/Libraries/WebSocket/RCTWebSocketManager.m
new file mode 100644
index 000000000000..7288dda09765
--- /dev/null
+++ b/Libraries/WebSocket/RCTWebSocketManager.m
@@ -0,0 +1,143 @@
+/**
+ * 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.
+ */
+
+#import "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+#import "RCTWebSocketManager.h"
+
+#import "RCTConvert.h"
+#import "RCTLog.h"
+#import "RCTUtils.h"
+#import "RCTSRWebSocket.h"
+
+#pragma mark - RCTWebSocketObserver
+
+@interface RCTWebSocketObserver : NSObject <RCTSRWebSocketDelegate>
+
+@property (nonatomic, strong) RCTSRWebSocket *socket;
+@property (nonatomic, weak) id<RCTWebSocketProxyDelegate> delegate;
+@property (nonatomic, strong) dispatch_semaphore_t socketOpenSemaphore;
+
+- (instancetype)initWithURL:(NSURL *)url delegate:(id<RCTWebSocketProxyDelegate>)delegate;
+
+@end
+
+@implementation RCTWebSocketObserver
+
+- (instancetype)initWithURL:(NSURL *)url delegate:(id<RCTWebSocketProxyDelegate>)delegate
+{
+  if ((self = [self init])) {
+    _socket = [[RCTSRWebSocket alloc] initWithURL:url];
+    _socket.delegate = self;
+
+    _delegate = delegate;
+}
+  return self;
+}
+
+- (void)start
+{
+  _socketOpenSemaphore = dispatch_semaphore_create(0);
+  [_socket open];
+  dispatch_semaphore_wait(_socketOpenSemaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2));
+}
+
+- (void)stop
+{
+  _socket.delegate = nil;
+  [_socket closeWithCode:1000 reason:@"Invalidated"];
+  _socket = nil;
+}
+
+- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
+{
+  if (_delegate) {
+    NSError *error = nil;
+    NSDictionary<NSString *, id> *msg = RCTJSONParse(message, &error);
+
+    if (!error) {
+      [_delegate socketProxy:[RCTWebSocketManager sharedInstance] didReceiveMessage:msg];
+    } else {
+      RCTLogError(@"WebSocketManager failed to parse message with error %@\n<message>\n%@\n</message>", error, message);
+    }
+  }
+}
+
+- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket
+{
+  dispatch_semaphore_signal(_socketOpenSemaphore);
+}
+
+- (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
+{
+  dispatch_semaphore_signal(_socketOpenSemaphore);
+  dispatch_async(dispatch_get_main_queue(), ^{
+    // Give the setUp method an opportunity to report an error first
+    RCTLogError(@"WebSocket connection failed with error %@", error);
+  });
+}
+
+@end
+
+#pragma mark - RCTWebSocketManager
+
+@interface RCTWebSocketManager()
+
+@property (nonatomic, strong) NSMutableDictionary *sockets;
+@property (nonatomic, strong) dispatch_queue_t queue;
+
+@end
+
+@implementation RCTWebSocketManager
+
++ (instancetype)sharedInstance
+{
+  static RCTWebSocketManager *sharedInstance = nil;
+  static dispatch_once_t onceToken;
+
+  dispatch_once(&onceToken, ^{
+    sharedInstance = [self new];
+  });
+
+  return sharedInstance;
+}
+
+- (void)setDelegate:(id<RCTWebSocketProxyDelegate>)delegate forURL:(NSURL *)url
+{
+  NSString *key = [url absoluteString];
+  RCTWebSocketObserver *observer = _sockets[key];
+
+  if (observer) {
+    if (!delegate) {
+      [observer stop];
+      [_sockets removeObjectForKey:key];
+    } else {
+      observer.delegate = delegate;
+    }
+  } else {
+    RCTWebSocketObserver *newObserver = [[RCTWebSocketObserver alloc] initWithURL:url delegate:delegate];
+    [newObserver start];
+    _sockets[key] = newObserver;
+  }
+}
+
+- (instancetype)init
+{
+  if ((self = [super init])) {
+    _sockets = [NSMutableDictionary new];
+    _queue = dispatch_queue_create("com.facebook.React.WebSocketManager", DISPATCH_QUEUE_SERIAL);
+  }
+  return self;
+}
+
+@end
+
+#endif
diff --git a/Libraries/WebSocket/RCTWebSocketModule.h b/Libraries/WebSocket/RCTWebSocketModule.h
index ee176a9f246d..082122e00248 100644
--- a/Libraries/WebSocket/RCTWebSocketModule.h
+++ b/Libraries/WebSocket/RCTWebSocketModule.h
@@ -8,7 +8,8 @@
  */
 
 #import "RCTBridgeModule.h"
+#import "RCTSRWebSocket.h"
 
-@interface RCTWebSocketModule : NSObject <RCTBridgeModule>
+@interface RCTWebSocketModule : NSObject <RCTBridgeModule, RCTSRWebSocketDelegate>
 
 @end
diff --git a/Libraries/WebSocket/RCTWebSocketModule.m b/Libraries/WebSocket/RCTWebSocketModule.m
index df706f0d6f1e..6e057f78b148 100644
--- a/Libraries/WebSocket/RCTWebSocketModule.m
+++ b/Libraries/WebSocket/RCTWebSocketModule.m
@@ -11,7 +11,6 @@
 
 #import "RCTBridge.h"
 #import "RCTEventDispatcher.h"
-#import "RCTSRWebSocket.h"
 #import "RCTUtils.h"
 
 @implementation RCTSRWebSocket (React)
@@ -28,10 +27,6 @@ - (void)setReactTag:(NSNumber *)reactTag
 
 @end
 
-@interface RCTWebSocketModule () <RCTSRWebSocketDelegate>
-
-@end
-
 @implementation RCTWebSocketModule
 {
     NSMutableDictionary<NSNumber *, RCTSRWebSocket *> *_sockets;
@@ -41,14 +36,6 @@ @implementation RCTWebSocketModule
 
 @synthesize bridge = _bridge;
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _sockets = [NSMutableDictionary new];
-  }
-  return self;
-}
-
 - (void)dealloc
 {
   for (RCTSRWebSocket *socket in _sockets.allValues) {
@@ -57,11 +44,14 @@ - (void)dealloc
   }
 }
 
-RCT_EXPORT_METHOD(connect:(NSURL *)URL socketID:(nonnull NSNumber *)socketID)
+RCT_EXPORT_METHOD(connect:(NSURL *)URL protocols:(NSArray *)protocols options:(NSDictionary *)options socketID:(nonnull NSNumber *)socketID)
 {
-  RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURL:URL];
+  RCTSRWebSocket *webSocket = [[RCTSRWebSocket alloc] initWithURL:URL protocols:protocols options:options];
   webSocket.delegate = self;
   webSocket.reactTag = socketID;
+  if (!_sockets) {
+    _sockets = [NSMutableDictionary new];
+  }
   _sockets[socketID] = webSocket;
   [webSocket open];
 }
@@ -81,8 +71,10 @@ - (void)dealloc
 
 - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
 {
+  BOOL binary = [message isKindOfClass:[NSData class]];
   [_bridge.eventDispatcher sendDeviceEventWithName:@"websocketMessage" body:@{
-    @"data": message,
+    @"data": binary ? [message base64EncodedStringWithOptions:0] : message,
+    @"type": binary ? @"binary" : @"text",
     @"id": webSocket.reactTag
   }];
 }
diff --git a/Libraries/WebSocket/WebSocket.js b/Libraries/WebSocket/WebSocket.js
index 3ce40bc67728..3814b4e555ce 100644
--- a/Libraries/WebSocket/WebSocket.js
+++ b/Libraries/WebSocket/WebSocket.js
@@ -7,6 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule WebSocket
+ * @flow
  */
 'use strict';
 
@@ -17,6 +18,8 @@ var Platform = require('Platform');
 var WebSocketBase = require('WebSocketBase');
 var WebSocketEvent = require('WebSocketEvent');
 
+var base64 = require('base64-js');
+
 var WebSocketId = 0;
 var CLOSE_NORMAL = 1000;
 
@@ -24,15 +27,16 @@ var CLOSE_NORMAL = 1000;
  * Browser-compatible WebSockets implementation.
  *
  * See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
+ * See https://github.com/websockets/ws
  */
 class WebSocket extends WebSocketBase {
   _socketId: number;
   _subs: any;
 
-  connectToSocketImpl(url: string): void {
+  connectToSocketImpl(url: string, protocols: ?Array<string>, options: ?{origin?: string}): void {
     this._socketId = WebSocketId++;
 
-    RCTWebSocketModule.connect(url, this._socketId);
+    RCTWebSocketModule.connect(url, protocols, options, this._socketId);
 
     this._registerEvents(this._socketId);
   }
@@ -79,7 +83,7 @@ class WebSocket extends WebSocketBase {
           return;
         }
         var event = new WebSocketEvent('message', {
-          data: ev.data
+          data: (ev.type === 'binary') ? base64.toByteArray(ev.data).buffer : ev.data
         });
         this.onmessage && this.onmessage(event);
         this.dispatchEvent(event);
diff --git a/Libraries/WebSocket/WebSocketBase.js b/Libraries/WebSocket/WebSocketBase.js
index ef52352d4122..843ae403540c 100644
--- a/Libraries/WebSocket/WebSocketBase.js
+++ b/Libraries/WebSocket/WebSocketBase.js
@@ -7,11 +7,17 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule WebSocketBase
+ * @flow
  */
 'use strict';
 
 var EventTarget = require('event-target-shim');
 
+const CONNECTING = 0;
+const OPEN = 1;
+const CLOSING = 2;
+const CLOSED = 3;
+
 /**
  * Shared base for platform-specific WebSocket implementations.
  */
@@ -33,19 +39,23 @@ class WebSocketBase extends EventTarget {
   readyState: number;
   url: ?string;
 
-  constructor(url: string, protocols: ?any) {
+  constructor(url: string, protocols: ?string | ?Array<string>, options: ?{origin?: string}) {
     super();
-    this.CONNECTING = 0;
-    this.OPEN = 1;
-    this.CLOSING = 2;
-    this.CLOSED = 3;
+    this.CONNECTING = CONNECTING;
+    this.OPEN = OPEN;
+    this.CLOSING = CLOSING;
+    this.CLOSED = CLOSED;
+
+    if (typeof protocols === 'string') {
+      protocols = [protocols];
+    }
 
-    if (!protocols) {
-      protocols = [];
+    if (!Array.isArray(protocols)) {
+      protocols = null;
     }
 
     this.readyState = this.CONNECTING;
-    this.connectToSocketImpl(url);
+    this.connectToSocketImpl(url, protocols, options);
   }
 
   close(): void {
@@ -80,7 +90,7 @@ class WebSocketBase extends EventTarget {
     throw new Error('Subclass must define closeConnectionImpl method');
   }
 
-  connectToSocketImpl(): void {
+  connectToSocketImpl(url: string, protocols: ?Array<string>, options: ?{origin?: string}): void {
     throw new Error('Subclass must define connectToSocketImpl method');
   }
 
@@ -88,7 +98,7 @@ class WebSocketBase extends EventTarget {
     throw new Error('Subclass must define cancelConnectionImpl method');
   }
 
-  sendStringImpl(): void {
+  sendStringImpl(message: string): void {
     throw new Error('Subclass must define sendStringImpl method');
   }
 
@@ -97,9 +107,9 @@ class WebSocketBase extends EventTarget {
   }
 }
 
-WebSocketBase.CONNECTING = 0;
-WebSocketBase.OPEN = 1;
-WebSocketBase.CLOSING = 2;
-WebSocketBase.CLOSED = 3;
+WebSocketBase.CONNECTING = CONNECTING;
+WebSocketBase.OPEN = OPEN;
+WebSocketBase.CLOSING = CLOSING;
+WebSocketBase.CLOSED = CLOSED;
 
 module.exports = WebSocketBase;
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index 206b5f785f9f..2b5a0bc964f6 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -6,108 +6,129 @@
  * 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.
  *
- * @flow
+ * @noflow - get/set properties not yet supported by flow. also `...require(x)` is broken #6560135
  */
 'use strict';
 
 // Export React, plus some native additions.
-//
-// The use of Object.create/assign is to work around a Flow bug (#6560135).
-// Once that is fixed, change this back to
-//
-//   var ReactNative = {...require('React'), /* additions */}
-//
-var ReactNative = Object.assign(Object.create(require('React')), {
+var ReactNative = {
   // Components
-  ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
-  ART: require('ReactNativeART'),
-  DatePickerIOS: require('DatePickerIOS'),
-  DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
-  Image: require('Image'),
-  ListView: require('ListView'),
-  MapView: require('MapView'),
-  Modal: require('Modal'),
-  Navigator: require('Navigator'),
-  NavigatorIOS: require('NavigatorIOS'),
-  PickerIOS: require('PickerIOS'),
-  ProgressBarAndroid: require('ProgressBarAndroid'),
-  ProgressViewIOS: require('ProgressViewIOS'),
-  ScrollView: require('ScrollView'),
-  SegmentedControlIOS: require('SegmentedControlIOS'),
-  SliderIOS: require('SliderIOS'),
-  SnapshotViewIOS: require('SnapshotViewIOS'),
-  Switch: require('Switch'),
-  SwitchAndroid: require('SwitchAndroid'),
-  SwitchIOS: require('SwitchIOS'),
-  TabBarIOS: require('TabBarIOS'),
-  Text: require('Text'),
-  TextInput: require('TextInput'),
-  ToastAndroid: require('ToastAndroid'),
-  ToolbarAndroid: require('ToolbarAndroid'),
-  Touchable: require('Touchable'),
-  TouchableHighlight: require('TouchableHighlight'),
-  TouchableNativeFeedback: require('TouchableNativeFeedback'),
-  TouchableOpacity: require('TouchableOpacity'),
-  TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
-  View: require('View'),
-  ViewPagerAndroid: require('ViewPagerAndroid'),
-  WebView: require('WebView'),
+  get ActivityIndicatorIOS() { return require('ActivityIndicatorIOS'); },
+  get ART() { return require('ReactNativeART'); },
+  get DatePickerIOS() { return require('DatePickerIOS'); },
+  get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); },
+  get Image() { return require('Image'); },
+  get ImageEditor() { return require('ImageEditor'); },
+  get ImageStore() { return require('ImageStore'); },
+  get ListView() { return require('ListView'); },
+  get MapView() { return require('MapView'); },
+  get Modal() { return require('Modal'); },
+  get Navigator() { return require('Navigator'); },
+  get NavigatorIOS() { return require('NavigatorIOS'); },
+  get Picker() { return require('Picker'); },
+  get PickerIOS() { return require('PickerIOS'); },
+  get ProgressBarAndroid() { return require('ProgressBarAndroid'); },
+  get ProgressViewIOS() { return require('ProgressViewIOS'); },
+  get ScrollView() { return require('ScrollView'); },
+  get SegmentedControlIOS() { return require('SegmentedControlIOS'); },
+  get SliderIOS() { return require('SliderIOS'); },
+  get SnapshotViewIOS() { return require('SnapshotViewIOS'); },
+  get Switch() { return require('Switch'); },
+  get PullToRefreshViewAndroid() { return require('PullToRefreshViewAndroid'); },
+  get RecyclerViewBackedScrollView() { return require('RecyclerViewBackedScrollView'); },
+  get RefreshControl() { return require('RefreshControl'); },
+  get StatusBar() { return require('StatusBar'); },
+  get SwitchAndroid() { return require('SwitchAndroid'); },
+  get SwitchIOS() { return require('SwitchIOS'); },
+  get TabBarIOS() { return require('TabBarIOS'); },
+  get Text() { return require('Text'); },
+  get TextInput() { return require('TextInput'); },
+  get ToastAndroid() { return require('ToastAndroid'); },
+  get ToolbarAndroid() { return require('ToolbarAndroid'); },
+  get Touchable() { return require('Touchable'); },
+  get TouchableHighlight() { return require('TouchableHighlight'); },
+  get TouchableNativeFeedback() { return require('TouchableNativeFeedback'); },
+  get TouchableOpacity() { return require('TouchableOpacity'); },
+  get TouchableWithoutFeedback() { return require('TouchableWithoutFeedback'); },
+  get View() { return require('View'); },
+  get ViewPagerAndroid() { return require('ViewPagerAndroid'); },
+  get WebView() { return require('WebView'); },
 
   // APIs
-  ActionSheetIOS: require('ActionSheetIOS'),
-  AdSupportIOS: require('AdSupportIOS'),
-  AlertIOS: require('AlertIOS'),
-  Animated: require('Animated'),
-  AppRegistry: require('AppRegistry'),
-  AppStateIOS: require('AppStateIOS'),
-  AsyncStorage: require('AsyncStorage'),
-  BackAndroid: require('BackAndroid'),
-  CameraRoll: require('CameraRoll'),
-  Dimensions: require('Dimensions'),
-  Easing: require('Easing'),
-  ImagePickerIOS: require('ImagePickerIOS'),
-  IntentAndroid: require('IntentAndroid'),
-  InteractionManager: require('InteractionManager'),
-  LayoutAnimation: require('LayoutAnimation'),
-  LinkingIOS: require('LinkingIOS'),
-  NetInfo: require('NetInfo'),
-  PanResponder: require('PanResponder'),
-  PixelRatio: require('PixelRatio'),
-  PushNotificationIOS: require('PushNotificationIOS'),
-  Settings: require('Settings'),
-  StatusBarIOS: require('StatusBarIOS'),
-  StyleSheet: require('StyleSheet'),
-  VibrationIOS: require('VibrationIOS'),
+  get ActionSheetIOS() { return require('ActionSheetIOS'); },
+  get AdSupportIOS() { return require('AdSupportIOS'); },
+  get Alert() { return require('Alert'); },
+  get AlertIOS() { return require('AlertIOS'); },
+  get Animated() { return require('Animated'); },
+  get AppRegistry() { return require('AppRegistry'); },
+  get AppState() { return require('AppState'); },
+  get AppStateIOS() { return require('AppStateIOS'); },
+  get AsyncStorage() { return require('AsyncStorage'); },
+  get BackAndroid() { return require('BackAndroid'); },
+  get CameraRoll() { return require('CameraRoll'); },
+  get Clipboard() { return require('Clipboard'); },
+  get DatePickerAndroid() { return require('DatePickerAndroid'); },
+  get Dimensions() { return require('Dimensions'); },
+  get Easing() { return require('Easing'); },
+  get ImagePickerIOS() { return require('ImagePickerIOS'); },
+  get IntentAndroid() { return require('IntentAndroid'); },
+  get InteractionManager() { return require('InteractionManager'); },
+  get LayoutAnimation() { return require('LayoutAnimation'); },
+  get Linking() { return require('Linking'); },
+  get LinkingIOS() { return require('LinkingIOS'); },
+  get NavigationExperimental() { return require('NavigationExperimental'); },
+  get NetInfo() { return require('NetInfo'); },
+  get PanResponder() { return require('PanResponder'); },
+  get PixelRatio() { return require('PixelRatio'); },
+  get PushNotificationIOS() { return require('PushNotificationIOS'); },
+  get Settings() { return require('Settings'); },
+  get StatusBarIOS() { return require('StatusBarIOS'); },
+  get StyleSheet() { return require('StyleSheet'); },
+  get TimePickerAndroid() { return require('TimePickerAndroid'); },
+  get UIManager() { return require('UIManager'); },
+  get Vibration() { return require('Vibration'); },
+  get VibrationIOS() { return require('VibrationIOS'); },
 
   // Plugins
-  DeviceEventEmitter: require('RCTDeviceEventEmitter'),
-  NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
-  NativeModules: require('NativeModules'),
-  Platform: require('Platform'),
-  processColor: require('processColor'),
-  requireNativeComponent: require('requireNativeComponent'),
+  get DeviceEventEmitter() { return require('RCTDeviceEventEmitter'); },
+  get NativeAppEventEmitter() { return require('RCTNativeAppEventEmitter'); },
+  get NativeModules() { return require('NativeModules'); },
+  get Platform() { return require('Platform'); },
+  get processColor() { return require('processColor'); },
+  get requireNativeComponent() { return require('requireNativeComponent'); },
 
   // Prop Types
-  EdgeInsetsPropType: require('EdgeInsetsPropType'),
-  PointPropType: require('PointPropType'),
+  get ColorPropType() { return require('ColorPropType'); },
+  get EdgeInsetsPropType() { return require('EdgeInsetsPropType'); },
+  get PointPropType() { return require('PointPropType'); },
 
   // See http://facebook.github.io/react/docs/addons.html
   addons: {
-    LinkedStateMixin: require('LinkedStateMixin'),
+    get LinkedStateMixin() { return require('LinkedStateMixin'); },
     Perf: undefined,
-    PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
-    TestModule: require('NativeModules').TestModule,
+    get PureRenderMixin() { return require('ReactComponentWithPureRenderMixin'); },
+    get TestModule() { return require('NativeModules').TestModule; },
     TestUtils: undefined,
-    batchedUpdates: require('ReactUpdates').batchedUpdates,
-    cloneWithProps: require('cloneWithProps'),
-    createFragment: require('ReactFragment').create,
-    update: require('update'),
+    get batchedUpdates() { return require('ReactUpdates').batchedUpdates; },
+    get cloneWithProps() { return require('cloneWithProps'); },
+    get createFragment() { return require('ReactFragment').create; },
+    get update() { return require('update'); },
   },
-});
+
+  // Note: this must be placed last to prevent eager
+  // evaluation of the getter-wrapped submodules above
+  ...require('React'),
+};
 
 if (__DEV__) {
-  ReactNative.addons.Perf = require('ReactDefaultPerf');
-  ReactNative.addons.TestUtils = require('ReactTestUtils');
+  Object.defineProperty(ReactNative.addons, 'Perf', {
+    enumerable: true,
+    get: () => require('ReactDefaultPerf'),
+  });
+  Object.defineProperty(ReactNative.addons, 'TestUtils', {
+    enumerable: true,
+    get: () => require('ReactTestUtils'),
+  });
 }
 
 module.exports = ReactNative;
diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow
new file mode 100644
index 000000000000..70d05ead71ca
--- /dev/null
+++ b/Libraries/react-native/react-native.js.flow
@@ -0,0 +1,136 @@
+/**
+ * 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.
+ *
+ * This file exists because react-native.js started using getters and setters
+ * and Flow doesn't have a good way to enable getters and setters for
+ * react-native without forcing all react-native users to also enable getters
+ * and setters. Until we solve that issue, we can use this .flow file to
+ * pretend like react-native doesn't use getters and setters
+ *
+ * @flow
+ */
+'use strict';
+
+// Export React, plus some native additions.
+//
+// The use of Object.create/assign is to work around a Flow bug (#6560135).
+// Once that is fixed, change this back to
+//
+//   var ReactNative = {...require('React'), /* additions */}
+//
+var ReactNative = Object.assign(Object.create(require('react')), {
+  // Components
+  ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
+  ART: require('ReactNativeART'),
+  DatePickerIOS: require('DatePickerIOS'),
+  DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
+  Image: require('Image'),
+  ImageEditor: require('ImageEditor'),
+  ImageStore: require('ImageStore'),
+  ListView: require('ListView'),
+  MapView: require('MapView'),
+  Modal: require('Modal'),
+  Navigator: require('Navigator'),
+  NavigatorIOS: require('NavigatorIOS'),
+  Picker: require('Picker'),
+  PickerIOS: require('PickerIOS'),
+  ProgressBarAndroid: require('ProgressBarAndroid'),
+  ProgressViewIOS: require('ProgressViewIOS'),
+  ScrollView: require('ScrollView'),
+  SegmentedControlIOS: require('SegmentedControlIOS'),
+  SliderIOS: require('SliderIOS'),
+  SnapshotViewIOS: require('SnapshotViewIOS'),
+  StatusBar: require('StatusBar'),
+  Switch: require('Switch'),
+  PullToRefreshViewAndroid: require('PullToRefreshViewAndroid'),
+  RecyclerViewBackedScrollView: require('RecyclerViewBackedScrollView'),
+  RefreshControl: require('RefreshControl'),
+  SwitchAndroid: require('SwitchAndroid'),
+  SwitchIOS: require('SwitchIOS'),
+  TabBarIOS: require('TabBarIOS'),
+  Text: require('Text'),
+  TextInput: require('TextInput'),
+  ToastAndroid: require('ToastAndroid'),
+  ToolbarAndroid: require('ToolbarAndroid'),
+  Touchable: require('Touchable'),
+  TouchableHighlight: require('TouchableHighlight'),
+  TouchableNativeFeedback: require('TouchableNativeFeedback'),
+  TouchableOpacity: require('TouchableOpacity'),
+  TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
+  View: require('View'),
+  ViewPagerAndroid: require('ViewPagerAndroid'),
+  WebView: require('WebView'),
+
+  // APIs
+  ActionSheetIOS: require('ActionSheetIOS'),
+  AdSupportIOS: require('AdSupportIOS'),
+  Alert: require('Alert'),
+  AlertIOS: require('AlertIOS'),
+  Animated: require('Animated'),
+  AppRegistry: require('AppRegistry'),
+  AppState: require('AppState'),
+  AppStateIOS: require('AppStateIOS'),
+  AsyncStorage: require('AsyncStorage'),
+  BackAndroid: require('BackAndroid'),
+  CameraRoll: require('CameraRoll'),
+  Clipboard: require('Clipboard'),
+  DatePickerAndroid: require('DatePickerAndroid'),
+  Dimensions: require('Dimensions'),
+  Easing: require('Easing'),
+  ImagePickerIOS: require('ImagePickerIOS'),
+  IntentAndroid: require('IntentAndroid'),
+  InteractionManager: require('InteractionManager'),
+  LayoutAnimation: require('LayoutAnimation'),
+  Linking: require('Linking'),
+  LinkingIOS: require('LinkingIOS'),
+  NavigationExperimental: require('NavigationExperimental'),
+  NetInfo: require('NetInfo'),
+  PanResponder: require('PanResponder'),
+  PixelRatio: require('PixelRatio'),
+  PushNotificationIOS: require('PushNotificationIOS'),
+  Settings: require('Settings'),
+  StatusBarIOS: require('StatusBarIOS'),
+  StyleSheet: require('StyleSheet'),
+  TimePickerAndroid: require('TimePickerAndroid'),
+  UIManager: require('UIManager'),
+  Vibration: require('Vibration'),
+  VibrationIOS: require('VibrationIOS'),
+
+  // Plugins
+  DeviceEventEmitter: require('RCTDeviceEventEmitter'),
+  NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
+  NativeModules: require('NativeModules'),
+  Platform: require('Platform'),
+  processColor: require('processColor'),
+  requireNativeComponent: require('requireNativeComponent'),
+
+  // Prop Types
+  ColorPropType: require('ColorPropType'),
+  EdgeInsetsPropType: require('EdgeInsetsPropType'),
+  PointPropType: require('PointPropType'),
+
+  // See http://facebook.github.io/react/docs/addons.html
+  addons: {
+    LinkedStateMixin: require('LinkedStateMixin'),
+    Perf: undefined,
+    PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
+    TestModule: require('NativeModules').TestModule,
+    TestUtils: undefined,
+    batchedUpdates: require('ReactUpdates').batchedUpdates,
+    cloneWithProps: require('cloneWithProps'),
+    createFragment: require('ReactFragment').create,
+    update: require('update'),
+  },
+});
+
+if (__DEV__) {
+  ReactNative.addons.Perf = require('ReactDefaultPerf');
+  ReactNative.addons.TestUtils = require('ReactTestUtils');
+}
+
+module.exports = ReactNative;
diff --git a/Libraries/vendor/core/Map.js b/Libraries/vendor/core/Map.js
index 114add77b308..b3153b39df1f 100644
--- a/Libraries/vendor/core/Map.js
+++ b/Libraries/vendor/core/Map.js
@@ -19,7 +19,7 @@
  */
 
 var guid = require('guid');
-var isNode = require('isNode');
+var isNode = require('fbjs/lib/isNode');
 var toIterator = require('toIterator');
 var _shouldPolyfillES6Collection = require('_shouldPolyfillES6Collection');
 
diff --git a/Libraries/vendor/core/mergeHelpers.js b/Libraries/vendor/core/mergeHelpers.js
index 58fa238d4f2b..d0c506b3d924 100644
--- a/Libraries/vendor/core/mergeHelpers.js
+++ b/Libraries/vendor/core/mergeHelpers.js
@@ -33,8 +33,8 @@
 
 "use strict";
 
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
+var invariant = require('fbjs/lib/invariant');
+var keyMirror = require('fbjs/lib/keyMirror');
 
 /**
  * Maximum number of levels to traverse. Will catch circular structures.
diff --git a/Libraries/vendor/crypto/crc32.js b/Libraries/vendor/crypto/crc32.js
deleted file mode 100644
index 540d60aa832b..000000000000
--- a/Libraries/vendor/crypto/crc32.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * @generated SignedSource<<77bdeb858138636c96c405d64b6be55c>>
- *
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !! This file is a check-in of a static_upstream project!      !!
- * !!                                                            !!
- * !! You should not modify this file directly. Instead:         !!
- * !! 1) Use `fjs use-upstream` to temporarily replace this with !!
- * !!    the latest version from upstream.                       !!
- * !! 2) Make your changes, test them, etc.                      !!
- * !! 3) Use `fjs push-upstream` to copy your changes back to    !!
- * !!    static_upstream.                                        !!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- *
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule crc32
- */
-
-/* jslint bitwise: true */
-
-/**
- * Modified from the original for performance improvements.
- *
- * @see http://create.stephan-brumme.com/crc32/
- * @see http://stackoverflow.com/questions/18638900/
- * @copyright 2006 Andrea Ercolino
- * @license MIT
- */
-
-var table = [
-  0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
-  0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
-  0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
-  0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
-  0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9,
-  0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
-  0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
-  0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
-  0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
-  0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
-  0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
-  0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
-  0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D,
-  0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
-  0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
-  0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
-  0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7,
-  0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
-  0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA,
-  0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
-  0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
-  0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
-  0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84,
-  0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
-  0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB,
-  0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
-  0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
-  0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
-  0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55,
-  0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
-  0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
-  0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
-  0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
-  0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
-  0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
-  0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
-  0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69,
-  0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
-  0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
-  0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
-  0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
-  0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
-  0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
-];
-
-if (global.Int32Array !== undefined) {
-  table = new Int32Array(table);
-}
-
-/**
- * @returns Number
- */
-function crc32(str) {
-  var crc = -1;
-  for (var i = 0, len = str.length; i < len; i++) {
-    crc = (crc >>> 8) ^ table[(crc ^ str.charCodeAt(i)) & 0xFF];
-  }
-  return ~crc;
-}
-
-module.exports = crc32;
diff --git a/Libraries/vendor/emitter/EmitterSubscription.js b/Libraries/vendor/emitter/EmitterSubscription.js
index a4d52a802ee5..bbd1d414c431 100644
--- a/Libraries/vendor/emitter/EmitterSubscription.js
+++ b/Libraries/vendor/emitter/EmitterSubscription.js
@@ -13,6 +13,7 @@
  * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  *
  * @providesModule EmitterSubscription
+ * @noflow
  * @typechecks
  */
 'use strict';
diff --git a/Libraries/vendor/emitter/EventEmitter.js b/Libraries/vendor/emitter/EventEmitter.js
index d8b679214e2a..ba6781a57e3f 100644
--- a/Libraries/vendor/emitter/EventEmitter.js
+++ b/Libraries/vendor/emitter/EventEmitter.js
@@ -1,26 +1,21 @@
 /**
- * @generated SignedSource<<494e66dea72a3e90b763a5ec50b1e0ca>>
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
  *
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- * !! This file is a check-in of a static_upstream project!      !!
- * !!                                                            !!
- * !! You should not modify this file directly. Instead:         !!
- * !! 1) Use `fjs use-upstream` to temporarily replace this with !!
- * !!    the latest version from upstream.                       !!
- * !! 2) Make your changes, test them, etc.                      !!
- * !! 3) Use `fjs push-upstream` to copy your changes back to    !!
- * !!    static_upstream.                                        !!
- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * 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 EventEmitter
+ * @noflow
  * @typechecks
  */
 
 var EmitterSubscription = require('EmitterSubscription');
 var ErrorUtils = require('ErrorUtils');
 var EventSubscriptionVendor = require('EventSubscriptionVendor');
-var emptyFunction = require('emptyFunction');
-var invariant = require('invariant');
+var emptyFunction = require('fbjs/lib/emptyFunction');
+var invariant = require('fbjs/lib/invariant');
 
 /**
  * @class EventEmitter
@@ -164,13 +159,9 @@ class EventEmitter {
         // The subscription may have been removed during this event loop.
         if (subscription) {
           this._currentSubscription = subscription;
-
-          ErrorUtils.applyWithGuard(
-            subscription.listener,
+          subscription.listener.apply(
             subscription.context,
-            Array.prototype.slice.call(arguments, 1),
-            null,
-            'EventEmitter:' + eventType
+            Array.prototype.slice.call(arguments, 1)
           );
         }
       }
diff --git a/Libraries/vendor/emitter/EventHolder.js b/Libraries/vendor/emitter/EventHolder.js
index 4345c3d387cd..76cfd33141e8 100644
--- a/Libraries/vendor/emitter/EventHolder.js
+++ b/Libraries/vendor/emitter/EventHolder.js
@@ -17,7 +17,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 class EventHolder {
   constructor() {
diff --git a/Libraries/vendor/emitter/EventSubscriptionVendor.js b/Libraries/vendor/emitter/EventSubscriptionVendor.js
index add1f408e4a8..d992a69a5536 100644
--- a/Libraries/vendor/emitter/EventSubscriptionVendor.js
+++ b/Libraries/vendor/emitter/EventSubscriptionVendor.js
@@ -17,7 +17,7 @@
  */
 'use strict';
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 /**
  * EventSubscriptionVendor stores a set of EventSubscriptions that are
diff --git a/Libraries/vendor/emitter/mixInEventEmitter.js b/Libraries/vendor/emitter/mixInEventEmitter.js
index cc62d04d4635..fc39f7b649e1 100644
--- a/Libraries/vendor/emitter/mixInEventEmitter.js
+++ b/Libraries/vendor/emitter/mixInEventEmitter.js
@@ -21,8 +21,8 @@ var EventHolder = require('EventHolder');
 var EventValidator = require('EventValidator');
 
 var copyProperties = require('copyProperties');
-var invariant = require('invariant');
-var keyOf = require('keyOf');
+var invariant = require('fbjs/lib/invariant');
+var keyOf = require('fbjs/lib/keyOf');
 
 var TYPES_KEY = keyOf({__types: true});
 
@@ -49,12 +49,13 @@ var TYPES_KEY = keyOf({__types: true});
  */
 function mixInEventEmitter(klass, types) {
   invariant(types, 'Must supply set of valid event types');
-  invariant(!this.__eventEmitter, 'An active emitter is already mixed in');
 
   // If this is a constructor, write to the prototype, otherwise write to the
   // singleton object.
   var target = klass.prototype || klass;
 
+  invariant(!target.__eventEmitter, 'An active emitter is already mixed in');
+
   var ctor = klass.constructor;
   if (ctor) {
     invariant(
diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
index f71a645675de..c633dd724be7 100644
--- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
+++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
@@ -23,9 +23,26 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY;
  * recognize simple multi-touch gestures.
  *
  * It provides a predictable wrapper of the responder handlers provided by the
- * [gesture responder system](/react-native/docs/gesture-responder-system.html).
+ * [gesture responder system](docs/gesture-responder-system.html).
  * For each handler, it provides a new `gestureState` object alongside the
- * normal event.
+ * native event object:
+ *
+ * ```
+ * onPanResponderMove: (event, gestureState) => {}
+ * ```
+ *
+ * A native event is a synthetic touch event with the following form:
+ *
+ *  - `nativeEvent`
+ *      + `changedTouches` - Array of all touch events that have changed since the last event
+ *      + `identifier` - The ID of the touch
+ *      + `locationX` - The X position of the touch, relative to the element
+ *      + `locationY` - The Y position of the touch, relative to the element
+ *      + `pageX` - The X position of the touch, relative to the root element
+ *      + `pageY` - The Y position of the touch, relative to the root element
+ *      + `target` - The node id of the element receiving the touch event
+ *      + `timestamp` - A time identifier for the touch, useful for velocity calculation
+ *      + `touches` - Array of all current touches on the screen
  *
  * A `gestureState` object has the following:
  *
@@ -39,7 +56,7 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY;
  *  - `dy` - accumulated distance of the gesture since the touch started
  *  - `vx` - current velocity of the gesture
  *  - `vy` - current velocity of the gesture
- *  - `numberActiveTouches` - Number of touches currently on screeen
+ *  - `numberActiveTouches` - Number of touches currently on screen
  *
  * ### Basic Usage
  *
diff --git a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js b/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js
deleted file mode 100644
index b84985e6ba10..000000000000
--- a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js
+++ /dev/null
@@ -1,612 +0,0 @@
-/**
- * Copyright 2013-2015, 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 ResponderEventPlugin
- */
-
-'use strict';
-
-var EventConstants = require('EventConstants');
-var EventPluginUtils = require('EventPluginUtils');
-var EventPropagators = require('EventPropagators');
-var ReactInstanceHandles = require('ReactInstanceHandles');
-var ResponderSyntheticEvent = require('ResponderSyntheticEvent');
-var ResponderTouchHistoryStore = require('ResponderTouchHistoryStore');
-
-var accumulate = require('accumulate');
-var invariant = require('invariant');
-var keyOf = require('keyOf');
-
-var isStartish = EventPluginUtils.isStartish;
-var isMoveish = EventPluginUtils.isMoveish;
-var isEndish = EventPluginUtils.isEndish;
-var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
-var hasDispatches = EventPluginUtils.hasDispatches;
-var executeDispatchesInOrderStopAtTrue =
-  EventPluginUtils.executeDispatchesInOrderStopAtTrue;
-
-/**
- * ID of element that should respond to touch/move types of interactions, as
- * indicated explicitly by relevant callbacks.
- */
-var responderID = null;
-
-/**
- * Count of current touches. A textInput should become responder iff the
- * the selection changes while there is a touch on the screen.
- */
-var trackedTouchCount = 0;
-
-/**
- * Last reported number of active touches.
- */
-var previousActiveTouches = 0;
-
-var changeResponder = function(nextResponderID, blockNativeResponder) {
-  var oldResponderID = responderID;
-  responderID = nextResponderID;
-  if (ResponderEventPlugin.GlobalResponderHandler !== null) {
-    ResponderEventPlugin.GlobalResponderHandler.onChange(
-      oldResponderID,
-      nextResponderID,
-      blockNativeResponder
-    );
-  }
-};
-
-var eventTypes = {
-  /**
-   * On a `touchStart`/`mouseDown`, is it desired that this element become the
-   * responder?
-   */
-  startShouldSetResponder: {
-    phasedRegistrationNames: {
-      bubbled: keyOf({onStartShouldSetResponder: null}),
-      captured: keyOf({onStartShouldSetResponderCapture: null}),
-    },
-  },
-
-  /**
-   * On a `scroll`, is it desired that this element become the responder? This
-   * is usually not needed, but should be used to retroactively infer that a
-   * `touchStart` had occured during momentum scroll. During a momentum scroll,
-   * a touch start will be immediately followed by a scroll event if the view is
-   * currently scrolling.
-   *
-   * TODO: This shouldn't bubble.
-   */
-  scrollShouldSetResponder: {
-    phasedRegistrationNames: {
-      bubbled: keyOf({onScrollShouldSetResponder: null}),
-      captured: keyOf({onScrollShouldSetResponderCapture: null}),
-    },
-  },
-
-  /**
-   * On text selection change, should this element become the responder? This
-   * is needed for text inputs or other views with native selection, so the
-   * JS view can claim the responder.
-   *
-   * TODO: This shouldn't bubble.
-   */
-  selectionChangeShouldSetResponder: {
-    phasedRegistrationNames: {
-      bubbled: keyOf({onSelectionChangeShouldSetResponder: null}),
-      captured: keyOf({onSelectionChangeShouldSetResponderCapture: null}),
-    },
-  },
-
-  /**
-   * On a `touchMove`/`mouseMove`, is it desired that this element become the
-   * responder?
-   */
-  moveShouldSetResponder: {
-    phasedRegistrationNames: {
-      bubbled: keyOf({onMoveShouldSetResponder: null}),
-      captured: keyOf({onMoveShouldSetResponderCapture: null}),
-    },
-  },
-
-  /**
-   * Direct responder events dispatched directly to responder. Do not bubble.
-   */
-  responderStart: {registrationName: keyOf({onResponderStart: null})},
-  responderMove: {registrationName: keyOf({onResponderMove: null})},
-  responderEnd: {registrationName: keyOf({onResponderEnd: null})},
-  responderRelease: {registrationName: keyOf({onResponderRelease: null})},
-  responderTerminationRequest: {
-    registrationName: keyOf({onResponderTerminationRequest: null}),
-  },
-  responderGrant: {registrationName: keyOf({onResponderGrant: null})},
-  responderReject: {registrationName: keyOf({onResponderReject: null})},
-  responderTerminate: {registrationName: keyOf({onResponderTerminate: null})},
-};
-
-/**
- *
- * Responder System:
- * ----------------
- *
- * - A global, solitary "interaction lock" on a view.
- * - If a node becomes the responder, it should convey visual feedback
- *   immediately to indicate so, either by highlighting or moving accordingly.
- * - To be the responder means, that touches are exclusively important to that
- *   responder view, and no other view.
- * - While touches are still occuring, the responder lock can be transfered to
- *   a new view, but only to increasingly "higher" views (meaning ancestors of
- *   the current responder).
- *
- * Responder being granted:
- * ------------------------
- *
- * - Touch starts, moves, and scrolls can cause an ID to become the responder.
- * - We capture/bubble `startShouldSetResponder`/`moveShouldSetResponder` to
- *   the "appropriate place".
- * - If nothing is currently the responder, the "appropriate place" is the
- *   initiating event's `targetID`.
- * - If something *is* already the responder, the "appropriate place" is the
- *   first common ancestor of the event target and the current `responderID`.
- * - Some negotiation happens: See the timing diagram below.
- * - Scrolled views automatically become responder. The reasoning is that a
- *   platform scroll view that isn't built on top of the responder system has
- *   began scrolling, and the active responder must now be notified that the
- *   interaction is no longer locked to it - the system has taken over.
- *
- * - Responder being released:
- *   As soon as no more touches that *started* inside of descendents of the
- *   *current* responderID, an `onResponderRelease` event is dispatched to the
- *   current responder, and the responder lock is released.
- *
- * TODO:
- * - on "end", a callback hook for `onResponderEndShouldRemainResponder` that
- *   determines if the responder lock should remain.
- * - If a view shouldn't "remain" the responder, any active touches should by
- *   default be considered "dead" and do not influence future negotiations or
- *   bubble paths. It should be as if those touches do not exist.
- * -- For multitouch: Usually a translate-z will choose to "remain" responder
- *  after one out of many touches ended. For translate-y, usually the view
- *  doesn't wish to "remain" responder after one of many touches end.
- * - Consider building this on top of a `stopPropagation` model similar to
- *   `W3C` events.
- * - Ensure that `onResponderTerminate` is called on touch cancels, whether or
- *   not `onResponderTerminationRequest` returns `true` or `false`.
- *
- */
-
-/*                                             Negotiation Performed
-                                             +-----------------------+
-                                            /                         \
-Process low level events to    +     Current Responder      +   wantsResponderID
-determine who to perform negot-|   (if any exists at all)   |
-iation/transition              | Otherwise just pass through|
--------------------------------+----------------------------+------------------+
-Bubble to find first ID        |                            |
-to return true:wantsResponderID|                            |
-                               |                            |
-     +-------------+           |                            |
-     | onTouchStart|           |                            |
-     +------+------+     none  |                            |
-            |            return|                            |
-+-----------v-------------+true| +------------------------+ |
-|onStartShouldSetResponder|----->|onResponderStart (cur)  |<-----------+
-+-----------+-------------+    | +------------------------+ |          |
-            |                  |                            | +--------+-------+
-            | returned true for|       false:REJECT +-------->|onResponderReject
-            | wantsResponderID |                    |       | +----------------+
-            | (now attempt     | +------------------+-----+ |
-            |  handoff)        | |   onResponder          | |
-            +------------------->|      TerminationRequest| |
-                               | +------------------+-----+ |
-                               |                    |       | +----------------+
-                               |         true:GRANT +-------->|onResponderGrant|
-                               |                            | +--------+-------+
-                               | +------------------------+ |          |
-                               | |   onResponderTerminate |<-----------+
-                               | +------------------+-----+ |
-                               |                    |       | +----------------+
-                               |                    +-------->|onResponderStart|
-                               |                            | +----------------+
-Bubble to find first ID        |                            |
-to return true:wantsResponderID|                            |
-                               |                            |
-     +-------------+           |                            |
-     | onTouchMove |           |                            |
-     +------+------+     none  |                            |
-            |            return|                            |
-+-----------v-------------+true| +------------------------+ |
-|onMoveShouldSetResponder |----->|onResponderMove (cur)   |<-----------+
-+-----------+-------------+    | +------------------------+ |          |
-            |                  |                            | +--------+-------+
-            | returned true for|       false:REJECT +-------->|onResponderRejec|
-            | wantsResponderID |                    |       | +----------------+
-            | (now attempt     | +------------------+-----+ |
-            |  handoff)        | |   onResponder          | |
-            +------------------->|      TerminationRequest| |
-                               | +------------------+-----+ |
-                               |                    |       | +----------------+
-                               |         true:GRANT +-------->|onResponderGrant|
-                               |                            | +--------+-------+
-                               | +------------------------+ |          |
-                               | |   onResponderTerminate |<-----------+
-                               | +------------------+-----+ |
-                               |                    |       | +----------------+
-                               |                    +-------->|onResponderMove |
-                               |                            | +----------------+
-                               |                            |
-                               |                            |
-      Some active touch started|                            |
-      inside current responder | +------------------------+ |
-      +------------------------->|      onResponderEnd    | |
-      |                        | +------------------------+ |
-  +---+---------+              |                            |
-  | onTouchEnd  |              |                            |
-  +---+---------+              |                            |
-      |                        | +------------------------+ |
-      +------------------------->|     onResponderEnd     | |
-      No active touches started| +-----------+------------+ |
-      inside current responder |             |              |
-                               |             v              |
-                               | +------------------------+ |
-                               | |    onResponderRelease  | |
-                               | +------------------------+ |
-                               |                            |
-                               +                            + */
-
-
-
-/**
- * A note about event ordering in the `EventPluginHub`.
- *
- * Suppose plugins are injected in the following order:
- *
- * `[R, S, C]`
- *
- * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
- * `onClick` etc) and `R` is `ResponderEventPlugin`.
- *
- * "Deferred-Dispatched Events":
- *
- * - The current event plugin system will traverse the list of injected plugins,
- *   in order, and extract events by collecting the plugin's return value of
- *   `extractEvents()`.
- * - These events that are returned from `extractEvents` are "deferred
- *   dispatched events".
- * - When returned from `extractEvents`, deferred-dispatched events contain an
- *   "accumulation" of deferred dispatches.
- * - These deferred dispatches are accumulated/collected before they are
- *   returned, but processed at a later time by the `EventPluginHub` (hence the
- *   name deferred).
- *
- * In the process of returning their deferred-dispatched events, event plugins
- * themselves can dispatch events on-demand without returning them from
- * `extractEvents`. Plugins might want to do this, so that they can use event
- * dispatching as a tool that helps them decide which events should be extracted
- * in the first place.
- *
- * "On-Demand-Dispatched Events":
- *
- * - On-demand-dispatched events are not returned from `extractEvents`.
- * - On-demand-dispatched events are dispatched during the process of returning
- *   the deferred-dispatched events.
- * - They should not have side effects.
- * - They should be avoided, and/or eventually be replaced with another
- *   abstraction that allows event plugins to perform multiple "rounds" of event
- *   extraction.
- *
- * Therefore, the sequence of event dispatches becomes:
- *
- * - `R`s on-demand events (if any)   (dispatched by `R` on-demand)
- * - `S`s on-demand events (if any)   (dispatched by `S` on-demand)
- * - `C`s on-demand events (if any)   (dispatched by `C` on-demand)
- * - `R`s extracted events (if any)   (dispatched by `EventPluginHub`)
- * - `S`s extracted events (if any)   (dispatched by `EventPluginHub`)
- * - `C`s extracted events (if any)   (dispatched by `EventPluginHub`)
- *
- * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
- * on-demand dispatch returns `true` (and some other details are satisfied) the
- * `onResponderGrant` deferred dispatched event is returned from
- * `extractEvents`. The sequence of dispatch executions in this case
- * will appear as follows:
- *
- * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
- * - `touchStartCapture`       (`EventPluginHub` dispatches as usual)
- * - `touchStart`              (`EventPluginHub` dispatches as usual)
- * - `responderGrant/Reject`   (`EventPluginHub` dispatches as usual)
- *
- * @param {string} topLevelType Record from `EventConstants`.
- * @param {string} topLevelTargetID ID of deepest React rendered element.
- * @param {object} nativeEvent Native browser event.
- * @return {*} An accumulation of synthetic events.
- */
-function setResponderAndExtractTransfer(
-    topLevelType,
-    topLevelTargetID,
-    nativeEvent,
-    nativeEventTarget) {
-  var shouldSetEventType =
-    isStartish(topLevelType) ? eventTypes.startShouldSetResponder :
-    isMoveish(topLevelType) ? eventTypes.moveShouldSetResponder :
-    topLevelType === EventConstants.topLevelTypes.topSelectionChange ?
-      eventTypes.selectionChangeShouldSetResponder :
-    eventTypes.scrollShouldSetResponder;
-
-  // TODO: stop one short of the the current responder.
-  var bubbleShouldSetFrom = !responderID ?
-    topLevelTargetID :
-    ReactInstanceHandles.getFirstCommonAncestorID(responderID, topLevelTargetID);
-
-  // When capturing/bubbling the "shouldSet" event, we want to skip the target
-  // (deepest ID) if it happens to be the current responder. The reasoning:
-  // It's strange to get an `onMoveShouldSetResponder` when you're *already*
-  // the responder.
-  var skipOverBubbleShouldSetFrom = bubbleShouldSetFrom === responderID;
-  var shouldSetEvent = ResponderSyntheticEvent.getPooled(
-    shouldSetEventType,
-    bubbleShouldSetFrom,
-    nativeEvent,
-    nativeEventTarget
-  );
-  shouldSetEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-  if (skipOverBubbleShouldSetFrom) {
-    EventPropagators.accumulateTwoPhaseDispatchesSkipTarget(shouldSetEvent);
-  } else {
-    EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
-  }
-  var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
-  if (!shouldSetEvent.isPersistent()) {
-    shouldSetEvent.constructor.release(shouldSetEvent);
-  }
-
-  if (!wantsResponderID || wantsResponderID === responderID) {
-    return null;
-  }
-  var extracted;
-  var grantEvent = ResponderSyntheticEvent.getPooled(
-    eventTypes.responderGrant,
-    wantsResponderID,
-    nativeEvent,
-    nativeEventTarget
-  );
-  grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-
-  EventPropagators.accumulateDirectDispatches(grantEvent);
-  var blockNativeResponder = executeDirectDispatch(grantEvent) === true;
-  if (responderID) {
-
-    var terminationRequestEvent = ResponderSyntheticEvent.getPooled(
-      eventTypes.responderTerminationRequest,
-      responderID,
-      nativeEvent,
-      nativeEventTarget
-    );
-    terminationRequestEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-    EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
-    var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
-      executeDirectDispatch(terminationRequestEvent);
-    if (!terminationRequestEvent.isPersistent()) {
-      terminationRequestEvent.constructor.release(terminationRequestEvent);
-    }
-
-    if (shouldSwitch) {
-      var terminateType = eventTypes.responderTerminate;
-      var terminateEvent = ResponderSyntheticEvent.getPooled(
-        terminateType,
-        responderID,
-        nativeEvent,
-        nativeEventTarget
-      );
-      terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-      EventPropagators.accumulateDirectDispatches(terminateEvent);
-      extracted = accumulate(extracted, [grantEvent, terminateEvent]);
-      changeResponder(wantsResponderID, blockNativeResponder);
-    } else {
-      var rejectEvent = ResponderSyntheticEvent.getPooled(
-        eventTypes.responderReject,
-        wantsResponderID,
-        nativeEvent,
-        nativeEventTarget
-      );
-      rejectEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-      EventPropagators.accumulateDirectDispatches(rejectEvent);
-      extracted = accumulate(extracted, rejectEvent);
-    }
-  } else {
-    extracted = accumulate(extracted, grantEvent);
-    changeResponder(wantsResponderID, blockNativeResponder);
-  }
-  return extracted;
-}
-
-/**
- * A transfer is a negotiation between a currently set responder and the next
- * element to claim responder status. Any start event could trigger a transfer
- * of responderID. Any move event could trigger a transfer.
- *
- * @param {string} topLevelType Record from `EventConstants`.
- * @return {boolean} True if a transfer of responder could possibly occur.
- */
-function canTriggerTransfer(topLevelType, topLevelTargetID, nativeEvent) {
-  return topLevelTargetID && (
-    // responderIgnoreScroll: We are trying to migrate away from specifically tracking native scroll
-    // events here and responderIgnoreScroll indicates we will send topTouchCancel to handle
-    // canceling touch events instead
-    (topLevelType === EventConstants.topLevelTypes.topScroll &&
-      !nativeEvent.responderIgnoreScroll) ||
-    (trackedTouchCount > 0 &&
-      topLevelType === EventConstants.topLevelTypes.topSelectionChange) ||
-    isStartish(topLevelType) ||
-    isMoveish(topLevelType)
-  );
-}
-
-/**
- * Returns whether or not this touch end event makes it such that there are no
- * longer any touches that started inside of the current `responderID`.
- *
- * @param {NativeEvent} nativeEvent Native touch end event.
- * @return {boolean} Whether or not this touch end event ends the responder.
- */
-function noResponderTouches(nativeEvent) {
-  var touches = nativeEvent.touches;
-  if (!touches || touches.length === 0) {
-    return true;
-  }
-  for (var i = 0; i < touches.length; i++) {
-    var activeTouch = touches[i];
-    var target = activeTouch.target;
-    if (target !== null && target !== undefined && target !== 0) {
-      // Is the original touch location inside of the current responder?
-      var isAncestor =
-        ReactInstanceHandles.isAncestorIDOf(
-          responderID,
-          EventPluginUtils.getID(target)
-        );
-      if (isAncestor) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-
-var ResponderEventPlugin = {
-
-  getResponderID: function() {
-    return responderID;
-  },
-
-  eventTypes: eventTypes,
-
-  /**
-   * We must be resilient to `topLevelTargetID` being `undefined` on
-   * `touchMove`, or `touchEnd`. On certain platforms, this means that a native
-   * scroll has assumed control and the original touch targets are destroyed.
-   *
-   * @param {string} topLevelType Record from `EventConstants`.
-   * @param {DOMEventTarget} topLevelTarget The listening component root node.
-   * @param {string} topLevelTargetID ID of `topLevelTarget`.
-   * @param {object} nativeEvent Native browser event.
-   * @return {*} An accumulation of synthetic events.
-   * @see {EventPluginHub.extractEvents}
-   */
-  extractEvents: function(
-      topLevelType,
-      topLevelTarget,
-      topLevelTargetID,
-      nativeEvent,
-      nativeEventTarget) {
-    if (isStartish(topLevelType)) {
-      trackedTouchCount += 1;
-    } else if (isEndish(topLevelType)) {
-      trackedTouchCount -= 1;
-      invariant(
-        trackedTouchCount >= 0,
-        'Ended a touch event which was not counted in trackedTouchCount.'
-      );
-    }
-
-    ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent, nativeEventTarget);
-
-    var extracted = canTriggerTransfer(topLevelType, topLevelTargetID, nativeEvent) ?
-      setResponderAndExtractTransfer(
-        topLevelType,
-        topLevelTargetID,
-        nativeEvent,
-        nativeEventTarget) :
-      null;
-    // Responder may or may not have transfered on a new touch start/move.
-    // Regardless, whoever is the responder after any potential transfer, we
-    // direct all touch start/move/ends to them in the form of
-    // `onResponderMove/Start/End`. These will be called for *every* additional
-    // finger that move/start/end, dispatched directly to whoever is the
-    // current responder at that moment, until the responder is "released".
-    //
-    // These multiple individual change touch events are are always bookended
-    // by `onResponderGrant`, and one of
-    // (`onResponderRelease/onResponderTerminate`).
-    var isResponderTouchStart = responderID && isStartish(topLevelType);
-    var isResponderTouchMove = responderID && isMoveish(topLevelType);
-    var isResponderTouchEnd = responderID && isEndish(topLevelType);
-    var incrementalTouch =
-      isResponderTouchStart ? eventTypes.responderStart :
-      isResponderTouchMove ? eventTypes.responderMove :
-      isResponderTouchEnd ? eventTypes.responderEnd :
-      null;
-
-    if (incrementalTouch) {
-      var gesture =
-        ResponderSyntheticEvent.getPooled(
-          incrementalTouch,
-          responderID,
-          nativeEvent,
-          nativeEventTarget
-        );
-      gesture.touchHistory = ResponderTouchHistoryStore.touchHistory;
-      EventPropagators.accumulateDirectDispatches(gesture);
-      extracted = accumulate(extracted, gesture);
-    }
-
-    var isResponderTerminate =
-      responderID &&
-      topLevelType === EventConstants.topLevelTypes.topTouchCancel;
-    var isResponderRelease =
-      responderID &&
-      !isResponderTerminate &&
-      isEndish(topLevelType) &&
-      noResponderTouches(nativeEvent);
-    var finalTouch =
-      isResponderTerminate ? eventTypes.responderTerminate :
-      isResponderRelease ? eventTypes.responderRelease :
-      null;
-    if (finalTouch) {
-      var finalEvent =
-        ResponderSyntheticEvent.getPooled(finalTouch, responderID, nativeEvent, nativeEventTarget);
-      finalEvent.touchHistory = ResponderTouchHistoryStore.touchHistory;
-      EventPropagators.accumulateDirectDispatches(finalEvent);
-      extracted = accumulate(extracted, finalEvent);
-      changeResponder(null);
-    }
-
-    var numberActiveTouches =
-      ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
-    if (ResponderEventPlugin.GlobalInteractionHandler &&
-      numberActiveTouches !== previousActiveTouches) {
-      ResponderEventPlugin.GlobalInteractionHandler.onChange(
-        numberActiveTouches
-      );
-    }
-    previousActiveTouches = numberActiveTouches;
-
-    return extracted;
-  },
-
-  GlobalResponderHandler: null,
-  GlobalInteractionHandler: null,
-
-  injection: {
-    /**
-     * @param {{onChange: (ReactID, ReactID) => void} GlobalResponderHandler
-     * Object that handles any change in responder. Use this to inject
-     * integration with an existing touch handling system etc.
-     */
-    injectGlobalResponderHandler: function(GlobalResponderHandler) {
-      ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
-    },
-
-    /**
-     * @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
-     * Object that handles any change in the number of active touches.
-     */
-    injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
-      ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
-    },
-  },
-};
-
-module.exports = ResponderEventPlugin;
diff --git a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
index b91feba9b1e2..ca7faa7fecfe 100644
--- a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
+++ b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
@@ -4,7 +4,7 @@
 
 var ReactNativeTagHandles = require('ReactNativeTagHandles');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var UniversalWorkerNodeHandle = {
   getRootNodeID: function(nodeHandle) {
diff --git a/Libraries/vendor/tinycolor/tinycolor.js b/Libraries/vendor/tinycolor/tinycolor.js
deleted file mode 100755
index 7035bc166fba..000000000000
--- a/Libraries/vendor/tinycolor/tinycolor.js
+++ /dev/null
@@ -1,503 +0,0 @@
-/**
- * @providesModule tinycolor
- * @nolint
- */
-'use strict';
-
-// TinyColor v1.2.1
-// https://github.com/bgrins/TinyColor
-// Brian Grinstead, MIT License
-
-var trimLeft = /^[\s,#]+/,
-    trimRight = /\s+$/,
-    tinyCounter = 0,
-    mathRound = Math.round,
-    mathMin = Math.min,
-    mathMax = Math.max;
-
-function tinycolor (color, opts) {
-    // If we are called as a function, call using new instead
-    if (!(this instanceof tinycolor)) {
-        return new tinycolor(color, opts);
-    }
-
-    color = (color) ? color : '';
-    opts = opts || { };
-
-    var rgb = inputToRGB(color);
-    this._r = rgb.r,
-    this._g = rgb.g,
-    this._b = rgb.b,
-    this._a = rgb.a,
-
-    this._ok = rgb.ok;
-}
-
-tinycolor.prototype = {
-    toRgb: function() {
-        return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
-    },
-    isValid: function() {
-        return this._ok;
-    },
-};
-
-// Given a string or object, convert that input to RGB
-// Possible string inputs:
-//
-//     "red"
-//     "#f00" or "f00"
-//     "#f00f" or "f00f"
-//     "#ff0000" or "ff0000"
-//     "#ff0000ff" or "ff0000ff"
-//     "rgb 255 0 0" or "rgb (255, 0, 0)"
-//     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
-//     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
-//     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
-//     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
-//     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
-//     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
-//
-function inputToRGB(color) {
-    var rgb = { r: 0, g: 0, b: 0 };
-    var a = 1;
-    var ok = false;
-    var format = false;
-
-    if (typeof color == "string") {
-        color = stringInputToObject(color);
-    }
-
-    if (typeof color == "object") {
-        if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
-            rgb = rgbToRgb(color.r, color.g, color.b);
-            ok = true;
-        }
-        else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
-            color.s = convertToPercentage(color.s);
-            color.v = convertToPercentage(color.v);
-            rgb = hsvToRgb(color.h, color.s, color.v);
-            ok = true;
-        }
-        else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
-            color.s = convertToPercentage(color.s);
-            color.l = convertToPercentage(color.l);
-            rgb = hslToRgb(color.h, color.s, color.l);
-            ok = true;
-        }
-
-        if (color.hasOwnProperty("a")) {
-            a = color.a;
-        }
-    }
-
-    a = boundAlpha(a);
-
-    return {
-        ok: ok,
-        r: mathMin(255, mathMax(rgb.r, 0)),
-        g: mathMin(255, mathMax(rgb.g, 0)),
-        b: mathMin(255, mathMax(rgb.b, 0)),
-        a: a
-    };
-}
-
-
-// Conversion Functions
-// --------------------
-
-// `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
-// <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
-
-// `rgbToRgb`
-// Handle bounds / percentage checking to conform to CSS color spec
-// <http://www.w3.org/TR/css3-color/>
-// *Assumes:* r, g, b in [0, 255] or [0, 1]
-// *Returns:* { r, g, b } in [0, 255]
-function rgbToRgb(r, g, b){
-    return {
-        r: bound01(r, 255) * 255,
-        g: bound01(g, 255) * 255,
-        b: bound01(b, 255) * 255
-    };
-}
-
-// `hslToRgb`
-// Converts an HSL color value to RGB.
-// *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
-// *Returns:* { r, g, b } in the set [0, 255]
-function hslToRgb(h, s, l) {
-    var r, g, b;
-
-    h = bound01(h, 360);
-    s = bound01(s, 100);
-    l = bound01(l, 100);
-
-    function hue2rgb(p, q, t) {
-        if(t < 0) t += 1;
-        if(t > 1) t -= 1;
-        if(t < 1/6) return p + (q - p) * 6 * t;
-        if(t < 1/2) return q;
-        if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
-        return p;
-    }
-
-    if(s === 0) {
-        r = g = b = l; // achromatic
-    }
-    else {
-        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
-        var p = 2 * l - q;
-        r = hue2rgb(p, q, h + 1/3);
-        g = hue2rgb(p, q, h);
-        b = hue2rgb(p, q, h - 1/3);
-    }
-
-    return { r: r * 255, g: g * 255, b: b * 255 };
-}
-
-// `hsvToRgb`
-// Converts an HSV color value to RGB.
-// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
-// *Returns:* { r, g, b } in the set [0, 255]
- function hsvToRgb(h, s, v) {
-    h = bound01(h, 360) * 6;
-    s = bound01(s, 100);
-    v = bound01(v, 100);
-
-    var i = math.floor(h),
-        f = h - i,
-        p = v * (1 - s),
-        q = v * (1 - f * s),
-        t = v * (1 - (1 - f) * s),
-        mod = i % 6,
-        r = [v, q, p, p, t, v][mod],
-        g = [t, v, v, q, p, p][mod],
-        b = [p, p, t, v, v, q][mod];
-
-    return { r: r * 255, g: g * 255, b: b * 255 };
-}
-
-// Big List of Colors
-// ------------------
-// <http://www.w3.org/TR/css3-color/#svg-color>
-var names = tinycolor.names = {
-    aliceblue: "f0f8ff",
-    antiquewhite: "faebd7",
-    aqua: "0ff",
-    aquamarine: "7fffd4",
-    azure: "f0ffff",
-    beige: "f5f5dc",
-    bisque: "ffe4c4",
-    black: "000",
-    blanchedalmond: "ffebcd",
-    blue: "00f",
-    blueviolet: "8a2be2",
-    brown: "a52a2a",
-    burlywood: "deb887",
-    burntsienna: "ea7e5d",
-    cadetblue: "5f9ea0",
-    chartreuse: "7fff00",
-    chocolate: "d2691e",
-    coral: "ff7f50",
-    cornflowerblue: "6495ed",
-    cornsilk: "fff8dc",
-    crimson: "dc143c",
-    cyan: "0ff",
-    darkblue: "00008b",
-    darkcyan: "008b8b",
-    darkgoldenrod: "b8860b",
-    darkgray: "a9a9a9",
-    darkgreen: "006400",
-    darkgrey: "a9a9a9",
-    darkkhaki: "bdb76b",
-    darkmagenta: "8b008b",
-    darkolivegreen: "556b2f",
-    darkorange: "ff8c00",
-    darkorchid: "9932cc",
-    darkred: "8b0000",
-    darksalmon: "e9967a",
-    darkseagreen: "8fbc8f",
-    darkslateblue: "483d8b",
-    darkslategray: "2f4f4f",
-    darkslategrey: "2f4f4f",
-    darkturquoise: "00ced1",
-    darkviolet: "9400d3",
-    deeppink: "ff1493",
-    deepskyblue: "00bfff",
-    dimgray: "696969",
-    dimgrey: "696969",
-    dodgerblue: "1e90ff",
-    firebrick: "b22222",
-    floralwhite: "fffaf0",
-    forestgreen: "228b22",
-    fuchsia: "f0f",
-    gainsboro: "dcdcdc",
-    ghostwhite: "f8f8ff",
-    gold: "ffd700",
-    goldenrod: "daa520",
-    gray: "808080",
-    green: "008000",
-    greenyellow: "adff2f",
-    grey: "808080",
-    honeydew: "f0fff0",
-    hotpink: "ff69b4",
-    indianred: "cd5c5c",
-    indigo: "4b0082",
-    ivory: "fffff0",
-    khaki: "f0e68c",
-    lavender: "e6e6fa",
-    lavenderblush: "fff0f5",
-    lawngreen: "7cfc00",
-    lemonchiffon: "fffacd",
-    lightblue: "add8e6",
-    lightcoral: "f08080",
-    lightcyan: "e0ffff",
-    lightgoldenrodyellow: "fafad2",
-    lightgray: "d3d3d3",
-    lightgreen: "90ee90",
-    lightgrey: "d3d3d3",
-    lightpink: "ffb6c1",
-    lightsalmon: "ffa07a",
-    lightseagreen: "20b2aa",
-    lightskyblue: "87cefa",
-    lightslategray: "789",
-    lightslategrey: "789",
-    lightsteelblue: "b0c4de",
-    lightyellow: "ffffe0",
-    lime: "0f0",
-    limegreen: "32cd32",
-    linen: "faf0e6",
-    magenta: "f0f",
-    maroon: "800000",
-    mediumaquamarine: "66cdaa",
-    mediumblue: "0000cd",
-    mediumorchid: "ba55d3",
-    mediumpurple: "9370db",
-    mediumseagreen: "3cb371",
-    mediumslateblue: "7b68ee",
-    mediumspringgreen: "00fa9a",
-    mediumturquoise: "48d1cc",
-    mediumvioletred: "c71585",
-    midnightblue: "191970",
-    mintcream: "f5fffa",
-    mistyrose: "ffe4e1",
-    moccasin: "ffe4b5",
-    navajowhite: "ffdead",
-    navy: "000080",
-    oldlace: "fdf5e6",
-    olive: "808000",
-    olivedrab: "6b8e23",
-    orange: "ffa500",
-    orangered: "ff4500",
-    orchid: "da70d6",
-    palegoldenrod: "eee8aa",
-    palegreen: "98fb98",
-    paleturquoise: "afeeee",
-    palevioletred: "db7093",
-    papayawhip: "ffefd5",
-    peachpuff: "ffdab9",
-    peru: "cd853f",
-    pink: "ffc0cb",
-    plum: "dda0dd",
-    powderblue: "b0e0e6",
-    purple: "800080",
-    rebeccapurple: "663399",
-    red: "f00",
-    rosybrown: "bc8f8f",
-    royalblue: "4169e1",
-    saddlebrown: "8b4513",
-    salmon: "fa8072",
-    sandybrown: "f4a460",
-    seagreen: "2e8b57",
-    seashell: "fff5ee",
-    sienna: "a0522d",
-    silver: "c0c0c0",
-    skyblue: "87ceeb",
-    slateblue: "6a5acd",
-    slategray: "708090",
-    slategrey: "708090",
-    snow: "fffafa",
-    springgreen: "00ff7f",
-    steelblue: "4682b4",
-    tan: "d2b48c",
-    teal: "008080",
-    thistle: "d8bfd8",
-    tomato: "ff6347",
-    turquoise: "40e0d0",
-    violet: "ee82ee",
-    wheat: "f5deb3",
-    white: "fff",
-    whitesmoke: "f5f5f5",
-    yellow: "ff0",
-    yellowgreen: "9acd32"
-};
-
-// Return a valid alpha value [0,1] with all invalid values being set to 1
-function boundAlpha(a) {
-    a = parseFloat(a);
-
-    if (isNaN(a) || a < 0 || a > 1) {
-        a = 1;
-    }
-
-    return a;
-}
-
-// Take input from [0, n] and return it as [0, 1]
-function bound01(n, max) {
-    if (isOnePointZero(n)) { n = "100%"; }
-
-    var processPercent = isPercentage(n);
-    n = mathMin(max, mathMax(0, parseFloat(n)));
-
-    // Automatically convert percentage into number
-    if (processPercent) {
-        n = parseInt(n * max, 10) / 100;
-    }
-
-    // Handle floating point rounding errors
-    if ((Math.abs(n - max) < 0.000001)) {
-        return 1;
-    }
-
-    // Convert into [0, 1] range if it isn't already
-    return (n % max) / parseFloat(max);
-}
-
-// Parse a base-16 hex value into a base-10 integer
-function parseIntFromHex(val) {
-    return parseInt(val, 16);
-}
-
-// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
-// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
-function isOnePointZero(n) {
-    return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
-}
-
-// Check to see if string passed in is a percentage
-function isPercentage(n) {
-    return typeof n === "string" && n.indexOf('%') != -1;
-}
-
-// Replace a decimal with it's percentage value
-function convertToPercentage(n) {
-    if (n <= 1) {
-        n = (n * 100) + "%";
-    }
-
-    return n;
-}
-
-// Converts a hex value to a decimal
-function convertHexToDecimal(h) {
-    return (parseIntFromHex(h) / 255);
-}
-
-var matchers = (function() {
-    // <http://www.w3.org/TR/css3-values/#integers>
-    var CSS_INTEGER = "[-\\+]?\\d+%?";
-
-    // <http://www.w3.org/TR/css3-values/#number-value>
-    var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
-
-    // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
-    var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
-
-    // Actual matching.
-    // Parentheses and commas are optional, but not required.
-    // Whitespace can take the place of commas or opening paren
-    var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
-    var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
-
-    return {
-        rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
-        rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
-        hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
-        hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
-        hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
-        hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
-        hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
-        hex4: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
-        hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
-        hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
-    };
-})();
-
-// `stringInputToObject`
-// Permissive string parsing.  Take in a number of formats, and output an object
-// based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
-function stringInputToObject(color) {
-    color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase();
-    var named = false;
-    if (names[color]) {
-        color = names[color];
-        named = true;
-    }
-    else if (color == 'transparent') {
-        return { r: 0, g: 0, b: 0, a: 0, format: "name" };
-    }
-
-    // Try to match string input using regular expressions.
-    // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
-    // Just return an object and let the conversion functions handle that.
-    // This way the result will be the same whether the tinycolor is initialized with string or object.
-    var match;
-    if ((match = matchers.rgb.exec(color))) {
-        return { r: match[1], g: match[2], b: match[3] };
-    }
-    if ((match = matchers.rgba.exec(color))) {
-        return { r: match[1], g: match[2], b: match[3], a: match[4] };
-    }
-    if ((match = matchers.hsl.exec(color))) {
-        return { h: match[1], s: match[2], l: match[3] };
-    }
-    if ((match = matchers.hsla.exec(color))) {
-        return { h: match[1], s: match[2], l: match[3], a: match[4] };
-    }
-    if ((match = matchers.hsv.exec(color))) {
-        return { h: match[1], s: match[2], v: match[3] };
-    }
-    if ((match = matchers.hsva.exec(color))) {
-        return { h: match[1], s: match[2], v: match[3], a: match[4] };
-    }
-    if ((match = matchers.hex8.exec(color))) {
-        return {
-            r: parseIntFromHex(match[1]),
-            g: parseIntFromHex(match[2]),
-            b: parseIntFromHex(match[3]),
-            a: convertHexToDecimal(match[4]),
-            format: named ? "name" : "hex"
-        };
-    }
-    if ((match = matchers.hex6.exec(color))) {
-        return {
-            r: parseIntFromHex(match[1]),
-            g: parseIntFromHex(match[2]),
-            b: parseIntFromHex(match[3]),
-            format: named ? "name" : "hex"
-        };
-    }
-    if ((match = matchers.hex4.exec(color))) {
-        return {
-            r: parseIntFromHex(match[1] + '' + match[1]),
-            g: parseIntFromHex(match[2] + '' + match[2]),
-            b: parseIntFromHex(match[3] + '' + match[3]),
-            a: convertHexToDecimal(match[4] + '' + match[4]),
-            format: named ? "name" : "hex"
-        };
-    }
-    if ((match = matchers.hex3.exec(color))) {
-        return {
-            r: parseIntFromHex(match[1] + '' + match[1]),
-            g: parseIntFromHex(match[2] + '' + match[2]),
-            b: parseIntFromHex(match[3] + '' + match[3]),
-            format: named ? "name" : "hex"
-        };
-    }
-
-    return false;
-}
-
-module.exports = tinycolor;
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000000..3b3d851da6a8
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,15 @@
+Hey there and thanks for submitting a pull request! Please have a look at the following checklist so that others have enough information to review your pull request:
+
+Explain the **motivation** for making this change. What existing problem does the pull request solve?
+
+Example: When saying "Add a function to do X", explain why it is necessary to have a way to do X.
+
+**Test plan (required)**
+
+Describe exact steps you've done to test that the code works. This means exact commands you ran, screenshots / videos if the pull request changes UI.
+
+Make sure tests pass on Circle CI.
+
+**Code formatting**
+
+See the simple [style guide](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#style-guide).
diff --git a/README.md b/README.md
index b7eff88088fc..04dbd5e4b03b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# React Native [![Build Status](https://travis-ci.org/facebook/react-native.svg?branch=master)](https://travis-ci.org/facebook/react-native)  [![npm version](https://badge.fury.io/js/react-native.svg)](http://badge.fury.io/js/react-native)
+# React Native [![Build Status](https://travis-ci.org/facebook/react-native.svg?branch=master)](https://travis-ci.org/facebook/react-native) [![Circle CI](https://circleci.com/gh/facebook/react-native.svg?style=svg)](https://circleci.com/gh/facebook/react-native) [![npm version](https://badge.fury.io/js/react-native.svg)](http://badge.fury.io/js/react-native)
 
 React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and [React](http://facebook.github.io/react). The focus of React Native is on developer efficiency across all the platforms you care about - learn once, write anywhere. Facebook uses React Native in multiple production apps and will continue investing in React Native.
 
@@ -71,7 +71,7 @@ Note that you'll need the Android NDK installed, see [prerequisites](https://git
 
 ## Extending React Native
 
-- Looking for a component? [react.parts](http://react.parts/)
+- Looking for a component? [JS.coach](https://js.coach/react-native)
 - Fellow developers write and publish React Native modules to npm and open source them on GitHub.
 - Making modules helps grow the React Native ecosystem and community. We recommend writing modules for your use cases and sharing them on npm.
 - Read the guides on Native Modules ([iOS](http://facebook.github.io/react-native/docs/native-modules-ios.html), [Android](http://facebook.github.io/react-native/docs/native-modules-android.html)) and Native UI Components ([iOS](http://facebook.github.io/react-native/docs/native-components-ios.html), [Android](http://facebook.github.io/react-native/docs/native-components-android.html)) if you are interested in extending native functionality.
@@ -90,6 +90,8 @@ The GitHub issues are intended for bug reports and feature requests. For help an
 
 For more information about contributing PRs and issues, see our [Contribution Guidelines](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md).
 
+[Good First Task](https://github.com/facebook/react-native/labels/Good%20First%20Task) is a great starting point for PRs.
+
 We encourage the community to ask and answer questions on Stack Overflow with [the react-native tag](https://stackoverflow.com/questions/tagged/react-native). It's a great way to help out and be involved!
 
 ## License
diff --git a/React.podspec b/React.podspec
index d8991c8ed29a..4891cb3b247f 100644
--- a/React.podspec
+++ b/React.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name                = "React"
-  s.version             = "0.15.0"
+  s.version             = "0.0.0-master"
   s.summary             = "Build high quality mobile apps using React."
   s.description         = <<-DESC
                             React Native apps are built using the React JS
@@ -22,7 +22,6 @@ Pod::Spec.new do |s|
   s.default_subspec     = 'Core'
   s.requires_arc        = true
   s.platform            = :ios, "7.0"
-  s.prepare_command     = 'npm install --production'
   s.preserve_paths      = "cli.js", "Libraries/**/*.js", "lint", "linter.js", "node_modules", "package.json", "packager", "PATENTS", "react-native-cli"
 
   s.subspec 'Core' do |ss|
@@ -49,6 +48,12 @@ Pod::Spec.new do |s|
     ss.preserve_paths   = "Libraries/AdSupport/*.js"
   end
 
+  s.subspec 'RCTCameraRoll' do |ss|
+    ss.dependency         'React/Core'
+    ss.source_files     = "Libraries/CameraRoll/*.{h,m}"
+    ss.preserve_paths   = "Libraries/CameraRoll/*.js"
+  end
+
   s.subspec 'RCTGeolocation' do |ss|
     ss.dependency         'React/Core'
     ss.source_files     = "Libraries/Geolocation/*.{h,m}"
diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m
index 03e40c9308fb..d0006e218f30 100644
--- a/React/Base/RCTAssert.m
+++ b/React/Base/RCTAssert.m
@@ -119,7 +119,7 @@ void _RCTAssertFormat(
 
 void RCTFatal(NSError *error)
 {
-  _RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", [error localizedDescription]);
+  _RCTLogNativeInternal(RCTLogLevelFatal, NULL, 0, @"%@", error.localizedDescription);
 
   RCTFatalHandler fatalHandler = RCTGetFatalHandler();
   if (fatalHandler) {
@@ -128,8 +128,9 @@ void RCTFatal(NSError *error)
 #if DEBUG
     @try {
 #endif
-      NSString *message = RCTFormatError([error localizedDescription], error.userInfo[RCTJSStackTraceKey], 75);
-      [NSException raise:RCTFatalExceptionName format:@"%@", message];
+      NSString *name = [NSString stringWithFormat:@"%@: %@", RCTFatalExceptionName, error.localizedDescription];
+      NSString *message = RCTFormatError(error.localizedDescription, error.userInfo[RCTJSStackTraceKey], 75);
+      [NSException raise:name format:@"%@", message];
 #if DEBUG
     } @catch (NSException *e) {}
 #endif
@@ -160,5 +161,5 @@ RCTFatalHandler RCTGetFatalHandler(void)
     }
   }
 
-  return [NSString stringWithFormat:@"Message: %@%@", message, prettyStack];
+  return [NSString stringWithFormat:@"%@%@", message, prettyStack];
 }
diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index 66f1f737d6a4..3154e4813c7c 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -11,64 +11,60 @@
 
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTBridgeMethod.h"
 #import "RCTConvert.h"
-#import "RCTContextExecutor.h"
+#import "RCTJSCExecutor.h"
 #import "RCTFrameUpdate.h"
 #import "RCTJavaScriptLoader.h"
 #import "RCTLog.h"
 #import "RCTModuleData.h"
-#import "RCTModuleMap.h"
 #import "RCTPerformanceLogger.h"
 #import "RCTProfile.h"
 #import "RCTSourceCode.h"
 #import "RCTUtils.h"
 
 #define RCTAssertJSThread() \
-  RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTContextExecutor"] || \
+  RCTAssert(![NSStringFromClass([_javaScriptExecutor class]) isEqualToString:@"RCTJSCExecutor"] || \
               [[[NSThread currentThread] name] isEqualToString:@"com.facebook.React.JavaScript"], \
             @"This method must be called on JS thread")
 
-NSString *const RCTEnqueueNotification = @"RCTEnqueueNotification";
-NSString *const RCTDequeueNotification = @"RCTDequeueNotification";
-
 /**
  * Must be kept in sync with `MessageQueue.js`.
  */
 typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
   RCTBridgeFieldRequestModuleIDs = 0,
   RCTBridgeFieldMethodIDs,
-  RCTBridgeFieldParamss,
+  RCTBridgeFieldParams,
+  RCTBridgeFieldCallID,
 };
 
 RCT_EXTERN NSArray<Class> *RCTGetModuleClasses(void);
 
-@interface RCTBridge ()
-
-+ (instancetype)currentBridge;
-+ (void)setCurrentBridge:(RCTBridge *)bridge;
-
-@end
-
 @interface RCTBatchedBridge : RCTBridge
 
 @property (nonatomic, weak) RCTBridge *parentBridge;
+@property (nonatomic, weak) id<RCTJavaScriptExecutor> javaScriptExecutor;
+@property (nonatomic, assign) BOOL moduleSetupComplete;
 
 @end
 
 @implementation RCTBatchedBridge
 {
-  BOOL _loading;
-  BOOL _valid;
   BOOL _wasBatchActive;
-  __weak id<RCTJavaScriptExecutor> _javaScriptExecutor;
-  NSMutableArray<NSArray *> *_pendingCalls;
-  NSMutableArray<RCTModuleData *> *_moduleDataByID;
-  RCTModuleMap *_modulesByName;
+  NSMutableArray<dispatch_block_t> *_pendingCalls;
+  NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
+  NSArray<RCTModuleData *> *_moduleDataByID;
+  NSArray<Class> *_moduleClassesByID;
   CADisplayLink *_jsDisplayLink;
   NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
 }
 
+@synthesize flowID = _flowID;
+@synthesize flowIDMap = _flowIDMap;
+@synthesize loading = _loading;
+@synthesize valid = _valid;
+
 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
 {
   RCTAssertMainThread();
@@ -86,15 +82,14 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge
     _valid = YES;
     _loading = YES;
     _pendingCalls = [NSMutableArray new];
-    _moduleDataByID = [NSMutableArray new];
     _frameUpdateObservers = [NSMutableSet new];
     _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
 
     [RCTBridge setCurrentBridge:self];
 
-    [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptWillStartLoadingNotification
-                                                        object:self
-                                                      userInfo:@{ @"bridge": self }];
+    [[NSNotificationCenter defaultCenter]
+     postNotificationName:RCTJavaScriptWillStartLoadingNotification
+     object:_parentBridge userInfo:@{@"bridge": self}];
 
     [self start];
   }
@@ -106,6 +101,8 @@ - (void)start
   dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);
 
   dispatch_group_t initModulesAndLoadSource = dispatch_group_create();
+
+  // Asynchronously load source code
   dispatch_group_enter(initModulesAndLoadSource);
   __weak RCTBatchedBridge *weakSelf = self;
   __block NSData *sourceCode;
@@ -120,8 +117,8 @@ - (void)start
     dispatch_group_leave(initModulesAndLoadSource);
   }];
 
-  // Synchronously initialize all native modules
-  [self initModules];
+  // Synchronously initialize all native modules that cannot be loaded lazily
+  [self initModulesWithDispatchGroup:initModulesAndLoadSource];
 
   if (RCTProfileIsProfiling()) {
     // Depends on moduleDataByID being loaded
@@ -132,12 +129,15 @@ - (void)start
   dispatch_group_enter(initModulesAndLoadSource);
   dispatch_async(bridgeQueue, ^{
     dispatch_group_t setupJSExecutorAndModuleConfig = dispatch_group_create();
+
+    // Asynchronously initialize the JS executor
     dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
-      [weakSelf setupExecutor];
+      [weakSelf setUpExecutor];
     });
 
+    // Asynchronously gather the module config
     dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
-      if (weakSelf.isValid) {
+      if (weakSelf.valid) {
         RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
         config = [weakSelf moduleConfig];
         RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
@@ -145,9 +145,9 @@ - (void)start
     });
 
     dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
-      // We're not waiting for this complete to leave the dispatch group, since
-      // injectJSONConfiguration and executeSourceCode will schedule operations on the
-      // same queue anyway.
+      // We're not waiting for this to complete to leave dispatch group, since
+      // injectJSONConfiguration and executeSourceCode will schedule operations
+      // on the same queue anyway.
       RCTPerformanceLoggerStart(RCTPLNativeModuleInjectConfig);
       [weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
         RCTPerformanceLoggerEnd(RCTPLNativeModuleInjectConfig);
@@ -164,9 +164,7 @@ - (void)start
   dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
     RCTBatchedBridge *strongSelf = weakSelf;
     if (sourceCode && strongSelf.loading) {
-      dispatch_async(bridgeQueue, ^{
-        [weakSelf executeSourceCode:sourceCode];
-      });
+      [strongSelf executeSourceCode:sourceCode];
     }
   });
 }
@@ -176,8 +174,11 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
   RCTPerformanceLoggerStart(RCTPLScriptDownload);
   NSUInteger cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil);
 
+  // Suppress a warning if RCTProfileBeginAsyncEvent gets compiled out
+  (void)cookie;
+
   RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSData *source) {
-    RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil);
+    RCTProfileEndAsyncEvent(0, @"native", cookie, @"JavaScript download", @"JS async", nil);
     RCTPerformanceLoggerEnd(RCTPLScriptDownload);
 
     _onSourceLoad(error, source);
@@ -191,22 +192,53 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
     // Allow testing without a script
     dispatch_async(dispatch_get_main_queue(), ^{
       [self didFinishLoading];
-      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
-                                                          object:_parentBridge
-                                                        userInfo:@{ @"bridge": self }];
+      [[NSNotificationCenter defaultCenter]
+       postNotificationName:RCTJavaScriptDidLoadNotification
+       object:_parentBridge userInfo:@{@"bridge": self}];
     });
     onSourceLoad(nil, nil);
   }
 }
 
-- (void)initModules
+- (NSArray<Class> *)moduleClasses
+{
+  if (RCT_DEBUG && _valid && _moduleClassesByID == nil) {
+    RCTLogError(@"Bridge modules have not yet been initialized. You may be "
+                "trying to access a module too early in the startup procedure.");
+  }
+  return _moduleClassesByID;
+}
+
+/**
+ * Used by RCTUIManager
+ */
+- (RCTModuleData *)moduleDataForName:(NSString *)moduleName
+{
+  return _moduleDataByName[moduleName];
+}
+
+- (id)moduleForName:(NSString *)moduleName
+{
+  return _moduleDataByName[moduleName].instance;
+}
+
+- (NSArray *)configForModuleName:(NSString *)moduleName
+{
+  RCTModuleData *moduleData = _moduleDataByName[moduleName];
+  if (!moduleData) {
+    moduleData = _moduleDataByName[[@"RCT" stringByAppendingString:moduleName]];
+  }
+  if (moduleData) {
+    return moduleData.config;
+  }
+  return (id)kCFNull;
+}
+
+- (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
 {
   RCTAssertMainThread();
   RCTPerformanceLoggerStart(RCTPLNativeModuleInit);
 
-  // Register passed-in module instances
-  NSMutableDictionary *preregisteredModules = [NSMutableDictionary new];
-
   NSArray<id<RCTBridgeModule>> *extraModules = nil;
   if (self.delegate) {
     if ([self.delegate respondsToSelector:@selector(extraModulesForBridge:)]) {
@@ -216,78 +248,150 @@ - (void)initModules
     extraModules = self.moduleProvider();
   }
 
+  if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
+
+    // Check for unexported modules
+    static Class *classes;
+    static unsigned int classCount;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+      classes = objc_copyClassList(&classCount);
+    });
+
+    NSMutableSet *moduleClasses = [NSMutableSet new];
+    [moduleClasses addObjectsFromArray:RCTGetModuleClasses()];
+    [moduleClasses addObjectsFromArray:[extraModules valueForKeyPath:@"class"]];
+
+    for (unsigned int i = 0; i < classCount; i++)
+    {
+      Class cls = classes[i];
+      Class superclass = cls;
+      while (superclass)
+      {
+        if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
+        {
+          if (![moduleClasses containsObject:cls]) {
+            RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
+                       "RCT_EXPORT_MODULE()?", cls);
+          }
+          break;
+        }
+        superclass = class_getSuperclass(superclass);
+      }
+    }
+  }
+
+  NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
+  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
+  NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
+
+  // Set up moduleData for pre-initialized module instances
   for (id<RCTBridgeModule> module in extraModules) {
-    preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
+    Class moduleClass = [module class];
+    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
+
+    if (RCT_DEBUG) {
+      // Check for name collisions between preregistered modules
+      RCTModuleData *moduleData = moduleDataByName[moduleName];
+      if (moduleData) {
+        RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
+                    "name '%@', but name was already registered by class %@",
+                    moduleClass, moduleName, moduleData.moduleClass);
+        continue;
+      }
+    }
+
+    // Instantiate moduleData container
+    RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
+                                                                       bridge:self];
+    moduleDataByName[moduleName] = moduleData;
+    [moduleClassesByID addObject:moduleClass];
+    [moduleDataByID addObject:moduleData];
   }
 
-  // Instantiate modules
-  _moduleDataByID = [NSMutableArray new];
-  NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
+  // Set up moduleData for automatically-exported modules
   for (Class moduleClass in RCTGetModuleClasses()) {
     NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
 
-     // Check if module instance has already been registered for this name
-     id<RCTBridgeModule> module = modulesByName[moduleName];
-
-     if (module) {
-       // Preregistered instances takes precedence, no questions asked
-       if (!preregisteredModules[moduleName]) {
-         // It's OK to have a name collision as long as the second instance is nil
-         RCTAssert([moduleClass new] == nil,
-                   @"Attempted to register RCTBridgeModule class %@ for the name "
-                   "'%@', but name was already registered by class %@", moduleClass,
-                   moduleName, [modulesByName[moduleName] class]);
-       }
-     } else {
-       // Module name hasn't been used before, so go ahead and instantiate
-       module = [moduleClass new];
-     }
-     if (module) {
-       modulesByName[moduleName] = module;
-     }
+    // Check for module name collisions
+    RCTModuleData *moduleData = moduleDataByName[moduleName];
+    if (moduleData) {
+      if (moduleData.hasInstance) {
+        // Existing module was preregistered, so it takes precedence
+        continue;
+      } else if ([moduleClass new] == nil) {
+        // The new module returned nil from init, so use the old module
+        continue;
+      } else if ([moduleData.moduleClass new] != nil) {
+        // Both modules were non-nil, so it's unclear which should take precedence
+        RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
+                    "name '%@', but name was already registered by class %@",
+                    moduleClass, moduleName, moduleData.moduleClass);
+      }
+    }
+    
+    // Instantiate moduleData (TODO: can we defer this until config generation?)
+    moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
+                                                     bridge:self];
+    moduleDataByName[moduleName] = moduleData;
+    [moduleClassesByID addObject:moduleClass];
+    [moduleDataByID addObject:moduleData];
   }
 
   // Store modules
-  _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName];
+  _moduleDataByID = [moduleDataByID copy];
+  _moduleDataByName = [moduleDataByName copy];
+  _moduleClassesByID = [moduleClassesByID copy];
 
-  /**
-   * The executor is a bridge module, wait for it to be created and set it before
-   * any other module has access to the bridge
-   */
-  _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)];
+  // The executor is a bridge module, wait for it to be created and set it up
+  // before any other module has access to the bridge.
+  _javaScriptExecutor = [self moduleForClass:self.executorClass];
 
-  for (id<RCTBridgeModule> module in _modulesByName.allValues) {
-    // Bridge must be set before moduleData is set up, as methodQueue
-    // initialization requires it (View Managers get their queue by calling
-    // self.bridge.uiManager.methodQueue)
-    if ([module respondsToSelector:@selector(setBridge:)]) {
-      module.bridge = self;
+  // Dispatch module init onto main thead for those modules that require it
+  for (RCTModuleData *moduleData in _moduleDataByID) {
+    if (moduleData.hasInstance) {
+      // Modules that were pre-initialized need to be set up before bridge init
+      // has finished, otherwise the caller may try to access the module
+      // directly rather than via `[bridge moduleForClass:]`, which won't
+      // trigger the lazy initialization process.
+      (void)[moduleData instance];
+    }
+    if (moduleData.requiresMainThreadSetup) {
+      // Modules that need to be set up on the main thread cannot be initialized
+      // lazily when required without doing a dispatch_sync to the main thread,
+      // which can result in deadlock. To avoid this, we initialize all of these
+      // modules on the main thread in parallel with loading the JS code, so that
+      // they will already be available before they are ever required.
+      __weak RCTBatchedBridge *weakSelf = self;
+      dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
+        if (weakSelf.valid) {
+          (void)[moduleData instance];
+          [moduleData gatherConstants];
+        }
+      });
     }
-
-    RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
-                                                               moduleID:@(_moduleDataByID.count)
-                                                               instance:module];
-    [_moduleDataByID addObject:moduleData];
   }
 
-  [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
-                                                      object:self];
+  // From this point on, RCTDidInitializeModuleNotification notifications will
+  // be sent the first time a module is accessed.
+  _moduleSetupComplete = YES;
+
   RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
 }
 
-- (void)setupExecutor
+- (void)setUpExecutor
 {
   [_javaScriptExecutor setUp];
 }
 
-- (NSString *)moduleConfig
+- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
+                       withModuleData:(RCTModuleData *)moduleData
 {
-  NSMutableArray<NSArray *> *config = [NSMutableArray new];
-  for (RCTModuleData *moduleData in _moduleDataByID) {
-    [config addObject:moduleData.config];
-    if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
+  if (![_frameUpdateObservers containsObject:moduleData]) {
+    if ([moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
       [_frameUpdateObservers addObject:moduleData];
-      id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
+      // Don't access the module instance via moduleData, as this will cause deadlock
+      id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
       __weak typeof(self) weakSelf = self;
       __weak typeof(_javaScriptExecutor) weakJavaScriptExecutor = _javaScriptExecutor;
       observer.pauseCallback = ^{
@@ -297,6 +401,18 @@ - (NSString *)moduleConfig
       };
     }
   }
+}
+
+- (NSString *)moduleConfig
+{
+  NSMutableArray<NSArray *> *config = [NSMutableArray new];
+  for (RCTModuleData *moduleData in _moduleDataByID) {
+    if (self.executorClass == [RCTJSCExecutor class]) {
+      [config addObject:@[moduleData.name]];
+    } else {
+      [config addObject:RCTNullIfNil(moduleData.config)];
+    }
+  }
 
   return RCTJSONStringify(@{
     @"remoteModuleConfig": config,
@@ -321,7 +437,7 @@ - (void)updateJSDisplayLinkState
 - (void)injectJSONConfiguration:(NSString *)configJSON
                      onComplete:(void (^)(NSError *))onComplete
 {
-  if (!self.valid) {
+  if (!_valid) {
     return;
   }
 
@@ -332,16 +448,16 @@ - (void)injectJSONConfiguration:(NSString *)configJSON
 
 - (void)executeSourceCode:(NSData *)sourceCode
 {
-  if (!self.valid || !_javaScriptExecutor) {
+  if (!_valid || !_javaScriptExecutor) {
     return;
   }
 
-  RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
+  RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
   sourceCodeModule.scriptURL = self.bundleURL;
   sourceCodeModule.scriptData = sourceCode;
 
   [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
-    if (!self.isValid) {
+    if (!_valid) {
       return;
     }
 
@@ -353,29 +469,38 @@ - (void)executeSourceCode:(NSData *)sourceCode
     }
 
     // Register the display link to start sending js calls after everything is setup
-    NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
+    NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
     [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
 
     // Perform the state update and notification on the main thread, so we can't run into
     // timing issues with RCTRootView
     dispatch_async(dispatch_get_main_queue(), ^{
       [self didFinishLoading];
-
-      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
-                                                          object:_parentBridge
-                                                        userInfo:@{ @"bridge": self }];
+      [[NSNotificationCenter defaultCenter]
+       postNotificationName:RCTJavaScriptDidLoadNotification
+       object:_parentBridge userInfo:@{@"bridge": self}];
     });
   }];
+
+#if RCT_DEV
+
+  if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
+    NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
+    NSString *host = self.bundleURL.host;
+    NSNumber *port = self.bundleURL.port;
+    [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path, host, RCTNullIfNil(port)]];
+  }
+
+#endif
+
 }
 
 - (void)didFinishLoading
 {
   _loading = NO;
   [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
-    for (NSArray *call in _pendingCalls) {
-      [self _actuallyInvokeAndProcessModule:call[0]
-                                     method:call[1]
-                                  arguments:call[2]];
+    for (dispatch_block_t call in _pendingCalls) {
+      call();
     }
   }];
 }
@@ -384,15 +509,17 @@ - (void)stopLoadingWithError:(NSError *)error
 {
   RCTAssertMainThread();
 
-  if (!self.isValid || !self.loading) {
+  if (!_valid || !_loading) {
     return;
   }
 
   _loading = NO;
+  [_javaScriptExecutor invalidate];
+
+  [[NSNotificationCenter defaultCenter]
+   postNotificationName:RCTJavaScriptDidFailToLoadNotification
+   object:_parentBridge userInfo:@{@"bridge": self, @"error": error}];
 
-  [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
-                                                      object:_parentBridge
-                                                    userInfo:@{@"bridge": self, @"error": error}];
   RCTFatal(error);
 }
 
@@ -413,7 +540,7 @@ - (void)reload
 
 - (Class)executorClass
 {
-  return _parentBridge.executorClass ?: [RCTContextExecutor class];
+  return _parentBridge.executorClass ?: [RCTJSCExecutor class];
 }
 
 - (void)setExecutorClass:(Class)executorClass
@@ -448,24 +575,26 @@ - (BOOL)isValid
   return _valid;
 }
 
-- (NSDictionary *)modules
+- (void)dispatchBlock:(dispatch_block_t)block
+                queue:(dispatch_queue_t)queue
 {
-  if (RCT_DEBUG && self.isValid && _modulesByName == nil) {
-    RCTLogError(@"Bridge modules have not yet been initialized. You may be "
-                "trying to access a module too early in the startup procedure.");
+  if (queue == RCTJSThread) {
+    [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
+  } else if (queue) {
+    dispatch_async(queue, block);
   }
-  return _modulesByName;
 }
 
 #pragma mark - RCTInvalidating
 
 - (void)invalidate
 {
-  if (!self.valid) {
+  if (!_valid) {
     return;
   }
 
   RCTAssertMainThread();
+  RCTAssert(_javaScriptExecutor != nil, @"Can't complete invalidation without a JS executor");
 
   _loading = NO;
   _valid = NO;
@@ -475,17 +604,19 @@ - (void)invalidate
 
   // Invalidate modules
   dispatch_group_t group = dispatch_group_create();
-  for (RCTModuleData *moduleData in _moduleDataByID) {
+  for (RCTModuleData *moduleData in _moduleDataByName.allValues) {
     if (moduleData.instance == _javaScriptExecutor) {
       continue;
     }
 
     if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
-      [moduleData dispatchBlock:^{
+      dispatch_group_enter(group);
+      [self dispatchBlock:^{
         [(id<RCTInvalidating>)moduleData.instance invalidate];
-      } dispatchGroup:group];
+        dispatch_group_leave(group);
+      } queue:moduleData.methodQueue];
     }
-    moduleData.queue = nil;
+    [moduleData invalidate];
   }
 
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
@@ -499,21 +630,25 @@ - (void)invalidate
       if (RCTProfileIsProfiling()) {
         RCTProfileUnhookModules(self);
       }
+
+      _moduleDataByName = nil;
       _moduleDataByID = nil;
-      _modulesByName = nil;
+      _moduleClassesByID = nil;
       _frameUpdateObservers = nil;
+      _pendingCalls = nil;
 
+      if (_flowIDMap != NULL) {
+        CFRelease(_flowIDMap);
+      }
     }];
   });
 }
 
 - (void)logMessage:(NSString *)message level:(NSString *)level
 {
-  if (RCT_DEBUG) {
-    [_javaScriptExecutor executeJSCall:@"RCTLog"
-                                method:@"logIfNoNativeHook"
-                             arguments:@[level, message]
-                              callback:^(__unused id json, __unused NSError *error) {}];
+  if (RCT_DEBUG && [_javaScriptExecutor isValid]) {
+    [self enqueueJSCall:@"RCTLog.logIfNoNativeHook"
+                   args:@[level, message]];
   }
 }
 
@@ -524,11 +659,70 @@ - (void)logMessage:(NSString *)message level:(NSString *)level
  */
 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
 {
+  /**
+   * AnyThread
+   */
+
+  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge enqueueJSCall:]", nil);
+
   NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."];
 
-  [self _invokeAndProcessModule:@"BatchedBridge"
-                         method:@"callFunctionReturnFlushedQueue"
-                      arguments:@[ids[0], ids[1], args ?: @[]]];
+  NSString *module = ids[0];
+  NSString *method = ids[1];
+
+  RCTProfileBeginFlowEvent();
+
+  __weak RCTBatchedBridge *weakSelf = self;
+  [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+    RCTProfileEndFlowEvent();
+
+    RCTBatchedBridge *strongSelf = weakSelf;
+    if (!strongSelf || !strongSelf.valid) {
+      return;
+    }
+
+    if (strongSelf.loading) {
+      dispatch_block_t pendingCall = ^{
+        [weakSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
+      };
+      [strongSelf->_pendingCalls addObject:pendingCall];
+    } else {
+      [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args ?: @[]];
+    }
+  }];
+
+  RCT_PROFILE_END_EVENT(0, @"", nil);
+}
+
+/**
+ * Called by RCTModuleMethod from any thread.
+ */
+- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
+{
+  /**
+   * AnyThread
+   */
+
+  RCTProfileBeginFlowEvent();
+
+  __weak RCTBatchedBridge *weakSelf = self;
+  [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
+    RCTProfileEndFlowEvent();
+
+    RCTBatchedBridge *strongSelf = weakSelf;
+    if (!strongSelf || !strongSelf.valid) {
+      return;
+    }
+
+    if (strongSelf.loading) {
+      dispatch_block_t pendingCall = ^{
+        [weakSelf _actuallyInvokeCallback:cbID arguments:args ?: @[]];
+      };
+      [strongSelf->_pendingCalls addObject:pendingCall];
+    } else {
+      [strongSelf _actuallyInvokeCallback:cbID arguments:args];
+    }
+  }];
 }
 
 /**
@@ -539,9 +733,9 @@ - (void)_immediatelyCallTimer:(NSNumber *)timer
   RCTAssertJSThread();
 
   dispatch_block_t block = ^{
-    [self _actuallyInvokeAndProcessModule:@"BatchedBridge"
-                                   method:@"callFunctionReturnFlushedQueue"
-                                arguments:@[@"JSTimersExecution", @"callTimers", @[@[timer]]]];
+    [self _actuallyInvokeAndProcessModule:@"JSTimersExecution"
+                                   method:@"callTimers"
+                                arguments:@[@[timer]]];
   };
 
   if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
@@ -568,10 +762,7 @@ - (void)enqueueApplicationScript:(NSData *)script
     }
 
     RCT_PROFILE_BEGIN_EVENT(0, @"FetchApplicationScriptCallbacks", nil);
-    [_javaScriptExecutor executeJSCall:@"BatchedBridge"
-                                method:@"flushedQueue"
-                             arguments:@[]
-                              callback:^(id json, NSError *error)
+    [_javaScriptExecutor flushedQueue:^(id json, NSError *error)
      {
        RCT_PROFILE_END_EVENT(0, @"js_call,init", @{
          @"json": RCTNullIfNil(json),
@@ -587,59 +778,48 @@ - (void)enqueueApplicationScript:(NSData *)script
 
 #pragma mark - Payload Generation
 
-/**
- * Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
- * on the JS thread, but only in non-batched mode.
- */
-- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
+- (void)_actuallyInvokeAndProcessModule:(NSString *)module
+                                 method:(NSString *)method
+                              arguments:(NSArray *)args
 {
-  /**
-   * AnyThread
-   */
-
-  RCTProfileBeginFlowEvent();
+  RCTAssertJSThread();
 
-  __weak RCTBatchedBridge *weakSelf = self;
-  [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
-    RCTProfileEndFlowEvent();
+  RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
+    if (error) {
+      RCTFatal(error);
+    }
 
-    RCTBatchedBridge *strongSelf = weakSelf;
-    if (!strongSelf || !strongSelf.valid) {
+    if (!_valid) {
       return;
     }
+    [self handleBuffer:json batchEnded:YES];
+  };
 
-    if (strongSelf.loading) {
-      [strongSelf->_pendingCalls addObject:@[module, method, args]];
-    } else {
-      [strongSelf _actuallyInvokeAndProcessModule:module method:method arguments:args];
-    }
-  }];
+  [_javaScriptExecutor callFunctionOnModule:module
+                                     method:method
+                                  arguments:args
+                                   callback:processResponse];
 }
 
-- (void)_actuallyInvokeAndProcessModule:(NSString *)module
-                                 method:(NSString *)method
-                              arguments:(NSArray *)args
+- (void)_actuallyInvokeCallback:(NSNumber *)cbID
+                      arguments:(NSArray *)args
 {
   RCTAssertJSThread();
 
-  [[NSNotificationCenter defaultCenter] postNotificationName:RCTEnqueueNotification object:nil userInfo:nil];
-
   RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
     if (error) {
       RCTFatal(error);
     }
 
-    if (!self.isValid) {
+    if (!_valid) {
       return;
     }
-    [[NSNotificationCenter defaultCenter] postNotificationName:RCTDequeueNotification object:nil userInfo:nil];
     [self handleBuffer:json batchEnded:YES];
   };
 
-  [_javaScriptExecutor executeJSCall:module
-                              method:method
-                           arguments:args
-                            callback:processResponse];
+  [_javaScriptExecutor invokeCallbackID:cbID
+                              arguments:args
+                               callback:processResponse];
 }
 
 #pragma mark - Payload Processing
@@ -651,6 +831,7 @@ - (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
   if (buffer != nil && buffer != (id)kCFNull) {
     _wasBatchActive = YES;
     [self handleBuffer:buffer];
+    [self partialBatchDidFlush];
   }
 
   if (batchEnded) {
@@ -662,19 +843,25 @@ - (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
   }
 }
 
-- (void)handleBuffer:(NSArray<NSArray *> *)buffer
+- (void)handleBuffer:(NSArray *)buffer
 {
-  NSArray<NSArray *> *requestsArray = [RCTConvert NSArrayArray:buffer];
+  NSArray *requestsArray = [RCTConvert NSArray:buffer];
 
-  if (RCT_DEBUG && requestsArray.count <= RCTBridgeFieldParamss) {
+  if (RCT_DEBUG && requestsArray.count <= RCTBridgeFieldParams) {
     RCTLogError(@"Buffer should contain at least %tu sub-arrays. Only found %tu",
-                RCTBridgeFieldParamss + 1, requestsArray.count);
+                RCTBridgeFieldParams + 1, requestsArray.count);
     return;
   }
 
-  NSArray<NSNumber *> *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
-  NSArray<NSNumber *> *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
-  NSArray<NSArray *> *paramsArrays = requestsArray[RCTBridgeFieldParamss];
+  NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
+  NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
+  NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParams]];
+
+  int64_t callID = -1;
+
+  if (requestsArray.count > 3) {
+    callID = [requestsArray[RCTBridgeFieldCallID] longLongValue];
+  }
 
   if (RCT_DEBUG && (moduleIDs.count != methodIDs.count || moduleIDs.count != paramsArrays.count)) {
     RCTLogError(@"Invalid data message - all must be length: %zd", moduleIDs.count);
@@ -683,15 +870,11 @@ - (void)handleBuffer:(NSArray<NSArray *> *)buffer
 
   NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
                                                   valueOptions:NSPointerFunctionsStrongMemory
-                                                      capacity:_moduleDataByID.count];
+                                                      capacity:_moduleDataByName.count];
 
   [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) {
     RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue];
-    if (RCT_DEBUG) {
-      // verify that class has been registered
-      (void)_modulesByName[moduleData.name];
-    }
-    dispatch_queue_t queue = moduleData.queue;
+    dispatch_queue_t queue = moduleData.methodQueue;
     NSMutableOrderedSet<NSNumber *> *set = [buckets objectForKey:queue];
     if (!set) {
       set = [NSMutableOrderedSet new];
@@ -705,16 +888,17 @@ - (void)handleBuffer:(NSArray<NSArray *> *)buffer
 
     dispatch_block_t block = ^{
       RCTProfileEndFlowEvent();
-
-#if RCT_DEV
-      NSString *_threadName = RCTCurrentThreadName();
-      RCT_PROFILE_BEGIN_EVENT(0, _threadName, nil);
-#endif
+      RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge handleBuffer:]", nil);
 
       NSOrderedSet *calls = [buckets objectForKey:queue];
       @autoreleasepool {
         for (NSNumber *indexObj in calls) {
           NSUInteger index = indexObj.unsignedIntegerValue;
+          if (callID != -1 && _flowIDMap != NULL) {
+            int64_t newFlowID = (int64_t)CFDictionaryGetValue(_flowIDMap, (const void *)(_flowID + index));
+            _RCTProfileEndFlowEvent(@(newFlowID));
+            CFDictionaryRemoveValue(_flowIDMap, (const void *)(_flowID + index));
+          }
           [self _handleRequestNumber:index
                             moduleID:[moduleIDs[index] integerValue]
                             methodID:[methodIDs[index] integerValue]
@@ -733,16 +917,29 @@ - (void)handleBuffer:(NSArray<NSArray *> *)buffer
       dispatch_async(queue, block);
     }
   }
+
+  _flowID = callID;
+}
+
+- (void)partialBatchDidFlush
+{
+  for (RCTModuleData *moduleData in _moduleDataByID) {
+    if (moduleData.implementsPartialBatchDidFlush) {
+      [self dispatchBlock:^{
+        [moduleData.instance partialBatchDidFlush];
+      } queue:moduleData.methodQueue];
+    }
+  }
 }
 
 - (void)batchDidComplete
 {
   // TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
   for (RCTModuleData *moduleData in _moduleDataByID) {
-    if ([moduleData.instance respondsToSelector:@selector(batchDidComplete)]) {
-      [moduleData dispatchBlock:^{
+    if (moduleData.implementsBatchDidComplete) {
+      [self dispatchBlock:^{
         [moduleData.instance batchDidComplete];
-      }];
+      } queue:moduleData.methodQueue];
     }
   }
 }
@@ -752,7 +949,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
                     methodID:(NSUInteger)methodID
                       params:(NSArray *)params
 {
-  if (!self.isValid) {
+  if (!_valid) {
     return NO;
   }
 
@@ -773,58 +970,44 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
     return NO;
   }
 
-  RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"[%@ %@]", moduleData.name, method.JSMethodName], nil);
-
   @try {
     [method invokeWithBridge:self module:moduleData.instance arguments:params];
   }
   @catch (NSException *exception) {
     // Pass on JS exceptions
-    if ([exception.name isEqualToString:RCTFatalExceptionName]) {
+    if ([exception.name hasPrefix:RCTFatalExceptionName]) {
       @throw exception;
     }
 
     NSString *message = [NSString stringWithFormat:
-                         @"Exception thrown while invoking %@ on target %@ with params %@: %@",
-                         method.JSMethodName, moduleData.name, params, exception];
+                         @"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
+                         exception, method.JSMethodName, moduleData.name, params];
     RCTFatal(RCTErrorWithMessage(message));
   }
 
-  if (RCTProfileIsProfiling()) {
-    NSMutableDictionary *args = [method.profileArgs mutableCopy];
-    args[@"method"] = method.JSMethodName;
-    args[@"args"] = RCTJSONStringify(RCTNullIfNil(params), NULL);
-    RCT_PROFILE_END_EVENT(0, @"objc_call", args);
-  }
-
   return YES;
 }
 
 - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
 {
   RCTAssertJSThread();
-  RCT_PROFILE_BEGIN_EVENT(0, @"DispatchFrameUpdate", nil);
+  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBatchedBridge _jsThreadUpdate:]", nil);
 
   RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink];
   for (RCTModuleData *moduleData in _frameUpdateObservers) {
     id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
     if (!observer.paused) {
-      RCT_IF_DEV(NSString *name = [NSString stringWithFormat:@"[%@ didUpdateFrame:%f]", observer, displayLink.timestamp];)
       RCTProfileBeginFlowEvent();
-
-      [moduleData dispatchBlock:^{
+      [self dispatchBlock:^{
         RCTProfileEndFlowEvent();
-        RCT_PROFILE_BEGIN_EVENT(0, name, nil);
         [observer didUpdateFrame:frameUpdate];
-        RCT_PROFILE_END_EVENT(0, @"objc_call,fps", nil);
-      }];
+      } queue:moduleData.methodQueue];
     }
   }
 
   [self updateJSDisplayLinkState];
 
-
-  RCTProfileImmediateEvent(0, @"JS Thread Tick", 'g');
+  RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g');
 
   RCT_PROFILE_END_EVENT(0, @"objc_call", nil);
 }
@@ -850,4 +1033,9 @@ - (void)stopProfiling:(void (^)(NSData *))callback
   }];
 }
 
+- (BOOL)isBatchActive
+{
+  return _wasBatchActive;
+}
+
 @end
diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h
new file mode 100644
index 000000000000..19f33c1a3ffe
--- /dev/null
+++ b/React/Base/RCTBridge+Private.h
@@ -0,0 +1,118 @@
+/**
+ * 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.
+ */
+
+#import "RCTBridge.h"
+
+@class RCTModuleData;
+
+@interface RCTBridge ()
+
+// Used for the profiler flow events between JS and native
+@property (nonatomic, assign) int64_t flowID;
+@property (nonatomic, assign) CFMutableDictionaryRef flowIDMap;
+
++ (instancetype)currentBridge;
++ (void)setCurrentBridge:(RCTBridge *)bridge;
+
+/**
+ * Bridge setup code - creates an instance of RCTBachedBridge. Exposed for
+ * test only
+ */
+- (void)setUp;
+
+/**
+ * This method is used to invoke a callback that was registered in the
+ * JavaScript application context. Safe to call from any thread.
+ */
+- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args;
+
+/**
+ * This property is mostly used on the main thread, but may be touched from
+ * a background thread if the RCTBridge happens to deallocate on a background
+ * thread. Therefore, we want all writes to it to be seen atomically.
+ */
+@property (atomic, strong) RCTBridge *batchedBridge;
+
+/**
+ * The block that creates the modules' instances to be added to the bridge.
+ * Exposed for the RCTBatchedBridge
+ */
+@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
+
+/**
+ * Used by RCTDevMenu to override the `hot` param of the current bundleURL.
+ */
+@property (nonatomic, strong, readwrite) NSURL *bundleURL;
+
+@end
+
+@interface RCTBridge (RCTBatchedBridge)
+
+/**
+ * Used for unit testing, to detect when executor has been invalidated.
+ */
+@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
+
+/**
+ * Used by RCTModuleData
+ */
+@property (nonatomic, assign, readonly) BOOL moduleSetupComplete;
+
+/**
+ * Used by RCTModuleData to register the module for frame updates after it is
+ * lazily initialized.
+ */
+- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
+                       withModuleData:(RCTModuleData *)moduleData;
+
+/**
+ * Dispatch work to a module's queue - this is also suports the fake RCTJSThread
+ * queue. Exposed for the RCTProfiler
+ */
+- (void)dispatchBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue;
+
+/**
+ * Get the module data for a given module name. Used by UIManager to implement
+ * the `dispatchViewManagerCommand` method.
+ */
+- (RCTModuleData *)moduleDataForName:(NSString *)moduleName;
+
+/**
+ * Systrace profiler toggling methods exposed for the RCTDevMenu
+ */
+- (void)startProfiling;
+- (void)stopProfiling:(void (^)(NSData *))callback;
+
+/**
+ * Executes native calls sent by JavaScript. Exposed for testing purposes only
+ */
+- (void)handleBuffer:(NSArray<NSArray *> *)buffer;
+
+/**
+ * Exposed for the RCTJSCExecutor for sending native methods called from
+ * JavaScript in the middle of a batch.
+ */
+- (void)handleBuffer:(NSArray<NSArray *> *)buffer batchEnded:(BOOL)hasEnded;
+
+/**
+ * Exposed for the RCTJSCExecutor for lazily loading native modules
+ */
+- (NSArray *)configForModuleName:(NSString *)moduleName;
+
+/**
+ * Hook exposed for RCTLog to send logs to JavaScript when not running in JSC
+ */
+- (void)logMessage:(NSString *)message level:(NSString *)level;
+
+/**
+ * Allow super fast, one time, timers to skip the queue and be directly executed
+ */
+- (void)_immediatelyCallTimer:(NSNumber *)timer;
+
+@end
diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
index c16786b454e1..1d8548607e2b 100644
--- a/React/Base/RCTBridge.h
+++ b/React/Base/RCTBridge.h
@@ -25,24 +25,28 @@
 RCT_EXTERN NSString *const RCTReloadNotification;
 
 /**
- * This notification fires when the bridge starts loading.
+ * This notification fires when the bridge starts loading the JS bundle.
  */
 RCT_EXTERN NSString *const RCTJavaScriptWillStartLoadingNotification;
 
 /**
- * This notification fires when the bridge has finished loading.
+ * This notification fires when the bridge has finished loading the JS bundle.
  */
 RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification;
 
 /**
- * This notification fires when the bridge failed to load.
+ * This notification fires when the bridge failed to load the JS bundle. The
+ * `error` key can be used to determine the error that occured.
  */
 RCT_EXTERN NSString *const RCTJavaScriptDidFailToLoadNotification;
 
 /**
- * This notification fires when the bridge created all registered native modules
+ * This notification fires each time a native module is instantiated. The
+ * `module` key will contain a reference to the newly-created module instance.
+ * Note that this notification may be fired before the module is available via
+ * the `[bridge moduleForClass:]` method.
  */
-RCT_EXTERN NSString *const RCTDidCreateNativeModules;
+RCT_EXTERN NSString *const RCTDidInitializeModuleNotification;
 
 /**
  * This block can be used to instantiate modules that require additional
@@ -59,6 +63,11 @@ typedef NSArray<id<RCTBridgeModule>> *(^RCTBridgeModuleProviderBlock)(void);
  */
 RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
 
+/**
+ * This function checks if a class has been registered
+ */
+RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
+
 /**
  * Async batched bridge used to communicate with the JavaScript application.
  */
@@ -98,19 +107,34 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
 
 /**
- * DEPRECATED: Do not use.
+ * Retrieve a bridge module instance by name or class. Note that modules are
+ * lazily instantiated, so calling these methods for the first time with a given
+ * module name/class may cause the class to be sychronously instantiated,
+ * potentially blocking both the calling thread and main thread for a short time.
+ */
+- (id)moduleForName:(NSString *)moduleName;
+- (id)moduleForClass:(Class)moduleClass;
+
+/**
+ * Convenience method for retrieving all modules conforming to a given protocol.
+ * Modules will be sychronously instantiated if they haven't already been,
+ * potentially blocking both the calling thread and main thread for a short time.
+ */
+- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol;
+
+/**
+ * All registered bridge module classes.
  */
-#define RCT_IMPORT_METHOD(module, method) \
-  _Pragma("message(\"This macro is no longer required\")")
+@property (nonatomic, copy, readonly) NSArray<Class> *moduleClasses;
 
 /**
  * URL of the script that was loaded into the bridge.
  */
-@property (nonatomic, strong) NSURL *bundleURL;
+@property (nonatomic, strong, readonly) NSURL *bundleURL;
 
 /**
- * The class of the executor currently being used *or* to be used after the next
- * reload.
+ * The class of the executor currently being used. Changes to this value will
+ * take effect after the bridge is reloaded.
  */
 @property (nonatomic, strong) Class executorClass;
 
@@ -130,11 +154,6 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
  */
 @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
 
-/**
- * A dictionary of all registered RCTBridgeModule instances, keyed by moduleName.
- */
-@property (nonatomic, copy, readonly) NSDictionary *modules;
-
 /**
  * The launch options that were used to initialize the bridge.
  */
@@ -151,13 +170,13 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
 @property (nonatomic, readonly, getter=isValid) BOOL valid;
 
 /**
- * The block passed in the constructor with pre-initialized modules
+ * Reload the bundle and reset executor & modules. Safe to call from any thread.
  */
-@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
+- (void)reload;
 
 /**
- * Reload the bundle and reset executor & modules. Safe to call from any thread.
+ * Says whether bridge has started recieving calls from javascript.
  */
-- (void)reload;
+- (BOOL)isBatchActive;
 
 @end
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 15f85c8da4a2..cd3edd45fd6e 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 
 #import <objc/runtime.h>
 
@@ -22,9 +22,7 @@
 NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
 NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
 NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
-NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
-
-@class RCTBatchedBridge;
+NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification";
 
 @interface RCTBatchedBridge : RCTBridge <RCTInvalidating>
 
@@ -34,12 +32,6 @@ - (instancetype)initWithParentBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZ
 
 @end
 
-@interface RCTBridge ()
-
-@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
-
-@end
-
 static NSMutableArray<Class> *RCTModuleClasses;
 NSArray<Class> *RCTGetModuleClasses(void);
 NSArray<Class> *RCTGetModuleClasses(void)
@@ -65,6 +57,9 @@ void RCTRegisterModule(Class moduleClass)
 
   // Register module
   [RCTModuleClasses addObject:moduleClass];
+
+  objc_setAssociatedObject(moduleClass, &RCTBridgeModuleClassIsRegistered,
+                           @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 }
 
 /**
@@ -73,7 +68,8 @@ void RCTRegisterModule(Class moduleClass)
 NSString *RCTBridgeModuleNameForClass(Class cls)
 {
 #if RCT_DEV
-  RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)], @"Bridge module classes must conform to RCTBridgeModule");
+  RCTAssert([cls conformsToProtocol:@protocol(RCTBridgeModule)],
+            @"Bridge module `%@` does not conform to RCTBridgeModule", cls);
 #endif
 
   NSString *name = [cls moduleName];
@@ -87,15 +83,17 @@ void RCTRegisterModule(Class moduleClass)
 }
 
 /**
- * Check if class has been registered
+ * This function checks if a class has been registered
  */
-BOOL RCTBridgeModuleClassIsRegistered(Class);
 BOOL RCTBridgeModuleClassIsRegistered(Class cls)
 {
   return [objc_getAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered) ?: @YES boolValue];
 }
 
 @implementation RCTBridge
+{
+  NSURL *_delegateBundleURL;
+}
 
 dispatch_queue_t RCTJSThread;
 
@@ -106,39 +104,6 @@ + (void)initialize
 
     // Set up JS thread
     RCTJSThread = (id)kCFNull;
-
-#if RCT_DEBUG
-
-    // Set up module classes
-    static unsigned int classCount;
-    Class *classes = objc_copyClassList(&classCount);
-
-    for (unsigned int i = 0; i < classCount; i++)
-    {
-      Class cls = classes[i];
-      Class superclass = cls;
-      while (superclass)
-      {
-        if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule)))
-        {
-          if (![RCTModuleClasses containsObject:cls]) {
-            RCTLogWarn(@"Class %@ was not exported. Did you forget to use "
-                       "RCT_EXPORT_MODULE()?", cls);
-
-            RCTRegisterModule(cls);
-            objc_setAssociatedObject(cls, &RCTBridgeModuleClassIsRegistered,
-                                     @NO, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-          }
-          break;
-        }
-        superclass = class_getSuperclass(superclass);
-      }
-    }
-
-    free(classes);
-
-#endif
-
   });
 }
 
@@ -222,17 +187,46 @@ - (void)bindKeys
   [commands registerKeyCommandWithInput:@"r"
                           modifierFlags:UIKeyModifierCommand
                                  action:^(__unused UIKeyCommand *command) {
-                                    [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
-                                                                                        object:nil
-                                                                                      userInfo:nil];
-                                 }];
+    [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification
+                                                        object:nil
+                                                      userInfo:nil];
+  }];
 
 #endif
 }
 
+- (NSArray<Class> *)moduleClasses
+{
+  return self.batchedBridge.moduleClasses;
+}
+
+- (id)moduleForName:(NSString *)moduleName
+{
+  return [self.batchedBridge moduleForName:moduleName];
+}
+
+- (id)moduleForClass:(Class)moduleClass
+{
+  return [self moduleForName:RCTBridgeModuleNameForClass(moduleClass)];
+}
+
+- (NSArray *)modulesConformingToProtocol:(Protocol *)protocol
+{
+  NSMutableArray *modules = [NSMutableArray new];
+  for (Class moduleClass in self.moduleClasses) {
+    if ([moduleClass conformsToProtocol:protocol]) {
+      id module = [self moduleForClass:moduleClass];
+      if (module) {
+        [modules addObject:module];
+      }
+    }
+  }
+  return [modules copy];
+}
+
 - (RCTEventDispatcher *)eventDispatcher
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])];
+  return [self moduleForClass:[RCTEventDispatcher class]];
 }
 
 - (void)reload
@@ -250,48 +244,49 @@ - (void)setUp
 {
   RCTAssertMainThread();
 
-  _bundleURL = [self.delegate sourceURLForBridge:self] ?: _bundleURL;
+  // Only update bundleURL from delegate if delegate bundleURL has changed
+  NSURL *previousDelegateURL = _delegateBundleURL;
+  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
+  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
+    _bundleURL = _delegateBundleURL;
+  }
 
   // Sanitize the bundle URL
   _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
 
-  _batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
+  [self createBatchedBridge];
 }
 
-- (BOOL)isLoading
+- (void)createBatchedBridge
 {
-  return _batchedBridge.loading;
+  self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
 }
 
-- (BOOL)isValid
+- (BOOL)isLoading
 {
-  return _batchedBridge.valid;
+  return self.batchedBridge.loading;
 }
 
-- (void)invalidate
+- (BOOL)isValid
 {
-  RCTAssertMainThread();
-
-  [_batchedBridge invalidate];
-  _batchedBridge = nil;
+  return self.batchedBridge.valid;
 }
 
-- (void)logMessage:(NSString *)message level:(NSString *)level
+- (BOOL)isBatchActive
 {
-  [_batchedBridge logMessage:message level:level];
+  return [_batchedBridge isBatchActive];
 }
 
-- (NSDictionary *)modules
+- (void)invalidate
 {
-  return _batchedBridge.modules;
-}
+  RCTBridge *batchedBridge = self.batchedBridge;
+  self.batchedBridge = nil;
 
-#define RCT_INNER_BRIDGE_ONLY(...) \
-- (void)__VA_ARGS__ \
-{ \
-  NSString *errorMessage = [NSString stringWithFormat:@"Called method \"%@\" on top level bridge. \
-    This method should oly be called from bridge instance in a bridge module", @(__func__)]; \
-  RCTFatal(RCTErrorWithMessage(errorMessage)); \
+  if (batchedBridge) {
+    RCTExecuteOnMainThread(^{
+      [batchedBridge invalidate];
+    }, NO);
+  }
 }
 
 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
@@ -299,7 +294,9 @@ - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
   [self.batchedBridge enqueueJSCall:moduleDotMethod args:args];
 }
 
-RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module
-                      method:(__unused NSString *)method
-                      arguments:(__unused NSArray *)args);
+- (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
+{
+  [self.batchedBridge enqueueCallback:cbID args:args];
+}
+
 @end
diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h
index ee51f5d5b412..067ecb4927d1 100644
--- a/React/Base/RCTBridgeModule.h
+++ b/React/Base/RCTBridgeModule.h
@@ -37,7 +37,7 @@ typedef void (^RCTPromiseResolveBlock)(id result);
  * The error may be nil but it is preferable to pass an NSError object for more
  * precise error messages.
  */
-typedef void (^RCTPromiseRejectBlock)(NSError *error);
+typedef void (^RCTPromiseRejectBlock)(NSString *code, NSString *message, NSError *error);
 
 /**
  * This constant can be returned from +methodQueue to force module
@@ -76,7 +76,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \
  * will be set automatically by the bridge when it initializes the module.
  * To implement this in your module, just add `@synthesize bridge = _bridge;`
  */
-@property (nonatomic, weak) RCTBridge *bridge;
+@property (nonatomic, weak, readonly) RCTBridge *bridge;
 
 /**
  * The queue that will be used to call all exported methods. If omitted, this
@@ -94,7 +94,7 @@ RCT_EXTERN void RCTRegisterModule(Class); \
  * }
  *
  * If you don't want to specify the queue yourself, but you need to use it
- * inside your class (e.g. if you have internal methods that need to disaptch
+ * inside your class (e.g. if you have internal methods that need to dispatch
  * onto that queue), you can just add `@synthesize methodQueue = _methodQueue;`
  * and the bridge will populate the methodQueue property for you automatically
  * when it initializes the module.
@@ -235,4 +235,12 @@ RCT_EXTERN void RCTRegisterModule(Class); \
  */
 - (void)batchDidComplete;
 
+/**
+ * Notifies the module that the active batch of JS method invocations has been
+ * partially flushed.
+ *
+ * This occurs before -batchDidComplete, and more frequently.
+ */
+- (void)partialBatchDidFlush;
+
 @end
diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h
index 35106a6940bc..231f888c4821 100644
--- a/React/Base/RCTConvert.h
+++ b/React/Base/RCTConvert.h
@@ -12,6 +12,7 @@
 
 #import "Layout.h"
 #import "RCTAnimationType.h"
+#import "RCTBorderStyle.h"
 #import "RCTTextDecorationLineType.h"
 #import "RCTDefines.h"
 #import "RCTLog.h"
@@ -84,9 +85,6 @@ typedef NSURL RCTFileURL;
 + (UIColor *)UIColor:(id)json;
 + (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED;
 
-+ (UIImage *)UIImage:(id)json;
-+ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED;
-
 + (UIFont *)UIFont:(id)json;
 + (UIFont *)UIFont:(UIFont *)font withSize:(id)json;
 + (UIFont *)UIFont:(UIFont *)font withWeight:(id)json;
@@ -96,28 +94,13 @@ typedef NSURL RCTFileURL;
               size:(id)size weight:(id)weight style:(id)style
    scaleMultiplier:(CGFloat)scaleMultiplier;
 
-typedef NSArray NSArrayArray;
 + (NSArray<NSArray *> *)NSArrayArray:(id)json;
-
-typedef NSArray NSStringArray;
 + (NSArray<NSString *> *)NSStringArray:(id)json;
-
-typedef NSArray NSStringArrayArray;
 + (NSArray<NSArray<NSString *> *> *)NSStringArrayArray:(id)json;
-
-typedef NSArray NSDictionaryArray;
 + (NSArray<NSDictionary *> *)NSDictionaryArray:(id)json;
-
-typedef NSArray NSURLArray;
 + (NSArray<NSURL *> *)NSURLArray:(id)json;
-
-typedef NSArray RCTFileURLArray;
-+ (NSArray<NSURL *> *)RCTFileURLArray:(id)json;
-
-typedef NSArray NSNumberArray;
++ (NSArray<RCTFileURL *> *)RCTFileURLArray:(id)json;
 + (NSArray<NSNumber *> *)NSNumberArray:(id)json;
-
-typedef NSArray UIColorArray;
 + (NSArray<UIColor *> *)UIColorArray:(id)json;
 
 typedef NSArray CGColorArray;
@@ -140,10 +123,35 @@ typedef BOOL css_clip_t, css_backface_visibility_t;
 
 + (RCTPointerEvents)RCTPointerEvents:(id)json;
 + (RCTAnimationType)RCTAnimationType:(id)json;
++ (RCTBorderStyle)RCTBorderStyle:(id)json;
 + (RCTTextDecorationLineType)RCTTextDecorationLineType:(id)json;
 
 @end
 
+@interface RCTConvert (Deprecated)
+
+/**
+ * Use lightweight generics syntax instead, e.g. NSArray<NSString *>
+ */
+typedef NSArray NSArrayArray __deprecated_msg("Use NSArray<NSArray *>");
+typedef NSArray NSStringArray __deprecated_msg("Use NSArray<NSString *>");
+typedef NSArray NSStringArrayArray __deprecated_msg("Use NSArray<NSArray<NSString *> *>");
+typedef NSArray NSDictionaryArray __deprecated_msg("Use NSArray<NSDictionary *>");
+typedef NSArray NSURLArray __deprecated_msg("Use NSArray<NSURL *>");
+typedef NSArray RCTFileURLArray __deprecated_msg("Use NSArray<RCTFileURL *>");
+typedef NSArray NSNumberArray __deprecated_msg("Use NSArray<NSNumber *>");
+typedef NSArray UIColorArray __deprecated_msg("Use NSArray<UIColor *>");
+
+/**
+ * Synchronous image loading is generally a bad idea for performance reasons.
+ * If you need to pass image references, try to use `RCTImageSource` and then
+ * `RCTImageLoader` instead of converting directly to a UIImage.
+ */
++ (UIImage *)UIImage:(id)json;
++ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED;
+
+@end
+
 /**
  * Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
  */
@@ -151,6 +159,11 @@ RCT_EXTERN NSNumber *RCTConvertEnumValue(const char *, NSDictionary *, NSNumber
 RCT_EXTERN NSNumber *RCTConvertMultiEnumValue(const char *, NSDictionary *, NSNumber *, id);
 RCT_EXTERN NSArray *RCTConvertArrayValue(SEL, id);
 
+/**
+ * Get the converter function for the specified type
+ */
+RCT_EXTERN SEL RCTConvertSelectorForType(NSString *type);
+
 /**
  * This macro is used for logging conversion errors. This is just used to
  * avoid repeating the same boilerplate for every error message.
@@ -227,7 +240,7 @@ RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter
  * This macro is used for creating converter functions for typed arrays.
  */
 #define RCT_ARRAY_CONVERTER(type)                      \
-+ (NSArray<type *> *)type##Array:(id)json                      \
++ (NSArray<id> *)type##Array:(id)json                      \
 {                                                      \
   return RCTConvertArrayValue(@selector(type:), json); \
 }
diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m
index 2531b69b9c38..ebdbee02a584 100644
--- a/React/Base/RCTConvert.m
+++ b/React/Base/RCTConvert.m
@@ -12,6 +12,8 @@
 #import <objc/message.h>
 
 #import "RCTDefines.h"
+#import "RCTImageSource.h"
+#import "RCTParserUtils.h"
 #import "RCTUtils.h"
 
 @implementation RCTConvert
@@ -116,8 +118,31 @@ + (NSURL *)NSURL:(id)json
 
 + (NSURLRequest *)NSURLRequest:(id)json
 {
-  NSURL *URL = [self NSURL:json];
-  return URL ? [NSURLRequest requestWithURL:URL] : nil;
+  if ([json isKindOfClass:[NSString class]]) {
+    NSURL *URL = [self NSURL:json];
+    return URL ? [NSURLRequest requestWithURL:URL] : nil;
+  }
+  if ([json isKindOfClass:[NSDictionary class]]) {
+    NSURL *URL = [self NSURL:json[@"uri"] ?: json[@"url"]];
+    if (!URL) {
+      return nil;
+    }
+    NSData *body = [self NSData:json[@"body"]];
+    NSString *method = [self NSString:json[@"method"]].uppercaseString ?: @"GET";
+    NSDictionary *headers = [self NSDictionary:json[@"headers"]];
+    if ([method isEqualToString:@"GET"] && headers == nil && body == nil) {
+      return [NSURLRequest requestWithURL:URL];
+    }
+    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
+    request.HTTPBody = body;
+    request.HTTPMethod = method;
+    request.allHTTPHeaderFields = headers;
+    return [request copy];
+  }
+  if (json) {
+    RCTLogConvertError(json, @"a valid URLRequest");
+  }
+  return nil;
 }
 
 + (RCTFileURL *)RCTFileURL:(id)json
@@ -172,7 +197,7 @@ + (NSDate *)NSDate:(id)json
   }
   if ([json isKindOfClass:[NSNumber class]]) {
     NSArray *allValues = mapping.allValues;
-    if ([mapping.allValues containsObject:json] || [json isEqual:defaultValue]) {
+    if ([allValues containsObject:json] || [json isEqual:defaultValue]) {
       return json;
     }
     RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues);
@@ -229,6 +254,12 @@ + (NSDate *)NSDate:(id)json
   @"dashed": @(NSUnderlinePatternDash | NSUnderlineStyleSingle),
 }), NSUnderlineStyleSingle, integerValue)
 
+RCT_ENUM_CONVERTER(RCTBorderStyle, (@{
+  @"solid": @(RCTBorderStyleSolid),
+  @"dotted": @(RCTBorderStyleDotted),
+  @"dashed": @(RCTBorderStyleDashed),
+}), RCTBorderStyleSolid, integerValue)
+
 RCT_ENUM_CONVERTER(RCTTextDecorationLineType, (@{
   @"none": @(RCTTextDecorationLineTypeNone),
   @"underline": @(RCTTextDecorationLineTypeUnderline),
@@ -397,6 +428,9 @@ + (type)type:(id)json                                 \
 
 + (UIColor *)UIColor:(id)json
 {
+  if (!json) {
+    return nil;
+  }
   if ([json isKindOfClass:[NSArray class]]) {
     NSArray *components = [self NSNumberArray:json];
     CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
@@ -404,84 +438,22 @@ + (UIColor *)UIColor:(id)json
                            green:[self CGFloat:components[1]]
                             blue:[self CGFloat:components[2]]
                            alpha:alpha];
-  } else {
+  } else if ([json isKindOfClass:[NSNumber class]]) {
     NSUInteger argb = [self NSUInteger:json];
     CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
     CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
     CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
     CGFloat b = (argb & 0xFF) / 255.0;
     return [UIColor colorWithRed:r green:g blue:b alpha:a];
-  }
-}
-
-+ (CGColorRef)CGColor:(id)json
-{
-  return [self UIColor:json].CGColor;
-}
-
-/* This method is only used when loading images synchronously, e.g. for tabbar icons */
-+ (UIImage *)UIImage:(id)json
-{
-  // TODO: we might as well cache the result of these checks (and possibly the
-  // image itself) so as to reduce overhead on subsequent checks of the same input
-
-  if (!json) {
-    return nil;
-  }
-
-  UIImage *image;
-  NSString *path;
-  CGFloat scale = 0.0;
-  BOOL isPackagerAsset = NO;
-  if ([json isKindOfClass:[NSString class]]) {
-    path = json;
-  } else if ([json isKindOfClass:[NSDictionary class]]) {
-    if (!(path = [self NSString:json[@"uri"]])) {
-      return nil;
-    }
-    scale = [self CGFloat:json[@"scale"]];
-    isPackagerAsset = [self BOOL:json[@"__packager_asset"]];
   } else {
-    RCTLogConvertError(json, @"an image");
+    RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?");
     return nil;
   }
-
-  NSURL *URL = [self NSURL:path];
-  NSString *scheme = URL.scheme.lowercaseString;
-  if ([scheme isEqualToString:@"file"]) {
-    if (RCTIsXCAssetURL(URL)) {
-      // Image may reside inside a .car file, in which case we have no choice
-      // but to use +[UIImage imageNamed] - but this method isn't thread safe
-      RCTAssertMainThread();
-      NSString *assetName = RCTBundlePathForURL(URL);
-      image = [UIImage imageNamed:assetName];
-    } else {
-      // Attempt to load from the file system
-      NSString *filePath = URL.path;
-      if (filePath.pathExtension.length == 0) {
-        filePath = [filePath stringByAppendingPathExtension:@"png"];
-      }
-      image = [UIImage imageWithContentsOfFile:filePath];
-    }
-  } else if ([scheme isEqualToString:@"data"]) {
-    image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
-  } else if ([scheme isEqualToString:@"http"] && isPackagerAsset) {
-    image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
-  } else {
-    RCTLogConvertError(json, @"an image. Only local files or data URIs are supported");
-  }
-
-  if (scale > 0) {
-    image = [UIImage imageWithCGImage:image.CGImage
-                                scale:scale
-                          orientation:image.imageOrientation];
-  }
-  return image;
 }
 
-+ (CGImageRef)CGImage:(id)json
++ (CGColorRef)CGColor:(id)json
 {
-  return [self UIImage:json].CGImage;
+  return [self UIColor:json].CGColor;
 }
 
 #if !defined(__IPHONE_8_2) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_2
@@ -526,8 +498,34 @@ + (CGImageRef)CGImage:(id)json
 
 static RCTFontWeight RCTWeightOfFont(UIFont *font)
 {
+  static NSDictionary *nameToWeight;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    nameToWeight = @{
+      @"normal": @(UIFontWeightRegular),
+      @"bold": @(UIFontWeightBold),
+      @"ultralight": @(UIFontWeightUltraLight),
+      @"thin": @(UIFontWeightThin),
+      @"light": @(UIFontWeightLight),
+      @"regular": @(UIFontWeightRegular),
+      @"medium": @(UIFontWeightMedium),
+      @"semibold": @(UIFontWeightSemibold),
+      @"bold": @(UIFontWeightBold),
+      @"heavy": @(UIFontWeightHeavy),
+      @"black": @(UIFontWeightBlack),
+    };
+  });
+
   NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
-  return [traits[UIFontWeightTrait] doubleValue];
+  RCTFontWeight weight = [traits[UIFontWeightTrait] doubleValue];
+  if (weight == 0.0) {
+    for (NSString *name in nameToWeight) {
+      if ([font.fontName.lowercaseString hasSuffix:name]) {
+        return [nameToWeight[name] doubleValue];
+      }
+    }
+  }
+  return weight;
 }
 
 static BOOL RCTFontIsItalic(UIFont *font)
@@ -701,6 +699,12 @@ + (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
   return values;
 }
 
+SEL RCTConvertSelectorForType(NSString *type)
+{
+  const char *input = type.UTF8String;
+  return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
+}
+
 RCT_ARRAY_CONVERTER(NSURL)
 RCT_ARRAY_CONVERTER(RCTFileURL)
 RCT_ARRAY_CONVERTER(UIColor)
@@ -841,3 +845,84 @@ + (NSPropertyList)NSPropertyList:(id)json
 }), RCTAnimationTypeEaseInEaseOut, integerValue)
 
 @end
+
+@interface RCTImageSource (Packager)
+
+@property (nonatomic, assign) BOOL packagerAsset;
+
+@end
+
+@implementation RCTConvert (Deprecated)
+
+/* This method is only used when loading images synchronously, e.g. for tabbar icons */
++ (UIImage *)UIImage:(id)json
+{
+  if (!json) {
+    return nil;
+  }
+
+  RCTImageSource *imageSource = [self RCTImageSource:json];
+  if (!imageSource) {
+    return nil;
+  }
+
+  __block UIImage *image;
+  if (![NSThread isMainThread]) {
+    // It seems that none of the UIImage loading methods can be guaranteed
+    // thread safe, so we'll pick the lesser of two evils here and block rather
+    // than run the risk of crashing
+    RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended");
+    dispatch_sync(dispatch_get_main_queue(), ^{
+      image = [self UIImage:json];
+    });
+    return image;
+  }
+
+  NSURL *URL = imageSource.imageURL;
+  NSString *scheme = URL.scheme.lowercaseString;
+  if ([scheme isEqualToString:@"file"]) {
+    NSString *assetName = RCTBundlePathForURL(URL);
+    image = [UIImage imageNamed:assetName];
+    if (!image) {
+      // Attempt to load from the file system
+      NSString *filePath = URL.path;
+      if (filePath.pathExtension.length == 0) {
+        filePath = [filePath stringByAppendingPathExtension:@"png"];
+      }
+      image = [UIImage imageWithContentsOfFile:filePath];
+    }
+  } else if ([scheme isEqualToString:@"data"]) {
+    image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
+  } else if ([scheme isEqualToString:@"http"] && imageSource.packagerAsset) {
+    image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
+  } else {
+    RCTLogConvertError(json, @"an image. Only local files or data URIs are supported");
+  }
+
+  CGFloat scale = imageSource.scale;
+  if (!scale && imageSource.size.width) {
+    // If no scale provided, set scale to image width / source width
+    scale = CGImageGetWidth(image.CGImage) / imageSource.size.width;
+  }
+
+  if (scale) {
+    image = [UIImage imageWithCGImage:image.CGImage
+                                scale:scale
+                          orientation:image.imageOrientation];
+  }
+
+  if (!CGSizeEqualToSize(imageSource.size, CGSizeZero) &&
+      !CGSizeEqualToSize(imageSource.size, image.size)) {
+    RCTLogError(@"Image source size %@ does not match loaded image size %@.",
+                NSStringFromCGSize(imageSource.size), NSStringFromCGSize(image.size));
+  }
+
+  return image;
+}
+
++ (CGImageRef)CGImage:(id)json
+{
+  return [self UIImage:json].CGImage;
+}
+
+@end
diff --git a/React/Base/RCTEventDispatcher.h b/React/Base/RCTEventDispatcher.h
index 4cbd84fa2920..4b424b2241d7 100644
--- a/React/Base/RCTEventDispatcher.h
+++ b/React/Base/RCTEventDispatcher.h
@@ -48,23 +48,17 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
 
 @property (nonatomic, strong, readonly) NSNumber *viewTag;
 @property (nonatomic, copy, readonly) NSString *eventName;
-@property (nonatomic, copy, readonly) NSDictionary *body;
-@property (nonatomic, assign, readonly) uint16_t coalescingKey;
 
 - (BOOL)canCoalesce;
 - (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
 
+// used directly for doing a JS call
 + (NSString *)moduleDotMethod;
+// must contain only JSON compatible values
+- (NSArray *)arguments;
 
 @end
 
-@interface RCTBaseEvent : NSObject <RCTEvent>
-
-- (instancetype)initWithViewTag:(NSNumber *)viewTag
-                      eventName:(NSString *)eventName
-                           body:(NSDictionary *)body NS_DESIGNATED_INITIALIZER;
-
-@end
 
 /**
  * This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
@@ -101,6 +95,14 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
 
 /**
  * Send a pre-prepared event object.
+ * 
+ * If the event can be coalesced it is added to a pool of events that are sent at the beginning of the next js frame.
+ * Otherwise if the event cannot be coalesced we first flush the pool of coalesced events and the new event after that.
+ *
+ * Why it works this way?
+ * Making sure js gets events in the right order is crucial for correctly interpreting gestures.
+ * Unfortunately we cannot emit all events as they come. If we would do that we would have to emit scroll and touch moved event on every frame,
+ * which is too much data to transfer and process on older devices. This is especially bad when js starts lagging behind main thread.
  */
 - (void)sendEvent:(id<RCTEvent>)event;
 
diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m
index d8d6f3268daa..2e8040e4606d 100644
--- a/React/Base/RCTEventDispatcher.m
+++ b/React/Base/RCTEventDispatcher.m
@@ -30,57 +30,9 @@
 {
   return @(
     event.viewTag.intValue |
-    (((uint64_t)event.eventName.hash & 0xFFFF) << 32)  |
-    (((uint64_t)event.coalescingKey) << 48)
-  );
+    (((uint64_t)event.eventName.hash & 0xFFFF) << 32));
 }
 
-@implementation RCTBaseEvent
-
-@synthesize viewTag = _viewTag;
-@synthesize eventName = _eventName;
-@synthesize body = _body;
-
-- (instancetype)initWithViewTag:(NSNumber *)viewTag
-                      eventName:(NSString *)eventName
-                           body:(NSDictionary *)body
-{
-  if (RCT_DEBUG) {
-    RCTAssertParam(eventName);
-  }
-
-  if ((self = [super init])) {
-    _viewTag = viewTag;
-    _eventName = eventName;
-    _body = body;
-  }
-  return self;
-}
-
-RCT_NOT_IMPLEMENTED(- (instancetype)init)
-
-- (uint16_t)coalescingKey
-{
-  return 0;
-}
-
-- (BOOL)canCoalesce
-{
-  return YES;
-}
-
-- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
-{
-  return newEvent;
-}
-
-+ (NSString *)moduleDotMethod
-{
-  return nil;
-}
-
-@end
-
 @interface RCTEventDispatcher() <RCTFrameUpdateObserver>
 
 @end
@@ -97,14 +49,12 @@ @implementation RCTEventDispatcher
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    _paused = YES;
-    _eventQueue = [NSMutableDictionary new];
-    _eventQueueLock = [NSLock new];
-  }
-  return self;
+  _bridge = bridge;
+  _paused = YES;
+  _eventQueue = [NSMutableDictionary new];
+  _eventQueueLock = [NSLock new];
 }
 
 - (void)setPaused:(BOOL)paused
@@ -188,6 +138,7 @@ - (void)sendTextEventWithType:(RCTTextEventType)type
 - (void)sendEvent:(id<RCTEvent>)event
 {
   if (!event.canCoalesce) {
+    [self flushEventsQueue];
     [self dispatchEvent:event];
     return;
   }
@@ -209,19 +160,7 @@ - (void)sendEvent:(id<RCTEvent>)event
 
 - (void)dispatchEvent:(id<RCTEvent>)event
 {
-  NSMutableArray<id /* any JSON value */> *arguments = [NSMutableArray new];
-
-  if (event.viewTag) {
-    [arguments addObject:event.viewTag];
-  }
-
-  [arguments addObject:RCTNormalizeInputEventName(event.eventName)];
-
-  if (event.body) {
-    [arguments addObject:event.body];
-  }
-
-  [_bridge enqueueJSCall:[[event class] moduleDotMethod] args:arguments];
+  [_bridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]];
 }
 
 - (dispatch_queue_t)methodQueue
@@ -230,9 +169,14 @@ - (dispatch_queue_t)methodQueue
 }
 
 - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
+{
+  [self flushEventsQueue];
+}
+
+- (void)flushEventsQueue
 {
   [_eventQueueLock lock];
-   NSDictionary *eventQueue = _eventQueue;
+  NSDictionary *eventQueue = _eventQueue;
   _eventQueue = [NSMutableDictionary new];
   self.paused = YES;
   [_eventQueueLock unlock];
diff --git a/React/Base/RCTImageSource.h b/React/Base/RCTImageSource.h
new file mode 100644
index 000000000000..efcefaa2b89f
--- /dev/null
+++ b/React/Base/RCTImageSource.h
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "RCTConvert.h"
+
+/**
+ * Object containing an image URL and associated metadata.
+ */
+@interface RCTImageSource : NSObject
+
+@property (nonatomic, strong, readonly) NSURL *imageURL;
+@property (nonatomic, assign, readonly) CGSize size;
+@property (nonatomic, assign, readonly) CGFloat scale;
+
+/**
+ * Create a new image source object.
+ * Pass a size of CGSizeZero if you do not know or wish to specify the image
+ * size. Pass a scale of zero if you do not know or wish to specify the scale.
+ */
+- (instancetype)initWithURL:(NSURL *)url
+                       size:(CGSize)size
+                      scale:(CGFloat)scale;
+
+/**
+ * Create a copy of the image source with the specified size and scale.
+ */
+- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale;
+
+@end
+
+@interface RCTConvert (ImageSource)
+
++ (RCTImageSource *)RCTImageSource:(id)json;
+
+@end
diff --git a/React/Base/RCTImageSource.m b/React/Base/RCTImageSource.m
new file mode 100644
index 000000000000..07518edf928a
--- /dev/null
+++ b/React/Base/RCTImageSource.m
@@ -0,0 +1,84 @@
+/**
+ * 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.
+ */
+
+#import "RCTImageSource.h"
+#import "RCTUtils.h"
+
+@interface RCTImageSource ()
+
+@property (nonatomic, assign) BOOL packagerAsset;
+
+@end
+
+@implementation RCTImageSource
+
+- (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale
+{
+  if ((self = [super init])) {
+    _imageURL  = url;
+    _size = size;
+    _scale = scale;
+  }
+  return self;
+}
+
+- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale
+{
+  RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:_imageURL
+                                                               size:size
+                                                              scale:scale];
+  imageSource.packagerAsset = _packagerAsset;
+  return imageSource;
+}
+
+- (BOOL)isEqual:(RCTImageSource *)object
+{
+  if (![object isKindOfClass:[RCTImageSource class]]) {
+    return NO;
+  }
+  return [_imageURL isEqual:object.imageURL] && _scale == object.scale &&
+  (CGSizeEqualToSize(_size, object.size) || CGSizeEqualToSize(object.size, CGSizeZero));
+}
+
+@end
+
+@implementation RCTConvert (ImageSource)
+
++ (RCTImageSource *)RCTImageSource:(id)json
+{
+  if (!json) {
+    return nil;
+  }
+
+  NSURL *imageURL;
+  CGSize size = CGSizeZero;
+  CGFloat scale = 1.0;
+  BOOL packagerAsset = NO;
+  if ([json isKindOfClass:[NSDictionary class]]) {
+    if (!(imageURL = [self NSURL:RCTNilIfNull(json[@"uri"])])) {
+      return nil;
+    }
+    size = [self CGSize:json];
+    scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0;
+    packagerAsset = [self BOOL:json[@"__packager_asset"]];
+  } else if ([json isKindOfClass:[NSString class]]) {
+    imageURL = [self NSURL:json];
+  } else {
+    RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?");
+    return nil;
+  }
+
+  RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL
+                                                               size:size
+                                                              scale:scale];
+  imageSource.packagerAsset = packagerAsset;
+  return imageSource;
+}
+
+@end
diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h
index 5506ddfa3187..2e0b80860d00 100644
--- a/React/Base/RCTJavaScriptExecutor.h
+++ b/React/Base/RCTJavaScriptExecutor.h
@@ -35,13 +35,29 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
 @property (nonatomic, readonly, getter=isValid) BOOL valid;
 
 /**
- * Executes given method with arguments on JS thread and calls the given callback
- * with JSValue and JSContext as a result of the JS module call.
+ * Executes BatchedBridge.flushedQueue on JS thread and calls the given callback
+ * with JSValue, containing the next queue, and JSContext.
  */
-- (void)executeJSCall:(NSString *)name
-               method:(NSString *)method
-            arguments:(NSArray *)arguments
-             callback:(RCTJavaScriptCallback)onComplete;
+- (void)flushedQueue:(RCTJavaScriptCallback)onComplete;
+
+/**
+ * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module name,
+ * method name and optional additional arguments on the JS thread and calls the
+ * given callback with JSValue, containing the next queue, and JSContext.
+ */
+- (void)callFunctionOnModule:(NSString *)module
+                      method:(NSString *)method
+                   arguments:(NSArray *)args
+                    callback:(RCTJavaScriptCallback)onComplete;
+
+/**
+ * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
+ * and optional additional arguments on the JS thread and calls the
+ * given callback with JSValue, containing the next queue, and JSContext.
+ */
+- (void)invokeCallbackID:(NSNumber *)cbID
+               arguments:(NSArray *)args
+                callback:(RCTJavaScriptCallback)onComplete;
 
 /**
  * Runs an application script, and notifies of the script load being complete via `onComplete`.
@@ -69,4 +85,10 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
  */
 - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
 
+/**
+ * For executors that support it, this method can be used to add a synchronous
+ * callback function for communicating with the javascript context.
+ */
+- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block;
+
 @end
diff --git a/React/Base/RCTKeyboardObserver.m b/React/Base/RCTKeyboardObserver.m
index fbe4e4f7647b..88523a549f4f 100644
--- a/React/Base/RCTKeyboardObserver.m
+++ b/React/Base/RCTKeyboardObserver.m
@@ -19,25 +19,24 @@ @implementation RCTKeyboardObserver
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+  _bridge = bridge;
+
+  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
 
 #define ADD_KEYBOARD_HANDLER(NAME, SELECTOR) \
-    [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil]
+  [nc addObserver:self selector:@selector(SELECTOR:) name:NAME object:nil]
 
-    ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow);
-    ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow);
-    ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide);
-    ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide);
-    ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame);
-    ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame);
+  ADD_KEYBOARD_HANDLER(UIKeyboardWillShowNotification, keyboardWillShow);
+  ADD_KEYBOARD_HANDLER(UIKeyboardDidShowNotification, keyboardDidShow);
+  ADD_KEYBOARD_HANDLER(UIKeyboardWillHideNotification, keyboardWillHide);
+  ADD_KEYBOARD_HANDLER(UIKeyboardDidHideNotification, keyboardDidHide);
+  ADD_KEYBOARD_HANDLER(UIKeyboardWillChangeFrameNotification, keyboardWillChangeFrame);
+  ADD_KEYBOARD_HANDLER(UIKeyboardDidChangeFrameNotification, keyboardDidChangeFrame);
 
 #undef ADD_KEYBOARD_HANDLER
-  }
 
-  return self;
 }
 
 - (void)dealloc
diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m
index 86392db1f999..459d8a6660d5 100644
--- a/React/Base/RCTLog.m
+++ b/React/Base/RCTLog.m
@@ -13,15 +13,10 @@
 
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTDefines.h"
 #import "RCTRedBox.h"
-
-@interface RCTBridge ()
-
-+ (RCTBridge *)currentBridge;
-- (void)logMessage:(NSString *)message level:(NSString *)level;
-
-@end
+#import "RCTUtils.h"
 
 static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
 
@@ -169,7 +164,7 @@ void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
     [log appendString:[formatter stringFromDate:timestamp]];
   }
   if (level) {
-    [log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
+    [log appendFormat:@"[%s]", RCTLogLevels[level]];
   }
 
   [log appendFormat:@"[tid:%@]", RCTCurrentThreadName()];
@@ -206,6 +201,7 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
     }
 
 #if RCT_DEBUG
+
     // Log to red box in debug mode.
     if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) {
       NSArray<NSString *> *stackSymbols = [NSThread callStackSymbols];
@@ -231,9 +227,13 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
       });
     }
 
-    // Log to JS executor
-    [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
+    if (!RCTRunningInTestEnvironment()) {
+      // Log to JS executor
+      [[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
+    }
+
 #endif
+
   }
 }
 
diff --git a/React/Base/RCTModuleData.h b/React/Base/RCTModuleData.h
index cc3747eae8be..a851b2a387b2 100644
--- a/React/Base/RCTModuleData.h
+++ b/React/Base/RCTModuleData.h
@@ -9,28 +9,75 @@
 
 #import <Foundation/Foundation.h>
 
-#import "RCTJavaScriptExecutor.h"
+#import "RCTInvalidating.h"
 
 @protocol RCTBridgeMethod;
+@protocol RCTBridgeModule;
+@class RCTBridge;
 
-@interface RCTModuleData : NSObject
+@interface RCTModuleData : NSObject <RCTInvalidating>
 
-@property (nonatomic, weak, readonly) id<RCTJavaScriptExecutor> javaScriptExecutor;
-@property (nonatomic, strong, readonly) NSNumber *moduleID;
-@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
+- (instancetype)initWithModuleClass:(Class)moduleClass
+                             bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
+                                bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Calls `constantsToExport` on the module and stores the result. Note that
+ * this will init the module if it has not already been created. This method
+ * can be called on any thread, but may block the main thread briefly if the
+ * module implements `constantsToExport`.
+ */
+- (void)gatherConstants;
 
 @property (nonatomic, strong, readonly) Class moduleClass;
 @property (nonatomic, copy, readonly) NSString *name;
+
+/**
+ * Returns the module methods. Note that this will gather the methods the first
+ * time it is called and then memoize the results.
+ */
 @property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods;
-@property (nonatomic, copy, readonly) NSArray *config;
 
-@property (nonatomic, strong) dispatch_queue_t queue;
+/**
+ * Returns YES if module instance has already been initialized; NO otherwise.
+ */
+@property (nonatomic, assign, readonly) BOOL hasInstance;
 
-- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
-                        moduleID:(NSNumber *)moduleID
-                        instance:(id<RCTBridgeModule>)instance NS_DESIGNATED_INITIALIZER;
+/**
+ * Returns YES if module instance must be created on the main thread.
+ */
+@property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup;
 
-- (void)dispatchBlock:(dispatch_block_t)block;
-- (void)dispatchBlock:(dispatch_block_t)block dispatchGroup:(dispatch_group_t)group;
+/**
+ * Returns the current module instance. Note that this will init the instance
+ * if it has not already been created. To check if the module instance exists
+ * without causing it to be created, use `hasInstance` instead.
+ */
+@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;
+
+/**
+ * Returns the module method dispatch queue. Note that this will init both the
+ * queue and the module itself if they have not already been created.
+ */
+@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
+
+/**
+ * Returns the module config. Calls `gatherConstants` internally, so the same
+ * usage caveats apply.
+ */
+@property (nonatomic, copy, readonly) NSArray *config;
+
+/**
+ * Whether the receiver has a valid `instance` which implements -batchDidComplete.
+ */
+@property (nonatomic, assign, readonly) BOOL implementsBatchDidComplete;
+
+/**
+ * Whether the receiver has a valid `instance` which implements
+ * -partialBatchDidFlush.
+ */
+@property (nonatomic, assign, readonly) BOOL implementsPartialBatchDidFlush;
 
 @end
diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m
index ddd0384d49d6..b3569db110e9 100644
--- a/React/Base/RCTModuleData.m
+++ b/React/Base/RCTModuleData.m
@@ -10,49 +10,205 @@
 #import "RCTModuleData.h"
 
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTModuleMethod.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
 
 @implementation RCTModuleData
 {
-  NSDictionary *_constants;
+  NSDictionary<NSString *, id> *_constantsToExport;
   NSString *_queueName;
+  __weak RCTBridge *_bridge;
+  NSLock *_instanceLock;
+  BOOL _setupComplete;
 }
 
 @synthesize methods = _methods;
+@synthesize instance = _instance;
+@synthesize methodQueue = _methodQueue;
 
-- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
-                        moduleID:(NSNumber *)moduleID
-                        instance:(id<RCTBridgeModule>)instance
+- (void)setUp
+{
+  _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
+  _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
+
+  _instanceLock = [NSLock new];
+
+  static IMP objectInitMethod;
+  static SEL setBridgeSelector;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
+    setBridgeSelector = NSSelectorFromString(@"setBridge:");
+  });
+
+  // If a module overrides `init`, `setBridge:`, or `constantsToExport` then we
+  // must assume that it expects for both of those methods to be called on the
+  // main thread, because those methods often need to access UIKit.
+  _requiresMainThreadSetup = _instance ||
+  [_moduleClass instancesRespondToSelector:setBridgeSelector] ||
+  RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport)) ||
+  [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod;
+}
+
+- (instancetype)initWithModuleClass:(Class)moduleClass
+                             bridge:(RCTBridge *)bridge
+{
+  if ((self = [super init])) {
+    _bridge = bridge;
+    _moduleClass = moduleClass;
+    [self setUp];
+  }
+  return self;
+}
+
+- (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
+                                bridge:(RCTBridge *)bridge
 {
   if ((self = [super init])) {
-    _javaScriptExecutor = javaScriptExecutor;
-    _moduleID = moduleID;
+    _bridge = bridge;
     _instance = instance;
     _moduleClass = [instance class];
-    _name = RCTBridgeModuleNameForClass(_moduleClass);
+    [self setUp];
+  }
+  return self;
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)init);
+
+#pragma mark - private setup methods
 
-    // Must be done at init time to ensure it's called on main thread
-    RCTAssertMainThread();
-    if ([_instance respondsToSelector:@selector(constantsToExport)]) {
-      _constants = [_instance constantsToExport];
+- (void)setUpInstanceAndBridge
+{
+  [_instanceLock lock];
+  if (!_setupComplete && _bridge.valid) {
+    if (!_instance) {
+      if (RCT_DEBUG && _requiresMainThreadSetup) {
+        RCTAssertMainThread();
+      }
+      _instance = [_moduleClass new];
+      if (!_instance) {
+        // Module init returned nil, probably because automatic instantatiation
+        // of the module is not supported, and it is supposed to be passed in to
+        // the bridge constructor. Mark setup complete to avoid doing more work.
+        _setupComplete = YES;
+        RCTLogWarn(@"The module %@ is returning nil from its constructor. You "
+                   "may need to instantiate it yourself and pass it into the "
+                   "bridge.", _moduleClass);
+      }
     }
+    // Bridge must be set before methodQueue is set up, as methodQueue
+    // initialization requires it (View Managers get their queue by calling
+    // self.bridge.uiManager.methodQueue)
+    [self setBridgeForInstance];
+  }
+  [_instanceLock unlock];
+
+  // This is called outside of the lock in order to prevent deadlock issues
+  // because the logic in `setUpMethodQueue` can cause `moduleData.instance`
+  // to be accessed re-entrantly.
+  [self setUpMethodQueue];
 
-    // Must be done at init time due to race conditions
-    (void)self.queue;
+  // This is called outside of the lock in order to prevent deadlock issues
+  // because the logic in `finishSetupForInstance` can cause
+  // `moduleData.instance` to be accessed re-entrantly.
+  if (_bridge.moduleSetupComplete) {
+    [self finishSetupForInstance];
   }
-  return self;
 }
 
-RCT_NOT_IMPLEMENTED(- (instancetype)init);
+- (void)setBridgeForInstance
+{
+  if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
+    @try {
+      [(id)_instance setValue:_bridge forKey:@"bridge"];
+    }
+    @catch (NSException *exception) {
+      RCTLogError(@"%@ has no setter or ivar for its bridge, which is not "
+                  "permitted. You must either @synthesize the bridge property, "
+                  "or provide your own setter method.", self.name);
+    }
+  }
+}
+
+- (void)finishSetupForInstance
+{
+  if (!_setupComplete && _instance) {
+    _setupComplete = YES;
+    [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
+    [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
+                                                        object:_bridge
+                                                      userInfo:@{@"module": _instance}];
+  }
+}
+
+- (void)setUpMethodQueue
+{
+  if (_instance && !_methodQueue) {
+    BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
+    if (implementsMethodQueue) {
+      _methodQueue = _instance.methodQueue;
+    }
+    if (!_methodQueue) {
+
+      // Create new queue (store queueName, as it isn't retained by dispatch_queue)
+      _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name];
+      _methodQueue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
+
+      // assign it to the module
+      if (implementsMethodQueue) {
+        @try {
+          [(id)_instance setValue:_methodQueue forKey:@"methodQueue"];
+        }
+        @catch (NSException *exception) {
+          RCTLogError(@"%@ is returning nil for its methodQueue, which is not "
+                      "permitted. You must either return a pre-initialized "
+                      "queue, or @synthesize the methodQueue to let the bridge "
+                      "create a queue for you.", self.name);
+        }
+      }
+    }
+  }
+}
+
+#pragma mark - public getters
+
+- (BOOL)hasInstance
+{
+  return _instance != nil;
+}
+
+- (id<RCTBridgeModule>)instance
+{
+  if (!_setupComplete) {
+    if (_requiresMainThreadSetup) {
+      // The chances of deadlock here are low, because module init very rarely
+      // calls out to other threads, however we can't control when a module might
+      // get accessed by client code during bridge setup, and a very low risk of
+      // deadlock is better than a fairly high risk of an assertion being thrown.
+      RCTExecuteOnMainThread(^{
+        [self setUpInstanceAndBridge];
+      }, YES);
+    } else {
+      [self setUpInstanceAndBridge];
+    }
+  }
+  return _instance;
+}
+
+- (NSString *)name
+{
+  return RCTBridgeModuleNameForClass(_moduleClass);
+}
 
 - (NSArray<id<RCTBridgeMethod>> *)methods
 {
   if (!_methods) {
     NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
 
-    if ([_instance respondsToSelector:@selector(methodsToExport)]) {
+    if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
+      [self instance];
       [moduleMethods addObjectsFromArray:[_instance methodsToExport]];
     }
 
@@ -67,9 +223,9 @@ - (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
         NSArray<NSString *> *entries =
           ((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);
         id<RCTBridgeMethod> moduleMethod =
-          [[RCTModuleMethod alloc] initWithObjCMethodName:entries[1]
-                                             JSMethodName:entries[0]
-                                              moduleClass:_moduleClass];
+          [[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
+                                              JSMethodName:entries[0]
+                                               moduleClass:_moduleClass];
 
         [moduleMethods addObject:moduleMethod];
       }
@@ -82,9 +238,27 @@ - (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
   return _methods;
 }
 
+- (void)gatherConstants
+{
+  if (!_constantsToExport) {
+    if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) {
+      RCTExecuteOnMainThread(^{
+        [self setUpInstanceAndBridge];
+        _constantsToExport = [_instance constantsToExport] ?: @{};
+      }, YES);
+    } else {
+      _constantsToExport = @{};
+    }
+  }
+}
+
 - (NSArray *)config
 {
-  if (_constants.count == 0 && self.methods.count == 0) {
+  [self gatherConstants];
+  __block NSDictionary<NSString *, id> *constants = _constantsToExport;
+  _constantsToExport = nil; // Not needed anymore
+
+  if (constants.count == 0 && self.methods.count == 0) {
     return (id)kCFNull; // Nothing to export
   }
 
@@ -101,9 +275,9 @@ - (NSArray *)config
   }
 
   NSMutableArray *config = [NSMutableArray new];
-  [config addObject:_name];
-  if (_constants.count) {
-    [config addObject:_constants];
+  [config addObject:self.name];
+  if (constants.count) {
+    [config addObject:constants];
   }
   if (methods) {
     [config addObject:methods];
@@ -114,53 +288,20 @@ - (NSArray *)config
   return config;
 }
 
-- (dispatch_queue_t)queue
+- (dispatch_queue_t)methodQueue
 {
-  if (!_queue) {
-    BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
-    if (implementsMethodQueue) {
-      _queue = _instance.methodQueue;
-    }
-    if (!_queue) {
-
-      // Create new queue (store queueName, as it isn't retained by dispatch_queue)
-      _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", _name];
-      _queue = dispatch_queue_create(_queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
-
-      // assign it to the module
-      if (implementsMethodQueue) {
-        @try {
-          [(id)_instance setValue:_queue forKey:@"methodQueue"];
-        }
-        @catch (NSException *exception) {
-          RCTLogError(@"%@ is returning nil for it's methodQueue, which is not "
-                      "permitted. You must either return a pre-initialized "
-                      "queue, or @synthesize the methodQueue to let the bridge "
-                      "create a queue for you.", _name);
-        }
-      }
-    }
-  }
-  return _queue;
+  (void)[self instance];
+  return _methodQueue;
 }
 
-- (void)dispatchBlock:(dispatch_block_t)block
+- (void)invalidate
 {
-  [self dispatchBlock:block dispatchGroup:NULL];
+  _methodQueue = nil;
 }
 
-- (void)dispatchBlock:(dispatch_block_t)block
-        dispatchGroup:(dispatch_group_t)group
+- (NSString *)description
 {
-  if (self.queue == RCTJSThread) {
-    [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
-  } else if (self.queue) {
-    if (group != NULL) {
-      dispatch_group_async(group, self.queue, block);
-    } else {
-      dispatch_async(self.queue, block);
-    }
-  }
+  return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name];
 }
 
 @end
diff --git a/React/Base/RCTModuleMap.m b/React/Base/RCTModuleMap.m
deleted file mode 100644
index fef338763193..000000000000
--- a/React/Base/RCTModuleMap.m
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * 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.
- */
-
-#import "RCTModuleMap.h"
-
-#import "RCTBridge.h"
-#import "RCTBridgeModule.h"
-#import "RCTDefines.h"
-#import "RCTLog.h"
-
-@implementation RCTModuleMap
-{
-  NSDictionary *_modulesByName;
-}
-
-RCT_NOT_IMPLEMENTED(- (instancetype)init)
-RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:aDecoder)
-RCT_NOT_IMPLEMENTED(- (instancetype)initWithObjects:(const id [])objects
-                                            forKeys:(const id<NSCopying> [])keys
-                                              count:(NSUInteger)cnt)
-
-- (instancetype)initWithDictionary:(NSDictionary *)modulesByName
-{
-  if ((self = [super init])) {
-    _modulesByName = [modulesByName copy];
-  }
-  return self;
-}
-
-- (NSUInteger)count
-{
-  return _modulesByName.count;
-}
-
-//declared in RCTBridge.m
-extern BOOL RCTBridgeModuleClassIsRegistered(Class cls);
-
-- (id)objectForKey:(NSString *)moduleName
-{
-  id<RCTBridgeModule> module = _modulesByName[moduleName];
-  if (RCT_DEBUG) {
-    if (module) {
-      Class moduleClass = [module class];
-      if (!RCTBridgeModuleClassIsRegistered(moduleClass)) {
-        RCTLogError(@"Class %@ was not exported. Did you forget to use "
-                    "RCT_EXPORT_MODULE()?", moduleClass);
-      }
-    } else {
-      Class moduleClass = NSClassFromString(moduleName);
-      module = _modulesByName[moduleName];
-      if (module) {
-        RCTLogError(@"bridge.modules[name] expects a module name, not a class "
-                    "name. Did you mean to pass '%@' instead?",
-                    RCTBridgeModuleNameForClass(moduleClass));
-      }
-    }
-  }
-  return module;
-}
-
-- (NSEnumerator *)keyEnumerator
-{
-  return [_modulesByName keyEnumerator];
-}
-
-- (NSArray<id<RCTBridgeModule>> *)allValues
-{
-  // don't perform validation in this case because we only want to error when
-  // an invalid module is specifically requested
-  return _modulesByName.allValues;
-}
-
-@end
diff --git a/React/Base/RCTModuleMethod.h b/React/Base/RCTModuleMethod.h
index f450c412cea0..ab525e030274 100644
--- a/React/Base/RCTModuleMethod.h
+++ b/React/Base/RCTModuleMethod.h
@@ -10,15 +10,10 @@
 #import <Foundation/Foundation.h>
 
 #import "RCTBridgeMethod.h"
+#import "RCTNullability.h"
 
 @class RCTBridge;
 
-typedef NS_ENUM(NSUInteger, RCTNullability) {
-  RCTNullabilityUnspecified,
-  RCTNullable,
-  RCTNonnullable,
-};
-
 @interface RCTMethodArgument : NSObject
 
 @property (nonatomic, copy, readonly) NSString *type;
@@ -32,12 +27,8 @@ typedef NS_ENUM(NSUInteger, RCTNullability) {
 @property (nonatomic, readonly) Class moduleClass;
 @property (nonatomic, readonly) SEL selector;
 
-- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
+- (instancetype)initWithMethodSignature:(NSString *)objCMethodName
                           JSMethodName:(NSString *)JSMethodName
                            moduleClass:(Class)moduleClass NS_DESIGNATED_INITIALIZER;
 
-- (void)invokeWithBridge:(RCTBridge *)bridge
-                  module:(id)module
-               arguments:(NSArray *)arguments;
-
 @end
diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m
index c35017c982fd..0e7dd72f246a 100644
--- a/React/Base/RCTModuleMethod.m
+++ b/React/Base/RCTModuleMethod.m
@@ -13,8 +13,10 @@
 
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTConvert.h"
 #import "RCTLog.h"
+#import "RCTParserUtils.h"
 #import "RCTUtils.h"
 
 typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id);
@@ -35,20 +37,12 @@ - (instancetype)initWithType:(NSString *)type
 
 @end
 
-@interface RCTBridge (RCTModuleMethod)
-
-- (void)_invokeAndProcessModule:(NSString *)module
-                         method:(NSString *)method
-                      arguments:(NSArray *)args;
-
-@end
-
 @implementation RCTModuleMethod
 {
   Class _moduleClass;
   NSInvocation *_invocation;
   NSArray<RCTArgumentBlock> *_argumentBlocks;
-  NSString *_objCMethodName;
+  NSString *_methodSignature;
   SEL _selector;
   NSDictionary *_profileArgs;
 }
@@ -66,77 +60,106 @@ static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index,
 
 RCT_NOT_IMPLEMENTED(- (instancetype)init)
 
-void RCTParseObjCMethodName(NSString **, NSArray<RCTMethodArgument *> **);
-void RCTParseObjCMethodName(NSString **objCMethodName, NSArray<RCTMethodArgument *> **arguments)
+// returns YES if the selector ends in a colon (indicating that there is at
+// least one argument, and maybe more selector parts) or NO if it doesn't.
+static BOOL RCTParseSelectorPart(const char **input, NSMutableString *selector)
 {
-  static NSRegularExpression *typeNameRegex;
-  static dispatch_once_t onceToken;
-  dispatch_once(&onceToken, ^{
-    NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
-    NSString *constPattern = @"(?:const)";
-    NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))";
-    NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))";
-    NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:(%@)|%@|(%@)|(%@))\\s*)",
-                                   unusedPattern, constPattern, nullablePattern, nonnullPattern];
-    NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
-                         annotationPattern];
-    typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
-  });
-
-  // Extract argument types
-  NSString *methodName = *objCMethodName;
-  NSRange methodRange = {0, methodName.length};
-  NSMutableArray *args = [NSMutableArray array];
-  [typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
-    NSRange typeRange = [result rangeAtIndex:5];
-    NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id";
-    BOOL unused = ([result rangeAtIndex:2].length > 0);
-    RCTNullability nullability = [result rangeAtIndex:3].length ? RCTNullable :
-      [result rangeAtIndex:4].length ? RCTNonnullable : RCTNullabilityUnspecified;
-    [args addObject:[[RCTMethodArgument alloc] initWithType:type
-                                                nullability:nullability
-                                                     unused:unused]];
-  }];
-  *arguments = [args copy];
+  NSString *selectorPart;
+  if (RCTParseIdentifier(input, &selectorPart)) {
+    [selector appendString:selectorPart];
+  }
+  RCTSkipWhitespace(input);
+  if (RCTReadChar(input, ':')) {
+    [selector appendString:@":"];
+    RCTSkipWhitespace(input);
+    return YES;
+  }
+  return NO;
+}
 
-  // Remove the parameter types and names
-  methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
-                                                         range:methodRange
-                                                  withTemplate:@""];
+static BOOL RCTParseUnused(const char **input)
+{
+  return RCTReadString(input, "__unused") ||
+         RCTReadString(input, "__attribute__((unused))");
+}
 
-  // Remove whitespace
-  methodName = [methodName stringByReplacingOccurrencesOfString:@"\n" withString:@""];
-  methodName = [methodName stringByReplacingOccurrencesOfString:@" " withString:@""];
+static RCTNullability RCTParseNullability(const char **input)
+{
+  if (RCTReadString(input, "nullable")) {
+    return RCTNullable;
+  } else if (RCTReadString(input, "nonnull")) {
+    return RCTNonnullable;
+  }
+  return RCTNullabilityUnspecified;
+}
 
-  // Strip trailing semicolon
-  if ([methodName hasSuffix:@";"]) {
-    methodName = [methodName substringToIndex:methodName.length - 1];
+SEL RCTParseMethodSignature(NSString *, NSArray<RCTMethodArgument *> **);
+SEL RCTParseMethodSignature(NSString *methodSignature, NSArray<RCTMethodArgument *> **arguments)
+{
+  const char *input = methodSignature.UTF8String;
+  RCTSkipWhitespace(&input);
+
+  NSMutableArray *args;
+  NSMutableString *selector = [NSMutableString new];
+  while (RCTParseSelectorPart(&input, selector)) {
+    if (!args) {
+      args = [NSMutableArray new];
+    }
+
+    // Parse type
+    if (RCTReadChar(&input, '(')) {
+      RCTSkipWhitespace(&input);
+
+      BOOL unused = RCTParseUnused(&input);
+      RCTSkipWhitespace(&input);
+
+      RCTNullability nullability = RCTParseNullability(&input);
+      RCTSkipWhitespace(&input);
+
+      NSString *type = RCTParseType(&input);
+      [args addObject:[[RCTMethodArgument alloc] initWithType:type
+                                                  nullability:nullability
+                                                       unused:unused]];
+      RCTSkipWhitespace(&input);
+      RCTReadChar(&input, ')');
+      RCTSkipWhitespace(&input);
+    } else {
+      // Type defaults to id if unspecified
+      [args addObject:[[RCTMethodArgument alloc] initWithType:@"id"
+                                                  nullability:RCTNullable
+                                                       unused:NO]];
+    }
+
+    // Argument name
+    RCTParseIdentifier(&input, NULL);
+    RCTSkipWhitespace(&input);
   }
 
-  *objCMethodName = methodName;
+  *arguments = [args copy];
+  return NSSelectorFromString(selector);
 }
 
-- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
-                          JSMethodName:(NSString *)JSMethodName
-                           moduleClass:(Class)moduleClass
+- (instancetype)initWithMethodSignature:(NSString *)methodSignature
+                           JSMethodName:(NSString *)JSMethodName
+                            moduleClass:(Class)moduleClass
 {
   if ((self = [super init])) {
 
     _moduleClass = moduleClass;
-    _objCMethodName = [objCMethodName copy];
+    _methodSignature = [methodSignature copy];
     _JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({
-      NSString *methodName = objCMethodName;
+      NSString *methodName = methodSignature;
       NSRange colonRange = [methodName rangeOfString:@":"];
       if (colonRange.location != NSNotFound) {
         methodName = [methodName substringToIndex:colonRange.location];
       }
       methodName = [methodName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
       RCTAssert(methodName.length, @"%@ is not a valid JS function name, please"
-                " supply an alternative using RCT_REMAP_METHOD()", objCMethodName);
+                " supply an alternative using RCT_REMAP_METHOD()", methodSignature);
       methodName;
     });
 
-    if ([_objCMethodName rangeOfString:@"RCTPromise"].length) {
+    if ([_methodSignature rangeOfString:@"RCTPromise"].length) {
       _functionType = RCTFunctionTypePromise;
     } else {
       _functionType = RCTFunctionTypeNormal;
@@ -149,15 +172,12 @@ - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
 - (void)processMethodSignature
 {
   NSArray<RCTMethodArgument *> *arguments;
-  NSString *objCMethodName = _objCMethodName;
-  RCTParseObjCMethodName(&objCMethodName, &arguments);
-
-  _selector = NSSelectorFromString(objCMethodName);
-  RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName);
+  _selector = RCTParseMethodSignature(_methodSignature, &arguments);
+  RCTAssert(_selector, @"%@ is not a valid selector", _methodSignature);
 
   // Create method invocation
   NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector];
-  RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName);
+  RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", _methodSignature);
   NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
   invocation.selector = _selector;
   _invocation = invocation;
@@ -191,9 +211,7 @@ - (void)processMethodSignature
       }
 
       RCT_BLOCK_ARGUMENT(^(NSArray *args) {
-        [bridge _invokeAndProcessModule:@"BatchedBridge"
-                                 method:@"invokeCallbackAndReturnFlushedQueue"
-                              arguments:@[json, args]];
+        [bridge enqueueCallback:json args:args];
       });
     )
   };
@@ -203,7 +221,7 @@ - (void)processMethodSignature
     BOOL isNullableType = NO;
     RCTMethodArgument *argument = arguments[i - 2];
     NSString *typeName = argument.type;
-    SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]);
+    SEL selector = RCTConvertSelectorForType(typeName);
     if ([RCTConvert respondsToSelector:selector]) {
       switch (objcType[0]) {
 
@@ -290,15 +308,13 @@ - (void)processMethodSignature
         }
 
         RCT_BLOCK_ARGUMENT(^(NSError *error) {
-          [bridge _invokeAndProcessModule:@"BatchedBridge"
-                                     method:@"invokeCallbackAndReturnFlushedQueue"
-                                arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
+          [bridge enqueueCallback:json args:@[RCTJSErrorFromNSError(error)]];
         });
       )
     } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
       RCTAssert(i == numberOfArguments - 2,
                 @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
-                _moduleClass, objCMethodName);
+                _moduleClass, _methodSignature);
       RCT_ARG_BLOCK(
         if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
           RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function");
@@ -306,26 +322,22 @@ - (void)processMethodSignature
         }
 
         RCT_BLOCK_ARGUMENT(^(id result) {
-          [bridge _invokeAndProcessModule:@"BatchedBridge"
-                                   method:@"invokeCallbackAndReturnFlushedQueue"
-                                arguments:@[json, result ? @[result] : @[]]];
+          [bridge enqueueCallback:json args:result ? @[result] : @[]];
         });
       )
     } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
       RCTAssert(i == numberOfArguments - 1,
                 @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
-                _moduleClass, objCMethodName);
+                _moduleClass, _methodSignature);
       RCT_ARG_BLOCK(
         if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
           RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function");
           return NO;
         }
 
-        RCT_BLOCK_ARGUMENT(^(NSError *error) {
-          NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
-          [bridge _invokeAndProcessModule:@"BatchedBridge"
-                                   method:@"invokeCallbackAndReturnFlushedQueue"
-                                arguments:@[json, @[errorJSON]]];
+        RCT_BLOCK_ARGUMENT(^(NSString *code, NSString *message, NSError *error) {
+          NSDictionary *errorJSON = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
+          [bridge enqueueCallback:json args:@[errorJSON]];
         });
       )
     } else {
@@ -436,7 +448,10 @@ - (void)invokeWithBridge:(RCTBridge *)bridge
         expectedCount -= 2;
       }
 
-      RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
+      RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd. \
+                  If you haven\'t changed this method yourself, this usually means that \
+                  your versions of the native code and JavaScript code are out of sync. \
+                  Updating both should make this error go away.",
                   RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
                   actualCount, expectedCount);
       return;
diff --git a/React/Base/RCTModuleMap.h b/React/Base/RCTNullability.h
similarity index 71%
rename from React/Base/RCTModuleMap.h
rename to React/Base/RCTNullability.h
index 48e7c73e66a7..6d250ee4702e 100644
--- a/React/Base/RCTModuleMap.h
+++ b/React/Base/RCTNullability.h
@@ -9,8 +9,8 @@
 
 #import <Foundation/Foundation.h>
 
-@interface RCTModuleMap : NSDictionary
-
-- (instancetype)initWithDictionary:(NSDictionary *)modulesByName NS_DESIGNATED_INITIALIZER;
-
-@end
+typedef NS_ENUM(NSUInteger, RCTNullability) {
+  RCTNullabilityUnspecified,
+  RCTNullable,
+  RCTNonnullable,
+};
diff --git a/React/Base/RCTParserUtils.h b/React/Base/RCTParserUtils.h
new file mode 100644
index 000000000000..dcbe413ef713
--- /dev/null
+++ b/React/Base/RCTParserUtils.h
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "RCTDefines.h"
+
+@interface RCTParserUtils : NSObject
+
+/**
+ * Generic utility functions for parsing Objective-C source code.
+ */
+RCT_EXTERN BOOL RCTReadChar(const char **input, char c);
+RCT_EXTERN BOOL RCTReadString(const char **input, const char *string);
+RCT_EXTERN void RCTSkipWhitespace(const char **input);
+RCT_EXTERN BOOL RCTParseIdentifier(const char **input, NSString **string);
+
+/**
+ * Parse an Objective-C type into a form that can be used by RCTConvert.
+ * This doesn't really belong here, but it's used by both RCTConvert and
+ * RCTModuleMethod, which makes it difficult to find a better home for it.
+ */
+RCT_EXTERN NSString *RCTParseType(const char **input);
+
+@end
diff --git a/React/Base/RCTParserUtils.m b/React/Base/RCTParserUtils.m
new file mode 100644
index 000000000000..5e9182a5fa45
--- /dev/null
+++ b/React/Base/RCTParserUtils.m
@@ -0,0 +1,118 @@
+/**
+ * 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.
+ */
+
+#import "RCTParserUtils.h"
+
+#import "RCTLog.h"
+
+@implementation RCTParserUtils
+
+BOOL RCTReadChar(const char **input, char c)
+{
+  if (**input == c) {
+    (*input)++;
+    return YES;
+  }
+  return NO;
+}
+
+BOOL RCTReadString(const char **input, const char *string)
+{
+  int i;
+  for (i = 0; string[i] != 0; i++) {
+    if (string[i] != (*input)[i]) {
+      return NO;
+    }
+  }
+  *input += i;
+  return YES;
+}
+
+void RCTSkipWhitespace(const char **input)
+{
+  while (isspace(**input)) {
+    (*input)++;
+  }
+}
+
+static BOOL RCTIsIdentifierHead(const char c)
+{
+  return isalpha(c) || c == '_';
+}
+
+static BOOL RCTIsIdentifierTail(const char c)
+{
+  return isalnum(c) || c == '_';
+}
+
+BOOL RCTParseIdentifier(const char **input, NSString **string)
+{
+  const char *start = *input;
+  if (!RCTIsIdentifierHead(**input)) {
+    return NO;
+  }
+  (*input)++;
+  while (RCTIsIdentifierTail(**input)) {
+    (*input)++;
+  }
+  if (string) {
+    *string = [[NSString alloc] initWithBytes:start
+                                       length:(NSInteger)(*input - start)
+                                     encoding:NSASCIIStringEncoding];
+  }
+  return YES;
+}
+
+static BOOL RCTIsCollectionType(NSString *type)
+{
+  static NSSet *collectionTypes;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    collectionTypes = [[NSSet alloc] initWithObjects:
+                       @"NSArray", @"NSSet", @"NSDictionary", nil];
+  });
+  return [collectionTypes containsObject:type];
+}
+
+NSString *RCTParseType(const char **input)
+{
+  NSString *type;
+  RCTParseIdentifier(input, &type);
+  RCTSkipWhitespace(input);
+  if (RCTReadChar(input, '<')) {
+    RCTSkipWhitespace(input);
+    NSString *subtype = RCTParseType(input);
+    if (RCTIsCollectionType(type)) {
+      if ([type isEqualToString:@"NSDictionary"]) {
+        // Dictionaries have both a key *and* value type, but the key type has
+        // to be a string for JSON, so we only care about the value type
+        if (RCT_DEBUG && ![subtype isEqualToString:@"NSString"]) {
+          RCTLogError(@"%@ is not a valid key type for a JSON dictionary", subtype);
+        }
+        RCTSkipWhitespace(input);
+        RCTReadChar(input, ',');
+        RCTSkipWhitespace(input);
+        subtype = RCTParseType(input);
+      }
+      if (![subtype isEqualToString:@"id"]) {
+        type = [type stringByReplacingCharactersInRange:(NSRange){0, 2 /* "NS" */}
+                                             withString:subtype];
+      }
+    } else {
+      // It's a protocol rather than a generic collection - ignore it
+    }
+    RCTSkipWhitespace(input);
+    RCTReadChar(input, '>');
+  }
+  RCTSkipWhitespace(input);
+  RCTReadChar(input, '*');
+  return type;
+}
+
+@end
diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m
index f099db677122..d7f45d6930c4 100644
--- a/React/Base/RCTPerformanceLogger.m
+++ b/React/Base/RCTPerformanceLogger.m
@@ -79,15 +79,14 @@ @implementation RCTPerformanceLogger
 
 @synthesize bridge = _bridge;
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(sendTimespans)
-                                                 name:RCTContentDidAppearNotification
-                                               object:nil];
-  }
-  return self;
+  _bridge = bridge;
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(sendTimespans)
+                                               name:RCTContentDidAppearNotification
+                                             object:nil];
 }
 
 - (void)dealloc
diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h
index fdb7d6b9b22e..de5908974719 100644
--- a/React/Base/RCTRootView.h
+++ b/React/Base/RCTRootView.h
@@ -74,14 +74,6 @@ extern NSString *const RCTContentDidAppearNotification;
  */
 @property (nonatomic, strong, readonly) RCTBridge *bridge;
 
-/**
- * DEPRECATED: access app properties via appProperties property instead
- *
- * The default properties to apply to the view when the script bundle
- * is first loaded. Defaults to nil/empty.
- */
-@property (nonatomic, copy, readonly) NSDictionary *initialProperties DEPRECATED_MSG_ATTRIBUTE ("use appProperties instead");
-
 /**
  * The properties to apply to the view. Use this property to update
  * application properties and rerender the view. Initialized with
@@ -91,13 +83,6 @@ extern NSString *const RCTContentDidAppearNotification;
  */
 @property (nonatomic, copy, readwrite) NSDictionary *appProperties;
 
-/**
- * The class of the RCTJavaScriptExecutor to use with this view.
- * If not specified, it will default to using RCTContextExecutor.
- * Changes will take effect next time the bundle is reloaded.
- */
-@property (nonatomic, strong) Class executorClass;
-
 /**
  * The size flexibility mode of the root view.
  */
@@ -131,6 +116,25 @@ extern NSString *const RCTContentDidAppearNotification;
  */
 @property (nonatomic, strong) UIView *loadingView;
 
+/**
+ * Calling this will result in emitting a "touches cancelled" event to js,
+ * which effectively cancels all js "gesture recognizers" such as as touchable
+ * (unless they explicitely ignore cancellation events, but noone should do that).
+ *
+ * This API is exposed for integration purposes where you embed RN rootView
+ * in a native view with a native gesture recognizer,
+ * whose activation should prevent any in-flight js "gesture recognizer" from activating.
+ *
+ * An example would be RN rootView embedded in an UIScrollView.
+ * When you touch down on a touchable component and drag your finger up,
+ * you don't want any touch to be registered as soon as the UIScrollView starts scrolling.
+ *
+ * Note that this doesn't help with tapping on a touchable element that is being scrolled,
+ * unless you can call cancelTouches exactly between "touches began" and "touches ended" events.
+ * This is a reason why this API may be soon removed in favor of a better solution.
+ */
+- (void)cancelTouches;
+
 /**
  * Timings for hiding the loading view after the content has loaded. Both of
  * these values default to 0.25 seconds.
diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m
index fb9116d620ab..d571a0690b2d 100644
--- a/React/Base/RCTRootView.m
+++ b/React/Base/RCTRootView.m
@@ -14,7 +14,7 @@
 #import <objc/runtime.h>
 
 #import "RCTAssert.h"
-#import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTEventDispatcher.h"
 #import "RCTKeyCommands.h"
 #import "RCTLog.h"
@@ -25,15 +25,10 @@
 #import "RCTUtils.h"
 #import "RCTView.h"
 #import "UIView+React.h"
+#import "RCTProfile.h"
 
 NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
 
-@interface RCTBridge (RCTRootView)
-
-@property (nonatomic, weak, readonly) RCTBridge *batchedBridge;
-
-@end
-
 @interface RCTUIManager (RCTRootView)
 
 - (NSNumber *)allocateRootTag;
@@ -43,8 +38,11 @@ - (NSNumber *)allocateRootTag;
 @interface RCTRootContentView : RCTView <RCTInvalidating>
 
 @property (nonatomic, readonly) BOOL contentHasAppeared;
+@property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler;
 
-- (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithFrame:(CGRect)frame
+                       bridge:(RCTBridge *)bridge
+                     reactTag:(NSNumber *)reactTag NS_DESIGNATED_INITIALIZER;
 
 @end
 
@@ -53,7 +51,6 @@ @implementation RCTRootView
   RCTBridge *_bridge;
   NSString *_moduleName;
   NSDictionary *_launchOptions;
-  NSDictionary *_initialProperties;
   RCTRootContentView *_contentView;
 }
 
@@ -65,18 +62,24 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
   RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
   RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
 
+  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTRootView init]", nil);
+
   if ((self = [super initWithFrame:CGRectZero])) {
 
     self.backgroundColor = [UIColor whiteColor];
 
     _bridge = bridge;
     _moduleName = moduleName;
-    _initialProperties = [initialProperties copy];
     _appProperties = [initialProperties copy];
     _loadingViewFadeDelay = 0.25;
     _loadingViewFadeDuration = 0.25;
     _sizeFlexibility = RCTRootViewSizeFlexibilityNone;
 
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(bridgeDidReload)
+                                                 name:RCTJavaScriptWillStartLoadingNotification
+                                               object:_bridge];
+
     [[NSNotificationCenter defaultCenter] addObserver:self
                                              selector:@selector(javaScriptDidLoad:)
                                                  name:RCTJavaScriptDidLoadNotification
@@ -86,12 +89,16 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
                                              selector:@selector(hideLoadingView)
                                                  name:RCTContentDidAppearNotification
                                                object:self];
+
     if (!_bridge.loading) {
       [self bundleFinishedLoading:_bridge.batchedBridge];
     }
 
     [self showLoadingView];
   }
+
+  RCT_PROFILE_END_EVENT(0, @"", nil);
+
   return self;
 }
 
@@ -165,6 +172,29 @@ - (void)hideLoadingView
   }
 }
 
+- (NSNumber *)reactTag
+{
+  RCTAssertMainThread();
+  if (!super.reactTag) {
+    /**
+     * Every root view that is created must have a unique react tag.
+     * Numbering of these tags goes from 1, 11, 21, 31, etc
+     *
+     * NOTE: Since the bridge persists, the RootViews might be reused, so the
+     * react tag must be re-assigned every time a new UIManager is created.
+     */
+    self.reactTag = [_bridge.uiManager allocateRootTag];
+  }
+  return super.reactTag;
+}
+
+- (void)bridgeDidReload
+{
+  RCTAssertMainThread();
+  // Clear the reactTag so it can be re-assigned
+  self.reactTag = nil;
+}
+
 - (void)javaScriptDidLoad:(NSNotification *)notification
 {
   RCTAssertMainThread();
@@ -179,11 +209,13 @@ - (void)bundleFinishedLoading:(RCTBridge *)bridge
   }
 
   [_contentView removeFromSuperview];
-  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge];
+  _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
+                                                    bridge:bridge
+                                                  reactTag:self.reactTag];
+  [self runApplication:bridge];
+
   _contentView.backgroundColor = self.backgroundColor;
   [self insertSubview:_contentView atIndex:0];
-
-  [self runApplication:bridge];
 }
 
 - (void)runApplication:(RCTBridge *)bridge
@@ -214,12 +246,6 @@ - (void)layoutSubviews
   };
 }
 
-- (NSDictionary *)initialProperties
-{
-  RCTLogWarn(@"Using deprecated 'initialProperties' property. Use 'appProperties' instead.");
-  return _initialProperties;
-}
-
 - (void)setAppProperties:(NSDictionary *)appProperties
 {
   RCTAssertMainThread();
@@ -253,11 +279,6 @@ - (void)setIntrinsicSize:(CGSize)intrinsicSize
   [_delegate rootViewDidChangeIntrinsicSize:self];
 }
 
-- (NSNumber *)reactTag
-{
-  return _contentView.reactTag;
-}
-
 - (void)contentViewInvalidated
 {
   [_contentView removeFromSuperview];
@@ -271,6 +292,11 @@ - (void)dealloc
   [_contentView invalidate];
 }
 
+- (void)cancelTouches
+{
+  [[_contentView touchHandler] cancel];
+}
+
 @end
 
 @implementation RCTUIManager (RCTRootView)
@@ -287,16 +313,19 @@ - (NSNumber *)allocateRootTag
 @implementation RCTRootContentView
 {
   __weak RCTBridge *_bridge;
-  RCTTouchHandler *_touchHandler;
   UIColor *_backgroundColor;
 }
 
 - (instancetype)initWithFrame:(CGRect)frame
                        bridge:(RCTBridge *)bridge
+                     reactTag:(NSNumber *)reactTag
 {
   if ((self = [super initWithFrame:frame])) {
     _bridge = bridge;
-    [self setUp];
+    self.reactTag = reactTag;
+    _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
+    [self addGestureRecognizer:_touchHandler];
+    [_bridge.uiManager registerRootView:self];
     self.layer.backgroundColor = NULL;
   }
   return self;
@@ -339,26 +368,12 @@ - (UIColor *)backgroundColor
   return _backgroundColor;
 }
 
-- (void)setUp
-{
-  /**
-   * Every root view that is created must have a unique react tag.
-   * Numbering of these tags goes from 1, 11, 21, 31, etc
-   *
-   * NOTE: Since the bridge persists, the RootViews might be reused, so now
-   * the react tag is assigned every time we load new content.
-   */
-  self.reactTag = [_bridge.uiManager allocateRootTag];
-  [self addGestureRecognizer:[[RCTTouchHandler alloc] initWithBridge:_bridge]];
-  [_bridge.uiManager registerRootView:self];
-}
-
 - (void)invalidate
 {
   if (self.userInteractionEnabled) {
     self.userInteractionEnabled = NO;
     [(RCTRootView *)self.superview contentViewInvalidated];
-    [_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer"
+    [_bridge enqueueJSCall:@"AppRegistry.unmountApplicationComponentAtRootTag"
                       args:@[self.reactTag]];
   }
 }
diff --git a/React/Base/RCTTouchEvent.h b/React/Base/RCTTouchEvent.h
new file mode 100644
index 000000000000..5fe3951dca7b
--- /dev/null
+++ b/React/Base/RCTTouchEvent.h
@@ -0,0 +1,23 @@
+/**
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "RCTEventDispatcher.h"
+
+/**
+ * Represents a touch event, which may be composed of several touches (one for every finger).
+ * For more information on contents of passed data structures see RCTTouchHandler.
+ */
+@interface RCTTouchEvent : NSObject <RCTEvent>
+
+- (instancetype)initWithEventName:(NSString *)eventName
+                     reactTouches:(NSArray<NSDictionary *> *)reactTouches
+                   changedIndexes:(NSArray<NSNumber *> *)changedIndexes NS_DESIGNATED_INITIALIZER;
+@end
diff --git a/React/Base/RCTTouchEvent.m b/React/Base/RCTTouchEvent.m
new file mode 100644
index 000000000000..a6a97c037094
--- /dev/null
+++ b/React/Base/RCTTouchEvent.m
@@ -0,0 +1,77 @@
+/**
+ * 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.
+ */
+
+#import "RCTTouchEvent.h"
+#import "RCTAssert.h"
+
+@implementation RCTTouchEvent
+{
+  NSArray<NSDictionary *> *_reactTouches;
+  NSArray<NSNumber *> *_changedIndexes;
+}
+
+@synthesize eventName = _eventName;
+@synthesize viewTag = _viewTag;
+
+- (instancetype)initWithEventName:(NSString *)eventName
+                     reactTouches:(NSArray<NSDictionary *> *)reactTouches
+                   changedIndexes:(NSArray<NSNumber *> *)changedIndexes
+{
+  if (self = [super init]) {
+    _eventName = eventName;
+    _reactTouches = reactTouches;
+    _changedIndexes = changedIndexes;
+  }
+  return self;
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)init)
+
+#pragma mark - RCTEvent
+
+- (BOOL)canCoalesce
+{
+  return [_eventName isEqual:@"touchMove"];
+}
+
+// We coalesce only move events, while holding some assumptions that seem reasonable but there are no explicit guarantees about them.
+- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
+{
+  RCTAssert([newEvent isKindOfClass:[RCTTouchEvent class]], @"Touch event cannot be coalesced with any other type of event, such as provided %@", newEvent);
+  RCTTouchEvent *newTouchEvent = (RCTTouchEvent *)newEvent;
+  RCTAssert([_reactTouches count] == [newTouchEvent->_reactTouches count], @"Touch events have different number of touches. %@ %@", self, newEvent);
+
+  BOOL newEventIsMoreRecent = NO;
+  BOOL oldEventIsMoreRecent = NO;
+  NSInteger count = _reactTouches.count;
+  for (int i = 0; i<count; i++) {
+    NSDictionary *touch = _reactTouches[i];
+    NSDictionary *newTouch = newTouchEvent->_reactTouches[i];
+    RCTAssert([touch[@"identifier"] isEqual:newTouch[@"identifier"]], @"Touch events doesn't have touches in the same order. %@ %@", touch, newTouch);
+    if ([touch[@"timestamp"] doubleValue] > [newTouch[@"timestamp"] doubleValue]) {
+      oldEventIsMoreRecent = YES;
+    } else {
+      newEventIsMoreRecent = YES;
+    }
+  }
+  RCTAssert(!(oldEventIsMoreRecent && newEventIsMoreRecent), @"Neither touch event is exclusively more recent than the other one. %@ %@", _reactTouches, newTouchEvent->_reactTouches);
+  return newEventIsMoreRecent ? newEvent : self;
+}
+
++ (NSString *)moduleDotMethod
+{
+  return @"RCTEventEmitter.receiveTouches";
+}
+
+- (NSArray *)arguments
+{
+  return @[RCTNormalizeInputEventName(_eventName), _reactTouches, _changedIndexes];
+}
+
+@end
diff --git a/React/Base/RCTTouchHandler.h b/React/Base/RCTTouchHandler.h
index 9d3f1cbd4f92..5ea1919930d0 100644
--- a/React/Base/RCTTouchHandler.h
+++ b/React/Base/RCTTouchHandler.h
@@ -16,5 +16,6 @@
 @interface RCTTouchHandler : UIGestureRecognizer
 
 - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
+- (void)cancel;
 
 @end
diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m
index 707830aab07f..1727709e7610 100644
--- a/React/Base/RCTTouchHandler.m
+++ b/React/Base/RCTTouchHandler.m
@@ -15,6 +15,7 @@
 #import "RCTBridge.h"
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
+#import "RCTTouchEvent.h"
 #import "RCTUIManager.h"
 #import "RCTUtils.h"
 #import "UIView+React.h"
@@ -23,7 +24,7 @@
 // module if we were to assume that modules and RootViews had a 1:1 relationship
 @implementation RCTTouchHandler
 {
-  __weak RCTBridge *_bridge;
+  __weak RCTEventDispatcher *_eventDispatcher;
 
   /**
    * Arrays managed in parallel tracking native touch object along with the
@@ -46,7 +47,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
 
   if ((self = [super initWithTarget:self action:@selector(handleGestureUpdate:)])) {
 
-    _bridge = bridge;
+    _eventDispatcher = [bridge moduleForClass:[RCTEventDispatcher class]];
     _dispatchedInitialTouches = NO;
     _nativeTouches = [NSMutableOrderedSet new];
     _reactTouches = [NSMutableArray new];
@@ -108,11 +109,9 @@ - (void)_recordNewTouches:(NSSet<UITouch *> *)touches
     }
 
     // Create touch
-    NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
+    NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:RCTMaxTouches];
     reactTouch[@"target"] = reactTag;
     reactTouch[@"identifier"] = @(touchID);
-    reactTouch[@"touches"] = (id)kCFNull;        // We hijack this touchObj to serve both as an event
-    reactTouch[@"changedTouches"] = (id)kCFNull; // and as a Touch object, so making this JIT friendly.
 
     // Add to arrays
     [_touchViews addObject:targetView];
@@ -150,6 +149,13 @@ - (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
   reactTouch[@"locationX"] = @(touchViewLocation.x);
   reactTouch[@"locationY"] = @(touchViewLocation.y);
   reactTouch[@"timestamp"] =  @(nativeTouch.timestamp * 1000); // in ms, for JS
+
+  // TODO: force for a 'normal' touch is usually 1.0;
+  // should we expose a `normalTouchForce` constant somewhere (which would
+  // have a value of `1.0 / nativeTouch.maximumPossibleForce`)?
+  if (RCTForceTouchAvailable()) {
+    reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force / nativeTouch.maximumPossibleForce));
+  }
 }
 
 /**
@@ -191,9 +197,10 @@ - (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches
     [reactTouches addObject:[touch copy]];
   }
 
-  eventName = RCTNormalizeInputEventName(eventName);
-  [_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
-                    args:@[eventName, reactTouches, changedIndexes]];
+  RCTTouchEvent *event = [[RCTTouchEvent alloc] initWithEventName:eventName
+                                                     reactTouches:reactTouches
+                                                   changedIndexes:changedIndexes];
+  [_eventDispatcher sendEvent:event];
 }
 
 #pragma mark - Gesture Recognizer Delegate Callbacks
@@ -311,4 +318,10 @@ - (void)reset
   _dispatchedInitialTouches = NO;
 }
 
+- (void)cancel
+{
+  self.enabled = NO;
+  self.enabled = YES;
+}
+
 @end
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 6d399594e954..ea319814588c 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -16,17 +16,23 @@
 #import "RCTAssert.h"
 #import "RCTDefines.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 // JSON serialization/deserialization
-RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
-RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
-RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
+RCT_EXTERN NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error);
+RCT_EXTERN id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error);
+RCT_EXTERN id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error);
 
-// Strip non JSON-safe values from an object graph
+// Sanitize a JSON object by stripping invalid types and/or NaN values
 RCT_EXTERN id RCTJSONClean(id object);
 
 // Get MD5 hash of a string
 RCT_EXTERN NSString *RCTMD5Hash(NSString *string);
 
+// Execute the specified block on the main thread. Unlike dispatch_sync/async
+// this will not context-switch if we're already running on the main thread.
+RCT_EXTERN void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync);
+
 // Get screen metrics in a thread-safe way
 RCT_EXTERN CGFloat RCTScreenScale(void);
 RCT_EXTERN CGSize RCTScreenSize(void);
@@ -36,6 +42,9 @@ RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value);
 RCT_EXTERN CGFloat RCTCeilPixelValue(CGFloat value);
 RCT_EXTERN CGFloat RCTFloorPixelValue(CGFloat value);
 
+// Convert a size in points to pixels, rounded up to the nearest integral size
+RCT_EXTERN CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale);
+
 // Method swizzling
 RCT_EXTERN void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
 RCT_EXTERN void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
@@ -44,10 +53,14 @@ RCT_EXTERN void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
 RCT_EXTERN BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
 RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
 
-// Creates a standardized error object
-RCT_EXTERN NSDictionary<NSString *, id> *RCTMakeError(NSString *message, id toStringify, NSDictionary<NSString *, id> *extraData);
-RCT_EXTERN NSDictionary<NSString *, id> *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary<NSString *, id> *extraData);
+// Creates a standardized error object to return in callbacks
+RCT_EXTERN NSDictionary<NSString *, id> *RCTMakeError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData);
+RCT_EXTERN NSDictionary<NSString *, id> *RCTMakeAndLogError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData);
 RCT_EXTERN NSDictionary<NSString *, id> *RCTJSErrorFromNSError(NSError *error);
+RCT_EXTERN NSDictionary<NSString *, id> *RCTJSErrorFromCodeMessageAndNSError(NSString *code, NSString *message, NSError *__nullable error);
+
+// The default error code to use as the `code` property for callback error objects
+RCT_EXTERN NSString *const RCTErrorUnspecified;
 
 // Returns YES if React is running in a test environment
 RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
@@ -56,39 +69,57 @@ RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
 RCT_EXTERN BOOL RCTRunningInAppExtension(void);
 
 // Returns the shared UIApplication instance, or nil if running in an App Extension
-RCT_EXTERN UIApplication *RCTSharedApplication(void);
+RCT_EXTERN UIApplication *__nullable RCTSharedApplication(void);
 
 // Returns the current main window, useful if you need to access the root view
 // or view controller, e.g. to present a modal view controller or alert.
-RCT_EXTERN UIWindow *RCTKeyWindow(void);
+RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void);
+
+// Does this device support force touch (aka 3D Touch)?
+RCT_EXTERN BOOL RCTForceTouchAvailable(void);
 
 // Return a UIAlertView initialized with the given values
 // or nil if running in an app extension
-RCT_EXTERN UIAlertView *RCTAlertView(NSString *title,
-                                     NSString *message,
-                                     id delegate,
-                                     NSString *cancelButtonTitle,
-                                     NSArray<NSString *> *otherButtonTitles);
-
-// Return YES if image has an alpha component
-RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
+RCT_EXTERN UIAlertView *__nullable RCTAlertView(NSString *title,
+                                                NSString *__nullable message,
+                                                id __nullable delegate,
+                                                NSString *__nullable cancelButtonTitle,
+                                                NSArray<NSString *> *__nullable otherButtonTitles);
 
 // Create an NSError in the RCTErrorDomain
 RCT_EXTERN NSError *RCTErrorWithMessage(NSString *message);
 
 // Convert nil values to NSNull, and vice-versa
-RCT_EXTERN id RCTNilIfNull(id value);
-RCT_EXTERN id RCTNullIfNil(id value);
+RCT_EXTERN id __nullable RCTNilIfNull(id __nullable value);
+RCT_EXTERN id RCTNullIfNil(id __nullable value);
+
+// Convert NaN or infinite values to zero, as these aren't JSON-safe
+RCT_EXTERN double RCTZeroIfNaN(double value);
 
 // Convert data to a Base64-encoded data URL
 RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
 
 // Gzip functionality - compression level in range 0 - 1 (-1 for default)
-RCT_EXTERN NSData *RCTGzipData(NSData *data, float level);
+RCT_EXTERN NSData *__nullable RCTGzipData(NSData *__nullable data, float level);
 
 // Returns the relative path within the main bundle for an absolute URL
 // (or nil, if the URL does not specify a path within the main bundle)
-RCT_EXTERN NSString *RCTBundlePathForURL(NSURL *URL);
+RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL);
 
 // Determines if a given image URL actually refers to an XCAsset
-RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *imageURL);
+RCT_EXTERN BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL);
+
+// Creates a new, unique temporary file path with the specified extension
+RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *__nullable extension, NSError **error);
+
+// Converts a CGColor to a hex string
+RCT_EXTERN NSString *RCTColorToHexString(CGColorRef color);
+
+// Get standard localized string (if it exists)
+RCT_EXTERN NSString *RCTUIKitLocalizedString(NSString *string);
+
+// URL manipulation
+RCT_EXTERN NSString *__nullable RCTGetURLQueryParam(NSURL *__nullable URL, NSString *param);
+RCT_EXTERN NSURL *__nullable RCTURLByReplacingQueryParam(NSURL *__nullable URL, NSString *param, NSString *__nullable value);
+
+NS_ASSUME_NONNULL_END
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index 036ecc2bd38a..c693ea1a702e 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -21,8 +21,14 @@
 
 #import "RCTLog.h"
 
-NSString *RCTJSONStringify(id jsonObject, NSError **error)
+NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
+
+static NSString *__nullable _RCTJSONStringifyNoRetry(id __nullable jsonObject, NSError **error)
 {
+  if (!jsonObject) {
+    return nil;
+  }
+
   static SEL JSONKitSelector = NULL;
   static NSSet<Class> *collectionTypes;
   static dispatch_once_t onceToken;
@@ -36,20 +42,51 @@
     }
   });
 
-  // Use JSONKit if available and object is not a fragment
-  if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
-    return ((NSString *(*)(id, SEL, int, NSError **))objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
+  @try {
+
+    // Use JSONKit if available and object is not a fragment
+    if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
+      return ((NSString *(*)(id, SEL, int, NSError **))objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
+    }
+
+    // Use Foundation JSON method
+    NSData *jsonData = [NSJSONSerialization
+                        dataWithJSONObject:jsonObject options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
+                        error:error];
+
+    return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
   }
+  @catch (NSException *exception) {
 
-  // Use Foundation JSON method
-  NSData *jsonData = [NSJSONSerialization
-                      dataWithJSONObject:jsonObject
-                      options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
-                      error:error];
-  return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
+    // Convert exception to error
+    if (error) {
+      *error = [NSError errorWithDomain:RCTErrorDomain code:0 userInfo:@{
+        NSLocalizedDescriptionKey: exception.description ?: @""
+      }];
+    }
+    return nil;
+  }
 }
 
-static id _RCTJSONParse(NSString *jsonString, BOOL mutable, NSError **error)
+NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error)
+{
+  if (error) {
+    return _RCTJSONStringifyNoRetry(jsonObject, error);
+  } else {
+    NSError *localError;
+    NSString *json = _RCTJSONStringifyNoRetry(jsonObject, &localError);
+    if (localError) {
+      RCTLogError(@"RCTJSONStringify() encountered the following error: %@",
+                  localError.localizedDescription);
+      // Sanitize the data, then retry. This is slow, but it prevents uncaught
+      // data issues from crashing in production
+      return _RCTJSONStringifyNoRetry(RCTJSONClean(jsonObject), NULL);
+    }
+    return json;
+  }
+}
+
+static id __nullable _RCTJSONParse(NSString *__nullable jsonString, BOOL mutable, NSError **error)
 {
   static SEL JSONKitSelector = NULL;
   static SEL JSONKitMutableSelector = NULL;
@@ -108,12 +145,12 @@ static id _RCTJSONParse(NSString *jsonString, BOOL mutable, NSError **error)
   return nil;
 }
 
-id RCTJSONParse(NSString *jsonString, NSError **error)
+id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error)
 {
   return _RCTJSONParse(jsonString, NO, error);
 }
 
-id RCTJSONParseMutable(NSString *jsonString, NSError **error)
+id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error)
 {
   return _RCTJSONParse(jsonString, YES, error);
 }
@@ -132,6 +169,14 @@ id RCTJSONClean(id object)
   });
 
   if ([validLeafTypes containsObject:[object classForCoder]]) {
+    if ([object isKindOfClass:[NSNumber class]]) {
+      return @(RCTZeroIfNaN([object doubleValue]));
+    }
+    if ([object isKindOfClass:[NSString class]]) {
+      if ([object UTF8String] == NULL) {
+        return (id)kCFNull;
+      }
+    }
     return object;
   }
 
@@ -183,18 +228,29 @@ id RCTJSONClean(id object)
   ];
 }
 
+void RCTExecuteOnMainThread(dispatch_block_t block, BOOL sync)
+{
+  if ([NSThread isMainThread]) {
+    block();
+  } else if (sync) {
+    dispatch_sync(dispatch_get_main_queue(), ^{
+      block();
+    });
+  } else {
+    dispatch_async(dispatch_get_main_queue(), ^{
+      block();
+    });
+  }
+}
+
 CGFloat RCTScreenScale()
 {
   static CGFloat scale;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
-    if (![NSThread isMainThread]) {
-      dispatch_sync(dispatch_get_main_queue(), ^{
-        scale = [UIScreen mainScreen].scale;
-      });
-    } else {
+    RCTExecuteOnMainThread(^{
       scale = [UIScreen mainScreen].scale;
-    }
+    }, YES);
   });
 
   return scale;
@@ -205,13 +261,9 @@ CGSize RCTScreenSize()
   static CGSize size;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
-    if (![NSThread isMainThread]) {
-      dispatch_sync(dispatch_get_main_queue(), ^{
-        size = [UIScreen mainScreen].bounds.size;
-      });
-    } else {
+    RCTExecuteOnMainThread(^{
       size = [UIScreen mainScreen].bounds.size;
-    }
+    }, YES);
   });
 
   return size;
@@ -235,6 +287,14 @@ CGFloat RCTFloorPixelValue(CGFloat value)
   return floor(value * scale) / scale;
 }
 
+CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
+{
+  return (CGSize){
+    ceil(pointSize.width * scale),
+    ceil(pointSize.height * scale),
+  };
+}
+
 void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
 {
   Method originalMethod = class_getClassMethod(cls, original);
@@ -288,41 +348,55 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
   return NO;
 }
 
-NSDictionary<NSString *, id> *RCTMakeError(NSString *message, id toStringify, NSDictionary<NSString *, id> *extraData)
+NSDictionary<NSString *, id> *RCTMakeError(NSString *message,
+                                           id __nullable toStringify,
+                                           NSDictionary<NSString *, id> *__nullable extraData)
 {
   if (toStringify) {
     message = [message stringByAppendingString:[toStringify description]];
   }
 
-  NSMutableDictionary<NSString *, id> *error = [NSMutableDictionary dictionaryWithDictionary:extraData];
+  NSMutableDictionary<NSString *, id> *error = [extraData mutableCopy] ?: [NSMutableDictionary new];
   error[@"message"] = message;
   return error;
 }
 
-NSDictionary<NSString *, id> *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary<NSString *, id> *extraData)
+NSDictionary<NSString *, id> *RCTMakeAndLogError(NSString *message,
+                                                 id __nullable toStringify,
+                                                 NSDictionary<NSString *, id> *__nullable extraData)
 {
   NSDictionary<NSString *, id> *error = RCTMakeError(message, toStringify, extraData);
   RCTLogError(@"\nError: %@", error);
   return error;
 }
 
-// TODO: Can we just replace RCTMakeError with this function instead?
 NSDictionary<NSString *, id> *RCTJSErrorFromNSError(NSError *error)
+{
+  return RCTJSErrorFromCodeMessageAndNSError(RCTErrorUnspecified,
+                                             error.localizedDescription,
+                                             error);
+}
+
+// TODO: Can we just replace RCTMakeError with this function instead?
+NSDictionary<NSString *, id> *RCTJSErrorFromCodeMessageAndNSError(NSString *code,
+                                                                  NSString *message,
+                                                                  NSError *__nullable error)
 {
   NSString *errorMessage;
   NSArray<NSString *> *stackTrace = [NSThread callStackSymbols];
   NSMutableDictionary<NSString *, id> *errorInfo =
-    [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"];
+  [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"];
 
   if (error) {
     errorMessage = error.localizedDescription ?: @"Unknown error from a native module";
     errorInfo[@"domain"] = error.domain ?: RCTErrorDomain;
-    errorInfo[@"code"] = @(error.code);
   } else {
     errorMessage = @"Unknown error from a native module";
     errorInfo[@"domain"] = RCTErrorDomain;
-    errorInfo[@"code"] = @-1;
   }
+  errorInfo[@"code"] = code ?: RCTErrorUnspecified;
+  // Allow for explicit overriding of the error message
+  errorMessage = message ?: errorMessage;
 
   return RCTMakeError(errorMessage, nil, errorInfo);
 }
@@ -342,7 +416,7 @@ BOOL RCTRunningInAppExtension(void)
   return [[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"];
 }
 
-UIApplication *RCTSharedApplication(void)
+UIApplication *__nullable RCTSharedApplication(void)
 {
   if (RCTRunningInAppExtension()) {
     return nil;
@@ -350,7 +424,7 @@ BOOL RCTRunningInAppExtension(void)
   return [[UIApplication class] performSelector:@selector(sharedApplication)];
 }
 
-UIWindow *RCTKeyWindow(void)
+UIWindow *__nullable RCTKeyWindow(void)
 {
   if (RCTRunningInAppExtension()) {
     return nil;
@@ -360,11 +434,24 @@ BOOL RCTRunningInAppExtension(void)
   return RCTSharedApplication().keyWindow;
 }
 
-UIAlertView *RCTAlertView(NSString *title,
-                          NSString *message,
-                          id delegate,
-                          NSString *cancelButtonTitle,
-                          NSArray<NSString *> *otherButtonTitles)
+BOOL RCTForceTouchAvailable(void)
+{
+  static BOOL forceSupported;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    forceSupported = [UITraitCollection class] &&
+    [UITraitCollection instancesRespondToSelector:@selector(forceTouchCapability)];
+  });
+
+  return forceSupported &&
+    (RCTKeyWindow() ?: [UIView new]).traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable;
+}
+
+UIAlertView *__nullable RCTAlertView(NSString *title,
+                                     NSString *__nullable message,
+                                     id __nullable delegate,
+                                     NSString *__nullable cancelButtonTitle,
+                                     NSArray<NSString *> *__nullable otherButtonTitles)
 {
   if (RCTRunningInAppExtension()) {
     RCTLogError(@"RCTAlertView is unavailable when running in an app extension");
@@ -385,34 +472,27 @@ BOOL RCTRunningInAppExtension(void)
   return alertView;
 }
 
-BOOL RCTImageHasAlpha(CGImageRef image)
-{
-  switch (CGImageGetAlphaInfo(image)) {
-    case kCGImageAlphaNone:
-    case kCGImageAlphaNoneSkipLast:
-    case kCGImageAlphaNoneSkipFirst:
-      return NO;
-    default:
-      return YES;
-  }
-}
-
 NSError *RCTErrorWithMessage(NSString *message)
 {
   NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey: message};
   return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
 }
 
-id RCTNullIfNil(id value)
+id RCTNullIfNil(id __nullable value)
 {
   return value ?: (id)kCFNull;
 }
 
-id RCTNilIfNull(id value)
+id __nullable RCTNilIfNull(id __nullable value)
 {
   return value == (id)kCFNull ? nil : value;
 }
 
+double RCTZeroIfNaN(double value)
+{
+  return isnan(value) || isinf(value) ? 0 : value;
+}
+
 NSURL *RCTDataURL(NSString *mimeType, NSData *data)
 {
   return [NSURL URLWithString:
@@ -420,14 +500,14 @@ id RCTNilIfNull(id value)
            [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
 }
 
-BOOL RCTIsGzippedData(NSData *); // exposed for unit testing purposes
-BOOL RCTIsGzippedData(NSData *data)
+BOOL RCTIsGzippedData(NSData *__nullable); // exposed for unit testing purposes
+BOOL RCTIsGzippedData(NSData *__nullable data)
 {
   UInt8 *bytes = (UInt8 *)data.bytes;
   return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
 }
 
-NSData *RCTGzipData(NSData *input, float level)
+NSData *__nullable RCTGzipData(NSData *__nullable input, float level)
 {
   if (input.length == 0 || RCTIsGzippedData(input)) {
     return input;
@@ -470,7 +550,7 @@ BOOL RCTIsGzippedData(NSData *data)
   return output;
 }
 
-NSString *RCTBundlePathForURL(NSURL *URL)
+NSString *__nullable  RCTBundlePathForURL(NSURL *__nullable URL)
 {
   if (!URL.fileURL) {
     // Not a file path
@@ -482,10 +562,14 @@ BOOL RCTIsGzippedData(NSData *data)
     // Not a bundle-relative file
     return nil;
   }
-  return [path substringFromIndex:bundlePath.length + 1];
+  path = [path substringFromIndex:bundlePath.length];
+  if ([path hasPrefix:@"/"]) {
+    path = [path substringFromIndex:1];
+  }
+  return path;
 }
 
-BOOL RCTIsXCAssetURL(NSURL *imageURL)
+BOOL RCTIsXCAssetURL(NSURL *__nullable imageURL)
 {
   NSString *name = RCTBundlePathForURL(imageURL);
   if (name.pathComponents.count != 1) {
@@ -504,3 +588,188 @@ BOOL RCTIsXCAssetURL(NSURL *imageURL)
   }
   return YES;
 }
+
+RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *extension, NSError **error)
+{
+  static NSError *setupError = nil;
+  static NSString *directory;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
+    // If the temporary directory already exists, we'll delete it to ensure
+    // that temp files from the previous run have all been deleted. This is not
+    // a security measure, it simply prevents the temp directory from using too
+    // much space, as the circumstances under which iOS clears it automatically
+    // are not well-defined.
+    NSFileManager *fileManager = [NSFileManager new];
+    if ([fileManager fileExistsAtPath:directory]) {
+      [fileManager removeItemAtPath:directory error:NULL];
+    }
+    if (![fileManager fileExistsAtPath:directory]) {
+      NSError *localError = nil;
+      if (![fileManager createDirectoryAtPath:directory
+                  withIntermediateDirectories:YES
+                                   attributes:nil
+                                        error:&localError]) {
+        // This is bad
+        RCTLogError(@"Failed to create temporary directory: %@", localError);
+        setupError = localError;
+        directory = nil;
+      }
+    }
+  });
+
+  if (!directory || setupError) {
+    if (error) {
+      *error = setupError;
+    }
+    return nil;
+  }
+
+  // Append a unique filename
+  NSString *filename = [NSUUID new].UUIDString;
+  if (extension) {
+    filename = [filename stringByAppendingPathExtension:extension];
+  }
+  return [directory stringByAppendingPathComponent:filename];
+}
+
+static void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
+{
+  CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
+  const CGFloat *components = CGColorGetComponents(color);
+  switch (model)
+  {
+    case kCGColorSpaceModelMonochrome:
+    {
+      rgba[0] = components[0];
+      rgba[1] = components[0];
+      rgba[2] = components[0];
+      rgba[3] = components[1];
+      break;
+    }
+    case kCGColorSpaceModelRGB:
+    {
+      rgba[0] = components[0];
+      rgba[1] = components[1];
+      rgba[2] = components[2];
+      rgba[3] = components[3];
+      break;
+    }
+    case kCGColorSpaceModelCMYK:
+    case kCGColorSpaceModelDeviceN:
+    case kCGColorSpaceModelIndexed:
+    case kCGColorSpaceModelLab:
+    case kCGColorSpaceModelPattern:
+    case kCGColorSpaceModelUnknown:
+    {
+
+#ifdef RCT_DEBUG
+      //unsupported format
+      RCTLogError(@"Unsupported color model: %i", model);
+#endif
+
+      rgba[0] = 0.0;
+      rgba[1] = 0.0;
+      rgba[2] = 0.0;
+      rgba[3] = 1.0;
+      break;
+    }
+  }
+}
+
+NSString *RCTColorToHexString(CGColorRef color)
+{
+  CGFloat rgba[4];
+  RCTGetRGBAColorComponents(color, rgba);
+  uint8_t r = rgba[0]*255;
+  uint8_t g = rgba[1]*255;
+  uint8_t b = rgba[2]*255;
+  uint8_t a = rgba[3]*255;
+  if (a < 255) {
+    return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a];
+  } else {
+    return [NSString stringWithFormat:@"#%02x%02x%02x", r, g, b];
+  }
+}
+
+// (https://github.com/0xced/XCDFormInputAccessoryView/blob/master/XCDFormInputAccessoryView/XCDFormInputAccessoryView.m#L10-L14)
+NSString *RCTUIKitLocalizedString(NSString *string)
+{
+  NSBundle *UIKitBundle = [NSBundle bundleForClass:[UIApplication class]];
+  return UIKitBundle ? [UIKitBundle localizedStringForKey:string value:string table:nil] : string;
+}
+
+NSString *__nullable RCTGetURLQueryParam(NSURL *__nullable URL, NSString *param)
+{
+  RCTAssertParam(param);
+  if (!URL) {
+    return nil;
+  }
+  NSURLComponents *components = [NSURLComponents componentsWithURL:URL
+                                           resolvingAgainstBaseURL:YES];
+
+  // TODO: use NSURLComponents.queryItems once we drop support for iOS 7
+  for (NSString *item in [components.percentEncodedQuery componentsSeparatedByString:@"&"].reverseObjectEnumerator) {
+    NSArray *keyValue = [item componentsSeparatedByString:@"="];
+    NSString *key = [keyValue.firstObject stringByRemovingPercentEncoding];
+    if ([key isEqualToString:param]) {
+      return [keyValue.lastObject stringByRemovingPercentEncoding];
+    }
+  }
+  return nil;
+}
+
+NSURL *__nullable RCTURLByReplacingQueryParam(NSURL *__nullable URL, NSString *param, NSString *__nullable value)
+{
+  RCTAssertParam(param);
+  if (!URL) {
+    return nil;
+  }
+  NSURLComponents *components = [NSURLComponents componentsWithURL:URL
+                                           resolvingAgainstBaseURL:YES];
+
+  // TODO: use NSURLComponents.queryItems once we drop support for iOS 7
+
+  // Unhelpfully, iOS doesn't provide this set as a constant
+  static NSCharacterSet *URLParamCharacterSet;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    NSMutableCharacterSet *characterSet = [NSMutableCharacterSet new];
+    [characterSet formUnionWithCharacterSet:[NSCharacterSet URLQueryAllowedCharacterSet]];
+    [characterSet removeCharactersInString:@"&=?"];
+    URLParamCharacterSet = [characterSet copy];
+  });
+
+  NSString *encodedParam =
+  [param stringByAddingPercentEncodingWithAllowedCharacters:URLParamCharacterSet];
+
+  __block NSInteger paramIndex = NSNotFound;
+  NSMutableArray *queryItems = [[components.percentEncodedQuery componentsSeparatedByString:@"&"] mutableCopy];
+  [queryItems enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:
+   ^(NSString *item, NSUInteger i, BOOL *stop) {
+     NSArray *keyValue = [item componentsSeparatedByString:@"="];
+     if ([keyValue.firstObject isEqualToString:encodedParam]) {
+       paramIndex = i;
+       *stop = YES;
+     }
+   }];
+
+  if (!value) {
+    if (paramIndex != NSNotFound) {
+      [queryItems removeObjectAtIndex:paramIndex];
+    }
+  } else {
+    NSString *encodedValue =
+    [value stringByAddingPercentEncodingWithAllowedCharacters:URLParamCharacterSet];
+
+    NSString *newItem = [encodedParam stringByAppendingFormat:@"=%@", encodedValue];
+    if (paramIndex == NSNotFound) {
+      [queryItems addObject:newItem];
+    } else {
+      [queryItems replaceObjectAtIndex:paramIndex withObject:newItem];
+    }
+  }
+  components.percentEncodedQuery = [queryItems componentsJoinedByString:@"&"];
+  return components.URL;
+}
diff --git a/React/Base/RCTWebSocketProxy.h b/React/Base/RCTWebSocketProxy.h
new file mode 100644
index 000000000000..e823c3c8fe68
--- /dev/null
+++ b/React/Base/RCTWebSocketProxy.h
@@ -0,0 +1,26 @@
+/**
+ * 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.
+ */
+
+#import "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+#import "RCTWebSocketProxyDelegate.h"
+
+@protocol RCTWebSocketProxy
+
++ (instancetype)sharedInstance;
+
+- (void)setDelegate:(id<RCTWebSocketProxyDelegate>)delegate forURL:(NSURL *)url;
+
+- (instancetype) init   __attribute__((unavailable("init not available, call sharedInstance instead")));
+
+@end
+
+#endif
diff --git a/React/Base/RCTWebSocketProxyDelegate.h b/React/Base/RCTWebSocketProxyDelegate.h
new file mode 100644
index 000000000000..f668bf1610a3
--- /dev/null
+++ b/React/Base/RCTWebSocketProxyDelegate.h
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+#import "RCTDefines.h"
+
+#if RCT_DEV // Only supported in dev mode
+
+@protocol RCTWebSocketProxy;
+
+@protocol RCTWebSocketProxyDelegate
+- (void)socketProxy:(id<RCTWebSocketProxy>)sender didReceiveMessage:(NSDictionary<NSString *, id> *)message;
+@end
+
+#endif
diff --git a/React/Executors/RCTContextExecutor.h b/React/Executors/RCTJSCExecutor.h
similarity index 63%
rename from React/Executors/RCTContextExecutor.h
rename to React/Executors/RCTJSCExecutor.h
index 3ad12683c20f..f8beaa67373f 100644
--- a/React/Executors/RCTContextExecutor.h
+++ b/React/Executors/RCTJSCExecutor.h
@@ -11,12 +11,24 @@
 
 #import "RCTJavaScriptExecutor.h"
 
-// TODO (#5906496): Might RCTJSCoreExecutor be a better name for this?
+/**
+ * Default name for the JS thread
+ */
+RCT_EXTERN NSString *const RCTJSCThreadName;
+
+/**
+ * This notification fires on the JS thread immediately after a `JSContext`
+ * is fully initialized, but before the JS bundle has been loaded. The object
+ * of this notification is the `JSContext`. Native modules should listen for
+ * notification only if they need to install custom functionality into the
+ * context. Note that this notification won't fire when debugging in Chrome.
+ */
+RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification;
 
 /**
  * Uses a JavaScriptCore context as the execution engine.
  */
-@interface RCTContextExecutor : NSObject <RCTJavaScriptExecutor>
+@interface RCTJSCExecutor : NSObject <RCTJavaScriptExecutor>
 
 /**
  * Configures the executor to run JavaScript on a specific thread with a given JS context.
diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTJSCExecutor.m
similarity index 51%
rename from React/Executors/RCTContextExecutor.m
rename to React/Executors/RCTJSCExecutor.m
index 163718799f87..91c915a90cb4 100644
--- a/React/Executors/RCTContextExecutor.m
+++ b/React/Executors/RCTJSCExecutor.m
@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTContextExecutor.h"
+#import "RCTJSCExecutor.h"
 
 #import <pthread.h>
 
@@ -15,31 +15,23 @@
 #import <UIKit/UIDevice.h>
 
 #import "RCTAssert.h"
+#import "RCTBridge+Private.h"
 #import "RCTDefines.h"
 #import "RCTDevMenu.h"
 #import "RCTLog.h"
 #import "RCTProfile.h"
 #import "RCTPerformanceLogger.h"
 #import "RCTUtils.h"
+#import "RCTJSCProfiler.h"
+#import "RCTRedBox.h"
+#import "RCTSourceCode.h"
 
-#ifndef RCT_JSC_PROFILER
-#if RCT_DEV
-#define RCT_JSC_PROFILER 1
-#else
-#define RCT_JSC_PROFILER 0
-#endif
-#endif
+NSString *const RCTJSCThreadName = @"com.facebook.React.JavaScript";
 
-#if RCT_JSC_PROFILER
-#include <dlfcn.h>
+NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
 
 static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
 
-#ifndef RCT_JSC_PROFILER_DYLIB
-#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
-#endif
-#endif
-
 @interface RCTJavaScriptContext : NSObject <RCTInvalidating>
 
 @property (nonatomic, strong, readonly) JSContext *context;
@@ -51,14 +43,22 @@ - (instancetype)initWithJSContext:(JSContext *)context NS_DESIGNATED_INITIALIZER
 
 @implementation RCTJavaScriptContext
 {
-  RCTJavaScriptContext *_self;
+  RCTJavaScriptContext *_selfReference;
 }
 
 - (instancetype)initWithJSContext:(JSContext *)context
 {
   if ((self = [super init])) {
     _context = context;
-    _self = self;
+
+    /**
+     * Explicitly introduce a retain cycle here - The RCTJSCExecutor might
+     * be deallocated while there's still work enqueued in the JS thread, so
+     * we wouldn't be able kill the JSContext. Instead we create this retain
+     * cycle, and enqueue the -invalidate message in this object, it then
+     * releases the JSContext, breaks the cycle and stops the runloop.
+     */
+    _selfReference = self;
   }
   return self;
 }
@@ -79,7 +79,7 @@ - (void)invalidate
 {
   if (self.isValid) {
     _context = nil;
-    _self = nil;
+    _selfReference = nil;
   }
 }
 
@@ -90,17 +90,11 @@ - (void)dealloc
 
 @end
 
-// Private bridge interface to allow middle-batch calls
-@interface RCTBridge (RCTContextExecutor)
-
-- (void)handleBuffer:(NSArray<NSArray *> *)buffer batchEnded:(BOOL)hasEnded;
-
-@end
-
-@implementation RCTContextExecutor
+@implementation RCTJSCExecutor
 {
   RCTJavaScriptContext *_context;
   NSThread *_javaScriptThread;
+  NSURL *_bundleURL;
 }
 
 @synthesize valid = _valid;
@@ -108,38 +102,6 @@ @implementation RCTContextExecutor
 
 RCT_EXPORT_MODULE()
 
-/**
- * The one tiny pure native hook that we implement is a native logging hook.
- * You could even argue that this is not necessary - we could plumb logging
- * calls through a batched bridge, but having the pure native hook allows
- * logging to successfully come through even in the event that a batched bridge
- * crashes.
- */
-
-static JSValueRef RCTNativeLoggingHook(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
-{
-  if (argumentCount > 0) {
-    NSString *message = RCTJSValueToNSString(context, arguments[0], exception);
-
-    RCTLogLevel level = RCTLogLevelInfo;
-    if (argumentCount > 1) {
-      level = MAX(level, JSValueToNumber(context, arguments[1], exception));
-    }
-
-    _RCTLogJavaScriptInternal(level, message);
-  }
-
-  return JSValueMakeUndefined(context);
-}
-
-// Do-very-little native hook for testing.
-static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception)
-{
-  static int counter = 0;
-  counter++;
-  return JSValueMakeUndefined(context);
-}
-
 static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value, JSValueRef *exception)
 {
   JSStringRef JSString = JSValueToStringCopy(context, value, exception);
@@ -164,97 +126,28 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u
 
 static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
 {
-  NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"unknown JS error";
-  NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"no details";
+  NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"Unknown JS error";
+  NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"No details";
   return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}];
 }
 
 #if RCT_DEV
 
-static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
-{
-  static int profileCounter = 1;
-  NSString *profileName;
-  double tag = 0;
-
-  if (argumentCount > 0) {
-    if (JSValueIsNumber(context, arguments[0])) {
-      tag = JSValueToNumber(context, arguments[0], NULL);
-    } else {
-      profileName = RCTJSValueToNSString(context, arguments[0], exception);
-    }
-  } else {
-    profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
-  }
-
-  if (argumentCount > 1 && JSValueIsString(context, arguments[1])) {
-    profileName = RCTJSValueToNSString(context, arguments[1], exception);
-  }
-
-  if (profileName) {
-    RCT_PROFILE_BEGIN_EVENT(tag, profileName, nil);
-  }
-
-  return JSValueMakeUndefined(context);
-}
-
-static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], JSValueRef *exception)
-{
-  if (argumentCount > 0) {
-    double tag = JSValueToNumber(context, arguments[0], exception);
-    RCT_PROFILE_END_EVENT((uint64_t)tag, @"console", nil);
-  }
-
-  return JSValueMakeUndefined(context);
-}
-
 static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
 {
-#if RCT_JSC_PROFILER
-  void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
-  if (JSCProfiler != NULL) {
-    void (*nativeProfilerStart)(JSContextRef, const char *) =
-      (__typeof__(nativeProfilerStart))dlsym(JSCProfiler, "nativeProfilerStart");
-    void (*nativeProfilerEnd)(JSContextRef, const char *, const char *) =
-      (__typeof__(nativeProfilerEnd))dlsym(JSCProfiler, "nativeProfilerEnd");
-
-    if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
-      void (*nativeProfilerEnableBytecode)(void) =
-        (__typeof__(nativeProfilerEnableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode");
-
-      if (nativeProfilerEnableBytecode != NULL) {
-        nativeProfilerEnableBytecode();
-      }
-
-      static BOOL isProfiling = NO;
-
-      if (isProfiling) {
-        nativeProfilerStart(context, "profile");
-      }
-
-      [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) {
-
-        if (shouldStart == isProfiling) {
-          return;
-        }
-
-        isProfiling = shouldStart;
-
+  if (RCTJSCProfilerIsSupported()) {
+    [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) {
+      if (shouldStart != RCTJSCProfilerIsProfiling(context)) {
         if (shouldStart) {
-          nativeProfilerStart(context, "profile");
+          RCTJSCProfilerStart(context);
         } else {
-          NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"];
-          nativeProfilerEnd(context, "profile", outputFile.UTF8String);
-          NSData *profileData = [NSData dataWithContentsOfFile:outputFile
-                                                       options:NSDataReadingMappedIfSafe
-                                                         error:NULL];
-
+          NSString *outputFile = RCTJSCProfilerStop(context);
+          NSData *profileData = [NSData dataWithContentsOfFile:outputFile options:NSDataReadingMappedIfSafe error:NULL];
           RCTProfileSendResult(bridge, @"cpu-profile", profileData);
         }
-      }]];
-    }
+      }
+    }]];
   }
-#endif
 }
 
 #endif
@@ -283,7 +176,7 @@ - (instancetype)init
   NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
                                                        selector:@selector(runRunLoopThread)
                                                          object:nil];
-  javaScriptThread.name = @"com.facebook.React.JavaScript";
+  javaScriptThread.name = RCTJSCThreadName;
 
   if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
     [javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
@@ -300,14 +193,14 @@ - (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
                                  context:(JSContext *)context
 {
   RCTAssert(javaScriptThread != nil,
-            @"Can't initialize RCTContextExecutor without a javaScriptThread");
+            @"Can't initialize RCTJSCExecutor without a javaScriptThread");
 
   if ((self = [super init])) {
     _valid = YES;
     _javaScriptThread = javaScriptThread;
-    __weak RCTContextExecutor *weakSelf = self;
+    __weak RCTJSCExecutor *weakSelf = self;
     [self executeBlockOnJavaScriptQueue: ^{
-      RCTContextExecutor *strongSelf = weakSelf;
+      RCTJSCExecutor *strongSelf = weakSelf;
       if (!strongSelf) {
         return;
       }
@@ -328,75 +221,162 @@ - (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
   return [self initWithJavaScriptThread:javaScriptThread context:context];
 }
 
-- (void)setUp
+- (RCTJavaScriptContext *)context
+{
+  RCTAssertThread(_javaScriptThread, @"Must be called on JS thread.");
+
+  if (!self.isValid) {
+    return nil;
+  }
+
+  if (!_context) {
+    JSContext *context = [JSContext new];
+    _context = [[RCTJavaScriptContext alloc] initWithJSContext:context];
+  }
+
+  return _context;
+}
+
+- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block
 {
-  __weak RCTContextExecutor *weakSelf = self;
+  __weak RCTJSCExecutor *weakSelf = self;
   [self executeBlockOnJavaScriptQueue:^{
-    RCTContextExecutor *strongSelf = weakSelf;
-    if (!strongSelf.isValid) {
-      return;
+    weakSelf.context.context[name] = block;
+  }];
+}
+
+- (void)setUp
+{
+  __weak RCTJSCExecutor *weakSelf = self;
+  [self addSynchronousHookWithName:@"noop" usingBlock:^{}];
+
+  [self addSynchronousHookWithName:@"nativeLoggingHook" usingBlock:^(NSString *message, NSNumber *logLevel) {
+    RCTLogLevel level = RCTLogLevelInfo;
+    if (logLevel) {
+      level = MAX(level, logLevel.integerValue);
     }
-    if (!strongSelf->_context) {
-      JSContext *context = [JSContext new];
-      strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context];
+
+    _RCTLogJavaScriptInternal(level, message);
+  }];
+
+  [self addSynchronousHookWithName:@"nativeRequireModuleConfig" usingBlock:^NSString *(NSString *moduleName) {
+    RCTJSCExecutor *strongSelf = weakSelf;
+    if (!strongSelf.valid) {
+      return nil;
     }
-    [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
-    [strongSelf _addNativeHook:RCTNoop withName:"noop"];
 
-    __weak RCTBridge *bridge = strongSelf->_bridge;
-    strongSelf->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
-      if (!weakSelf.valid || !calls) {
-        return;
-      }
+    RCT_PROFILE_BEGIN_EVENT(0, @"nativeRequireModuleConfig", nil);
+    NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
+    NSString *result = config ? RCTJSONStringify(config, NULL) : nil;
+    RCT_PROFILE_END_EVENT(0, @"js_call,config", @{ @"moduleName": moduleName });
+    return result;
+  }];
 
-      [bridge handleBuffer:calls batchEnded:NO];
-    };
+  [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
+    RCTJSCExecutor *strongSelf = weakSelf;
+    if (!strongSelf.valid || !calls) {
+      return;
+    }
 
-    strongSelf->_context.context[@"RCTPerformanceNow"] = ^(){
-      return CACurrentMediaTime() * 1000 * 1000;
-    };
+    RCT_PROFILE_BEGIN_EVENT(0, @"nativeFlushQueueImmediate", nil);
+    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
+    RCT_PROFILE_END_EVENT(0, @"js_call", nil);
+  }];
+
+  [self addSynchronousHookWithName:@"nativePerformanceNow" usingBlock:^{
+    return @(CACurrentMediaTime() * 1000);
+  }];
 
 #if RCT_DEV
-    if (RCTProfileIsProfiling()) {
-      strongSelf->_context.context[@"__RCTProfileIsProfiling"] = @YES;
-    }
+  if (RCTProfileIsProfiling()) {
+    // Cheating, since it's not a "hook", but meh
+    [self addSynchronousHookWithName:@"__RCTProfileIsProfiling" usingBlock:@YES];
+  }
 
-    [strongSelf _addNativeHook:RCTNativeTraceBeginSection withName:"nativeTraceBeginSection"];
-    [strongSelf _addNativeHook:RCTNativeTraceEndSection withName:"nativeTraceEndSection"];
+  CFMutableDictionaryRef cookieMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
+  [self addSynchronousHookWithName:@"nativeTraceBeginAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) {
+    NSUInteger newCookie = RCTProfileBeginAsyncEvent(tag, name, nil);
+    CFDictionarySetValue(cookieMap, (const void *)cookie, (const void *)newCookie);
+  }];
 
-    RCTInstallJSCProfiler(_bridge, strongSelf->_context.ctx);
+  [self addSynchronousHookWithName:@"nativeTraceEndAsyncSection" usingBlock:^(uint64_t tag, NSString *name, NSUInteger cookie) {
+    NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(cookieMap, (const void *)cookie);
+    RCTProfileEndAsyncEvent(tag, @"js,async", newCookie, name, @"JS async", nil);
+    CFDictionaryRemoveValue(cookieMap, (const void *)cookie);
+  }];
 
-    for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
-      [[NSNotificationCenter defaultCenter] addObserver:strongSelf
-                                               selector:@selector(toggleProfilingFlag:)
-                                                   name:event
-                                                 object:nil];
+  [self addSynchronousHookWithName:@"nativeTraceBeginSection" usingBlock:^(NSNumber *tag, NSString *profileName){
+    static int profileCounter = 1;
+    if (!profileName) {
+      profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
     }
+
+    RCT_PROFILE_BEGIN_EVENT(tag.longLongValue, profileName, nil);
+  }];
+
+  [self addSynchronousHookWithName:@"nativeTraceEndSection" usingBlock:^(NSNumber *tag) {
+    RCT_PROFILE_END_EVENT(tag.longLongValue, @"console", nil);
+  }];
+
+  __weak RCTBridge *weakBridge = _bridge;
+#ifndef __clang_analyzer__
+  weakBridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
 #endif
+  [self addSynchronousHookWithName:@"nativeTraceBeginAsyncFlow" usingBlock:^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
+    int64_t newCookie = [_RCTProfileBeginFlowEvent() longLongValue];
+    CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie);
+  }];
+
+  [self addSynchronousHookWithName:@"nativeTraceEndAsyncFlow" usingBlock:^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
+    int64_t newCookie = (int64_t)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
+    _RCTProfileEndFlowEvent(@(newCookie));
+    CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
+  }];
+
+  [self executeBlockOnJavaScriptQueue:^{
+    RCTJSCExecutor *strongSelf = weakSelf;
+    if (!strongSelf.valid) {
+      return;
+    }
+
+    JSContext *context = strongSelf.context.context;
+    RCTInstallJSCProfiler(_bridge, context.JSGlobalContextRef);
+
+    [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
+                                                        object:context];
+  }];
+
+  for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
+    [[NSNotificationCenter defaultCenter] addObserver:self
+                                             selector:@selector(toggleProfilingFlag:)
+                                                 name:event
+                                               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
 }
 
 - (void)toggleProfilingFlag:(NSNotification *)notification
 {
   [self executeBlockOnJavaScriptQueue:^{
     BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling];
-    NSString *script = [NSString stringWithFormat:@"var p = require('BridgeProfiling') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"];
-    JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String);
-    JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL);
-    JSStringRelease(scriptJSRef);
+    [_bridge enqueueJSCall:@"Systrace.setEnabled" args:@[enabled ? @YES : @NO]];
   }];
 }
 
-- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
-{
-  JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);
-
-  JSStringRef JSName = JSStringCreateWithUTF8CString(name);
-  JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL);
-  JSStringRelease(JSName);
-
-}
-
 - (void)invalidate
 {
   if (!self.isValid) {
@@ -421,15 +401,37 @@ - (void)dealloc
   [self invalidate];
 }
 
-- (void)executeJSCall:(NSString *)name
-               method:(NSString *)method
-            arguments:(NSArray *)arguments
-             callback:(RCTJavaScriptCallback)onComplete
+- (void)flushedQueue:(RCTJavaScriptCallback)onComplete
+{
+  // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
+  [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete];
+}
+
+- (void)callFunctionOnModule:(NSString *)module
+                      method:(NSString *)method
+                   arguments:(NSArray *)args
+                    callback:(RCTJavaScriptCallback)onComplete
+{
+  // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
+  [self _executeJSCall:@"callFunctionReturnFlushedQueue" arguments:@[module, method, args] callback:onComplete];
+}
+
+- (void)invokeCallbackID:(NSNumber *)cbID
+               arguments:(NSArray *)args
+                callback:(RCTJavaScriptCallback)onComplete
+{
+  // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
+  [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete];
+}
+
+- (void)_executeJSCall:(NSString *)method
+             arguments:(NSArray *)arguments
+              callback:(RCTJavaScriptCallback)onComplete
 {
   RCTAssert(onComplete != nil, @"onComplete block should not be nil");
-  __weak RCTContextExecutor *weakSelf = self;
+  __weak RCTJSCExecutor *weakSelf = self;
   [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
-    RCTContextExecutor *strongSelf = weakSelf;
+    RCTJSCExecutor *strongSelf = weakSelf;
     if (!strongSelf || !strongSelf.isValid) {
       return;
     }
@@ -446,71 +448,71 @@ - (void)executeJSCall:(NSString *)name
     JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
     JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);
 
-    // get require
-    JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require");
-    JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef);
-    JSStringRelease(requireNameJSStringRef);
-
-    if (requireJSRef != NULL && !JSValueIsUndefined(contextJSRef, requireJSRef) && errorJSRef == NULL) {
-
-      // get module
-      JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name);
-      JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef);
-      JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef);
-      JSStringRelease(moduleNameJSStringRef);
-
-      if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) {
-
-        // get method
-        JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
-        JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
-        JSStringRelease(methodNameJSStringRef);
+    // get the BatchedBridge object
+    JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge");
+    JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
+    JSStringRelease(moduleNameJSStringRef);
+
+    if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) {
+      // get method
+      JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
+      JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
+      JSStringRelease(methodNameJSStringRef);
+
+      if (methodJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, methodJSRef)) {
+        // direct method invoke with no arguments
+        if (arguments.count == 0) {
+          resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
+        }
 
-        if (methodJSRef != NULL && errorJSRef == NULL) {
+        // direct method invoke with 1 argument
+        else if(arguments.count == 1) {
+          JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
+          JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
+          resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef);
+          JSStringRelease(argsJSStringRef);
 
-          // direct method invoke with no arguments
-          if (arguments.count == 0) {
-            resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
-          }
+        } else {
+          // apply invoke with array of arguments
+          JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply");
+          JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef);
+          JSStringRelease(applyNameJSStringRef);
 
-          // direct method invoke with 1 argument
-          else if(arguments.count == 1) {
+          if (applyJSRef != NULL && errorJSRef == NULL) {
+            // invoke apply
             JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
             JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
-            resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef);
-            JSStringRelease(argsJSStringRef);
 
-          } else {
-            // apply invoke with array of arguments
-            JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply");
-            JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef);
-            JSStringRelease(applyNameJSStringRef);
+            JSValueRef args[2];
+            args[0] = JSValueMakeNull(contextJSRef);
+            args[1] = argsJSRef;
 
-            if (applyJSRef != NULL && errorJSRef == NULL) {
-              // invoke apply
-              JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
-              JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
-
-              JSValueRef args[2];
-              args[0] = JSValueMakeNull(contextJSRef);
-              args[1] = argsJSRef;
-
-              resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef);
-              JSStringRelease(argsJSStringRef);
-            }
+            resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef);
+            JSStringRelease(argsJSStringRef);
           }
         }
+      } else {
+        if (!errorJSRef && JSValueIsUndefined(contextJSRef, methodJSRef)) {
+          error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to execute JS call: method %@ is undefined", method]);
+        }
+      }
+    } else {
+      if (!errorJSRef && JSValueIsUndefined(contextJSRef, moduleJSRef)) {
+        error = RCTErrorWithMessage(@"Unable to execute JS call: __fbBatchedBridge is undefined");
       }
     }
 
-    if (errorJSRef) {
-      onComplete(nil, RCTNSErrorFromJSError(contextJSRef, errorJSRef));
+    if (errorJSRef || error) {
+      if (!error) {
+        error = RCTNSErrorFromJSError(contextJSRef, errorJSRef);
+      }
+      onComplete(nil, error);
       return;
     }
 
     // Looks like making lots of JSC API calls is slower than communicating by using a JSON
     // string. Also it ensures that data stuctures don't have cycles and non-serializable fields.
-    // see [RCTContextExecutorTests testDeserializationPerf]
+    // see [RCTJSCExecutorTests testDeserializationPerf]
     id objcValue;
     // We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
     // to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
@@ -525,7 +527,7 @@ - (void)executeJSCall:(NSString *)name
     }
 
     onComplete(objcValue, nil);
-  }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))];
+  }), 0, @"js_call", (@{@"method": method, @"args": arguments}))];
 }
 
 - (void)executeApplicationScript:(NSData *)script
@@ -535,9 +537,10 @@ - (void)executeApplicationScript:(NSData *)script
   RCTAssertParam(script);
   RCTAssertParam(sourceURL);
 
-  __weak RCTContextExecutor *weakSelf = self;
+  __weak RCTJSCExecutor *weakSelf = self;
+
   [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
-    RCTContextExecutor *strongSelf = weakSelf;
+    RCTJSCExecutor *strongSelf = weakSelf;
     if (!strongSelf || !strongSelf.isValid) {
       return;
     }
@@ -599,9 +602,9 @@ - (void)injectJSONText:(NSString *)script
     RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
   }
 
-  __weak RCTContextExecutor *weakSelf = self;
+  __weak RCTJSCExecutor *weakSelf = self;
   [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
-    RCTContextExecutor *strongSelf = weakSelf;
+    RCTJSCExecutor *strongSelf = weakSelf;
     if (!strongSelf || !strongSelf.isValid) {
       return;
     }
diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c
index 0a429529eee8..317f69bb6dfb 100644
--- a/React/Layout/Layout.c
+++ b/React/Layout/Layout.c
@@ -9,6 +9,7 @@
  * !! 3) Copy the file from github to here                       !!
  * !!    (don't forget to keep this header)                      !!
  * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * @generated
  *
  * Copyright (c) 2014, Facebook, Inc.
  * All rights reserved.
@@ -87,6 +88,7 @@ void init_css_node(css_node_t *node) {
   node->layout.last_requested_dimensions[CSS_WIDTH] = -1;
   node->layout.last_requested_dimensions[CSS_HEIGHT] = -1;
   node->layout.last_parent_max_width = -1;
+  node->layout.last_parent_max_height = -1;
   node->layout.last_direction = (css_direction_t)-1;
   node->layout.should_update = true;
 }
@@ -527,7 +529,7 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) {
   return -getPosition(node, trailing[axis]);
 }
 
-static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction_t parentDirection) {
+static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentMaxHeight, css_direction_t parentDirection) {
   /** START_GENERATED **/
   css_direction_t direction = resolveDirection(node, parentDirection);
   css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction);
@@ -556,6 +558,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
   // invocations during the layout calculation.
   int childCount = node->children_count;
   float paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis);
+  float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
 
   if (isMeasureDefined(node)) {
     bool isResolvedRowDimDefined = !isUndefined(node->layout.dimensions[dim[resolvedRowAxis]]);
@@ -571,6 +574,17 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
     }
     width -= paddingAndBorderAxisResolvedRow;
 
+    float height = CSS_UNDEFINED;
+    if (isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {
+      height = node->style.dimensions[CSS_HEIGHT];
+    } else if (!isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]])) {
+      height = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]];
+    } else {
+      height = parentMaxHeight -
+        getMarginAxis(node, resolvedRowAxis);
+    }
+    height -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
+
     // We only need to give a dimension for the text if we haven't got any
     // for it computed yet. It can either be from the style attribute or because
     // the element is flexible.
@@ -582,8 +596,9 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
     if (isRowUndefined || isColumnUndefined) {
       css_dim_t measureDim = node->measure(
         node->context,
-
-        width
+        
+        width,
+        height
       );
       if (isRowUndefined) {
         node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] +
@@ -591,7 +606,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
       }
       if (isColumnUndefined) {
         node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] +
-          getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
+          paddingAndBorderAxisColumn;
       }
     }
     if (childCount == 0) {
@@ -672,6 +687,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
     float crossDim = 0;
 
     float maxWidth;
+    float maxHeight;
     for (i = startLine; i < childCount; ++i) {
       child = node->get_child(node->context, i);
       child->line_index = linesCount;
@@ -752,6 +768,8 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
 
       } else {
         maxWidth = CSS_UNDEFINED;
+        maxHeight = CSS_UNDEFINED;
+
         if (!isMainRowDirection) {
           if (isDimDefined(node, resolvedRowAxis)) {
             maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] -
@@ -761,11 +779,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
               getMarginAxis(node, resolvedRowAxis) -
               paddingAndBorderAxisResolvedRow;
           }
+        } else {
+          if (isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {
+            maxHeight = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
+                paddingAndBorderAxisColumn;
+          } else {
+            maxHeight = parentMaxHeight -
+              getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) -
+              paddingAndBorderAxisColumn;
+          }
         }
 
         // This is the main recursive call. We layout non flexible children.
         if (alreadyComputedNextLayout == 0) {
-          layoutNode(child, maxWidth, direction);
+          layoutNode(child, maxWidth, maxHeight, direction);
         }
 
         // Absolute positioned elements do not take part of the layout, so we
@@ -895,9 +922,18 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
             getMarginAxis(node, resolvedRowAxis) -
             paddingAndBorderAxisResolvedRow;
         }
+        maxHeight = CSS_UNDEFINED;
+        if (isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {
+          maxHeight = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
+            paddingAndBorderAxisColumn;
+        } else if (isMainRowDirection) {
+          maxHeight = parentMaxHeight -
+            getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) -
+            paddingAndBorderAxisColumn;
+        }
 
         // And we recursively call the layout algorithm for this child
-        layoutNode(currentFlexChild, maxWidth, direction);
+        layoutNode(currentFlexChild, maxWidth, maxHeight, direction);
 
         child = currentFlexChild;
         currentFlexChild = currentFlexChild->next_flex_child;
@@ -1000,11 +1036,14 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
         // For a relative children, we're either using alignItems (parent) or
         // alignSelf (child) in order to determine the position in the cross axis
         if (child->style.position_type == CSS_POSITION_RELATIVE) {
+          /*eslint-disable */
+          // This variable is intentionally re-defined as the code is transpiled to a block scope language
           css_align_t alignItem = getAlignItem(node, child);
+          /*eslint-enable */
           if (alignItem == CSS_ALIGN_STRETCH) {
             // You can only stretch if the dimension has not already been set
             // previously.
-            if (!isDimDefined(child, crossAxis)) {
+            if (isUndefined(child->layout.dimensions[dim[crossAxis]])) {
               child->layout.dimensions[dim[crossAxis]] = fmaxf(
                 boundAxis(child, crossAxis, containerCrossAxis -
                   paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),
@@ -1213,7 +1252,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, css_direction
   /** END_GENERATED **/
 }
 
-void layoutNode(css_node_t *node, float parentMaxWidth, css_direction_t parentDirection) {
+void layoutNode(css_node_t *node, float parentMaxWidth, float parentMaxHeight, css_direction_t parentDirection) {
   css_layout_t *layout = &node->layout;
   css_direction_t direction = node->style.direction;
   layout->should_update = true;
@@ -1223,6 +1262,7 @@ void layoutNode(css_node_t *node, float parentMaxWidth, css_direction_t parentDi
     eq(layout->last_requested_dimensions[CSS_WIDTH], layout->dimensions[CSS_WIDTH]) &&
     eq(layout->last_requested_dimensions[CSS_HEIGHT], layout->dimensions[CSS_HEIGHT]) &&
     eq(layout->last_parent_max_width, parentMaxWidth);
+    eq(layout->last_parent_max_height, parentMaxHeight);
     eq(layout->last_direction, direction);
 
   if (skipLayout) {
@@ -1234,9 +1274,10 @@ void layoutNode(css_node_t *node, float parentMaxWidth, css_direction_t parentDi
     layout->last_requested_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH];
     layout->last_requested_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT];
     layout->last_parent_max_width = parentMaxWidth;
+    layout->last_parent_max_height = parentMaxHeight;
     layout->last_direction = direction;
 
-    layoutNodeImpl(node, parentMaxWidth, parentDirection);
+    layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, parentDirection);
 
     layout->last_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH];
     layout->last_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT];
diff --git a/React/Layout/Layout.h b/React/Layout/Layout.h
index f1d2e3833589..be37c9393966 100644
--- a/React/Layout/Layout.h
+++ b/React/Layout/Layout.h
@@ -9,6 +9,7 @@
  * !! 3) Copy the file from github to here                       !!
  * !!    (don't forget to keep this header)                      !!
  * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ * @generated 
  *
  * Copyright (c) 2014, Facebook, Inc.
  * All rights reserved.
@@ -102,6 +103,7 @@ typedef struct {
   bool should_update;
   float last_requested_dimensions[2];
   float last_parent_max_width;
+  float last_parent_max_height;
   float last_dimensions[2];
   float last_position[2];
   css_direction_t last_direction;
@@ -150,7 +152,7 @@ struct css_node {
   css_node_t* next_absolute_child;
   css_node_t* next_flex_child;
 
-  css_dim_t (*measure)(void *context, float width);
+  css_dim_t (*measure)(void *context, float width, float height);
   void (*print)(void *context);
   struct css_node* (*get_child)(void *context, int i);
   bool (*is_dirty)(void *context);
@@ -172,7 +174,7 @@ typedef enum {
 void print_css_node(css_node_t *node, css_print_options_t options);
 
 // Function that computes the layout!
-void layoutNode(css_node_t *node, float maxWidth, css_direction_t parentDirection);
+void layoutNode(css_node_t *node, float maxWidth, float maxHeight, css_direction_t parentDirection);
 bool isUndefined(float value);
 
 #endif
diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m
index eb4d319547b5..fd629cae33c0 100644
--- a/React/Modules/RCTAccessibilityManager.m
+++ b/React/Modules/RCTAccessibilityManager.m
@@ -59,6 +59,8 @@ + (NSString *)UIKitCategoryFromJSCategory:(NSString *)JSCategory
 - (instancetype)init
 {
   if ((self = [super init])) {
+
+    // TODO: can this be moved out of the startup path?
     [[NSNotificationCenter defaultCenter] addObserver:self
                                              selector:@selector(didReceiveNewContentSizeCategory:)
                                                  name:UIContentSizeCategoryDidChangeNotification
@@ -176,7 +178,7 @@ @implementation RCTBridge (RCTAccessibilityManager)
 
 - (RCTAccessibilityManager *)accessibilityManager
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTAccessibilityManager class])];
+  return [self moduleForClass:[RCTAccessibilityManager class]];
 }
 
 @end
diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m
index 3bdb59b40472..b2e7fe0a3ae0 100644
--- a/React/Modules/RCTAlertManager.m
+++ b/React/Modules/RCTAlertManager.m
@@ -14,6 +14,17 @@
 #import "RCTLog.h"
 #import "RCTUtils.h"
 
+@implementation RCTConvert (UIAlertViewStyle)
+
+RCT_ENUM_CONVERTER(UIAlertViewStyle, (@{
+  @"default": @(UIAlertViewStyleDefault),
+  @"secure-text": @(UIAlertViewStyleSecureTextInput),
+  @"plain-text": @(UIAlertViewStylePlainTextInput),
+  @"login-password": @(UIAlertViewStyleLoginAndPasswordInput),
+}), UIAlertViewStyleDefault, integerValue)
+
+@end
+
 @interface RCTAlertManager() <UIAlertViewDelegate>
 
 @end
@@ -28,17 +39,6 @@ @implementation RCTAlertManager
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _alerts = [NSMutableArray new];
-    _alertControllers = [NSMutableArray new];
-    _alertCallbacks = [NSMutableArray new];
-    _alertButtonKeys = [NSMutableArray new];
-  }
-  return self;
-}
-
 - (dispatch_queue_t)methodQueue
 {
   return dispatch_get_main_queue();
@@ -61,63 +61,84 @@ - (void)invalidate
  *     @"message": @"<Alert message>",
  *     @"buttons": @[
  *       @{@"<key1>": @"<title1>"},
- *       @{@"<key2>": @"<cancelButtonTitle>"},
- *     ]
+ *       @{@"<key2>": @"<title2>"},
+ *     ],
+ *     @"cancelButtonKey": @"<key2>",
  *   }
  * The key from the `buttons` dictionary is passed back in the callback on click.
- * Buttons are displayed in the order they are specified. If "cancel" is used as
- * the button key, it will be differently highlighted, according to iOS UI conventions.
+ * Buttons are displayed in the order they are specified.
  */
 RCT_EXPORT_METHOD(alertWithArgs:(NSDictionary *)args
                   callback:(RCTResponseSenderBlock)callback)
 {
   NSString *title = [RCTConvert NSString:args[@"title"]];
   NSString *message = [RCTConvert NSString:args[@"message"]];
-  NSString *type = [RCTConvert NSString:args[@"type"]];
-  NSDictionaryArray *buttons = [RCTConvert NSDictionaryArray:args[@"buttons"]];
-  BOOL allowsTextInput = [type isEqual:@"plain-text"];
+  UIAlertViewStyle type = [RCTConvert UIAlertViewStyle:args[@"type"]];
+  NSArray<NSDictionary *> *buttons = [RCTConvert NSDictionaryArray:args[@"buttons"]];
+  NSString *defaultValue = [RCTConvert NSString:args[@"defaultValue"]];
+  NSString *cancelButtonKey = [RCTConvert NSString:args[@"cancelButtonKey"]];
+  NSString *destructiveButtonKey = [RCTConvert NSString:args[@"destructiveButtonKey"]];
 
   if (!title && !message) {
     RCTLogError(@"Must specify either an alert title, or message, or both");
     return;
-  } else if (buttons.count == 0) {
-    RCTLogError(@"Must have at least one button.");
-    return;
+  }
+
+  if (buttons.count == 0) {
+    if (type == UIAlertViewStyleDefault) {
+      buttons = @[@{@"0": RCTUIKitLocalizedString(@"OK")}];
+      cancelButtonKey = @"0";
+    } else {
+      buttons = @[
+        @{@"0": RCTUIKitLocalizedString(@"OK")},
+        @{@"1": RCTUIKitLocalizedString(@"Cancel")},
+      ];
+      cancelButtonKey = @"1";
+    }
   }
 
 #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0
 
-  // TODO: we've encountered some bug when presenting alerts on top of a window that is subsequently
-  // dismissed. As a temporary solution to this, we'll use UIAlertView preferentially if it's available.
-  BOOL preferAlertView = (!RCTRunningInAppExtension() && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
+  // TODO: we've encountered some bug when presenting alerts on top of a window
+  // that is subsequently dismissed. As a temporary solution to this, we'll use
+  // UIAlertView preferentially if it's available and supports our use case.
+  BOOL preferAlertView = (!RCTRunningInAppExtension() &&
+                          !destructiveButtonKey &&
+                          UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone);
 
   if (preferAlertView || [UIAlertController class] == nil) {
 
     UIAlertView *alertView = RCTAlertView(title, nil, self, nil, nil);
-    NSMutableArray<NSString *> *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count];
+    alertView.alertViewStyle = type;
+    alertView.message = message;
 
-    if (allowsTextInput) {
-      alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
-      [alertView textFieldAtIndex:0].text = message;
-    } else {
-      alertView.message = message;
+    if (type != UIAlertViewStyleDefault) {
+      [alertView textFieldAtIndex:0].text = defaultValue;
     }
 
+    NSMutableArray<NSString *> *buttonKeys =
+      [[NSMutableArray alloc] initWithCapacity:buttons.count];
+
     NSInteger index = 0;
-    for (NSDictionary *button in buttons) {
+    for (NSDictionary<NSString *, id> *button in buttons) {
       if (button.count != 1) {
         RCTLogError(@"Button definitions should have exactly one key.");
       }
       NSString *buttonKey = button.allKeys.firstObject;
-      NSString *buttonTitle = [button[buttonKey] description];
+      NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]];
       [alertView addButtonWithTitle:buttonTitle];
-      if ([buttonKey isEqualToString:@"cancel"]) {
-        alertView.cancelButtonIndex = index;
+      if ([buttonKey isEqualToString:cancelButtonKey]) {
+        alertView.cancelButtonIndex = buttonKeys.count;
       }
       [buttonKeys addObject:buttonKey];
       index ++;
     }
 
+    if (!_alerts) {
+      _alerts = [NSMutableArray new];
+      _alertCallbacks = [NSMutableArray new];
+      _alertButtonKeys = [NSMutableArray new];
+    }
     [_alerts addObject:alertView];
     [_alertCallbacks addObject:callback ?: ^(__unused id unused) {}];
     [_alertButtonKeys addObject:buttonKeys];
@@ -135,46 +156,83 @@ - (void)invalidate
       return;
     }
 
-    // Walk the chain up to get the topmost modal view controller. If modals are presented,
-    // the root view controller's view might not be in the window hierarchy, and presenting from it will fail.
+    // Walk the chain up to get the topmost modal view controller. If modals are
+    // presented the root view controller's view might not be in the window
+    // hierarchy, and presenting from it will fail.
     while (presentingController.presentedViewController) {
       presentingController = presentingController.presentedViewController;
     }
 
     UIAlertController *alertController =
-    [UIAlertController alertControllerWithTitle:title
-                                        message:nil
-                                 preferredStyle:UIAlertControllerStyleAlert];
-
-    if (allowsTextInput) {
-      [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
-        textField.text = message;
-      }];
-    } else {
-      alertController.message = message;
+      [UIAlertController alertControllerWithTitle:title
+                                          message:nil
+                                   preferredStyle:UIAlertControllerStyleAlert];
+    switch (type) {
+      case UIAlertViewStylePlainTextInput:
+        [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+          textField.secureTextEntry = NO;
+        }];
+        break;
+      case UIAlertViewStyleSecureTextInput:
+        [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+          textField.placeholder = RCTUIKitLocalizedString(@"Password");
+          textField.secureTextEntry = YES;
+        }];
+        break;
+      case UIAlertViewStyleLoginAndPasswordInput:
+        [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+          textField.placeholder = RCTUIKitLocalizedString(@"Login");
+        }];
+        [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
+          textField.placeholder = RCTUIKitLocalizedString(@"Password");
+          textField.secureTextEntry = YES;
+        }];
+      case UIAlertViewStyleDefault:
+        break;
     }
 
-    for (NSDictionary *button in buttons) {
+    alertController.message = message;
+
+    for (NSDictionary<NSString *, id> *button in buttons) {
       if (button.count != 1) {
         RCTLogError(@"Button definitions should have exactly one key.");
       }
       NSString *buttonKey = button.allKeys.firstObject;
-      NSString *buttonTitle = [button[buttonKey] description];
-      UIAlertActionStyle buttonStyle = [buttonKey isEqualToString:@"cancel"] ? UIAlertActionStyleCancel : UIAlertActionStyleDefault;
-      UITextField *textField = allowsTextInput ? alertController.textFields.firstObject : nil;
+      NSString *buttonTitle = [RCTConvert NSString:button[buttonKey]];
+      UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault;
+      if ([buttonKey isEqualToString:cancelButtonKey]) {
+        buttonStyle = UIAlertActionStyleCancel;
+      } else if ([buttonKey isEqualToString:destructiveButtonKey]) {
+        buttonStyle = UIAlertActionStyleDestructive;
+      }
       [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle
                                                           style:buttonStyle
                                                         handler:^(__unused UIAlertAction *action) {
-        if (callback) {
-          if (allowsTextInput) {
-            callback(@[buttonKey, textField.text]);
-          } else {
-            callback(@[buttonKey]);
+        switch (type) {
+          case UIAlertViewStylePlainTextInput:
+          case UIAlertViewStyleSecureTextInput:
+            callback(@[buttonKey, [alertController.textFields.firstObject text]]);
+            break;
+          case UIAlertViewStyleLoginAndPasswordInput: {
+            NSDictionary<NSString *, NSString *> *loginCredentials = @{
+              @"login": [alertController.textFields.firstObject text],
+              @"password": [alertController.textFields.lastObject text]
+            };
+            callback(@[buttonKey, loginCredentials]);
+            break;
           }
+          case UIAlertViewStyleDefault:
+            callback(@[buttonKey]);
+            break;
         }
       }]];
     }
 
+    if (!_alertControllers) {
+      _alertControllers = [NSMutableArray new];
+    }
+    [_alertControllers addObject:alertController];
+
     [presentingController presentViewController:alertController animated:YES completion:nil];
   }
 }
@@ -189,10 +247,22 @@ - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)butto
   RCTResponseSenderBlock callback = _alertCallbacks[index];
   NSArray<NSString *> *buttonKeys = _alertButtonKeys[index];
 
-  if (alertView.alertViewStyle == UIAlertViewStylePlainTextInput) {
-    callback(@[buttonKeys[buttonIndex], [alertView textFieldAtIndex:0].text]);
-  } else {
-    callback(@[buttonKeys[buttonIndex]]);
+  switch (alertView.alertViewStyle) {
+    case UIAlertViewStylePlainTextInput:
+    case UIAlertViewStyleSecureTextInput:
+      callback(@[buttonKeys[buttonIndex], [alertView textFieldAtIndex:0].text]);
+      break;
+    case UIAlertViewStyleLoginAndPasswordInput: {
+      NSDictionary<NSString *, NSString *> *loginCredentials = @{
+        @"login": [alertView textFieldAtIndex:0].text,
+        @"password": [alertView textFieldAtIndex:1].text,
+      };
+      callback(@[buttonKeys[buttonIndex], loginCredentials]);
+      break;
+    }
+    case UIAlertViewStyleDefault:
+      callback(@[buttonKeys[buttonIndex]]);
+      break;
   }
 
   [_alerts removeObjectAtIndex:index];
diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m
index f9ee2cb33093..6a22ae626f0e 100644
--- a/React/Modules/RCTAppState.m
+++ b/React/Modules/RCTAppState.m
@@ -44,27 +44,26 @@ @implementation RCTAppState
 
 #pragma mark - Lifecycle
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
+  _bridge = bridge;
 
-    _lastKnownState = RCTCurrentAppBackgroundState();
-
-    for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
-                             UIApplicationDidEnterBackgroundNotification,
-                             UIApplicationDidFinishLaunchingNotification]) {
-      [[NSNotificationCenter defaultCenter] addObserver:self
-                                               selector:@selector(handleAppStateDidChange)
-                                                   name:name
-                                                 object:nil];
-    }
+  // Is this thread-safe?
+  _lastKnownState = RCTCurrentAppBackgroundState();
 
+  for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
+                           UIApplicationDidEnterBackgroundNotification,
+                           UIApplicationDidFinishLaunchingNotification]) {
     [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(handleMemoryWarning)
-                                                 name:UIApplicationDidReceiveMemoryWarningNotification
+                                             selector:@selector(handleAppStateDidChange)
+                                                 name:name
                                                object:nil];
   }
-  return self;
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(handleMemoryWarning)
+                                               name:UIApplicationDidReceiveMemoryWarningNotification
+                                             object:nil];
 }
 
 - (void)handleMemoryWarning
diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m
index 273ef40c6bf8..46d062224d02 100644
--- a/React/Modules/RCTAsyncLocalStorage.m
+++ b/React/Modules/RCTAsyncLocalStorage.m
@@ -20,7 +20,7 @@
 
 static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
 static NSString *const RCTManifestFileName = @"manifest.json";
-static const NSUInteger RCTInlineValueThreshold = 100;
+static const NSUInteger RCTInlineValueThreshold = 1024;
 
 #pragma mark - Static helper functions
 
@@ -319,7 +319,7 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL
 
 #pragma mark - Exported JS Functions
 
-RCT_EXPORT_METHOD(multiGet:(NSStringArray *)keys
+RCT_EXPORT_METHOD(multiGet:(NSArray<NSString *> *)keys
                   callback:(RCTResponseSenderBlock)callback)
 {
   NSDictionary *errorOut = [self _ensureSetup];
@@ -338,7 +338,7 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL
   callback(@[RCTNullIfNil(errors), result]);
 }
 
-RCT_EXPORT_METHOD(multiSet:(NSStringArrayArray *)kvPairs
+RCT_EXPORT_METHOD(multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
                   callback:(RCTResponseSenderBlock)callback)
 {
   NSDictionary *errorOut = [self _ensureSetup];
@@ -358,7 +358,7 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL
   callback(@[RCTNullIfNil(errors)]);
 }
 
-RCT_EXPORT_METHOD(multiMerge:(NSStringArrayArray *)kvPairs
+RCT_EXPORT_METHOD(multiMerge:(NSArray<NSArray<NSString *> *> *)kvPairs
                     callback:(RCTResponseSenderBlock)callback)
 {
   NSDictionary *errorOut = [self _ensureSetup];
@@ -394,7 +394,7 @@ - (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL
   callback(@[RCTNullIfNil(errors)]);
 }
 
-RCT_EXPORT_METHOD(multiRemove:(NSStringArray *)keys
+RCT_EXPORT_METHOD(multiRemove:(NSArray<NSString *> *)keys
                   callback:(RCTResponseSenderBlock)callback)
 {
   NSDictionary *errorOut = [self _ensureSetup];
diff --git a/React/Modules/RCTPasteboard.h b/React/Modules/RCTClipboard.h
similarity index 86%
rename from React/Modules/RCTPasteboard.h
rename to React/Modules/RCTClipboard.h
index d29a3fb1a434..bc65c62390dd 100644
--- a/React/Modules/RCTPasteboard.h
+++ b/React/Modules/RCTClipboard.h
@@ -9,6 +9,6 @@
 
 #import "RCTBridgeModule.h"
 
-@interface RCTPasteboard : NSObject <RCTBridgeModule>
+@interface RCTClipboard : NSObject <RCTBridgeModule>
 
 @end
diff --git a/React/Modules/RCTClipboard.m b/React/Modules/RCTClipboard.m
new file mode 100644
index 000000000000..8d059c4a3745
--- /dev/null
+++ b/React/Modules/RCTClipboard.m
@@ -0,0 +1,39 @@
+/**
+ * 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.
+ */
+
+#import "RCTClipboard.h"
+
+#import "RCTUtils.h"
+
+#import <UIKit/UIKit.h>
+
+@implementation RCTClipboard
+
+RCT_EXPORT_MODULE()
+
+- (dispatch_queue_t)methodQueue
+{
+  return dispatch_get_main_queue();
+}
+
+
+RCT_EXPORT_METHOD(setString:(NSString *)content)
+{
+  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
+  clipboard.string = (content ? : @"");
+}
+
+RCT_EXPORT_METHOD(getString:(RCTPromiseResolveBlock)resolve
+                  rejecter:(__unused RCTPromiseRejectBlock)reject)
+{
+  UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
+  resolve((clipboard.string ? : @""));
+}
+
+@end
diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m
index a0266b7ce60d..708454754fd5 100644
--- a/React/Modules/RCTDevLoadingView.m
+++ b/React/Modules/RCTDevLoadingView.m
@@ -13,6 +13,7 @@
 #import "RCTDevLoadingView.h"
 #import "RCTDefines.h"
 #import "RCTUtils.h"
+#import "RCTModalHostViewController.h"
 
 #if RCT_DEV
 
@@ -34,23 +35,6 @@ + (void)setEnabled:(BOOL)enabled
   isEnabled = enabled;
 }
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(hide)
-                                                 name:RCTJavaScriptDidLoadNotification
-                                               object:nil];
-
-    [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(hide)
-                                                 name:RCTJavaScriptDidFailToLoadNotification
-                                               object:nil];
-  }
-  return self;
-}
-
 - (void)dealloc
 {
   [[NSNotificationCenter defaultCenter] removeObserver:self];
@@ -59,58 +43,60 @@ - (void)dealloc
 - (void)setBridge:(RCTBridge *)bridge
 {
   _bridge = bridge;
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(hide)
+                                               name:RCTJavaScriptDidLoadNotification
+                                             object:nil];
+
+  [[NSNotificationCenter defaultCenter] addObserver:self
+                                           selector:@selector(hide)
+                                               name:RCTJavaScriptDidFailToLoadNotification
+                                             object:nil];
   [self showWithURL:bridge.bundleURL];
 }
 
-- (void)showWithURL:(NSURL *)URL
+RCT_EXPORT_METHOD(showMessage:(NSString *)message color:(UIColor *)color backgroundColor:(UIColor *)backgroundColor)
 {
   if (!isEnabled) {
     return;
   }
 
   dispatch_async(dispatch_get_main_queue(), ^{
-
     _showDate = [NSDate date];
     if (!_window && !RCTRunningInTestEnvironment()) {
       CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
       _window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)];
-      _window.backgroundColor = [UIColor blackColor];
       _window.windowLevel = UIWindowLevelStatusBar + 1;
 
+      // set a root VC so rotation is supported
+      _window.rootViewController = [RCTModalHostViewController new];
+
       _label = [[UILabel alloc] initWithFrame:_window.bounds];
       _label.font = [UIFont systemFontOfSize:12.0];
-      _label.textColor = [UIColor grayColor];
       _label.textAlignment = NSTextAlignmentCenter;
 
       [_window addSubview:_label];
       [_window makeKeyAndVisible];
     }
 
-    NSString *source;
-    if (URL.fileURL) {
-      source = @"pre-bundled file";
-    } else {
-      source = [NSString stringWithFormat:@"%@:%@", URL.host, URL.port];
-    }
-
-    _label.text = [NSString stringWithFormat:@"Loading from %@...", source];
+    _label.text = message;
+    _label.textColor = color;
+    _window.backgroundColor = backgroundColor;
     _window.hidden = NO;
-
   });
 }
 
-- (void)hide
+RCT_EXPORT_METHOD(hide)
 {
   if (!isEnabled) {
     return;
   }
 
   dispatch_async(dispatch_get_main_queue(), ^{
-
     const NSTimeInterval MIN_PRESENTED_TIME = 0.6;
     NSTimeInterval presentedTime = [[NSDate date] timeIntervalSinceDate:_showDate];
     NSTimeInterval delay = MAX(0, MIN_PRESENTED_TIME - presentedTime);
-
     CGRect windowFrame = _window.frame;
     [UIView animateWithDuration:0.25
                           delay:delay
@@ -125,6 +111,26 @@ - (void)hide
   });
 }
 
+- (void)showWithURL:(NSURL *)URL
+{
+  UIColor *color;
+  UIColor *backgroundColor;
+  NSString *source;
+  if (URL.fileURL) {
+    color = [UIColor grayColor];
+    backgroundColor = [UIColor blackColor];
+    source = @"pre-bundled file";
+  } else {
+    color = [UIColor whiteColor];
+    backgroundColor = [UIColor colorWithHue:1./3 saturation:1 brightness:.35 alpha:1];
+    source = [NSString stringWithFormat:@"%@:%@", URL.host, URL.port];
+  }
+
+  [self showMessage:[NSString stringWithFormat:@"Loading from %@...", source]
+              color:color
+    backgroundColor:backgroundColor];
+}
+
 @end
 
 #else
diff --git a/React/Modules/RCTDevMenu.h b/React/Modules/RCTDevMenu.h
index f3d70661b3ad..ef2230e80d69 100644
--- a/React/Modules/RCTDevMenu.h
+++ b/React/Modules/RCTDevMenu.h
@@ -36,6 +36,11 @@
  */
 @property (nonatomic, assign) BOOL liveReloadEnabled;
 
+/**
+ * Enables hot loading. Currently not supported in open source.
+ */
+@property (nonatomic, assign) BOOL hotLoadingEnabled;
+
 /**
  * Shows the FPS monitor for the JS and Main threads.
  */
diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m
index a57c4b6432f4..d9e39b7023fd 100644
--- a/React/Modules/RCTDevMenu.m
+++ b/React/Modules/RCTDevMenu.m
@@ -10,7 +10,7 @@
 #import "RCTDevMenu.h"
 
 #import "RCTAssert.h"
-#import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTDefines.h"
 #import "RCTEventDispatcher.h"
 #import "RCTKeyCommands.h"
@@ -19,16 +19,10 @@
 #import "RCTRootView.h"
 #import "RCTSourceCode.h"
 #import "RCTUtils.h"
+#import "RCTWebSocketProxy.h"
 
 #if RCT_DEV
 
-@interface RCTBridge (Profiling)
-
-- (void)startProfiling;
-- (void)stopProfiling:(void (^)(NSData *))callback;
-
-@end
-
 static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
 static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
 
@@ -124,7 +118,7 @@ - (void)callHandler
 
 @end
 
-@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate, RCTInvalidating>
+@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate, RCTInvalidating, RCTWebSocketProxyDelegate>
 
 @property (nonatomic, strong) Class executorClass;
 
@@ -201,6 +195,7 @@ - (instancetype)init
     // Delay setup until after Bridge init
     dispatch_async(dispatch_get_main_queue(), ^{
       [weakSelf updateSettings:_settings];
+      [weakSelf connectPackager];
     });
 
 #if TARGET_IPHONE_SIMULATOR
@@ -235,6 +230,54 @@ - (instancetype)init
   return self;
 }
 
+- (NSURL *)packagerURL
+{
+  NSString *host = [_bridge.bundleURL host];
+  if (!host) {
+    return nil;
+  }
+
+  NSString *scheme = [_bridge.bundleURL scheme];
+  NSNumber *port = [_bridge.bundleURL port];
+  return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=shell", scheme, host, port]];
+}
+
+// TODO: Move non-UI logic into separate RCTDevSettings module
+- (void)connectPackager
+{
+  Class webSocketManagerClass = NSClassFromString(@"RCTWebSocketManager");
+  id<RCTWebSocketProxy> webSocketManager = (id <RCTWebSocketProxy>)[webSocketManagerClass sharedInstance];
+  NSURL *url = [self packagerURL];
+  if (url) {
+    [webSocketManager setDelegate:self forURL:url];
+  }
+}
+
+- (BOOL)isSupportedVersion:(NSNumber *)version
+{
+  NSArray<NSNumber *> *const kSupportedVersions = @[ @1 ];
+  return [kSupportedVersions containsObject:version];
+}
+
+- (void)socketProxy:(__unused id<RCTWebSocketProxy>)sender didReceiveMessage:(NSDictionary<NSString *, id> *)message
+{
+  if ([self isSupportedVersion:message[@"version"]]) {
+    [self processTarget:message[@"target"] action:message[@"action"] options:message[@"options"]];
+  }
+}
+
+- (void)processTarget:(NSString *)target action:(NSString *)action options:(NSDictionary<NSString *, id> *)options
+{
+  if ([target isEqualToString:@"bridge"]) {
+    if ([action isEqualToString:@"reload"]) {
+      if ([options[@"debug"] boolValue]) {
+        _bridge.executorClass = NSClassFromString(@"RCTWebSocketExecutor");
+      }
+      [_bridge reload];
+    }
+  }
+}
+
 - (dispatch_queue_t)methodQueue
 {
   return dispatch_get_main_queue();
@@ -274,6 +317,7 @@ - (void)updateSettings:(NSDictionary *)settings
   self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
   self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
   self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
+  self.hotLoadingEnabled = [_settings[@"hotLoadingEnabled"] ?: @NO boolValue];
   self.showFPS = [_settings[@"showFPS"] ?: @NO boolValue];
   self.executorClass = NSClassFromString(_executorOverride ?: _settings[@"executorClass"]);
 }
@@ -321,14 +365,14 @@ - (void)jsLoaded:(NSNotification *)notification
 
   // Check if live reloading is available
   _liveReloadURL = nil;
-  RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
+  RCTSourceCode *sourceCodeModule = [_bridge moduleForClass:[RCTSourceCode class]];
   if (!sourceCodeModule.scriptURL) {
     if (!sourceCodeModule) {
       RCTLogWarn(@"RCTSourceCode module not found");
-    } else {
+    } else if (!RCTRunningInTestEnvironment()) {
       RCTLogWarn(@"RCTSourceCode module scriptURL has not been set");
     }
-  } else if (!(sourceCodeModule.scriptURL).fileURL) {
+  } else if (!sourceCodeModule.scriptURL.fileURL) {
     // Live reloading is disabled when running from bundled JS file
     _liveReloadURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
   }
@@ -427,6 +471,13 @@ - (void)addItem:(RCTDevMenuItem *)item
     }]];
   }
 
+  if ([self hotLoadingAvailable]) {
+    NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Loading" : @"Enable Hot Loading";
+    [items addObject:[RCTDevMenuItem buttonItemWithTitle:hotLoadingTitle handler:^{
+      weakSelf.hotLoadingEnabled = !_hotLoadingEnabled;
+    }]];
+  }
+
   [items addObjectsFromArray:_extraMenuItems];
 
   return items;
@@ -490,8 +541,6 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
 
 RCT_EXPORT_METHOD(reload)
 {
-  _jsLoaded = NO;
-  _liveReloadURL = nil;
   [_bridge reload];
 }
 
@@ -530,6 +579,24 @@ - (void)setLiveReloadEnabled:(BOOL)enabled
   }
 }
 
+- (BOOL)hotLoadingAvailable
+{
+  return _bridge.bundleURL && !_bridge.bundleURL.fileURL; // Only works when running from server
+}
+
+- (void)setHotLoadingEnabled:(BOOL)enabled
+{
+  _hotLoadingEnabled = enabled;
+  [self updateSetting:@"hotLoadingEnabled" value:@(_hotLoadingEnabled)];
+
+  BOOL actuallyEnabled = [self hotLoadingAvailable] && _hotLoadingEnabled;
+  if (RCTGetURLQueryParam(_bridge.bundleURL, @"hot").boolValue != actuallyEnabled) {
+    _bridge.bundleURL = RCTURLByReplacingQueryParam(_bridge.bundleURL, @"hot",
+                                                    actuallyEnabled ? @"true" : nil);
+    [_bridge reload];
+  }
+}
+
 - (void)setExecutorClass:(Class)executorClass
 {
   if (_executorClass != executorClass) {
@@ -550,7 +617,7 @@ - (void)setExecutorClass:(Class)executorClass
     }
 
     _bridge.executorClass = executorClass;
-    [self reload];
+    [_bridge reload];
   }
 }
 
@@ -614,7 +681,7 @@ @implementation  RCTBridge (RCTDevMenu)
 - (RCTDevMenu *)devMenu
 {
 #if RCT_DEV
-  return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])];
+  return [self moduleForClass:[RCTDevMenu class]];
 #else
   return nil;
 #endif
diff --git a/React/Modules/RCTExceptionsManager.h b/React/Modules/RCTExceptionsManager.h
index 20ca8d26e219..ad5625287eb7 100644
--- a/React/Modules/RCTExceptionsManager.h
+++ b/React/Modules/RCTExceptionsManager.h
@@ -23,7 +23,7 @@
 
 @interface RCTExceptionsManager : NSObject <RCTBridgeModule>
 
-- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate;
 
 @property (nonatomic, assign) NSUInteger maxReloadAttempts;
 
diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m
index 9cc5d572212c..791c163f16dd 100644
--- a/React/Modules/RCTExceptionsManager.m
+++ b/React/Modules/RCTExceptionsManager.m
@@ -27,20 +27,14 @@ @implementation RCTExceptionsManager
 
 - (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
 {
-  if ((self = [super init])) {
+  if ((self = [self init])) {
     _delegate = delegate;
-    _maxReloadAttempts = 0;
   }
   return self;
 }
 
-- (instancetype)init
-{
-  return [self initWithDelegate:nil];
-}
-
 RCT_EXPORT_METHOD(reportSoftException:(NSString *)message
-                  stack:(NSDictionaryArray *)stack
+                  stack:(NSArray<NSDictionary *> *)stack
                   exceptionId:(nonnull NSNumber *)exceptionId)
 {
   [_bridge.redBox showErrorMessage:message withStack:stack];
@@ -51,7 +45,7 @@ - (instancetype)init
 }
 
 RCT_EXPORT_METHOD(reportFatalException:(NSString *)message
-                  stack:(NSDictionaryArray *)stack
+                  stack:(NSArray<NSDictionary *> *)stack
                   exceptionId:(nonnull NSNumber *)exceptionId)
 {
   [_bridge.redBox showErrorMessage:message withStack:stack];
@@ -72,7 +66,7 @@ - (instancetype)init
 }
 
 RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message
-                  stack:(NSDictionaryArray *)stack
+                  stack:(NSArray<NSDictionary *> *)stack
                   exceptionId:(nonnull NSNumber *)exceptionId)
 {
   [_bridge.redBox updateErrorMessage:message withStack:stack];
@@ -84,7 +78,7 @@ - (instancetype)init
 
 // Deprecated.  Use reportFatalException directly instead.
 RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message
-                  stack:(NSDictionaryArray *)stack)
+                  stack:(NSArray<NSDictionary *> *)stack)
 {
   [self reportFatalException:message stack:stack exceptionId:@-1];
 }
diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m
index a334c753368b..ec0cfb850533 100644
--- a/React/Modules/RCTRedBox.m
+++ b/React/Modules/RCTRedBox.m
@@ -91,7 +91,7 @@ - (void)dealloc
 
 - (void)openStackFrameInEditor:(NSDictionary *)stackFrame
 {
-  NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
+  NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, NULL) dataUsingEncoding:NSUTF8StringEncoding];
   NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length];
   NSMutableURLRequest *request = [NSMutableURLRequest new];
   request.URL = [RCTConvert NSURL:@"http://localhost:8081/open-stack-frame"];
@@ -107,7 +107,9 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *>
 {
   if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) {
     _lastStackTrace = stack;
-    _lastErrorMessage = message;
+    // message is displayed using UILabel, which is unable to render text of
+    // unlimited length, so we truncate it
+    _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)];
 
     [_stackTraceTableView reloadData];
 
@@ -307,7 +309,7 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *>
   });
 }
 
-- (void)dismiss
+RCT_EXPORT_METHOD(dismiss)
 {
   dispatch_async(dispatch_get_main_queue(), ^{
     [_window dismiss];
@@ -325,7 +327,7 @@ @implementation RCTBridge (RCTRedBox)
 
 - (RCTRedBox *)redBox
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTRedBox class])];
+  return [self moduleForClass:[RCTRedBox class]];
 }
 
 @end
diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m
index 281a304f93ea..f8d96fac68e8 100644
--- a/React/Modules/RCTSourceCode.m
+++ b/React/Modules/RCTSourceCode.m
@@ -24,15 +24,17 @@ @implementation RCTSourceCode
 - (void)setScriptText:(NSString *)scriptText {}
 #endif
 
-RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
-                  failureCallback:(RCTResponseErrorBlock)failureCallback)
+NSString *const RCTErrorUnavailable = @"E_SOURCE_CODE_UNAVAILABLE";
+
+RCT_EXPORT_METHOD(getScriptText:(RCTPromiseResolveBlock)resolve
+                  reject:(RCTPromiseRejectBlock)reject)
 {
   if (RCT_DEV && self.scriptData && self.scriptURL) {
     NSString *scriptText = [[NSString alloc] initWithData:self.scriptData encoding:NSUTF8StringEncoding];
 
-    successCallback(@[@{@"text": scriptText, @"url": self.scriptURL.absoluteString}]);
+    resolve(@{@"text": scriptText, @"url": self.scriptURL.absoluteString});
   } else {
-    failureCallback(RCTErrorWithMessage(@"Source code is not available"));
+    reject(RCTErrorUnavailable, nil, RCTErrorWithMessage(@"Source code is not available"));
   }
 }
 
diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m
index 80e40c9aae14..026d1901209c 100644
--- a/React/Modules/RCTStatusBarManager.m
+++ b/React/Modules/RCTStatusBarManager.m
@@ -46,14 +46,13 @@ static BOOL RCTViewControllerBasedStatusBarAppearance()
 
 @synthesize bridge = _bridge;
 
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
 {
-  if ((self = [super init])) {
-    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
-    [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
-    [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
-  }
-  return self;
+  _bridge = bridge;
+
+  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+  [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
+  [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
 }
 
 - (void)dealloc
@@ -90,6 +89,13 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification
   [self emitEvent:@"statusBarFrameWillChange" forNotification:notification];
 }
 
+RCT_EXPORT_METHOD(getHeight:(RCTResponseSenderBlock)callback)
+{
+  callback(@[@{
+    @"height": @([UIApplication sharedApplication].statusBarFrame.size.height),
+  }]);
+}
+
 RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle
                   animated:(BOOL)animated)
 {
diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m
index fc2d9e716d4e..3aa084d3073f 100644
--- a/React/Modules/RCTTiming.m
+++ b/React/Modules/RCTTiming.m
@@ -11,18 +11,10 @@
 
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
 
-@interface RCTBridge (Private)
-
-/**
- * Allow super fast, one time, timers to skip the queue and be directly executed
- */
-- (void)_immediatelyCallTimer:(NSNumber *)timer;
-
-@end
-
 @interface RCTTimer : NSObject
 
 @property (nonatomic, strong, readonly) NSDate *target;
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index 9fd8cb7e8f3d..8a14a4a02eee 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -44,19 +44,6 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
  */
 @interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
 
-/**
- * The UIIManager has the concept of a designated "main scroll view", which is
- * useful for apps built around a central scrolling content area (e.g. a
- * timeline).
- */
-@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
-
-/**
- * Allows native environment code to respond to "the main scroll view" events.
- * see `RCTUIManager`'s `setMainScrollViewTag`.
- */
-@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
-
 /**
  * Register a root view with the RCTUIManager.
  */
@@ -73,6 +60,12 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
  */
 - (void)setFrame:(CGRect)frame forView:(UIView *)view;
 
+/**
+ * Set the natural size of a view, which is used when no explicit size is set.
+ * Use UIViewNoIntrinsicMetric to ignore a dimension.
+ */
+- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view;
+
 /**
  * Update the background color of a root view. This is usually triggered by
  * manually setting the background color of the root view with native code.
@@ -90,6 +83,23 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey;
  */
 + (UIView *)JSResponder;
 
+/**
+ * Normally, UI changes are not applied until the complete batch of method
+ * invocations from JavaScript to native has completed.
+ *
+ * Setting this to YES will flush UI changes sooner, which could potentially
+ * result in inconsistent UI updates.
+ *
+ * The default is NO (recommended).
+ */
+@property (atomic, assign) BOOL unsafeFlushUIChangesBeforeBatchEnds;
+
+/**
+ * In some cases we might want to trigger layout from native side.
+ * React won't be aware of this, so we need to make sure it happens.
+ */
+- (void)setNeedsLayout;
+
 @end
 
 /**
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index 679e84767553..d0db1c40aa5b 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -16,12 +16,15 @@
 #import "RCTAnimationType.h"
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
 #import "RCTComponent.h"
 #import "RCTComponentData.h"
 #import "RCTConvert.h"
 #import "RCTDefines.h"
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
+#import "RCTModuleData.h"
+#import "RCTModuleMethod.h"
 #import "RCTProfile.h"
 #import "RCTRootView.h"
 #import "RCTRootViewInternal.h"
@@ -179,14 +182,6 @@ - (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseS
 
 @end
 
-@interface RCTUIManager ()
-
-// NOTE: these are properties so that they can be accessed by unit tests
-@property (nonatomic, strong) NSMutableDictionary<NSNumber *, RCTShadowView *> *shadowViewRegistry; // RCT thread only
-@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIView *> *viewRegistry; // Main thread only
-
-@end
-
 @implementation RCTUIManager
 {
   dispatch_queue_t _shadowQueue;
@@ -199,6 +194,9 @@ @implementation RCTUIManager
   RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only
   RCTLayoutAnimation *_layoutAnimation; // Main thread only
 
+  NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
+  NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
+
   // Keyed by viewName
   NSDictionary *_componentDataByName;
 
@@ -214,31 +212,6 @@ @implementation RCTUIManager
  */
 extern NSString *RCTBridgeModuleNameForClass(Class cls);
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    const char *queueName = "com.facebook.React.ShadowQueue";
-
-    if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) {
-      dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
-      _shadowQueue = dispatch_queue_create(queueName, attr);
-    } else {
-      _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
-      dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
-    }
-
-    _shadowViewRegistry = [NSMutableDictionary new];
-    _viewRegistry = [NSMutableDictionary new];
-
-    // Internal resources
-    _pendingUIBlocks = [NSMutableArray new];
-    _rootViewTags = [NSMutableSet new];
-
-    _bridgeTransactionListeners = [NSMutableSet new];
-  }
-  return self;
-}
-
 - (void)didReceiveNewContentSizeMultiplier
 {
   __weak RCTUIManager *weakSelf = self;
@@ -276,18 +249,45 @@ - (void)invalidate
   });
 }
 
+- (NSMutableDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
+{
+  // NOTE: this method only exists so that it can be accessed by unit tests
+  if (!_shadowViewRegistry) {
+    _shadowViewRegistry = [NSMutableDictionary new];
+  }
+  return _shadowViewRegistry;
+}
+
+- (NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry
+{
+  // NOTE: this method only exists so that it can be accessed by unit tests
+  if (!_viewRegistry) {
+    _viewRegistry = [NSMutableDictionary new];
+  }
+  return _viewRegistry;
+}
+
 - (void)setBridge:(RCTBridge *)bridge
 {
   RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
 
   _bridge = bridge;
+
   _shadowViewRegistry = [NSMutableDictionary new];
+  _viewRegistry = [NSMutableDictionary new];
+
+  // Internal resources
+  _pendingUIBlocks = [NSMutableArray new];
+  _rootViewTags = [NSMutableSet new];
+
+  _bridgeTransactionListeners = [NSMutableSet new];
 
   // Get view managers from bridge
   NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
-  for (RCTViewManager *manager in _bridge.modules.allValues) {
-    if ([manager isKindOfClass:[RCTViewManager class]]) {
-      RCTComponentData *componentData = [[RCTComponentData alloc] initWithManager:manager];
+  for (Class moduleClass in _bridge.moduleClasses) {
+    if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
+      RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass
+                                                                                bridge:_bridge];
       componentDataByName[componentData.name] = componentData;
     }
   }
@@ -302,6 +302,17 @@ - (void)setBridge:(RCTBridge *)bridge
 
 - (dispatch_queue_t)methodQueue
 {
+  if (!_shadowQueue) {
+    const char *queueName = "com.facebook.React.ShadowQueue";
+
+    if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) {
+      dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
+      _shadowQueue = dispatch_queue_create(queueName, attr);
+    } else {
+      _shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
+      dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
+    }
+  }
   return _shadowQueue;
 }
 
@@ -339,7 +350,7 @@ - (void)registerRootView:(UIView *)rootView
 
   [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
                                                       object:self
-                                                    userInfo:@{ RCTUIManagerRootViewKey: rootView }];
+                                                    userInfo:@{RCTUIManagerRootViewKey: rootView}];
 }
 
 - (UIView *)viewForReactTag:(NSNumber *)reactTag
@@ -364,17 +375,40 @@ - (void)setFrame:(CGRect)frame forView:(UIView *)view
 
   NSNumber *reactTag = view.reactTag;
   dispatch_async(_shadowQueue, ^{
-    RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
-    RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
+    RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
+    RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag);
 
-    if (RCTIsReactRootView(reactTag)) {
-      rootShadowView.frame = frame;
-      rootShadowView.sizeFlexibility = sizeFlexibility;
-    } else {
-      rootShadowView.frame = frame;
+    BOOL dirtyLayout = NO;
+
+    if (!CGRectEqualToRect(frame, shadowView.frame)) {
+      shadowView.frame = frame;
+      dirtyLayout = YES;
     }
 
-    [rootShadowView updateLayout];
+    // Trigger re-layout when size flexibility changes, as the root view might grow or
+    // shrink in the flexible dimensions.
+    if (RCTIsReactRootView(reactTag) && shadowView.sizeFlexibility != sizeFlexibility) {
+      shadowView.sizeFlexibility = sizeFlexibility;
+      dirtyLayout = YES;
+    }
+
+    if (dirtyLayout) {
+      [shadowView dirtyLayout];
+      [self batchDidComplete];
+    }
+  });
+}
+
+- (void)setIntrinsicContentSize:(CGSize)size forView:(UIView *)view
+{
+  RCTAssertMainThread();
+
+  NSNumber *reactTag = view.reactTag;
+  dispatch_async(_shadowQueue, ^{
+    RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
+    RCTAssert(shadowView != nil, @"Could not locate root view with tag #%@", reactTag);
+
+    shadowView.intrinsicContentSize = size;
 
     [self batchDidComplete];
   });
@@ -472,6 +506,8 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
     [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
   NSMutableArray<NSNumber *> *parentsAreNew =
     [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
+  NSMutableDictionary<NSNumber *, RCTViewManagerUIBlock> *updateBlocks =
+    [NSMutableDictionary new];
 
   for (RCTShadowView *shadowView in viewsWithNewFrames) {
     [frameReactTags addObject:shadowView.reactTag];
@@ -489,10 +525,13 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
   // reactSetFrame: has been called. Note that if reactSetFrame: is not called,
   // these won't be called either, so this is not a suitable place to update
   // properties that aren't related to layout.
-  NSMutableArray<RCTViewManagerUIBlock> *updateBlocks = [NSMutableArray new];
   for (RCTShadowView *shadowView in viewsWithNewFrames) {
     RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
     RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
+    if (block) {
+      updateBlocks[shadowView.reactTag] = block;
+    }
+
     if (shadowView.onLayout) {
       CGRect frame = shadowView.frame;
       shadowView.onLayout(@{
@@ -514,14 +553,9 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
         RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
 
         RCTRootView *rootView = (RCTRootView *)[view superview];
-
         rootView.intrinsicSize = contentSize;
       });
     }
-
-    if (block) {
-      [updateBlocks addObject:block];
-    }
   }
 
   // Perform layout (possibly animated)
@@ -545,29 +579,15 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
 
       void (^completion)(BOOL) = ^(BOOL finished) {
         completionsCalled++;
-        if (callback && completionsCalled == frames.count - 1) {
+        if (callback && completionsCalled == frames.count) {
           callback(@[@(finished)]);
         }
       };
 
-      // Animate view update
-      if (updateAnimation) {
-        [updateAnimation performAnimations:^{
-          [view reactSetFrame:frame];
-          for (RCTViewManagerUIBlock block in updateBlocks) {
-            block(self, _viewRegistry);
-          }
-        } withCompletionBlock:completion];
-      } else {
-        [view reactSetFrame:frame];
-        for (RCTViewManagerUIBlock block in updateBlocks) {
-          block(self, _viewRegistry);
-        }
-        completion(YES);
-      }
-
       // Animate view creation
       if (createAnimation) {
+        [view reactSetFrame:frame];
+
         CATransform3D finalTransform = view.layer.transform;
         CGFloat finalOpacity = view.layer.opacity;
         if ([createAnimation.property isEqualToString:@"scaleXY"]) {
@@ -575,6 +595,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
         } else if ([createAnimation.property isEqualToString:@"opacity"]) {
           view.layer.opacity = 0.0;
         }
+
         [createAnimation performAnimations:^{
           if ([createAnimation.property isEqual:@"scaleXY"]) {
             view.layer.transform = finalTransform;
@@ -584,10 +605,33 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
             RCTLogError(@"Unsupported layout animation createConfig property %@",
                         createAnimation.property);
           }
-          for (RCTViewManagerUIBlock block in updateBlocks) {
-            block(self, _viewRegistry);
+
+          RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
+          if (updateBlock) {
+            updateBlock(self, _viewRegistry);
           }
-        } withCompletionBlock:nil];
+        } withCompletionBlock:completion];
+
+      // Animate view update
+      } else if (updateAnimation) {
+        [updateAnimation performAnimations:^{
+          [view reactSetFrame:frame];
+
+          RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
+          if (updateBlock) {
+            updateBlock(self, _viewRegistry);
+          }
+        } withCompletionBlock:completion];
+
+      // Update without animation
+      } else {
+        [view reactSetFrame:frame];
+
+        RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag];
+        if (updateBlock) {
+          updateBlock(self, _viewRegistry);
+        }
+        completion(YES);
       }
     }
   };
@@ -687,7 +731,7 @@ - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
 
     [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRemoveRootViewNotification
                                                         object:uiManager
-                                                      userInfo:@{ RCTUIManagerRootViewKey: rootView }];
+                                                      userInfo:@{RCTUIManagerRootViewKey: rootView}];
   }];
 }
 
@@ -712,12 +756,39 @@ - (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
         removeAtIndices:removeAtIndices];
 }
 
+RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag
+                  reactTags:(NSArray<NSNumber *> *)reactTags)
+{
+  RCTSetChildren(containerTag, reactTags,
+                 (NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
+
+  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
+
+    RCTSetChildren(containerTag, reactTags,
+                   (NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
+  }];
+}
+
+static void RCTSetChildren(NSNumber *containerTag,
+                           NSArray<NSNumber *> *reactTags,
+                           NSDictionary<NSNumber *, id<RCTComponent>> *registry)
+{
+  id<RCTComponent> container = registry[containerTag];
+  NSInteger index = 0;
+  for (NSNumber *reactTag in reactTags) {
+    id<RCTComponent> view = registry[reactTag];
+    if (view) {
+      [container insertReactSubview:view atIndex:index++];
+    }
+  }
+}
+
 RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
-                  moveFromIndices:(NSNumberArray *)moveFromIndices
-                  moveToIndices:(NSNumberArray *)moveToIndices
-                  addChildReactTags:(NSNumberArray *)addChildReactTags
-                  addAtIndices:(NSNumberArray *)addAtIndices
-                  removeAtIndices:(NSNumberArray *)removeAtIndices)
+                  moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
+                  moveToIndices:(NSArray<NSNumber *> *)moveToIndices
+                  addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
+                  addAtIndices:(NSArray<NSNumber *> *)addAtIndices
+                  removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices)
 {
   [self _manageChildren:containerReactTag
         moveFromIndices:moveFromIndices
@@ -805,7 +876,7 @@ - (void)_manageChildren:(NSNumber *)containerReactTag
   UIColor *backgroundColor = shadowView.backgroundColor;
 
   [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
-    UIView *view = [componentData createViewWithTag:reactTag props:props];
+    UIView *view = [componentData createViewWithTag:reactTag];
     if (view) {
       if ([view respondsToSelector:@selector(setBackgroundColor:)]) {
         ((UIView *)view).backgroundColor = backgroundColor;
@@ -872,12 +943,42 @@ - (void)_manageChildren:(NSNumber *)containerReactTag
   }];
 }
 
+RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
+                  commandID:(NSInteger)commandID
+                  commandArgs:(NSArray<id> *)commandArgs)
+{
+  RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
+  RCTComponentData *componentData = _componentDataByName[shadowView.viewName];
+  Class managerClass = componentData.managerClass;
+  RCTModuleData *moduleData = [_bridge moduleDataForName:RCTBridgeModuleNameForClass(managerClass)];
+  id<RCTBridgeMethod> method = moduleData.methods[commandID];
+
+  NSArray *args = [@[reactTag] arrayByAddingObjectsFromArray:commandArgs];
+  [method invokeWithBridge:_bridge module:componentData.manager arguments:args];
+}
+
+- (void)partialBatchDidFlush
+{
+  if (self.unsafeFlushUIChangesBeforeBatchEnds) {
+    [self flushUIBlocks];
+  }
+}
+
 - (void)batchDidComplete
+{
+  [self _layoutAndMount];
+}
+
+/**
+ * Sets up animations, computes layout, creates UI mounting blocks for computed layout,
+ * runs these blocks and all other already existing blocks.
+ */
+- (void)_layoutAndMount
 {
   // Gather blocks to be executed now that all view hierarchy manipulations have
   // been completed (note that these may still take place before layout has finished)
   for (RCTComponentData *componentData in _componentDataByName.allValues) {
-    RCTViewManagerUIBlock uiBlock = [componentData.manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
+    RCTViewManagerUIBlock uiBlock = [componentData uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
     [self addUIBlock:uiBlock];
   }
 
@@ -947,6 +1048,15 @@ - (void)flushUIBlocks
   }
 }
 
+- (void)setNeedsLayout
+{
+  // If there is an active batch layout will happen when batch finished, so we will wait for that.
+  // Otherwise we immidiately trigger layout.
+  if (![_bridge isBatchActive]) {
+    [self _layoutAndMount];
+  }
+}
+
 RCT_EXPORT_METHOD(measure:(nonnull NSNumber *)reactTag
                   callback:(RCTResponseSenderBlock)callback)
 {
@@ -958,18 +1068,16 @@ - (void)flushUIBlocks
       callback(@[]);
       return;
     }
-    CGRect frame = view.frame;
 
+    // If in a <Modal>, rootView will be the root of the modal container.
     UIView *rootView = view;
-    while (rootView && ![rootView isReactRootView]) {
+    while (rootView.superview && ![rootView isReactRootView]) {
       rootView = rootView.superview;
     }
 
-    // TODO: this doesn't work because sometimes view is inside a modal window
-    // RCTAssert([rootView isReactRootView], @"React view is not inside a React root view");
-
     // By convention, all coordinates, whether they be touch coordinates, or
     // measurement coordinates are with respect to the root view.
+    CGRect frame = view.frame;
     CGPoint pagePoint = [view.superview convertPoint:frame.origin toView:rootView];
 
     callback(@[
@@ -983,6 +1091,29 @@ - (void)flushUIBlocks
   }];
 }
 
+RCT_EXPORT_METHOD(measureInWindow:(nonnull NSNumber *)reactTag
+                  callback:(RCTResponseSenderBlock)callback)
+{
+  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
+    UIView *view = viewRegistry[reactTag];
+    if (!view) {
+      // this view was probably collapsed out
+      RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
+      callback(@[]);
+      return;
+    }
+
+    // Return frame coordinates in window
+    CGRect windowFrame = [view.window convertRect:view.frame fromView:view.superview];
+    callback(@[
+      @(windowFrame.origin.x),
+      @(windowFrame.origin.y),
+      @(windowFrame.size.width),
+      @(windowFrame.size.height),
+    ]);
+  }];
+}
+
 static void RCTMeasureLayout(RCTShadowView *view,
                              RCTShadowView *ancestor,
                              RCTResponseSenderBlock callback)
@@ -1095,70 +1226,70 @@ static void RCTMeasureLayout(RCTShadowView *view,
   callback(@[results]);
 }
 
-RCT_EXPORT_METHOD(setMainScrollViewTag:(nonnull NSNumber *)reactTag)
+RCT_EXPORT_METHOD(takeSnapshot:(id /* NSString or NSNumber */)target
+                  withOptions:(NSDictionary *)options
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(RCTPromiseRejectBlock)reject)
 {
-  [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
-    // - There should be at most one designated "main scroll view"
-    // - There should be at most one designated "`nativeMainScrollDelegate`"
-    // - The one designated main scroll view should have the one designated
-    // `nativeMainScrollDelegate` set as its `nativeMainScrollDelegate`.
-    if (uiManager.mainScrollView) {
-      uiManager.mainScrollView.nativeMainScrollDelegate = nil;
-    }
-    id view = viewRegistry[reactTag];
-    if (view) {
-      if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
-        uiManager.mainScrollView = (id<RCTScrollableProtocol>)view;
-        uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
-      } else {
-        RCTLogError(@"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
+  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
+
+    // Get view
+    UIView *view;
+    if (target == nil || [target isEqual:@"window"]) {
+      view = RCTKeyWindow();
+    } else if ([target isKindOfClass:[NSNumber class]]) {
+      view = viewRegistry[target];
+      if (!view) {
+        RCTLogError(@"No view found with reactTag: %@", target);
+        return;
       }
-    } else {
-      uiManager.mainScrollView = nil;
     }
-  }];
-}
 
-// TODO: we could just pass point property
-RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
-                  withOffsetX:(CGFloat)offsetX
-                  offsetY:(CGFloat)offsetY)
-{
-  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
-    UIView *view = viewRegistry[reactTag];
-    if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
-      [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:YES];
-    } else {
-      RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
+    // Get options
+    CGSize size = [RCTConvert CGSize:options];
+    NSString *format = [RCTConvert NSString:options[@"format"] ?: @"png"];
+
+    // Capture image
+    if (size.width < 0.1 || size.height < 0.1) {
+      size = view.bounds.size;
     }
-  }];
-}
+    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
+    BOOL success = [view drawViewHierarchyInRect:(CGRect){CGPointZero, size} afterScreenUpdates:YES];
+    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
 
-// TODO: we could just pass point property
-RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(nonnull NSNumber *)reactTag
-                  offsetX:(CGFloat)offsetX
-                  offsetY:(CGFloat)offsetY)
-{
-    [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
-        UIView *view = viewRegistry[reactTag];
-        if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
-            [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:NO];
-        } else {
-            RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
+    if (!success || !image) {
+      reject(RCTErrorUnspecified, @"Failed to capture view snapshot.", nil);
+      return;
+    }
+
+    // Convert image to data (on a background thread)
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+      NSData *data;
+      if ([format isEqualToString:@"png"]) {
+        data = UIImagePNGRepresentation(image);
+      } else if ([format isEqualToString:@"jpeg"]) {
+        CGFloat quality = [RCTConvert CGFloat:options[@"quality"] ?: @1];
+        data = UIImageJPEGRepresentation(image, quality);
+      } else {
+        RCTLogError(@"Unsupported image format: %@", format);
+        return;
+      }
+
+      // Save to a temp file
+      NSError *error = nil;
+      NSString *tempFilePath = RCTTempFilePath(format, &error);
+      if (tempFilePath) {
+        if ([data writeToFile:tempFilePath options:(NSDataWritingOptions)0 error:&error]) {
+          resolve(tempFilePath);
+          return;
         }
-    }];
-}
+      }
 
-RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag
-                  withRect:(CGRect)rect)
-{
-  [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
-    UIView *view = viewRegistry[reactTag];
-    if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
-      [(id<RCTScrollableProtocol>)view zoomToRect:rect animated:YES];
-    } else {
-      RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
-    }
+      // If we reached here, something went wrong
+      reject(RCTErrorUnspecified, error.localizedDescription, error);
+    });
   }];
 }
 
@@ -1193,12 +1324,11 @@ static void RCTMeasureLayout(RCTShadowView *view,
   [_componentDataByName enumerateKeysAndObjectsUsingBlock:
    ^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
 
-     RCTViewManager *manager = componentData.manager;
      NSMutableDictionary<NSString *, id> *constantsNamespace =
        [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
 
      // Add manager class
-     constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass([manager class]);
+     constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
 
      // Add native props
      NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
@@ -1246,10 +1376,6 @@ static void RCTMeasureLayout(RCTShadowView *view,
         @"height": @(RCTScreenSize().height),
         @"scale": @(RCTScreenScale()),
       },
-      @"modalFullscreenView": @{
-        @"width": @(RCTScreenSize().width),
-        @"height": @(RCTScreenSize().height),
-      },
     },
   }];
 
@@ -1283,7 +1409,7 @@ @implementation RCTBridge (RCTUIManager)
 
 - (RCTUIManager *)uiManager
 {
-  return self.modules[RCTBridgeModuleNameForClass([RCTUIManager class])];
+  return [self moduleForClass:[RCTUIManager class]];
 }
 
 @end
diff --git a/React/Profiler/RCTJSCProfiler.h b/React/Profiler/RCTJSCProfiler.h
new file mode 100644
index 000000000000..5f8e23b178c2
--- /dev/null
+++ b/React/Profiler/RCTJSCProfiler.h
@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+
+#import <JavaScriptCore/JavaScriptCore.h>
+
+#import "RCTDefines.h"
+
+/** The API is not thread-safe. */
+
+/** The context is not retained. */
+RCT_EXTERN void RCTJSCProfilerStart(JSContextRef ctx);
+/** Returns a file path containing the profiler data. */
+RCT_EXTERN NSString *RCTJSCProfilerStop(JSContextRef ctx);
+
+RCT_EXTERN BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx);
+RCT_EXTERN BOOL RCTJSCProfilerIsSupported(void);
diff --git a/React/Profiler/RCTJSCProfiler.m b/React/Profiler/RCTJSCProfiler.m
new file mode 100644
index 000000000000..fc0a21fff8bb
--- /dev/null
+++ b/React/Profiler/RCTJSCProfiler.m
@@ -0,0 +1,137 @@
+/**
+ * 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.
+ */
+
+#import "RCTJSCProfiler.h"
+#import "RCTLog.h"
+#import <UIKit/UIKit.h>
+
+#ifndef RCT_JSC_PROFILER
+  #if RCT_DEV
+    #define RCT_JSC_PROFILER 1
+  #else
+    #define RCT_JSC_PROFILER 0
+  #endif
+#endif
+
+#if RCT_JSC_PROFILER
+
+#include <dlfcn.h>
+
+#ifndef RCT_JSC_PROFILER_DYLIB
+  #define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
+#endif
+
+static const char *const JSCProfileName = "profile";
+
+typedef void (*JSCProfilerStartFunctionType)(JSContextRef, const char *);
+typedef void (*JSCProfilerEndFunctionType)(JSContextRef, const char *, const char *);
+typedef void (*JSCProfilerEnableFunctionType)(void);
+
+static NSMutableDictionary<NSValue *, NSNumber *> *RCTJSCProfilerStateMap;
+
+static JSCProfilerStartFunctionType RCTNativeProfilerStart  = NULL;
+static JSCProfilerEndFunctionType RCTNativeProfilerEnd    = NULL;
+
+NS_INLINE NSValue *RCTJSContextRefKey(JSContextRef ref) {
+  return [NSValue valueWithPointer:ref];
+}
+
+static void RCTJSCProfilerStateInit()
+{
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    RCTJSCProfilerStateMap = [NSMutableDictionary new];
+
+    void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
+
+    RCTNativeProfilerStart = (JSCProfilerStartFunctionType)dlsym(JSCProfiler, "nativeProfilerStart");
+    RCTNativeProfilerEnd =  (JSCProfilerEndFunctionType)dlsym(JSCProfiler, "nativeProfilerEnd");
+    JSCProfilerEnableFunctionType enableBytecode = (__typeof__(enableBytecode))dlsym(JSCProfiler, "nativeProfilerEnableBytecode");
+
+    if (RCTNativeProfilerStart && RCTNativeProfilerEnd && enableBytecode) {
+      enableBytecode();
+    } else {
+      RCTNativeProfilerStart = NULL;
+      RCTNativeProfilerEnd = NULL;
+    }
+  });
+}
+
+#endif
+
+void RCTJSCProfilerStart(JSContextRef ctx)
+{
+#if RCT_JSC_PROFILER
+  if (ctx != NULL) {
+    if (RCTJSCProfilerIsSupported()) {
+      NSValue *key = RCTJSContextRefKey(ctx);
+      BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue];
+      if (!isProfiling) {
+        RCTLogInfo(@"Starting JSC profiler for context: %p", ctx);
+        RCTJSCProfilerStateMap[key] = @YES;
+        RCTNativeProfilerStart(ctx, JSCProfileName);
+      } else {
+        RCTLogWarn(@"Trying to start JSC profiler on a context which is already profiled.");
+      }
+    } else {
+      RCTLogWarn(@"Cannot start JSC profiler as it's not supported.");
+    }
+  } else {
+    RCTLogWarn(@"Trying to start JSC profiler for NULL context.");
+  }
+#endif
+}
+
+NSString *RCTJSCProfilerStop(JSContextRef ctx)
+{
+  NSString *outputFile = nil;
+#if RCT_JSC_PROFILER
+  if (ctx != NULL) {
+    RCTJSCProfilerStateInit();
+    NSValue *key = RCTJSContextRefKey(ctx);
+    BOOL isProfiling = [RCTJSCProfilerStateMap[key] boolValue];
+    if (isProfiling) {
+      NSString *filename = [NSString stringWithFormat:@"cpu_profile_%ld.json", (long)CFAbsoluteTimeGetCurrent()];
+      outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
+      if (RCTNativeProfilerEnd) {
+        RCTNativeProfilerEnd(ctx, JSCProfileName, outputFile.UTF8String);
+      }
+      RCTLogInfo(@"Stopped JSC profiler for context: %p", ctx);
+    } else {
+      RCTLogWarn(@"Trying to stop JSC profiler on a context which is not being profiled.");
+    }
+    [RCTJSCProfilerStateMap removeObjectForKey:key];
+  } else {
+    RCTLogWarn(@"Trying to stop JSC profiler for NULL context.");
+  }
+#endif
+  return outputFile;
+}
+
+BOOL RCTJSCProfilerIsProfiling(JSContextRef ctx)
+{
+  BOOL isProfiling = NO;
+#if RCT_JSC_PROFILER
+  if (ctx != NULL) {
+    RCTJSCProfilerStateInit();
+    isProfiling = [RCTJSCProfilerStateMap[RCTJSContextRefKey(ctx)] boolValue];
+  }
+#endif
+  return isProfiling;
+}
+
+BOOL RCTJSCProfilerIsSupported(void)
+{
+  BOOL isSupported = NO;
+#if RCT_JSC_PROFILER
+  RCTJSCProfilerStateInit();
+  isSupported = (RCTNativeProfilerStart != NULL);
+#endif
+  return isSupported;
+}
diff --git a/React/Profiler/RCTPerfMonitor.m b/React/Profiler/RCTPerfMonitor.m
index e91360198e29..caf660385e8b 100644
--- a/React/Profiler/RCTPerfMonitor.m
+++ b/React/Profiler/RCTPerfMonitor.m
@@ -20,6 +20,7 @@
 #import "RCTFPSGraph.h"
 #import "RCTInvalidating.h"
 #import "RCTJavaScriptExecutor.h"
+#import "RCTJSCExecutor.h"
 #import "RCTPerformanceLogger.h"
 #import "RCTRootView.h"
 #import "RCTUIManager.h"
@@ -311,7 +312,7 @@ - (void)show
                        forMode:NSRunLoopCommonModes];
 
   id<RCTJavaScriptExecutor> executor = [_bridge valueForKey:@"javaScriptExecutor"];
-  if ([executor isKindOfClass:NSClassFromString(@"RCTContextExecutor")]) {
+  if ([executor isKindOfClass:[RCTJSCExecutor class]]) {
     self.container.frame = (CGRect) {
       self.container.frame.origin, {
         self.container.frame.size.width + 44,
@@ -500,12 +501,13 @@ - (void)threadUpdate:(CADisplayLink *)displayLink
 
 - (void)loadPerformanceLoggerData
 {
-  NSMutableArray *data = [NSMutableArray new];
-  NSArray *times = RCTPerformanceLoggerOutput();
   NSUInteger i = 0;
+  NSMutableArray<NSString *> *data = [NSMutableArray new];
+  NSArray<NSNumber *> *values = RCTPerformanceLoggerOutput();
   for (NSString *label in RCTPerformanceLoggerLabels()) {
-    [data addObject:[NSString stringWithFormat:@"%@: %lldus", label,
-                     [times[i+1] longLongValue] - [times[i] longLongValue]]];
+    long long value = values[i+1].longLongValue - values[i].longLongValue;
+    NSString *unit = [label isEqualToString:@"BundleSize"] ? @"b" : @"ms";
+    [data addObject:[NSString stringWithFormat:@"%@: %lld%@", label, value, unit]];
     i += 2;
   }
   _perfLoggerMarks = [data copy];
diff --git a/React/Profiler/RCTProfile.h b/React/Profiler/RCTProfile.h
index 5829a8eb4b93..2212e2cb1f19 100644
--- a/React/Profiler/RCTProfile.h
+++ b/React/Profiler/RCTProfile.h
@@ -16,8 +16,8 @@
  *
  * This file provides a set of functions and macros for performance profiling
  *
- * NOTE: This API is a work in a work in progress, please consider carefully
- * before before using it.
+ * NOTE: This API is a work in progress, please consider carefully before
+ * using it.
  */
 
 RCT_EXTERN NSString *const RCTProfileDidStartProfiling;
@@ -66,13 +66,16 @@ RCT_EXTERN void _RCTProfileBeginEvent(NSThread *calleeThread,
                                       uint64_t tag,
                                       NSString *name,
                                       NSDictionary *args);
-#define RCT_PROFILE_BEGIN_EVENT(...) { \
-  NSThread *calleeThread = [NSThread currentThread]; \
-  NSTimeInterval time = CACurrentMediaTime(); \
-  dispatch_async(RCTProfileGetQueue(), ^{ \
-    _RCTProfileBeginEvent(calleeThread, time, __VA_ARGS__); \
-  }); \
-}
+#define RCT_PROFILE_BEGIN_EVENT(...) \
+  do { \
+    if (RCTProfileIsProfiling()) { \
+      NSThread *__calleeThread = [NSThread currentThread]; \
+      NSTimeInterval __time = CACurrentMediaTime(); \
+      dispatch_async(RCTProfileGetQueue(), ^{ \
+        _RCTProfileBeginEvent(__calleeThread, __time, __VA_ARGS__); \
+      }); \
+    } \
+  } while(0)
 
 /**
  * The ID returned by BeginEvent should then be passed into EndEvent, with the
@@ -86,14 +89,17 @@ RCT_EXTERN void _RCTProfileEndEvent(NSThread *calleeThread,
                                     NSString *category,
                                     NSDictionary *args);
 
-#define RCT_PROFILE_END_EVENT(...) { \
-  NSThread *calleeThread = [NSThread currentThread]; \
-  NSString *threadName = RCTCurrentThreadName(); \
-  NSTimeInterval time = CACurrentMediaTime(); \
-  dispatch_async(RCTProfileGetQueue(), ^{ \
-    _RCTProfileEndEvent(calleeThread, threadName, time, __VA_ARGS__); \
-  }); \
-}
+#define RCT_PROFILE_END_EVENT(...) \
+  do { \
+    if (RCTProfileIsProfiling()) { \
+      NSThread *__calleeThread = [NSThread currentThread]; \
+      NSString *__threadName = RCTCurrentThreadName(); \
+      NSTimeInterval __time = CACurrentMediaTime(); \
+      dispatch_async(RCTProfileGetQueue(), ^{ \
+        _RCTProfileEndEvent(__calleeThread, __threadName, __time, __VA_ARGS__); \
+      }); \
+    } \
+  } while(0)
 
 /**
  * Collects the initial event information for the event and returns a reference ID
@@ -111,6 +117,7 @@ RCT_EXTERN void RCTProfileEndAsyncEvent(uint64_t tag,
                                         NSString *category,
                                         NSUInteger cookie,
                                         NSString *name,
+                                        NSString *threadName,
                                         NSDictionary *args);
 
 /**
@@ -118,6 +125,7 @@ RCT_EXTERN void RCTProfileEndAsyncEvent(uint64_t tag,
  */
 RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag,
                                          NSString *name,
+                                         NSTimeInterval time,
                                          char scope);
 
 /**
@@ -183,7 +191,7 @@ RCT_EXTERN void RCTProfileRegisterCallbacks(RCTProfileCallbacks *);
 #define _RCTProfileBeginFlowEvent() @0
 
 #define RCTProfileEndFlowEvent()
-#define _RCTProfileEndFlowEvent()
+#define _RCTProfileEndFlowEvent(...)
 
 #define RCTProfileIsProfiling(...) NO
 #define RCTProfileInit(...)
diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m
index db23fb8c200e..14dbefb1f140 100644
--- a/React/Profiler/RCTProfile.m
+++ b/React/Profiler/RCTProfile.m
@@ -20,10 +20,14 @@
 
 #import "RCTAssert.h"
 #import "RCTBridge.h"
+#import "RCTBridge+Private.h"
+#import "RCTComponentData.h"
 #import "RCTDefines.h"
 #import "RCTLog.h"
 #import "RCTModuleData.h"
 #import "RCTUtils.h"
+#import "RCTUIManager.h"
+#import "RCTJSCExecutor.h"
 
 NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
 NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
@@ -32,8 +36,8 @@
 
 #pragma mark - Constants
 
-NSString const *RCTProfileTraceEvents = @"traceEvents";
-NSString const *RCTProfileSamples = @"samples";
+NSString *const RCTProfileTraceEvents = @"traceEvents";
+NSString *const RCTProfileSamples = @"samples";
 NSString *const RCTProfilePrefix = @"rct_profile_";
 
 #pragma mark - Variables
@@ -41,11 +45,11 @@
 // This is actually a BOOL - but has to be compatible with OSAtomic
 static volatile uint32_t RCTProfileProfiling;
 
-static BOOL RCTProfileHookedModules;
 static NSDictionary *RCTProfileInfo;
 static NSMutableDictionary *RCTProfileOngoingEvents;
 static NSTimeInterval RCTProfileStartTime;
 static NSUInteger RCTProfileEventID = 0;
+static CADisplayLink *RCTProfileDisplayLink;
 
 #pragma mark - Macros
 
@@ -78,7 +82,7 @@
     systrace_args[i].key = keyc;
     systrace_args[i].key_len = (int)strlen(keyc);
 
-    const char *valuec = RCTJSONStringify(value, nil).UTF8String;
+    const char *valuec = RCTJSONStringify(value, NULL).UTF8String;
     systrace_args[i].value = valuec;
     systrace_args[i].value_len = (int)strlen(valuec);
     i++;
@@ -174,9 +178,9 @@ IMP RCTProfileGetImplementation(id obj, SEL cmd)
  * state, call the actual function we want to profile and stop the profiler.
  *
  * The implementation can be found in RCTProfileTrampoline-<arch>.s where arch
- * is one of: x86, x86_64, arm, arm64.
+ * is one of: i386, x86_64, arm, arm64.
  */
-#if defined(__x86__) || \
+#if defined(__i386__) || \
     defined(__x86_64__) || \
     defined(__arm__) || \
     defined(__arm64__)
@@ -189,7 +193,12 @@ IMP RCTProfileGetImplementation(id obj, SEL cmd)
 RCT_EXTERN void RCTProfileTrampolineStart(id, SEL);
 void RCTProfileTrampolineStart(id self, SEL cmd)
 {
-  RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName([self class]), sel_getName(cmd)], nil);
+  /**
+   * This call might be during dealloc, so we shouldn't retain the object in the
+   * block.
+   */
+  Class klass = [self class];
+  RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)], nil);
 }
 
 RCT_EXTERN void RCTProfileTrampolineEnd(void);
@@ -198,74 +207,151 @@ void RCTProfileTrampolineEnd(void)
   RCT_PROFILE_END_EVENT(0, @"objc_call,modules,auto", nil);
 }
 
+static void RCTProfileHookInstance(id instance)
+{
+  Class moduleClass = object_getClass(instance);
+
+  /**
+   * We swizzle the instance -class method to return the original class, but
+   * object_getClass will return the actual class.
+   *
+   * If they are different, it means that the object is returning the original
+   * class, but it's actual class is the proxy subclass we created.
+   */
+  if ([instance class] != moduleClass) {
+    return;
+  }
+
+  Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
+
+  if (!proxyClass) {
+    proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass));
+    if (proxyClass) {
+      object_setClass(instance, proxyClass);
+    }
+    return;
+  }
+
+  unsigned int methodCount;
+  Method *methods = class_copyMethodList(moduleClass, &methodCount);
+  for (NSUInteger i = 0; i < methodCount; i++) {
+    Method method = methods[i];
+    SEL selector = method_getName(method);
+
+    /**
+     * Bail out on struct returns (except arm64) - we don't use it enough
+     * to justify writing a stret version
+     */
+#ifdef __arm64__
+    BOOL returnsStruct = NO;
+#else
+    const char *typeEncoding = method_getTypeEncoding(method);
+    // bail out on structs and unions (since they might contain structs)
+    BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '(';
+#endif
+
+    /**
+     * Avoid hooking into NSObject methods, methods generated by React Native
+     * and special methods that start `.` (e.g. .cxx_destruct)
+     */
+    if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || sel_getName(selector)[0] == '.' || returnsStruct) {
+      continue;
+    }
+
+    const char *types = method_getTypeEncoding(method);
+    class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types);
+  }
+  free(methods);
+
+  class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:");
+
+  for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
+    Method oldImp = class_getInstanceMethod(cls, @selector(class));
+    class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));
+  }
+
+  objc_registerClassPair(proxyClass);
+  object_setClass(instance, proxyClass);
+}
+
+static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *);
+
+RCT_EXTERN UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag);
+UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag)
+{
+  UIView *view = originalCreateView(self, _cmd, tag);
+
+  RCTProfileHookInstance(view);
+
+  return view;
+}
+
 void RCTProfileHookModules(RCTBridge *bridge)
 {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wtautological-pointer-compare"
-  if (RCTProfileTrampoline == NULL || RCTProfileHookedModules) {
+  if (RCTProfileTrampoline == NULL) {
     return;
   }
 #pragma clang diagnostic pop
 
-  RCTProfileHookedModules = YES;
-
   for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) {
-    [moduleData dispatchBlock:^{
-      Class moduleClass = moduleData.moduleClass;
-      Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0);
-
-      if (!proxyClass) {
-        return;
-      }
-
-      unsigned int methodCount;
-      Method *methods = class_copyMethodList(moduleClass, &methodCount);
-      for (NSUInteger i = 0; i < methodCount; i++) {
-        Method method = methods[i];
-        SEL selector = method_getName(method);
-        if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) {
-          continue;
-        }
-        const char *types = method_getTypeEncoding(method);
-
-        class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types);
-      }
-      free(methods);
-
-      class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:");
-
-      for (Class cls in @[proxyClass, object_getClass(proxyClass)]) {
-        Method oldImp = class_getInstanceMethod(cls, @selector(class));
-        class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp));
-      }
-
-      objc_registerClassPair(proxyClass);
-      object_setClass(moduleData.instance, proxyClass);
-    }];
+    [bridge dispatchBlock:^{
+      RCTProfileHookInstance(moduleData.instance);
+    } queue:moduleData.methodQueue];
   }
+
+  dispatch_async(dispatch_get_main_queue(), ^{
+    for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) {
+      RCTProfileHookInstance([bridge.uiManager viewForReactTag:view]);
+    }
+
+    Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:));
+
+    if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) {
+      originalCreateView = (typeof(originalCreateView))method_getImplementation(createView);
+      method_setImplementation(createView, (IMP)RCTProfileCreateView);
+    }
+  });
 }
 
-void RCTProfileUnhookModules(RCTBridge *bridge)
+static void RCTProfileUnhookInstance(id instance)
 {
-  if (!RCTProfileHookedModules) {
-    return;
+  if ([instance class] != object_getClass(instance)) {
+    object_setClass(instance, [instance class]);
   }
+}
 
-  RCTProfileHookedModules = NO;
-
+void RCTProfileUnhookModules(RCTBridge *bridge)
+{
   dispatch_group_enter(RCTProfileGetUnhookGroup());
 
   for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) {
-    Class proxyClass = object_getClass(moduleData.instance);
-    if (moduleData.moduleClass != proxyClass) {
-      object_setClass(moduleData.instance, moduleData.moduleClass);
-      objc_disposeClassPair(proxyClass);
-    }
+    RCTProfileUnhookInstance(moduleData.instance);
   }
 
-  dispatch_group_leave(RCTProfileGetUnhookGroup());
+  dispatch_async(dispatch_get_main_queue(), ^{
+    for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) {
+      RCTProfileUnhookInstance(view);
+    }
+
+    dispatch_group_leave(RCTProfileGetUnhookGroup());
+  });
+}
+
+#pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target
+
+@interface RCTProfile : NSObject
+@end
+
+@implementation RCTProfile
+
++ (void)vsync:(CADisplayLink *)displayLink
+{
+  RCTProfileImmediateEvent(0, @"VSYNC", displayLink.timestamp, 'g');
 }
 
+@end
 
 #pragma mark - Public Functions
 
@@ -287,7 +373,6 @@ BOOL RCTProfileIsProfiling(void)
 void RCTProfileInit(RCTBridge *bridge)
 {
   // TODO: enable assert JS thread from any file (and assert here)
-
   if (RCTProfileIsProfiling()) {
     return;
   }
@@ -310,10 +395,29 @@ void RCTProfileInit(RCTBridge *bridge)
     });
   }
 
+  // Set up thread ordering
+  dispatch_async(RCTProfileGetQueue(), ^{
+    NSString *shadowQueue = @(dispatch_queue_get_label([[bridge uiManager] methodQueue]));
+    NSArray *orderedThreads = @[@"JS async", RCTJSCThreadName, shadowQueue, @"main"];
+    [orderedThreads enumerateObjectsUsingBlock:^(NSString *thread, NSUInteger idx, __unused BOOL *stop) {
+      RCTProfileAddEvent(RCTProfileTraceEvents,
+        @"ph": @"M", // metadata event
+        @"name": @"thread_sort_index",
+        @"tid": thread,
+        @"args": @{ @"sort_index": @(-1000 + (NSInteger)idx) }
+      );
+    }];
+  });
+
   RCTProfileHookModules(bridge);
 
+  RCTProfileDisplayLink = [CADisplayLink displayLinkWithTarget:[RCTProfile class]
+                                                      selector:@selector(vsync:)];
+  [RCTProfileDisplayLink addToRunLoop:[NSRunLoop mainRunLoop]
+                              forMode:NSRunLoopCommonModes];
+
   [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidStartProfiling
-                                                      object:nil];
+                                                      object:bridge];
 }
 
 void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *))
@@ -327,7 +431,10 @@ void RCTProfileEnd(RCTBridge *bridge, void (^callback)(NSString *))
   OSAtomicAnd32Barrier(0, &RCTProfileProfiling);
 
   [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling
-                                                      object:nil];
+                                                      object:bridge];
+
+  [RCTProfileDisplayLink invalidate];
+  RCTProfileDisplayLink = nil;
 
   RCTProfileUnhookModules(bridge);
 
@@ -379,7 +486,6 @@ void _RCTProfileBeginEvent(
   NSMutableArray *events = RCTProfileGetThreadEvents(calleeThread);
   [events addObject:@[
     RCTProfileTimestamp(time),
-    @(tag),
     name,
     RCTNullIfNil(args),
   ]];
@@ -414,12 +520,12 @@ void _RCTProfileEndEvent(
 
   RCTProfileAddEvent(RCTProfileTraceEvents,
     @"tid": threadName,
-    @"name": event[2],
+    @"name": event[1],
     @"cat": category,
     @"ph": @"X",
     @"ts": start,
     @"dur": @(RCTProfileTimestamp(time).doubleValue - start.doubleValue),
-    @"args": RCTProfileMergeArgs(event[3], args),
+    @"args": RCTProfileMergeArgs(event[2], args),
   );
 }
 
@@ -455,6 +561,7 @@ void RCTProfileEndAsyncEvent(
   NSString *category,
   NSUInteger cookie,
   NSString *name,
+  NSString *threadName,
   NSDictionary *args
 ) {
   CHECK();
@@ -465,7 +572,6 @@ void RCTProfileEndAsyncEvent(
   }
 
   NSTimeInterval time = CACurrentMediaTime();
-  NSString *threadName = RCTCurrentThreadName();
 
   dispatch_async(RCTProfileGetQueue(), ^{
     NSArray *event = RCTProfileOngoingEvents[@(cookie)];
@@ -490,6 +596,7 @@ void RCTProfileEndAsyncEvent(
 void RCTProfileImmediateEvent(
   uint64_t tag,
   NSString *name,
+  NSTimeInterval time,
   char scope
 ) {
   CHECK();
@@ -499,7 +606,6 @@ void RCTProfileImmediateEvent(
     return;
   }
 
-  NSTimeInterval time = CACurrentMediaTime();
   NSString *threadName = RCTCurrentThreadName();
 
   dispatch_async(RCTProfileGetQueue(), ^{
@@ -570,7 +676,7 @@ void _RCTProfileEndFlowEvent(NSNumber *flowID)
 void RCTProfileSendResult(RCTBridge *bridge, NSString *route, NSData *data)
 {
   if (![bridge.bundleURL.scheme hasPrefix:@"http"]) {
-    RCTLogError(@"Cannot upload profile information");
+    RCTLogWarn(@"Cannot upload profile information because you're not connected to the packager. The profiling data is still saved in the app container.");
     return;
   }
 
diff --git a/React/Profiler/RCTProfileTrampoline-arm.S b/React/Profiler/RCTProfileTrampoline-arm.S
index 4835d261adcb..61c32df92855 100644
--- a/React/Profiler/RCTProfileTrampoline-arm.S
+++ b/React/Profiler/RCTProfileTrampoline-arm.S
@@ -1,3 +1,12 @@
+/**
+ * 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.
+ */
+
 #include "RCTDefines.h"
 #include "RCTMacros.h"
 
diff --git a/React/Profiler/RCTProfileTrampoline-arm64.S b/React/Profiler/RCTProfileTrampoline-arm64.S
index 92ea42a833f0..435d86a4d517 100644
--- a/React/Profiler/RCTProfileTrampoline-arm64.S
+++ b/React/Profiler/RCTProfileTrampoline-arm64.S
@@ -1,3 +1,12 @@
+/**
+ * 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.
+ */
+
 #include "RCTDefines.h"
 #include "RCTMacros.h"
 
diff --git a/React/Profiler/RCTProfileTrampoline-x86.S b/React/Profiler/RCTProfileTrampoline-i386.S
similarity index 90%
rename from React/Profiler/RCTProfileTrampoline-x86.S
rename to React/Profiler/RCTProfileTrampoline-i386.S
index 0ccd37b4fe09..7aed481325a2 100644
--- a/React/Profiler/RCTProfileTrampoline-x86.S
+++ b/React/Profiler/RCTProfileTrampoline-i386.S
@@ -1,3 +1,12 @@
+/**
+ * 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.
+ */
+
 #include "RCTDefines.h"
 #include "RCTMacros.h"
 
diff --git a/React/Profiler/RCTProfileTrampoline-x86_64.S b/React/Profiler/RCTProfileTrampoline-x86_64.S
index 2f9d17786290..6d9c93fb7cd0 100644
--- a/React/Profiler/RCTProfileTrampoline-x86_64.S
+++ b/React/Profiler/RCTProfileTrampoline-x86_64.S
@@ -1,11 +1,17 @@
+/**
+ * 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.
+ */
+
 #include "RCTDefines.h"
 #include "RCTMacros.h"
 
 #if RCT_DEV && defined(__x86_64__)
 
-/**
- * Define both symbols for compatibility with other assemblers
- */
   .globl SYMBOL_NAME(RCTProfileTrampoline)
 SYMBOL_NAME(RCTProfileTrampoline):
 
@@ -136,21 +142,14 @@ SYMBOL_NAME(RCTProfileTrampoline):
   // call the actual function and save the return value
   callq *%r11
   pushq %rax
-  subq $0x10+8, %rsp //16-bytes xmm register + 8-bytes for alignment
-  movdqa %xmm0, (%rsp)
-
-  // align stack
-  pushq %r12
-  movq %rsp, %r12
-  andq $-0x10, %rsp
+  pushq %rdx
+  subq $0x20, %rsp // 2 16-bytes xmm register
+  movdqa %xmm0, 0x00(%rsp)
+  movdqa %xmm1, 0x10(%rsp)
 
   // void RCTProfileTrampolineEnd(void) in RCTProfile.m - just ends this profile
   callq SYMBOL_NAME(RCTProfileTrampolineEnd)
 
-  // unalign stack and restore %r12
-  movq %r12, %rsp
-  popq %r12
-
   /**
    * Restore the initial value of the callee saved registers, saved in the
    * memory allocated.
@@ -161,7 +160,7 @@ SYMBOL_NAME(RCTProfileTrampoline):
   movq 0x8(%r14), %r14
 
   /**
-   * Save caller address and actual function return (previously in the allocated
+   * save caller address and actual function return (previously in the allocated
    * memory) and align the stack
    */
   pushq %rcx
@@ -177,12 +176,14 @@ SYMBOL_NAME(RCTProfileTrampoline):
   popq %r12
 
   /**
-   * pop the caller address to %rcx and the actual function return value to
-   * %rax, so it's the return value of RCTProfileTrampoline
+   * pop the caller address to %rcx and the actual function return value(s)
+   * so it's the return value of RCTProfileTrampoline
    */
   popq %rcx
-  movdqa (%rsp), %xmm0
-  addq $0x10+8, %rsp
+  movdqa 0x00(%rsp), %xmm0
+  movdqa 0x10(%rsp), %xmm1
+  addq $0x20, %rsp
+  popq %rdx
   popq %rax
 
   // jump to caller
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 973e917618c7..30a1710b4a48 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -13,7 +13,7 @@
 		133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; };
 		13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
 		13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
-		134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
+		134FCB3D1A6E7F0800051CC8 /* RCTJSCExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTJSCExecutor.m */; };
 		13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; };
 		13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; };
 		1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; };
@@ -21,12 +21,13 @@
 		137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; };
 		137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; };
 		137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; };
-		1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 1385D0331B665AAE000A309B /* RCTModuleMap.m */; };
 		13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */; };
 		13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A0C2881B74F71200B29F6F /* RCTDevMenu.m */; };
 		13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; };
+		13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */; };
 		13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AB90C01B6FA36700713B4F /* RCTComponentData.m */; };
 		13AF20451AE707F9005F5298 /* RCTSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AF20441AE707F9005F5298 /* RCTSlider.m */; };
+		13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */; };
 		13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; };
 		13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; };
 		13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEE1A69327A00A75B9A /* RCTTiming.m */; };
@@ -38,16 +39,18 @@
 		13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; };
 		13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; };
 		13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; };
-		13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; };
-		13B202041BFB948C00C07393 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTPointAnnotation.m */; };
+		13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; };
+		13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; };
 		13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; };
 		13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; };
 		13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */ = {isa = PBXBuildFile; fileRef = 13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */; };
+		13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D033621C1837FE0021DC29 /* RCTClipboard.m */; };
 		13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; };
 		13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; };
 		13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
 		13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
 		13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
+		13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */ = {isa = PBXBuildFile; fileRef = 14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */; };
 		13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 13F17A841B8493E5007D4C75 /* RCTRedBox.m */; };
 		14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; };
 		142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; };
@@ -56,8 +59,8 @@
 		1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF811BCFF28A00208362 /* RCTProfile.m */; };
 		1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */; };
 		1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */; };
-		1450FF891BCFF28A00208362 /* RCTProfileTrampoline-x86.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */; };
 		1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */ = {isa = PBXBuildFile; fileRef = 1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */; };
+		14A43DF31C20B1C900794BC8 /* RCTJSCProfiler.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A43DF21C20B1C900794BC8 /* RCTJSCProfiler.m */; };
 		14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */; };
 		14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */; };
 		14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */; };
@@ -67,6 +70,9 @@
 		14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
 		14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */; };
 		14F7A0F01BDA714B003C6C10 /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */; };
+		191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */; };
+		191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */; };
+		391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 391E86A21C623EC800009732 /* RCTTouchEvent.m */; };
 		58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; };
 		58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
 		58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; };
@@ -118,8 +124,8 @@
 		13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+MapKit.m"; sourceTree = "<group>"; };
 		1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestDelegate.h; sourceTree = "<group>"; };
 		1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTURLRequestHandler.h; sourceTree = "<group>"; };
-		134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTContextExecutor.h; sourceTree = "<group>"; };
-		134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; };
+		134FCB391A6E7F0800051CC8 /* RCTJSCExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSCExecutor.h; sourceTree = "<group>"; };
+		134FCB3A1A6E7F0800051CC8 /* RCTJSCExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSCExecutor.m; sourceTree = "<group>"; };
 		13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProgressViewManager.h; sourceTree = "<group>"; };
 		13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProgressViewManager.m; sourceTree = "<group>"; };
 		13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = "<group>"; };
@@ -134,19 +140,24 @@
 		137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = "<group>"; };
 		137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = "<group>"; };
 		137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = "<group>"; };
-		1385D0331B665AAE000A309B /* RCTModuleMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMap.m; sourceTree = "<group>"; };
-		1385D0351B6661DB000A309B /* RCTModuleMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTModuleMap.h; sourceTree = "<group>"; };
 		13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevLoadingView.h; sourceTree = "<group>"; };
 		13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevLoadingView.m; sourceTree = "<group>"; };
 		13A0C2871B74F71200B29F6F /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = "<group>"; };
 		13A0C2881B74F71200B29F6F /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = "<group>"; };
 		13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = "<group>"; };
 		13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = "<group>"; };
+		13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTParserUtils.h; sourceTree = "<group>"; };
+		13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTParserUtils.m; sourceTree = "<group>"; };
+		13A6E20F1C19ABC700845B82 /* RCTNullability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNullability.h; sourceTree = "<group>"; };
 		13AB90BF1B6FA36700713B4F /* RCTComponentData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponentData.h; sourceTree = "<group>"; };
 		13AB90C01B6FA36700713B4F /* RCTComponentData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTComponentData.m; sourceTree = "<group>"; };
 		13AF1F851AE6E777005F5298 /* RCTDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDefines.h; sourceTree = "<group>"; };
 		13AF20431AE707F8005F5298 /* RCTSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSlider.h; sourceTree = "<group>"; };
 		13AF20441AE707F9005F5298 /* RCTSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSlider.m; sourceTree = "<group>"; };
+		13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapOverlay.h; sourceTree = "<group>"; };
+		13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapOverlay.m; sourceTree = "<group>"; };
+		13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridgeMethod.h; sourceTree = "<group>"; };
+		13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootViewDelegate.h; sourceTree = "<group>"; };
 		13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = "<group>"; };
 		13B07FC81A68125100A75B9A /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = "<group>"; };
 		13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = "<group>"; };
@@ -171,10 +182,10 @@
 		13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = "<group>"; };
 		13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = "<group>"; };
 		13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = "<group>"; };
-		13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = "<group>"; };
-		13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = "<group>"; };
-		13B202021BFB948C00C07393 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = "<group>"; };
-		13B202031BFB948C00C07393 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = "<group>"; };
+		13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = "<group>"; };
+		13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = "<group>"; };
+		13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = "<group>"; };
+		13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = "<group>"; };
 		13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = "<group>"; };
 		13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = "<group>"; };
 		13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = "<group>"; };
@@ -184,6 +195,8 @@
 		13C325281AA63B6A0048765F /* RCTComponent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTComponent.h; sourceTree = "<group>"; };
 		13CC8A801B17642100940AE7 /* RCTBorderDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderDrawing.h; sourceTree = "<group>"; };
 		13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBorderDrawing.m; sourceTree = "<group>"; };
+		13D033611C1837FE0021DC29 /* RCTClipboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTClipboard.h; sourceTree = "<group>"; };
+		13D033621C1837FE0021DC29 /* RCTClipboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipboard.m; sourceTree = "<group>"; };
 		13E067481A70F434002CDEE1 /* RCTUIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManager.h; sourceTree = "<group>"; };
 		13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = "<group>"; };
 		13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = "<group>"; };
@@ -210,9 +223,13 @@
 		1450FF811BCFF28A00208362 /* RCTProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProfile.m; sourceTree = "<group>"; };
 		1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm.S"; sourceTree = "<group>"; };
 		1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-arm64.S"; sourceTree = "<group>"; };
-		1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86.S"; sourceTree = "<group>"; };
 		1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-x86_64.S"; sourceTree = "<group>"; };
 		1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeDelegate.h; sourceTree = "<group>"; };
+		14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RCTBridge+Private.h"; sourceTree = "<group>"; };
+		14A43DF11C20B1C900794BC8 /* RCTJSCProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSCProfiler.h; sourceTree = "<group>"; };
+		14A43DF21C20B1C900794BC8 /* RCTJSCProfiler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSCProfiler.m; sourceTree = "<group>"; };
+		14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; path = "RCTProfileTrampoline-i386.S"; sourceTree = "<group>"; };
+		14BF71811C04795500C97D0C /* RCTMacros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMacros.h; sourceTree = "<group>"; };
 		14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleMethod.h; sourceTree = "<group>"; };
 		14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethod.m; sourceTree = "<group>"; };
 		14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTModuleData.h; sourceTree = "<group>"; };
@@ -228,6 +245,14 @@
 		14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfMonitor.m; sourceTree = "<group>"; };
 		14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; };
 		14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; };
+		191E3EBC1C29D9AF00C180A6 /* RCTRefreshControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControlManager.h; sourceTree = "<group>"; };
+		191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControlManager.m; sourceTree = "<group>"; };
+		191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRefreshControl.h; sourceTree = "<group>"; };
+		191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRefreshControl.m; sourceTree = "<group>"; };
+		391E86A21C623EC800009732 /* RCTTouchEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchEvent.m; sourceTree = "<group>"; };
+		391E86A31C623EC800009732 /* RCTTouchEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchEvent.h; sourceTree = "<group>"; };
+		3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxy.h; sourceTree = "<group>"; };
+		3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebSocketProxyDelegate.h; sourceTree = "<group>"; };
 		58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = "<group>"; };
 		58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = "<group>"; };
 		58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = "<group>"; };
@@ -266,6 +291,7 @@
 		83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; };
 		83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = "<group>"; };
 		83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = "<group>"; };
+		ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderStyle.h; sourceTree = "<group>"; };
 		E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = "<group>"; };
 		E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAccessibilityManager.h; sourceTree = "<group>"; };
 		E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAccessibilityManager.m; sourceTree = "<group>"; };
@@ -285,8 +311,8 @@
 		134FCB381A6E7F0800051CC8 /* Executors */ = {
 			isa = PBXGroup;
 			children = (
-				134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */,
-				134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */,
+				134FCB391A6E7F0800051CC8 /* RCTJSCExecutor.h */,
+				134FCB3A1A6E7F0800051CC8 /* RCTJSCExecutor.m */,
 			);
 			path = Executors;
 			sourceTree = "<group>";
@@ -311,14 +337,14 @@
 				1372B7091AB030C200659ED6 /* RCTAppState.m */,
 				58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */,
 				58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */,
+				13D033611C1837FE0021DC29 /* RCTClipboard.h */,
+				13D033621C1837FE0021DC29 /* RCTClipboard.m */,
 				13A0C2851B74F71200B29F6F /* RCTDevLoadingView.h */,
 				13A0C2861B74F71200B29F6F /* RCTDevLoadingView.m */,
 				13A0C2871B74F71200B29F6F /* RCTDevMenu.h */,
 				13A0C2881B74F71200B29F6F /* RCTDevMenu.m */,
 				13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
 				13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */,
-				13B201FF1BFB945300C07393 /* RCTPasteboard.h */,
-				13B202001BFB945300C07393 /* RCTPasteboard.m */,
 				13F17A831B8493E5007D4C75 /* RCTRedBox.h */,
 				13F17A841B8493E5007D4C75 /* RCTRedBox.m */,
 				000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */,
@@ -344,11 +370,11 @@
 				13CC8A811B17642100940AE7 /* RCTBorderDrawing.m */,
 				13C325281AA63B6A0048765F /* RCTComponent.h */,
 				13AB90BF1B6FA36700713B4F /* RCTComponentData.h */,
+				13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
 				13AB90C01B6FA36700713B4F /* RCTComponentData.m */,
 				13EF7F441BC69646003F47DD /* RCTImageComponent.h */,
 				13456E911ADAD2DE009F94A7 /* RCTConvert+CoreLocation.h */,
 				13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
-				13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
 				13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
 				133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */,
 				133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */,
@@ -356,6 +382,10 @@
 				58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
 				14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
 				14435CE21AAC4AE100FC20F4 /* RCTMap.m */,
+				13B202021BFB948C00C07393 /* RCTMapAnnotation.h */,
+				13B202031BFB948C00C07393 /* RCTMapAnnotation.m */,
+				13AFBC9E1C07247D00BBAEAA /* RCTMapOverlay.h */,
+				13AFBC9F1C07247D00BBAEAA /* RCTMapOverlay.m */,
 				14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */,
 				14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */,
 				83A1FE8A1B62640A00BE0E65 /* RCTModalHostView.h */,
@@ -364,8 +394,6 @@
 				83392EB21B6634E10013B15F /* RCTModalHostViewController.m */,
 				83A1FE8D1B62643A00BE0E65 /* RCTModalHostViewManager.h */,
 				83A1FE8E1B62643A00BE0E65 /* RCTModalHostViewManager.m */,
-				13B202021BFB948C00C07393 /* RCTPointAnnotation.h */,
-				13B202031BFB948C00C07393 /* RCTPointAnnotation.m */,
 				13B0800C1A69489C00A75B9A /* RCTNavigator.h */,
 				13B0800D1A69489C00A75B9A /* RCTNavigator.m */,
 				13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */,
@@ -381,6 +409,10 @@
 				13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
 				13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */,
 				13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */,
+				191E3EBF1C29DC3800C180A6 /* RCTRefreshControl.h */,
+				191E3EC01C29DC3800C180A6 /* RCTRefreshControl.m */,
+				191E3EBC1C29D9AF00C180A6 /* RCTRefreshControlManager.h */,
+				191E3EBD1C29D9AF00C180A6 /* RCTRefreshControlManager.m */,
 				13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */,
 				13B07FF61A6947C200A75B9A /* RCTScrollView.h */,
 				13B07FF71A6947C200A75B9A /* RCTScrollView.m */,
@@ -409,6 +441,7 @@
 				137327E51AA5CF210034F82E /* RCTTabBarManager.h */,
 				137327E61AA5CF210034F82E /* RCTTabBarManager.m */,
 				E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */,
+				ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */,
 				13E0674F1A70F44B002CDEE1 /* RCTView.h */,
 				13E067501A70F44B002CDEE1 /* RCTView.m */,
 				13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */,
@@ -430,6 +463,9 @@
 		1450FF7F1BCFF28A00208362 /* Profiler */ = {
 			isa = PBXGroup;
 			children = (
+				14A43DF11C20B1C900794BC8 /* RCTJSCProfiler.h */,
+				14A43DF21C20B1C900794BC8 /* RCTJSCProfiler.m */,
+				14BF71811C04795500C97D0C /* RCTMacros.h */,
 				14F7A0EB1BDA3B3C003C6C10 /* RCTPerfMonitor.m */,
 				14F7A0EE1BDA714B003C6C10 /* RCTFPSGraph.h */,
 				14F7A0EF1BDA714B003C6C10 /* RCTFPSGraph.m */,
@@ -437,7 +473,7 @@
 				1450FF811BCFF28A00208362 /* RCTProfile.m */,
 				1450FF821BCFF28A00208362 /* RCTProfileTrampoline-arm.S */,
 				1450FF831BCFF28A00208362 /* RCTProfileTrampoline-arm64.S */,
-				1450FF841BCFF28A00208362 /* RCTProfileTrampoline-x86.S */,
+				14BF717F1C04793D00C97D0C /* RCTProfileTrampoline-i386.S */,
 				1450FF851BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S */,
 			);
 			path = Profiler;
@@ -477,13 +513,14 @@
 		83CBBA491A601E3B00E9B192 /* Base */ = {
 			isa = PBXGroup;
 			children = (
-				6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */,
 				83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
 				83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
 				14C2CA771B3ACB0400E6CBB2 /* RCTBatchedBridge.m */,
 				83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
 				83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */,
+				14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */,
 				1482F9E61B55B927000ADFF3 /* RCTBridgeDelegate.h */,
+				13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */,
 				830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
 				83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
 				83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
@@ -492,6 +529,8 @@
 				83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */,
 				1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
 				14C2CA751B3AC64F00E6CBB2 /* RCTFrameUpdate.m */,
+				13BB3D001BECD54500932C10 /* RCTImageSource.h */,
+				13BB3D011BECD54500932C10 /* RCTImageSource.m */,
 				83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */,
 				83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */,
 				14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */,
@@ -504,20 +543,27 @@
 				83CBBA4E1A601E3B00E9B192 /* RCTLog.m */,
 				14C2CA721B3AC64300E6CBB2 /* RCTModuleData.h */,
 				14C2CA731B3AC64300E6CBB2 /* RCTModuleData.m */,
-				1385D0351B6661DB000A309B /* RCTModuleMap.h */,
-				1385D0331B665AAE000A309B /* RCTModuleMap.m */,
 				14C2CA6F1B3AC63800E6CBB2 /* RCTModuleMethod.h */,
 				14C2CA701B3AC63800E6CBB2 /* RCTModuleMethod.m */,
+				13A6E20F1C19ABC700845B82 /* RCTNullability.h */,
+				13A6E20C1C19AA0C00845B82 /* RCTParserUtils.h */,
+				13A6E20D1C19AA0C00845B82 /* RCTParserUtils.m */,
 				142014181B32094000CC17BA /* RCTPerformanceLogger.h */,
 				142014171B32094000CC17BA /* RCTPerformanceLogger.m */,
 				830A229C1A66C68A008503DA /* RCTRootView.h */,
 				830A229D1A66C68A008503DA /* RCTRootView.m */,
+				13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */,
+				6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */,
+				391E86A31C623EC800009732 /* RCTTouchEvent.h */,
+				391E86A21C623EC800009732 /* RCTTouchEvent.m */,
 				83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
 				83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
 				1345A83A1B265A0E00583190 /* RCTURLRequestDelegate.h */,
 				1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
 				83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
 				83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
+				3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */,
+				3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */,
 			);
 			path = Base;
 			sourceTree = "<group>";
@@ -588,7 +634,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "if nc -w 5 -z localhost 8081 ; then\n  if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n    echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n    exit 2\n  fi\nelse\n  open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi";
+			shellScript = "if nc -w 5 -z localhost 8081 ; then\n  if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n    echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n    exit 2\n  fi\nelse\n  open \"$SRCROOT/../packager/launchPackager.command\" || echo \"Can't start packager automatically\"\nfi";
 		};
 		142C4F7F1B582EA6001F0B58 /* ShellScript */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -614,15 +660,15 @@
 				13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */,
 				13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
 				000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
+				14A43DF31C20B1C900794BC8 /* RCTJSCProfiler.m in Sources */,
 				133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */,
-				13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */,
 				14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
 				13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
 				83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
 				131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
 				830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
 				13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
-				13B202041BFB948C00C07393 /* RCTPointAnnotation.m in Sources */,
+				13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */,
 				13A0C28A1B74F71200B29F6F /* RCTDevMenu.m in Sources */,
 				14C2CA711B3AC63800E6CBB2 /* RCTModuleMethod.m in Sources */,
 				13CC8A821B17642100940AE7 /* RCTBorderDrawing.m in Sources */,
@@ -637,10 +683,12 @@
 				14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
 				13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */,
 				13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
+				13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */,
 				58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
 				1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */,
 				14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */,
 				1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */,
+				13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */,
 				13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
 				14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */,
 				137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
@@ -650,21 +698,25 @@
 				13A0C2891B74F71200B29F6F /* RCTDevLoadingView.m in Sources */,
 				13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */,
 				1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
-				134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
+				134FCB3D1A6E7F0800051CC8 /* RCTJSCExecutor.m in Sources */,
 				14C2CA781B3ACB0400E6CBB2 /* RCTBatchedBridge.m in Sources */,
 				13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
 				14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
+				13D033631C1837FE0021DC29 /* RCTClipboard.m in Sources */,
 				14C2CA741B3AC64300E6CBB2 /* RCTModuleData.m in Sources */,
 				142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */,
 				83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
 				83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
 				13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
+				13A6E20E1C19AA0C00845B82 /* RCTParserUtils.m in Sources */,
 				13E067571A70F44B002CDEE1 /* RCTView.m in Sources */,
+				13AFBCA01C07247D00BBAEAA /* RCTMapOverlay.m in Sources */,
 				13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */,
 				137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */,
 				13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
 				83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
 				14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
+				191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */,
 				13C156051AB1A2840079392D /* RCTWebView.m in Sources */,
 				83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
 				13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
@@ -675,17 +727,17 @@
 				1450FF871BCFF28A00208362 /* RCTProfileTrampoline-arm.S in Sources */,
 				131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */,
 				58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
+				191E3EBE1C29D9AF00C180A6 /* RCTRefreshControlManager.m in Sources */,
 				13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
 				137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
 				13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */,
 				83392EB31B6634E10013B15F /* RCTModalHostViewController.m in Sources */,
 				14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
-				1450FF891BCFF28A00208362 /* RCTProfileTrampoline-x86.S in Sources */,
 				13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
-				1385D0341B665AAE000A309B /* RCTModuleMap.m in Sources */,
 				83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
 				83A1FE8F1B62643A00BE0E65 /* RCTModalHostViewManager.m in Sources */,
 				13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
+				391E86A41C623EC800009732 /* RCTTouchEvent.m in Sources */,
 				1450FF861BCFF28A00208362 /* RCTProfile.m in Sources */,
 				13AB90C11B6FA36700713B4F /* RCTComponentData.m in Sources */,
 				13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,
diff --git a/React/Views/RCTBorderDrawing.h b/React/Views/RCTBorderDrawing.h
index b67074337e7a..52e3b4a86df4 100644
--- a/React/Views/RCTBorderDrawing.h
+++ b/React/Views/RCTBorderDrawing.h
@@ -9,6 +9,8 @@
 
 #import <UIKit/UIKit.h>
 
+#import "RCTBorderStyle.h"
+
 typedef struct {
   CGFloat topLeft;
   CGFloat topRight;
@@ -39,6 +41,7 @@ BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors);
 
 /**
  * Convert RCTCornerRadii to RCTCornerInsets by applying border insets.
+ * Effectively, returns radius - inset, with a lower bound of 0.0.
  */
 RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii,
                                    UIEdgeInsets borderInsets);
@@ -52,9 +55,14 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds,
                                        const CGAffineTransform *transform);
 
 /**
- * Draw a CSS-compliant border as a scalable image.
+ * Draw a CSS-compliant border as an image. You can determine if it's scalable
+ * by inspecting the image's `capInsets`.
+ *
+ * `borderInsets` defines the border widths for each edge.
  */
-UIImage *RCTGetBorderImage(RCTCornerRadii cornerRadii,
+UIImage *RCTGetBorderImage(RCTBorderStyle borderStyle,
+                           CGSize viewSize,
+                           RCTCornerRadii cornerRadii,
                            UIEdgeInsets borderInsets,
                            RCTBorderColors borderColors,
                            CGColorRef backgroundColor,
diff --git a/React/Views/RCTBorderDrawing.m b/React/Views/RCTBorderDrawing.m
index 8d9e7c0d4e23..2c661a2e36d2 100644
--- a/React/Views/RCTBorderDrawing.m
+++ b/React/Views/RCTBorderDrawing.m
@@ -8,6 +8,7 @@
  */
 
 #import "RCTBorderDrawing.h"
+#import "RCTLog.h"
 
 static const CGFloat RCTViewBorderThreshold = 0.001;
 
@@ -95,10 +96,22 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds,
   const CGFloat maxX = CGRectGetMaxX(bounds);
   const CGFloat maxY = CGRectGetMaxY(bounds);
 
-  const CGSize topLeft = cornerInsets.topLeft;
-  const CGSize topRight = cornerInsets.topRight;
-  const CGSize bottomLeft = cornerInsets.bottomLeft;
-  const CGSize bottomRight = cornerInsets.bottomRight;
+  const CGSize topLeft = {
+    MAX(0, MIN(cornerInsets.topLeft.width, bounds.size.width - cornerInsets.topRight.width)),
+    MAX(0, MIN(cornerInsets.topLeft.height, bounds.size.height - cornerInsets.bottomLeft.height)),
+  };
+  const CGSize topRight = {
+    MAX(0, MIN(cornerInsets.topRight.width, bounds.size.width - cornerInsets.topLeft.width)),
+    MAX(0, MIN(cornerInsets.topRight.height, bounds.size.height - cornerInsets.bottomRight.height)),
+  };
+  const CGSize bottomLeft = {
+    MAX(0, MIN(cornerInsets.bottomLeft.width, bounds.size.width - cornerInsets.bottomRight.width)),
+    MAX(0, MIN(cornerInsets.bottomLeft.height, bounds.size.height - cornerInsets.topLeft.height)),
+  };
+  const CGSize bottomRight = {
+    MAX(0, MIN(cornerInsets.bottomRight.width, bounds.size.width - cornerInsets.bottomLeft.width)),
+    MAX(0, MIN(cornerInsets.bottomRight.height, bounds.size.height - cornerInsets.topRight.height)),
+  };
 
   CGMutablePathRef path = CGPathCreateMutable();
   RCTPathAddEllipticArc(path, transform, (CGPoint){
@@ -150,20 +163,48 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
   intersections[1] = (CGPoint){x2 + ellipseCenter.x, y2 + ellipseCenter.y};
 }
 
-UIImage *RCTGetBorderImage(RCTCornerRadii cornerRadii,
-                           UIEdgeInsets borderInsets,
-                           RCTBorderColors borderColors,
-                           CGColorRef backgroundColor,
-                           BOOL drawToEdge)
-{
-  const BOOL hasCornerRadii =
-  cornerRadii.topLeft > RCTViewBorderThreshold ||
-  cornerRadii.topRight > RCTViewBorderThreshold ||
-  cornerRadii.bottomLeft > RCTViewBorderThreshold ||
-  cornerRadii.bottomRight > RCTViewBorderThreshold;
+NS_INLINE BOOL RCTCornerRadiiAreAboveThreshold(RCTCornerRadii cornerRadii) {
+  return (cornerRadii.topLeft > RCTViewBorderThreshold ||
+    cornerRadii.topRight > RCTViewBorderThreshold      ||
+    cornerRadii.bottomLeft > RCTViewBorderThreshold    ||
+    cornerRadii.bottomRight > RCTViewBorderThreshold);
+}
+
+static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCornerRadii cornerRadii) {
+  if (drawToEdge) {
+    return CGPathCreateWithRect(rect, NULL);
+  }
+
+  return RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
+}
+
+static CGContextRef RCTUIGraphicsBeginImageContext(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge) {
+  const CGFloat alpha = CGColorGetAlpha(backgroundColor);
+  const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
+  UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
+  return UIGraphicsGetCurrentContext();
+}
 
+static UIImage *RCTGetSolidBorderImage(RCTCornerRadii cornerRadii,
+                                       CGSize viewSize,
+                                       UIEdgeInsets borderInsets,
+                                       RCTBorderColors borderColors,
+                                       CGColorRef backgroundColor,
+                                       BOOL drawToEdge)
+{
+  const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
   const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, borderInsets);
 
+  const BOOL makeStretchable =
+  (borderInsets.left + cornerInsets.topLeft.width +
+   borderInsets.right + cornerInsets.bottomRight.width <= viewSize.width) &&
+  (borderInsets.left + cornerInsets.bottomLeft.width +
+   borderInsets.right + cornerInsets.topRight.width <= viewSize.width) &&
+  (borderInsets.top + cornerInsets.topLeft.height +
+   borderInsets.bottom + cornerInsets.bottomRight.height <= viewSize.height) &&
+  (borderInsets.top + cornerInsets.topRight.height +
+   borderInsets.bottom + cornerInsets.bottomLeft.height <= viewSize.height);
+
   const UIEdgeInsets edgeInsets = (UIEdgeInsets){
     borderInsets.top + MAX(cornerInsets.topLeft.height, cornerInsets.topRight.height),
     borderInsets.left + MAX(cornerInsets.topLeft.width, cornerInsets.bottomLeft.width),
@@ -171,24 +212,15 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
     borderInsets.right + MAX(cornerInsets.bottomRight.width, cornerInsets.topRight.width)
   };
 
-  const CGSize size = (CGSize){
+  const CGSize size = makeStretchable ? (CGSize){
+    // 1pt for the middle stretchable area along each axis
     edgeInsets.left + 1 + edgeInsets.right,
     edgeInsets.top + 1 + edgeInsets.bottom
-  };
-
-  const CGFloat alpha = CGColorGetAlpha(backgroundColor);
-  const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
-  UIGraphicsBeginImageContextWithOptions(size, opaque, 0.0);
+  } : viewSize;
 
-  CGContextRef ctx = UIGraphicsGetCurrentContext();
+  CGContextRef ctx = RCTUIGraphicsBeginImageContext(size, backgroundColor, hasCornerRadii, drawToEdge);
   const CGRect rect = {.size = size};
-
-  CGPathRef path;
-  if (drawToEdge) {
-    path = CGPathCreateWithRect(rect, NULL);
-  } else {
-    path = RCTPathCreateWithRoundedRect(rect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
-  }
+  CGPathRef path = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);
 
   if (backgroundColor) {
     CGContextSetFillColorWithColor(ctx, backgroundColor);
@@ -261,6 +293,8 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
       }
     }
 
+    CGColorRef currentColor = NULL;
+
     // RIGHT
     if (borderInsets.right > 0) {
 
@@ -271,9 +305,8 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
         (CGPoint){size.width, size.height},
       };
 
-      CGContextSetFillColorWithColor(ctx, borderColors.right);
+      currentColor = borderColors.right;
       CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
-      CGContextFillPath(ctx);
     }
 
     // BOTTOM
@@ -286,9 +319,12 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
         (CGPoint){size.width, size.height},
       };
 
-      CGContextSetFillColorWithColor(ctx, borderColors.bottom);
+      if (!CGColorEqualToColor(currentColor, borderColors.bottom)) {
+        CGContextSetFillColorWithColor(ctx, currentColor);
+        CGContextFillPath(ctx);
+        currentColor = borderColors.bottom;
+      }
       CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
-      CGContextFillPath(ctx);
     }
 
     // LEFT
@@ -301,9 +337,12 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
         (CGPoint){0, size.height},
       };
 
-      CGContextSetFillColorWithColor(ctx, borderColors.left);
+      if (!CGColorEqualToColor(currentColor, borderColors.left)) {
+        CGContextSetFillColorWithColor(ctx, currentColor);
+        CGContextFillPath(ctx);
+        currentColor = borderColors.left;
+      }
       CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
-      CGContextFillPath(ctx);
     }
 
     // TOP
@@ -316,10 +355,16 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
         (CGPoint){size.width, 0},
       };
 
-      CGContextSetFillColorWithColor(ctx, borderColors.top);
+      if (!CGColorEqualToColor(currentColor, borderColors.top)) {
+        CGContextSetFillColorWithColor(ctx, currentColor);
+        CGContextFillPath(ctx);
+        currentColor = borderColors.top;
+      }
       CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
-      CGContextFillPath(ctx);
     }
+
+    CGContextSetFillColorWithColor(ctx, currentColor);
+    CGContextFillPath(ctx);
   }
 
   CGPathRelease(insetPath);
@@ -327,5 +372,150 @@ static void RCTEllipseGetIntersectionsWithLine(CGRect ellipseBounds,
   UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();
 
-  return [image resizableImageWithCapInsets:edgeInsets];
+  if (makeStretchable) {
+    image = [image resizableImageWithCapInsets:edgeInsets];
+  }
+
+  return image;
+}
+
+// Currently, the dashed / dotted implementation only supports a single colour +
+// single width, as that's currently required and supported on Android.
+//
+// Supporting individual widths + colours on each side is possible by modifying
+// the current implementation. The idea is that we will draw four different lines
+// and clip appropriately for each side (might require adjustment of phase so that
+// they line up but even browsers don't do a good job at that).
+//
+// Firstly, create two paths for the outer and inner paths. The inner path is
+// generated exactly the same way as the outer, just given an inset rect, derived
+// from the insets on each side. Then clip using the odd-even rule
+// (CGContextEOClip()). This will give us a nice rounded (possibly) clip mask.
+//
+// +----------------------------------+
+// |@@@@@@@@  Clipped Space  @@@@@@@@@|
+// |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
+// |@@+----------------------+@@@@@@@@|
+// |@@|                      |@@@@@@@@|
+// |@@|                      |@@@@@@@@|
+// |@@|                      |@@@@@@@@|
+// |@@+----------------------+@@@@@@@@|
+// |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
+// +----------------------------------+
+//
+// Afterwards, we create a clip path for each border side (CGContextSaveGState()
+// and CGContextRestoreGState() when drawing each side). The clip mask for each
+// segment is a trapezoid connecting corresponding edges of the inner and outer
+// rects. For example, in the case of the top edge, the points would be:
+// - (MinX(outer), MinY(outer))
+// - (MaxX(outer), MinY(outer))
+// - (MinX(inner) + topLeftRadius, MinY(inner) + topLeftRadius)
+// - (MaxX(inner) - topRightRadius, MinY(inner) + topRightRadius)
+//
+//         +------------------+
+//         |\                /|
+//         | \              / |
+//         |  \    top     /  |
+//         |   \          /   |
+//         |    \        /    |
+//         |     +------+     |
+//         |     |      |     |
+//         |     |      |     |
+//         |     |      |     |
+//         |left |      |right|
+//         |     |      |     |
+//         |     |      |     |
+//         |     +------+     |
+//         |    /        \    |
+//         |   /          \   |
+//         |  /            \  |
+//         | /    bottom    \ |
+//         |/                \|
+//         +------------------+
+//
+//
+// Note that this approach will produce discontinous colour changes at the edge
+// (which is okay). The reason is that Quartz does not currently support drawing
+// of gradients _along_ a path (NB: clipping a path and drawing a linear gradient
+// is _not_ equivalent).
+
+static UIImage *RCTGetDashedOrDottedBorderImage(RCTBorderStyle borderStyle,
+                                                RCTCornerRadii cornerRadii,
+                                                CGSize viewSize,
+                                                UIEdgeInsets borderInsets,
+                                                RCTBorderColors borderColors,
+                                                CGColorRef backgroundColor,
+                                                BOOL drawToEdge)
+{
+  NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted);
+
+  if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) {
+    RCTLogWarn(@"Unsupported dashed / dotted border style");
+    return nil;
+  }
+
+  const CGFloat lineWidth = borderInsets.top;
+  if (lineWidth <= 0.0) {
+    return nil;
+  }
+
+  const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
+  CGContextRef ctx = RCTUIGraphicsBeginImageContext(viewSize, backgroundColor, hasCornerRadii, drawToEdge);
+  const CGRect rect = {.size = viewSize};
+
+  if (backgroundColor) {
+    CGPathRef outerPath = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);
+    CGContextAddPath(ctx, outerPath);
+    CGPathRelease(outerPath);
+
+    CGContextSetFillColorWithColor(ctx, backgroundColor);
+    CGContextFillPath(ctx);
+  }
+
+  // Stroking means that the width is divided in half and grows in both directions
+  // perpendicular to the path, that's why we inset by half the width, so that it
+  // reaches the edge of the rect.
+  CGRect pathRect = CGRectInset(rect, lineWidth / 2.0, lineWidth / 2.0);
+  CGPathRef path = RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL);
+
+  CGFloat dashLengths[2];
+  dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;
+
+  CGContextSetLineWidth(ctx, lineWidth);
+  CGContextSetLineDash(ctx, 0, dashLengths, sizeof(dashLengths) / sizeof(*dashLengths));
+
+  CGContextSetStrokeColorWithColor(ctx, [UIColor yellowColor].CGColor);
+
+  CGContextAddPath(ctx, path);
+  CGContextSetStrokeColorWithColor(ctx, borderColors.top);
+  CGContextStrokePath(ctx);
+
+  CGPathRelease(path);
+
+  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
+  UIGraphicsEndImageContext();
+
+  return image;
+}
+
+UIImage *RCTGetBorderImage(RCTBorderStyle borderStyle,
+                           CGSize viewSize,
+                           RCTCornerRadii cornerRadii,
+                           UIEdgeInsets borderInsets,
+                           RCTBorderColors borderColors,
+                           CGColorRef backgroundColor,
+                           BOOL drawToEdge)
+{
+
+  switch (borderStyle) {
+    case RCTBorderStyleSolid:
+      return RCTGetSolidBorderImage(cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
+    case RCTBorderStyleDashed:
+    case RCTBorderStyleDotted:
+      return RCTGetDashedOrDottedBorderImage(borderStyle, cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
+    case RCTBorderStyleUnset:
+      break;
+  }
+
+  return nil;
 }
diff --git a/React/Views/RCTBorderStyle.h b/React/Views/RCTBorderStyle.h
new file mode 100644
index 000000000000..470d832314b7
--- /dev/null
+++ b/React/Views/RCTBorderStyle.h
@@ -0,0 +1,17 @@
+/**
+ * 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.
+ */
+
+#import <Foundation/Foundation.h>
+
+typedef NS_ENUM(NSInteger, RCTBorderStyle) {
+  RCTBorderStyleUnset = 0,
+  RCTBorderStyleSolid,
+  RCTBorderStyleDotted,
+  RCTBorderStyleDashed,
+};
diff --git a/React/Views/RCTComponent.h b/React/Views/RCTComponent.h
index 2940b431145e..5236f850b799 100644
--- a/React/Views/RCTComponent.h
+++ b/React/Views/RCTComponent.h
@@ -36,6 +36,13 @@ typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
 
 @optional
 
+/**
+ * Called each time props have been set.
+ * Not all props have to be set - React can set only changed ones.
+ * @param changedProps String names of all set props.
+ */
+- (void)didSetProps:(NSArray<NSString *> *)changedProps;
+
 // TODO: Deprecate this
 // This method is called after layout has been performed for all views known
 // to the RCTViewManager. It is only called on UIViews, not shadow views.
diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h
index 9a43e9865a39..596dcc8399b7 100644
--- a/React/Views/RCTComponentData.h
+++ b/React/Views/RCTComponentData.h
@@ -11,23 +11,28 @@
 
 #import "RCTComponent.h"
 #import "RCTDefines.h"
+#import "RCTViewManager.h"
 
+@class RCTBridge;
 @class RCTShadowView;
-@class RCTViewManager;
 @class UIView;
 
 @interface RCTComponentData : NSObject
 
+@property (nonatomic, readonly) Class managerClass;
 @property (nonatomic, copy, readonly) NSString *name;
-@property (nonatomic, strong, readonly) RCTViewManager *manager;
+@property (nonatomic, weak, readonly) RCTViewManager *manager;
 
-- (instancetype)initWithManager:(RCTViewManager *)manager NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithManagerClass:(Class)managerClass
+                              bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
 
-- (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary<NSString *, id> *)props;
+- (UIView *)createViewWithTag:(NSNumber *)tag;
 - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag;
 - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>)view;
 - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView;
 
 - (NSDictionary<NSString *, id> *)viewConfig;
 
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)registry;
+
 @end
diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m
index 20c64c3e1582..d5d34f5f8f8d 100644
--- a/React/Views/RCTComponentData.m
+++ b/React/Views/RCTComponentData.m
@@ -14,7 +14,6 @@
 #import "RCTBridge.h"
 #import "RCTShadowView.h"
 #import "RCTUtils.h"
-#import "RCTViewManager.h"
 #import "UIView+React.h"
 
 typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@@ -44,31 +43,53 @@ @implementation RCTComponentData
   RCTShadowView *_defaultShadowView;
   NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks;
   NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks;
+  BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
+  __weak RCTBridge *_bridge;
 }
 
-- (instancetype)initWithManager:(RCTViewManager *)manager
+@synthesize manager = _manager;
+
+- (instancetype)initWithManagerClass:(Class)managerClass
+                              bridge:(RCTBridge *)bridge
 {
   if ((self = [super init])) {
-    _manager = manager;
+    _bridge = bridge;
+    _managerClass = managerClass;
     _viewPropBlocks = [NSMutableDictionary new];
     _shadowPropBlocks = [NSMutableDictionary new];
 
-    _name = RCTBridgeModuleNameForClass([manager class]);
+    _name = RCTBridgeModuleNameForClass(_managerClass);
     RCTAssert(_name.length, @"Invalid moduleName '%@'", _name);
     if ([_name hasSuffix:@"Manager"]) {
       _name = [_name substringToIndex:_name.length - @"Manager".length];
     }
+
+    _implementsUIBlockToAmendWithShadowViewRegistry = NO;
+    Class cls = _managerClass;
+    while (cls != [RCTViewManager class]) {
+      _implementsUIBlockToAmendWithShadowViewRegistry = _implementsUIBlockToAmendWithShadowViewRegistry ||
+      RCTClassOverridesInstanceMethod(cls, @selector(uiBlockToAmendWithShadowViewRegistry:));
+      cls = [cls superclass];
+    }
   }
   return self;
 }
 
+- (RCTViewManager *)manager
+{
+  if (!_manager) {
+    _manager = [_bridge moduleForClass:_managerClass];
+  }
+  return _manager;
+}
+
 RCT_NOT_IMPLEMENTED(- (instancetype)init)
 
-- (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary<NSString *, id> *)props
+- (UIView *)createViewWithTag:(NSNumber *)tag
 {
   RCTAssertMainThread();
 
-  UIView *view = (UIView *)(props ? [_manager viewWithProps:props] : [_manager view]);
+  UIView *view = [_manager view];
   view.reactTag = tag;
   view.multipleTouchEnabled = YES;
   view.userInteractionEnabled = YES; // required for touch handling
@@ -78,7 +99,7 @@ - (UIView *)createViewWithTag:(NSNumber *)tag props:(NSDictionary<NSString *, id
 
 - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
 {
-  RCTShadowView *shadowView = [_manager shadowView];
+  RCTShadowView *shadowView = [self.manager shadowView];
   shadowView.reactTag = tag;
   shadowView.viewName = _name;
   return shadowView;
@@ -101,7 +122,7 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
     if ([managerClass respondsToSelector:selector]) {
       NSArray<NSString *> *typeAndKeyPath =
         ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(managerClass, selector);
-      type = NSSelectorFromString([typeAndKeyPath[0] stringByAppendingString:@":"]);
+      type = RCTConvertSelectorForType(typeAndKeyPath[0]);
       keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
     } else {
       propBlock = ^(__unused id view, __unused id json) {};
@@ -159,6 +180,10 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
 
         // Ordinary property handlers
         NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
+        if (!typeSignature) {
+          RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
+          return ^(__unused id<RCTComponent> view, __unused id json){};
+        }
         switch (typeSignature.methodReturnType[0]) {
 
   #define RCT_CASE(_value, _type) \
@@ -283,12 +308,16 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>
   }
 
   if (!_defaultView) {
-    _defaultView = [self createViewWithTag:nil props:nil];
+    _defaultView = [self createViewWithTag:nil];
   }
 
   [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
     [self propBlockForKey:key defaultView:_defaultView](view, json);
   }];
+
+  if ([view respondsToSelector:@selector(didSetProps:)]) {
+    [view didSetProps:[props allKeys]];
+  }
 }
 
 - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
@@ -305,20 +334,20 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
     [self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
   }];
 
-  [shadowView updateLayout];
+  if ([shadowView respondsToSelector:@selector(didSetProps:)]) {
+    [shadowView didSetProps:[props allKeys]];
+  }
 }
 
 - (NSDictionary<NSString *, id> *)viewConfig
 {
-  Class managerClass = [_manager class];
-
   NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
-  if (RCTClassOverridesInstanceMethod(managerClass, @selector(customDirectEventTypes))) {
-    NSArray<NSString *> *events = [_manager customDirectEventTypes];
+  if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customDirectEventTypes))) {
+    NSArray<NSString *> *events = [self.manager customDirectEventTypes];
     if (RCT_DEBUG) {
       RCTAssert(!events || [events isKindOfClass:[NSArray class]],
         @"customDirectEventTypes must return an array, but %@ returned %@",
-        managerClass, [events class]);
+        _managerClass, [events class]);
     }
     for (NSString *event in events) {
       [directEvents addObject:RCTNormalizeInputEventName(event)];
@@ -326,12 +355,12 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
   }
 
   NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
-  if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) {
-    NSArray<NSString *> *events = [_manager customBubblingEventTypes];
+  if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) {
+    NSArray<NSString *> *events = [self.manager customBubblingEventTypes];
     if (RCT_DEBUG) {
       RCTAssert(!events || [events isKindOfClass:[NSArray class]],
         @"customBubblingEventTypes must return an array, but %@ returned %@",
-        managerClass, [events class]);
+        _managerClass, [events class]);
     }
     for (NSString *event in events) {
       [bubblingEvents addObject:RCTNormalizeInputEventName(event)];
@@ -340,7 +369,7 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
 
   unsigned int count = 0;
   NSMutableDictionary *propTypes = [NSMutableDictionary new];
-  Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
+  Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
   for (unsigned int i = 0; i < count; i++) {
     Method method = methods[i];
     SEL selector = method_getName(method);
@@ -349,7 +378,7 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
       NSRange nameRange = [methodName rangeOfString:@"_"];
       if (nameRange.length) {
         NSString *name = [methodName substringFromIndex:nameRange.location + 1];
-        NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(managerClass, selector)[0];
+        NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0];
         if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) {
           RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
                       "to '%@'", name, _name, propTypes[name], type);
@@ -391,4 +420,12 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
   };
 }
 
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *,RCTShadowView *> *)registry
+{
+  if (_implementsUIBlockToAmendWithShadowViewRegistry) {
+    return [[self manager] uiBlockToAmendWithShadowViewRegistry:registry];
+  }
+  return nil;
+}
+
 @end
diff --git a/React/Views/RCTConvert+MapKit.h b/React/Views/RCTConvert+MapKit.h
index d3e7fbc153f3..46fddc8dc925 100644
--- a/React/Views/RCTConvert+MapKit.h
+++ b/React/Views/RCTConvert+MapKit.h
@@ -9,21 +9,21 @@
 
 #import <MapKit/MapKit.h>
 
-#import "RCTPointAnnotation.h"
 #import "RCTConvert.h"
 
+@class RCTMapAnnotation;
+@class RCTMapOverlay;
+
 @interface RCTConvert (MapKit)
 
 + (MKCoordinateSpan)MKCoordinateSpan:(id)json;
 + (MKCoordinateRegion)MKCoordinateRegion:(id)json;
-+ (MKShape *)MKShape:(id)json;
 + (MKMapType)MKMapType:(id)json;
-+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json;
 
-typedef NSArray MKShapeArray;
-+ (MKShapeArray *)MKShapeArray:(id)json;
++ (RCTMapAnnotation *)RCTMapAnnotation:(id)json;
++ (RCTMapOverlay *)RCTMapOverlay:(id)json;
 
-typedef NSArray RCTPointAnnotationArray;
-+ (RCTPointAnnotationArray *)RCTPointAnnotationArray:(id)json;
++ (NSArray<RCTMapAnnotation *> *)RCTMapAnnotationArray:(id)json;
++ (NSArray<RCTMapOverlay *> *)RCTMapOverlayArray:(id)json;
 
 @end
diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m
index 9206c3a4050e..354b133b6780 100644
--- a/React/Views/RCTConvert+MapKit.m
+++ b/React/Views/RCTConvert+MapKit.m
@@ -9,7 +9,8 @@
 
 #import "RCTConvert+MapKit.h"
 #import "RCTConvert+CoreLocation.h"
-#import "RCTPointAnnotation.h"
+#import "RCTMapAnnotation.h"
+#import "RCTMapOverlay.h"
 
 @implementation RCTConvert(MapKit)
 
@@ -30,40 +31,58 @@ + (MKCoordinateRegion)MKCoordinateRegion:(id)json
   };
 }
 
-+ (MKShape *)MKShape:(id)json
-{
-  json = [self NSDictionary:json];
-
-  // TODO: more shape types
-  MKShape *shape = [MKPointAnnotation new];
-  shape.coordinate = [self CLLocationCoordinate2D:json];
-  shape.title = [RCTConvert NSString:json[@"title"]];
-  shape.subtitle = [RCTConvert NSString:json[@"subtitle"]];
-  return shape;
-}
-
-RCT_ARRAY_CONVERTER(MKShape)
-
 RCT_ENUM_CONVERTER(MKMapType, (@{
   @"standard": @(MKMapTypeStandard),
   @"satellite": @(MKMapTypeSatellite),
   @"hybrid": @(MKMapTypeHybrid),
 }), MKMapTypeStandard, integerValue)
 
-+ (RCTPointAnnotation *)RCTPointAnnotation:(id)json
++ (RCTMapAnnotation *)RCTMapAnnotation:(id)json
 {
   json = [self NSDictionary:json];
-  RCTPointAnnotation *shape = [RCTPointAnnotation new];
-  shape.coordinate = [self CLLocationCoordinate2D:json];
-  shape.title = [RCTConvert NSString:json[@"title"]];
-  shape.subtitle = [RCTConvert NSString:json[@"subtitle"]];
-  shape.identifier = [RCTConvert NSString:json[@"id"]];
-  shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]];
-  shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]];
-  shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]];
-  return shape;
+  RCTMapAnnotation *annotation = [RCTMapAnnotation new];
+  annotation.coordinate = [self CLLocationCoordinate2D:json];
+  annotation.draggable = [self BOOL:json[@"draggable"]];
+  annotation.title = [self NSString:json[@"title"]];
+  annotation.subtitle = [self NSString:json[@"subtitle"]];
+  annotation.identifier = [self NSString:json[@"id"]];
+  annotation.hasLeftCallout = [self BOOL:json[@"hasLeftCallout"]];
+  annotation.hasRightCallout = [self BOOL:json[@"hasRightCallout"]];
+  annotation.animateDrop = [self BOOL:json[@"animateDrop"]];
+  annotation.tintColor = [self UIColor:json[@"tintColor"]];
+  annotation.image = [self UIImage:json[@"image"]];
+  annotation.viewIndex =
+    [self NSInteger:json[@"viewIndex"] ?: @(NSNotFound)];
+  annotation.leftCalloutViewIndex =
+    [self NSInteger:json[@"leftCalloutViewIndex"] ?: @(NSNotFound)];
+  annotation.rightCalloutViewIndex =
+    [self NSInteger:json[@"rightCalloutViewIndex"] ?: @(NSNotFound)];
+  annotation.detailCalloutViewIndex =
+    [self NSInteger:json[@"detailCalloutViewIndex"] ?: @(NSNotFound)];
+  return annotation;
+}
+
+RCT_ARRAY_CONVERTER(RCTMapAnnotation)
+
++ (RCTMapOverlay *)RCTMapOverlay:(id)json
+{
+  json = [self NSDictionary:json];
+  NSArray<NSDictionary *> *locations = [self NSDictionaryArray:json[@"coordinates"]];
+  CLLocationCoordinate2D coordinates[locations.count];
+  NSUInteger index = 0;
+  for (NSDictionary *location in locations) {
+    coordinates[index++] = [self CLLocationCoordinate2D:location];
+  }
+
+  RCTMapOverlay *overlay = [RCTMapOverlay polylineWithCoordinates:coordinates
+                                                            count:locations.count];
+
+  overlay.strokeColor = [self UIColor:json[@"strokeColor"]];
+  overlay.identifier = [self NSString:json[@"id"]];
+  overlay.lineWidth = [self CGFloat:json[@"lineWidth"] ?: @1];
+  return overlay;
 }
 
-RCT_ARRAY_CONVERTER(RCTPointAnnotation)
+RCT_ARRAY_CONVERTER(RCTMapOverlay)
 
 @end
diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h
index c9025d6d346a..e6c5cabf0b80 100644
--- a/React/Views/RCTMap.h
+++ b/React/Views/RCTMap.h
@@ -13,9 +13,9 @@
 #import "RCTConvert+MapKit.h"
 #import "RCTComponent.h"
 
-extern const CLLocationDegrees RCTMapDefaultSpan;
-extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
-extern const CGFloat RCTMapZoomBoundBuffer;
+RCT_EXTERN const CLLocationDegrees RCTMapDefaultSpan;
+RCT_EXTERN const NSTimeInterval RCTMapRegionChangeObserveInterval;
+RCT_EXTERN const CGFloat RCTMapZoomBoundBuffer;
 
 @interface RCTMap: MKMapView
 
@@ -25,11 +25,16 @@ extern const CGFloat RCTMapZoomBoundBuffer;
 @property (nonatomic, assign) CGFloat maxDelta;
 @property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
 @property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
-@property (nonatomic, copy) NSArray<NSString *> *annotationIds;
+@property (nonatomic, copy) NSArray<NSString *> *annotationIDs;
+@property (nonatomic, copy) NSArray<NSString *> *overlayIDs;
 
 @property (nonatomic, copy) RCTBubblingEventBlock onChange;
 @property (nonatomic, copy) RCTBubblingEventBlock onPress;
+@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationDragStateChange;
+@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationFocus;
+@property (nonatomic, copy) RCTBubblingEventBlock onAnnotationBlur;
 
-- (void)setAnnotations:(RCTPointAnnotationArray *)annotations;
+- (void)setAnnotations:(NSArray<RCTMapAnnotation *> *)annotations;
+- (void)setOverlays:(NSArray<RCTMapOverlay *> *)overlays;
 
 @end
diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m
index 238c7a4d3270..1b3472530155 100644
--- a/React/Views/RCTMap.m
+++ b/React/Views/RCTMap.m
@@ -11,6 +11,8 @@
 
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
+#import "RCTMapAnnotation.h"
+#import "RCTMapOverlay.h"
 #import "RCTUtils.h"
 
 const CLLocationDegrees RCTMapDefaultSpan = 0.005;
@@ -21,6 +23,7 @@ @implementation RCTMap
 {
   UIView *_legalLabel;
   CLLocationManager *_locationManager;
+  NSMutableArray<UIView *> *_reactSubviews;
 }
 
 - (instancetype)init
@@ -28,6 +31,7 @@ - (instancetype)init
   if ((self = [super init])) {
 
     _hasStartedRendering = NO;
+    _reactSubviews = [NSMutableArray new];
 
     // Find Apple link label
     for (UIView *subview in self.subviews) {
@@ -47,6 +51,21 @@ - (void)dealloc
   [_regionChangeObserveTimer invalidate];
 }
 
+- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
+{
+  [_reactSubviews insertObject:subview atIndex:atIndex];
+}
+
+- (void)removeReactSubview:(UIView *)subview
+{
+  [_reactSubviews removeObject:subview];
+}
+
+- (NSArray<UIView *> *)reactSubviews
+{
+  return _reactSubviews;
+}
+
 - (void)layoutSubviews
 {
   [super layoutSubviews];
@@ -81,10 +100,6 @@ - (void)setShowsUserLocation:(BOOL)showsUserLocation
       }
     }
     super.showsUserLocation = showsUserLocation;
-
-    // If it needs to show user location, force map view centered
-    // on user's current location on user location updates
-    _followUserLocation = showsUserLocation;
   }
 }
 
@@ -107,32 +122,34 @@ - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
   [super setRegion:region animated:animated];
 }
 
-- (void)setAnnotations:(RCTPointAnnotationArray *)annotations
+// TODO: this doesn't preserve order. Should it? If so we should change the
+// algorithm. If not, it would be more efficient to use an NSSet
+- (void)setAnnotations:(NSArray<RCTMapAnnotation *> *)annotations
 {
-  NSMutableArray<NSString *> *newAnnotationIds = [NSMutableArray new];
-  NSMutableArray<RCTPointAnnotation *> *annotationsToDelete = [NSMutableArray new];
-  NSMutableArray<RCTPointAnnotation *> *annotationsToAdd = [NSMutableArray new];
+  NSMutableArray<NSString *> *newAnnotationIDs = [NSMutableArray new];
+  NSMutableArray<RCTMapAnnotation *> *annotationsToDelete = [NSMutableArray new];
+  NSMutableArray<RCTMapAnnotation *> *annotationsToAdd = [NSMutableArray new];
 
-  for (RCTPointAnnotation *annotation in annotations) {
-    if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
+  for (RCTMapAnnotation *annotation in annotations) {
+    if (![annotation isKindOfClass:[RCTMapAnnotation class]]) {
       continue;
     }
 
-    [newAnnotationIds addObject:annotation.identifier];
+    [newAnnotationIDs addObject:annotation.identifier];
 
-    // If the current set does not contain the new annotation, mark it as add
-    if (![self.annotationIds containsObject:annotation.identifier]) {
+    // If the current set does not contain the new annotation, mark it to add
+    if (![_annotationIDs containsObject:annotation.identifier]) {
       [annotationsToAdd addObject:annotation];
     }
   }
 
-  for (RCTPointAnnotation *annotation in self.annotations) {
-    if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
+  for (RCTMapAnnotation *annotation in self.annotations) {
+    if (![annotation isKindOfClass:[RCTMapAnnotation class]]) {
       continue;
     }
 
-    // If the new set does not contain an existing annotation, mark it as delete
-    if (![newAnnotationIds containsObject:annotation.identifier]) {
+    // If the new set does not contain an existing annotation, mark it to delete
+    if (![newAnnotationIDs containsObject:annotation.identifier]) {
       [annotationsToDelete addObject:annotation];
     }
   }
@@ -145,14 +162,64 @@ - (void)setAnnotations:(RCTPointAnnotationArray *)annotations
     [self addAnnotations:(NSArray<id<MKAnnotation>> *)annotationsToAdd];
   }
 
-  NSMutableArray<NSString *> *newIds = [NSMutableArray new];
-  for (RCTPointAnnotation *annotation in self.annotations) {
-    if ([annotation isKindOfClass:[MKUserLocation class]]) {
+  self.annotationIDs = newAnnotationIDs;
+}
+
+// TODO: this doesn't preserve order. Should it? If so we should change the
+// algorithm. If not, it would be more efficient to use an NSSet
+- (void)setOverlays:(NSArray<RCTMapOverlay *> *)overlays
+{
+  NSMutableArray *newOverlayIDs = [NSMutableArray new];
+  NSMutableArray *overlaysToDelete = [NSMutableArray new];
+  NSMutableArray *overlaysToAdd = [NSMutableArray new];
+
+  for (RCTMapOverlay *overlay in overlays) {
+    if (![overlay isKindOfClass:[RCTMapOverlay class]]) {
+      continue;
+    }
+
+    [newOverlayIDs addObject:overlay.identifier];
+
+    // If the current set does not contain the new annotation, mark it to add
+    if (![_annotationIDs containsObject:overlay.identifier]) {
+      [overlaysToAdd addObject:overlay];
+    }
+  }
+
+  for (RCTMapOverlay *overlay in self.overlays) {
+    if (![overlay isKindOfClass:[RCTMapOverlay class]]) {
       continue;
     }
-    [newIds addObject:annotation.identifier];
+
+    // If the new set does not contain an existing annotation, mark it to delete
+    if (![newOverlayIDs containsObject:overlay.identifier]) {
+      [overlaysToDelete addObject:overlay];
+    }
+  }
+
+  if (overlaysToDelete.count) {
+    [self removeOverlays:(NSArray<id<MKOverlay>> *)overlaysToDelete];
+  }
+
+  if (overlaysToAdd.count) {
+    [self addOverlays:(NSArray<id<MKOverlay>> *)overlaysToAdd
+                level:MKOverlayLevelAboveRoads];
+  }
+
+  self.overlayIDs = newOverlayIDs;
+}
+
+- (BOOL)showsCompass {
+  if ([MKMapView instancesRespondToSelector:@selector(showsCompass)]) {
+    return super. showsCompass;
+  }
+  return NO;
+}
+
+- (void)setShowsCompass:(BOOL)showsCompass {
+  if ([MKMapView instancesRespondToSelector:@selector(setShowsCompass:)]) {
+    super.showsCompass = showsCompass;
   }
-  self.annotationIds = newIds;
 }
 
 @end
diff --git a/React/Views/RCTMapAnnotation.h b/React/Views/RCTMapAnnotation.h
new file mode 100644
index 000000000000..ec5b4edc32ad
--- /dev/null
+++ b/React/Views/RCTMapAnnotation.h
@@ -0,0 +1,26 @@
+/**
+ * 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.
+ */
+
+#import <MapKit/MapKit.h>
+
+@interface RCTMapAnnotation : MKPointAnnotation <MKAnnotation>
+
+@property (nonatomic, copy) NSString *identifier;
+@property (nonatomic, assign) BOOL hasLeftCallout;
+@property (nonatomic, assign) BOOL hasRightCallout;
+@property (nonatomic, assign) BOOL animateDrop;
+@property (nonatomic, strong) UIColor *tintColor;
+@property (nonatomic, strong) UIImage *image;
+@property (nonatomic, assign) NSInteger viewIndex;
+@property (nonatomic, assign) NSInteger leftCalloutViewIndex;
+@property (nonatomic, assign) NSInteger rightCalloutViewIndex;
+@property (nonatomic, assign) NSInteger detailCalloutViewIndex;
+@property (nonatomic, assign) BOOL draggable;
+
+@end
diff --git a/React/Views/RCTPointAnnotation.m b/React/Views/RCTMapAnnotation.m
similarity index 82%
rename from React/Views/RCTPointAnnotation.m
rename to React/Views/RCTMapAnnotation.m
index aaaf2d7e20a2..0b90fe8ab1e8 100644
--- a/React/Views/RCTPointAnnotation.m
+++ b/React/Views/RCTMapAnnotation.m
@@ -7,8 +7,8 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#import "RCTPointAnnotation.h"
+#import "RCTMapAnnotation.h"
 
-@implementation RCTPointAnnotation
+@implementation RCTMapAnnotation
 
 @end
diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m
index 44dd0743ec4f..ec4595a5a3a5 100644
--- a/React/Views/RCTMapManager.m
+++ b/React/Views/RCTMapManager.m
@@ -14,13 +14,59 @@
 #import "RCTConvert+MapKit.h"
 #import "RCTEventDispatcher.h"
 #import "RCTMap.h"
+#import "RCTUtils.h"
 #import "UIView+React.h"
-#import "RCTPointAnnotation.h"
+#import "RCTMapAnnotation.h"
+#import "RCTMapOverlay.h"
 
 #import <MapKit/MapKit.h>
 
 static NSString *const RCTMapViewKey = @"MapView";
 
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+
+static NSString *const RCTMapPinRed = @"#ff3b30";
+static NSString *const RCTMapPinGreen = @"#4cd964";
+static NSString *const RCTMapPinPurple = @"#c969e0";
+
+@implementation RCTConvert (MKPinAnnotationColor)
+
+RCT_ENUM_CONVERTER(MKPinAnnotationColor, (@{
+  RCTMapPinRed: @(MKPinAnnotationColorRed),
+  RCTMapPinGreen: @(MKPinAnnotationColorGreen),
+  RCTMapPinPurple: @(MKPinAnnotationColorPurple)
+}), MKPinAnnotationColorRed, unsignedIntegerValue)
+
+@end
+
+#endif
+
+@interface RCTMapAnnotationView : MKAnnotationView
+
+@property (nonatomic, strong) UIView *contentView;
+
+@end
+
+@implementation RCTMapAnnotationView
+
+- (void)setContentView:(UIView *)contentView
+{
+  [_contentView removeFromSuperview];
+  _contentView = contentView;
+  [self addSubview:_contentView];
+}
+
+- (void)layoutSubviews
+{
+  [super layoutSubviews];
+  self.bounds = (CGRect){
+    CGPointZero,
+    _contentView.frame.size,
+  };
+}
+
+@end
+
 @interface RCTMapManager() <MKMapViewDelegate>
 
 @end
@@ -38,6 +84,8 @@ - (UIView *)view
 
 RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(showsPointsOfInterest, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(showsCompass, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(followUserLocation, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
@@ -46,7 +94,11 @@ - (UIView *)view
 RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
 RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
 RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType)
-RCT_EXPORT_VIEW_PROPERTY(annotations, RCTPointAnnotationArray)
+RCT_EXPORT_VIEW_PROPERTY(annotations, NSArray<RCTMapAnnotation *>)
+RCT_EXPORT_VIEW_PROPERTY(overlays, NSArray<RCTMapOverlay *>)
+RCT_EXPORT_VIEW_PROPERTY(onAnnotationDragStateChange, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onAnnotationFocus, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onAnnotationBlur, RCTBubblingEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
 RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
@@ -54,13 +106,42 @@ - (UIView *)view
   [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
 }
 
+- (NSDictionary<NSString *, id> *)constantsToExport
+{
+  NSString *red, *green, *purple;
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
+
+  if (![MKPinAnnotationView respondsToSelector:@selector(redPinColor)]) {
+    red = RCTMapPinRed;
+    green = RCTMapPinGreen;
+    purple = RCTMapPinPurple;
+  } else
+
+#endif
+
+  {
+    red = RCTColorToHexString([MKPinAnnotationView redPinColor].CGColor);
+    green = RCTColorToHexString([MKPinAnnotationView greenPinColor].CGColor);
+    purple = RCTColorToHexString([MKPinAnnotationView purplePinColor].CGColor);
+  }
+
+  return @{
+    @"PinColors": @{
+      @"RED": red,
+      @"GREEN": green,
+      @"PURPLE": purple,
+    }
+  };
+}
+
 #pragma mark MKMapViewDelegate
 
 - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
 {
-  if (mapView.onPress && [view.annotation isKindOfClass:[RCTPointAnnotation class]]) {
-
-    RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
+  // TODO: Remove deprecated onAnnotationPress API call later.
+  if (mapView.onPress && [view.annotation isKindOfClass:[RCTMapAnnotation class]]) {
+    RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
     mapView.onPress(@{
       @"action": @"annotation-click",
       @"annotation": @{
@@ -72,38 +153,182 @@ - (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)vi
       }
     });
   }
+
+  if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) {
+    RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
+    if (mapView.onAnnotationFocus) {
+      mapView.onAnnotationFocus(@{
+        @"annotationId": annotation.identifier
+      });
+    }
+  }
+}
+
+- (void)mapView:(RCTMap *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
+{
+  if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) {
+    RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
+    if (mapView.onAnnotationBlur) {
+      mapView.onAnnotationBlur(@{
+        @"annotationId": annotation.identifier
+      });
+    }
+  }
+}
+
+- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view
+                              didChangeDragState:(MKAnnotationViewDragState)newState
+                                    fromOldState:(MKAnnotationViewDragState)oldState
+{
+  static NSArray *states;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    states = @[@"idle", @"starting", @"dragging", @"canceling", @"ending"];
+  });
+
+  if ([view.annotation isKindOfClass:[RCTMapAnnotation class]]) {
+    RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
+    if (mapView.onAnnotationDragStateChange) {
+      mapView.onAnnotationDragStateChange(@{
+        @"state": states[newState],
+        @"oldState": states[oldState],
+        @"annotationId": annotation.identifier,
+        @"latitude": @(annotation.coordinate.latitude),
+        @"longitude": @(annotation.coordinate.longitude),
+      });
+    }
+  }
 }
 
-- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation
+- (MKAnnotationView *)mapView:(RCTMap *)mapView
+            viewForAnnotation:(RCTMapAnnotation *)annotation
 {
-  if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
+  if (![annotation isKindOfClass:[RCTMapAnnotation class]]) {
     return nil;
   }
 
-  MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"RCTAnnotation"];
+  MKAnnotationView *annotationView;
+  if (annotation.viewIndex != NSNotFound) {
+
+    NSString *reuseIdentifier = NSStringFromClass([RCTMapAnnotationView class]);
+    annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier];
+    if (!annotationView) {
+      annotationView = [[RCTMapAnnotationView alloc] initWithAnnotation:annotation
+                                                        reuseIdentifier:reuseIdentifier];
+    }
+    UIView *reactView = mapView.reactSubviews[annotation.viewIndex];
+    ((RCTMapAnnotationView *)annotationView).contentView = reactView;
+
+  } else if (annotation.image) {
+
+    NSString *reuseIdentifier = NSStringFromClass([MKAnnotationView class]);
+    annotationView =
+      [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?:
+      [[MKAnnotationView alloc] initWithAnnotation:annotation
+                                   reuseIdentifier:reuseIdentifier];
+    annotationView.image = annotation.image;
+
+  } else {
+
+    NSString *reuseIdentifier = NSStringFromClass([MKPinAnnotationView class]);
+    annotationView =
+      [mapView dequeueReusableAnnotationViewWithIdentifier:reuseIdentifier] ?:
+      [[MKPinAnnotationView alloc] initWithAnnotation:annotation
+                                      reuseIdentifier:reuseIdentifier];
+    ((MKPinAnnotationView *)annotationView).animatesDrop = annotation.animateDrop;
 
-  annotationView.canShowCallout = true;
-  annotationView.animatesDrop = annotation.animateDrop;
+#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0
 
-  annotationView.leftCalloutAccessoryView = nil;
-  if (annotation.hasLeftCallout) {
-    annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
+    if (![annotationView respondsToSelector:@selector(pinTintColor)]) {
+      NSString *hexColor = annotation.tintColor ?
+        RCTColorToHexString(annotation.tintColor.CGColor) : RCTMapPinRed;
+      ((MKPinAnnotationView *)annotationView).pinColor =
+        [RCTConvert MKPinAnnotationColor:hexColor];
+    } else
+
+#endif
+
+    {
+      ((MKPinAnnotationView *)annotationView).pinTintColor =
+        annotation.tintColor ?: [MKPinAnnotationView redPinColor];
+    }
+  }
+  annotationView.canShowCallout = (annotation.title.length > 0);
+
+  if (annotation.leftCalloutViewIndex != NSNotFound) {
+    annotationView.leftCalloutAccessoryView =
+      mapView.reactSubviews[annotation.leftCalloutViewIndex];
+  } else if (annotation.hasLeftCallout) {
+    annotationView.leftCalloutAccessoryView =
+      [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
+  } else {
+    annotationView.leftCalloutAccessoryView = nil;
   }
 
-  annotationView.rightCalloutAccessoryView = nil;
-  if (annotation.hasRightCallout) {
-    annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
+  if (annotation.rightCalloutViewIndex != NSNotFound) {
+    annotationView.rightCalloutAccessoryView =
+      mapView.reactSubviews[annotation.rightCalloutViewIndex];
+  } else if (annotation.hasRightCallout) {
+    annotationView.rightCalloutAccessoryView =
+      [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
+  } else {
+    annotationView.rightCalloutAccessoryView = nil;
   }
 
+  //http://stackoverflow.com/questions/32581049/mapkit-ios-9-detailcalloutaccessoryview-usage
+  if ([annotationView respondsToSelector:@selector(detailCalloutAccessoryView)]) {
+    if (annotation.detailCalloutViewIndex != NSNotFound) {
+      UIView *calloutView = mapView.reactSubviews[annotation.detailCalloutViewIndex];
+      NSLayoutConstraint *widthConstraint =
+        [NSLayoutConstraint constraintWithItem:calloutView
+                                     attribute:NSLayoutAttributeWidth
+                                     relatedBy:NSLayoutRelationEqual
+                                        toItem:nil
+                                     attribute:NSLayoutAttributeNotAnAttribute
+                                    multiplier:1
+                                      constant:calloutView.frame.size.width];
+      [calloutView addConstraint:widthConstraint];
+      NSLayoutConstraint *heightConstraint =
+        [NSLayoutConstraint constraintWithItem:calloutView
+                                     attribute:NSLayoutAttributeHeight
+                                     relatedBy:NSLayoutRelationEqual
+                                        toItem:nil
+                                     attribute:NSLayoutAttributeNotAnAttribute
+                                    multiplier:1
+                                      constant:calloutView.frame.size.height];
+      [calloutView addConstraint:heightConstraint];
+      annotationView.detailCalloutAccessoryView = calloutView;
+    } else {
+      annotationView.detailCalloutAccessoryView = nil;
+    }
+  }
+
+  annotationView.draggable = annotation.draggable;
+
   return annotationView;
 }
 
-- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
+- (MKOverlayRenderer *)mapView:(__unused MKMapView *)mapView
+            rendererForOverlay:(RCTMapOverlay *)overlay
+{
+  if ([overlay isKindOfClass:[RCTMapOverlay class]]) {
+    MKPolylineRenderer *polylineRenderer =
+      [[MKPolylineRenderer alloc] initWithPolyline:overlay];
+    polylineRenderer.strokeColor = overlay.strokeColor;
+    polylineRenderer.lineWidth = overlay.lineWidth;
+    return polylineRenderer;
+  } else {
+    return nil;
+  }
+}
+
+- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view
+                   calloutAccessoryControlTapped:(UIControl *)control
 {
   if (mapView.onPress) {
 
-    // Pass to js
-    RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
+    // Pass to JS
+    RCTMapAnnotation *annotation = (RCTMapAnnotation *)view.annotation;
     mapView.onPress(@{
       @"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right",
       @"action": @"callout-click",
@@ -120,9 +345,6 @@ - (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)locati
     region.span.longitudeDelta = RCTMapDefaultSpan;
     region.center = location.coordinate;
     [mapView setRegion:region animated:YES];
-
-    // Move to user location only for the first time it loads up.
-    mapView.followUserLocation = NO;
   }
 }
 
@@ -130,13 +352,15 @@ - (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(__unused BOOL)animat
 {
   [self _regionChanged:mapView];
 
-  mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
-                                                             target:self
-                                                           selector:@selector(_onTick:)
-                                                           userInfo:@{ RCTMapViewKey: mapView }
-                                                            repeats:YES];
+  mapView.regionChangeObserveTimer =
+    [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
+                            target:self
+                          selector:@selector(_onTick:)
+                          userInfo:@{ RCTMapViewKey: mapView }
+                           repeats:YES];
 
-  [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
+  [[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer
+                            forMode:NSRunLoopCommonModes];
 }
 
 - (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(__unused BOOL)animated
@@ -171,22 +395,29 @@ - (void)_regionChanged:(RCTMap *)mapView
   BOOL needZoom = NO;
   CGFloat newLongitudeDelta = 0.0f;
   MKCoordinateRegion region = mapView.region;
-  // On iOS 7, it's possible that we observe invalid locations during initialization of the map.
-  // Filter those out.
+
+  // On iOS 7, it's possible that we observe invalid locations during
+  // initialization of the map. Filter those out.
   if (!CLLocationCoordinate2DIsValid(region.center)) {
     return;
   }
-  // Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time.
-  // So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound.
-  if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) {
+
+  // Calculation on float is not 100% accurate. If user zoom to max/min and then
+  // move, it's likely the map will auto zoom to max/min from time to time.
+  // So let's try to make map zoom back to 99% max or 101% min so that there is
+  // some buffer, and moving the map won't constantly hit the max/min bound.
+  if (mapView.maxDelta > FLT_EPSILON &&
+      region.span.longitudeDelta > mapView.maxDelta) {
     needZoom = YES;
     newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
-  } else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) {
+  } else if (mapView.minDelta > FLT_EPSILON &&
+             region.span.longitudeDelta < mapView.minDelta) {
     needZoom = YES;
     newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer);
   }
   if (needZoom) {
-    region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
+    region.span.latitudeDelta =
+      region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
     region.span.longitudeDelta = newLongitudeDelta;
     mapView.region = region;
   }
@@ -203,15 +434,13 @@ - (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
       return;
     }
 
-  #define FLUSH_NAN(value) (isnan(value) ? 0 : value)
-
     mapView.onChange(@{
       @"continuous": @(continuous),
       @"region": @{
-        @"latitude": @(FLUSH_NAN(region.center.latitude)),
-        @"longitude": @(FLUSH_NAN(region.center.longitude)),
-        @"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
-        @"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
+        @"latitude": @(RCTZeroIfNaN(region.center.latitude)),
+        @"longitude": @(RCTZeroIfNaN(region.center.longitude)),
+        @"latitudeDelta": @(RCTZeroIfNaN(region.span.latitudeDelta)),
+        @"longitudeDelta": @(RCTZeroIfNaN(region.span.longitudeDelta)),
       }
     });
   }
diff --git a/React/Views/RCTPointAnnotation.h b/React/Views/RCTMapOverlay.h
similarity index 64%
rename from React/Views/RCTPointAnnotation.h
rename to React/Views/RCTMapOverlay.h
index 0646608d4805..a6fcdad5e452 100644
--- a/React/Views/RCTPointAnnotation.h
+++ b/React/Views/RCTMapOverlay.h
@@ -9,11 +9,10 @@
 
 #import <MapKit/MapKit.h>
 
-@interface RCTPointAnnotation : MKPointAnnotation <MKAnnotation>
+@interface RCTMapOverlay : MKPolyline <MKAnnotation>
 
 @property (nonatomic, copy) NSString *identifier;
-@property (nonatomic, assign) BOOL hasLeftCallout;
-@property (nonatomic, assign) BOOL hasRightCallout;
-@property (nonatomic, assign) BOOL animateDrop;
+@property (nonatomic, strong) UIColor *strokeColor;
+@property (nonatomic, assign) CGFloat lineWidth;
 
 @end
diff --git a/React/Views/RCTMapOverlay.m b/React/Views/RCTMapOverlay.m
new file mode 100644
index 000000000000..2a52dd5b28fb
--- /dev/null
+++ b/React/Views/RCTMapOverlay.m
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ */
+
+#import "RCTMapOverlay.h"
+
+@implementation RCTMapOverlay
+
+@end
diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h
index cafe771c8f5e..c67579da0236 100644
--- a/React/Views/RCTModalHostView.h
+++ b/React/Views/RCTModalHostView.h
@@ -10,6 +10,7 @@
 #import <UIKit/UIKit.h>
 
 #import "RCTInvalidating.h"
+#import "RCTView.h"
 
 @class RCTBridge;
 
@@ -18,6 +19,8 @@
 @property (nonatomic, assign, getter=isAnimated) BOOL animated;
 @property (nonatomic, assign, getter=isTransparent) BOOL transparent;
 
+@property (nonatomic, copy) RCTDirectEventBlock onShow;
+
 - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
 
 @end
diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m
index aecb77dbaec9..75e50105c488 100644
--- a/React/Views/RCTModalHostView.m
+++ b/React/Views/RCTModalHostView.m
@@ -58,13 +58,17 @@ - (void)notifyForBoundsChange:(CGRect)newBounds
 
 - (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
 {
+  RCTAssert([_modalViewController.view reactTag] == nil, @"Modal view can only have one subview");
   [subview addGestureRecognizer:_touchHandler];
+  subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
+                             UIViewAutoresizingFlexibleWidth;
   _modalViewController.view = subview;
 }
 
 - (void)removeReactSubview:(UIView *)subview
 {
   RCTAssert(subview == _modalViewController.view, @"Cannot remove view other than modal view");
+  [subview removeGestureRecognizer:_touchHandler];
   _modalViewController.view = nil;
 }
 
@@ -82,7 +86,11 @@ - (void)didMoveToWindow
 
   if (!_isPresented && self.window) {
     RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller");
-    [self.reactViewController presentViewController:_modalViewController animated:self.animated completion:nil];
+    [self.reactViewController presentViewController:_modalViewController animated:self.animated completion:^{
+      if (_onShow) {
+        _onShow(nil);
+      }
+    }];
     _isPresented = YES;
   }
 }
diff --git a/React/Views/RCTModalHostViewController.m b/React/Views/RCTModalHostViewController.m
index 055036726bcd..192e7ffb9d36 100644
--- a/React/Views/RCTModalHostViewController.m
+++ b/React/Views/RCTModalHostViewController.m
@@ -9,7 +9,8 @@
 
 #import "RCTModalHostViewController.h"
 
-@implementation RCTModalHostViewController {
+@implementation RCTModalHostViewController
+{
   CGRect _lastViewFrame;
 }
 
@@ -23,4 +24,14 @@ - (void)viewDidLayoutSubviews
   }
 }
 
+- (UIInterfaceOrientationMask)supportedInterfaceOrientations
+{
+  // Picking some defaults here, we should probably make this configurable
+  if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
+    return UIInterfaceOrientationMaskAll;
+  } else {
+    return UIInterfaceOrientationMaskPortrait;
+  }
+}
+
 @end
diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m
index 3ad022b213fb..be4e6b79e050 100644
--- a/React/Views/RCTModalHostViewManager.m
+++ b/React/Views/RCTModalHostViewManager.m
@@ -20,18 +20,12 @@ @implementation RCTModalHostViewManager
 
 RCT_EXPORT_MODULE()
 
-- (instancetype)init
-{
-  if ((self = [super init])) {
-    _hostViews = [NSHashTable weakObjectsHashTable];
-  }
-
-  return self;
-}
-
 - (UIView *)view
 {
   UIView *view = [[RCTModalHostView alloc] initWithBridge:self.bridge];
+  if (_hostViews) {
+    _hostViews = [NSHashTable weakObjectsHashTable];
+  }
   [_hostViews addObject:view];
   return view;
 }
@@ -46,5 +40,6 @@ - (void)invalidate
 
 RCT_EXPORT_VIEW_PROPERTY(animated, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
 
 @end
diff --git a/React/Views/RCTNavItem.h b/React/Views/RCTNavItem.h
index 418d5c5c97cf..fdfeb66e4f98 100644
--- a/React/Views/RCTNavItem.h
+++ b/React/Views/RCTNavItem.h
@@ -31,7 +31,7 @@
 @property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
 @property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
 
-@property (nonatomic, copy) RCTBubblingEventBlock onNavLeftButtonTap;
-@property (nonatomic, copy) RCTBubblingEventBlock onNavRightButtonTap;
+@property (nonatomic, copy) RCTBubblingEventBlock onLeftButtonPress;
+@property (nonatomic, copy) RCTBubblingEventBlock onRightButtonPress;
 
 @end
diff --git a/React/Views/RCTNavItem.m b/React/Views/RCTNavItem.m
index 5e4043f8495f..5fa8005c94f9 100644
--- a/React/Views/RCTNavItem.m
+++ b/React/Views/RCTNavItem.m
@@ -67,14 +67,14 @@ - (UIBarButtonItem *)leftButtonItem
       [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
                                        style:UIBarButtonItemStylePlain
                                       target:self
-                                      action:@selector(handleNavLeftButtonTapped)];
+                                      action:@selector(handleLeftButtonPress)];
 
     } else if (_leftButtonTitle.length) {
       _leftButtonItem =
       [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
                                        style:UIBarButtonItemStylePlain
                                       target:self
-                                      action:@selector(handleNavLeftButtonTapped)];
+                                      action:@selector(handleLeftButtonPress)];
     } else {
       _leftButtonItem = nil;
     }
@@ -82,10 +82,10 @@ - (UIBarButtonItem *)leftButtonItem
   return _leftButtonItem;
 }
 
-- (void)handleNavLeftButtonTapped
+- (void)handleLeftButtonPress
 {
-  if (_onNavLeftButtonTap) {
-    _onNavLeftButtonTap(nil);
+  if (_onLeftButtonPress) {
+    _onLeftButtonPress(nil);
   }
 }
 
@@ -109,14 +109,14 @@ - (UIBarButtonItem *)rightButtonItem
       [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
                                        style:UIBarButtonItemStylePlain
                                       target:self
-                                      action:@selector(handleNavRightButtonTapped)];
+                                      action:@selector(handleRightButtonPress)];
 
     } else if (_rightButtonTitle.length) {
       _rightButtonItem =
       [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
                                        style:UIBarButtonItemStylePlain
                                       target:self
-                                      action:@selector(handleNavRightButtonTapped)];
+                                      action:@selector(handleRightButtonPress)];
     } else {
       _rightButtonItem = nil;
     }
@@ -124,10 +124,10 @@ - (UIBarButtonItem *)rightButtonItem
   return _rightButtonItem;
 }
 
-- (void)handleNavRightButtonTapped
+- (void)handleRightButtonPress
 {
-  if (_onNavRightButtonTap) {
-    _onNavRightButtonTap(nil);
+  if (_onRightButtonPress) {
+    _onRightButtonPress(nil);
   }
 }
 
diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m
index 040b7ffceac5..be4f879dde6d 100644
--- a/React/Views/RCTNavItemManager.m
+++ b/React/Views/RCTNavItemManager.m
@@ -39,7 +39,7 @@ - (UIView *)view
 RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
 RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
 
-RCT_EXPORT_VIEW_PROPERTY(onNavLeftButtonTap, RCTBubblingEventBlock)
-RCT_EXPORT_VIEW_PROPERTY(onNavRightButtonTap, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLeftButtonPress, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onRightButtonPress, RCTBubblingEventBlock)
 
 @end
diff --git a/React/Views/RCTNavigator.m b/React/Views/RCTNavigator.m
index 460a5fc6ef17..10e415ab0ecf 100644
--- a/React/Views/RCTNavigator.m
+++ b/React/Views/RCTNavigator.m
@@ -361,6 +361,13 @@ - (void)navigationController:(UINavigationController *)navigationController
       (RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextFromViewControllerKey];
     RCTWrapperViewController *toController =
       (RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey];
+      
+    // This may be triggered by a navigation controller unrelated to me: if so, ignore.
+    if (fromController.navigationController != _navigationController ||
+        toController.navigationController != _navigationController) {
+      return;
+    }
+    
     NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
     NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
     CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
diff --git a/React/Views/RCTPicker.h b/React/Views/RCTPicker.h
index 8fb39b5a5de3..0066d140f8e4 100644
--- a/React/Views/RCTPicker.h
+++ b/React/Views/RCTPicker.h
@@ -15,6 +15,11 @@
 
 @property (nonatomic, copy) NSArray<NSDictionary *> *items;
 @property (nonatomic, assign) NSInteger selectedIndex;
+
+@property (nonatomic, strong) UIColor *color;
+@property (nonatomic, strong) UIFont *font;
+@property (nonatomic, assign) NSTextAlignment textAlign;
+
 @property (nonatomic, copy) RCTBubblingEventBlock onChange;
 
 @end
diff --git a/React/Views/RCTPicker.m b/React/Views/RCTPicker.m
index f979c2116b77..b83a9667cbf0 100644
--- a/React/Views/RCTPicker.m
+++ b/React/Views/RCTPicker.m
@@ -9,6 +9,7 @@
 
 #import "RCTPicker.h"
 
+#import "RCTConvert.h"
 #import "RCTUtils.h"
 
 @interface RCTPicker() <UIPickerViewDataSource, UIPickerViewDelegate>
@@ -19,7 +20,10 @@ @implementation RCTPicker
 - (instancetype)initWithFrame:(CGRect)frame
 {
   if ((self = [super initWithFrame:frame])) {
+    _color = [UIColor blackColor];
+    _font = [UIFont systemFontOfSize:21]; // TODO: selected title default should be 23.5
     _selectedIndex = NSNotFound;
+    _textAlign = NSTextAlignmentCenter;
     self.delegate = self;
   }
   return self;
@@ -59,20 +63,33 @@ - (NSInteger)pickerView:(__unused UIPickerView *)pickerView
 
 #pragma mark - UIPickerViewDelegate methods
 
-- (NSDictionary *)itemForRow:(NSInteger)row
+- (NSString *)pickerView:(__unused UIPickerView *)pickerView
+             titleForRow:(NSInteger)row
+            forComponent:(__unused NSInteger)component
 {
-  return _items[row];
+  return [RCTConvert NSString:_items[row][@"label"]];
 }
 
-- (id)valueForRow:(NSInteger)row
+- (UIView *)pickerView:(UIPickerView *)pickerView
+            viewForRow:(NSInteger)row
+          forComponent:(NSInteger)component
+           reusingView:(UILabel *)label
 {
-  return [self itemForRow:row][@"value"];
-}
+  if (!label) {
+    label = [[UILabel alloc] initWithFrame:(CGRect){
+      CGPointZero,
+      {
+        [pickerView rowSizeForComponent:component].width,
+        [pickerView rowSizeForComponent:component].height,
+      }
+    }];
+  }
 
-- (NSString *)pickerView:(__unused UIPickerView *)pickerView
-             titleForRow:(NSInteger)row forComponent:(__unused NSInteger)component
-{
-  return [self itemForRow:row][@"label"];
+  label.font = _font;
+  label.textColor = _color;
+  label.textAlignment = _textAlign;
+  label.text = [self pickerView:pickerView titleForRow:row forComponent:component];
+  return label;
 }
 
 - (void)pickerView:(__unused UIPickerView *)pickerView
@@ -82,7 +99,7 @@ - (void)pickerView:(__unused UIPickerView *)pickerView
   if (_onChange) {
     _onChange(@{
       @"newIndex": @(row),
-      @"newValue": [self valueForRow:row]
+      @"newValue": RCTNullIfNil(_items[row][@"value"]),
     });
   }
 }
diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m
index 79d9c4758a63..54d75f210c9c 100644
--- a/React/Views/RCTPickerManager.m
+++ b/React/Views/RCTPickerManager.m
@@ -10,7 +10,6 @@
 #import "RCTPickerManager.h"
 
 #import "RCTBridge.h"
-#import "RCTConvert.h"
 #import "RCTPicker.h"
 
 @implementation RCTPickerManager
@@ -22,9 +21,27 @@ - (UIView *)view
   return [RCTPicker new];
 }
 
-RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
+RCT_EXPORT_VIEW_PROPERTY(items, NSArray<NSDictionary *>)
 RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
 RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(textAlign, NSTextAlignment)
+RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTPicker)
+{
+  view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
+}
+RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, __unused RCTPicker)
+{
+  view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
+}
+RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, __unused RCTPicker)
+{
+  view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
+}
+RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTPicker)
+{
+  view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
+}
 
 - (NSDictionary<NSString *, id> *)constantsToExport
 {
diff --git a/React/Views/RCTRefreshControl.h b/React/Views/RCTRefreshControl.h
new file mode 100644
index 000000000000..4316576aab96
--- /dev/null
+++ b/React/Views/RCTRefreshControl.h
@@ -0,0 +1,19 @@
+/**
+ * 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.
+ */
+
+#import <UIKit/UIKit.h>
+
+#import "RCTComponent.h"
+
+@interface RCTRefreshControl : UIRefreshControl
+
+@property (nonatomic, copy) NSString *title;
+@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
+
+@end
diff --git a/React/Views/RCTRefreshControl.m b/React/Views/RCTRefreshControl.m
new file mode 100644
index 000000000000..282cd7f30308
--- /dev/null
+++ b/React/Views/RCTRefreshControl.m
@@ -0,0 +1,99 @@
+/**
+ * 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.
+ */
+
+#import "RCTRefreshControl.h"
+
+#import "RCTUtils.h"
+
+@implementation RCTRefreshControl {
+  BOOL _initialRefreshingState;
+  BOOL _isInitialRender;
+}
+
+- (instancetype)init
+{
+  if ((self = [super init])) {
+    [self addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
+    _isInitialRender = true;
+  }
+  return self;
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
+
+- (void)layoutSubviews
+{
+  [super layoutSubviews];
+
+  // If the control is refreshing when mounted we need to call
+  // beginRefreshing in layoutSubview or it doesn't work.
+  if (_isInitialRender && _initialRefreshingState) {
+    [self beginRefreshing];
+  }
+  _isInitialRender = false;
+}
+
+- (void)beginRefreshing
+{
+  // When using begin refreshing we need to adjust the ScrollView content offset manually.
+  UIScrollView *scrollView = (UIScrollView *)self.superview;
+  CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
+  // Don't animate when the prop is set initialy.
+  if (_isInitialRender) {
+    scrollView.contentOffset = offset;
+    [super beginRefreshing];
+  } else {
+    // `beginRefreshing` must be called after the animation is done. This is why it is impossible
+    // to use `setContentOffset` with `animated:YES`.
+    [UIView animateWithDuration:0.25
+                          delay:0
+                        options:UIViewAnimationOptionBeginFromCurrentState
+                     animations:^(void) {
+                       [scrollView setContentOffset:offset];
+                     } completion:^(__unused BOOL finished) {
+                       [super beginRefreshing];
+                     }];
+  }
+}
+
+- (NSString *)title
+{
+  return self.attributedTitle.string;
+}
+
+- (void)setTitle:(NSString *)title
+{
+  self.attributedTitle = [[NSAttributedString alloc] initWithString:title];
+}
+
+- (void)setRefreshing:(BOOL)refreshing
+{
+  if (self.refreshing != refreshing) {
+    if (refreshing) {
+      // If it is the initial render, beginRefreshing will get called
+      // in layoutSubviews.
+      if (_isInitialRender) {
+        _initialRefreshingState = refreshing;
+      } else {
+        [self beginRefreshing];
+      }
+    } else {
+      [self endRefreshing];
+    }
+  }
+}
+
+- (void)refreshControlValueChanged
+{
+  if (_onRefresh) {
+    _onRefresh(nil);
+  }
+}
+
+@end
diff --git a/React/Views/RCTRefreshControlManager.h b/React/Views/RCTRefreshControlManager.h
new file mode 100644
index 000000000000..8d1c3f961668
--- /dev/null
+++ b/React/Views/RCTRefreshControlManager.h
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ */
+
+#import "RCTViewManager.h"
+
+@interface RCTRefreshControlManager : RCTViewManager
+
+@end
diff --git a/React/Views/RCTRefreshControlManager.m b/React/Views/RCTRefreshControlManager.m
new file mode 100644
index 000000000000..bc479a150ebc
--- /dev/null
+++ b/React/Views/RCTRefreshControlManager.m
@@ -0,0 +1,28 @@
+/**
+ * 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.
+ */
+
+#import "RCTRefreshControlManager.h"
+
+#import "RCTRefreshControl.h"
+
+@implementation RCTRefreshControlManager
+
+RCT_EXPORT_MODULE()
+
+- (UIView *)view
+{
+  return [RCTRefreshControl new];
+}
+
+RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(title, NSString)
+
+@end
diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h
index d44be6fafae1..47fb1753b7ad 100644
--- a/React/Views/RCTScrollView.h
+++ b/React/Views/RCTScrollView.h
@@ -47,6 +47,9 @@
 @property (nonatomic, assign) int snapToInterval;
 @property (nonatomic, copy) NSString *snapToAlignment;
 @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
+@property (nonatomic, copy) RCTDirectEventBlock onRefreshStart;
+
+- (void)endRefreshing;
 
 @end
 
diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m
index e125061f4b78..af0091e03a71 100644
--- a/React/Views/RCTScrollView.m
+++ b/React/Views/RCTScrollView.m
@@ -14,6 +14,7 @@
 #import "RCTConvert.h"
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
+#import "RCTRefreshControl.h"
 #import "RCTUIManager.h"
 #import "RCTUtils.h"
 #import "UIView+Private.h"
@@ -58,11 +59,6 @@ - (instancetype)initWithType:(RCTScrollEventType)type
 
 RCT_NOT_IMPLEMENTED(- (instancetype)init)
 
-- (uint16_t)coalescingKey
-{
-  return 0;
-}
-
 - (NSDictionary *)body
 {
   NSDictionary *body = @{
@@ -133,6 +129,11 @@ + (NSString *)moduleDotMethod
   return @"RCTEventEmitter.receiveEvent";
 }
 
+- (NSArray *)arguments
+{
+  return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), [self body]];
+}
+
 @end
 
 /**
@@ -144,6 +145,7 @@ @interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
 
 @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
 @property (nonatomic, assign) BOOL centerContent;
+@property (nonatomic, strong) RCTRefreshControl *refreshControl;
 
 @end
 
@@ -181,7 +183,7 @@ - (BOOL)_shouldDisableScrollInteraction
 
 - (void)handleCustomPan:(__unused UIPanGestureRecognizer *)sender
 {
-  if ([self _shouldDisableScrollInteraction]) {
+  if ([self _shouldDisableScrollInteraction] && ![[RCTUIManager JSResponder] isKindOfClass:[RCTScrollView class]]) {
     self.panGestureRecognizer.enabled = NO;
     self.panGestureRecognizer.enabled = YES;
     // TODO: If mid bounce, animate the scroll view to a non-bounced position
@@ -273,6 +275,11 @@ - (void)dockClosestSectionHeader
 {
   UIView *contentView = [self contentView];
   CGFloat scrollTop = self.bounds.origin.y + self.contentInset.top;
+  // If the RefreshControl is refreshing, remove it's height so sticky headers are
+  // positioned properly when scrolling down while refreshing.
+  if (self.refreshControl != nil && self.refreshControl.refreshing) {
+    scrollTop -= self.refreshControl.frame.size.height;
+  }
 
   // Find the section headers that need to be docked
   __block UIView *previousHeader = nil;
@@ -280,10 +287,12 @@ - (void)dockClosestSectionHeader
   __block UIView *nextHeader = nil;
   NSUInteger subviewCount = contentView.reactSubviews.count;
   [_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:
-   ^(NSUInteger idx, __unused BOOL *stop) {
+   ^(NSUInteger idx, BOOL *stop) {
 
+    // If the subviews are out of sync with the sticky header indices don't
+    // do anything.
     if (idx >= subviewCount) {
-      RCTLogError(@"Sticky header index %zd was outside the range {0, %zd}", idx, subviewCount);
+      *stop = YES;
       return;
     }
 
@@ -342,8 +351,14 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
 {
   __block UIView *hitView;
 
+  NSArray *subviews = [self contentView].reactSubviews;
+  NSUInteger subviewCount = subviews.count;
   [_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
-    UIView *stickyHeader = [self contentView].reactSubviews[idx];
+    if (idx >= subviewCount) {
+      *stop = YES;
+      return;
+    }
+    UIView *stickyHeader = subviews[idx];
     CGPoint convertedPoint = [stickyHeader convertPoint:point fromView:self];
     hitView = [stickyHeader hitTest:convertedPoint withEvent:event];
     *stop = (hitView != nil);
@@ -352,6 +367,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
   return hitView ?: [super hitTest:point withEvent:event];
 }
 
+- (void)setRefreshControl:(RCTRefreshControl *)refreshControl
+{
+  if (_refreshControl) {
+    [_refreshControl removeFromSuperview];
+  }
+  _refreshControl = refreshControl;
+  [self addSubview:_refreshControl];
+}
+
 @end
 
 @implementation RCTScrollView
@@ -365,7 +389,7 @@ @implementation RCTScrollView
   CGRect _lastClippedToRect;
 }
 
-@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate;
+@synthesize nativeScrollDelegate = _nativeScrollDelegate;
 
 - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
 {
@@ -400,20 +424,31 @@ - (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
 
 - (void)insertReactSubview:(UIView *)view atIndex:(__unused NSInteger)atIndex
 {
-  RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
-  _contentView = view;
-  [_scrollView addSubview:view];
+  if ([view isKindOfClass:[RCTRefreshControl class]]) {
+    _scrollView.refreshControl = (RCTRefreshControl*)view;
+  } else {
+    RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
+    _contentView = view;
+    [_scrollView addSubview:view];
+  }
 }
 
 - (void)removeReactSubview:(UIView *)subview
 {
-  RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
-  _contentView = nil;
-  [subview removeFromSuperview];
+  if ([subview isKindOfClass:[RCTRefreshControl class]]) {
+    _scrollView.refreshControl = nil;
+  } else {
+    RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
+    _contentView = nil;
+    [subview removeFromSuperview];
+  }
 }
 
 - (NSArray<UIView *> *)reactSubviews
 {
+  if (_contentView && _scrollView.refreshControl) {
+    return @[_contentView, _scrollView.refreshControl];
+  }
   return _contentView ? @[_contentView] : @[];
 }
 
@@ -532,14 +567,14 @@ - (void)refreshContentInset
 - (void)delegateMethod:(UIScrollView *)scrollView           \
 {                                                           \
   [_eventDispatcher sendScrollEventWithType:eventName reactTag:self.reactTag scrollView:scrollView userData:nil]; \
-  if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \
-    [_nativeMainScrollDelegate delegateMethod:scrollView]; \
+  if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \
+    [_nativeScrollDelegate delegateMethod:scrollView]; \
   } \
 }
 
 #define RCT_FORWARD_SCROLL_EVENT(call) \
-if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \
-  [_nativeMainScrollDelegate call]; \
+if ([_nativeScrollDelegate respondsToSelector:_cmd]) { \
+  [_nativeScrollDelegate call]; \
 }
 
 RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, RCTScrollEventTypeEndDeceleration)
@@ -696,8 +731,8 @@ - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)vi
 
 - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
 {
-  if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) {
-    return [_nativeMainScrollDelegate scrollViewShouldScrollToTop:scrollView];
+  if ([_nativeScrollDelegate respondsToSelector:_cmd]) {
+    return [_nativeScrollDelegate scrollViewShouldScrollToTop:scrollView];
   }
   return YES;
 }
@@ -793,6 +828,17 @@ - (void)reactBridgeDidFinishTransaction
     _scrollView.contentSize = contentSize;
     _scrollView.contentOffset = newOffset;
   }
+
+  if (RCT_DEBUG) {
+    // Validate that sticky headers are not out of range.
+    NSUInteger subviewCount = _scrollView.contentView.reactSubviews.count;
+    NSUInteger lastIndex = _scrollView.stickyHeaderIndices.lastIndex;
+    if (lastIndex != NSNotFound && lastIndex >= subviewCount) {
+      RCTLogWarn(@"Sticky header index %zd was outside the range {0, %zd}",
+                 lastIndex, subviewCount);
+    }
+  }
+
   [_scrollView dockClosestSectionHeader];
 }
 
@@ -816,6 +862,7 @@ - (void)setter:(type)value                           \
 RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, BOOL)
 RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, CGFloat)
 RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, UIScrollViewIndicatorStyle)
 RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, UIScrollViewKeyboardDismissMode)
 RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, CGFloat)
 RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, CGFloat)
@@ -844,6 +891,34 @@ - (id)valueForUndefinedKey:(NSString *)key
   return [_scrollView valueForKey:key];
 }
 
+- (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
+{
+  if (!onRefreshStart) {
+    _onRefreshStart = nil;
+    _scrollView.refreshControl = nil;
+    return;
+  }
+  _onRefreshStart = [onRefreshStart copy];
+
+  if (!_scrollView.refreshControl) {
+    RCTRefreshControl *refreshControl = [[RCTRefreshControl alloc] init];
+    [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
+    _scrollView.refreshControl = refreshControl;
+  }
+}
+
+- (void)refreshControlValueChanged
+{
+  if (self.onRefreshStart) {
+    self.onRefreshStart(nil);
+  }
+}
+
+- (void)endRefreshing
+{
+  [_scrollView.refreshControl endRefreshing];
+}
+
 @end
 
 @implementation RCTEventDispatcher (RCTScrollView)
diff --git a/React/Views/RCTScrollViewManager.h b/React/Views/RCTScrollViewManager.h
index 83b3126e8f1c..a53bf4c80796 100644
--- a/React/Views/RCTScrollViewManager.h
+++ b/React/Views/RCTScrollViewManager.h
@@ -19,4 +19,3 @@
 @interface RCTScrollViewManager : RCTViewManager
 
 @end
-
diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m
index e90bda75ecf1..9ab7ca806bc4 100644
--- a/React/Views/RCTScrollViewManager.m
+++ b/React/Views/RCTScrollViewManager.m
@@ -29,6 +29,12 @@ @implementation RCTConvert (UIScrollView)
   @"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
 }), UIScrollViewKeyboardDismissModeNone, integerValue)
 
+RCT_ENUM_CONVERTER(UIScrollViewIndicatorStyle, (@{
+  @"default": @(UIScrollViewIndicatorStyleDefault),
+  @"black": @(UIScrollViewIndicatorStyleBlack),
+  @"white": @(UIScrollViewIndicatorStyleWhite),
+}), UIScrollViewIndicatorStyleDefault, integerValue)
+
 @end
 
 @implementation RCTScrollViewManager
@@ -49,6 +55,7 @@ - (UIView *)view
 RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
 RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat)
 RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle)
 RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode)
 RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat)
 RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat)
@@ -65,11 +72,11 @@ - (UIView *)view
 RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
 RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
 RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
+RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock)
 
 - (NSDictionary<NSString *, id> *)constantsToExport
 {
   return @{
-    // TODO: unused - remove these?
     @"DecelerationRate": @{
       @"normal": @(UIScrollViewDecelerationRateNormal),
       @"fast": @(UIScrollViewDecelerationRateFast),
@@ -80,7 +87,8 @@ - (UIView *)view
 RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
                   callback:(RCTResponseSenderBlock)callback)
 {
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
+  [self.bridge.uiManager addUIBlock:
+   ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
 
     RCTScrollView *view = viewRegistry[reactTag];
     if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
@@ -97,9 +105,10 @@ - (UIView *)view
 }
 
 RCT_EXPORT_METHOD(calculateChildFrames:(nonnull NSNumber *)reactTag
-                    callback:(RCTResponseSenderBlock)callback)
+                  callback:(RCTResponseSenderBlock)callback)
 {
-  [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
+  [self.bridge.uiManager addUIBlock:
+   ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
 
     RCTScrollView *view = viewRegistry[reactTag];
     if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
@@ -114,6 +123,54 @@ - (UIView *)view
   }];
 }
 
+RCT_EXPORT_METHOD(endRefreshing:(nonnull NSNumber *)reactTag)
+{
+  [self.bridge.uiManager addUIBlock:
+   ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
+
+    RCTScrollView *view = viewRegistry[reactTag];
+    if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
+      RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
+      return;
+    }
+
+    [view endRefreshing];
+  }];
+}
+
+RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag
+                  offsetX:(CGFloat)x
+                  offsetY:(CGFloat)y
+                  animated:(BOOL)animated)
+{
+  [self.bridge.uiManager addUIBlock:
+   ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
+    UIView *view = viewRegistry[reactTag];
+    if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
+      [(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){x, y} animated:animated];
+    } else {
+      RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ "
+                  "with tag #%@", view, reactTag);
+    }
+  }];
+}
+
+RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag
+                  withRect:(CGRect)rect
+                  animated:(BOOL)animated)
+{
+  [self.bridge.uiManager addUIBlock:
+   ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
+    UIView *view = viewRegistry[reactTag];
+    if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
+      [(id<RCTScrollableProtocol>)view zoomToRect:rect animated:animated];
+    } else {
+      RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ "
+                  "with tag #%@", view, reactTag);
+    }
+  }];
+}
+
 - (NSArray<NSString *> *)customDirectEventTypes
 {
   return @[
diff --git a/React/Views/RCTScrollableProtocol.h b/React/Views/RCTScrollableProtocol.h
index f612a7afa2cc..f871d51925d4 100644
--- a/React/Views/RCTScrollableProtocol.h
+++ b/React/Views/RCTScrollableProtocol.h
@@ -15,7 +15,7 @@
  */
 @protocol RCTScrollableProtocol
 
-@property (nonatomic, weak) NSObject<UIScrollViewDelegate> *nativeMainScrollDelegate;
+@property (nonatomic, weak) NSObject<UIScrollViewDelegate> *nativeScrollDelegate;
 @property (nonatomic, readonly) CGSize contentSize;
 
 - (void)scrollToOffset:(CGPoint)offset;
diff --git a/React/Views/RCTSegmentedControlManager.m b/React/Views/RCTSegmentedControlManager.m
index e56be4cf0317..c44f822cf323 100644
--- a/React/Views/RCTSegmentedControlManager.m
+++ b/React/Views/RCTSegmentedControlManager.m
@@ -22,7 +22,7 @@ - (UIView *)view
   return [RCTSegmentedControl new];
 }
 
-RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray)
+RCT_EXPORT_VIEW_PROPERTY(values, NSArray<NSString *>)
 RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
 RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
 RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL)
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index e565e73d14ed..e845060f9595 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -63,15 +63,17 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
 
 @property (nonatomic, assign) CGFloat width;
 @property (nonatomic, assign) CGFloat height;
-@property (nonatomic, assign) CGFloat minWidth;
-@property (nonatomic, assign) CGFloat minHeight;
-@property (nonatomic, assign) CGFloat maxWidth;
-@property (nonatomic, assign) CGFloat maxHeight;
 @property (nonatomic, assign) CGRect frame;
 
 - (void)setTopLeft:(CGPoint)topLeft;
 - (void)setSize:(CGSize)size;
 
+/**
+ * Set the natural size of the view, which is used when no explicit size is set.
+ * Use UIViewNoIntrinsicMetric to ignore a dimension.
+ */
+- (void)setIntrinsicContentSize:(CGSize)size;
+
 /**
  * Size flexibility type used to find size constraints.
  * Default to RCTRootViewSizeFlexibilityNone
@@ -164,10 +166,7 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
 - (void)setTextComputed NS_REQUIRES_SUPER;
 - (BOOL)isTextDirty;
 
-/**
- * Triggers a recalculation of the shadow view's layout.
- */
-- (void)updateLayout NS_REQUIRES_SUPER;
+- (void)didSetProps:(NSArray<NSString *> *)changedProps NS_REQUIRES_SUPER;
 
 /**
  * Computes the recursive offset, meaning the sum of all descendant offsets -
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index e47bec2443e0..c755bbade042 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -241,7 +241,7 @@ - (void)applySizeConstraints
   [self applySizeConstraints];
 
   [self fillCSSNode:_cssNode];
-  layoutNode(_cssNode, CSS_UNDEFINED, CSS_DIRECTION_INHERIT);
+  layoutNode(_cssNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT);
 
   NSMutableSet<RCTShadowView *> *viewsWithNewFrame = [NSMutableSet set];
   [self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
@@ -507,10 +507,6 @@ - (CGFloat)getProp                                                     \
 
 RCT_DIMENSIONS_PROPERTY(Width, width, WIDTH, dimensions)
 RCT_DIMENSIONS_PROPERTY(Height, height, HEIGHT, dimensions)
-RCT_DIMENSIONS_PROPERTY(MinWidth, minWidth, WIDTH, minDimensions)
-RCT_DIMENSIONS_PROPERTY(MinHeight, minHeight, HEIGHT, minDimensions)
-RCT_DIMENSIONS_PROPERTY(MaxWidth, maxWidth, WIDTH, maxDimensions)
-RCT_DIMENSIONS_PROPERTY(MaxHeight, maxHeight, HEIGHT, maxDimensions)
 
 // Position
 
@@ -539,6 +535,29 @@ - (void)setFrame:(CGRect)frame
   [self dirtyLayout];
 }
 
+static inline BOOL
+RCTAssignSuggestedDimension(css_node_t *css_node, int dimension, CGFloat amount)
+{
+  if (amount != UIViewNoIntrinsicMetric
+      && isnan(css_node->style.dimensions[dimension])) {
+    css_node->style.dimensions[dimension] = amount;
+    return YES;
+  }
+  return NO;
+}
+
+- (void)setIntrinsicContentSize:(CGSize)size
+{
+  if (_cssNode->style.flex == 0) {
+    BOOL dirty = NO;
+    dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_HEIGHT, size.height);
+    dirty |= RCTAssignSuggestedDimension(_cssNode, CSS_WIDTH, size.width);
+    if (dirty) {
+      [self dirtyLayout];
+    }
+  }
+}
+
 - (void)setTopLeft:(CGPoint)topLeft
 {
   _cssNode->style.position[CSS_LEFT] = topLeft.x;
@@ -580,7 +599,7 @@ - (void)setBackgroundColor:(UIColor *)color
   [self dirtyPropagation];
 }
 
-- (void)updateLayout
+- (void)didSetProps:(__unused NSArray<NSString *> *)changedProps
 {
   if (_recomputePadding) {
     RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding);
diff --git a/React/Views/RCTSlider.h b/React/Views/RCTSlider.h
index 0c2269ce8436..c84593c50c33 100644
--- a/React/Views/RCTSlider.h
+++ b/React/Views/RCTSlider.h
@@ -20,5 +20,10 @@
 @property (nonatomic, assign) float lastValue;
 
 @property (nonatomic, strong) UIImage *trackImage;
+@property (nonatomic, strong) UIImage *minimumTrackImage;
+@property (nonatomic, strong) UIImage *maximumTrackImage;
+
+@property (nonatomic, strong) UIImage *thumbImage;
+
 
 @end
diff --git a/React/Views/RCTSlider.m b/React/Views/RCTSlider.m
index 0886c8e20ef3..4032794eb19b 100644
--- a/React/Views/RCTSlider.m
+++ b/React/Views/RCTSlider.m
@@ -9,6 +9,8 @@
 
 #import "RCTSlider.h"
 
+#import <tgmath.h>
+
 @implementation RCTSlider
 {
   float _unclippedValue;
@@ -36,15 +38,54 @@ - (void)setTrackImage:(UIImage *)trackImage
 {
   if (trackImage != _trackImage) {
     _trackImage = trackImage;
+    CGFloat width = trackImage.size.width / 2;
+    UIImage *minimumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){
+      0, width, 0, width
+    } resizingMode:UIImageResizingModeStretch];
+    UIImage *maximumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){
+      0, width, 0, width
+    } resizingMode:UIImageResizingModeStretch];
+    [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
+    [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
+  }
+}
 
-    CGFloat width = trackImage.size.width;
+- (void)setMinimumTrackImage:(UIImage *)minimumTrackImage
+{
+  _trackImage = nil;
+  minimumTrackImage = [minimumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){
+    0, minimumTrackImage.size.width, 0, 0
+  } resizingMode:UIImageResizingModeStretch];
+  [self setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
+}
 
-    UIImage *minimumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){0, width, 0, 0}];
-    UIImage *maximumTrackImage = [trackImage resizableImageWithCapInsets:(UIEdgeInsets){0, 0, 0, width}];
+- (UIImage *)minimumTrackImage
+{
+  return [self thumbImageForState:UIControlStateNormal];
+}
 
-    [super setMinimumTrackImage:minimumTrackImage forState:UIControlStateNormal];
-    [super setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
-  }
+- (void)setMaximumTrackImage:(UIImage *)maximumTrackImage
+{
+  _trackImage = nil;
+  maximumTrackImage = [maximumTrackImage resizableImageWithCapInsets:(UIEdgeInsets){
+    0, 0, 0, maximumTrackImage.size.width
+  } resizingMode:UIImageResizingModeStretch];
+  [self setMaximumTrackImage:maximumTrackImage forState:UIControlStateNormal];
+}
+
+- (UIImage *)maximumTrackImage
+{
+  return [self thumbImageForState:UIControlStateNormal];
+}
+
+- (void)setThumbImage:(UIImage *)thumbImage
+{
+  [self setThumbImage:thumbImage forState:UIControlStateNormal];
+}
+
+- (UIImage *)thumbImage
+{
+  return [self thumbImageForState:UIControlStateNormal];
 }
 
 @end
diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m
index 92eb4b7c4709..50638f2cb9c2 100644
--- a/React/Views/RCTSliderManager.m
+++ b/React/Views/RCTSliderManager.m
@@ -77,12 +77,15 @@ - (void)sliderTouchEnd:(RCTSlider *)sender
 RCT_EXPORT_VIEW_PROPERTY(value, float);
 RCT_EXPORT_VIEW_PROPERTY(step, float);
 RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage);
+RCT_EXPORT_VIEW_PROPERTY(minimumTrackImage, UIImage);
+RCT_EXPORT_VIEW_PROPERTY(maximumTrackImage, UIImage);
 RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
 RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
 RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
 RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
 RCT_EXPORT_VIEW_PROPERTY(onValueChange, RCTBubblingEventBlock);
 RCT_EXPORT_VIEW_PROPERTY(onSlidingComplete, RCTBubblingEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(thumbImage, UIImage);
 RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSlider)
 {
   if (json) {
diff --git a/React/Views/RCTTabBarItem.h b/React/Views/RCTTabBarItem.h
index def1abf6c347..cee2df982e47 100644
--- a/React/Views/RCTTabBarItem.h
+++ b/React/Views/RCTTabBarItem.h
@@ -13,7 +13,9 @@
 
 @interface RCTTabBarItem : UIView
 
-@property (nonatomic, copy) id icon;
+@property (nonatomic, copy) id /* NSString or NSNumber */ badge;
+@property (nonatomic, strong) UIImage *icon;
+@property (nonatomic, assign) UITabBarSystemItem systemIcon;
 @property (nonatomic, assign, getter=isSelected) BOOL selected;
 @property (nonatomic, readonly) UITabBarItem *barItem;
 @property (nonatomic, copy) RCTBubblingEventBlock onPress;
diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m
index 8699ff96d71a..a05ae75ee0af 100644
--- a/React/Views/RCTTabBarItem.m
+++ b/React/Views/RCTTabBarItem.m
@@ -13,73 +13,78 @@
 #import "RCTLog.h"
 #import "UIView+React.h"
 
+@implementation RCTConvert (UITabBarSystemItem)
+
+RCT_ENUM_CONVERTER(UITabBarSystemItem, (@{
+  @"bookmarks": @(UITabBarSystemItemBookmarks),
+  @"contacts": @(UITabBarSystemItemContacts),
+  @"downloads": @(UITabBarSystemItemDownloads),
+  @"favorites": @(UITabBarSystemItemFavorites),
+  @"featured": @(UITabBarSystemItemFeatured),
+  @"history": @(UITabBarSystemItemHistory),
+  @"more": @(UITabBarSystemItemMore),
+  @"most-recent": @(UITabBarSystemItemMostRecent),
+  @"most-viewed": @(UITabBarSystemItemMostViewed),
+  @"recents": @(UITabBarSystemItemRecents),
+  @"search": @(UITabBarSystemItemSearch),
+  @"top-rated": @(UITabBarSystemItemTopRated),
+}), NSNotFound, integerValue)
+
+@end
+
 @implementation RCTTabBarItem
 
 @synthesize barItem = _barItem;
 
+- (instancetype)initWithFrame:(CGRect)frame
+{
+  if ((self = [super initWithFrame:frame])) {
+    _systemIcon = NSNotFound;
+  }
+  return self;
+}
+
 - (UITabBarItem *)barItem
 {
   if (!_barItem) {
     _barItem = [UITabBarItem new];
+    _systemIcon = NSNotFound;
   }
   return _barItem;
 }
 
-- (void)setIcon:(id)icon
+- (void)setBadge:(id)badge
 {
-  static NSDictionary *systemIcons;
-  static dispatch_once_t onceToken;
-  dispatch_once(&onceToken, ^{
-    systemIcons = @{
-      @"bookmarks": @(UITabBarSystemItemBookmarks),
-      @"contacts": @(UITabBarSystemItemContacts),
-      @"downloads": @(UITabBarSystemItemDownloads),
-      @"favorites": @(UITabBarSystemItemFavorites),
-      @"featured": @(UITabBarSystemItemFeatured),
-      @"history": @(UITabBarSystemItemHistory),
-      @"more": @(UITabBarSystemItemMore),
-      @"most-recent": @(UITabBarSystemItemMostRecent),
-      @"most-viewed": @(UITabBarSystemItemMostViewed),
-      @"recents": @(UITabBarSystemItemRecents),
-      @"search": @(UITabBarSystemItemSearch),
-      @"top-rated": @(UITabBarSystemItemTopRated),
-    };
-  });
-
-  // Update icon
-  BOOL wasSystemIcon = (systemIcons[_icon] != nil);
-  _icon = [icon copy];
+  _badge = [badge copy];
+  self.barItem.badgeValue = [badge description];
+}
 
-  // Check if string matches any custom images first
-  UIImage *image = [RCTConvert UIImage:_icon];
-  UITabBarItem *oldItem = _barItem;
-  if (image) {
-    // Recreate barItem if previous item was a system icon. Calling self.barItem
-    // creates a new instance if it wasn't set yet.
-    if (wasSystemIcon) {
-      _barItem = nil;
-      self.barItem.image = image;
-    } else {
-      self.barItem.image = image;
-      return;
-    }
-  } else if ([icon isKindOfClass:[NSString class]] && [icon length] > 0) {
-    // Not a custom image, may be a system item?
-    NSNumber *systemIcon = systemIcons[icon];
-    if (!systemIcon) {
-      RCTLogError(@"The tab bar icon '%@' did not match any known image or system icon", icon);
-      return;
-    }
-    _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemIcon.integerValue tag:oldItem.tag];
-  } else {
-    self.barItem.image = nil;
+- (void)setSystemIcon:(UITabBarSystemItem)systemIcon
+{
+  if (_systemIcon != systemIcon) {
+    _systemIcon = systemIcon;
+    UITabBarItem *oldItem = _barItem;
+    _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:_systemIcon
+                                                          tag:oldItem.tag];
+    _barItem.title = oldItem.title;
+    _barItem.imageInsets = oldItem.imageInsets;
+    _barItem.badgeValue = oldItem.badgeValue;
   }
+}
 
-  // Reapply previous properties
-  _barItem.title = oldItem.title;
-  _barItem.imageInsets = oldItem.imageInsets;
-  _barItem.selectedImage = oldItem.selectedImage;
-  _barItem.badgeValue = oldItem.badgeValue;
+- (void)setIcon:(UIImage *)icon
+{
+  _icon = icon;
+  if (_icon && _systemIcon != NSNotFound) {
+    _systemIcon = NSNotFound;
+    UITabBarItem *oldItem = _barItem;
+    _barItem = [UITabBarItem new];
+    _barItem.title = oldItem.title;
+    _barItem.imageInsets = oldItem.imageInsets;
+    _barItem.selectedImage = oldItem.selectedImage;
+    _barItem.badgeValue = oldItem.badgeValue;
+  }
+  self.barItem.image = _icon;
 }
 
 - (UIViewController *)reactViewController
diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m
index a926a54f343f..d92e3a2d95e9 100644
--- a/React/Views/RCTTabBarItemManager.m
+++ b/React/Views/RCTTabBarItemManager.m
@@ -21,10 +21,11 @@ - (UIView *)view
   return [RCTTabBarItem new];
 }
 
+RCT_EXPORT_VIEW_PROPERTY(badge, id /* NSString or NSNumber */)
 RCT_EXPORT_VIEW_PROPERTY(selected, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(icon, id)
+RCT_EXPORT_VIEW_PROPERTY(icon, UIImage)
 RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage)
-RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString)
+RCT_EXPORT_VIEW_PROPERTY(systemIcon, UITabBarSystemItem)
 RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
 RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
 {
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 1222dc9cf7d0..7dc5bf3187ad 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -11,6 +11,7 @@
 
 #import <UIKit/UIKit.h>
 
+#import "RCTBorderStyle.h"
 #import "RCTComponent.h"
 #import "RCTPointerEvents.h"
 
@@ -84,4 +85,14 @@
 @property (nonatomic, assign) CGFloat borderLeftWidth;
 @property (nonatomic, assign) CGFloat borderWidth;
 
+/**
+ * Border styles.
+ */
+@property (nonatomic, assign) RCTBorderStyle borderStyle;
+
+/**
+ *  Insets used when hit testing inside this view.
+ */
+@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;
+
 @end
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 8f143d8a9ff8..ba0aa82a4337 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -108,6 +108,8 @@ - (instancetype)initWithFrame:(CGRect)frame
     _borderTopRightRadius = -1;
     _borderBottomLeftRadius = -1;
     _borderBottomRightRadius = -1;
+    _borderStyle = RCTBorderStyleSolid;
+    _hitTestEdgeInsets = UIEdgeInsetsZero;
 
     _backgroundColor = super.backgroundColor;
   }
@@ -179,6 +181,15 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
   }
 }
 
+- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
+{
+  if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero)) {
+    return [super pointInside:point withEvent:event];
+  }
+  CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
+  return CGRectContainsPoint(hitFrame, point);
+}
+
 - (BOOL)accessibilityActivate
 {
   if (_onAccessibilityTap) {
@@ -476,15 +487,26 @@ - (UIEdgeInsets)bordersAsInsets
 
 - (RCTCornerRadii)cornerRadii
 {
-  const CGRect bounds = self.bounds;
-  const CGFloat maxRadius = MIN(bounds.size.height, bounds.size.width);
+  // Get corner radii
   const CGFloat radius = MAX(0, _borderRadius);
-
+  const CGFloat topLeftRadius = _borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius;
+  const CGFloat topRightRadius = _borderTopRightRadius >= 0 ? _borderTopRightRadius : radius;
+  const CGFloat bottomLeftRadius = _borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius;
+  const CGFloat bottomRightRadius = _borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius;
+
+  // Get scale factors required to prevent radii from overlapping
+  const CGSize size = self.bounds.size;
+  const CGFloat topScaleFactor = RCTZeroIfNaN(MIN(1, size.width / (topLeftRadius + topRightRadius)));
+  const CGFloat bottomScaleFactor = RCTZeroIfNaN(MIN(1, size.width / (bottomLeftRadius + bottomRightRadius)));
+  const CGFloat rightScaleFactor = RCTZeroIfNaN(MIN(1, size.height / (topRightRadius + bottomRightRadius)));
+  const CGFloat leftScaleFactor = RCTZeroIfNaN(MIN(1, size.height / (topLeftRadius + bottomLeftRadius)));
+
+  // Return scaled radii
   return (RCTCornerRadii){
-    MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius),
-    MIN(_borderTopRightRadius >= 0 ? _borderTopRightRadius : radius, maxRadius),
-    MIN(_borderBottomLeftRadius >= 0 ? _borderBottomLeftRadius : radius, maxRadius),
-    MIN(_borderBottomRightRadius >= 0 ? _borderBottomRightRadius : radius, maxRadius),
+    topLeftRadius * MIN(topScaleFactor, leftScaleFactor),
+    topRightRadius * MIN(topScaleFactor, rightScaleFactor),
+    bottomLeftRadius * MIN(bottomScaleFactor, leftScaleFactor),
+    bottomRightRadius * MIN(bottomScaleFactor, rightScaleFactor),
   };
 }
 
@@ -498,8 +520,26 @@ - (RCTBorderColors)borderColors
   };
 }
 
+- (void)reactSetFrame:(CGRect)frame
+{
+  // If frame is zero, or below the threshold where the border radii can
+  // be rendered as a stretchable image, we'll need to re-render.
+  // TODO: detect up-front if re-rendering is necessary
+  CGSize oldSize = self.bounds.size;
+  [super reactSetFrame:frame];
+  if (!CGSizeEqualToSize(self.bounds.size, oldSize)) {
+    [self.layer setNeedsDisplay];
+  }
+}
+
 - (void)displayLayer:(CALayer *)layer
 {
+  if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
+    return;
+  }
+
+  RCTUpdateShadowPathForView(self);
+
   const RCTCornerRadii cornerRadii = [self cornerRadii];
   const UIEdgeInsets borderInsets = [self bordersAsInsets];
   const RCTBorderColors borderColors = [self borderColors];
@@ -509,6 +549,7 @@ - (void)displayLayer:(CALayer *)layer
   RCTCornerRadiiAreEqual(cornerRadii) &&
   RCTBorderInsetsAreEqual(borderInsets) &&
   RCTBorderColorsAreEqual(borderColors) &&
+  _borderStyle == RCTBorderStyleSolid &&
 
   // iOS draws borders in front of the content whereas CSS draws them behind
   // the content. For this reason, only use iOS border drawing when clipping
@@ -531,12 +572,22 @@ - (void)displayLayer:(CALayer *)layer
     return;
   }
 
-  UIImage *image = RCTGetBorderImage([self cornerRadii],
-                                     [self bordersAsInsets],
-                                     [self borderColors],
+  UIImage *image = RCTGetBorderImage(_borderStyle,
+                                     layer.bounds.size,
+                                     cornerRadii,
+                                     borderInsets,
+                                     borderColors,
                                      _backgroundColor.CGColor,
                                      self.clipsToBounds);
 
+  layer.backgroundColor = NULL;
+
+  if (image == nil) {
+    layer.contents = nil;
+    layer.needsDisplayOnBoundsChange = NO;
+    return;
+  }
+
   CGRect contentsCenter = ({
     CGSize size = image.size;
     UIEdgeInsets insets = image.capInsets;
@@ -557,16 +608,59 @@ - (void)displayLayer:(CALayer *)layer
     contentsCenter = CGRectMake(0, 0, 1, 1);
   }
 
-  layer.backgroundColor = NULL;
   layer.contents = (id)image.CGImage;
-  layer.contentsCenter = contentsCenter;
   layer.contentsScale = image.scale;
-  layer.magnificationFilter = kCAFilterNearest;
   layer.needsDisplayOnBoundsChange = YES;
+  layer.magnificationFilter = kCAFilterNearest;
+
+  const BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
+  if (isResizable) {
+    layer.contentsCenter = contentsCenter;
+  } else {
+    layer.contentsCenter = CGRectMake(0.0, 0.0, 1.0, 1.0);
+  }
 
   [self updateClippingForLayer:layer];
 }
 
+static BOOL RCTLayerHasShadow(CALayer *layer)
+{
+  return layer.shadowOpacity * CGColorGetAlpha(layer.shadowColor) > 0;
+}
+
+- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
+{
+  // Inherit background color if a shadow has been set, as an optimization
+  if (RCTLayerHasShadow(self.layer)) {
+    self.backgroundColor = inheritedBackgroundColor;
+  }
+}
+
+static void RCTUpdateShadowPathForView(RCTView *view)
+{
+  if (RCTLayerHasShadow(view.layer)) {
+    if (CGColorGetAlpha(view.backgroundColor.CGColor) > 0.999) {
+
+      // If view has a solid background color, calculate shadow path from border
+      const RCTCornerRadii cornerRadii = [view cornerRadii];
+      const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero);
+      CGPathRef shadowPath = RCTPathCreateWithRoundedRect(view.bounds, cornerInsets, NULL);
+      view.layer.shadowPath = shadowPath;
+      CGPathRelease(shadowPath);
+
+    } else {
+
+      // Can't accurately calculate box shadow, so fall back to pixel-based shadow
+      view.layer.shadowPath = nil;
+
+      RCTLogWarn(@"View #%@ of type %@ has a shadow set but cannot calculate "
+                 "shadow efficiently. Consider setting a background color to "
+                 "fix this, or apply the shadow to a more specific component.",
+                 view.reactTag, [view class]);
+    }
+  }
+}
+
 - (void)updateClippingForLayer:(CALayer *)layer
 {
   CALayer *mask = nil;
@@ -630,6 +724,8 @@ - (void)setBorder##side##Width:(CGFloat)width \
 setBorderWidth(Bottom)
 setBorderWidth(Left)
 
+#pragma mark - Border Radius
+
 #define setBorderRadius(side)                     \
   - (void)setBorder##side##Radius:(CGFloat)radius \
   {                                               \
@@ -646,6 +742,20 @@ - (void)setBorder##side##Radius:(CGFloat)radius \
 setBorderRadius(BottomLeft)
 setBorderRadius(BottomRight)
 
+#pragma mark - Border Style
+
+#define setBorderStyle(side)                           \
+  - (void)setBorder##side##Style:(RCTBorderStyle)style \
+  {                                                    \
+    if (_border##side##Style == style) {               \
+      return;                                          \
+    }                                                  \
+    _border##side##Style = style;                      \
+    [self.layer setNeedsDisplay];                      \
+  }
+
+setBorderStyle()
+
 - (void)dealloc
 {
   CGColorRelease(_borderColor);
diff --git a/React/Views/RCTViewManager.h b/React/Views/RCTViewManager.h
index def09910d286..c570b93ff738 100644
--- a/React/Views/RCTViewManager.h
+++ b/React/Views/RCTViewManager.h
@@ -30,7 +30,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary<NSNu
  * allowing the manager (or the views that it manages) to manipulate the view
  * hierarchy and send events back to the JS context.
  */
-@property (nonatomic, weak) RCTBridge *bridge;
+@property (nonatomic, weak, readonly) RCTBridge *bridge;
 
 /**
  * This method instantiates a native view to be managed by the module. Override
@@ -41,15 +41,6 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, NSDictionary<NSNu
  */
 - (UIView *)view;
 
-/**
- * This method instantiates a native view using the props passed into the component.
- * This method should be used when you need to know about specific props in order to
- * initialize a view. By default, this just calls the -view method. Each prop will
- * still be set individually, after the view is created. Like the -view method,
- * -viewWithProps: should return a fresh instance each time it is called.
- */
-- (UIView *)viewWithProps:(NSDictionary<NSString *, id> *)props;
-
 /**
  * This method instantiates a shadow view to be managed by the module. If omitted,
  * an ordinary RCTShadowView instance will be created, which is typically fine for
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index ef70a4cdd440..88368290acf1 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -10,6 +10,7 @@
 #import "RCTViewManager.h"
 
 #import "RCTBridge.h"
+#import "RCTBorderStyle.h"
 #import "RCTConvert.h"
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
@@ -51,14 +52,12 @@ @implementation RCTViewManager
 
 - (dispatch_queue_t)methodQueue
 {
+  RCTAssert(_bridge, @"Bridge not set");
+  RCTAssert(_bridge.uiManager, @"UIManager not initialized");
+  RCTAssert(_bridge.uiManager.methodQueue, @"UIManager.methodQueue not initialized");
   return _bridge.uiManager.methodQueue;
 }
 
-- (UIView *)viewWithProps:(__unused NSDictionary<NSString *, id> *)props
-{
-  return [self view];
-}
-
 - (UIView *)view
 {
   return [RCTView new];
@@ -97,7 +96,7 @@ - (RCTShadowView *)shadowView
 
 - (NSDictionary<NSString *, id> *)constantsToExport
 {
-  return nil;
+  return @{@"forceTouchAvailable": @(RCTForceTouchAvailable())};
 }
 
 - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView
@@ -191,6 +190,23 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
     view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth;
   }
 }
+RCT_CUSTOM_VIEW_PROPERTY(borderStyle, RCTBorderStyle, RCTView)
+{
+  if ([view respondsToSelector:@selector(setBorderStyle:)]) {
+    view.borderStyle = json ? [RCTConvert RCTBorderStyle:json] : defaultView.borderStyle;
+  }
+}
+RCT_CUSTOM_VIEW_PROPERTY(hitSlop, UIEdgeInsets, RCTView)
+{
+  if ([view respondsToSelector:@selector(setHitTestEdgeInsets:)]) {
+    if (json) {
+      UIEdgeInsets hitSlopInsets = [RCTConvert UIEdgeInsets:json];
+      view.hitTestEdgeInsets = UIEdgeInsetsMake(-hitSlopInsets.top, -hitSlopInsets.left, -hitSlopInsets.bottom, -hitSlopInsets.right);
+    } else {
+      view.hitTestEdgeInsets = defaultView.hitTestEdgeInsets;
+    }
+  }
+}
 RCT_EXPORT_VIEW_PROPERTY(onAccessibilityTap, RCTDirectEventBlock)
 RCT_EXPORT_VIEW_PROPERTY(onMagicTap, RCTDirectEventBlock)
 
@@ -237,10 +253,6 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
 
 RCT_EXPORT_SHADOW_PROPERTY(width, CGFloat)
 RCT_EXPORT_SHADOW_PROPERTY(height, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(minWidth, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(minHeight, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(maxWidth, CGFloat)
-RCT_EXPORT_SHADOW_PROPERTY(maxHeight, CGFloat)
 
 RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat)
 RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat)
diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h
index d806cd987507..05246a09b788 100644
--- a/React/Views/RCTWebView.h
+++ b/React/Views/RCTWebView.h
@@ -31,10 +31,11 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
 
 @property (nonatomic, weak) id<RCTWebViewDelegate> delegate;
 
-@property (nonatomic, strong) NSURL *URL;
+@property (nonatomic, copy) NSDictionary *source;
 @property (nonatomic, assign) UIEdgeInsets contentInset;
 @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
 @property (nonatomic, copy) NSString *injectedJavaScript;
+@property (nonatomic, assign) BOOL scalesPageToFit;
 
 - (void)goForward;
 - (void)goBack;
diff --git a/React/Views/RCTWebView.m b/React/Views/RCTWebView.m
index 46a263d0e620..f7240ec97009 100644
--- a/React/Views/RCTWebView.m
+++ b/React/Views/RCTWebView.m
@@ -12,6 +12,7 @@
 #import <UIKit/UIKit.h>
 
 #import "RCTAutoInsetsProtocol.h"
+#import "RCTConvert.h"
 #import "RCTEventDispatcher.h"
 #import "RCTLog.h"
 #import "RCTUtils.h"
@@ -35,6 +36,11 @@ @implementation RCTWebView
   NSString *_injectedJavaScript;
 }
 
+- (void)dealloc
+{
+  _webView.delegate = nil;
+}
+
 - (instancetype)initWithFrame:(CGRect)frame
 {
   if ((self = [super initWithFrame:frame])) {
@@ -65,31 +71,37 @@ - (void)reload
   [_webView reload];
 }
 
-- (NSURL *)URL
+- (void)setSource:(NSDictionary *)source
 {
-  return _webView.request.URL;
-}
+  if (![_source isEqualToDictionary:source]) {
+    _source = [source copy];
 
-- (void)setURL:(NSURL *)URL
-{
-  // Because of the way React works, as pages redirect, we actually end up
-  // passing the redirect urls back here, so we ignore them if trying to load
-  // the same url. We'll expose a call to 'reload' to allow a user to load
-  // the existing page.
-  if ([URL isEqual:_webView.request.URL]) {
-    return;
-  }
-  if (!URL) {
-    // Clear the webview
-    [_webView loadHTMLString:@"" baseURL:nil];
-    return;
-  }
-  [_webView loadRequest:[NSURLRequest requestWithURL:URL]];
-}
+    // Check for a static html source first
+    NSString *html = [RCTConvert NSString:source[@"html"]];
+    if (html) {
+      NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
+      if (!baseURL) {
+        baseURL = [NSURL URLWithString:@"about:blank"];
+      }
+      [_webView loadHTMLString:html baseURL:baseURL];
+      return;
+    }
 
-- (void)setHTML:(NSString *)HTML
-{
-  [_webView loadHTMLString:HTML baseURL:nil];
+    NSURLRequest *request = [RCTConvert NSURLRequest:source];
+    // Because of the way React works, as pages redirect, we actually end up
+    // passing the redirect urls back here, so we ignore them if trying to load
+    // the same url. We'll expose a call to 'reload' to allow a user to load
+    // the existing page.
+    if ([request.URL isEqual:_webView.request.URL]) {
+      return;
+    }
+    if (!request.URL) {
+      // Clear the webview
+      [_webView loadHTMLString:@"" baseURL:nil];
+      return;
+    }
+    [_webView loadRequest:request];
+  }
 }
 
 - (void)layoutSubviews
@@ -106,6 +118,19 @@ - (void)setContentInset:(UIEdgeInsets)contentInset
                       updateOffset:NO];
 }
 
+- (void)setScalesPageToFit:(BOOL)scalesPageToFit
+{
+  if (_webView.scalesPageToFit != scalesPageToFit) {
+    _webView.scalesPageToFit = scalesPageToFit;
+    [_webView reload];
+  }
+}
+
+- (BOOL)scalesPageToFit
+{
+  return _webView.scalesPageToFit;
+}
+
 - (void)setBackgroundColor:(UIColor *)backgroundColor
 {
   CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m
index 6c765e8d4382..d79e5827c54d 100644
--- a/React/Views/RCTWebViewManager.m
+++ b/React/Views/RCTWebViewManager.m
@@ -18,7 +18,8 @@ @interface RCTWebViewManager () <RCTWebViewDelegate>
 
 @end
 
-@implementation RCTWebViewManager {
+@implementation RCTWebViewManager
+{
   NSConditionLock *_shouldStartLoadLock;
   BOOL _shouldStartLoad;
 }
@@ -32,18 +33,19 @@ - (UIView *)view
   return webView;
 }
 
-RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL);
-RCT_REMAP_VIEW_PROPERTY(html, HTML, NSString);
-RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL);
-RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL);
-RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL);
-RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString);
-RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets);
-RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL);
-RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock);
-RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock);
+RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
+RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
+RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
+RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat)
+RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
+RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
+RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
+RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
 
 - (NSDictionary<NSString *, id> *)constantsToExport
 {
diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m
index 89291236434e..bbc74901e30d 100644
--- a/React/Views/UIView+React.m
+++ b/React/Views/UIView+React.m
@@ -82,9 +82,9 @@ - (void)reactSetFrame:(CGRect)frame
   self.bounds = bounds;
 }
 
-- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
+- (void)reactSetInheritedBackgroundColor:(__unused UIColor *)inheritedBackgroundColor
 {
-  self.backgroundColor = inheritedBackgroundColor;
+  // Does nothing by default
 }
 
 - (UIViewController *)reactViewController
diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS
new file mode 100644
index 000000000000..f9f43995ea80
--- /dev/null
+++ b/ReactAndroid/DEFS
@@ -0,0 +1,99 @@
+# Helpers for referring to React Native open source code.
+#
+# This lets us build React Native:
+# - At Facebook by running buck from the root of the fb repo
+# - Outside of Facebook by running buck in the root of the git repo
+
+import os
+
+
+# Example: react_native_target('java/com/facebook/react/common:common')
+def react_native_target(path):
+  return '//ReactAndroid/src/main/' + path
+
+# Example: react_native_tests_target('java/com/facebook/react/modules:modules')
+def react_native_tests_target(path):
+  return '//ReactAndroid/src/test/' + path
+
+# Example: react_native_integration_tests_target('java/com/facebook/react/testing:testing')
+def react_native_integration_tests_target(path):
+  return '//ReactAndroid/src/androidTest/' + path
+
+# Helper for referring to non-RN code from RN OSS code.
+# Example: react_native_dep('java/com/facebook/systrace:systrace')
+def react_native_dep(path):
+  return '//ReactAndroid/src/main/' + path
+
+JSC_DEPS = [
+  '//native/third-party/jsc:jsc',
+  '//native/third-party/jsc:jsc_legacy_profiler',
+]
+
+JSC_INTERNAL_DEPS = [
+  '//native/third-party/jsc-internal:jsc',
+  '//native/third-party/jsc-internal:jsc_legacy_profiler',
+]
+
+INTERNAL_APP = 'PUBLIC'
+
+# React property preprocessor
+original_android_library=android_library
+def android_library(
+  name,
+  deps=[],
+  annotation_processors=[],
+  annotation_processor_deps=[],
+  *args,
+  **kwargs):
+
+  common_processors = [
+    'com.facebook.react.processing.ReactPropertyProcessor',
+  ]
+  common_processor_deps = [
+    react_native_target('java/com/facebook/react/processing:processing'),
+  ]
+
+  if react_native_target('java/com/facebook/react/uimanager/annotations:annotations') in deps and name != 'processing':
+      annotation_processors = list(set(annotation_processors + common_processors))
+      annotation_processor_deps = list(set(annotation_processor_deps + common_processor_deps))
+
+  original_android_library(
+    name=name,
+    deps=deps,
+    annotation_processors=annotation_processors,
+    annotation_processor_deps=annotation_processor_deps,
+    *args,
+    **kwargs)
+
+
+def robolectric3_test(name, deps, vm_args=None, *args, **kwargs):
+
+  vm_args = vm_args or []
+
+  # We may need to create buck-out/gen/ if we're running after buck clean.
+  tmp = 'buck-out/gen/' + get_base_path() + '/__java_test_' + name + '_output__'
+  extra_vm_args = [
+    '-XX:+UseConcMarkSweepGC', # required by -XX:+CMSClassUnloadingEnabled
+    '-XX:+CMSClassUnloadingEnabled',
+    '-XX:ReservedCodeCacheSize=150M',
+    '-Drobolectric.dependency.dir=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric3/robolectric',
+    '-Dlibraries=buck-out/gen/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/*.jar',
+    '-Drobolectric.logging.enabled=true',
+    '-XX:MaxPermSize=620m',
+    '-Drobolectric.offline=true',
+  ]
+  if os.path.isdir("/dev/shm"):
+      extra_vm_args.append('-Djava.io.tmpdir=/dev/shm')
+  else:
+      extra_vm_args.append(
+          '-Djava.io.tmpdir=%s' % os.path.join(os.path.abspath('.'),
+                                               'buck-out/bin'))
+
+  # defined in BUCK
+  robolectric_test(
+    name=name,
+    deps=deps,
+    vm_args=vm_args + extra_vm_args,
+    *args,
+    **kwargs
+  )
\ No newline at end of file
diff --git a/ReactAndroid/DevExperience.md b/ReactAndroid/DevExperience.md
index 2defb472032e..ccb1f629fc4c 100644
--- a/ReactAndroid/DevExperience.md
+++ b/ReactAndroid/DevExperience.md
@@ -8,7 +8,7 @@ Make sure you have the following installed:
 - SDK build tools version 23
 - Android Support Repository 17 (for Android Support Library)
 
-Follow steps on https://github.com/facebook/react-native/blob/master/react-native-cli/CONTRIBUTING.md, but be sure to bump the verison of react-native in package.json to some version > 0.9 (latest published npm version) or set up proxying properly for react-native
+Follow steps on https://github.com/facebook/react-native/blob/master/react-native-cli/CONTRIBUTING.md, but be sure to bump the version of react-native in package.json to some version > 0.9 (latest published npm version) or set up proxying properly for react-native
   
 - From the react-native-android repo:
   - `./gradlew :ReactAndroid:installArchives`
diff --git a/ReactAndroid/README.md b/ReactAndroid/README.md
index 891b008ac522..08c34b992492 100644
--- a/ReactAndroid/README.md
+++ b/ReactAndroid/README.md
@@ -1,101 +1,3 @@
 # Building React Native for Android
 
-This guide contains instructions for building the Android code and running the sample apps.
-
-## Supported Operating Systems
-
-This setup has only been tested on Mac OS so far.
-
-## Prerequisites
-
-Assuming you have the [Android SDK](https://developer.android.com/sdk/installing/index.html) installed, run `android` to open the Android SDK Manager.
-
-Make sure you have the following installed:
-
-- Android SDK version 23 (compileSdkVersion in [`build.gradle`](build.gradle))
-- SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](build.gradle))
-- Android Support Repository >= 17 (for Android Support Library)
-- Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html))
-
-Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK` defined, or create a `local.properties` file in the root of your `react-native` checkout with the following contents:
-
-    sdk.dir=absolute_path_to_android_sdk
-    ndk.dir=absolute_path_to_android_ndk
-
-Example:
-
-    sdk.dir=/Users/your_unix_name/android-sdk-macosx
-    ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e
-
-## Run `npm install`
-
-This is needed to fetch the dependencies for the packager.
-
-```bash
-cd react-native
-npm install
-```
-
-## Building from the command line
-
-To build the framework code:
-
-```bash
-cd react-native
-./gradlew :ReactAndroid:assembleDebug
-```
-
-To install a snapshot version of the framework code in your local Maven repo:
-
-```bash
-./gradlew :ReactAndroid:installArchives
-```
-
-## Running the examples
-
-To run the UIExplorer app:
-
-```bash
-cd react-native
-./gradlew :Examples:UIExplorer:android:app:installDebug
-# Start the packager in a separate shell:
-# Make sure you ran npm install
-./packager/packager.sh
-# Open UIExplorer in your emulator, Menu button -> Reload JS should work
-```
-
-You can run any other sample app the same way, e.g.:
-
-```bash
-./gradlew :Examples:Movies:android:app:installDebug
-```
-
-## Building from Android Studio
-
-You'll need to do one additional step until we release the React Native Gradle plugin to Maven central. This is because Android Studio has its own local Maven repo:
-
-    mkdir -p /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react
-    cp -r ~/.m2/repository/com/facebook/react/gradleplugin /Applications/Android\ Studio.app/Contents/gradle/m2repository/com/facebook/react/
-
-Now, open Android Studio, click _Import Non-Android Studio project_ and find your `react-native` repo.
-
-In the configurations dropdown, _app_ should be selected. Click _Run_.
-
-## Installing the React Native .aar in your local Maven repo
-
-In some cases, for example when working on the `react-native-cli` it's useful to publish a snapshot version of React Native into your local Maven repo. This way, Gradle can pick it up when building projects that have a Maven dependency on React Native.
-
-Run:
-
-```bash
-cd react-native-android
-./gradlew :ReactAndroid:installArchives
-```
-
-## Troubleshooting
-
-Gradle build fails in `ndk-build`. See the section about `local.properties` file above.
-
-Gradle build fails "Could not find any version that matches com.facebook.react:gradleplugin:...". See the section about the React Native Gradle plugin above.
-
-Packager throws an error saying a module is not found. Try running `npm install` in the root of the repo.
+See [docs on the website](https://facebook.github.io/react-native/docs/android-building-from-source.html).
diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle
index 13d95719b767..8f26d7997656 100644
--- a/ReactAndroid/build.gradle
+++ b/ReactAndroid/build.gradle
@@ -24,22 +24,18 @@ task createNativeDepsDirectories {
 task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) {
     // Use ZIP version as it's faster this way to selectively extract some parts of the archive
     src 'https://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.zip'
+    // alternative
+    // src 'http://mirror.nienbo.com/boost/boost_1_57_0.zip'
     onlyIfNewer true
     overwrite false
     dest new File(downloadsDir, 'boost_1_57_0.zip')
 }
 
-task prepareBoost(dependsOn: downloadBoost) {
-    inputs.files downloadBoost.dest, 'src/main/jni/third-party/boost/Android.mk'
-    outputs.dir "$thirdPartyNdkDir/boost"
-    doLast {
-        copy {
-            from { zipTree(downloadBoost.dest) }
-            from 'src/main/jni/third-party/boost/Android.mk'
-            include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk'
-            into "$thirdPartyNdkDir/boost"
-        }
-    }
+task prepareBoost(dependsOn: downloadBoost, type: Copy) {
+    from zipTree(downloadBoost.dest)
+    from 'src/main/jni/third-party/boost/Android.mk'
+    include 'boost_1_57_0/boost/**/*.hpp', 'Android.mk'
+    into "$thirdPartyNdkDir/boost"
 }
 
 task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) {
@@ -193,7 +189,7 @@ task buildReactNdkLib(dependsOn: [prepareJSC, prepareBoost, prepareDoubleConvers
             "NDK_LIBS_OUT=$buildDir/react-ndk/all",
             "THIRD_PARTY_NDK_DIR=$buildDir/third-party-ndk",
             '-C', file('src/main/jni/react/jni').absolutePath,
-            '--jobs', Runtime.runtime.availableProcessors()
+            '--jobs', project.hasProperty("jobs") ? project.property("jobs") : Runtime.runtime.availableProcessors()
 }
 
 task cleanReactNdkLib(type: Exec) {
@@ -208,6 +204,11 @@ task packageReactNdkLibs(dependsOn: buildReactNdkLib, type: Copy) {
     into "$buildDir/react-ndk/exported"
 }
 
+task packageReactNdkLibsForBuck(dependsOn: packageReactNdkLibs, type: Copy) {
+  from "$buildDir/react-ndk/exported"
+  into "src/main/jni/prebuilt/lib"
+}
+
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.1"
@@ -223,12 +224,18 @@ android {
         }
 
         buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false'
+        testApplicationId "com.facebook.react.tests.gradle"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
     sourceSets.main {
         jni.srcDirs = []
         jniLibs.srcDir "$buildDir/react-ndk/exported"
         res.srcDirs = ['src/main/res/devsupport', 'src/main/res/shell']
+        java {
+          srcDirs = ['src/main/java', 'src/main/libraries/soloader']
+          exclude 'com/facebook/react/processing'
+        }
     }
 
     tasks.withType(JavaCompile) {
@@ -243,17 +250,30 @@ android {
 }
 
 dependencies {
-    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile fileTree(dir: 'src/main/third-party/java/infer-annotations/', include: ['*.jar'])
     compile 'com.android.support:appcompat-v7:23.0.1'
     compile 'com.android.support:recyclerview-v7:23.0.1'
     compile 'com.facebook.fresco:fresco:0.8.1'
     compile 'com.facebook.fresco:imagepipeline-okhttp:0.8.1'
+    compile 'com.facebook.stetho:stetho:1.2.0'
+    compile 'com.facebook.stetho:stetho-okhttp:1.2.0'
     compile 'com.fasterxml.jackson.core:jackson-core:2.2.3'
     compile 'com.google.code.findbugs:jsr305:3.0.0'
     compile 'com.squareup.okhttp:okhttp:2.5.0'
     compile 'com.squareup.okhttp:okhttp-ws:2.5.0'
     compile 'com.squareup.okio:okio:1.6.0'
     compile 'org.webkit:android-jsc:r174650'
+
+    testCompile "junit:junit:${JUNIT_VERSION}"
+    testCompile "org.powermock:powermock-api-mockito:${POWERMOCK_VERSION}"
+    testCompile 'com.fasterxml.jackson.core:jackson-databind:2.2.3'
+    testCompile "org.powermock:powermock-module-junit4-rule:${POWERMOCK_VERSION}"
+    testCompile "org.powermock:powermock-classloading-xstream:${POWERMOCK_VERSION}"
+    testCompile "org.mockito:mockito-core:${MOCKITO_CORE_VERSION}"
+    testCompile "org.easytesting:fest-assert-core:${FEST_ASSERT_CORE_VERSION}"
+    testCompile "org.robolectric:robolectric:${ROBOLECTRIC_VERSION}"
+
+    androidTestCompile "com.android.support.test:testing-support-lib:0.1"
 }
 
 apply from: 'release.gradle'
diff --git a/ReactAndroid/gradle.properties b/ReactAndroid/gradle.properties
index 96433c14b48e..e038115dd4ea 100644
--- a/ReactAndroid/gradle.properties
+++ b/ReactAndroid/gradle.properties
@@ -6,3 +6,9 @@ POM_ARTIFACT_ID=react-native
 POM_PACKAGING=aar
 
 android.useDeprecatedNdk=true
+
+MOCKITO_CORE_VERSION=1.+
+POWERMOCK_VERSION=1.6.2
+ROBOLECTRIC_VERSION=3.0
+JUNIT_VERSION=4.12
+FEST_ASSERT_CORE_VERSION=2.0M10
diff --git a/ReactAndroid/libs/BUCK b/ReactAndroid/libs/BUCK
new file mode 100644
index 000000000000..8699bb3393c6
--- /dev/null
+++ b/ReactAndroid/libs/BUCK
@@ -0,0 +1,22 @@
+android_prebuilt_aar(
+    name = 'appcompat',
+    aar = ':appcompat-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'appcompat-binary-aar',
+    url = 'mvn:com.android.support:appcompat-v7:aar:23.0.1',
+    sha1 = '7d659f671541394a8bc2b9f909950aa2a5ec87ff',
+)
+
+android_prebuilt_aar(
+  name = 'android-jsc',
+  aar = ':android-jsc-aar',
+)
+
+remote_file(
+  name = 'android-jsc-aar',
+  url = 'mvn:org.webkit:android-jsc:aar:r174650',
+  sha1 = '880cedd93f43e0fc841f01f2fa185a63d9230f85',
+)
diff --git a/ReactAndroid/release.gradle b/ReactAndroid/release.gradle
index 9b8cd35a8b0f..e5b34d73cc79 100644
--- a/ReactAndroid/release.gradle
+++ b/ReactAndroid/release.gradle
@@ -117,11 +117,9 @@ afterEvaluate { project ->
     task installArchives(type: Upload) {
         configuration = configurations.archives
         repositories.mavenDeployer {
-            beforeDeployment {
-                MavenDeployment deployment -> signing.signPom(deployment)
-            }
-
-            repository url: "file://${System.properties['user.home']}/.m2/repository"
+            // Deploy to react-native/android, ready to publish to npm
+            repository url: "file://${projectDir}/../android"
+            
             configureReactNativePom pom
         }
     }
diff --git a/ReactAndroid/src/androidTest/AndroidManifest.xml b/ReactAndroid/src/androidTest/AndroidManifest.xml
new file mode 100644
index 000000000000..f6ce0fa0511c
--- /dev/null
+++ b/ReactAndroid/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.facebook.react.tests.gradle"
+          android:versionCode="1"
+          android:versionName="1.0" >
+  <uses-sdk android:targetSdkVersion="7" />
+  <supports-screens android:anyDensity="true" />
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <!-- needed for screenshot tests -->
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+  <application
+    android:hardwareAccelerated="false">
+    <activity
+      android:name="com.facebook.react.testing.ReactAppTestActivity"
+      android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen"
+    />
+  </application>
+</manifest>
diff --git a/ReactAndroid/src/androidTest/assets/BUCK b/ReactAndroid/src/androidTest/assets/BUCK
new file mode 100644
index 000000000000..4648c5ef94f5
--- /dev/null
+++ b/ReactAndroid/src/androidTest/assets/BUCK
@@ -0,0 +1,5 @@
+android_resource(
+  name = 'assets',
+  assets = '.',
+  visibility = ['PUBLIC'],
+)
diff --git a/ReactAndroid/src/androidTest/assets/ScrollViewTestModule.js b/ReactAndroid/src/androidTest/assets/ScrollViewTestModule.js
new file mode 100644
index 000000000000..e7dac8c7e1fa
--- /dev/null
+++ b/ReactAndroid/src/androidTest/assets/ScrollViewTestModule.js
@@ -0,0 +1,135 @@
+/**
+ * @providesModule ScrollViewTestModule
+ */
+
+'use strict';
+
+var BatchedBridge = require('BatchedBridge');
+var React = require('React');
+var View = require('View');
+var ScrollView = require('ScrollView');
+var Text = require('Text');
+var StyleSheet = require('StyleSheet');
+var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
+var ScrollListener = require('NativeModules').ScrollListener;
+
+var NUM_ITEMS = 100;
+
+// Shared by integration tests for ScrollView and HorizontalScrollView
+
+var scrollViewApp;
+
+var Item = React.createClass({
+  render: function() {
+    return (
+      <TouchableWithoutFeedback onPress={this.props.onPress}>
+        <View style={styles.item_container}>
+          <Text style={styles.item_text}>{this.props.text}</Text>
+        </View>
+      </TouchableWithoutFeedback>
+    );
+  },
+});
+
+var getInitialState = function() {
+  var data = [];
+  for (var i = 0; i < NUM_ITEMS; i++) {
+    data[i] = {text: 'Item ' + i + '!'};
+  }
+  return {
+    data: data,
+  };
+};
+
+var onScroll = function(e) {
+  ScrollListener.onScroll(e.nativeEvent.contentOffset.x, e.nativeEvent.contentOffset.y);
+};
+
+var onScrollBeginDrag = function(e) {
+  ScrollListener.onScrollBeginDrag(e.nativeEvent.contentOffset.x, e.nativeEvent.contentOffset.y);
+};
+
+var onScrollEndDrag = function(e) {
+  ScrollListener.onScrollEndDrag(e.nativeEvent.contentOffset.x, e.nativeEvent.contentOffset.y);
+};
+
+var onItemPress = function(itemNumber) {
+  ScrollListener.onItemPress(itemNumber);
+};
+
+var ScrollViewTestApp = React.createClass({
+  getInitialState: getInitialState,
+  onScroll: onScroll,
+  onItemPress: onItemPress,
+  onScrollBeginDrag: onScrollBeginDrag,
+  onScrollEndDrag: onScrollEndDrag,
+
+  scrollTo: function(destX, destY) {
+    this.refs.scrollView.scrollTo(destY, destX);
+  },
+
+  render: function() {
+    scrollViewApp = this;
+    var children = this.state.data.map((item, index) => (
+      <Item
+        key={index} text={item.text}
+        onPress={this.onItemPress.bind(this, index)} />
+    ));
+    return (
+      <ScrollView onScroll={this.onScroll} onScrollBeginDrag={this.onScrollBeginDrag} onScrollEndDrag={this.onScrollEndDrag} ref="scrollView">
+        {children}
+      </ScrollView>
+    );
+  },
+});
+
+var HorizontalScrollViewTestApp = React.createClass({
+  getInitialState: getInitialState,
+  onScroll: onScroll,
+  onItemPress: onItemPress,
+
+  scrollTo: function(destX, destY) {
+    this.refs.scrollView.scrollTo(destY, destX);
+  },
+
+  render: function() {
+    scrollViewApp = this;
+    var children = this.state.data.map((item, index) => (
+      <Item
+        key={index} text={item.text}
+        onPress={this.onItemPress.bind(this, index)} />
+    ));
+    return (
+      <ScrollView horizontal={true} onScroll={this.onScroll} ref="scrollView">
+        {children}
+      </ScrollView>
+    );
+  },
+});
+
+var styles = StyleSheet.create({
+  item_container: {
+    padding: 30,
+    backgroundColor: '#ffffff',
+  },
+  item_text: {
+    flex: 1,
+    fontSize: 18,
+    alignSelf: 'center',
+  },
+});
+
+var ScrollViewTestModule = {
+  ScrollViewTestApp: ScrollViewTestApp,
+  HorizontalScrollViewTestApp: HorizontalScrollViewTestApp,
+  scrollTo: function(destX, destY) {
+    scrollViewApp.scrollTo(destX, destY);
+  },
+};
+
+BatchedBridge.registerCallableModule(
+  'ScrollViewTestModule',
+  ScrollViewTestModule
+);
+
+module.exports = ScrollViewTestModule;
diff --git a/ReactAndroid/src/androidTest/assets/TestBundle.js b/ReactAndroid/src/androidTest/assets/TestBundle.js
new file mode 100644
index 000000000000..1bbdad7348e0
--- /dev/null
+++ b/ReactAndroid/src/androidTest/assets/TestBundle.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2014-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.
+ */
+'use strict';
+
+// Disable YellowBox so we do not have to mock its dependencies
+console.disableYellowBox = true;
+
+// Include modules used by integration tests
+require('ScrollViewTestModule');
+
+// Define catalyst test apps used in integration tests
+var AppRegistry = require('AppRegistry');
+
+var apps = [
+{
+    appKey: 'ScrollViewTestApp',
+    component: () => require('ScrollViewTestModule').ScrollViewTestApp
+},
+{
+  appKey: 'HorizontalScrollViewTestApp',
+  component: () => require('ScrollViewTestModule').HorizontalScrollViewTestApp
+},
+];
+
+AppRegistry.registerConfig(apps);
diff --git a/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
new file mode 100644
index 000000000000..8fd01fc76607
--- /dev/null
+++ b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.facebook.react.tests">
+
+  <supports-screens android:anyDensity="true" />
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+  <!-- needed for screenshot tests -->
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+  <application
+    android:hardwareAccelerated="false">
+    <uses-library android:name="android.test.runner" />
+    <activity
+      android:name="com.facebook.react.testing.ReactAppTestActivity"
+      android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen">
+    </activity>
+  </application>
+
+  <instrumentation
+    android:name="android.support.test.runner.AndroidJUnitRunner"
+    android:targetPackage="com.facebook.react.tests"
+    android:label="Buck runs instrumentation tests"/>
+
+</manifest>
diff --git a/ReactAndroid/src/androidTest/buck-runner/BUCK b/ReactAndroid/src/androidTest/buck-runner/BUCK
new file mode 100644
index 000000000000..337e846c1e05
--- /dev/null
+++ b/ReactAndroid/src/androidTest/buck-runner/BUCK
@@ -0,0 +1,23 @@
+include_defs('//ReactAndroid/DEFS')
+
+# We are running instrumentation tests in simple mode: app code and instrumentation are in the same APK
+# Currently you need to run these commands to execute tests:
+#
+# node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/assets/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
+# gradle :ReactAndroid:packageReactNdkLibsForBuck
+# buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests
+# ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests
+android_binary(
+  name = 'instrumentation-tests',
+  manifest = 'AndroidManifest.xml',
+  keystore = '//keystores:debug',
+  deps = [
+    react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
+    react_native_integration_tests_target('assets:assets'),
+    react_native_target('jni/prebuilt:reactnative-libs'),
+    react_native_target('jni/prebuilt:android-jsc'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+    react_native_target('java/com/facebook/react/devsupport:devsupport'),
+  ],
+)
+
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AbstractScrollViewTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AbstractScrollViewTestCase.java
new file mode 100644
index 000000000000..afdf4bc903d5
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AbstractScrollViewTestCase.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.testing.ReactInstanceSpecForTest;
+import com.facebook.react.testing.ReactAppInstrumentationTestCase;
+
+/**
+ * Shared by {@link ReactScrollViewTestCase} and {@link ReactHorizontalScrollViewTestCase}.
+ * See also ScrollViewTestModule.js
+ */
+public abstract class AbstractScrollViewTestCase extends ReactAppInstrumentationTestCase {
+
+  protected ScrollListenerModule mScrollListenerModule;
+
+  protected static interface ScrollViewTestModule extends JavaScriptModule {
+    public void scrollTo(float destX, float destY);
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    waitForBridgeAndUIIdle(60000);
+    super.tearDown();
+  }
+
+  @Override
+  protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
+    mScrollListenerModule = new ScrollListenerModule();
+    return super.createReactInstanceSpecForTest()
+        .addNativeModule(mScrollListenerModule)
+        .addJSModule(ScrollViewTestModule.class);
+  }
+
+  // See ScrollViewListenerModule.js
+  protected static class ScrollListenerModule extends BaseJavaModule {
+
+    private final ArrayList<Double> mXOffsets = new ArrayList<Double>();
+    private final ArrayList<Double> mYOffsets = new ArrayList<Double>();
+    private final ArrayList<Integer> mItemsPressed = new ArrayList<Integer>();
+    private final Semaphore mScrollSignaler = new Semaphore(0);
+    private boolean mScrollBeginDragCalled;
+    private boolean mScrollEndDragCalled;
+
+    // Matches ScrollViewListenerModule.js
+    @Override
+    public String getName() {
+      return "ScrollListener";
+    }
+
+    @ReactMethod
+    public void onScroll(double x, double y) {
+      mXOffsets.add(x);
+      mYOffsets.add(y);
+      mScrollSignaler.release();
+    }
+
+    @ReactMethod
+    public void onItemPress(int itemNumber) {
+      mItemsPressed.add(itemNumber);
+    }
+
+    @ReactMethod
+    public void onScrollBeginDrag(double x, double y) {
+      mScrollBeginDragCalled = true;
+    }
+
+    @ReactMethod
+    public void onScrollEndDrag(double x, double y) {
+      mScrollEndDragCalled = true;
+    }
+
+    public void waitForScrollIdle() {
+      while (true) {
+        try {
+          boolean gotScrollSignal = mScrollSignaler.tryAcquire(1000, TimeUnit.MILLISECONDS);
+          if (!gotScrollSignal) {
+            return;
+          }
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    public ArrayList<Double> getXOffsets() {
+      return mXOffsets;
+    }
+
+    public ArrayList<Double> getYOffsets() {
+      return mYOffsets;
+    }
+
+    public ArrayList<Integer> getItemsPressed() {
+      return mItemsPressed;
+    }
+
+    public boolean dragEventsMatch() {
+      return mScrollBeginDragCalled && mScrollEndDragCalled;
+    }
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AssertModule.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AssertModule.java
new file mode 100644
index 000000000000..4379470164fc
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/AssertModule.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * NativeModule for tests that allows assertions from JS to propagate to Java.
+ */
+public class AssertModule extends BaseJavaModule {
+
+  private boolean mGotSuccess;
+  private boolean mGotFailure;
+  private @Nullable String mFirstFailureStackTrace;
+
+  @Override
+  public String getName() {
+    return "Assert";
+  }
+
+  @ReactMethod
+  public void fail(String stackTrace) {
+    if (!mGotFailure) {
+      mGotFailure = true;
+      mFirstFailureStackTrace = stackTrace;
+    }
+  }
+
+  @ReactMethod
+  public void success() {
+    mGotSuccess = true;
+  }
+
+  /**
+   * Allows the user of this module to verify that asserts are actually being called from JS and
+   * that none of them failed.
+   */
+  public void verifyAssertsAndReset() {
+    assertFalse("First failure: " + mFirstFailureStackTrace, mGotFailure);
+    assertTrue("Received no assertions during the test!", mGotSuccess);
+    mGotFailure = false;
+    mGotSuccess = false;
+    mFirstFailureStackTrace = null;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK
new file mode 100644
index 000000000000..2ec63965134a
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK
@@ -0,0 +1,29 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'testing',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/modules/debug:debug'),
+    react_native_target('java/com/facebook/react/devsupport:devsupport'),
+    react_native_target('java/com/facebook/react/shell:shell'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/testing-support-lib:testing-support-lib'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':testing',
+)
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/FakeAsyncLocalStorage.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/FakeAsyncLocalStorage.java
new file mode 100644
index 000000000000..e62d09ecb0c1
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/FakeAsyncLocalStorage.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.WritableMap;
+
+/**
+ * Dummy implementation of storage module, used for testing
+ */
+public final class FakeAsyncLocalStorage extends BaseJavaModule {
+
+  private static WritableMap errorMessage;
+  static {
+    errorMessage = Arguments.createMap();
+    errorMessage.putString("message", "Fake Async Local Storage");
+  }
+
+  @Override
+  public String getName() {
+    return "AsyncSQLiteDBStorage";
+  }
+
+  @ReactMethod
+  public void multiGet(final ReadableArray keys, final Callback callback) {
+    callback.invoke(errorMessage, null);
+  }
+
+  @ReactMethod
+  public void multiSet(final ReadableArray keyValueArray, final Callback callback) {
+    callback.invoke(errorMessage);
+  }
+
+  @ReactMethod
+  public void multiRemove(final ReadableArray keys, final Callback callback) {
+    callback.invoke(errorMessage);
+  }
+
+  @ReactMethod
+  public void clear(Callback callback) {
+    callback.invoke(errorMessage);
+  }
+
+  @ReactMethod
+  public void getAllKeys(final Callback callback) {
+    callback.invoke(errorMessage, null);
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java
new file mode 100644
index 000000000000..98884c1033a3
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+/**
+ * Interface for something that knows how to wait for bridge and UI idle.
+ */
+public interface IdleWaiter {
+
+  void waitForBridgeAndUIIdle();
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/InstanceSpecForTestPackage.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/InstanceSpecForTestPackage.java
new file mode 100644
index 000000000000..cb7bbf3153a7
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/InstanceSpecForTestPackage.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.List;
+
+import android.view.View;
+
+import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.react.ReactPackage;
+
+/**
+ * This class wraps {@class ReactInstanceSpecForTest} in {@class ReactPackage} interface.
+ * TODO(6788898): Refactor test code to use ReactPackage instead of SpecForTest
+ */
+public class InstanceSpecForTestPackage implements ReactPackage {
+
+  private final ReactInstanceSpecForTest mSpecForTest;
+
+  public InstanceSpecForTestPackage(ReactInstanceSpecForTest specForTest) {
+    mSpecForTest = specForTest;
+  }
+
+  @Override
+  public List<NativeModule> createNativeModules(
+      ReactApplicationContext catalystApplicationContext) {
+    return mSpecForTest.getExtraNativeModulesForTest();
+  }
+
+  @Override
+  public List<Class<? extends JavaScriptModule>> createJSModules() {
+    return mSpecForTest.getExtraJSModulesForTest();
+  }
+
+  @Override
+  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
+    return mSpecForTest.getExtraViewManagers();
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IntRecordingModule.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IntRecordingModule.java
new file mode 100644
index 000000000000..5cf7e4373ef6
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IntRecordingModule.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+/**
+ * Native module provides single method {@link #record} which records its single int argument
+ * in calls array
+ */
+public class IntRecordingModule extends BaseJavaModule {
+
+  private final List<Integer> mCalls = new ArrayList<>();
+  private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+  @Override
+  public String getName() {
+    return "Recording";
+  }
+
+  @ReactMethod
+  public void record(int i) {
+    mCalls.add(i);
+    mCountDownLatch.countDown();
+  }
+
+  public void reset() {
+    mCalls.clear();
+  }
+
+  public List<Integer> getCalls() {
+    return mCalls;
+  }
+
+  public void waitForFirstCall() {
+    try {
+      if (!mCountDownLatch.await(15000, TimeUnit.MILLISECONDS)) {
+        throw new RuntimeException("Timed out waiting for first call");
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/JSIntegrationTestChecker.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/JSIntegrationTestChecker.java
new file mode 100644
index 000000000000..1ddf186c90de
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/JSIntegrationTestChecker.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactMethod;
+
+/**
+ * This class is used to verify that some JS integration tests have completed successfully.
+ * The JS integration tests can be started from a ReactIntegrationTestCase and upon
+ * finishing successfully the {@link JSIntegrationTestChecker#testDone()} method will be called.
+ * To verify if the test has completed successfully, call {#link JSIntegrationTestChecker#await()}
+ * to wait for the test to run, and {#link JSIntegrationTestChecker#isTestDone()} to check if it
+ * completed successfully.
+ */
+public class JSIntegrationTestChecker extends BaseJavaModule {
+
+  private final CountDownLatch mLatch;
+
+  public JSIntegrationTestChecker() {
+    mLatch = new CountDownLatch(1);
+  }
+
+  @Override
+  public String getName() {
+    return "TestModule";
+  }
+
+  @ReactMethod
+  public void markTestCompleted() {
+    mLatch.countDown();
+  }
+
+  @ReactMethod
+  public void verifySnapshot(Callback callback) {
+  }
+
+  public boolean await(long ms) {
+    try {
+      return mLatch.await(ms, TimeUnit.MILLISECONDS);
+    } catch (InterruptedException ignore) {
+      return false;
+    }
+  }
+
+  public boolean isTestDone() {
+    return mLatch.getCount() == 0;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/MultipleFailureException.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/MultipleFailureException.java
new file mode 100644
index 000000000000..a996403d44f3
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/MultipleFailureException.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+/**
+ * Custom implementation of {@link org.junit.runners.model.MultipleFailureException} that includes
+ * stack information of collected exception as a part of the message.
+ */
+public class MultipleFailureException extends org.junit.runners.model.MultipleFailureException {
+
+  public MultipleFailureException(List<Throwable> errors) {
+    super(errors);
+  }
+
+  @Override
+  public String getMessage() {
+    StringBuilder sb = new StringBuilder();
+    List<Throwable> errors = getFailures();
+
+    sb.append(String.format("There were %d errors:", errors.size()));
+
+    int i = 0;
+    for (Throwable e : errors) {
+      sb.append(String.format("%n---- Error #%d", i));
+      sb.append("\n" + getStackTraceAsString(e));
+      i++;
+    }
+    sb.append("\n");
+    return sb.toString();
+  }
+
+  private static String getStackTraceAsString(Throwable throwable) {
+    StringWriter stringWriter = new StringWriter();
+    throwable.printStackTrace(new PrintWriter(stringWriter));
+    return stringWriter.toString();
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java
new file mode 100644
index 000000000000..43a5ebfcee90
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/NetworkRecordingModuleMock.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+
+/**
+ * Mock Networking module that records last request received by {@link #sendRequest} method and
+ * returns reponse code and body that should be set with {@link #setResponse}
+ */
+public class NetworkRecordingModuleMock extends ReactContextBaseJavaModule {
+
+  public int mRequestCount = 0;
+  public @Nullable String mRequestMethod;
+  public @Nullable String mRequestURL;
+  public @Nullable ReadableArray mRequestHeaders;
+  public @Nullable ReadableMap mRequestData;
+  public int mLastRequestId;
+  public boolean mAbortedRequest;
+
+  private final boolean mCompleteRequest;
+
+  public NetworkRecordingModuleMock(ReactApplicationContext reactContext) {
+    this(reactContext, true);
+  }
+
+  public NetworkRecordingModuleMock(ReactApplicationContext reactContext, boolean completeRequest) {
+    super(reactContext);
+    mCompleteRequest = completeRequest;
+  }
+
+  public static interface RequestListener {
+    public void onRequest(String method, String url, ReadableArray header, ReadableMap data);
+  }
+
+  private int mResponseCode;
+  private @Nullable String mResponseBody;
+  private @Nullable RequestListener mRequestListener;
+
+  public void setResponse(int code, String body) {
+    mResponseCode = code;
+    mResponseBody = body;
+  }
+
+  public void setRequestListener(RequestListener requestListener) {
+    mRequestListener = requestListener;
+  }
+
+  @Override
+  public final String getName() {
+    return "RCTNetworking";
+  }
+
+  private void fireReactCallback(
+      Callback callback,
+      int status,
+      @Nullable String headers,
+      @Nullable String body) {
+    callback.invoke(status, headers, body);
+  }
+
+  @ReactMethod
+  public final void sendRequest(
+      String method,
+      String url,
+      int requestId,
+      ReadableArray headers,
+      ReadableMap data,
+      boolean incrementalUpdates,
+      int timeout) {
+    mLastRequestId = requestId;
+    mRequestCount++;
+    mRequestMethod = method;
+    mRequestURL = url;
+    mRequestHeaders = headers;
+    mRequestData = data;
+    if (mRequestListener != null) {
+      mRequestListener.onRequest(method, url, headers, data);
+    }
+    if (mCompleteRequest) {
+      onResponseReceived(requestId, mResponseCode, null);
+      onDataReceived(requestId, mResponseBody);
+      onRequestComplete(requestId, null);
+    }
+  }
+
+  @ReactMethod
+  public void abortRequest(int requestId) {
+    mLastRequestId = requestId;
+    mAbortedRequest = true;
+  }
+
+  private void onDataReceived(int requestId, String data) {
+    WritableArray args = Arguments.createArray();
+    args.pushInt(requestId);
+    args.pushString(data);
+
+    getEventEmitter().emit("didReceiveNetworkData", args);
+  }
+
+  private void onRequestComplete(int requestId, @Nullable String error) {
+    WritableArray args = Arguments.createArray();
+    args.pushInt(requestId);
+    args.pushString(error);
+
+    getEventEmitter().emit("didCompleteNetworkResponse", args);
+  }
+
+  private void onResponseReceived(int requestId, int code, WritableMap headers) {
+    WritableArray args = Arguments.createArray();
+    args.pushInt(requestId);
+    args.pushInt(code);
+    args.pushMap(headers);
+
+    getEventEmitter().emit("didReceiveNetworkResponse", args);
+  }
+
+  private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
+    return getReactApplicationContext()
+        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java
new file mode 100644
index 000000000000..aaba651def94
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import android.graphics.Bitmap;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.ReactContext;
+
+/**
+ * Base class for instrumentation tests that runs React based react application in UI mode
+ */
+public abstract class ReactAppInstrumentationTestCase extends
+    ActivityInstrumentationTestCase2<ReactAppTestActivity> implements IdleWaiter {
+
+  public ReactAppInstrumentationTestCase() {
+    super(ReactAppTestActivity.class);
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    final ReactAppTestActivity activity = getActivity();
+    try {
+      runTestOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          activity.loadApp(
+              getReactApplicationKeyUnderTest(),
+              createReactInstanceSpecForTest(),
+              getEnableDevSupport());
+        }
+      });
+    } catch (Throwable t) {
+      throw new Exception("Unable to load react app", t);
+    }
+    waitForBridgeAndUIIdle();
+    assertTrue("Layout never occurred!", activity.waitForLayout(5000));
+    waitForBridgeAndUIIdle();
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    ReactAppTestActivity activity = getActivity();
+    super.tearDown();
+    activity.waitForDestroy(5000);
+  }
+
+  public ViewGroup getRootView() {
+    return (ViewGroup) getActivity().getRootView();
+  }
+
+  /**
+   * This method isn't safe since it doesn't factor in layout-only view removal. Use
+   * {@link #getViewByTestId(String)} instead.
+   */
+  @Deprecated
+  public <T extends View> T getViewAtPath(int... path) {
+    return ReactTestHelper.getViewAtPath((ViewGroup) getRootView().getParent(), path);
+  }
+
+  public <T extends View> T getViewByTestId(String testID) {
+    return (T) ReactTestHelper
+        .getViewWithReactTestId((ViewGroup) getRootView().getParent(), testID);
+  }
+
+  public SingleTouchGestureGenerator createGestureGenerator() {
+    return new SingleTouchGestureGenerator(getRootView(), this);
+  }
+
+  public void waitForBridgeAndUIIdle() {
+    getActivity().waitForBridgeAndUIIdle();
+  }
+
+  public void waitForBridgeAndUIIdle(long timeoutMs) {
+    getActivity().waitForBridgeAndUIIdle(timeoutMs);
+  }
+
+  protected Bitmap getScreenshot() {
+    // Wait for the UI to settle. If the UI is doing animations, this may be unsafe!
+    getInstrumentation().waitForIdleSync();
+
+    final CountDownLatch latch = new CountDownLatch(1);
+    final BitmapHolder bitmapHolder = new BitmapHolder();
+    final Runnable getScreenshotRunnable = new Runnable() {
+
+      private static final int MAX_TRIES = 1000;
+      // This is the constant used in the support library for APIs that don't have Choreographer
+      private static final int FRAME_DELAY_MS = 10;
+
+      private int mNumRuns = 0;
+
+      @Override
+      public void run() {
+        mNumRuns++;
+        ReactAppTestActivity activity = getActivity();
+        if (!activity.isScreenshotReady()) {
+          if (mNumRuns > MAX_TRIES) {
+            throw new RuntimeException(
+                "Waited " + MAX_TRIES + " frames to get screenshot but it's still not ready!");
+          }
+          activity.postDelayed(this, FRAME_DELAY_MS);
+          return;
+        }
+
+        bitmapHolder.bitmap = getActivity().getCurrentScreenshot();
+        latch.countDown();
+      }
+    };
+
+
+    getActivity().runOnUiThread(getScreenshotRunnable);
+    try {
+      if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+        throw new RuntimeException("Timed out waiting for screenshot runnable to run!");
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+    return Assertions.assertNotNull(bitmapHolder.bitmap);
+  }
+
+  /**
+   * Implement this method to provide application key to be launched. List of available
+   * application is located in TestBundle.js file
+   */
+  protected abstract String getReactApplicationKeyUnderTest();
+
+  protected boolean getEnableDevSupport() {
+    return false;
+  }
+
+  /**
+   * Override this method to provide extra native modules to be loaded before the app starts
+   */
+  protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
+    return new ReactInstanceSpecForTest();
+  }
+
+  protected ReactContext getReactContext() {
+    return getActivity().getReactContext();
+  }
+
+  /**
+   * Helper class to pass the bitmap between execution scopes in {@link #getScreenshot()}.
+   */
+  private static class BitmapHolder {
+
+    public @Nullable volatile Bitmap bitmap;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
new file mode 100644
index 000000000000..37f0a4db3955
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
@@ -0,0 +1,257 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.LifecycleState;
+import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.ReactRootView;
+import com.facebook.react.shell.MainReactPackage;
+import com.facebook.react.uimanager.UIImplementationProvider;
+
+public class ReactAppTestActivity extends FragmentActivity implements
+    DefaultHardwareBackBtnHandler
+{
+
+  private static final String DEFAULT_BUNDLE_NAME = "AndroidTestBundle.js";
+  private static final int ROOT_VIEW_ID = 8675309;
+  // we need a bigger timeout for CI builds because they run on a slow emulator
+  private static final long IDLE_TIMEOUT_MS = 60000;
+
+  private CountDownLatch mLayoutEvent = new CountDownLatch(1);
+  private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
+  private ScreenshotingFrameLayout mScreenshotingFrameLayout;
+  private final CountDownLatch mDestroyCountDownLatch = new CountDownLatch(1);
+  private @Nullable ReactInstanceManager mReactInstanceManager;
+  private @Nullable ReactRootView mReactRootView;
+  private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    overridePendingTransition(0, 0);
+
+    // We wrap screenshot layout in another FrameLayout in order to handle custom dimensions of the
+    // screenshot view set through {@link #setScreenshotDimensions}
+    FrameLayout rootView = new FrameLayout(this);
+    setContentView(rootView);
+
+    mScreenshotingFrameLayout = new ScreenshotingFrameLayout(this);
+    mScreenshotingFrameLayout.setId(ROOT_VIEW_ID);
+    rootView.addView(mScreenshotingFrameLayout);
+
+    mReactRootView = new ReactRootView(this);
+    mScreenshotingFrameLayout.addView(mReactRootView);
+  }
+
+  @Override
+  protected void onPause() {
+    super.onPause();
+
+    mLifecycleState = LifecycleState.BEFORE_RESUME;
+
+    overridePendingTransition(0, 0);
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onHostPause();
+    }
+  }
+
+  @Override
+  protected void onResume() {
+    super.onResume();
+
+    mLifecycleState = LifecycleState.RESUMED;
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onHostResume(this, this);
+    }
+  }
+
+  @Override
+  protected void onDestroy() {
+    super.onDestroy();
+    mDestroyCountDownLatch.countDown();
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.destroy();
+    }
+  }
+
+  public void waitForDestroy(long timeoutMs) throws InterruptedException {
+    mDestroyCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+  }
+
+  public void loadApp(String appKey, ReactInstanceSpecForTest spec, boolean enableDevSupport) {
+    loadApp(appKey, spec, null, DEFAULT_BUNDLE_NAME, enableDevSupport);
+  }
+
+  public void loadApp(String appKey, ReactInstanceSpecForTest spec, String bundleName) {
+    loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */);
+  }
+
+  public void loadApp(
+    String appKey,
+    ReactInstanceSpecForTest spec,
+    String bundleName,
+    UIImplementationProvider uiImplementationProvider) {
+    loadApp(appKey, spec, null, bundleName, false /* = useDevSupport */, uiImplementationProvider);
+  }
+
+  public void resetRootViewForScreenshotTests() {
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.destroy();
+      mReactInstanceManager = null;
+    }
+    mReactRootView = new ReactRootView(this);
+    mScreenshotingFrameLayout.removeAllViews();
+    mScreenshotingFrameLayout.addView(mReactRootView);
+  }
+
+  public void loadApp(
+      String appKey,
+      ReactInstanceSpecForTest spec,
+      @Nullable Bundle initialProps,
+      String bundleName,
+      boolean useDevSupport) {
+    loadApp(appKey, spec, initialProps, bundleName, useDevSupport, null);
+  }
+
+  public void loadApp(
+    String appKey,
+    ReactInstanceSpecForTest spec,
+    @Nullable Bundle initialProps,
+    String bundleName,
+    boolean useDevSupport,
+    UIImplementationProvider uiImplementationProvider) {
+
+    final CountDownLatch currentLayoutEvent = mLayoutEvent = new CountDownLatch(1);
+    mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
+
+    ReactInstanceManager.Builder builder =
+      ReactTestHelper.getReactTestFactory().getReactInstanceManagerBuilder()
+        .setApplication(getApplication())
+        .setBundleAssetName(bundleName)
+        // By not setting a JS module name, we force the bundle to be always loaded from
+        // assets, not the devserver, even if dev mode is enabled (such as when testing redboxes).
+        // This makes sense because we never run the devserver in tests.
+        //.setJSMainModuleName()
+        .addPackage(spec.getAlternativeReactPackageForTest() != null ?
+            spec.getAlternativeReactPackageForTest() : new MainReactPackage())
+        .addPackage(new InstanceSpecForTestPackage(spec))
+        .setUseDeveloperSupport(useDevSupport)
+        .setBridgeIdleDebugListener(mBridgeIdleSignaler)
+        .setInitialLifecycleState(mLifecycleState)
+        .setUIImplementationProvider(uiImplementationProvider);
+
+    mReactInstanceManager = builder.build();
+    mReactInstanceManager.onHostResume(this, this);
+
+    Assertions.assertNotNull(mReactRootView).getViewTreeObserver().addOnGlobalLayoutListener(
+        new ViewTreeObserver.OnGlobalLayoutListener() {
+          @Override
+          public void onGlobalLayout() {
+            currentLayoutEvent.countDown();
+          }
+        });
+    Assertions.assertNotNull(mReactRootView)
+        .startReactApplication(mReactInstanceManager, appKey, initialProps);
+  }
+
+  public boolean waitForLayout(long millis) throws InterruptedException {
+    return mLayoutEvent.await(millis, TimeUnit.MILLISECONDS);
+  }
+
+  public void waitForBridgeAndUIIdle() {
+    waitForBridgeAndUIIdle(IDLE_TIMEOUT_MS);
+  }
+
+  public void waitForBridgeAndUIIdle(long timeoutMs) {
+    ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
+        Assertions.assertNotNull(mBridgeIdleSignaler),
+        getReactContext(),
+        timeoutMs);
+  }
+
+  public View getRootView() {
+    return Assertions.assertNotNull(mReactRootView);
+  }
+
+  public ReactContext getReactContext() {
+    return waitForReactContext();
+  }
+
+  // Because react context is created asynchronously, we may have to wait until it is available.
+  // It's simpler than exposing synchronosition mechanism to notify listener than react context
+  // creation has completed.
+  private ReactContext waitForReactContext() {
+    Assertions.assertNotNull(mReactInstanceManager);
+
+    try {
+      while (true) {
+        ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
+        if (reactContext != null) {
+           return reactContext;
+        }
+        Thread.sleep(100);
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public void postDelayed(Runnable r, int delayMS) {
+    getRootView().postDelayed(r, delayMS);
+  }
+
+  /**
+   * Does not ensure that this is run on the UI thread or that the UI Looper is idle like
+   * {@link ReactAppInstrumentationTestCase#getScreenshot()}. You probably want to use that
+   * instead.
+   */
+  public Bitmap getCurrentScreenshot() {
+    return mScreenshotingFrameLayout.getLastDrawnBitmap();
+  }
+
+  public boolean isScreenshotReady() {
+    return mScreenshotingFrameLayout.isScreenshotReady();
+  }
+
+  public void setScreenshotDimensions(int width, int height) {
+    mScreenshotingFrameLayout.setLayoutParams(new FrameLayout.LayoutParams(width, height));
+  }
+
+  @Override
+  public void invokeDefaultOnBackPressed() {
+    super.onBackPressed();
+  }
+
+  @Override
+  public void onRequestPermissionsResult(
+      int requestCode,
+      String[] permissions,
+      int[] grantResults) {
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java
new file mode 100644
index 000000000000..ffd941f9a228
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
+
+/**
+ * Utility class that uses {@link NotThreadSafeBridgeIdleDebugListener} interface to allow callers
+ * to wait for the bridge to be idle.
+ */
+public class ReactBridgeIdleSignaler implements NotThreadSafeBridgeIdleDebugListener {
+
+  // Starts at 1 since bridge starts idle. The logic here is that the semaphore is only acquirable
+  // if the bridge is idle.
+  private final Semaphore mBridgeIdleSemaphore = new Semaphore(1);
+
+  private volatile boolean mIsBridgeIdle = true;
+
+  @Override
+  public void onTransitionToBridgeIdle() {
+    mIsBridgeIdle = true;
+    mBridgeIdleSemaphore.release();
+  }
+
+  @Override
+  public void onTransitionToBridgeBusy() {
+    mIsBridgeIdle = false;
+    try {
+      if (!mBridgeIdleSemaphore.tryAcquire(15000, TimeUnit.MILLISECONDS)) {
+        throw new RuntimeException(
+            "Timed out waiting to acquire the test idle listener semaphore. Deadlock?");
+      }
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Got interrupted", e);
+    }
+  }
+
+  public boolean isBridgeIdle() {
+    return mIsBridgeIdle;
+  }
+
+  public boolean waitForIdle(long millis) {
+    try {
+      if (mBridgeIdleSemaphore.tryAcquire(millis, TimeUnit.MILLISECONDS)) {
+        mBridgeIdleSemaphore.release();
+        return true;
+      }
+      return false;
+    } catch (InterruptedException e) {
+      throw new RuntimeException("Got interrupted", e);
+    }
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java
new file mode 100644
index 000000000000..340dec8347f6
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.view.Choreographer;
+
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.UiThreadUtil;
+
+public class ReactIdleDetectionUtil {
+
+  /**
+   * Waits for both the UI thread and bridge to be idle. It determines this by waiting for the
+   * bridge to become idle, then waiting for the UI thread to become idle, then checking if the
+   * bridge is idle again (if the bridge was idle before and is still idle after running the UI
+   * thread to idle, then there are no more events to process in either place).
+   * <p/>
+   * Also waits for any Choreographer callbacks to run after the initial sync since things like UI
+   * events are initiated from Choreographer callbacks.
+   */
+  public static void waitForBridgeAndUIIdle(
+      ReactBridgeIdleSignaler idleSignaler,
+      final ReactContext reactContext,
+      long timeoutMs) {
+    UiThreadUtil.assertNotOnUiThread();
+
+    long startTime = SystemClock.uptimeMillis();
+    waitInner(idleSignaler, timeoutMs);
+
+    long timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
+    waitForChoreographer(timeToWait);
+    waitForJSIdle(reactContext);
+
+    timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
+    waitInner(idleSignaler, timeToWait);
+    timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime));
+    waitForChoreographer(timeToWait);
+  }
+
+  private static void waitForChoreographer(long timeToWait) {
+    final int waitFrameCount = 2;
+    final CountDownLatch latch = new CountDownLatch(1);
+    UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            Choreographer.getInstance().postFrameCallback(
+                new Choreographer.FrameCallback() {
+
+                  private int frameCount = 0;
+
+                  @Override
+                  public void doFrame(long frameTimeNanos) {
+                    frameCount++;
+                    if (frameCount == waitFrameCount) {
+                      latch.countDown();
+                    } else {
+                      Choreographer.getInstance().postFrameCallback(this);
+                    }
+                  }
+                });
+          }
+        });
+    try {
+      if (!latch.await(timeToWait, TimeUnit.MILLISECONDS)) {
+        throw new RuntimeException("Timed out waiting for Choreographer");
+      }
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static void waitForJSIdle(ReactContext reactContext) {
+    if (!reactContext.hasActiveCatalystInstance()) {
+      return;
+    }
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    reactContext.runOnJSQueueThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            latch.countDown();
+          }
+        });
+
+    try {
+      if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
+        throw new RuntimeException("Timed out waiting for JS thread");
+      }
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static void waitInner(ReactBridgeIdleSignaler idleSignaler, long timeToWait) {
+    // TODO gets broken in gradle, do we need it?
+    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+    long startTime = SystemClock.uptimeMillis();
+    boolean bridgeWasIdle = false;
+    while (SystemClock.uptimeMillis() - startTime < timeToWait) {
+      boolean bridgeIsIdle = idleSignaler.isBridgeIdle();
+      if (bridgeIsIdle && bridgeWasIdle) {
+        return;
+      }
+      bridgeWasIdle = bridgeIsIdle;
+      long newTimeToWait = Math.max(1, timeToWait - (SystemClock.uptimeMillis() - startTime));
+      idleSignaler.waitForIdle(newTimeToWait);
+      instrumentation.waitForIdleSync();
+    }
+    throw new RuntimeException("Timed out waiting for bridge and UI idle!");
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstanceSpecForTest.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstanceSpecForTest.java
new file mode 100644
index 000000000000..b9de7a1e7726
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactInstanceSpecForTest.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.annotation.SuppressLint;
+
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.react.ReactPackage;
+
+/**
+ * A spec that allows a test to add additional NativeModules/JS modules to the ReactInstance. This
+ * can also be used to stub out existing native modules by adding another module with the same name
+ * as a built-in module.
+ */
+@SuppressLint("JavatestsIncorrectFolder")
+public class ReactInstanceSpecForTest {
+
+  private final List<NativeModule> mNativeModules = new ArrayList<>();
+  private final List<Class<? extends JavaScriptModule>> mJSModuleSpecs = new ArrayList<>();
+  private final List<ViewManager> mViewManagers = new ArrayList<>();
+  private ReactPackage mReactPackage = null;
+
+  public ReactInstanceSpecForTest addNativeModule(NativeModule module) {
+    mNativeModules.add(module);
+    return this;
+  }
+
+  public ReactInstanceSpecForTest addJSModule(Class jsClass) {
+    mJSModuleSpecs.add(jsClass);
+    return this;
+  }
+
+  public ReactInstanceSpecForTest setPackage(ReactPackage reactPackage) {
+    mReactPackage = reactPackage;
+    return this;
+  }
+
+  public ReactInstanceSpecForTest addViewManager(ViewManager viewManager) {
+    mViewManagers.add(viewManager);
+    return this;
+  }
+
+  public List<NativeModule> getExtraNativeModulesForTest() {
+    return mNativeModules;
+  }
+
+  public List<Class<? extends JavaScriptModule>> getExtraJSModulesForTest() {
+    return mJSModuleSpecs;
+  }
+
+  public ReactPackage getAlternativeReactPackageForTest() {
+    return mReactPackage;
+  }
+
+  public List<ViewManager> getExtraViewManagers() {
+    return mViewManagers;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
new file mode 100644
index 000000000000..b8d85fe0ffdb
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import android.app.Application;
+import android.support.test.InstrumentationRegistry;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.SoftAssertions;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.common.ApplicationHolder;
+import com.facebook.react.common.futures.SimpleSettableFuture;
+import com.facebook.react.modules.core.Timing;
+
+/**
+ * Use this class for writing integration tests of catalyst. This class will run all JNI call
+ * within separate android looper, thus you don't need to care about starting your own looper.
+ *
+ * Keep in mind that all JS remote method calls and script load calls are asynchronous and you
+ * should not expect them to return results immediately.
+ *
+ * In order to write catalyst integration:
+ *  1) Make {@link ReactIntegrationTestCase} a base class of your test case
+ *  2) Use {@link ReactTestHelper#catalystInstanceBuilder()}
+ *  instead of {@link com.facebook.react.bridge.CatalystInstanceImpl.Builder} to build catalyst
+ *  instance for testing purposes
+ *
+ */
+public abstract class ReactIntegrationTestCase extends AndroidTestCase {
+
+  private static final long IDLE_TIMEOUT_MS = 15000;
+
+  private @Nullable CatalystInstance mInstance;
+  private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
+  private @Nullable ReactApplicationContext mReactContext;
+
+  @Override
+  public ReactApplicationContext getContext() {
+    if (mReactContext == null) {
+      mReactContext = new ReactApplicationContext(super.getContext());
+      Assertions.assertNotNull(mReactContext);
+    }
+
+    return mReactContext;
+  }
+
+  public void shutDownContext() {
+    if (mInstance != null) {
+      final ReactContext contextToDestroy = mReactContext;
+      mReactContext = null;
+      mInstance = null;
+
+      final SimpleSettableFuture<Void> semaphore = new SimpleSettableFuture<>();
+      UiThreadUtil.runOnUiThread(new Runnable() {
+        @Override
+        public void run() {
+          if (contextToDestroy != null) {
+            contextToDestroy.destroy();
+          }
+          semaphore.set(null);
+        }
+      });
+      semaphore.getOrThrow();
+    }
+  }
+
+  /**
+   * This method isn't safe since it doesn't factor in layout-only view removal. Use
+   * {@link #getViewByTestId} instead.
+   */
+  @Deprecated
+  public <T extends View> T getViewAtPath(ViewGroup rootView, int... path) {
+    return ReactTestHelper.getViewAtPath(rootView, path);
+  }
+
+  public <T extends View> T getViewByTestId(ViewGroup rootView, String testID) {
+    return (T) ReactTestHelper.getViewWithReactTestId(rootView, testID);
+  }
+
+  public static class Event {
+    private final CountDownLatch mLatch;
+
+    public Event() {
+      this(1);
+    }
+
+    public Event(int counter) {
+      mLatch = new CountDownLatch(counter);
+    }
+
+    public void occur() {
+      mLatch.countDown();
+    }
+
+    public boolean didOccur() {
+      return mLatch.getCount() == 0;
+    }
+
+    public boolean await(long millis) {
+      try {
+        return mLatch.await(millis, TimeUnit.MILLISECONDS);
+      } catch (InterruptedException ignore) {
+        return false;
+      }
+    }
+  }
+
+  /**
+   * Timing module needs to be created on the main thread so that it gets the correct Choreographer.
+   */
+  protected Timing createTimingModule() {
+    final SimpleSettableFuture<Timing> simpleSettableFuture = new SimpleSettableFuture<Timing>();
+    UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            Timing timing = new Timing(getContext());
+            simpleSettableFuture.set(timing);
+          }
+        });
+    try {
+      return simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  public void initializeWithInstance(CatalystInstance instance) {
+    mInstance = instance;
+    mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
+    mInstance.addBridgeIdleDebugListener(mBridgeIdleSignaler);
+    getContext().initializeWithInstance(mInstance);
+  }
+
+  public boolean waitForBridgeIdle(long millis) {
+    return Assertions.assertNotNull(mBridgeIdleSignaler).waitForIdle(millis);
+  }
+
+  public void waitForIdleSync() {
+    InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+  }
+
+  public void waitForBridgeAndUIIdle() {
+    ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
+        Assertions.assertNotNull(mBridgeIdleSignaler),
+        getContext(),
+        IDLE_TIMEOUT_MS);
+  }
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    ApplicationHolder.setApplication((Application) getContext().getApplicationContext());
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    super.tearDown();
+    shutDownContext();
+  }
+
+  protected static void initializeJavaModule(final BaseJavaModule javaModule) {
+    final Semaphore semaphore = new Semaphore(0);
+    UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            javaModule.initialize();
+            if (javaModule instanceof LifecycleEventListener) {
+              ((LifecycleEventListener) javaModule).onHostResume();
+            }
+            semaphore.release();
+          }
+        });
+    try {
+      SoftAssertions.assertCondition(
+          semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS),
+          "Timed out initializing timing module");
+    } catch (InterruptedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java
new file mode 100644
index 000000000000..417638dd2a8f
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactSettingsForTests.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import com.facebook.react.modules.debug.DeveloperSettings;
+
+public class ReactSettingsForTests implements DeveloperSettings {
+
+  @Override
+  public boolean isFpsDebugEnabled() {
+    return false;
+  }
+
+  @Override
+  public boolean isAnimationFpsDebugEnabled() {
+    return false;
+  }
+
+  @Override
+  public boolean isJSDevModeEnabled() {
+    return true;
+  }
+
+  @Override
+  public boolean isElementInspectorEnabled() {
+    return false;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestFactory.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestFactory.java
new file mode 100644
index 000000000000..42877cdd8969
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestFactory.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import android.content.Context;
+
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.NativeModule;
+
+public interface ReactTestFactory {
+  public static interface ReactInstanceEasyBuilder {
+    ReactInstanceEasyBuilder setContext(Context context);
+    ReactInstanceEasyBuilder addNativeModule(NativeModule module);
+    ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass);
+    CatalystInstance build();
+  }
+
+  ReactInstanceEasyBuilder getCatalystInstanceBuilder();
+  ReactInstanceManager.Builder getReactInstanceManagerBuilder();
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
new file mode 100644
index 000000000000..20afcb234106
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.facebook.react.ReactInstanceManager;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.CatalystInstanceImpl;
+import com.facebook.react.bridge.JSBundleLoader;
+import com.facebook.react.bridge.JSCJavaScriptExecutor;
+import com.facebook.react.bridge.JavaScriptModulesConfig;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.NativeModuleRegistry;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+
+import com.android.internal.util.Predicate;
+
+public class ReactTestHelper {
+  private static class DefaultReactTestFactory implements ReactTestFactory {
+    private static class ReactInstanceEasyBuilderImpl implements ReactInstanceEasyBuilder {
+      private @Nullable Context mContext;
+      private final NativeModuleRegistry.Builder mNativeModuleRegistryBuilder =
+        new NativeModuleRegistry.Builder();
+      private final JavaScriptModulesConfig.Builder mJSModulesConfigBuilder =
+        new JavaScriptModulesConfig.Builder();
+
+      @Override
+      public ReactInstanceEasyBuilder setContext(Context context) {
+        mContext = context;
+        return this;
+      }
+
+      @Override
+      public ReactInstanceEasyBuilder addNativeModule(NativeModule module) {
+        mNativeModuleRegistryBuilder.add(module);
+        return this;
+      }
+
+      @Override
+      public ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass) {
+        mJSModulesConfigBuilder.add(moduleInterfaceClass);
+        return this;
+      }
+
+      @Override
+      public CatalystInstance build() {
+        return new CatalystInstanceImpl.Builder()
+          .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
+          .setJSExecutor(new JSCJavaScriptExecutor(new WritableNativeMap()))
+          .setRegistry(mNativeModuleRegistryBuilder.build())
+          .setJSModulesConfig(mJSModulesConfigBuilder.build())
+          .setJSBundleLoader(JSBundleLoader.createFileLoader(
+                               mContext,
+                               "assets://AndroidTestBundle.js"))
+          .setNativeModuleCallExceptionHandler(
+            new NativeModuleCallExceptionHandler() {
+                @Override
+                public void handleException(Exception e) {
+                  throw new RuntimeException(e);
+                }
+            })
+          .build();
+      }
+    }
+
+    @Override
+    public ReactInstanceEasyBuilder getCatalystInstanceBuilder() {
+      return new ReactInstanceEasyBuilderImpl();
+    }
+
+    @Override
+    public ReactInstanceManager.Builder getReactInstanceManagerBuilder() {
+      return ReactInstanceManager.builder();
+    }
+  }
+
+  public static ReactTestFactory getReactTestFactory() {
+    Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+    if (!(inst instanceof ReactTestFactory)) {
+      return new DefaultReactTestFactory();
+    }
+
+    return (ReactTestFactory) inst;
+  }
+
+  public static ReactTestFactory.ReactInstanceEasyBuilder catalystInstanceBuilder(
+      final ReactIntegrationTestCase testCase) {
+    final ReactTestFactory.ReactInstanceEasyBuilder builder =
+      getReactTestFactory().getCatalystInstanceBuilder();
+    ReactTestFactory.ReactInstanceEasyBuilder postBuilder =
+      new ReactTestFactory.ReactInstanceEasyBuilder() {
+        @Override
+        public ReactTestFactory.ReactInstanceEasyBuilder setContext(Context context) {
+          builder.setContext(context);
+          return this;
+        }
+
+        @Override
+        public ReactTestFactory.ReactInstanceEasyBuilder addNativeModule(NativeModule module) {
+          builder.addNativeModule(module);
+          return this;
+        }
+
+        @Override
+        public ReactTestFactory.ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass) {
+          builder.addJSModule(moduleInterfaceClass);
+          return this;
+        }
+
+        @Override
+        public CatalystInstance build() {
+          CatalystInstance instance = builder.build();
+          testCase.initializeWithInstance(instance);
+          instance.runJSBundle();
+          testCase.waitForBridgeAndUIIdle();
+          return instance;
+        }
+      };
+
+    postBuilder.setContext(testCase.getContext());
+    return postBuilder;
+  }
+
+  /**
+   * Gets the view at given path in the UI hierarchy, ignoring modals.
+   */
+  public static <T extends View> T getViewAtPath(ViewGroup rootView, int... path) {
+    // The application root element is wrapped in a helper view in order
+    // to be able to display modals. See renderApplication.js.
+    ViewGroup appWrapperView = rootView;
+    View view = appWrapperView.getChildAt(0);
+    for (int i = 0; i < path.length; i++) {
+      view = ((ViewGroup) view).getChildAt(path[i]);
+    }
+    return (T) view;
+  }
+
+  /**
+   * Gets the view with a given react test ID in the UI hierarchy. React test ID is currently
+   * propagated into view content description.
+   */
+  public static View getViewWithReactTestId(View rootView, String testId) {
+    return findChild(rootView, hasTagValue(testId));
+  }
+
+  public static String getTestId(View view) {
+    return view.getTag() instanceof String ? (String) view.getTag() : null;
+  }
+
+  private static View findChild(View root, Predicate<View> predicate) {
+    if (predicate.apply(root)) {
+      return root;
+    }
+    if (root instanceof ViewGroup) {
+      ViewGroup viewGroup = (ViewGroup) root;
+      for (int i = 0; i < viewGroup.getChildCount(); i++) {
+        View child = viewGroup.getChildAt(i);
+        View result = findChild(child, predicate);
+        if (result != null) {
+          return result;
+        }
+      }
+    }
+    return null;
+  }
+
+  private static Predicate<View> hasTagValue(final String tagValue) {
+    return new Predicate<View>() {
+      @Override
+      public boolean apply(View view) {
+        Object tag = view.getTag();
+        return tag != null && tag.equals(tagValue);
+      }
+    };
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ScreenshotingFrameLayout.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ScreenshotingFrameLayout.java
new file mode 100644
index 000000000000..ffc648e79157
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ScreenshotingFrameLayout.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import javax.annotation.Nullable;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.os.Looper;
+import android.widget.FrameLayout;
+
+/**
+ * A FrameLayout that allows you to access the result of the last time its hierarchy was drawn. It
+ * accomplishes this by drawing its hierarchy into a software Canvas, saving the resulting Bitmap
+ * and then drawing that Bitmap to the actual Canvas provided by the system.
+ */
+public class ScreenshotingFrameLayout extends FrameLayout {
+
+  private @Nullable Bitmap mBitmap;
+  private Canvas mCanvas;
+
+  public ScreenshotingFrameLayout(Context context) {
+    super(context);
+    mCanvas = new Canvas();
+  }
+
+  @Override
+  protected void dispatchDraw(Canvas canvas) {
+    if (mBitmap == null) {
+      mBitmap = createNewBitmap(canvas);
+      mCanvas.setBitmap(mBitmap);
+    } else if (mBitmap.getWidth() != canvas.getWidth() ||
+        mBitmap.getHeight() != canvas.getHeight()) {
+      mBitmap.recycle();
+      mBitmap = createNewBitmap(canvas);
+      mCanvas.setBitmap(mBitmap);
+    }
+
+    super.dispatchDraw(mCanvas);
+    canvas.drawBitmap(mBitmap, 0, 0, null);
+  }
+
+  private static Bitmap createNewBitmap(Canvas canvas) {
+    return Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
+  }
+
+  public Bitmap getLastDrawnBitmap() {
+    if (mBitmap == null) {
+      throw new RuntimeException("View has not been drawn yet!");
+    }
+    if (Looper.getMainLooper() != Looper.myLooper()) {
+      throw new RuntimeException(
+          "Must access screenshots from main thread or you may get partially drawn Bitmaps");
+    }
+    if (!isScreenshotReady()) {
+      throw new RuntimeException("Trying to get screenshot, but the view is dirty or needs layout");
+    }
+    return Bitmap.createBitmap(mBitmap);
+  }
+
+  public boolean isScreenshotReady() {
+    return !isDirty() && !isLayoutRequested();
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java
new file mode 100644
index 000000000000..278facbcb280
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * Provides methods for generating touch events and dispatching them directly to a given view.
+ * Events scenarios are based on {@link android.test.TouchUtils} but they get gets dispatched
+ * directly through the view hierarchy using {@link View#dispatchTouchEvent} method instead of
+ * using instrumentation API.
+ * <p>
+ * All the events for a gesture are dispatched immediately which makes tests run very fast.
+ * The eventTime for each event is still set correctly. Android's gesture recognizers check
+ * eventTime in order to figure out gesture speed, and therefore scroll vs fling is recognized.
+ */
+public class SingleTouchGestureGenerator {
+
+  private static final long DEFAULT_DELAY_MS = 20;
+
+  private View mDispatcherView;
+  private IdleWaiter mIdleWaiter;
+  private long mLastDownTime;
+  private long mEventTime;
+  private float mLastX;
+  private float mLastY;
+
+  private ViewConfiguration mViewConfig;
+
+  public SingleTouchGestureGenerator(View view, IdleWaiter idleWaiter) {
+    mDispatcherView = view;
+    mIdleWaiter = idleWaiter;
+    mViewConfig = ViewConfiguration.get(view.getContext());
+  }
+
+  private SingleTouchGestureGenerator dispatchEvent(
+      final int action,
+      final float x,
+      final float y,
+      long eventTime) {
+    mEventTime = eventTime;
+    if (action == MotionEvent.ACTION_DOWN) {
+      mLastDownTime = eventTime;
+    }
+    mLastX = x;
+    mLastY = y;
+    mDispatcherView.post(
+        new Runnable() {
+          @Override
+          public void run() {
+            MotionEvent event = MotionEvent.obtain(mLastDownTime, mEventTime, action, x, y, 0);
+            mDispatcherView.dispatchTouchEvent(event);
+            event.recycle();
+          }
+        });
+    mIdleWaiter.waitForBridgeAndUIIdle();
+    return this;
+  }
+
+  private float getViewCenterX(View view) {
+    int[] xy = new int[2];
+    view.getLocationOnScreen(xy);
+    int viewWidth = view.getWidth();
+    return xy[0] + (viewWidth / 2.0f);
+  }
+
+  private float getViewCenterY(View view) {
+    int[] xy = new int[2];
+    view.getLocationOnScreen(xy);
+    int viewHeight = view.getHeight();
+    return xy[1] + (viewHeight / 2.0f);
+  }
+
+  public SingleTouchGestureGenerator startGesture(float x, float y) {
+    return dispatchEvent(MotionEvent.ACTION_DOWN, x, y, SystemClock.uptimeMillis());
+  }
+
+  public SingleTouchGestureGenerator startGesture(View view) {
+    return startGesture(getViewCenterX(view), getViewCenterY(view));
+  }
+
+  private SingleTouchGestureGenerator dispatchDelayedEvent(
+      int action,
+      float x,
+      float y,
+      long delay) {
+    return dispatchEvent(action, x, y, mEventTime + delay);
+  }
+
+  public SingleTouchGestureGenerator endGesture(float x, float y, long delay) {
+    return dispatchDelayedEvent(MotionEvent.ACTION_UP, x, y, delay);
+  }
+
+  public SingleTouchGestureGenerator endGesture(float x, float y) {
+    return endGesture(x, y, DEFAULT_DELAY_MS);
+  }
+
+  public SingleTouchGestureGenerator endGesture() {
+    return endGesture(mLastX, mLastY);
+  }
+
+  public SingleTouchGestureGenerator moveGesture(float x, float y, long delay) {
+    return dispatchDelayedEvent(MotionEvent.ACTION_MOVE, x, y, delay);
+  }
+
+  public SingleTouchGestureGenerator moveBy(float dx, float dy, long delay) {
+    return moveGesture(mLastX + dx, mLastY + dy, delay);
+  }
+
+  public SingleTouchGestureGenerator moveBy(float dx, float dy) {
+    return moveBy(dx, dy, DEFAULT_DELAY_MS);
+  }
+
+  public SingleTouchGestureGenerator clickViewAt(float x, float y) {
+    float touchSlop = mViewConfig.getScaledTouchSlop();
+    return startGesture(x, y).moveBy(touchSlop / 2.0f, touchSlop / 2.0f).endGesture();
+  }
+
+  public SingleTouchGestureGenerator drag(
+      float fromX,
+      float fromY,
+      float toX,
+      float toY,
+      int stepCount,
+      long totalDelay) {
+
+    float xStep = (toX - fromX) / stepCount;
+    float yStep = (toY - fromY) / stepCount;
+
+    float x = fromX;
+    float y = fromY;
+
+    for (int i = 0; i < stepCount; i++) {
+      x += xStep;
+      y += yStep;
+      moveGesture(x, y, totalDelay / stepCount);
+    }
+    return this;
+  }
+
+  public SingleTouchGestureGenerator dragTo(float toX, float toY, int stepCount, long totalDelay) {
+    return drag(mLastX, mLastY, toX, toY, stepCount, totalDelay);
+  }
+
+  public SingleTouchGestureGenerator dragTo(View view, int stepCount, long totalDelay) {
+    return dragTo(getViewCenterX(view), getViewCenterY(view), stepCount, totalDelay);
+  }
+
+  public SingleTouchGestureGenerator dragTo(View view, int stepCount) {
+    return dragTo(view, stepCount, stepCount * DEFAULT_DELAY_MS);
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/StringRecordingModule.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/StringRecordingModule.java
new file mode 100644
index 000000000000..8caa3b8f7117
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/StringRecordingModule.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2014-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.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+/**
+ * Native module provides single method {@link #record} which records its single string argument
+ * in calls array
+ */
+public class StringRecordingModule extends BaseJavaModule {
+
+  private final List<String> mCalls = new ArrayList<String>();
+
+  @Override
+  public String getName() {
+    return "Recording";
+  }
+
+  @ReactMethod
+  public void record(String text) {
+    mCalls.add(text);
+  }
+
+  public void reset() {
+    mCalls.clear();
+  }
+
+  public List<String> getCalls() {
+    return mCalls;
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK
new file mode 100644
index 000000000000..78d23f13542f
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK
@@ -0,0 +1,18 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'tests',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_integration_tests_target('java/com/facebook/react/testing:testing'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':tests',
+)
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactHorizontalScrollViewTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactHorizontalScrollViewTestCase.java
new file mode 100644
index 000000000000..963442d47062
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactHorizontalScrollViewTestCase.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2014-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.tests;
+
+import java.util.ArrayList;
+
+import android.view.View;
+import android.widget.HorizontalScrollView;
+
+import com.facebook.react.testing.AbstractScrollViewTestCase;
+import com.facebook.react.testing.SingleTouchGestureGenerator;
+import com.facebook.react.uimanager.PixelUtil;
+
+/**
+ * Integration test for horizontal ScrollView.
+ * See ScrollViewTestModule.js
+ */
+public class ReactHorizontalScrollViewTestCase extends AbstractScrollViewTestCase {
+
+  @Override
+  protected String getReactApplicationKeyUnderTest() {
+    return "HorizontalScrollViewTestApp";
+  }
+
+  private void dragLeft() {
+    dragLeft(200);
+  }
+
+  private void dragLeft(int durationMs) {
+    createGestureGenerator()
+        .startGesture(150, 50)
+        .dragTo(50, 60, 10, durationMs)
+        .endGesture(50, 60);
+  }
+
+  public void testScrolling() {
+    HorizontalScrollView scrollView = getViewAtPath(0);
+    assertNotNull(scrollView);
+    assertEquals(0, scrollView.getScrollX());
+
+    dragLeft();
+
+    assertTrue("Expected to scroll by at least 50 pixels", scrollView.getScrollX() >= 50);
+  }
+
+  public void testScrollEvents() {
+    HorizontalScrollView scrollView = getViewAtPath(0);
+
+    dragLeft();
+
+    waitForBridgeAndUIIdle();
+    mScrollListenerModule.waitForScrollIdle();
+    waitForBridgeAndUIIdle();
+
+    ArrayList<Double> xOffsets = mScrollListenerModule.getXOffsets();
+    assertFalse("Expected to receive at least one scroll event", xOffsets.isEmpty());
+    assertTrue("Expected offset to be greater than 0", xOffsets.get(xOffsets.size() - 1) > 0);
+    assertTrue(
+        "Expected no item click event fired",
+        mScrollListenerModule.getItemsPressed().isEmpty());
+    assertEquals(
+        "Expected last offset to be offset of scroll view",
+        PixelUtil.toDIPFromPixel(scrollView.getScrollX()),
+        xOffsets.get(xOffsets.size() - 1).doubleValue(),
+        1e-5);
+  }
+
+  public void testScrollAndClick() throws Exception {
+    SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
+
+    // Slowly drag the ScrollView to prevent fling
+    dragLeft(15000);
+
+    waitForBridgeAndUIIdle();
+    getInstrumentation().waitForIdleSync();
+
+    // Find visible item to be clicked
+    View visibleItem = null;
+    int visibleItemNumber = 0;
+    for (; visibleItemNumber < 100; visibleItemNumber++) {
+      visibleItem = getViewAtPath(0, 0, visibleItemNumber);
+      int pos[] = new int[2];
+      visibleItem.getLocationInWindow(pos);
+      if (pos[0] >= 0) {
+        break;
+      }
+    }
+
+    // Click first visible item
+    gestureGenerator.startGesture(visibleItem).endGesture();
+    waitForBridgeAndUIIdle();
+
+    ArrayList<Double> xOffsets = mScrollListenerModule.getXOffsets();
+    ArrayList<Integer> itemIds = mScrollListenerModule.getItemsPressed();
+    assertFalse("Expected to receive at least one scroll event", xOffsets.isEmpty());
+    assertTrue("Expected offset to be greater than 0", xOffsets.get(xOffsets.size() - 1) > 0);
+    assertEquals("Expected to receive exactly one item click event", 1, itemIds.size());
+    assertEquals(visibleItemNumber, (int) itemIds.get(0));
+  }
+
+  /**
+   * Verify that 'scrollTo' command makes ScrollView start scrolling
+   */
+  public void testScrollToCommand() throws Exception {
+    HorizontalScrollView scrollView = getViewAtPath(0);
+    ScrollViewTestModule jsModule =
+        getReactContext().getCatalystInstance().getJSModule(ScrollViewTestModule.class);
+
+    assertEquals(0, scrollView.getScrollX());
+
+    jsModule.scrollTo(300, 0);
+    waitForBridgeAndUIIdle();
+    getInstrumentation().waitForIdleSync();
+
+    // Unfortunately we need to use timeouts here in order to wait for scroll animation to happen
+    // there is no better way (yet) for waiting for scroll animation to finish
+    long timeout = 10000;
+    long interval = 50;
+    long start = System.currentTimeMillis();
+    while (System.currentTimeMillis() - start < timeout) {
+      if (scrollView.getScrollX() > 0) {
+        break;
+      }
+      Thread.sleep(interval);
+    }
+    assertNotSame(0, scrollView.getScrollX());
+  }
+}
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactScrollViewTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactScrollViewTestCase.java
new file mode 100644
index 000000000000..45a47a67383e
--- /dev/null
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/ReactScrollViewTestCase.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2014-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.tests;
+
+import java.util.ArrayList;
+
+import android.view.View;
+import android.widget.ScrollView;
+
+import com.facebook.react.testing.AbstractScrollViewTestCase;
+import com.facebook.react.testing.SingleTouchGestureGenerator;
+import com.facebook.react.uimanager.PixelUtil;
+
+/**
+ * Integration test for vertical ScrollView.
+ * See ScrollViewTestModule.js
+ */
+public class ReactScrollViewTestCase extends AbstractScrollViewTestCase {
+
+  @Override
+  protected String getReactApplicationKeyUnderTest() {
+    return "ScrollViewTestApp";
+  }
+
+  private void dragUp() {
+    dragUp(200);
+  }
+
+  private void dragUp(int durationMs) {
+    createGestureGenerator()
+        .startGesture(200, 200)
+        .dragTo(180, 100, 10, durationMs)
+        .endGesture(180, 100);
+  }
+
+  public void testScrolling() {
+    ScrollView scrollView = getViewAtPath(0);
+    assertNotNull(scrollView);
+    assertEquals(0, scrollView.getScrollY());
+
+    dragUp();
+
+    assertTrue("Expected to scroll by at least 50 pixels", scrollView.getScrollY() >= 50);
+  }
+
+  public void testScrollEvents() {
+    ScrollView scrollView = getViewAtPath(0);
+
+    dragUp();
+
+    waitForBridgeAndUIIdle();
+    mScrollListenerModule.waitForScrollIdle();
+    waitForBridgeAndUIIdle();
+
+    ArrayList<Double> yOffsets = mScrollListenerModule.getYOffsets();
+    assertFalse("Expected to receive at least one scroll event", yOffsets.isEmpty());
+    assertTrue("Expected offset to be greater than 0", yOffsets.get(yOffsets.size() - 1) > 0);
+    assertTrue(
+        "Expected no item click event fired",
+        mScrollListenerModule.getItemsPressed().isEmpty());
+    assertEquals(
+        "Expected last offset to be offset of scroll view",
+        PixelUtil.toDIPFromPixel(scrollView.getScrollY()),
+        yOffsets.get(yOffsets.size() - 1).doubleValue(),
+        1e-5);
+    assertTrue("Begin and End Drag should be called", mScrollListenerModule.dragEventsMatch());
+  }
+
+  public void testScrollAndClick() throws Exception {
+    SingleTouchGestureGenerator gestureGenerator = createGestureGenerator();
+
+    // Slowly drag the ScrollView to prevent fling
+    dragUp(15000);
+
+    waitForBridgeAndUIIdle();
+    getInstrumentation().waitForIdleSync();
+
+    // Find visible item to be clicked
+    View visibleItem = null;
+    int visibleItemNumber = 0;
+    for (; visibleItemNumber < 100; visibleItemNumber++) {
+      visibleItem = getViewAtPath(0, 0, visibleItemNumber);
+      int pos[] = new int[2];
+      visibleItem.getLocationInWindow(pos);
+      if (pos[1] >= 0) {
+        break;
+      }
+    }
+
+    // Click first visible item
+    gestureGenerator.startGesture(visibleItem).endGesture();
+    waitForBridgeAndUIIdle();
+
+    ArrayList<Double> yOffsets = mScrollListenerModule.getYOffsets();
+    ArrayList<Integer> itemIds = mScrollListenerModule.getItemsPressed();
+    assertFalse("Expected to receive at least one scroll event", yOffsets.isEmpty());
+    assertTrue("Expected offset to be greater than 0", yOffsets.get(yOffsets.size() - 1) > 0);
+    assertEquals("Expected to receive exactly one item click event", 1, itemIds.size());
+    assertEquals(visibleItemNumber, (int) itemIds.get(0));
+  }
+
+  /**
+   * Verify that 'scrollTo' command makes ScrollView start scrolling
+   */
+  public void testScrollToCommand() throws Exception {
+    ScrollView scrollView = getViewAtPath(0);
+    ScrollViewTestModule jsModule =
+        getReactContext().getCatalystInstance().getJSModule(ScrollViewTestModule.class);
+
+    assertEquals(0, scrollView.getScrollY());
+
+    jsModule.scrollTo(0, 300);
+    waitForBridgeAndUIIdle();
+    getInstrumentation().waitForIdleSync();
+
+    // Unfortunately we need to use timeouts here in order to wait for scroll animation to happen
+    // there is no better way (yet) for waiting for scroll animation to finish
+    long timeout = 10000;
+    long interval = 50;
+    long start = System.currentTimeMillis();
+    while (System.currentTimeMillis() - start < timeout) {
+      if (scrollView.getScrollY() > 0) {
+        break;
+      }
+      Thread.sleep(interval);
+    }
+    assertNotSame(0, scrollView.getScrollY());
+    assertFalse("Drag should not be called with scrollTo", mScrollListenerModule.dragEventsMatch());
+  }
+}
diff --git a/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK b/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK
new file mode 100644
index 000000000000..8dd771536fa1
--- /dev/null
+++ b/ReactAndroid/src/main/android_res/android/support/v7/appcompat-orig/BUCK
@@ -0,0 +1,10 @@
+include_defs('//ReactAndroid/DEFS')
+
+# used by ReactToolbarManager because of Gradle
+# TODO t10182713 will be replaced with res-for-appcompat when we stop using Gradle
+android_resource(
+  name = 'res-for-react-native',
+  res = react_native_dep('third-party/android/support/v7/appcompat-orig:res-unpacker-cmd'),
+  package = 'com.facebook.react',
+  visibility = ['//ReactAndroid/...',],
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java
index a5519e129af8..46827fb92fdd 100644
--- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java
@@ -7,7 +7,7 @@
  */
 
 // NOTE: this file is auto-copied from https://github.com/facebook/css-layout
-// @generated SignedSource<<4b95f0548441afa1e91e957a93fa6f0b>>
+// @generated SignedSource<<1f520d46cbfddbbea0661a8fb6a00748>>
 
 package com.facebook.csslayout;
 
@@ -56,7 +56,7 @@ public static interface MeasureFunction {
      *
      * NB: measure is NOT guaranteed to be threadsafe/re-entrant safe!
      */
-    public void measure(CSSNode node, float width, MeasureOutput measureOutput);
+    public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput);
   }
 
   // VisibleForTesting
@@ -128,13 +128,13 @@ public boolean isMeasureDefined() {
     return mMeasureFunction != null;
   }
 
-  /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width) {
+  /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width, float height) {
     if (!isMeasureDefined()) {
       throw new RuntimeException("Measure function isn't defined!");
     }
     measureOutput.height = CSSConstants.UNDEFINED;
     measureOutput.width = CSSConstants.UNDEFINED;
-    Assertions.assertNotNull(mMeasureFunction).measure(this, width, measureOutput);
+    Assertions.assertNotNull(mMeasureFunction).measure(this, width, height, measureOutput);
     return measureOutput;
   }
 
@@ -143,7 +143,7 @@ public boolean isMeasureDefined() {
    */
   public void calculateLayout(CSSLayoutContext layoutContext) {
     layout.resetResult();
-    LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, null);
+    LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, CSSConstants.UNDEFINED, null);
   }
 
   /**
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java
index 97ef4886f360..6d631d37c945 100644
--- a/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java
+++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CachedCSSLayout.java
@@ -7,7 +7,7 @@
  */
 
 // NOTE: this file is auto-copied from https://github.com/facebook/css-layout
-// @generated SignedSource<<8276834951a75286a0b6d4a980bc43ce>>
+// @generated SignedSource<<e2f8139d4c50e4d2a9281a4587f8c095>>
 
 package com.facebook.csslayout;
 
@@ -21,4 +21,5 @@ public class CachedCSSLayout extends CSSLayout {
   public float requestedWidth = CSSConstants.UNDEFINED;
   public float requestedHeight = CSSConstants.UNDEFINED;
   public float parentMaxWidth = CSSConstants.UNDEFINED;
+  public float parentMaxHeight = CSSConstants.UNDEFINED;
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java
index 30e14f052f61..5efd88e3b2a1 100644
--- a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java
+++ b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java
@@ -7,7 +7,7 @@
  */
 
 // NOTE: this file is auto-copied from https://github.com/facebook/css-layout
-// @generated SignedSource<<c4feb337df98136c86629cf72c93f049>>
+// @generated SignedSource<<dcc87213906997ff353adb1148c8e77c>>
 
 package com.facebook.csslayout;
 
@@ -19,7 +19,7 @@
 import static com.facebook.csslayout.CSSLayout.POSITION_TOP;
 
 /**
- * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float)}.
+ * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float, float)}.
  */
 public class LayoutEngine {
 
@@ -109,7 +109,7 @@ private static void setDimensionFromStyle(CSSNode node, int axis) {
       return;
     }
     // We only run if there's a width or height defined
-    if (Float.isNaN(node.style.dimensions[dim[axis]]) || 
+    if (Float.isNaN(node.style.dimensions[dim[axis]]) ||
         node.style.dimensions[dim[axis]] <= 0.0) {
       return;
     }
@@ -183,7 +183,7 @@ private static boolean isMeasureDefined(CSSNode node) {
     return node.isMeasureDefined();
   }
 
-  static boolean needsRelayout(CSSNode node, float parentMaxWidth) {
+  static boolean needsRelayout(CSSNode node, float parentMaxWidth, float parentMaxHeight) {
     return node.isDirty() ||
         !FloatUtil.floatsEqual(
             node.lastLayout.requestedHeight,
@@ -191,20 +191,23 @@ static boolean needsRelayout(CSSNode node, float parentMaxWidth) {
         !FloatUtil.floatsEqual(
             node.lastLayout.requestedWidth,
             node.layout.dimensions[DIMENSION_WIDTH]) ||
-        !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth);
+        !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth) ||
+        !FloatUtil.floatsEqual(node.lastLayout.parentMaxHeight, parentMaxHeight);
   }
 
   /*package*/ static void layoutNode(
       CSSLayoutContext layoutContext,
       CSSNode node,
       float parentMaxWidth,
+      float parentMaxHeight,
       CSSDirection parentDirection) {
-    if (needsRelayout(node, parentMaxWidth)) {
+    if (needsRelayout(node, parentMaxWidth, parentMaxHeight)) {
       node.lastLayout.requestedWidth = node.layout.dimensions[DIMENSION_WIDTH];
       node.lastLayout.requestedHeight = node.layout.dimensions[DIMENSION_HEIGHT];
       node.lastLayout.parentMaxWidth = parentMaxWidth;
+      node.lastLayout.parentMaxHeight = parentMaxHeight;
 
-      layoutNodeImpl(layoutContext, node, parentMaxWidth, parentDirection);
+      layoutNodeImpl(layoutContext, node, parentMaxWidth, parentMaxHeight, parentDirection);
       node.lastLayout.copy(node.layout);
     } else {
       node.layout.copy(node.lastLayout);
@@ -217,6 +220,7 @@ private static void layoutNodeImpl(
       CSSLayoutContext layoutContext,
       CSSNode node,
       float parentMaxWidth,
+      float parentMaxHeight,
       CSSDirection parentDirection) {
     for (int i = 0, childCount = node.getChildCount(); i < childCount; i++) {
       node.getChildAt(i).layout.resetResult();
@@ -251,9 +255,10 @@ private static void layoutNodeImpl(
     // invocations during the layout calculation.
     int childCount = node.getChildCount();
     float paddingAndBorderAxisResolvedRow = ((node.style.padding.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.border.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis])) + (node.style.padding.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]) + node.style.border.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])));
+    float paddingAndBorderAxisColumn = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])));
   
     if (isMeasureDefined(node)) {
-      boolean isResolvedRowDimDefined = !Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]);
+      boolean isResolvedRowDimDefined = (!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0);
   
       float width = CSSConstants.UNDEFINED;
       if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
@@ -266,6 +271,17 @@ private static void layoutNodeImpl(
       }
       width -= paddingAndBorderAxisResolvedRow;
   
+      float height = CSSConstants.UNDEFINED;
+      if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+        height = node.style.dimensions[DIMENSION_HEIGHT];
+      } else if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+        height = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]];
+      } else {
+        height = parentMaxHeight -
+          (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]));
+      }
+      height -= ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])));
+  
       // We only need to give a dimension for the text if we haven't got any
       // for it computed yet. It can either be from the style attribute or because
       // the element is flexible.
@@ -278,7 +294,8 @@ private static void layoutNodeImpl(
         MeasureOutput measureDim = node.measure(
           
           layoutContext.measureOutput,
-          width
+          width,
+          height
         );
         if (isRowUndefined) {
           node.layout.dimensions[DIMENSION_WIDTH] = measureDim.width +
@@ -286,7 +303,7 @@ private static void layoutNodeImpl(
         }
         if (isColumnUndefined) {
           node.layout.dimensions[DIMENSION_HEIGHT] = measureDim.height +
-            ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])));
+            paddingAndBorderAxisColumn;
         }
       }
       if (childCount == 0) {
@@ -303,8 +320,8 @@ private static void layoutNodeImpl(
     float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])));
     float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])));
   
-    boolean isMainDimDefined = !Float.isNaN(node.layout.dimensions[dim[mainAxis]]);
-    boolean isCrossDimDefined = !Float.isNaN(node.layout.dimensions[dim[crossAxis]]);
+    boolean isMainDimDefined = (!Float.isNaN(node.layout.dimensions[dim[mainAxis]]) && node.layout.dimensions[dim[mainAxis]] >= 0.0);
+    boolean isCrossDimDefined = (!Float.isNaN(node.layout.dimensions[dim[crossAxis]]) && node.layout.dimensions[dim[crossAxis]] >= 0.0);
     boolean isMainRowDirection = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE);
   
     int i;
@@ -366,7 +383,8 @@ private static void layoutNodeImpl(
       float mainDim = leadingPaddingAndBorderMain;
       float crossDim = 0;
   
-      float maxWidth;
+      float maxWidth = CSSConstants.UNDEFINED;
+      float maxHeight = CSSConstants.UNDEFINED;
       for (i = startLine; i < childCount; ++i) {
         child = node.getChildAt(i);
         child.lineIndex = linesCount;
@@ -403,7 +421,7 @@ private static void layoutNodeImpl(
           // left and right or top and bottom).
           for (ii = 0; ii < 2; ii++) {
             axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
-            if (!Float.isNaN(node.layout.dimensions[dim[axis]]) &&
+            if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) &&
                 !(!Float.isNaN(child.style.dimensions[dim[axis]]) && child.style.dimensions[dim[axis]] >= 0.0) &&
                 !Float.isNaN(child.style.position[leading[axis]]) &&
                 !Float.isNaN(child.style.position[trailing[axis]])) {
@@ -447,8 +465,10 @@ private static void layoutNodeImpl(
   
         } else {
           maxWidth = CSSConstants.UNDEFINED;
+          maxHeight = CSSConstants.UNDEFINED;
+  
           if (!isMainRowDirection) {
-            if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
+            if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
               maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
                 paddingAndBorderAxisResolvedRow;
             } else {
@@ -456,11 +476,20 @@ private static void layoutNodeImpl(
                 (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
                 paddingAndBorderAxisResolvedRow;
             }
+          } else {
+            if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+              maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
+                  paddingAndBorderAxisColumn;
+            } else {
+              maxHeight = parentMaxHeight -
+                (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) -
+                paddingAndBorderAxisColumn;
+            }
           }
   
           // This is the main recursive call. We layout non flexible children.
           if (alreadyComputedNextLayout == 0) {
-            layoutNode(layoutContext, child, maxWidth, direction);
+            layoutNode(layoutContext, child, maxWidth, maxHeight, direction);
           }
   
           // Absolute positioned elements do not take part of the layout, so we
@@ -499,7 +528,7 @@ private static void layoutNodeImpl(
         if (isSimpleStackCross &&
             (child.style.positionType != CSSPositionType.RELATIVE ||
                 (alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) ||
-                Float.isNaN(child.layout.dimensions[dim[crossAxis]]))) {
+                (alignItem == CSSAlign.STRETCH && !isCrossDimDefined))) {
           isSimpleStackCross = false;
           firstComplexCross = i;
         }
@@ -582,7 +611,7 @@ private static void layoutNodeImpl(
           );
   
           maxWidth = CSSConstants.UNDEFINED;
-          if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
+          if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) {
             maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] -
               paddingAndBorderAxisResolvedRow;
           } else if (!isMainRowDirection) {
@@ -590,9 +619,18 @@ private static void layoutNodeImpl(
               (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) -
               paddingAndBorderAxisResolvedRow;
           }
+          maxHeight = CSSConstants.UNDEFINED;
+          if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+            maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] -
+              paddingAndBorderAxisColumn;
+          } else if (isMainRowDirection) {
+            maxHeight = parentMaxHeight -
+              (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) -
+              paddingAndBorderAxisColumn;
+          }
   
           // And we recursively call the layout algorithm for this child
-          layoutNode(layoutContext, currentFlexChild, maxWidth, direction);
+          layoutNode(layoutContext, currentFlexChild, maxWidth, maxHeight, direction);
   
           child = currentFlexChild;
           currentFlexChild = currentFlexChild.nextFlexChild;
@@ -700,15 +738,31 @@ private static void layoutNodeImpl(
             CSSAlign alignItem = getAlignItem(node, child);
             /*eslint-enable */
             if (alignItem == CSSAlign.STRETCH) {
-              // You can only stretch if the dimension has not already been set
+              // You can only stretch if the dimension has not already been defined
               // previously.
-              if (Float.isNaN(child.layout.dimensions[dim[crossAxis]])) {
+              if (!(!Float.isNaN(child.style.dimensions[dim[crossAxis]]) && child.style.dimensions[dim[crossAxis]] >= 0.0)) {
+                float dimCrossAxis = child.layout.dimensions[dim[crossAxis]];
                 child.layout.dimensions[dim[crossAxis]] = Math.max(
                   boundAxis(child, crossAxis, containerCrossAxis -
                     paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))),
                   // You never want to go smaller than padding
                   ((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))
                 );
+  
+                // If the size has changed, and this child has children we need to re-layout this child
+                if (dimCrossAxis != child.layout.dimensions[dim[crossAxis]] && child.getChildCount() > 0) {
+                  // Reset child margins before re-layout as they are added back in layoutNode and would be doubled
+                  child.layout.position[leading[mainAxis]] -= child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) +
+                    getRelativePosition(child, mainAxis);
+                  child.layout.position[trailing[mainAxis]] -= child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) +
+                    getRelativePosition(child, mainAxis);
+                  child.layout.position[leading[crossAxis]] -= child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) +
+                    getRelativePosition(child, crossAxis);
+                  child.layout.position[trailing[crossAxis]] -= child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) +
+                    getRelativePosition(child, crossAxis);
+  
+                  layoutNode(layoutContext, child, maxWidth, maxHeight, direction);
+                }
               }
             } else if (alignItem != CSSAlign.FLEX_START) {
               // The remaining space between the parent dimensions+padding and child
@@ -786,7 +840,7 @@ private static void layoutNodeImpl(
           if (child.lineIndex != i) {
             break;
           }
-          if (!Float.isNaN(child.layout.dimensions[dim[crossAxis]])) {
+          if ((!Float.isNaN(child.layout.dimensions[dim[crossAxis]]) && child.layout.dimensions[dim[crossAxis]] >= 0.0)) {
             lineHeight = Math.max(
               lineHeight,
               child.layout.dimensions[dim[crossAxis]] + (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))
@@ -879,7 +933,7 @@ private static void layoutNodeImpl(
       for (ii = 0; ii < 2; ii++) {
         axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
   
-        if (!Float.isNaN(node.layout.dimensions[dim[axis]]) &&
+        if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) &&
             !(!Float.isNaN(currentAbsoluteChild.style.dimensions[dim[axis]]) && currentAbsoluteChild.style.dimensions[dim[axis]] >= 0.0) &&
             !Float.isNaN(currentAbsoluteChild.style.position[leading[axis]]) &&
             !Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]])) {
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README
index 4916c22183a1..0de9f29a65f6 100644
--- a/ReactAndroid/src/main/java/com/facebook/csslayout/README
+++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README
@@ -1,7 +1,7 @@
 The source of truth for css-layout is: https://github.com/facebook/css-layout
 
 The code here should be kept in sync with GitHub.
-HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/d3b702e1ad0925f8683ce3039be8e493abbf179b
+HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/3f0f4be2e868a9af4f3ed1e019f71efbc814e8a2
 
 There is generated code in:
  - README (this file)
diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook
index b7b2a9e90972..fc9720464910 100644
--- a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook
+++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook
@@ -1,7 +1,7 @@
 The source of truth for css-layout is: https://github.com/facebook/css-layout
 
 The code here should be kept in sync with GitHub.
-HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/d3b702e1ad0925f8683ce3039be8e493abbf179b
+HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/3f0f4be2e868a9af4f3ed1e019f71efbc814e8a2
 
 There is generated code in:
  - README.facebook (this file)
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
index 3006da53a95c..a0c845dd6287 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/CppException.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Facebook, Inc.
  * All rights reserved.
  *
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java b/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
index 18f754bf4749..13090a18ce30 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/CppSystemErrorException.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Facebook, Inc.
  * All rights reserved.
  *
diff --git a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
index fa6e971f6d41..45e9bfe0cee0 100644
--- a/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
+++ b/ReactAndroid/src/main/java/com/facebook/jni/UnknownCppException.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Facebook, Inc.
  * All rights reserved.
  *
diff --git a/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK b/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK
index 911bbd556733..8bd1a243fa96 100644
--- a/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/quicklog/BUCK
@@ -1,8 +1,10 @@
+include_defs('//ReactAndroid/DEFS')
+
 android_library(
   name = 'quicklog',
   srcs = glob(['*.java']),
   exported_deps = [
-    '//java/com/facebook/quicklog/identifiers:identifiers',
+    react_native_dep('java/com/facebook/quicklog/identifiers:identifiers'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK
index 8102c187c31b..0cc6b1e0d734 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK
@@ -4,7 +4,6 @@ android_library(
   name = 'react',
   srcs = glob(['*.java']),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/devsupport:devsupport'),
@@ -13,6 +12,8 @@ android_library(
     react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
     react_native_target('java/com/facebook/react/modules/toast:toast'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_dep('java/com/facebook/systrace:systrace'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
     react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
index a7bc29aa63a1..d7b34e270ace 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
@@ -13,13 +13,13 @@
 import java.util.Arrays;
 import java.util.List;
 
-import com.facebook.react.bridge.BridgeProfiling;
 import com.facebook.react.bridge.JavaScriptModule;
 import com.facebook.react.bridge.NativeModule;
 import com.facebook.react.bridge.ReactApplicationContext;
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
 import com.facebook.react.modules.core.DeviceEventManagerModule;
 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.RCTNativeAppEventEmitter;
 import com.facebook.react.modules.core.Timing;
@@ -27,11 +27,12 @@
 import com.facebook.react.modules.debug.SourceCodeModule;
 import com.facebook.react.modules.systeminfo.AndroidInfoModule;
 import com.facebook.react.uimanager.AppRegistry;
-import com.facebook.react.uimanager.ReactNative;
+import com.facebook.react.uimanager.UIImplementationProvider;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ViewManager;
 import com.facebook.react.uimanager.debug.DebugComponentOwnershipModule;
 import com.facebook.react.uimanager.events.RCTEventEmitter;
+import com.facebook.systrace.Systrace;
 
 /**
  * Package defining core framework modules (e.g. UIManager). It should be used for modules that
@@ -42,17 +43,35 @@
 
   private final ReactInstanceManager mReactInstanceManager;
   private final DefaultHardwareBackBtnHandler mHardwareBackBtnHandler;
+  private final UIImplementationProvider mUIImplementationProvider;
 
   CoreModulesPackage(
       ReactInstanceManager reactInstanceManager,
-      DefaultHardwareBackBtnHandler hardwareBackBtnHandler) {
+      DefaultHardwareBackBtnHandler hardwareBackBtnHandler,
+      UIImplementationProvider uiImplementationProvider) {
     mReactInstanceManager = reactInstanceManager;
     mHardwareBackBtnHandler = hardwareBackBtnHandler;
+    mUIImplementationProvider = uiImplementationProvider;
   }
 
   @Override
   public List<NativeModule> createNativeModules(
       ReactApplicationContext catalystApplicationContext) {
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createUIManagerModule");
+    UIManagerModule uiManagerModule;
+    try {
+      List<ViewManager> viewManagersList = mReactInstanceManager.createAllViewManagers(
+          catalystApplicationContext);
+      uiManagerModule = new UIManagerModule(
+          catalystApplicationContext,
+          viewManagersList,
+          mUIImplementationProvider.createUIImplementation(
+              catalystApplicationContext,
+              viewManagersList));
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+
     return Arrays.<NativeModule>asList(
         new AnimationsDebugModule(
             catalystApplicationContext,
@@ -64,9 +83,7 @@ public List<NativeModule> createNativeModules(
         new SourceCodeModule(
             mReactInstanceManager.getSourceUrl(),
             mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
-        new UIManagerModule(
-            catalystApplicationContext,
-            mReactInstanceManager.createAllViewManagers(catalystApplicationContext)),
+        uiManagerModule,
         new DebugComponentOwnershipModule(catalystApplicationContext));
   }
 
@@ -78,8 +95,8 @@ public List<Class<? extends JavaScriptModule>> createJSModules() {
         RCTEventEmitter.class,
         RCTNativeAppEventEmitter.class,
         AppRegistry.class,
-        BridgeProfiling.class,
-        ReactNative.class,
+        com.facebook.react.bridge.Systrace.class,
+        HMRClient.class,
         DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java b/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java
new file mode 100644
index 000000000000..959a6f8d36c9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/JSCConfig.java
@@ -0,0 +1,12 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react;
+
+import com.facebook.react.bridge.WritableNativeMap;
+
+/**
+ * Interface for the configuration object that is passed to JSC.
+ */
+public interface JSCConfig {
+  public WritableNativeMap getConfigMap();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java b/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
index f8598e908845..068bd063fb0c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/LifecycleState.java
@@ -21,7 +21,7 @@
  * RESUMED
  */
 public enum LifecycleState {
-
+  BEFORE_CREATE,
   BEFORE_RESUME,
   RESUMED,
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
new file mode 100644
index 000000000000..e9fd7574895f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
@@ -0,0 +1,110 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import android.annotation.TargetApi;
+import android.app.Application;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+
+import com.facebook.react.bridge.MemoryPressure;
+import com.facebook.react.bridge.MemoryPressureListener;
+
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+
+/**
+ * Translates and routes memory pressure events to the current catalyst instance.
+ */
+public class MemoryPressureRouter {
+  // Trigger this by sending an intent to your activity with adb shell:
+  // am broadcast -a com.facebook.catalyst.ACTION_TRIM_MEMORY_MODERATE
+  private static final String ACTION_TRIM_MEMORY_MODERATE =
+    "com.facebook.rnfeed.ACTION_TRIM_MEMORY_MODERATE";
+  private static final String ACTION_TRIM_MEMORY_CRITICAL =
+    "com.facebook.rnfeed.ACTION_TRIM_MEMORY_CRITICAL";
+
+  private final Set<MemoryPressureListener> mListeners =
+    Collections.synchronizedSet(new LinkedHashSet<MemoryPressureListener>());
+  private final ComponentCallbacks2 mCallbacks = new ComponentCallbacks2() {
+    @Override
+    public void onTrimMemory(int level) {
+      trimMemory(level);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+    }
+
+    @Override
+    public void onLowMemory() {
+    }
+  };
+
+  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+  public static boolean handleDebugIntent(Application application, String action) {
+    switch (action) {
+      case ACTION_TRIM_MEMORY_MODERATE:
+        simulateTrimMemory(application, TRIM_MEMORY_MODERATE);
+        break;
+      case ACTION_TRIM_MEMORY_CRITICAL:
+        simulateTrimMemory(application, TRIM_MEMORY_COMPLETE);
+      default:
+        return false;
+    }
+
+    return true;
+  }
+
+  MemoryPressureRouter(Context context) {
+    context.getApplicationContext().registerComponentCallbacks(mCallbacks);
+  }
+
+  /**
+   * Add a listener to be notified of memory pressure events.
+   */
+  public void addMemoryPressureListener(MemoryPressureListener listener) {
+    mListeners.add(listener);
+  }
+
+  /**
+   * Remove a listener previously added with {@link #addMemoryPressureListener}.
+   */
+  public void removeMemoryPressureListener(MemoryPressureListener listener) {
+    mListeners.remove(listener);
+  }
+
+  public void destroy(Context context) {
+    context.getApplicationContext().unregisterComponentCallbacks(mCallbacks);
+  }
+
+  private void trimMemory(int level) {
+    if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
+      dispatchMemoryPressure(MemoryPressure.CRITICAL);
+    } else if (level >= TRIM_MEMORY_BACKGROUND || level == TRIM_MEMORY_RUNNING_CRITICAL) {
+      dispatchMemoryPressure(MemoryPressure.MODERATE);
+    }
+  }
+
+  private void dispatchMemoryPressure(MemoryPressure level) {
+    // copy listeners array to avoid ConcurrentModificationException if any of the listeners remove
+    // themselves in handleMemoryPressure()
+    MemoryPressureListener[] listeners =
+      mListeners.toArray(new MemoryPressureListener[mListeners.size()]);
+    for (MemoryPressureListener listener : listeners) {
+      listener.handleMemoryPressure(level);
+    }
+  }
+
+  private static void simulateTrimMemory(Application application, int level) {
+    application.onTrimMemory(level);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
new file mode 100644
index 000000000000..d708e7dc2e6b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@@ -0,0 +1,226 @@
+package com.facebook.react;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.view.KeyEvent;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Base Activity for React Native applications.
+ */
+public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
+
+  private static final String REDBOX_PERMISSION_MESSAGE =
+      "Overlay permissions needs to be granted in order for react native apps to run in dev mode";
+
+  private @Nullable ReactInstanceManager mReactInstanceManager;
+  private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
+  private boolean mDoRefresh = false;
+
+  /**
+   * Returns the name of the bundle in assets. If this is null, and no file path is specified for
+   * the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
+   * always try to load the JS bundle from the packager server.
+   * e.g. "index.android.bundle"
+   */
+  protected @Nullable String getBundleAssetName() {
+    return "index.android.bundle";
+  };
+
+  /**
+   * Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
+   * from a custom path. By default it is loaded from Android assets, from a path specified
+   * by {@link getBundleAssetName}.
+   * e.g. "file://sdcard/myapp_cache/index.android.bundle"
+   */
+  protected @Nullable String getJSBundleFile() {
+    return null;
+  }
+
+  /**
+   * Returns the name of the main module. Determines the URL used to fetch the JS bundle
+   * from the packager server. It is only used when dev support is enabled.
+   * This is the first file to be executed once the {@link ReactInstanceManager} is created.
+   * e.g. "index.android"
+   */
+  protected String getJSMainModuleName() {
+    return "index.android";
+  }
+
+  /**
+   * Returns the launchOptions which will be passed to the {@link ReactInstanceManager}
+   * when the application is started. By default, this will return null and an empty
+   * object will be passed to your top level component as its initial props.
+   * If your React Native application requires props set outside of JS, override
+   * this method to return the Android.os.Bundle of your desired initial props.
+   */
+  protected @Nullable Bundle getLaunchOptions() {
+    return null;
+  }
+
+  /**
+   * Returns the name of the main component registered from JavaScript.
+   * This is used to schedule rendering of the component.
+   * e.g. "MoviesApp"
+   */
+  protected abstract String getMainComponentName();
+
+  /**
+   * Returns whether dev mode should be enabled. This enables e.g. the dev menu.
+   */
+  protected abstract boolean getUseDeveloperSupport();
+
+  /**
+   * Returns a list of {@link ReactPackage} used by the app.
+   * You'll most likely want to return at least the {@code MainReactPackage}.
+   * If your app uses additional views or modules besides the default ones,
+   * you'll want to include more packages here.
+   */
+  protected abstract List<ReactPackage> getPackages();
+
+  /**
+   * A subclass may override this method if it needs to use a custom instance.
+   */
+  protected ReactInstanceManager createReactInstanceManager() {
+    ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
+        .setApplication(getApplication())
+        .setJSMainModuleName(getJSMainModuleName())
+        .setUseDeveloperSupport(getUseDeveloperSupport())
+        .setInitialLifecycleState(mLifecycleState);
+
+    for (ReactPackage reactPackage : getPackages()) {
+      builder.addPackage(reactPackage);
+    }
+
+    String jsBundleFile = getJSBundleFile();
+
+    if (jsBundleFile != null) {
+      builder.setJSBundleFile(jsBundleFile);
+    } else {
+      builder.setBundleAssetName(getBundleAssetName());
+    }
+
+    return builder.build();
+  }
+
+  /**
+   * A subclass may override this method if it needs to use a custom {@link ReactRootView}.
+   */
+  protected ReactRootView createRootView() {
+    return new ReactRootView(this);
+  }
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) {
+      // Get permission to show redbox in dev builds.
+      if (!Settings.canDrawOverlays(this)) {
+        Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+        startActivity(serviceIntent);
+        FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
+        Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
+      }
+    }
+
+    mReactInstanceManager = createReactInstanceManager();
+    ReactRootView mReactRootView = createRootView();
+    mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions());
+    setContentView(mReactRootView);
+  }
+
+  @Override
+  protected void onPause() {
+    super.onPause();
+
+    mLifecycleState = LifecycleState.BEFORE_RESUME;
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onHostPause();
+    }
+  }
+
+  @Override
+  protected void onResume() {
+    super.onResume();
+
+    mLifecycleState = LifecycleState.RESUMED;
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onHostResume(this, this);
+    }
+  }
+
+  @Override
+  protected void onDestroy() {
+    super.onDestroy();
+
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.destroy();
+    }
+  }
+
+  @Override
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onActivityResult(requestCode, resultCode, data);
+    }
+  }
+
+  @Override
+  public boolean onKeyUp(int keyCode, KeyEvent event) {
+    if (mReactInstanceManager != null &&
+        mReactInstanceManager.getDevSupportManager().getDevSupportEnabled()) {
+      if (keyCode == KeyEvent.KEYCODE_MENU) {
+        mReactInstanceManager.showDevOptionsDialog();
+        return true;
+      }
+      if (keyCode == KeyEvent.KEYCODE_R && !(getCurrentFocus() instanceof EditText)) {
+        // Enable double-tap-R-to-reload
+        if (mDoRefresh) {
+          mReactInstanceManager.getDevSupportManager().handleReloadJS();
+          mDoRefresh = false;
+        } else {
+          mDoRefresh = true;
+          new Handler().postDelayed(
+              new Runnable() {
+                @Override
+                public void run() {
+                  mDoRefresh = false;
+                }
+              },
+              200);
+        }
+      }
+    }
+    return super.onKeyUp(keyCode, event);
+  }
+
+  @Override
+  public void onBackPressed() {
+    if (mReactInstanceManager != null) {
+      mReactInstanceManager.onBackPressed();
+    } else {
+      super.onBackPressed();
+    }
+  }
+
+  @Override
+  public void invokeDefaultOnBackPressed() {
+    super.onBackPressed();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
index 4678fd9f4944..2e2ca160301f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
@@ -16,43 +16,18 @@
 
 import android.app.Activity;
 import android.app.Application;
-import android.content.Context;
 import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.view.View;
 
-import com.facebook.common.logging.FLog;
 import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.bridge.Arguments;
-import com.facebook.react.bridge.CatalystInstance;
-import com.facebook.react.bridge.JSBundleLoader;
-import com.facebook.react.bridge.JSCJavaScriptExecutor;
-import com.facebook.react.bridge.JavaScriptExecutor;
-import com.facebook.react.bridge.JavaScriptModule;
-import com.facebook.react.bridge.JavaScriptModulesConfig;
-import com.facebook.react.bridge.NativeModule;
-import com.facebook.react.bridge.NativeModuleRegistry;
+import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
 import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
-import com.facebook.react.bridge.ProxyJavaScriptExecutor;
 import com.facebook.react.bridge.ReactApplicationContext;
 import com.facebook.react.bridge.ReactContext;
-import com.facebook.react.bridge.UiThreadUtil;
-import com.facebook.react.bridge.WritableMap;
-import com.facebook.react.bridge.WritableNativeMap;
-import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
-import com.facebook.react.common.ReactConstants;
 import com.facebook.react.common.annotations.VisibleForTesting;
-import com.facebook.react.devsupport.DevServerHelper;
 import com.facebook.react.devsupport.DevSupportManager;
-import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
 import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
-import com.facebook.react.modules.core.DeviceEventManagerModule;
-import com.facebook.react.uimanager.AppRegistry;
-import com.facebook.react.uimanager.ReactNative;
-import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.UIImplementationProvider;
 import com.facebook.react.uimanager.ViewManager;
-import com.facebook.soloader.SoLoader;
 
 /**
  * This class is managing instances of {@link CatalystInstance}. It expose a way to configure
@@ -66,172 +41,28 @@
  * The lifecycle of the instance of {@link ReactInstanceManager} should be bound to the activity
  * that owns the {@link ReactRootView} that is used to render react application using this
  * instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass
- * owning activity's lifecycle events to the instance manager (see {@link #onPause},
- * {@link #onDestroy} and {@link #onResume}).
+ * owning activity's lifecycle events to the instance manager (see {@link #onHostPause},
+ * {@link #onHostDestroy} and {@link #onHostResume}).
  *
- * To instantiate an instance of this class use {@link #builder}.
+ * Ideally, this would be an interface, but because of the API used by earlier versions, it has to
+ * have a static method, and so cannot (in Java < 8), be one.
  */
-public class ReactInstanceManager {
-
-  /* should only be accessed from main thread (UI thread) */
-  private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
-  private LifecycleState mLifecycleState;
-  private boolean mIsContextInitAsyncTaskRunning;
-  private @Nullable ReactContextInitParams mPendingReactContextInitParams;
-
-  /* accessed from any thread */
-  private @Nullable String mJSBundleFile; /* path to JS bundle on file system */
-  private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
-  private final List<ReactPackage> mPackages;
-  private final DevSupportManager mDevSupportManager;
-  private final boolean mUseDeveloperSupport;
-  private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
-  private @Nullable volatile ReactContext mCurrentReactContext;
-  private final Context mApplicationContext;
-  private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
-  private String mSourceUrl;
-  private @Nullable Activity mCurrentActivity;
-  private volatile boolean mHasStartedCreatingInitialContext = false;
-
-  private final ReactInstanceDevCommandsHandler mDevInterface =
-      new ReactInstanceDevCommandsHandler() {
-
-        @Override
-        public void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
-          ReactInstanceManager.this.onReloadWithJSDebugger(proxyExecutor);
-        }
-
-        @Override
-        public void onJSBundleLoadedFromServer() {
-          ReactInstanceManager.this.onJSBundleLoadedFromServer();
-        }
-
-        @Override
-        public void toggleElementInspector() {
-          ReactInstanceManager.this.toggleElementInspector();
-        }
-      };
-
-  private final DefaultHardwareBackBtnHandler mBackBtnHandler =
-      new DefaultHardwareBackBtnHandler() {
-        @Override
-        public void invokeDefaultOnBackPressed() {
-          ReactInstanceManager.this.invokeDefaultOnBackPressed();
-        }
-      };
-
-  private class ReactContextInitParams {
-    private final JavaScriptExecutor mJsExecutor;
-    private final JSBundleLoader mJsBundleLoader;
-
-    public ReactContextInitParams(
-        JavaScriptExecutor jsExecutor,
-        JSBundleLoader jsBundleLoader) {
-      mJsExecutor = Assertions.assertNotNull(jsExecutor);
-      mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);
-    }
-
-    public JavaScriptExecutor getJsExecutor() {
-      return mJsExecutor;
-    }
-
-    public JSBundleLoader getJsBundleLoader() {
-      return mJsBundleLoader;
-    }
-  }
-
-  /*
-   * Task class responsible for (re)creating react context in the background. These tasks can only
-   * be executing one at time, see {@link #recreateReactContextInBackground()}.
-   */
-  private final class ReactContextInitAsyncTask extends
-      AsyncTask<ReactContextInitParams, Void, ReactApplicationContext> {
-
-    @Override
-    protected void onPreExecute() {
-      if (mCurrentReactContext != null) {
-        tearDownReactContext(mCurrentReactContext);
-        mCurrentReactContext = null;
-      }
-    }
-
-    @Override
-    protected ReactApplicationContext doInBackground(ReactContextInitParams... params) {
-      Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
-      return createReactContext(params[0].getJsExecutor(), params[0].getJsBundleLoader());
-    }
-
-    @Override
-    protected void onPostExecute(ReactApplicationContext reactContext) {
-      try {
-        setupReactContext(reactContext);
-      } finally {
-        mIsContextInitAsyncTaskRunning = false;
-      }
-
-      // Handle enqueued request to re-initialize react context.
-      if (mPendingReactContextInitParams != null) {
-        recreateReactContextInBackground(
-            mPendingReactContextInitParams.getJsExecutor(),
-            mPendingReactContextInitParams.getJsBundleLoader());
-        mPendingReactContextInitParams = null;
-      }
-    }
-  }
-
-  private ReactInstanceManager(
-      Context applicationContext,
-      @Nullable String jsBundleFile,
-      @Nullable String jsMainModuleName,
-      List<ReactPackage> packages,
-      boolean useDeveloperSupport,
-      @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
-      LifecycleState initialLifecycleState) {
-    initializeSoLoaderIfNecessary(applicationContext);
-
-    mApplicationContext = applicationContext;
-    mJSBundleFile = jsBundleFile;
-    mJSMainModuleName = jsMainModuleName;
-    mPackages = packages;
-    mUseDeveloperSupport = useDeveloperSupport;
-    // We need to instantiate DevSupportManager regardless to the useDeveloperSupport option,
-    // although will prevent dev support manager from displaying any options or dialogs by
-    // checking useDeveloperSupport option before calling setDevSupportEnabled on this manager
-    // TODO(6803830): Don't instantiate devsupport manager when useDeveloperSupport is false
-    mDevSupportManager = new DevSupportManager(
-        applicationContext,
-        mDevInterface,
-        mJSMainModuleName,
-        useDeveloperSupport);
-    mBridgeIdleDebugListener = bridgeIdleDebugListener;
-    mLifecycleState = initialLifecycleState;
-  }
-
-  public DevSupportManager getDevSupportManager() {
-    return mDevSupportManager;
-  }
+public abstract class ReactInstanceManager {
 
   /**
-   * Creates a builder that is capable of creating an instance of {@link ReactInstanceManager}.
+   * Listener interface for react instance events.
    */
-  public static Builder builder() {
-    return new Builder();
+  public interface ReactInstanceEventListener {
+    /**
+     * Called when the react context is initialized (all modules registered). Always called on the
+     * UI thread.
+     */
+    void onReactContextInitialized(ReactContext context);
   }
 
-  private static void initializeSoLoaderIfNecessary(Context applicationContext) {
-    // Call SoLoader.initialize here, this is required for apps that does not use exopackage and
-    // does not use SoLoader for loading other native code except from the one used by React Native
-    // This way we don't need to require others to have additional initialization code and to
-    // subclass android.app.Application.
-
-    // Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call
-    // SoLoader.init with appropriate args before initializing ReactInstanceManager
-    SoLoader.init(applicationContext, /* native exopackage */ false);
-  }
+  public abstract DevSupportManager getDevSupportManager();
 
-  public void setJSBundleFile(String jsBundleFile) {
-    mJSBundleFile = jsBundleFile;
-  }
+  public abstract MemoryPressureRouter getMemoryPressureRouter();
 
   /**
    * Trigger react context initialization asynchronously in a background async task. This enables
@@ -242,187 +73,52 @@ public void setJSBundleFile(String jsBundleFile) {
    *
    * Called from UI thread.
    */
-  public void createReactContextInBackground() {
-    Assertions.assertCondition(
-        !mHasStartedCreatingInitialContext,
-        "createReactContextInBackground should only be called when creating the react " +
-            "application for the first time. When reloading JS, e.g. from a new file, explicitly" +
-            "use recreateReactContextInBackground");
-
-    mHasStartedCreatingInitialContext = true;
-    recreateReactContextInBackgroundInner();
-  }
-
-  /**
-   * Recreate the react application and context. This should be called if configuration has
-   * changed or the developer has requested the app to be reloaded. It should only be called after
-   * an initial call to createReactContextInBackground.
-   *
-   * Called from UI thread.
-   */
-  public void recreateReactContextInBackground() {
-    Assertions.assertCondition(
-        mHasStartedCreatingInitialContext,
-        "recreateReactContextInBackground should only be called after the initial " +
-            "createReactContextInBackground call.");
-    recreateReactContextInBackgroundInner();
-  }
-
-  private void recreateReactContextInBackgroundInner() {
-    UiThreadUtil.assertOnUiThread();
-
-    if (mUseDeveloperSupport && mJSMainModuleName != null) {
-      if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
-        // If there is a up-to-date bundle downloaded from server, always use that
-        onJSBundleLoadedFromServer();
-      } else if (mJSBundleFile == null) {
-        mDevSupportManager.handleReloadJS();
-      } else {
-        mDevSupportManager.isPackagerRunning(
-            new DevServerHelper.PackagerStatusCallback() {
-              @Override
-              public void onPackagerStatusFetched(final boolean packagerIsRunning) {
-                UiThreadUtil.runOnUiThread(
-                    new Runnable() {
-                      @Override
-                      public void run() {
-                        if (packagerIsRunning) {
-                          mDevSupportManager.handleReloadJS();
-                        } else {
-                          recreateReactContextInBackgroundFromBundleFile();
-                        }
-                      }
-                    });
-              }
-            });
-      }
-      return;
-    }
-
-    recreateReactContextInBackgroundFromBundleFile();
-  }
-
-  private void recreateReactContextInBackgroundFromBundleFile() {
-    recreateReactContextInBackground(
-        new JSCJavaScriptExecutor(),
-        JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
-  }
+  public abstract void createReactContextInBackground();
 
   /**
    * @return whether createReactContextInBackground has been called. Will return false after
    * onDestroy until a new initial context has been created.
    */
-  public boolean hasStartedCreatingInitialContext() {
-    return mHasStartedCreatingInitialContext;
-  }
+  public abstract boolean hasStartedCreatingInitialContext();
 
   /**
    * This method will give JS the opportunity to consume the back button event. If JS does not
    * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
    */
-  public void onBackPressed() {
-    UiThreadUtil.assertOnUiThread();
-    ReactContext reactContext = mCurrentReactContext;
-    if (mCurrentReactContext == null) {
-      // Invoke without round trip to JS.
-      FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
-      invokeDefaultOnBackPressed();
-    } else {
-      DeviceEventManagerModule deviceEventManagerModule =
-          Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class);
-      deviceEventManagerModule.emitHardwareBackPressed();
-    }
-  }
-
-  private void invokeDefaultOnBackPressed() {
-    UiThreadUtil.assertOnUiThread();
-    if (mDefaultBackButtonImpl != null) {
-      mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
-    }
-  }
-
-  private void toggleElementInspector() {
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext
-          .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
-          .emit("toggleElementInspector", null);
-    }
-  }
-
-  public void onPause() {
-    UiThreadUtil.assertOnUiThread();
-
-    mLifecycleState = LifecycleState.BEFORE_RESUME;
-
-    mDefaultBackButtonImpl = null;
-    if (mUseDeveloperSupport) {
-      mDevSupportManager.setDevSupportEnabled(false);
-    }
-
-    mCurrentActivity = null;
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onPause();
-    }
-  }
+  public abstract void onBackPressed();
 
+  /**
+   * Call this from {@link Activity#onPause()}. This notifies any listening modules so they can do
+   * any necessary cleanup.
+   */
+  public abstract void onHostPause();
   /**
    * Use this method when the activity resumes to enable invoking the back button directly from JS.
    *
    * This method retains an instance to provided mDefaultBackButtonImpl. Thus it's
    * important to pass from the activity instance that owns this particular instance of {@link
-   * ReactInstanceManager}, so that once this instance receive {@link #onDestroy} event it will
+   * ReactInstanceManager}, so that once this instance receive {@link #onHostDestroy} event it will
    * clear the reference to that defaultBackButtonImpl.
    *
    * @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
    * this instance of {@link ReactInstanceManager}.
    */
-  public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
-    UiThreadUtil.assertOnUiThread();
-
-    mLifecycleState = LifecycleState.RESUMED;
-
-    mDefaultBackButtonImpl = defaultBackButtonImpl;
-    if (mUseDeveloperSupport) {
-      mDevSupportManager.setDevSupportEnabled(true);
-    }
-
-    mCurrentActivity = activity;
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onResume(activity);
-    }
-  }
-
-  public void onDestroy() {
-    UiThreadUtil.assertOnUiThread();
-
-    if (mUseDeveloperSupport) {
-      mDevSupportManager.setDevSupportEnabled(false);
-    }
-
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onDestroy();
-      mCurrentReactContext = null;
-      mHasStartedCreatingInitialContext = false;
-    }
-  }
-
-  public void onActivityResult(int requestCode, int resultCode, Intent data) {
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onActivityResult(requestCode, resultCode, data);
-    }
-  }
+  public abstract void onHostResume(
+    Activity activity,
+    DefaultHardwareBackBtnHandler defaultBackButtonImpl);
 
-  public void showDevOptionsDialog() {
-    UiThreadUtil.assertOnUiThread();
-    mDevSupportManager.showDevOptionsDialog();
-  }
+  /**
+   * Call this from {@link Activity#onDestroy()}. This notifies any listening modules so they can do
+   * any necessary cleanup.
+   */
+  public abstract void onHostDestroy();
+  public abstract void onActivityResult(int requestCode, int resultCode, Intent data);
+  public abstract void showDevOptionsDialog();
 
   /**
    * Get the URL where the last bundle was loaded from.
    */
-  public String getSourceUrl() {
-    return Assertions.assertNotNull(mSourceUrl);
-  }
+  public abstract String getSourceUrl();
 
   /**
    * Attach given {@param rootView} to a catalyst instance manager and start JS application using
@@ -432,227 +128,80 @@ public String getSourceUrl() {
    * This view will then be tracked by this manager and in case of catalyst instance restart it will
    * be re-attached.
    */
-  /* package */ void attachMeasuredRootView(ReactRootView rootView) {
-    UiThreadUtil.assertOnUiThread();
-    mAttachedRootViews.add(rootView);
-
-    // If react context is being created in the background, JS application will be started
-    // automatically when creation completes, as root view is part of the attached root view list.
-    if (!mIsContextInitAsyncTaskRunning) {
-      if (mCurrentReactContext == null) {
-        createReactContextInBackground();
-      } else {
-        attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
-      }
-    }
-  }
+  public abstract void attachMeasuredRootView(ReactRootView rootView);
 
   /**
    * Detach given {@param rootView} from current catalyst instance. It's safe to call this method
    * multiple times on the same {@param rootView} - in that case view will be detached with the
    * first call.
    */
-  /* package */ void detachRootView(ReactRootView rootView) {
-    UiThreadUtil.assertOnUiThread();
-    if (mAttachedRootViews.remove(rootView)) {
-      if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) {
-        detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance());
-      }
-    }
-  }
+  public abstract void detachRootView(ReactRootView rootView);
 
   /**
-   * Uses configured {@link ReactPackage} instances to create all view managers
+   * Destroy this React instance and the attached JS context.
    */
-  /* package */ List<ViewManager> createAllViewManagers(
-      ReactApplicationContext catalystApplicationContext) {
-    List<ViewManager> allViewManagers = new ArrayList<>();
-    for (ReactPackage reactPackage : mPackages) {
-      allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext));
-    }
-    return allViewManagers;
-  }
-
-  @VisibleForTesting
-  public @Nullable ReactContext getCurrentReactContext() {
-    return mCurrentReactContext;
-  }
-
-  private void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor) {
-    recreateReactContextInBackground(
-        proxyExecutor,
-        JSBundleLoader.createRemoteDebuggerBundleLoader(
-            mDevSupportManager.getJSBundleURLForRemoteDebugging()));
-  }
-
-  private void onJSBundleLoadedFromServer() {
-    recreateReactContextInBackground(
-        new JSCJavaScriptExecutor(),
-        JSBundleLoader.createCachedBundleFromNetworkLoader(
-            mDevSupportManager.getSourceUrl(),
-            mDevSupportManager.getDownloadedJSBundleFile()));
-  }
-
-  private void recreateReactContextInBackground(
-      JavaScriptExecutor jsExecutor,
-      JSBundleLoader jsBundleLoader) {
-    UiThreadUtil.assertOnUiThread();
-
-    ReactContextInitParams initParams = new ReactContextInitParams(jsExecutor, jsBundleLoader);
-    if (!mIsContextInitAsyncTaskRunning) {
-      // No background task to create react context is currently running, create and execute one.
-      ReactContextInitAsyncTask initTask = new ReactContextInitAsyncTask();
-      initTask.execute(initParams);
-      mIsContextInitAsyncTaskRunning = true;
-    } else {
-      // Background task is currently running, queue up most recent init params to recreate context
-      // once task completes.
-      mPendingReactContextInitParams = initParams;
-    }
-  }
-
-  private void setupReactContext(ReactApplicationContext reactContext) {
-    UiThreadUtil.assertOnUiThread();
-    Assertions.assertCondition(mCurrentReactContext == null);
-    mCurrentReactContext = Assertions.assertNotNull(reactContext);
-    CatalystInstance catalystInstance =
-        Assertions.assertNotNull(reactContext.getCatalystInstance());
-
-    catalystInstance.initialize();
-    mDevSupportManager.onNewReactContextCreated(reactContext);
-    moveReactContextToCurrentLifecycleState(reactContext);
-
-    for (ReactRootView rootView : mAttachedRootViews) {
-      attachMeasuredRootViewToInstance(rootView, catalystInstance);
-    }
-  }
-
-  private void attachMeasuredRootViewToInstance(
-      ReactRootView rootView,
-      CatalystInstance catalystInstance) {
-    UiThreadUtil.assertOnUiThread();
-
-    // Reset view content as it's going to be populated by the application content from JS
-    rootView.removeAllViews();
-    rootView.setId(View.NO_ID);
-
-    UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
-    int rootTag = uiManagerModule.addMeasuredRootView(rootView);
-    @Nullable Bundle launchOptions = rootView.getLaunchOptions();
-    WritableMap initialProps = launchOptions != null
-        ? Arguments.fromBundle(launchOptions)
-        : Arguments.createMap();
-    String jsAppModuleName = rootView.getJSModuleName();
-
-    WritableNativeMap appParams = new WritableNativeMap();
-    appParams.putDouble("rootTag", rootTag);
-    appParams.putMap("initialProps", initialProps);
-    catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
-  }
-
-  private void detachViewFromInstance(
-      ReactRootView rootView,
-      CatalystInstance catalystInstance) {
-    UiThreadUtil.assertOnUiThread();
-    catalystInstance.getJSModule(ReactNative.class)
-        .unmountComponentAtNodeAndRemoveContainer(rootView.getId());
-  }
-
-  private void tearDownReactContext(ReactContext reactContext) {
-    UiThreadUtil.assertOnUiThread();
-    if (mLifecycleState == LifecycleState.RESUMED) {
-      reactContext.onPause();
-    }
-    for (ReactRootView rootView : mAttachedRootViews) {
-      detachViewFromInstance(rootView, reactContext.getCatalystInstance());
-    }
-    reactContext.onDestroy();
-    mDevSupportManager.onReactInstanceDestroyed(reactContext);
-  }
+  public abstract void destroy();
 
   /**
-   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
+   * Uses configured {@link ReactPackage} instances to create all view managers
    */
-  private ReactApplicationContext createReactContext(
-      JavaScriptExecutor jsExecutor,
-      JSBundleLoader jsBundleLoader) {
-    FLog.i(ReactConstants.TAG, "Creating react context.");
-    mSourceUrl = jsBundleLoader.getSourceUrl();
-    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
-    JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
-
-    ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
-    if (mUseDeveloperSupport) {
-      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
-    }
-
-    CoreModulesPackage coreModulesPackage =
-        new CoreModulesPackage(this, mBackBtnHandler);
-    processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
-
-    // TODO(6818138): Solve use-case of native/js modules overriding
-    for (ReactPackage reactPackage : mPackages) {
-      processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
-    }
+  public abstract List<ViewManager> createAllViewManagers(
+    ReactApplicationContext catalystApplicationContext);
 
-    CatalystInstance.Builder catalystInstanceBuilder = new CatalystInstance.Builder()
-        .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault())
-        .setJSExecutor(jsExecutor)
-        .setRegistry(nativeRegistryBuilder.build())
-        .setJSModulesConfig(jsModulesBuilder.build())
-        .setJSBundleLoader(jsBundleLoader)
-        .setNativeModuleCallExceptionHandler(mDevSupportManager);
-
-    CatalystInstance catalystInstance = catalystInstanceBuilder.build();
-    if (mBridgeIdleDebugListener != null) {
-      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
-    }
+  /**
+   * Add a listener to be notified of react instance events.
+   */
+  public abstract void addReactInstanceEventListener(ReactInstanceEventListener listener);
 
-    reactContext.initializeWithInstance(catalystInstance);
-    catalystInstance.runJSBundle();
+  /**
+   * Remove a listener previously added with {@link #addReactInstanceEventListener}.
+   */
+  public abstract void removeReactInstanceEventListener(ReactInstanceEventListener listener);
 
-    return reactContext;
-  }
+  @VisibleForTesting
+  public abstract @Nullable ReactContext getCurrentReactContext();
 
-  private void processPackage(
-      ReactPackage reactPackage,
-      ReactApplicationContext reactContext,
-      NativeModuleRegistry.Builder nativeRegistryBuilder,
-      JavaScriptModulesConfig.Builder jsModulesBuilder) {
-    for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
-      nativeRegistryBuilder.add(nativeModule);
-    }
-    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
-      jsModulesBuilder.add(jsModuleClass);
-    }
-  }
+  public abstract LifecycleState getLifecycleState();
 
-  private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) {
-    if (mLifecycleState == LifecycleState.RESUMED) {
-      reactContext.onResume(mCurrentActivity);
-    }
+  /**
+   * Creates a builder that is capable of creating an instance of {@link ReactInstanceManagerImpl}.
+   */
+  public static Builder builder() {
+    return new Builder();
   }
 
   /**
-   * Builder class for {@link ReactInstanceManager}
+   * Builder class for {@link ReactInstanceManagerImpl}
    */
   public static class Builder {
 
-    private final List<ReactPackage> mPackages = new ArrayList<>();
+    protected final List<ReactPackage> mPackages = new ArrayList<>();
 
-    private @Nullable String mJSBundleFile;
-    private @Nullable String mJSMainModuleName;
-    private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
-    private @Nullable Application mApplication;
-    private boolean mUseDeveloperSupport;
-    private @Nullable LifecycleState mInitialLifecycleState;
+    protected @Nullable String mJSBundleFile;
+    protected @Nullable String mJSMainModuleName;
+    protected @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
+    protected @Nullable Application mApplication;
+    protected boolean mUseDeveloperSupport;
+    protected @Nullable LifecycleState mInitialLifecycleState;
+    protected @Nullable UIImplementationProvider mUIImplementationProvider;
+    protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+    protected @Nullable JSCConfig mJSCConfig;
 
-    private Builder() {
+    protected Builder() {
+    }
+
+    /**
+     * Sets a provider of {@link UIImplementation}.
+     * Uses default provider if null is passed.
+     */
+    public Builder setUIImplementationProvider(
+        @Nullable UIImplementationProvider uiImplementationProvider) {
+      mUIImplementationProvider = uiImplementationProvider;
+      return this;
     }
 
     /**
      * Name of the JS bundle file to be loaded from application's raw assets.
-     *
      * Example: {@code "index.android.js"}
      */
     public Builder setBundleAssetName(String bundleAssetName) {
@@ -662,7 +211,7 @@ public Builder setBundleAssetName(String bundleAssetName) {
     /**
      * Path to the JS bundle file to be loaded from the file system.
      *
-     * Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle}
+     * Example: {@code "assets://index.android.js" or "/sdcard/main.jsbundle"}
      */
     public Builder setJSBundleFile(String jsBundleFile) {
       mJSBundleFile = jsBundleFile;
@@ -721,7 +270,22 @@ public Builder setInitialLifecycleState(LifecycleState initialLifecycleState) {
     }
 
     /**
-     * Instantiates a new {@link ReactInstanceManager}.
+     * Set the exception handler for all native module calls. If not set, the default
+     * {@link DevSupportManager} will be used, which shows a redbox in dev mode and rethrows
+     * (crashes the app) in prod mode.
+     */
+    public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHandler handler) {
+      mNativeModuleCallExceptionHandler = handler;
+      return this;
+    }
+
+    public Builder setJSCConfig(JSCConfig jscConfig) {
+      mJSCConfig = jscConfig;
+      return this;
+    }
+
+    /**
+     * Instantiates a new {@link ReactInstanceManagerImpl}.
      * Before calling {@code build}, the following must be called:
      * <ul>
      * <li> {@link #setApplication}
@@ -737,7 +301,12 @@ public ReactInstanceManager build() {
           mJSMainModuleName != null || mJSBundleFile != null,
           "Either MainModuleName or JS Bundle File needs to be provided");
 
-      return new ReactInstanceManager(
+      if (mUIImplementationProvider == null) {
+        // create default UIImplementationProvider if the provided one is null.
+        mUIImplementationProvider = new UIImplementationProvider();
+      }
+
+      return new ReactInstanceManagerImpl(
           Assertions.assertNotNull(
               mApplication,
               "Application property has not been set with this builder"),
@@ -746,7 +315,10 @@ public ReactInstanceManager build() {
           mPackages,
           mUseDeveloperSupport,
           mBridgeIdleDebugListener,
-          Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"));
+          Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
+          mUIImplementationProvider,
+          mNativeModuleCallExceptionHandler,
+          mJSCConfig);
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
new file mode 100644
index 000000000000..0c480eb58680
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
@@ -0,0 +1,907 @@
+/**
+ * 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;
+
+import javax.annotation.Nullable;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.CatalystInstanceImpl;
+import com.facebook.react.bridge.JSBundleLoader;
+import com.facebook.react.bridge.JSCJavaScriptExecutor;
+import com.facebook.react.bridge.JavaJSExecutor;
+import com.facebook.react.bridge.JavaScriptExecutor;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.JavaScriptModulesConfig;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.NativeModuleRegistry;
+import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
+import com.facebook.react.bridge.ProxyJavaScriptExecutor;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactMarker;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+import com.facebook.react.common.ApplicationHolder;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.devsupport.DevServerHelper;
+import com.facebook.react.devsupport.DevSupportManager;
+import com.facebook.react.devsupport.DevSupportManagerFactory;
+import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
+import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.facebook.react.uimanager.AppRegistry;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.UIImplementationProvider;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.soloader.SoLoader;
+import com.facebook.systrace.Systrace;
+
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START;
+
+/**
+ * This class is managing instances of {@link CatalystInstance}. It expose a way to configure
+ * catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that
+ * instance. It also sets up connection between the instance and developers support functionality
+ * of the framework.
+ *
+ * An instance of this manager is required to start JS application in {@link ReactRootView} (see
+ * {@link ReactRootView#startReactApplication} for more info).
+ *
+ * The lifecycle of the instance of {@link ReactInstanceManagerImpl} should be bound to the activity
+ * that owns the {@link ReactRootView} that is used to render react application using this
+ * instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass
+ * owning activity's lifecycle events to the instance manager (see {@link #onHostPause},
+ * {@link #onHostDestroy} and {@link #onHostResume}).
+ *
+ * To instantiate an instance of this class use {@link #builder}.
+ */
+/* package */ class ReactInstanceManagerImpl extends ReactInstanceManager {
+
+  /* should only be accessed from main thread (UI thread) */
+  private final List<ReactRootView> mAttachedRootViews = new ArrayList<>();
+  private LifecycleState mLifecycleState;
+  private @Nullable ReactContextInitParams mPendingReactContextInitParams;
+  private @Nullable ReactContextInitAsyncTask mReactContextInitAsyncTask;
+
+  /* accessed from any thread */
+  private @Nullable String mJSBundleFile; /* path to JS bundle on file system */
+  private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
+  private final List<ReactPackage> mPackages;
+  private final DevSupportManager mDevSupportManager;
+  private final boolean mUseDeveloperSupport;
+  private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
+  private @Nullable volatile ReactContext mCurrentReactContext;
+  private final Context mApplicationContext;
+  private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
+  private String mSourceUrl;
+  private @Nullable Activity mCurrentActivity;
+  private final Collection<ReactInstanceEventListener> mReactInstanceEventListeners =
+      Collections.synchronizedSet(new HashSet<ReactInstanceEventListener>());
+  private volatile boolean mHasStartedCreatingInitialContext = false;
+  private final UIImplementationProvider mUIImplementationProvider;
+  private final MemoryPressureRouter mMemoryPressureRouter;
+  private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+  private final @Nullable JSCConfig mJSCConfig;
+
+  private final ReactInstanceDevCommandsHandler mDevInterface =
+      new ReactInstanceDevCommandsHandler() {
+
+        @Override
+        public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
+          ReactInstanceManagerImpl.this.onReloadWithJSDebugger(jsExecutorFactory);
+        }
+
+        @Override
+        public void onJSBundleLoadedFromServer() {
+          ReactInstanceManagerImpl.this.onJSBundleLoadedFromServer();
+        }
+
+        @Override
+        public void toggleElementInspector() {
+          ReactInstanceManagerImpl.this.toggleElementInspector();
+        }
+      };
+
+  private final DefaultHardwareBackBtnHandler mBackBtnHandler =
+      new DefaultHardwareBackBtnHandler() {
+        @Override
+        public void invokeDefaultOnBackPressed() {
+          ReactInstanceManagerImpl.this.invokeDefaultOnBackPressed();
+        }
+      };
+
+  private class ReactContextInitParams {
+    private final JavaScriptExecutor.Factory mJsExecutorFactory;
+    private final JSBundleLoader mJsBundleLoader;
+
+    public ReactContextInitParams(
+        JavaScriptExecutor.Factory jsExecutorFactory,
+        JSBundleLoader jsBundleLoader) {
+      mJsExecutorFactory = Assertions.assertNotNull(jsExecutorFactory);
+      mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);
+    }
+
+    public JavaScriptExecutor.Factory getJsExecutorFactory() {
+      return mJsExecutorFactory;
+    }
+
+    public JSBundleLoader getJsBundleLoader() {
+      return mJsBundleLoader;
+    }
+  }
+
+  /*
+   * Task class responsible for (re)creating react context in the background. These tasks can only
+   * be executing one at time, see {@link #recreateReactContextInBackground()}.
+   */
+  private final class ReactContextInitAsyncTask extends
+      AsyncTask<ReactContextInitParams, Void, Result<ReactApplicationContext>> {
+    @Override
+    protected void onPreExecute() {
+      if (mCurrentReactContext != null) {
+        tearDownReactContext(mCurrentReactContext);
+        mCurrentReactContext = null;
+      }
+    }
+
+    @Override
+    protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
+      Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
+      try {
+        JavaScriptExecutor jsExecutor =
+            params[0].getJsExecutorFactory().create(
+              mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap());
+        return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
+      } catch (Exception e) {
+        // Pass exception to onPostExecute() so it can be handled on the main thread
+        return Result.of(e);
+      }
+    }
+
+    @Override
+    protected void onPostExecute(Result<ReactApplicationContext> result) {
+      try {
+        setupReactContext(result.get());
+      } catch (Exception e) {
+        mDevSupportManager.handleException(e);
+      } finally {
+        mReactContextInitAsyncTask = null;
+      }
+
+      // Handle enqueued request to re-initialize react context.
+      if (mPendingReactContextInitParams != null) {
+        recreateReactContextInBackground(
+            mPendingReactContextInitParams.getJsExecutorFactory(),
+            mPendingReactContextInitParams.getJsBundleLoader());
+        mPendingReactContextInitParams = null;
+      }
+    }
+
+    @Override
+    protected void onCancelled(Result<ReactApplicationContext> reactApplicationContextResult) {
+      try {
+        mMemoryPressureRouter.destroy(reactApplicationContextResult.get());
+      } catch (Exception e) {
+        FLog.w(ReactConstants.TAG, "Caught exception after cancelling react context init", e);
+      } finally {
+        mReactContextInitAsyncTask = null;
+      }
+    }
+  }
+
+  private static class Result<T> {
+    @Nullable private final T mResult;
+    @Nullable private final Exception mException;
+
+    public static <T, U extends T> Result<T> of(U result) {
+      return new Result<T>(result);
+    }
+
+    public static <T> Result<T> of(Exception exception) {
+      return new Result<>(exception);
+    }
+
+    private Result(T result) {
+      mException = null;
+      mResult = result;
+    }
+
+    private Result(Exception exception) {
+      mException = exception;
+      mResult = null;
+    }
+
+    public T get() throws Exception {
+      if (mException != null) {
+        throw mException;
+      }
+
+      Assertions.assertNotNull(mResult);
+
+      return mResult;
+    }
+  }
+
+  /* package */ ReactInstanceManagerImpl(
+      Context applicationContext,
+      @Nullable String jsBundleFile,
+      @Nullable String jsMainModuleName,
+      List<ReactPackage> packages,
+      boolean useDeveloperSupport,
+      @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
+      LifecycleState initialLifecycleState,
+      UIImplementationProvider uiImplementationProvider,
+      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
+      @Nullable JSCConfig jscConfig) {
+    initializeSoLoaderIfNecessary(applicationContext);
+
+    // TODO(9577825): remove this
+    ApplicationHolder.setApplication((Application) applicationContext.getApplicationContext());
+    setDisplayMetrics(applicationContext);
+
+    mApplicationContext = applicationContext;
+    mJSBundleFile = jsBundleFile;
+    mJSMainModuleName = jsMainModuleName;
+    mPackages = packages;
+    mUseDeveloperSupport = useDeveloperSupport;
+    mDevSupportManager = DevSupportManagerFactory.create(
+        applicationContext,
+        mDevInterface,
+        mJSMainModuleName,
+        useDeveloperSupport);
+    mBridgeIdleDebugListener = bridgeIdleDebugListener;
+    mLifecycleState = initialLifecycleState;
+    mUIImplementationProvider = uiImplementationProvider;
+    mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
+    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+    mJSCConfig = jscConfig;
+  }
+
+  @Override
+  public DevSupportManager getDevSupportManager() {
+    return mDevSupportManager;
+  }
+
+  @Override
+  public MemoryPressureRouter getMemoryPressureRouter() {
+    return mMemoryPressureRouter;
+  }
+
+  private static void initializeSoLoaderIfNecessary(Context applicationContext) {
+    // Call SoLoader.initialize here, this is required for apps that does not use exopackage and
+    // does not use SoLoader for loading other native code except from the one used by React Native
+    // This way we don't need to require others to have additional initialization code and to
+    // subclass android.app.Application.
+
+    // Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call
+    // SoLoader.init with appropriate args before initializing ReactInstanceManagerImpl
+    SoLoader.init(applicationContext, /* native exopackage */ false);
+  }
+
+  private static void setDisplayMetrics(Context context) {
+    DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+
+    DisplayMetrics screenDisplayMetrics = new DisplayMetrics();
+    screenDisplayMetrics.setTo(displayMetrics);
+    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+    Display display = wm.getDefaultDisplay();
+
+    // Get the real display metrics if we are using API level 17 or higher.
+    // The real metrics include system decor elements (e.g. soft menu bar).
+    //
+    // See: http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
+    if (Build.VERSION.SDK_INT >= 17) {
+      display.getRealMetrics(screenDisplayMetrics);
+    } else {
+      // For 14 <= API level <= 16, we need to invoke getRawHeight and getRawWidth to get the real dimensions.
+      // Since react-native only supports API level 16+ we don't have to worry about other cases.
+      //
+      // Reflection exceptions are rethrown at runtime.
+      //
+      // See: http://stackoverflow.com/questions/14341041/how-to-get-real-screen-height-and-width/23861333#23861333
+      try {
+        Method mGetRawH = Display.class.getMethod("getRawHeight");
+        Method mGetRawW = Display.class.getMethod("getRawWidth");
+        screenDisplayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
+        screenDisplayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
+      } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
+        throw new RuntimeException("Error getting real dimensions for API level < 17", e);
+      }
+    }
+    DisplayMetricsHolder.setScreenDisplayMetrics(screenDisplayMetrics);
+  }
+
+  /**
+   * Trigger react context initialization asynchronously in a background async task. This enables
+   * applications to pre-load the application JS, and execute global code before
+   * {@link ReactRootView} is available and measured. This should only be called the first time the
+   * application is set up, which is enforced to keep developers from accidentally creating their
+   * application multiple times without realizing it.
+   *
+   * Called from UI thread.
+   */
+  @Override
+  public void createReactContextInBackground() {
+    Assertions.assertCondition(
+        !mHasStartedCreatingInitialContext,
+        "createReactContextInBackground should only be called when creating the react " +
+            "application for the first time. When reloading JS, e.g. from a new file, explicitly" +
+            "use recreateReactContextInBackground");
+
+    mHasStartedCreatingInitialContext = true;
+    recreateReactContextInBackgroundInner();
+  }
+
+  /**
+   * Recreate the react application and context. This should be called if configuration has
+   * changed or the developer has requested the app to be reloaded. It should only be called after
+   * an initial call to createReactContextInBackground.
+   *
+   * Called from UI thread.
+   */
+  public void recreateReactContextInBackground() {
+    Assertions.assertCondition(
+        mHasStartedCreatingInitialContext,
+        "recreateReactContextInBackground should only be called after the initial " +
+            "createReactContextInBackground call.");
+    recreateReactContextInBackgroundInner();
+  }
+
+  private void recreateReactContextInBackgroundInner() {
+    UiThreadUtil.assertOnUiThread();
+
+    if (mUseDeveloperSupport && mJSMainModuleName != null) {
+      if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
+        // If there is a up-to-date bundle downloaded from server, always use that
+        onJSBundleLoadedFromServer();
+      } else if (mJSBundleFile == null) {
+        mDevSupportManager.handleReloadJS();
+      } else {
+        mDevSupportManager.isPackagerRunning(
+            new DevServerHelper.PackagerStatusCallback() {
+              @Override
+              public void onPackagerStatusFetched(final boolean packagerIsRunning) {
+                UiThreadUtil.runOnUiThread(
+                    new Runnable() {
+                      @Override
+                      public void run() {
+                        if (packagerIsRunning) {
+                          mDevSupportManager.handleReloadJS();
+                        } else {
+                          recreateReactContextInBackgroundFromBundleFile();
+                        }
+                      }
+                    });
+              }
+            });
+      }
+      return;
+    }
+
+    recreateReactContextInBackgroundFromBundleFile();
+  }
+
+  private void recreateReactContextInBackgroundFromBundleFile() {
+    recreateReactContextInBackground(
+        new JSCJavaScriptExecutor.Factory(),
+        JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
+  }
+
+  /**
+   * @return whether createReactContextInBackground has been called. Will return false after
+   * onDestroy until a new initial context has been created.
+   */
+  public boolean hasStartedCreatingInitialContext() {
+    return mHasStartedCreatingInitialContext;
+  }
+
+  /**
+   * This method will give JS the opportunity to consume the back button event. If JS does not
+   * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
+   */
+  @Override
+  public void onBackPressed() {
+    UiThreadUtil.assertOnUiThread();
+    ReactContext reactContext = mCurrentReactContext;
+    if (mCurrentReactContext == null) {
+      // Invoke without round trip to JS.
+      FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
+      invokeDefaultOnBackPressed();
+    } else {
+      DeviceEventManagerModule deviceEventManagerModule =
+          Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class);
+      deviceEventManagerModule.emitHardwareBackPressed();
+    }
+  }
+
+  private void invokeDefaultOnBackPressed() {
+    UiThreadUtil.assertOnUiThread();
+    if (mDefaultBackButtonImpl != null) {
+      mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
+    }
+  }
+
+  private void toggleElementInspector() {
+    if (mCurrentReactContext != null) {
+      mCurrentReactContext
+          .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
+          .emit("toggleElementInspector", null);
+    }
+  }
+
+  @Override
+  public void onHostPause() {
+    UiThreadUtil.assertOnUiThread();
+
+    mDefaultBackButtonImpl = null;
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(false);
+    }
+
+    moveToBeforeResumeLifecycleState();
+    mCurrentActivity = null;
+  }
+
+  /**
+   * Use this method when the activity resumes to enable invoking the back button directly from JS.
+   *
+   * This method retains an instance to provided mDefaultBackButtonImpl. Thus it's
+   * important to pass from the activity instance that owns this particular instance of {@link
+   * ReactInstanceManagerImpl}, so that once this instance receive {@link #onHostDestroy} event it will
+   * clear the reference to that defaultBackButtonImpl.
+   *
+   * @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
+   * this instance of {@link ReactInstanceManagerImpl}.
+   */
+  @Override
+  public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
+    UiThreadUtil.assertOnUiThread();
+
+
+    mDefaultBackButtonImpl = defaultBackButtonImpl;
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(true);
+    }
+
+    mCurrentActivity = activity;
+    moveToResumedLifecycleState(false);
+  }
+
+  @Override
+  public void onHostDestroy() {
+    UiThreadUtil.assertOnUiThread();
+
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(false);
+    }
+
+    moveToBeforeCreateLifecycleState();
+    mCurrentActivity = null;
+  }
+
+  @Override
+  public void destroy() {
+    UiThreadUtil.assertOnUiThread();
+
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(false);
+    }
+
+    moveToBeforeCreateLifecycleState();
+
+    if (mReactContextInitAsyncTask != null) {
+      mReactContextInitAsyncTask.cancel(true);
+    }
+
+    mMemoryPressureRouter.destroy(mApplicationContext);
+
+    if (mCurrentReactContext != null) {
+      mCurrentReactContext.destroy();
+      mCurrentReactContext = null;
+      mHasStartedCreatingInitialContext = false;
+    }
+    mCurrentActivity = null;
+  }
+
+  private void moveToResumedLifecycleState(boolean force) {
+    if (mCurrentReactContext != null) {
+      // we currently don't have an onCreate callback so we call onResume for both transitions
+      if (force ||
+          mLifecycleState == LifecycleState.BEFORE_RESUME ||
+          mLifecycleState == LifecycleState.BEFORE_CREATE) {
+        mCurrentReactContext.onHostResume(mCurrentActivity);
+      }
+    }
+    mLifecycleState = LifecycleState.RESUMED;
+  }
+
+  private void moveToBeforeResumeLifecycleState() {
+    if (mCurrentReactContext != null) {
+      if (mLifecycleState == LifecycleState.BEFORE_CREATE) {
+        mCurrentReactContext.onHostResume(mCurrentActivity);
+        mCurrentReactContext.onHostPause();
+      } else if (mLifecycleState == LifecycleState.RESUMED) {
+        mCurrentReactContext.onHostPause();
+      }
+    }
+    mLifecycleState = LifecycleState.BEFORE_RESUME;
+  }
+
+  private void moveToBeforeCreateLifecycleState() {
+    if (mCurrentReactContext != null) {
+      if (mLifecycleState == LifecycleState.RESUMED) {
+        mCurrentReactContext.onHostPause();
+        mLifecycleState = LifecycleState.BEFORE_RESUME;
+      }
+      if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
+        mCurrentReactContext.onHostDestroy();
+      }
+    }
+    mLifecycleState = LifecycleState.BEFORE_CREATE;
+  }
+
+  public LifecycleState getLifecycleState() {
+    return mLifecycleState;
+  }
+
+  @Override
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    if (mCurrentReactContext != null) {
+      mCurrentReactContext.onActivityResult(requestCode, resultCode, data);
+    }
+  }
+
+  @Override
+  public void showDevOptionsDialog() {
+    UiThreadUtil.assertOnUiThread();
+    mDevSupportManager.showDevOptionsDialog();
+  }
+
+  /**
+   * Get the URL where the last bundle was loaded from.
+   */
+  @Override
+  public String getSourceUrl() {
+    return Assertions.assertNotNull(mSourceUrl);
+  }
+
+  /**
+   * Attach given {@param rootView} to a catalyst instance manager and start JS application using
+   * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently
+   * being (re)-created, or if react context has not been created yet, the JS application associated
+   * with the provided root view will be started asynchronously, i.e this method won't block.
+   * This view will then be tracked by this manager and in case of catalyst instance restart it will
+   * be re-attached.
+   */
+  @Override
+  public void attachMeasuredRootView(ReactRootView rootView) {
+    UiThreadUtil.assertOnUiThread();
+    mAttachedRootViews.add(rootView);
+
+    // If react context is being created in the background, JS application will be started
+    // automatically when creation completes, as root view is part of the attached root view list.
+    if (mReactContextInitAsyncTask == null && mCurrentReactContext != null) {
+      attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
+    }
+  }
+
+  /**
+   * Detach given {@param rootView} from current catalyst instance. It's safe to call this method
+   * multiple times on the same {@param rootView} - in that case view will be detached with the
+   * first call.
+   */
+  @Override
+  public void detachRootView(ReactRootView rootView) {
+    UiThreadUtil.assertOnUiThread();
+    if (mAttachedRootViews.remove(rootView)) {
+      if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) {
+        detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance());
+      }
+    }
+  }
+
+  /**
+   * Uses configured {@link ReactPackage} instances to create all view managers
+   */
+  @Override
+  public List<ViewManager> createAllViewManagers(
+      ReactApplicationContext catalystApplicationContext) {
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createAllViewManagers");
+    try {
+      List<ViewManager> allViewManagers = new ArrayList<>();
+      for (ReactPackage reactPackage : mPackages) {
+        allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext));
+      }
+      return allViewManagers;
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+  }
+
+  @Override
+  public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
+    mReactInstanceEventListeners.add(listener);
+  }
+
+  @Override
+  public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
+    mReactInstanceEventListeners.remove(listener);
+  }
+
+  @VisibleForTesting
+  @Override
+  public @Nullable ReactContext getCurrentReactContext() {
+    return mCurrentReactContext;
+  }
+
+  private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
+    recreateReactContextInBackground(
+        new ProxyJavaScriptExecutor.Factory(jsExecutorFactory),
+        JSBundleLoader.createRemoteDebuggerBundleLoader(
+            mDevSupportManager.getJSBundleURLForRemoteDebugging(),
+            mDevSupportManager.getSourceUrl()));
+  }
+
+  private void onJSBundleLoadedFromServer() {
+    recreateReactContextInBackground(
+        new JSCJavaScriptExecutor.Factory(),
+        JSBundleLoader.createCachedBundleFromNetworkLoader(
+            mDevSupportManager.getSourceUrl(),
+            mDevSupportManager.getDownloadedJSBundleFile()));
+  }
+
+  private void recreateReactContextInBackground(
+      JavaScriptExecutor.Factory jsExecutorFactory,
+      JSBundleLoader jsBundleLoader) {
+    UiThreadUtil.assertOnUiThread();
+
+    ReactContextInitParams initParams =
+        new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
+    if (mReactContextInitAsyncTask == null) {
+      // No background task to create react context is currently running, create and execute one.
+      mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
+      mReactContextInitAsyncTask.execute(initParams);
+    } else {
+      // Background task is currently running, queue up most recent init params to recreate context
+      // once task completes.
+      mPendingReactContextInitParams = initParams;
+    }
+  }
+
+  private void setupReactContext(ReactApplicationContext reactContext) {
+    UiThreadUtil.assertOnUiThread();
+    Assertions.assertCondition(mCurrentReactContext == null);
+    mCurrentReactContext = Assertions.assertNotNull(reactContext);
+    CatalystInstance catalystInstance =
+        Assertions.assertNotNull(reactContext.getCatalystInstance());
+
+    catalystInstance.initialize();
+    mDevSupportManager.onNewReactContextCreated(reactContext);
+    mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
+    moveReactContextToCurrentLifecycleState();
+
+    for (ReactRootView rootView : mAttachedRootViews) {
+      attachMeasuredRootViewToInstance(rootView, catalystInstance);
+    }
+
+    ReactInstanceEventListener[] listeners =
+      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
+    listeners = mReactInstanceEventListeners.toArray(listeners);
+
+    for (ReactInstanceEventListener listener : listeners) {
+      listener.onReactContextInitialized(reactContext);
+    }
+  }
+
+  private void attachMeasuredRootViewToInstance(
+      ReactRootView rootView,
+      CatalystInstance catalystInstance) {
+    UiThreadUtil.assertOnUiThread();
+
+    // Reset view content as it's going to be populated by the application content from JS
+    rootView.removeAllViews();
+    rootView.setId(View.NO_ID);
+
+    UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
+    int rootTag = uiManagerModule.addMeasuredRootView(rootView);
+    @Nullable Bundle launchOptions = rootView.getLaunchOptions();
+    WritableMap initialProps = launchOptions != null
+        ? Arguments.fromBundle(launchOptions)
+        : Arguments.createMap();
+    String jsAppModuleName = rootView.getJSModuleName();
+
+    WritableNativeMap appParams = new WritableNativeMap();
+    appParams.putDouble("rootTag", rootTag);
+    appParams.putMap("initialProps", initialProps);
+    catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
+  }
+
+  private void detachViewFromInstance(
+      ReactRootView rootView,
+      CatalystInstance catalystInstance) {
+    UiThreadUtil.assertOnUiThread();
+    catalystInstance.getJSModule(AppRegistry.class)
+        .unmountApplicationComponentAtRootTag(rootView.getId());
+  }
+
+  private void tearDownReactContext(ReactContext reactContext) {
+    UiThreadUtil.assertOnUiThread();
+    if (mLifecycleState == LifecycleState.RESUMED) {
+      reactContext.onHostPause();
+    }
+    for (ReactRootView rootView : mAttachedRootViews) {
+      detachViewFromInstance(rootView, reactContext.getCatalystInstance());
+    }
+    reactContext.destroy();
+    mDevSupportManager.onReactInstanceDestroyed(reactContext);
+    mMemoryPressureRouter.removeMemoryPressureListener(reactContext.getCatalystInstance());
+  }
+
+  /**
+   * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
+   */
+  private ReactApplicationContext createReactContext(
+      JavaScriptExecutor jsExecutor,
+      JSBundleLoader jsBundleLoader) {
+    FLog.i(ReactConstants.TAG, "Creating react context.");
+    ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
+    mSourceUrl = jsBundleLoader.getSourceUrl();
+    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
+    JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
+
+    ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
+    if (mUseDeveloperSupport) {
+      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
+    }
+
+    ReactMarker.logMarker(PROCESS_PACKAGES_START);
+    Systrace.beginSection(
+        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+        "createAndProcessCoreModulesPackage");
+    try {
+      CoreModulesPackage coreModulesPackage =
+          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
+      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+
+    // TODO(6818138): Solve use-case of native/js modules overriding
+    for (ReactPackage reactPackage : mPackages) {
+      Systrace.beginSection(
+          Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+          "createAndProcessCustomReactPackage");
+      try {
+        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
+      } finally {
+        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+      }
+    }
+    ReactMarker.logMarker(PROCESS_PACKAGES_END);
+
+    ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
+    NativeModuleRegistry nativeModuleRegistry;
+    try {
+       nativeModuleRegistry = nativeRegistryBuilder.build();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+      ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
+    }
+
+    ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_START);
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig");
+    JavaScriptModulesConfig javaScriptModulesConfig;
+    try {
+      javaScriptModulesConfig = jsModulesBuilder.build();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+      ReactMarker.logMarker(BUILD_JS_MODULE_CONFIG_END);
+    }
+
+    NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
+        ? mNativeModuleCallExceptionHandler
+        : mDevSupportManager;
+    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
+        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
+        .setJSExecutor(jsExecutor)
+        .setRegistry(nativeModuleRegistry)
+        .setJSModulesConfig(javaScriptModulesConfig)
+        .setJSBundleLoader(jsBundleLoader)
+        .setNativeModuleCallExceptionHandler(exceptionHandler);
+
+    ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
+    CatalystInstance catalystInstance;
+    try {
+      catalystInstance = catalystInstanceBuilder.build();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+      ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
+    }
+
+    if (mBridgeIdleDebugListener != null) {
+      catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
+    }
+
+    reactContext.initializeWithInstance(catalystInstance);
+
+    ReactMarker.logMarker(RUN_JS_BUNDLE_START);
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
+    try {
+      catalystInstance.runJSBundle();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+      ReactMarker.logMarker(RUN_JS_BUNDLE_END);
+    }
+
+    ReactMarker.logMarker(CREATE_REACT_CONTEXT_END);
+    return reactContext;
+  }
+
+  private void processPackage(
+      ReactPackage reactPackage,
+      ReactApplicationContext reactContext,
+      NativeModuleRegistry.Builder nativeRegistryBuilder,
+      JavaScriptModulesConfig.Builder jsModulesBuilder) {
+    for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
+      nativeRegistryBuilder.add(nativeModule);
+    }
+    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
+      jsModulesBuilder.add(jsModuleClass);
+    }
+  }
+
+  private void moveReactContextToCurrentLifecycleState() {
+    if (mLifecycleState == LifecycleState.RESUMED) {
+      moveToResumedLifecycleState(true);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
index 0a4eadc00862..bf4b8b4bc843 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java
@@ -14,7 +14,6 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -28,6 +27,7 @@
 import com.facebook.react.bridge.UiThreadUtil;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.react.modules.core.DeviceEventManagerModule;
 import com.facebook.react.uimanager.DisplayMetricsHolder;
@@ -55,12 +55,12 @@
  */
 public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
 
-  private final KeyboardListener mKeyboardListener = new KeyboardListener();
-
   private @Nullable ReactInstanceManager mReactInstanceManager;
   private @Nullable String mJSModuleName;
   private @Nullable Bundle mLaunchOptions;
+  private @Nullable KeyboardListener mKeyboardListener;
   private int mTargetTag = -1;
+  private final float[] mTargetCoordinates = new float[2];
   private boolean mChildIsHandlingNativeGesture = false;
   private boolean mWasMeasured = false;
   private boolean mAttachScheduled = false;
@@ -106,7 +106,7 @@ public void run() {
           Assertions.assertNotNull(mReactInstanceManager)
               .attachMeasuredRootView(ReactRootView.this);
           mIsAttachedToInstance = true;
-          getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
+          getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
         }
       });
     }
@@ -143,9 +143,19 @@ private void handleTouchEvent(MotionEvent ev) {
       // {@link #findTargetTagForTouch} to find react view ID that will be responsible for handling
       // this gesture
       mChildIsHandlingNativeGesture = false;
-      mTargetTag = TouchTargetHelper.findTargetTagForTouch(ev.getY(), ev.getX(), this);
+      mTargetTag = TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
+          ev.getX(),
+          ev.getY(),
+          this,
+          mTargetCoordinates);
       eventDispatcher.dispatchEvent(
-          TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
+          TouchEvent.obtain(
+              mTargetTag,
+              SystemClock.nanoTime(),
+              TouchEventType.START,
+              ev,
+              mTargetCoordinates[0],
+              mTargetCoordinates[1]));
     } else if (mChildIsHandlingNativeGesture) {
       // If the touch was intercepted by a child, we've already sent a cancel event to JS for this
       // gesture, so we shouldn't send any more touches related to it.
@@ -161,20 +171,44 @@ private void handleTouchEvent(MotionEvent ev) {
       // End of the gesture. We reset target tag to -1 and expect no further event associated with
       // this gesture.
       eventDispatcher.dispatchEvent(
-          TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
+          TouchEvent.obtain(
+              mTargetTag,
+              SystemClock.nanoTime(),
+              TouchEventType.END,
+              ev,
+              mTargetCoordinates[0],
+              mTargetCoordinates[1]));
       mTargetTag = -1;
     } else if (action == MotionEvent.ACTION_MOVE) {
       // Update pointer position for current gesture
       eventDispatcher.dispatchEvent(
-          TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.MOVE, ev));
+          TouchEvent.obtain(
+              mTargetTag,
+              SystemClock.nanoTime(),
+              TouchEventType.MOVE,
+              ev,
+              mTargetCoordinates[0],
+              mTargetCoordinates[1]));
     } else if (action == MotionEvent.ACTION_POINTER_DOWN) {
       // New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
       eventDispatcher.dispatchEvent(
-          TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.START, ev));
+          TouchEvent.obtain(
+              mTargetTag,
+              SystemClock.nanoTime(),
+              TouchEventType.START,
+              ev,
+              mTargetCoordinates[0],
+              mTargetCoordinates[1]));
     } else if (action == MotionEvent.ACTION_POINTER_UP) {
       // Exactly onw of the pointers goes up
       eventDispatcher.dispatchEvent(
-          TouchEvent.obtain(mTargetTag, SystemClock.uptimeMillis(), TouchEventType.END, ev));
+          TouchEvent.obtain(
+              mTargetTag,
+              SystemClock.nanoTime(),
+              TouchEventType.END,
+              ev,
+              mTargetCoordinates[0],
+              mTargetCoordinates[1]));
     } else if (action == MotionEvent.ACTION_CANCEL) {
       dispatchCancelEvent(ev);
       mTargetTag = -1;
@@ -221,9 +255,11 @@ private void dispatchCancelEvent(MotionEvent androidEvent) {
     Assertions.assertNotNull(eventDispatcher).dispatchEvent(
         TouchEvent.obtain(
             mTargetTag,
-            SystemClock.uptimeMillis(),
+            SystemClock.nanoTime(),
             TouchEventType.CANCEL,
-            androidEvent));
+            androidEvent,
+            mTargetCoordinates[0],
+            mTargetCoordinates[1]));
   }
 
   @Override
@@ -261,7 +297,7 @@ protected void onDetachedFromWindow() {
     if (mReactInstanceManager != null && !mAttachScheduled) {
       mReactInstanceManager.detachRootView(this);
       mIsAttachedToInstance = false;
-      getViewTreeObserver().removeOnGlobalLayoutListener(mKeyboardListener);
+      getViewTreeObserver().removeOnGlobalLayoutListener(getKeyboardListener());
     }
   }
 
@@ -319,7 +355,7 @@ public void startReactApplication(
     if (mWasMeasured && mIsAttachedToWindow) {
       mReactInstanceManager.attachMeasuredRootView(this);
       mIsAttachedToInstance = true;
-      getViewTreeObserver().addOnGlobalLayoutListener(mKeyboardListener);
+      getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
     } else {
       mAttachScheduled = true;
     }
@@ -344,9 +380,23 @@ public void startReactApplication(
     mWasMeasured = true;
   }
 
+  private KeyboardListener getKeyboardListener() {
+    if (mKeyboardListener == null) {
+      mKeyboardListener = new KeyboardListener();
+    }
+    return mKeyboardListener;
+  }
+
   private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
+    private final Rect mVisibleViewArea;
+    private final int mMinKeyboardHeightDetected;
+
     private int mKeyboardHeight = 0;
-    private final Rect mVisibleViewArea = new Rect();
+
+    /* package */ KeyboardListener() {
+      mVisibleViewArea = new Rect();
+      mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
+    }
 
     @Override
     public void onGlobalLayout() {
@@ -360,8 +410,8 @@ public void onGlobalLayout() {
 
       getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
       final int heightDiff =
-          DisplayMetricsHolder.getDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
-      if (mKeyboardHeight != heightDiff && heightDiff > 0) {
+          DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
+      if (mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected) {
         // keyboard is now showing, or the keyboard height has changed
         mKeyboardHeight = heightDiff;
         WritableMap params = Arguments.createMap();
@@ -372,9 +422,9 @@ public void onGlobalLayout() {
         coordinates.putDouble("height", PixelUtil.toDIPFromPixel(mKeyboardHeight));
         params.putMap("endCoordinates", coordinates);
         sendEvent("keyboardDidShow", params);
-      } else if (mKeyboardHeight != 0 && heightDiff == 0) {
+      } else if (mKeyboardHeight != 0 && heightDiff <= mMinKeyboardHeightDetected) {
         // keyboard is now hidden
-        mKeyboardHeight = heightDiff;
+        mKeyboardHeight = 0;
         sendEvent("keyboardDidHide", null);
       }
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK b/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK
index 9d8c59acd80d..43aaf9c5d40f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BUCK
@@ -16,21 +16,22 @@ python_binary(
 android_library(
   name = 'bridge',
   srcs = glob(['**/*.java']),
+  exported_deps = [
+    react_native_dep('java/com/facebook/jni:jni'),
+    react_native_dep('java/com/facebook/proguard/annotations:annotations'),
+  ],
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/common:common'),
-    react_native_target('jni/react/jni:jni'),
-    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
     react_native_dep('java/com/facebook/systrace:systrace'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jackson:core'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
     react_native_dep('third-party/java/okhttp:okhttp-ws'),
   ],
-  exported_deps = [
-    react_native_dep('java/com/facebook/jni:jni'),
-    react_native_dep('java/com/facebook/proguard/annotations:annotations'),
-  ],
   visibility = [
     'PUBLIC',
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
index 061611e7408d..afa121acb402 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
@@ -57,14 +57,14 @@ public int getJSArgumentsNeeded() {
     }
 
     public abstract @Nullable T extractArgument(
-        CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex);
+        CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex);
   }
 
   static final private ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
       new ArgumentExtractor<Boolean>() {
         @Override
         public Boolean extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return jsArguments.getBoolean(atIndex);
         }
       };
@@ -73,7 +73,7 @@ public Boolean extractArgument(
       new ArgumentExtractor<Double>() {
         @Override
         public Double extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return jsArguments.getDouble(atIndex);
         }
       };
@@ -82,7 +82,7 @@ public Double extractArgument(
       new ArgumentExtractor<Float>() {
         @Override
         public Float extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return (float) jsArguments.getDouble(atIndex);
         }
       };
@@ -91,7 +91,7 @@ public Float extractArgument(
       new ArgumentExtractor<Integer>() {
         @Override
         public Integer extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return (int) jsArguments.getDouble(atIndex);
         }
       };
@@ -100,7 +100,7 @@ public Integer extractArgument(
       new ArgumentExtractor<String>() {
         @Override
         public String extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return jsArguments.getString(atIndex);
         }
       };
@@ -109,7 +109,7 @@ public String extractArgument(
       new ArgumentExtractor<ReadableNativeArray>() {
         @Override
         public ReadableNativeArray extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return jsArguments.getArray(atIndex);
         }
       };
@@ -118,7 +118,7 @@ public ReadableNativeArray extractArgument(
       new ArgumentExtractor<ReadableMap>() {
         @Override
         public ReadableMap extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           return jsArguments.getMap(atIndex);
         }
       };
@@ -127,12 +127,12 @@ public ReadableMap extractArgument(
       new ArgumentExtractor<Callback>() {
         @Override
         public @Nullable Callback extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           if (jsArguments.isNull(atIndex)) {
             return null;
           } else {
             int id = (int) jsArguments.getDouble(atIndex);
-            return new CallbackImpl(catalystInstance, id);
+            return new CallbackImpl(catalystInstance, executorToken, id);
           }
         }
       };
@@ -146,11 +146,11 @@ public int getJSArgumentsNeeded() {
 
         @Override
         public Promise extractArgument(
-            CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
+            CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
           Callback resolve = ARGUMENT_EXTRACTOR_CALLBACK
-              .extractArgument(catalystInstance, jsArguments, atIndex);
+              .extractArgument(catalystInstance, executorToken, jsArguments, atIndex);
           Callback reject = ARGUMENT_EXTRACTOR_CALLBACK
-              .extractArgument(catalystInstance, jsArguments, atIndex + 1);
+              .extractArgument(catalystInstance, executorToken, jsArguments, atIndex + 1);
           return new PromiseImpl(resolve, reject);
         }
       };
@@ -174,9 +174,21 @@ public JavaMethod(Method method) {
     }
 
     private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
-      ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
-      for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
-        Class argumentClass = paramTypes[i];
+      // Modules that support web workers are expected to take an ExecutorToken as the first
+      // parameter to all their @ReactMethod-annotated methods. We compensate for that here.
+      int executorTokenOffset = 0;
+      if (BaseJavaModule.this.supportsWebWorkers()) {
+        if (paramTypes[0] != ExecutorToken.class) {
+          throw new RuntimeException(
+              "Module " + BaseJavaModule.this + " supports web workers, but " + mMethod.getName() +
+                  "does not take an ExecutorToken as its first parameter.");
+        }
+        executorTokenOffset = 1;
+      }
+
+      ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length - executorTokenOffset];
+      for (int i = 0; i < paramTypes.length - executorTokenOffset; i += argumentExtractors[i].getJSArgumentsNeeded()) {
+        Class argumentClass = paramTypes[i + executorTokenOffset];
         if (argumentClass == Boolean.class || argumentClass == boolean.class) {
           argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
         } else if (argumentClass == Integer.class || argumentClass == int.class) {
@@ -220,7 +232,7 @@ private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
     }
 
     @Override
-    public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
+    public void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters) {
       Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
       try {
         if (mJSArgumentsNeeded != parameters.size()) {
@@ -229,11 +241,18 @@ public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parame
               parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
         }
 
+        // Modules that support web workers are expected to take an ExecutorToken as the first
+        // parameter to all their @ReactMethod-annotated methods. We compensate for that here.
         int i = 0, jsArgumentsConsumed = 0;
+        int executorTokenOffset = 0;
+        if (BaseJavaModule.this.supportsWebWorkers()) {
+          mArguments[0] = executorToken;
+          executorTokenOffset = 1;
+        }
         try {
           for (; i < mArgumentExtractors.length; i++) {
-            mArguments[i] = mArgumentExtractors[i].extractArgument(
-                catalystInstance, parameters, jsArgumentsConsumed);
+            mArguments[i + executorTokenOffset] = mArgumentExtractors[i].extractArgument(
+                catalystInstance, executorToken, parameters, jsArgumentsConsumed);
             jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
           }
         } catch (UnexpectedNativeTypeException e) {
@@ -327,8 +346,23 @@ public void initialize() {
     // do nothing
   }
 
+  @Override
+  public boolean canOverrideExistingModule() {
+    return false;
+  }
+
+  @Override
+  public void onReactBridgeInitialized(ReactBridge bridge) {
+    // do nothing
+  }
+
   @Override
   public void onCatalystInstanceDestroy() {
     // do nothing
   }
+  
+  @Override
+  public boolean supportsWebWorkers() {
+    return false;
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CallbackImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CallbackImpl.java
index 8b5153e5cc51..a7bc858026ee 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CallbackImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CallbackImpl.java
@@ -15,15 +15,17 @@
 public final class CallbackImpl implements Callback {
 
   private final CatalystInstance mCatalystInstance;
+  private final ExecutorToken mExecutorToken;
   private final int mCallbackId;
 
-  public CallbackImpl(CatalystInstance bridge, int callbackId) {
+  public CallbackImpl(CatalystInstance bridge, ExecutorToken executorToken, int callbackId) {
     mCatalystInstance = bridge;
+    mExecutorToken = executorToken;
     mCallbackId = callbackId;
   }
 
   @Override
   public void invoke(Object... args) {
-    mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
+    mCatalystInstance.invokeCallback(mExecutorToken, mCallbackId, Arguments.fromJavaArgs(args));
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
index c96ddb8e469d..17c01f542869 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
@@ -9,29 +9,11 @@
 
 package com.facebook.react.bridge;
 
-import javax.annotation.Nullable;
-
-import java.io.IOException;
-import java.io.StringWriter;
 import java.util.Collection;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
-import com.facebook.common.logging.FLog;
-import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
-import com.facebook.react.bridge.queue.CatalystQueueConfigurationSpec;
-import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
-import com.facebook.proguard.annotations.DoNotStrip;
-import com.facebook.react.common.ReactConstants;
+import com.facebook.react.bridge.queue.ReactQueueConfiguration;
 import com.facebook.react.common.annotations.VisibleForTesting;
-import com.facebook.infer.annotation.Assertions;
-import com.facebook.systrace.Systrace;
-import com.facebook.systrace.TraceListener;
-
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonGenerator;
+import com.facebook.proguard.annotations.DoNotStrip;
 
 /**
  * A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -39,253 +21,32 @@
  * Java APIs be invokable from JavaScript as well.
  */
 @DoNotStrip
-public class CatalystInstance {
-
-  private static final int BRIDGE_SETUP_TIMEOUT_MS = 30000;
-  private static final int LOAD_JS_BUNDLE_TIMEOUT_MS = 30000;
-
-  private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
-
-  // Access from any thread
-  private final CatalystQueueConfiguration mCatalystQueueConfiguration;
-  private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
-  private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
-  private final String mJsPendingCallsTitleForTrace =
-      "pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
-  private volatile boolean mDestroyed = false;
-  private final TraceListener mTraceListener;
-  private final JavaScriptModuleRegistry mJSModuleRegistry;
-  private final JSBundleLoader mJSBundleLoader;
-
-  // Access from native modules thread
-  private final NativeModuleRegistry mJavaRegistry;
-  private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
-  private boolean mInitialized = false;
-
-  // Access from JS thread
-  private @Nullable ReactBridge mBridge;
-  private boolean mJSBundleHasLoaded;
-
-      private CatalystInstance(
-      final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
-      final JavaScriptExecutor jsExecutor,
-      final NativeModuleRegistry registry,
-      final JavaScriptModulesConfig jsModulesConfig,
-      final JSBundleLoader jsBundleLoader,
-      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
-    mCatalystQueueConfiguration = CatalystQueueConfiguration.create(
-        catalystQueueConfigurationSpec,
-        new NativeExceptionHandler());
-    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
-    mJavaRegistry = registry;
-    mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstance.this, jsModulesConfig);
-    mJSBundleLoader = jsBundleLoader;
-    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
-    mTraceListener = new JSProfilerTraceListener();
-    Systrace.registerListener(mTraceListener);
-
-    final CountDownLatch initLatch = new CountDownLatch(1);
-    mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
-        new Runnable() {
-          @Override
-          public void run() {
-            initializeBridge(jsExecutor, jsModulesConfig);
-            initLatch.countDown();
-          }
-        });
-
-    try {
-      Assertions.assertCondition(
-          initLatch.await(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS),
-          "Timed out waiting for bridge to initialize!");
-    } catch (InterruptedException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  private void initializeBridge(
-      JavaScriptExecutor jsExecutor,
-      JavaScriptModulesConfig jsModulesConfig) {
-    mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
-    Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
-
-    mBridge = new ReactBridge(
-        jsExecutor,
-        new NativeModulesReactCallback(),
-        mCatalystQueueConfiguration.getNativeModulesQueueThread());
-    mBridge.setGlobalVariable(
-        "__fbBatchedBridgeConfig",
-        buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
-  }
-
-  public void runJSBundle() {
-    Systrace.beginSection(
-        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-        "CatalystInstance_runJSBundle");
-
-    try {
-      final CountDownLatch initLatch = new CountDownLatch(1);
-      mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
-          new Runnable() {
-            @Override
-            public void run() {
-              Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
-              mJSBundleHasLoaded = true;
-
-              incrementPendingJSCalls();
-
-              try {
-                mJSBundleLoader.loadScript(mBridge);
-              } catch (JSExecutionException e) {
-                mNativeModuleCallExceptionHandler.handleException(e);
-              }
-
-              initLatch.countDown();
-            }
-          });
-      Assertions.assertCondition(
-          initLatch.await(LOAD_JS_BUNDLE_TIMEOUT_MS, TimeUnit.MILLISECONDS),
-          "Timed out loading JS!");
-    } catch (InterruptedException e) {
-      throw new RuntimeException(e);
-    } finally {
-      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
-    }
-  }
-
-  /* package */ void callFunction(
-      final int moduleId,
-      final int methodId,
-      final NativeArray arguments,
-      final String tracingName) {
-    if (mDestroyed) {
-      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
-      return;
-    }
-
-    incrementPendingJSCalls();
-
-    mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
-        new Runnable() {
-          @Override
-          public void run() {
-            mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
-
-            if (mDestroyed) {
-              return;
-            }
-
-            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName);
-            try {
-              Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments);
-            } finally {
-              Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
-            }
-          }
-        });
-  }
-
+public interface CatalystInstance extends MemoryPressureListener {
+  void runJSBundle();
   // This is called from java code, so it won't be stripped anyway, but proguard will rename it,
   // which this prevents.
   @DoNotStrip
-  /* package */ void invokeCallback(final int callbackID, final NativeArray arguments) {
-    if (mDestroyed) {
-      FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
-      return;
-    }
-
-    incrementPendingJSCalls();
-
-    mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
-        new Runnable() {
-          @Override
-          public void run() {
-            mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
-
-            if (mDestroyed) {
-              return;
-            }
-
-            Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "<callback>");
-            try {
-              Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
-            } finally {
-              Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
-            }
-          }
-        });
-  }
-
+  void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray arguments);
   /**
-   * Destroys this catalyst instance, waiting for any other threads in CatalystQueueConfiguration
+   * Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
    * (besides the UI thread) to finish running. Must be called from the UI thread so that we can
    * fully shut down other threads.
    */
-  /* package */ void destroy() {
-    UiThreadUtil.assertOnUiThread();
-
-    if (mDestroyed) {
-      return;
-    }
-
-    // TODO: tell all APIs to shut down
-    mDestroyed = true;
-    mJavaRegistry.notifyCatalystInstanceDestroy();
-    mCatalystQueueConfiguration.destroy();
-    boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
-    if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
-      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
-        listener.onTransitionToBridgeIdle();
-      }
-    }
-
-    if (mTraceListener != null) {
-      Systrace.unregisterListener(mTraceListener);
-    }
-
-    // We can access the Bridge from any thread now because we know either we are on the JS thread
-    // or the JS thread has finished via CatalystQueueConfiguration#destroy()
-    Assertions.assertNotNull(mBridge).dispose();
-  }
-
-  public boolean isDestroyed() {
-    return mDestroyed;
-  }
+  void destroy();
+  boolean isDestroyed();
 
   /**
    * Initialize all the native modules
    */
   @VisibleForTesting
-  public void initialize() {
-    UiThreadUtil.assertOnUiThread();
-    Assertions.assertCondition(
-        !mInitialized,
-        "This catalyst instance has already been initialized");
-    mInitialized = true;
-    mJavaRegistry.notifyCatalystInstanceInitialized();
-  }
-
-  public CatalystQueueConfiguration getCatalystQueueConfiguration() {
-    return mCatalystQueueConfiguration;
-  }
-
-  @VisibleForTesting
-  public @Nullable
-  ReactBridge getBridge() {
-    return mBridge;
-  }
-
-  public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
-    return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
-  }
+  void initialize();
 
-  public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
-    return mJavaRegistry.getModule(nativeModuleInterface);
-  }
+  ReactQueueConfiguration getReactQueueConfiguration();
 
-  public Collection<NativeModule> getNativeModules() {
-    return mJavaRegistry.getAllModules();
-  }
+  <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface);
+  <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface);
+  <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface);
+  Collection<NativeModule> getNativeModules();
 
   /**
    * Adds a idle listener for this Catalyst instance. The listener will receive notifications
@@ -293,183 +54,18 @@ public Collection<NativeModule> getNativeModules() {
    * defined as there being some non-zero number of calls to JS that haven't resolved via a
    * onBatchCompleted call. The listener should be purely passive and not affect application logic.
    */
-  public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
-    mBridgeIdleListeners.add(listener);
-  }
+  void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
 
   /**
    * Removes a NotThreadSafeBridgeIdleDebugListener previously added with
    * {@link #addBridgeIdleDebugListener}
    */
-  public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
-    mBridgeIdleListeners.remove(listener);
-  }
-
-  private String buildModulesConfigJSONProperty(
-      NativeModuleRegistry nativeModuleRegistry,
-      JavaScriptModulesConfig jsModulesConfig) {
-    // TODO(5300733): Serialize config using single json generator
-    JsonFactory jsonFactory = new JsonFactory();
-    StringWriter writer = new StringWriter();
-    try {
-      JsonGenerator jg = jsonFactory.createGenerator(writer);
-      jg.writeStartObject();
-      jg.writeFieldName("remoteModuleConfig");
-      jg.writeRawValue(nativeModuleRegistry.moduleDescriptions());
-      jg.writeFieldName("localModulesConfig");
-      jg.writeRawValue(jsModulesConfig.moduleDescriptions());
-      jg.writeEndObject();
-      jg.close();
-    } catch (IOException ioe) {
-      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
-    }
-    return writer.getBuffer().toString();
-  }
-
-  private void incrementPendingJSCalls() {
-    int oldPendingCalls = mPendingJSCalls.getAndIncrement();
-    boolean wasIdle = oldPendingCalls == 0;
-    Systrace.traceCounter(
-        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-        mJsPendingCallsTitleForTrace,
-        oldPendingCalls + 1);
-    if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
-      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
-        listener.onTransitionToBridgeBusy();
-      }
-    }
-  }
-
-  private void decrementPendingJSCalls() {
-    int newPendingCalls = mPendingJSCalls.decrementAndGet();
-    Assertions.assertCondition(newPendingCalls >= 0);
-    boolean isNowIdle = newPendingCalls == 0;
-    Systrace.traceCounter(
-        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-        mJsPendingCallsTitleForTrace,
-        newPendingCalls);
-
-    if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
-      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
-        listener.onTransitionToBridgeIdle();
-      }
-    }
-  }
-
-  private class NativeModulesReactCallback implements ReactCallback {
-
-    @Override
-    public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
-      mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
-
-      // Suppress any callbacks if destroyed - will only lead to sadness.
-      if (mDestroyed) {
-        return;
-      }
-
-      mJavaRegistry.call(CatalystInstance.this, moduleId, methodId, parameters);
-    }
+  void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener);
 
-    @Override
-    public void onBatchComplete() {
-      mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
+  boolean supportsProfiling();
+  void startProfiler(String title);
+  void stopProfiler(String title, String filename);
 
-      // The bridge may have been destroyed due to an exception during the batch. In that case
-      // native modules could be in a bad state so we don't want to call anything on them. We
-      // still want to trigger the debug listener since it allows instrumentation tests to end and
-      // check their assertions without waiting for a timeout.
-      if (!mDestroyed) {
-        Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
-        try {
-          mJavaRegistry.onBatchComplete();
-        } finally {
-          Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
-        }
-      }
-
-      decrementPendingJSCalls();
-    }
-  }
-
-  private class NativeExceptionHandler implements QueueThreadExceptionHandler {
-
-    @Override
-    public void handleException(Exception e) {
-      // Any Exception caught here is because of something in JS. Even if it's a bug in the
-      // framework/native code, it was triggered by JS and theoretically since we were able
-      // to set up the bridge, JS could change its logic, reload, and not trigger that crash.
-      mNativeModuleCallExceptionHandler.handleException(e);
-      mCatalystQueueConfiguration.getUIQueueThread().runOnQueue(
-          new Runnable() {
-            @Override
-            public void run() {
-              destroy();
-            }
-          });
-    }
-  }
-
-  private class JSProfilerTraceListener implements TraceListener {
-    @Override
-    public void onTraceStarted() {
-      getJSModule(BridgeProfiling.class).setEnabled(true);
-    }
-
-    @Override
-    public void onTraceStopped() {
-      getJSModule(BridgeProfiling.class).setEnabled(false);
-    }
-  }
-
-  public static class Builder {
-
-    private @Nullable CatalystQueueConfigurationSpec mCatalystQueueConfigurationSpec;
-    private @Nullable JSBundleLoader mJSBundleLoader;
-    private @Nullable NativeModuleRegistry mRegistry;
-    private @Nullable JavaScriptModulesConfig mJSModulesConfig;
-    private @Nullable JavaScriptExecutor mJSExecutor;
-    private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
-
-    public Builder setCatalystQueueConfigurationSpec(
-        CatalystQueueConfigurationSpec catalystQueueConfigurationSpec) {
-      mCatalystQueueConfigurationSpec = catalystQueueConfigurationSpec;
-      return this;
-    }
-
-    public Builder setRegistry(NativeModuleRegistry registry) {
-      mRegistry = registry;
-      return this;
-    }
-
-    public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
-      mJSModulesConfig = jsModulesConfig;
-      return this;
-    }
-
-    public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
-      mJSBundleLoader = jsBundleLoader;
-      return this;
-    }
-
-    public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
-      mJSExecutor = jsExecutor;
-      return this;
-    }
-
-    public Builder setNativeModuleCallExceptionHandler(
-        NativeModuleCallExceptionHandler handler) {
-      mNativeModuleCallExceptionHandler = handler;
-      return this;
-    }
-
-    public CatalystInstance build() {
-      return new CatalystInstance(
-          Assertions.assertNotNull(mCatalystQueueConfigurationSpec),
-          Assertions.assertNotNull(mJSExecutor),
-          Assertions.assertNotNull(mRegistry),
-          Assertions.assertNotNull(mJSModulesConfig),
-          Assertions.assertNotNull(mJSBundleLoader),
-          Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
-    }
-  }
+  @VisibleForTesting
+  void setGlobalVariable(String propName, String jsonValue);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
new file mode 100644
index 000000000000..533c8721ff00
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
@@ -0,0 +1,538 @@
+/**
+ * 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.bridge;
+
+import javax.annotation.Nullable;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.queue.ReactQueueConfiguration;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.common.futures.SimpleSettableFuture;
+import com.facebook.systrace.Systrace;
+import com.facebook.systrace.TraceListener;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+
+/**
+ * This provides an implementation of the public CatalystInstance instance.  It is public because
+ * it is built by ReactInstanceManager which is in a different package.
+ */
+@DoNotStrip
+public class CatalystInstanceImpl implements CatalystInstance {
+
+  private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
+
+  // Access from any thread
+  private final ReactQueueConfigurationImpl mReactQueueConfiguration;
+  private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
+  private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
+  private final String mJsPendingCallsTitleForTrace =
+      "pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
+  private volatile boolean mDestroyed = false;
+  private final TraceListener mTraceListener;
+  private final JavaScriptModuleRegistry mJSModuleRegistry;
+  private final JSBundleLoader mJSBundleLoader;
+  private final Object mTeardownLock = new Object();
+  private @Nullable ExecutorToken mMainExecutorToken;
+
+  // Access from native modules thread
+  private final NativeModuleRegistry mJavaRegistry;
+  private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+  private boolean mInitialized = false;
+
+  // Access from JS thread
+  private final ReactBridge mBridge;
+  private boolean mJSBundleHasLoaded;
+
+  private CatalystInstanceImpl(
+      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
+      final JavaScriptExecutor jsExecutor,
+      final NativeModuleRegistry registry,
+      final JavaScriptModulesConfig jsModulesConfig,
+      final JSBundleLoader jsBundleLoader,
+      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
+    FLog.d(ReactConstants.TAG, "Initializing React Bridge.");
+    mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
+        ReactQueueConfigurationSpec,
+        new NativeExceptionHandler());
+    mBridgeIdleListeners = new CopyOnWriteArrayList<>();
+    mJavaRegistry = registry;
+    mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);
+    mJSBundleLoader = jsBundleLoader;
+    mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+    mTraceListener = new JSProfilerTraceListener();
+
+    try {
+      mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
+          new Callable<ReactBridge>() {
+            @Override
+            public ReactBridge call() throws Exception {
+              Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge");
+              try {
+                return initializeBridge(jsExecutor, jsModulesConfig);
+              } finally {
+                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+              }
+            }
+          }).get();
+    } catch (Exception t) {
+      throw new RuntimeException("Failed to initialize bridge", t);
+    }
+  }
+
+  private ReactBridge initializeBridge(
+      JavaScriptExecutor jsExecutor,
+      JavaScriptModulesConfig jsModulesConfig) {
+    mReactQueueConfiguration.getJSQueueThread().assertIsOnThread();
+    Assertions.assertCondition(mBridge == null, "initializeBridge should be called once");
+
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor");
+    ReactBridge bridge;
+    try {
+      bridge = new ReactBridge(
+          jsExecutor,
+          new NativeModulesReactCallback(),
+          mReactQueueConfiguration.getNativeModulesQueueThread());
+      mMainExecutorToken = bridge.getMainExecutorToken();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig");
+    try {
+      bridge.setGlobalVariable(
+          "__fbBatchedBridgeConfig",
+          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
+      bridge.setGlobalVariable(
+          "__RCTProfileIsProfiling",
+          Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false");
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+
+    mJavaRegistry.notifyReactBridgeInitialized(bridge);
+    return bridge;
+  }
+
+  @Override
+  public void runJSBundle() {
+    try {
+      mJSBundleHasLoaded = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
+          new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+              Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
+
+              incrementPendingJSCalls();
+
+              Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "loadJSScript");
+              try {
+                mJSBundleLoader.loadScript(mBridge);
+
+                // This is registered after JS starts since it makes a JS call
+                Systrace.registerListener(mTraceListener);
+              } catch (JSExecutionException e) {
+                mNativeModuleCallExceptionHandler.handleException(e);
+              } finally {
+                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+              }
+
+              return true;
+            }
+          }).get();
+    } catch (Exception t) {
+      throw new RuntimeException(t);
+    }
+  }
+
+  /* package */ void callFunction(
+      ExecutorToken executorToken,
+      int moduleId,
+      int methodId,
+      NativeArray arguments,
+      String tracingName) {
+    synchronized (mTeardownLock) {
+      if (mDestroyed) {
+        FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
+        return;
+      }
+
+      incrementPendingJSCalls();
+
+      Assertions.assertNotNull(mBridge).callFunction(executorToken, moduleId, methodId, arguments, tracingName);
+    }
+  }
+
+  // This is called from java code, so it won't be stripped anyway, but proguard will rename it,
+  // which this prevents.
+  @DoNotStrip
+  @Override
+  public void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments) {
+    synchronized (mTeardownLock) {
+      if (mDestroyed) {
+        FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
+        return;
+      }
+
+      incrementPendingJSCalls();
+
+      Assertions.assertNotNull(mBridge).invokeCallback(executorToken, callbackID, arguments);
+    }
+  }
+
+  /**
+   * Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
+   * (besides the UI thread) to finish running. Must be called from the UI thread so that we can
+   * fully shut down other threads.
+   */
+  @Override
+  public void destroy() {
+    UiThreadUtil.assertOnUiThread();
+
+    synchronized (mTeardownLock) {
+      if (mDestroyed) {
+        return;
+      }
+
+      // TODO: tell all APIs to shut down
+      mDestroyed = true;
+      mJavaRegistry.notifyCatalystInstanceDestroy();
+
+      Systrace.unregisterListener(mTraceListener);
+
+      synchronouslyDisposeBridgeOnJSThread();
+    }
+
+    mReactQueueConfiguration.destroy();
+
+    boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
+    if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
+      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+        listener.onTransitionToBridgeIdle();
+      }
+    }
+  }
+
+  private void synchronouslyDisposeBridgeOnJSThread() {
+    final SimpleSettableFuture<Void> bridgeDisposeFuture = new SimpleSettableFuture<>();
+    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
+        new Runnable() {
+          @Override
+          public void run() {
+            mBridge.destroy();
+            mBridge.dispose();
+            bridgeDisposeFuture.set(null);
+          }
+        });
+    bridgeDisposeFuture.getOrThrow();
+  }
+
+  @Override
+  public boolean isDestroyed() {
+    return mDestroyed;
+  }
+
+  /**
+   * Initialize all the native modules
+   */
+  @VisibleForTesting
+  @Override
+  public void initialize() {
+    UiThreadUtil.assertOnUiThread();
+    Assertions.assertCondition(
+        !mInitialized,
+        "This catalyst instance has already been initialized");
+    mInitialized = true;
+    mJavaRegistry.notifyCatalystInstanceInitialized();
+  }
+
+  @Override
+  public ReactQueueConfiguration getReactQueueConfiguration() {
+    return mReactQueueConfiguration;
+  }
+
+  @Override
+  public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
+    return getJSModule(Assertions.assertNotNull(mMainExecutorToken), jsInterface);
+  }
+
+  @Override
+  public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
+    return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(executorToken, jsInterface);
+  }
+
+  @Override
+  public <T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface) {
+    return mJavaRegistry.getModule(nativeModuleInterface);
+  }
+
+  @Override
+  public Collection<NativeModule> getNativeModules() {
+    return mJavaRegistry.getAllModules();
+  }
+
+  @Override
+  public void handleMemoryPressure(final MemoryPressure level) {
+    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
+      new Runnable() {
+        @Override
+        public void run() {
+          Assertions.assertNotNull(mBridge).handleMemoryPressure(level);
+        }
+      });
+  }
+
+  /**
+   * Adds a idle listener for this Catalyst instance. The listener will receive notifications
+   * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
+   * defined as there being some non-zero number of calls to JS that haven't resolved via a
+   * onBatchCompleted call. The listener should be purely passive and not affect application logic.
+   */
+  @Override
+  public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
+    mBridgeIdleListeners.add(listener);
+  }
+
+  /**
+   * Removes a NotThreadSafeBridgeIdleDebugListener previously added with
+   * {@link #addBridgeIdleDebugListener}
+   */
+  @Override
+  public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
+    mBridgeIdleListeners.remove(listener);
+  }
+
+  @Override
+  public boolean supportsProfiling() {
+    return mBridge.supportsProfiling();
+  }
+
+  @Override
+  public void startProfiler(String title) {
+    mBridge.startProfiler(title);
+  }
+
+  @Override
+  public void stopProfiler(String title, String filename) {
+    mBridge.stopProfiler(title, filename);
+  }
+
+  @VisibleForTesting
+  @Override
+  public void setGlobalVariable(String propName, String jsonValue) {
+    mBridge.setGlobalVariable(propName, jsonValue);
+  }
+
+  private String buildModulesConfigJSONProperty(
+      NativeModuleRegistry nativeModuleRegistry,
+      JavaScriptModulesConfig jsModulesConfig) {
+    JsonFactory jsonFactory = new JsonFactory();
+    StringWriter writer = new StringWriter();
+    try {
+      JsonGenerator jg = jsonFactory.createGenerator(writer);
+      jg.writeStartObject();
+      jg.writeFieldName("remoteModuleConfig");
+      nativeModuleRegistry.writeModuleDescriptions(jg);
+      jg.writeFieldName("localModulesConfig");
+      jsModulesConfig.writeModuleDescriptions(jg);
+      jg.writeEndObject();
+      jg.close();
+    } catch (IOException ioe) {
+      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
+    }
+    return writer.getBuffer().toString();
+  }
+
+  private void incrementPendingJSCalls() {
+    int oldPendingCalls = mPendingJSCalls.getAndIncrement();
+    boolean wasIdle = oldPendingCalls == 0;
+    Systrace.traceCounter(
+        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+        mJsPendingCallsTitleForTrace,
+        oldPendingCalls + 1);
+    if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
+      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+        listener.onTransitionToBridgeBusy();
+      }
+    }
+  }
+
+  private void decrementPendingJSCalls() {
+    int newPendingCalls = mPendingJSCalls.decrementAndGet();
+    // TODO(9604406): handle case of web workers injecting messages to main thread
+    //Assertions.assertCondition(newPendingCalls >= 0);
+    boolean isNowIdle = newPendingCalls == 0;
+    Systrace.traceCounter(
+        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+        mJsPendingCallsTitleForTrace,
+        newPendingCalls);
+
+    if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
+      for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+        listener.onTransitionToBridgeIdle();
+      }
+    }
+  }
+
+  private class NativeModulesReactCallback implements ReactCallback {
+
+    @Override
+    public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
+      mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
+
+      synchronized (mTeardownLock) {
+        // Suppress any callbacks if destroyed - will only lead to sadness.
+        if (mDestroyed) {
+          return;
+        }
+
+        mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
+      }
+    }
+
+    @Override
+    public void onBatchComplete() {
+      mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
+
+      // The bridge may have been destroyed due to an exception during the batch. In that case
+      // native modules could be in a bad state so we don't want to call anything on them. We
+      // still want to trigger the debug listener since it allows instrumentation tests to end and
+      // check their assertions without waiting for a timeout.
+      synchronized (mTeardownLock) {
+        if (!mDestroyed) {
+          Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
+          try {
+            mJavaRegistry.onBatchComplete();
+          } finally {
+            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+          }
+        }
+      }
+
+      decrementPendingJSCalls();
+    }
+
+    @Override
+    public void onExecutorUnregistered(ExecutorToken executorToken) {
+      mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
+
+      // Since onCatalystInstanceDestroy happens on the UI thread, we don't want to also execute
+      // this callback on the native modules thread at the same time. Longer term, onCatalystInstanceDestroy
+      // should probably be executed on the native modules thread as well instead.
+      synchronized (mTeardownLock) {
+        if (!mDestroyed) {
+          Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onExecutorUnregistered");
+          try {
+            mJavaRegistry.onExecutorUnregistered(executorToken);
+          } finally {
+            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+          }
+        }
+      }
+    }
+  }
+
+  private class NativeExceptionHandler implements QueueThreadExceptionHandler {
+
+    @Override
+    public void handleException(Exception e) {
+      // Any Exception caught here is because of something in JS. Even if it's a bug in the
+      // framework/native code, it was triggered by JS and theoretically since we were able
+      // to set up the bridge, JS could change its logic, reload, and not trigger that crash.
+      mNativeModuleCallExceptionHandler.handleException(e);
+      mReactQueueConfiguration.getUIQueueThread().runOnQueue(
+          new Runnable() {
+            @Override
+            public void run() {
+              destroy();
+            }
+          });
+    }
+  }
+
+  private class JSProfilerTraceListener implements TraceListener {
+    @Override
+    public void onTraceStarted() {
+      getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(
+          true);
+    }
+
+    @Override
+    public void onTraceStopped() {
+      getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(false);
+    }
+  }
+
+  public static class Builder {
+
+    private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
+    private @Nullable JSBundleLoader mJSBundleLoader;
+    private @Nullable NativeModuleRegistry mRegistry;
+    private @Nullable JavaScriptModulesConfig mJSModulesConfig;
+    private @Nullable JavaScriptExecutor mJSExecutor;
+    private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+
+    public Builder setReactQueueConfigurationSpec(
+        ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
+      mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
+      return this;
+    }
+
+    public Builder setRegistry(NativeModuleRegistry registry) {
+      mRegistry = registry;
+      return this;
+    }
+
+    public Builder setJSModulesConfig(JavaScriptModulesConfig jsModulesConfig) {
+      mJSModulesConfig = jsModulesConfig;
+      return this;
+    }
+
+    public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
+      mJSBundleLoader = jsBundleLoader;
+      return this;
+    }
+
+    public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
+      mJSExecutor = jsExecutor;
+      return this;
+    }
+
+    public Builder setNativeModuleCallExceptionHandler(
+        NativeModuleCallExceptionHandler handler) {
+      mNativeModuleCallExceptionHandler = handler;
+      return this;
+    }
+
+    public CatalystInstanceImpl build() {
+      return new CatalystInstanceImpl(
+          Assertions.assertNotNull(mReactQueueConfigurationSpec),
+          Assertions.assertNotNull(mJSExecutor),
+          Assertions.assertNotNull(mRegistry),
+          Assertions.assertNotNull(mJSModulesConfig),
+          Assertions.assertNotNull(mJSBundleLoader),
+          Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/DefaultNativeModuleCallExceptionHandler.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/DefaultNativeModuleCallExceptionHandler.java
new file mode 100644
index 000000000000..4d47156c7479
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/DefaultNativeModuleCallExceptionHandler.java
@@ -0,0 +1,27 @@
+/**
+ * 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.bridge;
+
+/**
+ * Crashy crashy exception handler.
+ */
+public class DefaultNativeModuleCallExceptionHandler implements NativeModuleCallExceptionHandler {
+
+  @Override
+  public void handleException(Exception e) {
+    if (e instanceof RuntimeException) {
+      // Because we are rethrowing the original exception, the original stacktrace will be
+      // preserved.
+      throw (RuntimeException) e;
+    } else {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ExecutorToken.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ExecutorToken.java
new file mode 100644
index 000000000000..e23bbebfb7d1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ExecutorToken.java
@@ -0,0 +1,24 @@
+package com.facebook.react.bridge;
+
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+
+/**
+ * Class corresponding to a JS VM that can call into native modules. In Java, this should
+ * just be treated as a black box to be used to help the framework route native->JS calls back to
+ * the proper JS VM. See {@link ReactContext#getJSModule(ExecutorToken, Class)} and
+ * {@link BaseJavaModule#supportsWebWorkers()}.
+ *
+ * Note: If your application doesn't use web workers, it will only have a single ExecutorToken
+ * per instance of React Native.
+ */
+@DoNotStrip
+public class ExecutorToken {
+
+  private final HybridData mHybridData;
+
+  @DoNotStrip
+  private ExecutorToken(HybridData hybridData) {
+    mHybridData = hybridData;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java
index 917c1279c667..83a7fb014e34 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedAsyncTask.java
@@ -16,9 +16,8 @@
  * handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
  * the app is in dev mode.
  *
- * This class doesn't allow doInBackground to return a results. This is mostly because when this
- * class was written that functionality wasn't used and it would require some extra code to make
- * work correctly with caught exceptions. Don't let that stop you from adding it if you need it :)
+ * This class doesn't allow doInBackground to return a results. If you need this
+ * use GuardedResultAsyncTask instead.
  */
 public abstract class GuardedAsyncTask<Params, Progress>
     extends AsyncTask<Params, Progress, Void> {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java
new file mode 100644
index 000000000000..db13a71464e4
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/GuardedResultAsyncTask.java
@@ -0,0 +1,43 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.bridge;
+
+import android.os.AsyncTask;
+
+/**
+ * Abstract base for a AsyncTask with result support that should have any RuntimeExceptions it
+ * throws handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler}
+ * registered if the app is in dev mode.
+ */
+public abstract class GuardedResultAsyncTask<Result>
+    extends AsyncTask<Void, Void, Result> {
+
+  private final ReactContext mReactContext;
+
+  protected GuardedResultAsyncTask(ReactContext reactContext) {
+    mReactContext = reactContext;
+  }
+
+  @Override
+  protected final Result doInBackground(Void... params) {
+    try {
+      return doInBackgroundGuarded();
+    } catch (RuntimeException e) {
+      mReactContext.handleException(e);
+      throw e;
+    }
+  }
+
+  @Override
+  protected final void onPostExecute(Result result) {
+    try {
+      onPostExecuteGuarded(result);
+    } catch (RuntimeException e) {
+      mReactContext.handleException(e);
+    }
+  }
+
+  protected abstract Result doInBackgroundGuarded();
+  protected abstract void onPostExecuteGuarded(Result result);
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
index c784cd00791f..799863b087c4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
@@ -31,13 +31,13 @@ public void loadScript(ReactBridge bridge) {
         if (fileName.startsWith("assets://")) {
           bridge.loadScriptFromAssets(context.getAssets(), fileName.replaceFirst("assets://", ""));
         } else {
-          bridge.loadScriptFromFile(fileName, fileName);
+          bridge.loadScriptFromFile(fileName, "file://" + fileName);
         }
       }
 
       @Override
       public String getSourceUrl() {
-        return fileName;
+        return (fileName.startsWith("assets://") ? "" : "file://") + fileName;
       }
     };
   }
@@ -68,18 +68,22 @@ public String getSourceUrl() {
   /**
    * 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.
+   *
+   * @param proxySourceURL the URL to load the JS bundle from in Chrome
+   * @param realSourceURL the URL to report as the source URL, e.g. for asset loading
    */
   public static JSBundleLoader createRemoteDebuggerBundleLoader(
-      final String sourceURL) {
+      final String proxySourceURL,
+      final String realSourceURL) {
     return new JSBundleLoader() {
       @Override
       public void loadScript(ReactBridge bridge) {
-        bridge.loadScriptFromFile(null, sourceURL);
+        bridge.loadScriptFromFile(null, proxySourceURL);
       }
 
       @Override
       public String getSourceUrl() {
-        return sourceURL;
+        return realSourceURL;
       }
     };
   }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java
index d371cf5be621..b4431c0d66d9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java
@@ -14,15 +14,21 @@
 
 @DoNotStrip
 public class JSCJavaScriptExecutor extends JavaScriptExecutor {
+  public static class Factory implements JavaScriptExecutor.Factory {
+    @Override
+    public JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception {
+      return new JSCJavaScriptExecutor(jscConfig);
+    }
+  }
 
   static {
     SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
   }
 
-  public JSCJavaScriptExecutor() {
-    initialize();
+  public JSCJavaScriptExecutor(WritableNativeMap jscConfig) {
+    initialize(jscConfig);
   }
 
-  private native void initialize();
+  private native void initialize(WritableNativeMap jscConfig);
 
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java
index 8940ffc0860d..849a0ea79c9c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSDebuggerWebSocketClient.java
@@ -103,7 +103,7 @@ public void prepareJSRuntime(JSDebuggerCallback callback) {
     }
   }
 
-  public void executeApplicationScript(
+  public void loadApplicationScript(
       String sourceURL,
       HashMap<String, String> injectedObjects,
       JSDebuggerCallback callback) {
@@ -126,7 +126,6 @@ public void executeApplicationScript(
   }
 
   public void executeJSCall(
-      String moduleName,
       String methodName,
       String jsonArgsArray,
       JSDebuggerCallback callback) {
@@ -136,9 +135,7 @@ public void executeJSCall(
 
     try {
       JsonGenerator jg = startMessageObject(requestID);
-      jg.writeStringField("method","executeJSCall");
-      jg.writeStringField("moduleName", moduleName);
-      jg.writeStringField("moduleMethod", methodName);
+      jg.writeStringField("method", methodName);
       jg.writeFieldName("arguments");
       jg.writeRawValue(jsonArgsArray);
       sendMessage(requestID, endMessageObject(jg));
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java
new file mode 100644
index 000000000000..991fdfb6f292
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java
@@ -0,0 +1,57 @@
+/**
+ * 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.bridge;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+/**
+ * This is class represents java version of native js executor interface. When set through
+ * {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will
+ * delegate js calls to the given implementation of this interface.
+ */
+@DoNotStrip
+public interface JavaJSExecutor {
+  interface Factory {
+    JavaJSExecutor create() throws Exception;
+  }
+
+  class ProxyExecutorException extends Exception {
+    public ProxyExecutorException(Throwable cause) {
+      super(cause);
+    }
+  }
+
+  /**
+   * Close this executor and cleanup any resources that it was using. No further calls are
+   * expected after this.
+   */
+  void close();
+
+  /**
+   * Load javascript into the js context
+   * @param script script contet to be executed
+   * @param sourceURL url or file location from which script content was loaded
+   */
+  @DoNotStrip
+  void loadApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
+
+  /**
+   * Execute javascript method within js context
+   * @param methodName name of the method to be executed
+   * @param jsonArgsArray json encoded array of arguments provided for the method call
+   * @return json encoded value returned from the method call
+   */
+  @DoNotStrip
+  String executeJSCall(String methodName, String jsonArgsArray)
+      throws ProxyExecutorException;
+
+  @DoNotStrip
+  void setGlobalVariable(String propertyName, String jsonEncodedValue);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java
new file mode 100644
index 000000000000..4ac476a31b5f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyArray.java
@@ -0,0 +1,153 @@
+/**
+ * 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.bridge;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Java {@link ArrayList} backed impementation of {@link ReadableArray} and {@link WritableArray}
+ * Instances of this class SHOULD NOT be used for communication between java and JS, use instances
+ * of {@link WritableNativeArray} created via {@link Arguments#createArray} or just
+ * {@link ReadableArray} interface if you want your "native" module method to take an array from JS
+ * as an argument.
+ *
+ * Main purpose for this class is to be used in java-only unit tests, but could also be used outside
+ * of tests in the code that operates only in java and needs to communicate with RN modules via
+ * their JS-exposed API.
+ */
+public class JavaOnlyArray implements ReadableArray, WritableArray {
+
+  private final List mBackingList;
+
+  public static JavaOnlyArray from(List list) {
+    return new JavaOnlyArray(list);
+  }
+
+  public static JavaOnlyArray of(Object... values) {
+    return new JavaOnlyArray(values);
+  }
+
+  private JavaOnlyArray(Object... values) {
+    mBackingList = Arrays.asList(values);
+  }
+
+  private JavaOnlyArray(List list) {
+    mBackingList = new ArrayList(list);
+  }
+
+  public JavaOnlyArray() {
+    mBackingList = new ArrayList();
+  }
+
+  @Override
+  public int size() {
+    return mBackingList.size();
+  }
+
+  @Override
+  public boolean isNull(int index) {
+    return mBackingList.get(index) == null;
+  }
+
+  @Override
+  public double getDouble(int index) {
+    return (Double) mBackingList.get(index);
+  }
+
+  @Override
+  public int getInt(int index) {
+    return (Integer) mBackingList.get(index);
+  }
+
+  @Override
+  public String getString(int index) {
+    return (String) mBackingList.get(index);
+  }
+
+  @Override
+  public JavaOnlyArray getArray(int index) {
+    return (JavaOnlyArray) mBackingList.get(index);
+  }
+
+  @Override
+  public boolean getBoolean(int index) {
+    return (Boolean) mBackingList.get(index);
+  }
+
+  @Override
+  public JavaOnlyMap getMap(int index) {
+    return (JavaOnlyMap) mBackingList.get(index);
+  }
+
+  @Override
+  public ReadableType getType(int index) {
+    return null;
+  }
+
+  @Override
+  public void pushBoolean(boolean value) {
+    mBackingList.add(value);
+  }
+
+  @Override
+  public void pushDouble(double value) {
+    mBackingList.add(value);
+  }
+
+  @Override
+  public void pushInt(int value) {
+    mBackingList.add(value);
+  }
+
+  @Override
+  public void pushString(String value) {
+    mBackingList.add(value);
+  }
+
+  @Override
+  public void pushArray(WritableArray array) {
+    mBackingList.add(array);
+  }
+
+  @Override
+  public void pushMap(WritableMap map) {
+    mBackingList.add(map);
+  }
+
+  @Override
+  public void pushNull() {
+    mBackingList.add(null);
+  }
+
+  @Override
+  public String toString() {
+    return mBackingList.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    JavaOnlyArray that = (JavaOnlyArray) o;
+
+    if (mBackingList != null ? !mBackingList.equals(that.mBackingList) : that.mBackingList != null)
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return mBackingList != null ? mBackingList.hashCode() : 0;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java
new file mode 100644
index 000000000000..607d686e2c6f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaOnlyMap.java
@@ -0,0 +1,175 @@
+/**
+ * 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.bridge;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Java {@link HashMap} backed impementation of {@link ReadableMap} and {@link WritableMap}
+ * Instances of this class SHOULD NOT be used for communication between java and JS, use instances
+ * of {@link WritableNativeMap} created via {@link Arguments#createMap} or just {@link ReadableMap}
+ * interface if you want your "native" module method to take a map from JS as an argument.
+ *
+ * Main purpose for this class is to be used in java-only unit tests, but could also be used outside
+ * of tests in the code that operates only in java and needs to communicate with RN modules via
+ * their JS-exposed API.
+ */
+public class JavaOnlyMap implements ReadableMap, WritableMap {
+
+  private final Map mBackingMap;
+
+  public static JavaOnlyMap of(Object... keysAndValues) {
+    return new JavaOnlyMap(keysAndValues);
+  }
+
+  /**
+   * @param keysAndValues keys and values, interleaved
+   */
+  private JavaOnlyMap(Object... keysAndValues) {
+    if (keysAndValues.length % 2 != 0) {
+      throw new IllegalArgumentException("You must provide the same number of keys and values");
+    }
+    mBackingMap = new HashMap();
+    for (int i = 0; i < keysAndValues.length; i += 2) {
+      mBackingMap.put(keysAndValues[i], keysAndValues[i + 1]);
+    }
+  }
+
+  public JavaOnlyMap() {
+    mBackingMap = new HashMap();
+  }
+
+  @Override
+  public boolean hasKey(String name) {
+    return mBackingMap.containsKey(name);
+  }
+
+  @Override
+  public boolean isNull(String name) {
+    return mBackingMap.get(name) == null;
+  }
+
+  @Override
+  public boolean getBoolean(String name) {
+    return (Boolean) mBackingMap.get(name);
+  }
+
+  @Override
+  public double getDouble(String name) {
+    return (Double) mBackingMap.get(name);
+  }
+
+  @Override
+  public int getInt(String name) {
+    return (Integer) mBackingMap.get(name);
+  }
+
+  @Override
+  public String getString(String name) {
+    return (String) mBackingMap.get(name);
+  }
+
+  @Override
+  public JavaOnlyMap getMap(String name) {
+    return (JavaOnlyMap) mBackingMap.get(name);
+  }
+
+  @Override
+  public JavaOnlyArray getArray(String name) {
+    return (JavaOnlyArray) mBackingMap.get(name);
+  }
+
+  @Override
+  public ReadableType getType(String name) {
+    throw new UnsupportedOperationException("Method not implemented");
+  }
+
+  @Override
+  public ReadableMapKeySetIterator keySetIterator() {
+    return new ReadableMapKeySetIterator() {
+      Iterator<String> mIterator = mBackingMap.keySet().iterator();
+
+      @Override
+      public boolean hasNextKey() {
+        return mIterator.hasNext();
+      }
+
+      @Override
+      public String nextKey() {
+        return mIterator.next();
+      }
+    };
+  }
+
+  @Override
+  public void putBoolean(String key, boolean value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public void putDouble(String key, double value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public void putInt(String key, int value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public void putString(String key, String value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public void putNull(String key) {
+    mBackingMap.put(key, null);
+  }
+
+  @Override
+  public void putMap(String key, WritableMap value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public void merge(ReadableMap source) {
+    mBackingMap.putAll(((JavaOnlyMap) source).mBackingMap);
+  }
+
+  @Override
+  public void putArray(String key, WritableArray value) {
+    mBackingMap.put(key, value);
+  }
+
+  @Override
+  public String toString() {
+    return mBackingMap.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    JavaOnlyMap that = (JavaOnlyMap) o;
+
+    if (mBackingMap != null ? !mBackingMap.equals(that.mBackingMap) : that.mBackingMap != null)
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return mBackingMap != null ? mBackingMap.hashCode() : 0;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java
index 2bc5e26c5c53..92499d314412 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java
@@ -14,6 +14,9 @@
 
 @DoNotStrip
 public abstract class JavaScriptExecutor extends Countable {
+  public interface Factory {
+    JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception;
+  }
 
   /**
    * Close this executor and cleanup any resources that it was using. No further calls are
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java
index fab0f231e339..9a48e686bb55 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java
@@ -12,12 +12,16 @@
 import javax.annotation.Nullable;
 
 import java.lang.Class;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.HashMap;
+import java.util.WeakHashMap;
 
+import com.facebook.common.logging.FLog;
 import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.common.ReactConstants;
 
 /**
  * Class responsible for holding all the {@link JavaScriptModule}s registered to this
@@ -27,45 +31,71 @@
  */
 /*package*/ class JavaScriptModuleRegistry {
 
-  private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
+  private final CatalystInstanceImpl mCatalystInstance;
+  private final WeakHashMap<ExecutorToken, HashMap<Class<? extends JavaScriptModule>, JavaScriptModule>> mModuleInstances;
+  private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModuleRegistration> mModuleRegistrations;
 
   public JavaScriptModuleRegistry(
-      CatalystInstance instance,
+      CatalystInstanceImpl instance,
       JavaScriptModulesConfig config) {
-    mModuleInstances = new HashMap<>();
+    mCatalystInstance = instance;
+    mModuleInstances = new WeakHashMap<>();
+    mModuleRegistrations = new HashMap<>();
     for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
-      Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
-      JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
-          moduleInterface.getClassLoader(),
-          new Class[]{moduleInterface},
-          new JavaScriptModuleInvocationHandler(instance, registration));
-
-      mModuleInstances.put(moduleInterface, interfaceProxy);
+      mModuleRegistrations.put(registration.getModuleInterface(), registration);
     }
   }
 
-  public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
-    return (T) Assertions.assertNotNull(
-        mModuleInstances.get(moduleInterface),
-        "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
+  public synchronized  <T extends JavaScriptModule> T getJavaScriptModule(ExecutorToken executorToken, Class<T> moduleInterface) {
+    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
+        mModuleInstances.get(executorToken);
+    if (instancesForContext == null) {
+      instancesForContext = new HashMap<>();
+      mModuleInstances.put(executorToken, instancesForContext);
+    }
+
+    JavaScriptModule module = instancesForContext.get(moduleInterface);
+    if (module != null) {
+      return (T) module;
+    }
+
+    JavaScriptModuleRegistration registration =
+        Assertions.assertNotNull(
+            mModuleRegistrations.get(moduleInterface),
+            "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
+    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
+        moduleInterface.getClassLoader(),
+        new Class[]{moduleInterface},
+        new JavaScriptModuleInvocationHandler(executorToken, mCatalystInstance, registration));
+    instancesForContext.put(moduleInterface, interfaceProxy);
+    return (T) interfaceProxy;
   }
 
   private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
 
-    private final CatalystInstance mCatalystInstance;
+    private final WeakReference<ExecutorToken> mExecutorToken;
+    private final CatalystInstanceImpl mCatalystInstance;
     private final JavaScriptModuleRegistration mModuleRegistration;
 
     public JavaScriptModuleInvocationHandler(
-        CatalystInstance catalystInstance,
+        ExecutorToken executorToken,
+        CatalystInstanceImpl catalystInstance,
         JavaScriptModuleRegistration moduleRegistration) {
+      mExecutorToken = new WeakReference<>(executorToken);
       mCatalystInstance = catalystInstance;
       mModuleRegistration = moduleRegistration;
     }
 
     @Override
     public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      ExecutorToken executorToken = mExecutorToken.get();
+      if (executorToken == null) {
+        FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
+        return null;
+      }
       String tracingName = mModuleRegistration.getTracingName(method);
       mCatalystInstance.callFunction(
+        executorToken,
           mModuleRegistration.getModuleId(),
           mModuleRegistration.getMethodId(method),
           Arguments.fromJavaArgs(args),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java
index bc75da277911..e6241f6e41cf 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java
@@ -10,12 +10,10 @@
 package com.facebook.react.bridge;
 
 import java.io.IOException;
-import java.io.StringWriter;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonGenerator;
 
 /**
@@ -33,23 +31,14 @@ private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
     return mModules;
   }
 
-  /*package*/ String moduleDescriptions() {
-    JsonFactory jsonFactory = new JsonFactory();
-    StringWriter writer = new StringWriter();
-    try {
-      JsonGenerator jg = jsonFactory.createGenerator(writer);
-      jg.writeStartObject();
-      for (JavaScriptModuleRegistration registration : mModules) {
-        jg.writeObjectFieldStart(registration.getName());
-        appendJSModuleToJSONObject(jg, registration);
-        jg.writeEndObject();
-      }
+  /*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
+    jg.writeStartObject();
+    for (JavaScriptModuleRegistration registration : mModules) {
+      jg.writeObjectFieldStart(registration.getName());
+      appendJSModuleToJSONObject(jg, registration);
       jg.writeEndObject();
-      jg.close();
-    } catch (IOException ioe) {
-      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
     }
-    return writer.getBuffer().toString();
+    jg.writeEndObject();
   }
 
   private void appendJSModuleToJSONObject(
@@ -63,6 +52,9 @@ private void appendJSModuleToJSONObject(
       jg.writeEndObject();
     }
     jg.writeEndObject();
+    if (registration.getModuleInterface().isAnnotationPresent(SupportsWebWorkers.class)) {
+      jg.writeBooleanField("supportsWebWorkers", true);
+    }
   }
 
   public static class Builder {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java
new file mode 100644
index 000000000000..c947782efe40
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java
@@ -0,0 +1,8 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.bridge;
+
+public enum MemoryPressure {
+  MODERATE,
+  CRITICAL
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressureListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressureListener.java
new file mode 100644
index 000000000000..e4640b96e277
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressureListener.java
@@ -0,0 +1,15 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.bridge;
+
+/**
+ * Listener interface for memory pressure events.
+ */
+public interface MemoryPressureListener {
+
+  /**
+   * Called when the system generates a memory warning.
+   */
+  void handleMemoryPressure(MemoryPressure level);
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
index 480e42182079..e954bc901552 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
@@ -22,8 +22,8 @@
  * register themselves using {@link CxxModuleWrapper}.
  */
 public interface NativeModule {
-  public static interface NativeMethod {
-    void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
+  interface NativeMethod {
+    void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters);
     String getType();
   }
 
@@ -31,28 +31,68 @@ public static interface NativeMethod {
    * @return the name of this module. This will be the name used to {@code require()} this module
    * from javascript.
    */
-  public String getName();
+  String getName();
 
   /**
    * @return methods callable from JS on this module
    */
-  public Map<String, NativeMethod> getMethods();
+  Map<String, NativeMethod> getMethods();
 
   /**
    * Append a field which represents the constants this module exports
    * to JS.  If no constants are exported this should do nothing.
    */
-  public void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException;
+  void writeConstantsField(JsonGenerator jg, String fieldName) throws IOException;
 
   /**
    * This is called at the end of {@link CatalystApplicationFragment#createCatalystInstance()}
    * after the CatalystInstance has been created, in order to initialize NativeModules that require
    * the CatalystInstance or JS modules.
    */
-  public void initialize();
+  void initialize();
+
+  /**
+   * Return true if you intend to override some other native module that was registered e.g. as part
+   * of a different package (such as the core one). Trying to override without returning true from
+   * this method is considered an error and will throw an exception during initialization. By
+   * default all modules return false.
+   */
+  boolean canOverrideExistingModule();
+
+  /**
+   * Called on the JS thread after a ReactBridge has been created. This is useful for native modules
+   * that need to do any setup before the JS bundle has been loaded. An example of this would be
+   * installing custom functionality into the JavaScriptCore context.
+   *
+   * @param bridge the ReactBridge instance that has just been created
+   */
+  void onReactBridgeInitialized(ReactBridge bridge);
 
   /**
    * Called before {CatalystInstance#onHostDestroy}
    */
-  public void onCatalystInstanceDestroy();
+  void onCatalystInstanceDestroy();
+
+  /**
+   * In order to support web workers, a module must be aware that it can be invoked from multiple
+   * different JS VMs. Supporting web workers means recognizing things like:
+   *
+   * 1) ids (e.g. timer ids, request ids, etc.) may only unique on a per-VM basis
+   * 2) the module needs to make sure to enqueue callbacks and JS module calls to the correct VM
+   *
+   * In order to facilitate this, modules that support web workers will have all their @ReactMethod-
+   * annotated methods passed a {@link ExecutorToken} as the first parameter before any arguments
+   * from JS. This ExecutorToken internally maps to a specific JS VM and can be used by the
+   * framework to route calls appropriately. In order to make JS module calls correctly, start using
+   * the version of {@link ReactContext#getJSModule(ExecutorToken, Class)} that takes an
+   * ExecutorToken. It will ensure that any calls you dispatch to the returned object will go to
+   * the right VM. For Callbacks, you don't have to do anything special -- the framework
+   * automatically tags them with the correct ExecutorToken when the are created.
+   *
+   * Note: even though calls can come from multiple JS VMs on multiple threads, calls to this module
+   * will still only occur on a single thread.
+   *
+   * @return whether this module supports web workers.
+   */
+  boolean supportsWebWorkers();
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
index 72b3ad666953..adb48a364764 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
@@ -10,19 +10,16 @@
 package com.facebook.react.bridge;
 
 import java.io.IOException;
-import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import com.facebook.react.common.MapBuilder;
-import com.facebook.react.common.SetBuilder;
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.systrace.Systrace;
 
-import com.fasterxml.jackson.core.JsonFactory;
 import com.fasterxml.jackson.core.JsonGenerator;
 
 /**
@@ -30,30 +27,33 @@
   */
 public class NativeModuleRegistry {
 
-  private final ArrayList<ModuleDefinition> mModuleTable;
-  private final Map<Class<NativeModule>, NativeModule> mModuleInstances;
-  private final String mModuleDescriptions;
+  private final List<ModuleDefinition> mModuleTable;
+  private final Map<Class<? extends NativeModule>, NativeModule> mModuleInstances;
   private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
+  private final ArrayList<OnExecutorUnregisteredListener> mOnExecutorUnregisteredListenerModules;
 
   private NativeModuleRegistry(
-      ArrayList<ModuleDefinition> moduleTable,
-      Map<Class<NativeModule>, NativeModule> moduleInstances,
-      String moduleDescriptions) {
+      List<ModuleDefinition> moduleTable,
+      Map<Class<? extends NativeModule>, NativeModule> moduleInstances) {
     mModuleTable = moduleTable;
     mModuleInstances = moduleInstances;
-    mModuleDescriptions = moduleDescriptions;
 
-    mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleTable.size());
+    mBatchCompleteListenerModules = new ArrayList<>(mModuleTable.size());
+    mOnExecutorUnregisteredListenerModules = new ArrayList<>(mModuleTable.size());
     for (int i = 0; i < mModuleTable.size(); i++) {
       ModuleDefinition definition = mModuleTable.get(i);
       if (definition.target instanceof OnBatchCompleteListener) {
         mBatchCompleteListenerModules.add((OnBatchCompleteListener) definition.target);
       }
+      if (definition.target instanceof OnExecutorUnregisteredListener) {
+        mOnExecutorUnregisteredListenerModules.add((OnExecutorUnregisteredListener) definition.target);
+      }
     }
   }
 
   /* package */ void call(
       CatalystInstance catalystInstance,
+      ExecutorToken executorToken,
       int moduleId,
       int methodId,
       ReadableNativeArray parameters) {
@@ -61,11 +61,33 @@ private NativeModuleRegistry(
     if (definition == null) {
       throw new RuntimeException("Call to unknown module: " + moduleId);
     }
-    definition.call(catalystInstance, methodId, parameters);
+    definition.call(catalystInstance, executorToken, methodId, parameters);
   }
 
-  /* package */ String moduleDescriptions() {
-    return mModuleDescriptions;
+  /* package */ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateJSON");
+    try {
+      jg.writeStartObject();
+      for (ModuleDefinition moduleDef : mModuleTable) {
+        jg.writeObjectFieldStart(moduleDef.name);
+        jg.writeNumberField("moduleID", moduleDef.id);
+        jg.writeBooleanField("supportsWebWorkers", moduleDef.target.supportsWebWorkers());
+        jg.writeObjectFieldStart("methods");
+        for (int i = 0; i < moduleDef.methods.size(); i++) {
+          MethodRegistration method = moduleDef.methods.get(i);
+          jg.writeObjectFieldStart(method.name);
+          jg.writeNumberField("methodID", i);
+          jg.writeStringField("type", method.method.getType());
+          jg.writeEndObject();
+        }
+        jg.writeEndObject();
+        moduleDef.target.writeConstantsField(jg, "constants");
+        jg.writeEndObject();
+      }
+      jg.writeEndObject();
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
   }
 
   /* package */ void notifyCatalystInstanceDestroy() {
@@ -99,12 +121,31 @@ private NativeModuleRegistry(
     }
   }
 
+  /* package */ void notifyReactBridgeInitialized(ReactBridge bridge) {
+    Systrace.beginSection(
+        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+        "NativeModuleRegistry_notifyReactBridgeInitialized");
+    try {
+      for (NativeModule nativeModule : mModuleInstances.values()) {
+        nativeModule.onReactBridgeInitialized(bridge);
+      }
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+  }
+
   public void onBatchComplete() {
     for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) {
       mBatchCompleteListenerModules.get(i).onBatchComplete();
     }
   }
 
+  public void onExecutorUnregistered(ExecutorToken executorToken) {
+    for (int i = 0; i < mOnExecutorUnregisteredListenerModules.size(); i++) {
+      mOnExecutorUnregisteredListenerModules.get(i).onExecutorDestroyed(executorToken);
+    }
+  }
+
   public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
     return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
   }
@@ -135,12 +176,13 @@ public ModuleDefinition(int id, String name, NativeModule target) {
 
     public void call(
         CatalystInstance catalystInstance,
+        ExecutorToken executorToken,
         int methodId,
         ReadableNativeArray parameters) {
       MethodRegistration method = this.methods.get(methodId);
       Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName);
       try {
-        this.methods.get(methodId).method.invoke(catalystInstance, parameters);
+        this.methods.get(methodId).method.invoke(catalystInstance, executorToken, parameters);
       } finally {
         Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
       }
@@ -161,58 +203,31 @@ public MethodRegistration(String name, String tracingName, NativeModule.NativeMe
 
   public static class Builder {
 
-    private ArrayList<ModuleDefinition> mModuleDefinitions;
-    private Map<Class<NativeModule>, NativeModule> mModuleInstances;
-    private Set<String> mSeenModuleNames;
-
-    public Builder() {
-      mModuleDefinitions = new ArrayList<ModuleDefinition>();
-      mModuleInstances = MapBuilder.newHashMap();
-      mSeenModuleNames = SetBuilder.newHashSet();
-    }
+    private final HashMap<String, NativeModule> mModules = MapBuilder.newHashMap();
 
     public Builder add(NativeModule module) {
-      ModuleDefinition registration = new ModuleDefinition(
-          mModuleDefinitions.size(),
-          module.getName(),
-          module);
-      Assertions.assertCondition(
-          !mSeenModuleNames.contains(module.getName()),
-          "Module " + module.getName() + " was already registered!");
-      mSeenModuleNames.add(module.getName());
-      mModuleDefinitions.add(registration);
-      mModuleInstances.put((Class<NativeModule>) module.getClass(), module);
+      NativeModule existing = mModules.get(module.getName());
+      if (existing != null && !module.canOverrideExistingModule()) {
+        throw new IllegalStateException("Native module " + module.getClass().getSimpleName() +
+            " tried to override " + existing.getClass().getSimpleName() + " for module name " +
+            module.getName() + ". If this was your intention, return true from " +
+            module.getClass().getSimpleName() + "#canOverrideExistingModule()");
+      }
+      mModules.put(module.getName(), module);
       return this;
     }
 
     public NativeModuleRegistry build() {
-      JsonFactory jsonFactory = new JsonFactory();
-      StringWriter writer = new StringWriter();
-      try {
-        JsonGenerator jg = jsonFactory.createGenerator(writer);
-        jg.writeStartObject();
-        for (ModuleDefinition module : mModuleDefinitions) {
-          jg.writeObjectFieldStart(module.name);
-          jg.writeNumberField("moduleID", module.id);
-          jg.writeObjectFieldStart("methods");
-          for (int i = 0; i < module.methods.size(); i++) {
-            MethodRegistration method = module.methods.get(i);
-            jg.writeObjectFieldStart(method.name);
-            jg.writeNumberField("methodID", i);
-            jg.writeStringField("type", method.method.getType());
-            jg.writeEndObject();
-          }
-          jg.writeEndObject();
-          module.target.writeConstantsField(jg, "constants");
-          jg.writeEndObject();
-        }
-        jg.writeEndObject();
-        jg.close();
-      } catch (IOException ioe) {
-        throw new RuntimeException("Unable to serialize Java module configuration", ioe);
+      List<ModuleDefinition> moduleTable = new ArrayList<>();
+      Map<Class<? extends NativeModule>, NativeModule> moduleInstances = new HashMap<>();
+
+      int idx = 0;
+      for (NativeModule module : mModules.values()) {
+        ModuleDefinition moduleDef = new ModuleDefinition(idx++, module.getName(), module);
+        moduleTable.add(moduleDef);
+        moduleInstances.put(module.getClass(), module);
       }
-      String moduleDefinitionJson = writer.getBuffer().toString();
-      return new NativeModuleRegistry(mModuleDefinitions, mModuleInstances, moduleDefinitionJson);
+      return new NativeModuleRegistry(moduleTable, moduleInstances);
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/OnExecutorUnregisteredListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/OnExecutorUnregisteredListener.java
new file mode 100644
index 000000000000..d7cbe6cee334
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/OnExecutorUnregisteredListener.java
@@ -0,0 +1,22 @@
+/**
+ * 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.bridge;
+
+/**
+ * Interface for a module that will be notified when JS executors have been unregistered from the bridge.
+ * Note that this will NOT notify listeners about the main executor being destroyed: use
+ * {@link NativeModule#onCatalystInstanceDestroy()} for that. Once a module has received a
+ * {@link NativeModule#onCatalystInstanceDestroy()} call, it will not receive any onExecutorUnregistered
+ * calls.
+ */
+public interface OnExecutorUnregisteredListener {
+
+  void onExecutorDestroyed(ExecutorToken executorToken);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java
index c19907af83cd..db192da980fa 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java
@@ -9,6 +9,8 @@
 
 package com.facebook.react.bridge;
 
+import javax.annotation.Nullable;
+
 /**
  * Interface that represents a JavaScript Promise which can be passed to the native module as a
  * method parameter.
@@ -17,6 +19,38 @@
  * will be marked as "remoteAsync" and will return a promise when invoked from JavaScript.
  */
 public interface Promise {
-  void resolve(Object value);
-  void reject(String reason);
+
+  /**
+   * Successfully resolve the Promise.
+   */
+  void resolve(@Nullable Object value);
+
+  /**
+   * Report an error which wasn't caused by an exception.
+   */
+  void reject(String code, String message);
+
+  /**
+   * Report an exception.
+   */
+  void reject(String code, Throwable e);
+
+  /**
+   * Report an exception with a custom error message.
+   */
+  void reject(String code, String message, Throwable e);
+
+  /**
+   * Report an error which wasn't caused by an exception.
+   * @deprecated Prefer passing a module-specific error code to JS.
+   *             Using this method will pass the error code "EUNSPECIFIED".
+   */
+  @Deprecated
+  void reject(String message);
+
+  /**
+   * Report an exception, with default error code.
+   * Useful in catch-all scenarios where it's unclear why the error occurred.
+   */
+  void reject(Throwable reason);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java
index 688b081028e8..519448f6d9ed 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java
@@ -15,6 +15,9 @@
 import javax.annotation.Nullable;
 
 public class PromiseImpl implements Promise {
+
+  private static final String DEFAULT_ERROR = "EUNSPECIFIED";
+
   private @Nullable Callback mResolve;
   private @Nullable Callback mReject;
 
@@ -31,14 +34,39 @@ public void resolve(Object value) {
   }
 
   @Override
-  public void reject(String reason) {
+  public void reject(String code, String message) {
+    reject(code, message, /*Throwable*/null);
+  }
+
+  @Override
+  @Deprecated
+  public void reject(String message) {
+    reject(DEFAULT_ERROR, message, /*Throwable*/null);
+  }
+
+  @Override
+  public void reject(String code, Throwable e) {
+    reject(code, e.getMessage(), e);
+  }
+
+  @Override
+  public void reject(Throwable e) {
+    reject(DEFAULT_ERROR, e.getMessage(), e);
+  }
+
+  @Override
+  public void reject(String code, String message, @Nullable Throwable e) {
     if (mReject != null) {
+      if (code == null) {
+        code = DEFAULT_ERROR;
+      }
       // The JavaScript side expects a map with at least the error message.
       // It is possible to expose all kind of information. It will be available on the JS
       // error instance.
-      // TODO(8850038): add more useful information, e.g. the native stack trace.
       WritableNativeMap errorInfo = new WritableNativeMap();
-      errorInfo.putString("message", reason);
+      errorInfo.putString("code", code);
+      errorInfo.putString("message", message);
+      // TODO(8850038): add the stack trace info in, need to figure out way to serialize that
       mReject.invoke(errorInfo);
     }
   }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
index 08447802bf0b..37d83b338617 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
@@ -24,51 +24,21 @@
  */
 @DoNotStrip
 public class ProxyJavaScriptExecutor extends JavaScriptExecutor {
+  public static class Factory implements JavaScriptExecutor.Factory {
+    private final JavaJSExecutor.Factory mJavaJSExecutorFactory;
 
-  static {
-    SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
-  }
+    public Factory(JavaJSExecutor.Factory javaJSExecutorFactory) {
+      mJavaJSExecutorFactory = javaJSExecutorFactory;
+    }
 
-  public static class ProxyExecutorException extends Exception {
-    public ProxyExecutorException(Throwable cause) {
-      super(cause);
+    @Override
+    public JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception {
+      return new ProxyJavaScriptExecutor(mJavaJSExecutorFactory.create());
     }
   }
 
-  /**
-   * This is class represents java version of native js executor interface. When set through
-   * {@link ProxyJavaScriptExecutor} as a {@link CatalystInstance} executor, native code will
-   * delegate js calls to the given implementation of this interface.
-   */
-  @DoNotStrip
-  public interface JavaJSExecutor {
-    /**
-     * Close this executor and cleanup any resources that it was using. No further calls are
-     * expected after this.
-     */
-    void close();
-
-    /**
-     * Load javascript into the js context
-     * @param script script contet to be executed
-     * @param sourceURL url or file location from which script content was loaded
-     */
-    @DoNotStrip
-    void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
-
-    /**
-     * Execute javascript method within js context
-     * @param modulename name of the common-js like module to execute the method from
-     * @param methodName name of the method to be executed
-     * @param jsonArgsArray json encoded array of arguments provided for the method call
-     * @return json encoded value returned from the method call
-     */
-    @DoNotStrip
-    String executeJSCall(String modulename, String methodName, String jsonArgsArray)
-        throws ProxyExecutorException;
-
-    @DoNotStrip
-    void setGlobalVariable(String propertyName, String jsonEncodedValue);
+  static {
+    SoLoader.loadLibrary(ReactBridge.REACT_NATIVE_LIB);
   }
 
   private @Nullable JavaJSExecutor mJavaJSExecutor;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
index d84c7b844917..3c8128244f4b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
@@ -56,6 +56,19 @@ public void dispose() {
     super.dispose();
   }
 
+  public void handleMemoryPressure(MemoryPressure level) {
+    switch (level) {
+      case MODERATE:
+        handleMemoryPressureModerate();
+        break;
+      case CRITICAL:
+        handleMemoryPressureCritical();
+        break;
+      default:
+        throw new IllegalArgumentException("Unknown level: " + level);
+    }
+  }
+
   private native void initialize(
       JavaScriptExecutor jsExecutor,
       ReactCallback callback,
@@ -66,10 +79,23 @@ private native void initialize(
    */
   public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
   public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
-  public native void callFunction(int moduleId, int methodId, NativeArray arguments);
-  public native void invokeCallback(int callbackID, NativeArray arguments);
+  public native void callFunction(ExecutorToken executorToken, int moduleId, int methodId, NativeArray arguments, String tracingName);
+  public native void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
   public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
   public native boolean supportsProfiling();
   public native void startProfiler(String title);
   public native void stopProfiler(String title, String filename);
+  public native ExecutorToken getMainExecutorToken();
+  private native void handleMemoryPressureModerate();
+  private native void handleMemoryPressureCritical();
+  public native void destroy();
+
+  /**
+   * This method will return a long representing the underlying JSGlobalContextRef pointer or
+   * 0 (representing NULL) when in Chrome debug mode, and is only useful if passed back through
+   * the JNI to native code that will use it with the JavaScriptCore C API.
+   * **WARNING:** This method is *experimental* and should only be used when no other option is
+   * available. It will likely change in a future release!
+   */
+  public native long getJavaScriptContextNativePtrExperimental();
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactCallback.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactCallback.java
index 7e4376c56851..3e5c36be13f0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactCallback.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactCallback.java
@@ -15,8 +15,11 @@
 public interface ReactCallback {
 
   @DoNotStrip
-  void call(int moduleId, int methodId, ReadableNativeArray parameters);
+  void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters);
 
   @DoNotStrip
   void onBatchComplete();
+
+  @DoNotStrip
+  void onExecutorUnregistered(ExecutorToken executorToken);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
index ee669462ad35..8e7880b594e2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
@@ -21,7 +21,7 @@
 import android.view.LayoutInflater;
 
 import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.bridge.queue.CatalystQueueConfiguration;
+import com.facebook.react.bridge.queue.ReactQueueConfiguration;
 import com.facebook.react.bridge.queue.MessageQueueThread;
 
 /**
@@ -60,7 +60,7 @@ public void initializeWithInstance(CatalystInstance catalystInstance) {
 
     mCatalystInstance = catalystInstance;
 
-    CatalystQueueConfiguration queueConfig = catalystInstance.getCatalystQueueConfiguration();
+    ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration();
     mUiMessageQueueThread = queueConfig.getUIQueueThread();
     mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread();
     mJSMessageQueueThread = queueConfig.getJSQueueThread();
@@ -96,6 +96,13 @@ public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
     return mCatalystInstance.getJSModule(jsInterface);
   }
 
+  public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
+    if (mCatalystInstance == null) {
+      throw new RuntimeException("Trying to invoke JS before CatalystInstance has been set!");
+    }
+    return mCatalystInstance.getJSModule(executorToken, jsInterface);
+  }
+
   /**
    * @return the instance of the specified module interface associated with this ReactContext.
    */
@@ -133,7 +140,7 @@ public void removeActivityEventListener(ActivityEventListener listener) {
   /**
    * Should be called by the hosting Fragment in {@link Fragment#onResume}
    */
-  public void onResume(@Nullable Activity activity) {
+  public void onHostResume(@Nullable Activity activity) {
     UiThreadUtil.assertOnUiThread();
     mCurrentActivity = activity;
     for (LifecycleEventListener listener : mLifecycleEventListeners) {
@@ -144,9 +151,8 @@ public void onResume(@Nullable Activity activity) {
   /**
    * Should be called by the hosting Fragment in {@link Fragment#onPause}
    */
-  public void onPause() {
+  public void onHostPause() {
     UiThreadUtil.assertOnUiThread();
-    mCurrentActivity = null;
     for (LifecycleEventListener listener : mLifecycleEventListeners) {
       listener.onHostPause();
     }
@@ -155,11 +161,20 @@ public void onPause() {
   /**
    * Should be called by the hosting Fragment in {@link Fragment#onDestroy}
    */
-  public void onDestroy() {
+  public void onHostDestroy() {
     UiThreadUtil.assertOnUiThread();
     for (LifecycleEventListener listener : mLifecycleEventListeners) {
       listener.onHostDestroy();
     }
+    mCurrentActivity = null;
+  }
+
+  /**
+   * Destroy this instance, making it unusable.
+   */
+  public void destroy() {
+    UiThreadUtil.assertOnUiThread();
+
     if (mCatalystInstance != null) {
       mCatalystInstance.destroy();
     }
@@ -225,16 +240,27 @@ public void handleException(RuntimeException e) {
     }
   }
 
+  public boolean hasCurrentActivity() {
+    return mCurrentActivity != null;
+  }
+
   /**
    * Same as {@link Activity#startActivityForResult(Intent, int)}, this just redirects the call to
    * the current activity. Returns whether the activity was started, as this might fail if this
    * was called before the context is in the right state.
    */
   public boolean startActivityForResult(Intent intent, int code, Bundle bundle) {
-    if (mCurrentActivity == null) {
-      return false;
-    }
+    Assertions.assertNotNull(mCurrentActivity);
     mCurrentActivity.startActivityForResult(intent, code, bundle);
     return true;
   }
+
+  /**
+   * Get the activity to which this context is currently attached, or {@code null} if not attached.
+   * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
+   * MEMORY LEAKS.
+   */
+  /* package */ @Nullable Activity getCurrentActivity() {
+    return mCurrentActivity;
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java
index 4d0470f46b86..6a73f73ccb25 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContextBaseJavaModule.java
@@ -9,6 +9,10 @@
 
 package com.facebook.react.bridge;
 
+import javax.annotation.Nullable;
+
+import android.app.Activity;
+
 /**
  * Base class for Catalyst native modules that require access to the {@link ReactContext}
  * instance.
@@ -27,4 +31,17 @@ public ReactContextBaseJavaModule(ReactApplicationContext reactContext) {
   protected final ReactApplicationContext getReactApplicationContext() {
     return mReactApplicationContext;
   }
+
+  /**
+   * Get the activity to which this context is currently attached, or {@code null} if not attached.
+   *
+   * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
+   * MEMORY LEAKS.
+   *
+   * For example, never store the value returned by this method in a member variable. Instead, call
+   * this method whenever you actually need the Activity and make sure to check for {@code null}.
+   */
+  protected @Nullable final Activity getCurrentActivity() {
+    return mReactApplicationContext.getCurrentActivity();
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java
new file mode 100644
index 000000000000..fb920b9ac0fa
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactMarkerConstants.java
@@ -0,0 +1,24 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.bridge;
+
+/**
+ * Constants used by ReactMarker.
+ */
+public class ReactMarkerConstants {
+
+  public static final String CREATE_REACT_CONTEXT_START = "CREATE_REACT_CONTEXT_START";
+  public static final String CREATE_REACT_CONTEXT_END = "CREATE_REACT_CONTEXT_END";
+  public static final String PROCESS_PACKAGES_START = "PROCESS_PACKAGES_START";
+  public static final String PROCESS_PACKAGES_END = "PROCESS_PACKAGES_END";
+  public static final String BUILD_NATIVE_MODULE_REGISTRY_START =
+      "BUILD_NATIVE_MODULE_REGISTRY_START";
+  public static final String BUILD_NATIVE_MODULE_REGISTRY_END =
+      "BUILD_NATIVE_MODULE_REGISTRY_END";
+  public static final String BUILD_JS_MODULE_CONFIG_START = "BUILD_JS_MODULE_CONFIG_START";
+  public static final String BUILD_JS_MODULE_CONFIG_END = "BUILD_JS_MODULE_CONFIG_END";
+  public static final String CREATE_CATALYST_INSTANCE_START = "CREATE_CATALYST_INSTANCE_START";
+  public static final String CREATE_CATALYST_INSTANCE_END = "CREATE_CATALYST_INSTANCE_END";
+  public static final String RUN_JS_BUNDLE_START = "RUN_JS_BUNDLE_START";
+  public static final String RUN_JS_BUNDLE_END = "RUN_JS_BUNDLE_END";
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/SupportsWebWorkers.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/SupportsWebWorkers.java
new file mode 100644
index 000000000000..ad3dc87f4b2b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/SupportsWebWorkers.java
@@ -0,0 +1,15 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.bridge;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation indicating that a JS module should be made available to web
+ * workers spawned by the main JS executor.
+ */
+@Retention(RUNTIME)
+public @interface SupportsWebWorkers {
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java
similarity index 66%
rename from ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java
rename to ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java
index 50a3a72b984f..9f571e8651fa 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeProfiling.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Systrace.java
@@ -5,10 +5,10 @@
 import com.facebook.proguard.annotations.DoNotStrip;
 
 /**
- * Interface to the JavaScript BridgeProfiling Module
+ * Interface to the JavaScript Systrace Module
  */
 @DoNotStrip
-public interface BridgeProfiling extends JavaScriptModule{
+public interface Systrace extends JavaScriptModule{
   @DoNotStrip
   void setEnabled(boolean enabled);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
index 4557d9fe6747..b9e1da2adbf0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
@@ -13,17 +13,17 @@
 
 import java.util.HashMap;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import android.os.Handler;
+import android.os.Looper;
 
 import com.facebook.infer.annotation.Assertions;
 
 /**
  * Executes JS remotely via the react nodejs server as a proxy to a browser on the host machine.
  */
-public class WebsocketJavaScriptExecutor implements ProxyJavaScriptExecutor.JavaJSExecutor {
+public class WebsocketJavaScriptExecutor implements JavaJSExecutor {
 
   private static final long CONNECT_TIMEOUT_MS = 5000;
   private static final int CONNECT_RETRY_COUNT = 3;
@@ -97,9 +97,13 @@ private void connectInternal(
       String webSocketServerUrl,
       final JSExecutorConnectCallback callback) {
     final JSDebuggerWebSocketClient client = new JSDebuggerWebSocketClient();
-    final Handler timeoutHandler = new Handler();
+    final Handler timeoutHandler = new Handler(Looper.getMainLooper());
     client.connect(
         webSocketServerUrl, new JSDebuggerWebSocketClient.JSDebuggerCallback() {
+          // It's possible that both callbacks can fire on an error so make sure we only
+          // dispatch results once to our callback.
+          private boolean didSendResult = false;
+
           @Override
           public void onSuccess(@Nullable String response) {
             client.prepareJSRuntime(
@@ -108,20 +112,30 @@ public void onSuccess(@Nullable String response) {
                   public void onSuccess(@Nullable String response) {
                     timeoutHandler.removeCallbacksAndMessages(null);
                     mWebSocketClient = client;
-                    callback.onSuccess();
+                    if (!didSendResult) {
+                      callback.onSuccess();
+                      didSendResult = true;
+                    }
                   }
 
                   @Override
                   public void onFailure(Throwable cause) {
                     timeoutHandler.removeCallbacksAndMessages(null);
-                    callback.onFailure(cause);
+                    if (!didSendResult) {
+                      callback.onFailure(cause);
+                      didSendResult = true;
+                    }
                   }
                 });
           }
 
           @Override
           public void onFailure(Throwable cause) {
-            callback.onFailure(cause);
+            timeoutHandler.removeCallbacksAndMessages(null);
+            if (!didSendResult) {
+              callback.onFailure(cause);
+              didSendResult = true;
+            }
           }
         });
     timeoutHandler.postDelayed(
@@ -145,39 +159,38 @@ public void close() {
   }
 
   @Override
-  public void executeApplicationScript(String script, String sourceURL)
-      throws ProxyJavaScriptExecutor.ProxyExecutorException {
+  public void loadApplicationScript(String script, String sourceURL)
+      throws ProxyExecutorException {
     JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
-    Assertions.assertNotNull(mWebSocketClient).executeApplicationScript(
+    Assertions.assertNotNull(mWebSocketClient).loadApplicationScript(
         sourceURL,
         mInjectedObjects,
         callback);
     try {
       callback.get();
     } catch (Throwable cause) {
-      throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
+      throw new ProxyExecutorException(cause);
     }
   }
 
   @Override
-  public @Nullable String executeJSCall(String moduleName, String methodName, String jsonArgsArray)
-      throws ProxyJavaScriptExecutor.ProxyExecutorException {
+  public @Nullable String executeJSCall(String methodName, String jsonArgsArray)
+      throws ProxyExecutorException {
     JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
     Assertions.assertNotNull(mWebSocketClient).executeJSCall(
-        moduleName,
         methodName,
         jsonArgsArray,
         callback);
     try {
       return callback.get();
     } catch (Throwable cause) {
-      throw new ProxyJavaScriptExecutor.ProxyExecutorException(cause);
+      throw new ProxyExecutorException(cause);
     }
   }
 
   @Override
   public void setGlobalVariable(String propertyName, String jsonEncodedValue) {
-    // Store and use in the next executeApplicationScript() call.
+    // Store and use in the next loadApplicationScript() call.
     mInjectedObjects.put(propertyName, jsonEncodedValue);
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java
index 0090b82ad0bf..84cc9accb2ba 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThread.java
@@ -9,136 +9,48 @@
 
 package com.facebook.react.bridge.queue;
 
-import android.os.Looper;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
 
-import com.facebook.common.logging.FLog;
 import com.facebook.proguard.annotations.DoNotStrip;
-import com.facebook.react.bridge.AssertionException;
-import com.facebook.react.bridge.SoftAssertions;
-import com.facebook.react.common.ReactConstants;
-import com.facebook.react.common.futures.SimpleSettableFuture;
 
 /**
- * Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables.
+ * Encapsulates a Thread that can accept Runnables.
  */
 @DoNotStrip
-public class MessageQueueThread {
-
-  private final String mName;
-  private final Looper mLooper;
-  private final MessageQueueThreadHandler mHandler;
-  private final String mAssertionErrorMessage;
-  private volatile boolean mIsFinished = false;
-
-  private MessageQueueThread(
-      String name,
-      Looper looper,
-      QueueThreadExceptionHandler exceptionHandler) {
-    mName = name;
-    mLooper = looper;
-    mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
-    mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
-  }
-
+public interface MessageQueueThread {
   /**
    * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
    * if it is being submitted from the same queue Thread.
    */
   @DoNotStrip
-  public void runOnQueue(Runnable runnable) {
-    if (mIsFinished) {
-      FLog.w(
-          ReactConstants.TAG,
-          "Tried to enqueue runnable on already finished thread: '" + getName() +
-              "... dropping Runnable.");
-    }
-    mHandler.post(runnable);
-  }
-
-  /**
-   * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
-   */
-  public boolean isOnThread() {
-    return mLooper.getThread() == Thread.currentThread();
-  }
+  void runOnQueue(Runnable runnable);
 
   /**
-   * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
-   * {@link AssertionError}) if the assertion fails.
+   * Runs the given Callable on this Thread. It will be submitted to the end of the event queue even
+   * if it is being submitted from the same queue Thread.
    */
-  public void assertIsOnThread() {
-    SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
-  }
+  @DoNotStrip
+  <T> Future<T> callOnQueue(final Callable<T> callable);
 
   /**
-   * Quits this queue's Looper. If that Looper was running on a different Thread than the current
-   * Thread, also waits for the last message being processed to finish and the Thread to die.
+   * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
    */
-  public void quitSynchronous() {
-    mIsFinished = true;
-    mLooper.quit();
-    if (mLooper.getThread() != Thread.currentThread()) {
-      try {
-        mLooper.getThread().join();
-      } catch (InterruptedException e) {
-        throw new RuntimeException("Got interrupted waiting to join thread " + mName);
-      }
-    }
-  }
-
-  public Looper getLooper() {
-    return mLooper;
-  }
-
-  public String getName() {
-    return mName;
-  }
-
-  public static MessageQueueThread create(
-      MessageQueueThreadSpec spec,
-      QueueThreadExceptionHandler exceptionHandler) {
-    switch (spec.getThreadType()) {
-      case MAIN_UI:
-        return createForMainThread(spec.getName(), exceptionHandler);
-      case NEW_BACKGROUND:
-        return startNewBackgroundThread(spec.getName(), exceptionHandler);
-      default:
-        throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
-    }
-  }
+  @DoNotStrip
+  boolean isOnThread();
 
   /**
-   * @return a MessageQueueThread corresponding to Android's main UI thread.
+   * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
+   * {@link AssertionError}) if the assertion fails.
    */
-  private static MessageQueueThread createForMainThread(
-      String name,
-      QueueThreadExceptionHandler exceptionHandler) {
-    Looper mainLooper = Looper.getMainLooper();
-    return new MessageQueueThread(name, mainLooper, exceptionHandler);
-  }
+  @DoNotStrip
+  void assertIsOnThread();
 
   /**
-   * Creates  and starts a new MessageQueueThread encapsulating a new Thread with a new Looper
-   * running on it. Give it a name for easier debugging. When this method exits, the new
-   * MessageQueueThread is ready to receive events.
+   * Quits this MessageQueueThread. If called from this MessageQueueThread, this will be the last
+   * thing the thread runs. If called from a separate thread, this will block until the thread can
+   * be quit and joined.
    */
-  private static MessageQueueThread startNewBackgroundThread(
-      String name,
-      QueueThreadExceptionHandler exceptionHandler) {
-    final SimpleSettableFuture<Looper> simpleSettableFuture = new SimpleSettableFuture<>();
-    Thread bgThread = new Thread(
-        new Runnable() {
-          @Override
-          public void run() {
-            Looper.prepare();
-
-            simpleSettableFuture.set(Looper.myLooper());
-
-            Looper.loop();
-          }
-        }, "mqt_" + name);
-    bgThread.start();
-
-    return new MessageQueueThread(name, simpleSettableFuture.get(5000), exceptionHandler);
-  }
+  @DoNotStrip
+  void quitSynchronous();
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
new file mode 100644
index 000000000000..5593cb02f7aa
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
@@ -0,0 +1,198 @@
+/**
+ * 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.bridge.queue;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+import android.os.Looper;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.AssertionException;
+import com.facebook.react.bridge.SoftAssertions;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.futures.SimpleSettableFuture;
+
+/**
+ * Encapsulates a Thread that has a {@link Looper} running on it that can accept Runnables.
+ */
+@DoNotStrip
+public class MessageQueueThreadImpl implements MessageQueueThread {
+
+  private final String mName;
+  private final Looper mLooper;
+  private final MessageQueueThreadHandler mHandler;
+  private final String mAssertionErrorMessage;
+  private volatile boolean mIsFinished = false;
+
+  private MessageQueueThreadImpl(
+      String name,
+      Looper looper,
+      QueueThreadExceptionHandler exceptionHandler) {
+    mName = name;
+    mLooper = looper;
+    mHandler = new MessageQueueThreadHandler(looper, exceptionHandler);
+    mAssertionErrorMessage = "Expected to be called from the '" + getName() + "' thread!";
+  }
+
+  /**
+   * Runs the given Runnable on this Thread. It will be submitted to the end of the event queue even
+   * if it is being submitted from the same queue Thread.
+   */
+  @DoNotStrip
+  @Override
+  public void runOnQueue(Runnable runnable) {
+    if (mIsFinished) {
+      FLog.w(
+          ReactConstants.TAG,
+          "Tried to enqueue runnable on already finished thread: '" + getName() +
+              "... dropping Runnable.");
+    }
+    mHandler.post(runnable);
+  }
+
+
+  @DoNotStrip
+  @Override
+  public <T> Future<T> callOnQueue(final Callable<T> callable) {
+    final SimpleSettableFuture<T> future = new SimpleSettableFuture<>();
+    runOnQueue(
+        new Runnable() {
+          @Override
+          public void run() {
+            try {
+              future.set(callable.call());
+            } catch (Exception e) {
+              future.setException(e);
+            }
+          }
+        });
+    return future;
+  }
+
+  /**
+   * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
+   */
+  @DoNotStrip
+  @Override
+  public boolean isOnThread() {
+    return mLooper.getThread() == Thread.currentThread();
+  }
+
+  /**
+   * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
+   * {@link AssertionError}) if the assertion fails.
+   */
+  @DoNotStrip
+  @Override
+  public void assertIsOnThread() {
+    SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
+  }
+
+  /**
+   * Quits this queue's Looper. If that Looper was running on a different Thread than the current
+   * Thread, also waits for the last message being processed to finish and the Thread to die.
+   */
+  @DoNotStrip
+  @Override
+  public void quitSynchronous() {
+    mIsFinished = true;
+    mLooper.quit();
+    if (mLooper.getThread() != Thread.currentThread()) {
+      try {
+        mLooper.getThread().join();
+      } catch (InterruptedException e) {
+        throw new RuntimeException("Got interrupted waiting to join thread " + mName);
+      }
+    }
+  }
+
+  public Looper getLooper() {
+    return mLooper;
+  }
+
+  public String getName() {
+    return mName;
+  }
+
+  public static MessageQueueThreadImpl create(
+      MessageQueueThreadSpec spec,
+      QueueThreadExceptionHandler exceptionHandler) {
+    switch (spec.getThreadType()) {
+      case MAIN_UI:
+        return createForMainThread(spec.getName(), exceptionHandler);
+      case NEW_BACKGROUND:
+        return startNewBackgroundThread(spec.getName(), exceptionHandler);
+      default:
+        throw new RuntimeException("Unknown thread type: " + spec.getThreadType());
+    }
+  }
+
+  /**
+   * @return a MessageQueueThreadImpl corresponding to Android's main UI thread.
+   */
+  private static MessageQueueThreadImpl createForMainThread(
+      String name,
+      QueueThreadExceptionHandler exceptionHandler) {
+    Looper mainLooper = Looper.getMainLooper();
+    final MessageQueueThreadImpl mqt =
+        new MessageQueueThreadImpl(name, mainLooper, exceptionHandler);
+
+    // Ensure that the MQT is registered by the time this method returns
+    if (UiThreadUtil.isOnUiThread()) {
+      MessageQueueThreadRegistry.register(mqt);
+    } else {
+      final SimpleSettableFuture<Void> registrationFuture = new SimpleSettableFuture<>();
+      UiThreadUtil.runOnUiThread(
+          new Runnable() {
+            @Override
+            public void run() {
+              MessageQueueThreadRegistry.register(mqt);
+              registrationFuture.set(null);
+            }
+          });
+      registrationFuture.getOrThrow();
+    }
+    return mqt;
+  }
+
+  /**
+   * Creates  and starts a new MessageQueueThreadImpl encapsulating a new Thread with a new Looper
+   * running on it. Give it a name for easier debugging. When this method exits, the new
+   * MessageQueueThreadImpl is ready to receive events.
+   */
+  public static MessageQueueThreadImpl startNewBackgroundThread(
+      final String name,
+      QueueThreadExceptionHandler exceptionHandler) {
+    final SimpleSettableFuture<Looper> looperFuture = new SimpleSettableFuture<>();
+    final SimpleSettableFuture<MessageQueueThread> mqtFuture = new SimpleSettableFuture<>();
+    Thread bgThread = new Thread(
+        new Runnable() {
+          @Override
+          public void run() {
+            Looper.prepare();
+
+            looperFuture.set(Looper.myLooper());
+            MessageQueueThreadRegistry.register(mqtFuture.getOrThrow());
+
+            Looper.loop();
+          }
+        }, "mqt_" + name);
+    bgThread.start();
+
+    Looper myLooper = looperFuture.getOrThrow();
+    MessageQueueThreadImpl mqt = new MessageQueueThreadImpl(name, myLooper, exceptionHandler);
+    mqtFuture.set(mqt);
+
+    return mqt;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java
new file mode 100644
index 000000000000..fd0602a93751
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadRegistry.java
@@ -0,0 +1,39 @@
+/**
+ * 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.bridge.queue;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.proguard.annotations.DoNotStrip;
+
+/**
+ * A utility for getting the MessageQueueThread for the current thread. Once there is only one
+ * implementation of MessageQueueThread, this should move to that class.
+ */
+@DoNotStrip
+public class MessageQueueThreadRegistry {
+
+  private static ThreadLocal<MessageQueueThread> sMyMessageQueueThread = new ThreadLocal<>();
+
+  /*package*/ static void register(MessageQueueThread mqt) {
+    mqt.assertIsOnThread();
+    sMyMessageQueueThread.set(mqt);
+  }
+
+  /**
+   * @return the MessageQueueThread that owns the current Thread.
+   */
+  @DoNotStrip
+  public static MessageQueueThread myMessageQueueThread() {
+    return Assertions.assertNotNull(
+        sMyMessageQueueThread.get(),
+        "This thread doesn't have a MessageQueueThread registered to it!");
+  }
+}
+
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnable.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnable.java
index 23eb266d4381..d724405a99cb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnable.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnable.java
@@ -9,20 +9,19 @@
 
 package com.facebook.react.bridge.queue;
 
-import com.facebook.jni.Countable;
+import com.facebook.jni.HybridData;
 import com.facebook.proguard.annotations.DoNotStrip;
 
 /**
  * A Runnable that has a native run implementation.
  */
 @DoNotStrip
-public class NativeRunnable extends Countable implements Runnable {
+public class NativeRunnable implements Runnable {
 
-  /**
-   * Should only be instantiated via native (JNI) code.
-   */
-  @DoNotStrip
-  private NativeRunnable() {
+  private final HybridData mHybridData;
+
+  private NativeRunnable(HybridData hybridData) {
+    mHybridData = hybridData;
   }
 
   public native void run();
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnableDeprecated.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnableDeprecated.java
new file mode 100644
index 000000000000..9e77c7eb88c7
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/NativeRunnableDeprecated.java
@@ -0,0 +1,27 @@
+/**
+ * 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.bridge.queue;
+
+import com.facebook.jni.Countable;
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+
+/**
+ * A Runnable that has a native run implementation.
+ */
+@DoNotStrip
+public class NativeRunnableDeprecated extends Countable implements Runnable {
+
+  @DoNotStrip
+  private NativeRunnableDeprecated() {
+  }
+
+  public native void run();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ProxyQueueThreadExceptionHandler.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ProxyQueueThreadExceptionHandler.java
new file mode 100644
index 000000000000..1be2d8a3c3c8
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ProxyQueueThreadExceptionHandler.java
@@ -0,0 +1,34 @@
+/**
+ * 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.bridge.queue;
+
+/**
+ * An Exception handler that posts the Exception to be thrown on the given delegate
+ * MessageQueueThread.
+ */
+public class ProxyQueueThreadExceptionHandler implements QueueThreadExceptionHandler {
+
+  private final MessageQueueThread mDelegateThread;
+
+  public ProxyQueueThreadExceptionHandler(MessageQueueThread delegateThread) {
+    mDelegateThread = delegateThread;
+  }
+
+  @Override
+  public void handleException(final Exception e) {
+    mDelegateThread.runOnQueue(
+        new Runnable() {
+          @Override
+          public void run() {
+            throw new RuntimeException(e);
+          }
+        });
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfiguration.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfiguration.java
new file mode 100644
index 000000000000..6e40d4ba5c88
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfiguration.java
@@ -0,0 +1,25 @@
+/**
+ * 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.bridge.queue;
+
+/**
+ * Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
+ * execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
+ * the same but should be coded against as if they are different.
+ *
+ * UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
+ * Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
+ * JS Queue Thread: The thread and Looper that JS is executed on.
+ */
+public interface ReactQueueConfiguration {
+  MessageQueueThread getUIQueueThread();
+  MessageQueueThread getNativeModulesQueueThread();
+  MessageQueueThread getJSQueueThread();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationImpl.java
similarity index 52%
rename from ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java
rename to ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationImpl.java
index 10be2a44df10..00af88372efc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfiguration.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationImpl.java
@@ -15,38 +15,32 @@
 
 import com.facebook.react.common.MapBuilder;
 
-/**
- * Specifies which {@link MessageQueueThread}s must be used to run the various contexts of
- * execution within catalyst (Main UI thread, native modules, and JS). Some of these queues *may* be
- * the same but should be coded against as if they are different.
- *
- * UI Queue Thread: The standard Android main UI thread and Looper. Not configurable.
- * Native Modules Queue Thread: The thread and Looper that native modules are invoked on.
- * JS Queue Thread: The thread and Looper that JS is executed on.
- */
-public class CatalystQueueConfiguration {
+public class ReactQueueConfigurationImpl implements ReactQueueConfiguration {
 
-  private final MessageQueueThread mUIQueueThread;
-  private final MessageQueueThread mNativeModulesQueueThread;
-  private final MessageQueueThread mJSQueueThread;
+  private final MessageQueueThreadImpl mUIQueueThread;
+  private final MessageQueueThreadImpl mNativeModulesQueueThread;
+  private final MessageQueueThreadImpl mJSQueueThread;
 
-  private CatalystQueueConfiguration(
-      MessageQueueThread uiQueueThread,
-      MessageQueueThread nativeModulesQueueThread,
-      MessageQueueThread jsQueueThread) {
+  private ReactQueueConfigurationImpl(
+      MessageQueueThreadImpl uiQueueThread,
+      MessageQueueThreadImpl nativeModulesQueueThread,
+      MessageQueueThreadImpl jsQueueThread) {
     mUIQueueThread = uiQueueThread;
     mNativeModulesQueueThread = nativeModulesQueueThread;
     mJSQueueThread = jsQueueThread;
   }
 
+  @Override
   public MessageQueueThread getUIQueueThread() {
     return mUIQueueThread;
   }
 
+  @Override
   public MessageQueueThread getNativeModulesQueueThread() {
     return mNativeModulesQueueThread;
   }
 
+  @Override
   public MessageQueueThread getJSQueueThread() {
     return mJSQueueThread;
   }
@@ -64,27 +58,28 @@ public void destroy() {
     }
   }
 
-  public static CatalystQueueConfiguration create(
-      CatalystQueueConfigurationSpec spec,
+  public static ReactQueueConfigurationImpl create(
+      ReactQueueConfigurationSpec spec,
       QueueThreadExceptionHandler exceptionHandler) {
-    Map<MessageQueueThreadSpec, MessageQueueThread> specsToThreads = MapBuilder.newHashMap();
+    Map<MessageQueueThreadSpec, MessageQueueThreadImpl> specsToThreads = MapBuilder.newHashMap();
 
     MessageQueueThreadSpec uiThreadSpec = MessageQueueThreadSpec.mainThreadSpec();
-    MessageQueueThread uiThread = MessageQueueThread.create( uiThreadSpec, exceptionHandler);
+    MessageQueueThreadImpl uiThread =
+      MessageQueueThreadImpl.create( uiThreadSpec, exceptionHandler);
     specsToThreads.put(uiThreadSpec, uiThread);
 
-    MessageQueueThread jsThread = specsToThreads.get(spec.getJSQueueThreadSpec());
+    MessageQueueThreadImpl jsThread = specsToThreads.get(spec.getJSQueueThreadSpec());
     if (jsThread == null) {
-      jsThread = MessageQueueThread.create(spec.getJSQueueThreadSpec(), exceptionHandler);
+      jsThread = MessageQueueThreadImpl.create(spec.getJSQueueThreadSpec(), exceptionHandler);
     }
 
-    MessageQueueThread nativeModulesThread =
+    MessageQueueThreadImpl nativeModulesThread =
         specsToThreads.get(spec.getNativeModulesQueueThreadSpec());
     if (nativeModulesThread == null) {
       nativeModulesThread =
-          MessageQueueThread.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
+          MessageQueueThreadImpl.create(spec.getNativeModulesQueueThreadSpec(), exceptionHandler);
     }
 
-    return new CatalystQueueConfiguration(uiThread, nativeModulesThread, jsThread);
+    return new ReactQueueConfigurationImpl(uiThread, nativeModulesThread, jsThread);
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationSpec.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationSpec.java
similarity index 87%
rename from ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationSpec.java
rename to ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationSpec.java
index c5eb9ad68593..aceeba6aa9ca 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/CatalystQueueConfigurationSpec.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/ReactQueueConfigurationSpec.java
@@ -14,17 +14,17 @@
 import com.facebook.infer.annotation.Assertions;
 
 /**
- * Spec for creating a CatalystQueueConfiguration. This exists so that CatalystInstance is able to
+ * Spec for creating a ReactQueueConfiguration. This exists so that CatalystInstance is able to
  * set Exception handlers on the MessageQueueThreads it uses and it would not be super clean if the
  * threads were configured, then passed to CatalystInstance where they are configured more. These
  * specs allows the Threads to be created fully configured.
  */
-public class CatalystQueueConfigurationSpec {
+public class ReactQueueConfigurationSpec {
 
   private final MessageQueueThreadSpec mNativeModulesQueueThreadSpec;
   private final MessageQueueThreadSpec mJSQueueThreadSpec;
 
-  private CatalystQueueConfigurationSpec(
+  private ReactQueueConfigurationSpec(
       MessageQueueThreadSpec nativeModulesQueueThreadSpec,
       MessageQueueThreadSpec jsQueueThreadSpec) {
     mNativeModulesQueueThreadSpec = nativeModulesQueueThreadSpec;
@@ -43,7 +43,7 @@ public static Builder builder() {
     return new Builder();
   }
 
-  public static CatalystQueueConfigurationSpec createDefault() {
+  public static ReactQueueConfigurationSpec createDefault() {
     return builder()
         .setJSQueueThreadSpec(MessageQueueThreadSpec.newBackgroundThreadSpec("js"))
         .setNativeModulesQueueThreadSpec(
@@ -70,8 +70,8 @@ public Builder setJSQueueThreadSpec(MessageQueueThreadSpec spec) {
       return this;
     }
 
-    public CatalystQueueConfigurationSpec build() {
-      return new CatalystQueueConfigurationSpec(
+    public ReactQueueConfigurationSpec build() {
+      return new ReactQueueConfigurationSpec(
           Assertions.assertNotNull(mNativeModulesQueueSpec),
           Assertions.assertNotNull(mJSQueueSpec));
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java
new file mode 100644
index 000000000000..1cc874e9cb32
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java
@@ -0,0 +1,29 @@
+/**
+ * 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.bridge.webworkers;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.queue.MessageQueueThread;
+import com.facebook.react.bridge.queue.MessageQueueThreadImpl;
+import com.facebook.react.bridge.queue.ProxyQueueThreadExceptionHandler;
+
+@DoNotStrip
+public class WebWorkers {
+
+  /**
+   * Creates a new MessageQueueThread for a background web worker owned by the JS thread with the
+   * given MessageQueueThread.
+   */
+  public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) {
+    return MessageQueueThreadImpl.startNewBackgroundThread(
+        "web-worker-" + id,
+        new ProxyQueueThreadExceptionHandler(ownerThread));
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/ApplicationHolder.java b/ReactAndroid/src/main/java/com/facebook/react/common/ApplicationHolder.java
new file mode 100644
index 000000000000..f35d1a18d0ad
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/ApplicationHolder.java
@@ -0,0 +1,37 @@
+/**
+ * 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.common;
+
+import android.app.Application;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.proguard.annotations.DoNotStrip;
+
+/**
+ * Holds the current Application Object.
+ *
+ * TODO(9577825): This is a bad pattern, we should thread through an Environment object instead.
+ * Remove once bridge is unforked.
+ */
+@DoNotStrip
+@Deprecated
+public class ApplicationHolder {
+
+  private static Application sApplication;
+
+  public static void setApplication(Application application) {
+    sApplication = application;
+  }
+
+  @DoNotStrip
+  public static Application getApplication() {
+    return Assertions.assertNotNull(sApplication);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
index d034ce6ec68e..e98c528e86bc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK
@@ -8,6 +8,9 @@ android_library(
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
+  exported_deps = [
+    react_native_dep('java/com/facebook/proguard/annotations:annotations'),
+  ],
   visibility = [
     'PUBLIC',
   ],
@@ -16,12 +19,12 @@ android_library(
 android_build_config(
   name = 'build_config',
   package = 'com.facebook.react',
-  visibility = [
-    'PUBLIC',
-  ],
   values = [
    'boolean IS_INTERNAL_BUILD = true',
   ],
+  visibility = [
+    'PUBLIC',
+  ],
 )
 
 project_config(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java b/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java
new file mode 100644
index 000000000000..6fcec141fc51
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java
@@ -0,0 +1,24 @@
+/**
+ * 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.common.build;
+
+import com.facebook.react.BuildConfig;
+
+/**
+ * Convenience class for accessing auto-generated BuildConfig so that a) other modules can just
+ * depend on this module instead of having to manually depend on generating their own build config
+ * and b) we don't have to deal with IntelliJ getting confused about the autogenerated BuildConfig
+ * class all over the place.
+ */
+public class ReactBuildConfig {
+
+  public static final boolean DEBUG = BuildConfig.DEBUG;
+  public static final boolean IS_INTERNAL_BUILD = BuildConfig.IS_INTERNAL_BUILD;
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java
index a94a65cbc6e4..9caf34cd6231 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java
@@ -12,29 +12,65 @@
 import javax.annotation.Nullable;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A super simple Future-like class that can safely notify another Thread when a value is ready.
- * Does not support setting errors or canceling.
+ * Does not support canceling.
  */
-public class SimpleSettableFuture<T> {
-
+public class SimpleSettableFuture<T> implements Future<T> {
   private final CountDownLatch mReadyLatch = new CountDownLatch(1);
-  private volatile @Nullable T mResult;
+  private @Nullable T mResult;
+  private @Nullable Exception mException;
 
   /**
    * Sets the result. If another thread has called {@link #get}, they will immediately receive the
-   * value. Must only be called once.
+   * value. set or setException must only be called once.
    */
-  public void set(T result) {
-    if (mReadyLatch.getCount() == 0) {
-      throw new RuntimeException("Result has already been set!");
-    }
+  public void set(@Nullable T result) {
+    checkNotSet();
     mResult = result;
     mReadyLatch.countDown();
   }
 
+  /**
+   * Sets the exception. If another thread has called {@link #get}, they will immediately receive
+   * the exception. set or setException must only be called once.
+   */
+  public void setException(Exception exception) {
+    checkNotSet();
+    mException = exception;
+    mReadyLatch.countDown();
+  }
+
+  @Override
+  public boolean cancel(boolean mayInterruptIfRunning) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isCancelled() {
+    return false;
+  }
+
+  @Override
+  public boolean isDone() {
+    return mReadyLatch.getCount() == 0;
+  }
+
+  @Override
+  public @Nullable T get() throws InterruptedException, ExecutionException {
+    mReadyLatch.await();
+    if (mException != null) {
+      throw new ExecutionException(mException);
+    }
+
+    return mResult;
+  }
+
   /**
    * Wait up to the timeout time for another Thread to set a value on this future. If a value has
    * already been set, this method will return immediately.
@@ -42,21 +78,46 @@ public void set(T result) {
    * NB: For simplicity, we catch and wrap InterruptedException. Do NOT use this class if you
    * are in the 1% of cases where you actually want to handle that.
    */
-  public @Nullable T get(long timeoutMS) {
+  @Override
+  public @Nullable T get(long timeout, TimeUnit unit) throws
+      InterruptedException, ExecutionException, TimeoutException {
+    if (!mReadyLatch.await(timeout, unit)) {
+      throw new TimeoutException("Timed out waiting for result");
+    }
+    if (mException != null) {
+      throw new ExecutionException(mException);
+    }
+
+    return mResult;
+  }
+
+  /**
+   * Convenience wrapper for {@link #get()} that re-throws get()'s Exceptions as
+   * RuntimeExceptions.
+   */
+  public @Nullable T getOrThrow() {
     try {
-      if (!mReadyLatch.await(timeoutMS, TimeUnit.MILLISECONDS)) {
-        throw new TimeoutException();
-      }
-    } catch (InterruptedException e) {
+      return get();
+    } catch (InterruptedException | ExecutionException e) {
       throw new RuntimeException(e);
     }
-    return mResult;
   }
 
-  public static class TimeoutException extends RuntimeException {
+  /**
+   * Convenience wrapper for {@link #get(long, TimeUnit)} that re-throws get()'s Exceptions as
+   * RuntimeExceptions.
+   */
+  public @Nullable T getOrThrow(long timeout, TimeUnit unit) {
+    try {
+      return get(timeout, unit);
+    } catch (InterruptedException | ExecutionException | TimeoutException e) {
+      throw new RuntimeException(e);
+    }
+  }
 
-    public TimeoutException() {
-      super("Timed out waiting for future");
+  private void checkNotSet() {
+    if (mReadyLatch.getCount() == 0) {
+      throw new RuntimeException("Result has already been set!");
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK
index 06bf27bc42d8..5f81485ca5f7 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BUCK
@@ -5,24 +5,19 @@ android_library(
   manifest = 'AndroidManifest.xml',
   srcs = glob(['**/*.java']),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('res:devsupport'),
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/modules/debug:debug'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
     react_native_dep('third-party/java/okhttp:okhttp'),
     react_native_dep('third-party/java/okio:okio'),
+
   ],
   visibility = [
-    react_native_target('java/com/facebook/react/...'),
-    '//instrumentation_tests/com/facebook/catalyst/...',
-    '//java/com/facebook/catalyst/...',
-    '//java/com/facebook/groups/treehouse/react/...',
-    '//java/com/facebook/fbreact/...',
-    '//javatests/com/facebook/catalyst/...',
-    '//javatests/com/facebook/react/...',
+    'PUBLIC',
   ],
 )
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugServerException.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugServerException.java
index 59f80aba467b..1cf1ff387f15 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugServerException.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugServerException.java
@@ -22,24 +22,16 @@
 import org.json.JSONObject;
 
 /**
+ * Tracks errors connecting to or received from the debug derver.
  * The debug server returns errors as json objects. This exception represents that error.
  */
 public class DebugServerException extends IOException {
-
-  public final String description;
-  public final String fileName;
-  public final int lineNumber;
-  public final int column;
-
   private DebugServerException(String description, String fileName, int lineNumber, int column) {
-    this.description = description;
-    this.fileName = fileName;
-    this.lineNumber = lineNumber;
-    this.column = column;
+    super(description + "\n  at " + fileName + ":" + lineNumber + ":" + column);
   }
 
-  public String toReadableMessage() {
-    return description + "\n  at " + fileName + ":" + lineNumber + ":" + column;
+  public DebugServerException(String description) {
+    super(description);
   }
 
   /**
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java
index 9c3454fe8f97..825bc1c59843 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevInternalSettings.java
@@ -33,6 +33,8 @@ public class DevInternalSettings implements
   private static final String PREFS_DEBUG_SERVER_HOST_KEY = "debug_http_host";
   private static final String PREFS_ANIMATIONS_DEBUG_KEY = "animations_debug";
   private static final String PREFS_RELOAD_ON_JS_CHANGE_KEY = "reload_on_js_change";
+  private static final String PREFS_INSPECTOR_DEBUG_KEY = "inspector_debug";
+  private static final String PREFS_HOT_MODULE_REPLACEMENT_KEY = "hot_module_replacement";
 
   private final SharedPreferences mPreferences;
   private final DevSupportManager mDebugManager;
@@ -76,6 +78,14 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin
     }
   }
 
+  public boolean isHotModuleReplacementEnabled() {
+    return mPreferences.getBoolean(PREFS_HOT_MODULE_REPLACEMENT_KEY, false);
+  }
+
+  public void setHotModuleReplacementEnabled(boolean enabled) {
+    mPreferences.edit().putBoolean(PREFS_HOT_MODULE_REPLACEMENT_KEY, enabled).apply();
+  }
+
   public boolean isReloadOnJSChangeEnabled() {
     return mPreferences.getBoolean(PREFS_RELOAD_ON_JS_CHANGE_KEY, false);
   }
@@ -83,4 +93,12 @@ public boolean isReloadOnJSChangeEnabled() {
   public void setReloadOnJSChangeEnabled(boolean enabled) {
     mPreferences.edit().putBoolean(PREFS_RELOAD_ON_JS_CHANGE_KEY, enabled).apply();
   }
+
+  public boolean isElementInspectorEnabled() {
+    return mPreferences.getBoolean(PREFS_INSPECTOR_DEBUG_KEY, false);
+  }
+
+  public void setElementInspectorEnabled(boolean enabled) {
+    mPreferences.edit().putBoolean(PREFS_INSPECTOR_DEBUG_KEY, enabled).apply();
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java
index 2db8f0b019db..d8d802320837 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java
@@ -13,6 +13,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URL;
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
@@ -25,7 +26,6 @@
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.UiThreadUtil;
 import com.facebook.react.common.ReactConstants;
-
 import com.squareup.okhttp.Call;
 import com.squareup.okhttp.Callback;
 import com.squareup.okhttp.ConnectionPool;
@@ -33,6 +33,7 @@
 import com.squareup.okhttp.Request;
 import com.squareup.okhttp.Response;
 import com.squareup.okhttp.ResponseBody;
+
 import okio.Okio;
 import okio.Sink;
 
@@ -55,14 +56,14 @@ public class DevServerHelper {
   private static final String DEVICE_LOCALHOST = "localhost:8081";
 
   private static final String BUNDLE_URL_FORMAT =
-      "http://%s/%s.bundle?platform=android&dev=%s";
+      "http://%s/%s.bundle?platform=android&dev=%s&hot=%s";
   private static final String SOURCE_MAP_URL_FORMAT =
       BUNDLE_URL_FORMAT.replaceFirst("\\.bundle", ".map");
   private static final String LAUNCH_CHROME_DEVTOOLS_COMMAND_URL_FORMAT =
       "http://%s/launch-chrome-devtools";
   private static final String ONCHANGE_ENDPOINT_URL_FORMAT =
       "http://%s/onchange";
-  private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy";
+  private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
   private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status";
 
   private static final String PACKAGER_OK_STATUS = "packager-status:running";
@@ -91,6 +92,7 @@ public interface PackagerStatusCallback {
   private boolean mOnChangePollingEnabled;
   private @Nullable OkHttpClient mOnChangePollingClient;
   private @Nullable OnServerContentChangeListener mOnServerContentChangeListener;
+  private @Nullable Call mDownloadBundleFromURLCall;
 
   public DevServerHelper(DevInternalSettings settings) {
     mSettings = settings;
@@ -120,12 +122,19 @@ private static String getHostForJSProxy() {
   }
 
   /**
-   * @return whether we should enabled dev mode or not when requesting JS bundles.
+   * @return whether we should enable dev mode when requesting JS bundles.
    */
   private boolean getDevMode() {
     return mSettings.isJSDevModeEnabled();
   }
 
+  /**
+   * @return whether we should enabled HMR when requesting JS bundles.
+   */
+  private boolean getHMR() {
+    return mSettings.isHotModuleReplacementEnabled();
+  }
+
   /**
    * @return the host to use when connecting to the bundle server.
    */
@@ -160,27 +169,49 @@ private boolean isRunningOnStockEmulator() {
     return Build.FINGERPRINT.contains("generic");
   }
 
-  private static String createBundleURL(String host, String jsModulePath, boolean devMode) {
-    return String.format(Locale.US, BUNDLE_URL_FORMAT, host, jsModulePath, devMode);
+  private static String createBundleURL(String host, String jsModulePath, boolean devMode, boolean hmr) {
+    return String.format(Locale.US, BUNDLE_URL_FORMAT, host, jsModulePath, devMode, hmr);
   }
 
   public void downloadBundleFromURL(
       final BundleDownloadCallback callback,
       final String jsModulePath,
       final File outputFile) {
-    final String bundleURL = createBundleURL(getDebugServerHost(), jsModulePath, getDevMode());
-    Request request = new Request.Builder()
+    final String bundleURL = createBundleURL(getDebugServerHost(), jsModulePath, getDevMode(), getHMR());
+    final Request request = new Request.Builder()
         .url(bundleURL)
         .build();
-    Call call = mClient.newCall(request);
-    call.enqueue(new Callback() {
+    mDownloadBundleFromURLCall = Assertions.assertNotNull(mClient.newCall(request));
+    mDownloadBundleFromURLCall.enqueue(new Callback() {
       @Override
       public void onFailure(Request request, IOException e) {
-        callback.onFailure(e);
+        // ignore callback if call was cancelled
+        if (mDownloadBundleFromURLCall == null || mDownloadBundleFromURLCall.isCanceled()) {
+          mDownloadBundleFromURLCall = null;
+          return;
+        }
+        mDownloadBundleFromURLCall = null;
+
+        StringBuilder sb = new StringBuilder();
+        sb.append("Could not connect to development server.\n\n")
+          .append("URL: ").append(request.urlString()).append("\n\n")
+          .append("Try the following to fix the issue:\n")
+          .append("\u2022 Ensure that the packager server is running\n")
+          .append("\u2022 Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices\n")
+          .append("\u2022 If you're on a physical device connected to the same machine, run 'adb reverse tcp:8081 tcp:8081' to forward requests from your device\n")
+          .append("\u2022 If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:8081");
+        callback.onFailure(new DebugServerException(sb.toString()));
       }
 
       @Override
       public void onResponse(Response response) throws IOException {
+        // ignore callback if call was cancelled
+        if (mDownloadBundleFromURLCall == null || mDownloadBundleFromURLCall.isCanceled()) {
+          mDownloadBundleFromURLCall = null;
+          return;
+        }
+        mDownloadBundleFromURLCall = null;
+
         // Check for server errors. If the server error has the expected form, fail with more info.
         if (!response.isSuccessful()) {
           String body = response.body().string();
@@ -188,7 +219,12 @@ public void onResponse(Response response) throws IOException {
           if (debugServerException != null) {
             callback.onFailure(debugServerException);
           } else {
-            callback.onFailure(new IOException("Unexpected response code: " + response.code()));
+            StringBuilder sb = new StringBuilder();
+            sb.append("The development server returned response error code: ").append(response.code()).append("\n\n")
+              .append("URL: ").append(request.urlString()).append("\n\n")
+              .append("Body:\n")
+              .append(body);
+            callback.onFailure(new DebugServerException(sb.toString()));
           }
           return;
         }
@@ -207,6 +243,13 @@ public void onResponse(Response response) throws IOException {
     });
   }
 
+  public void cancelDownloadBundleFromURL() {
+    if (mDownloadBundleFromURLCall != null) {
+      mDownloadBundleFromURLCall.cancel();
+      mDownloadBundleFromURLCall = null;
+    }
+  }
+
   public void isPackagerRunning(final PackagerStatusCallback callback) {
     String statusURL = createPackagerStatusURL(getDebugServerHost());
     Request request = new Request.Builder()
@@ -217,7 +260,10 @@ public void isPackagerRunning(final PackagerStatusCallback callback) {
         new Callback() {
           @Override
           public void onFailure(Request request, IOException e) {
-            FLog.e(ReactConstants.TAG, "IOException requesting status from packager", e);
+            FLog.w(
+                ReactConstants.TAG,
+                "The packager does not seem to be running as we got an IOException requesting " +
+                    "its status: " + e.getMessage());
             callback.onPackagerStatusFetched(false);
           }
 
@@ -351,17 +397,17 @@ public void onResponse(Response response) throws IOException {
   }
 
   public String getSourceMapUrl(String mainModuleName) {
-    return String.format(Locale.US, SOURCE_MAP_URL_FORMAT, getDebugServerHost(), mainModuleName, getDevMode());
+    return String.format(Locale.US, SOURCE_MAP_URL_FORMAT, getDebugServerHost(), mainModuleName, getDevMode(), getHMR());
   }
 
   public String getSourceUrl(String mainModuleName) {
-    return String.format(Locale.US, BUNDLE_URL_FORMAT, getDebugServerHost(), mainModuleName, getDevMode());
+    return String.format(Locale.US, BUNDLE_URL_FORMAT, getDebugServerHost(), mainModuleName, getDevMode(), getHMR());
   }
 
   public String getJSBundleURLForRemoteDebugging(String mainModuleName) {
     // 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
     // host itself.
-    return createBundleURL(getHostForJSProxy(), mainModuleName, getDevMode());
+    return createBundleURL(getHostForJSProxy(), mainModuleName, getDevMode(), getHMR());
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java
index 69ef7fa96211..4b27023c06bf 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java
@@ -9,667 +9,35 @@
 
 package com.facebook.react.devsupport;
 
-import javax.annotation.Nullable;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.Locale;
-
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.hardware.SensorManager;
-import android.os.Debug;
-import android.os.Environment;
-import android.view.WindowManager;
-import android.widget.Toast;
-
-import com.facebook.common.logging.FLog;
-import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.R;
-import com.facebook.react.bridge.CatalystInstance;
 import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
-import com.facebook.react.bridge.ProxyJavaScriptExecutor;
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReadableArray;
-import com.facebook.react.bridge.UiThreadUtil;
-import com.facebook.react.bridge.WebsocketJavaScriptExecutor;
-import com.facebook.react.common.ReactConstants;
-import com.facebook.react.common.ShakeDetector;
-import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
 import com.facebook.react.modules.debug.DeveloperSettings;
 
 /**
- * Interface for accessing and interacting with development features. Following features
- * are supported through this manager class:
- * 1) Displaying JS errors (aka RedBox)
- * 2) Displaying developers menu (Reload JS, Debug JS)
- * 3) Communication with developer server in order to download updated JS bundle
- * 4) Starting/stopping broadcast receiver for js reload signals
- * 5) Starting/stopping motion sensor listener that recognize shake gestures which in turn may
- *    trigger developers menu.
- * 6) Launching developers settings view
- *
- * This class automatically monitors the state of registered views and activities to which they are
- * bound to make sure that we don't display overlay or that we we don't listen for sensor events
- * when app is backgrounded.
- *
- * {@link ReactInstanceDevCommandsHandler} implementation is responsible for instantiating this
- * instance and for populating with an instance of {@link CatalystInstance} whenever instance
- * manager recreates it (through {@link #onNewCatalystContextCreated}). Also, instance manager is
- * responsible for enabling/disabling dev support in case when app is backgrounded or when all the
- * views has been detached from the instance (through {@link #setDevSupportEnabled} method).
- *
- * IMPORTANT: In order for developer support to work correctly it is required that the
- * manifest of your application contain the following entries:
- * {@code <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>}
- * {@code <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>}
+ * Interface for accessing and interacting with development features.
+ * In dev mode, use the implementation {@link DevSupportManagerImpl}.
+ * In production mode, use the dummy implementation {@link DisabledDevSupportManager}.
  */
-public class DevSupportManager implements NativeModuleCallExceptionHandler {
-
-  private static final int JAVA_ERROR_COOKIE = -1;
-  private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js";
-
-  private static final String EXOPACKAGE_LOCATION_FORMAT
-      = "/data/local/tmp/exopackage/%s//secondary-dex";
-
-  private static final int JAVA_SAMPLING_PROFILE_MEMORY_BYTES = 8 * 1024 * 1024;
-  private static final int JAVA_SAMPLING_PROFILE_DELTA_US = 100;
-
-  private final Context mApplicationContext;
-  private final ShakeDetector mShakeDetector;
-  private final BroadcastReceiver mReloadAppBroadcastReceiver;
-  private final DevServerHelper mDevServerHelper;
-  private final LinkedHashMap<String, DevOptionHandler> mCustomDevOptions =
-      new LinkedHashMap<>();
-  private final ReactInstanceDevCommandsHandler mReactInstanceCommandsHandler;
-  private final @Nullable String mJSAppBundleName;
-  private final File mJSBundleTempFile;
-
-  private @Nullable RedBoxDialog mRedBoxDialog;
-  private @Nullable AlertDialog mDevOptionsDialog;
-  private @Nullable DebugOverlayController mDebugOverlayController;
-  private @Nullable ReactContext mCurrentContext;
-  private DevInternalSettings mDevSettings;
-  private boolean mIsUsingJSProxy = false;
-  private boolean mIsReceiverRegistered = false;
-  private boolean mIsShakeDetectorStarted = false;
-  private boolean mIsDevSupportEnabled = false;
-  private boolean mIsCurrentlyProfiling = false;
-  private int     mProfileIndex = 0;
-
-  public DevSupportManager(
-      Context applicationContext,
-      ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
-      @Nullable String packagerPathForJSBundleName,
-      boolean enableOnCreate) {
-    mReactInstanceCommandsHandler = reactInstanceCommandsHandler;
-    mApplicationContext = applicationContext;
-    mJSAppBundleName = packagerPathForJSBundleName;
-    mDevSettings = new DevInternalSettings(applicationContext, this);
-    mDevServerHelper = new DevServerHelper(mDevSettings);
-
-    // Prepare shake gesture detector (will be started/stopped from #reload)
-    mShakeDetector = new ShakeDetector(new ShakeDetector.ShakeListener() {
-      @Override
-      public void onShake() {
-        showDevOptionsDialog();
-      }
-    });
-
-    // Prepare reload APP broadcast receiver (will be registered/unregistered from #reload)
-    mReloadAppBroadcastReceiver = new BroadcastReceiver() {
-      @Override
-      public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        if (DevServerHelper.getReloadAppAction(context).equals(action)) {
-          if (intent.getBooleanExtra(DevServerHelper.RELOAD_APP_EXTRA_JS_PROXY, false)) {
-            mIsUsingJSProxy = true;
-            mDevServerHelper.launchChromeDevtools();
-          } else {
-            mIsUsingJSProxy = false;
-          }
-          handleReloadJS();
-        }
-      }
-    };
-
-    // We store JS bundle loaded from dev server in a single destination in app's data dir.
-    // In case when someone schedule 2 subsequent reloads it may happen that JS thread will
-    // start reading first reload output while the second reload starts writing to the same
-    // file. As this should only be the case in dev mode we leave it as it is.
-    // TODO(6418010): Fix readers-writers problem in debug reload from HTTP server
-    mJSBundleTempFile = new File(applicationContext.getFilesDir(), JS_BUNDLE_FILE_NAME);
-
-    setDevSupportEnabled(enableOnCreate);
-  }
-
-  @Override
-  public void handleException(Exception e) {
-    if (mIsDevSupportEnabled) {
-      FLog.e(ReactConstants.TAG, "Exception in native call from JS", e);
-      showNewError(e.getMessage(), StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE);
-    } else {
-      if (e instanceof RuntimeException) {
-        // Because we are rethrowing the original exception, the original stacktrace will be
-        // preserved
-        throw (RuntimeException) e;
-      } else {
-        throw new RuntimeException(e);
-      }
-    }
-  }
-
-  /**
-   * Add option item to dev settings dialog displayed by this manager. In the case user select given
-   * option from that dialog, the appropriate handler passed as {@param optionHandler} will be
-   * called.
-   */
-  public void addCustomDevOption(
-      String optionName,
-      DevOptionHandler optionHandler) {
-    mCustomDevOptions.put(optionName, optionHandler);
-  }
-
-  public void showNewJSError(String message, ReadableArray details, int errorCookie) {
-    showNewError(message, StackTraceHelper.convertJsStackTrace(details), errorCookie);
-  }
-
-  public void updateJSError(
-      final String message,
-      final ReadableArray details,
-      final int errorCookie) {
-    UiThreadUtil.runOnUiThread(
-        new Runnable() {
-          @Override
-          public void run() {
-            // Since we only show the first JS error in a succession of JS errors, make sure we only
-            // update the error message for that error message. This assumes that updateJSError
-            // belongs to the most recent showNewJSError
-            if (mRedBoxDialog == null ||
-                !mRedBoxDialog.isShowing() ||
-                errorCookie != mRedBoxDialog.getErrorCookie()) {
-              return;
-            }
-            mRedBoxDialog.setExceptionDetails(
-                message,
-                StackTraceHelper.convertJsStackTrace(details));
-            mRedBoxDialog.show();
-          }
-        });
-  }
-
-  private void showNewError(
-      final String message,
-      final StackFrame[] stack,
-      final int errorCookie) {
-    UiThreadUtil.runOnUiThread(
-        new Runnable() {
-          @Override
-          public void run() {
-            if (mRedBoxDialog == null) {
-              mRedBoxDialog = new RedBoxDialog(mApplicationContext, DevSupportManager.this);
-              mRedBoxDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-            }
-            if (mRedBoxDialog.isShowing()) {
-              // Sometimes errors cause multiple errors to be thrown in JS in quick succession. Only
-              // show the first and most actionable one.
-              return;
-            }
-            mRedBoxDialog.setExceptionDetails(message, stack);
-            mRedBoxDialog.setErrorCookie(errorCookie);
-            mRedBoxDialog.show();
-          }
-        });
-  }
-
-  public void showDevOptionsDialog() {
-    if (mDevOptionsDialog != null || !mIsDevSupportEnabled) {
-      return;
-    }
-    LinkedHashMap<String, DevOptionHandler> options = new LinkedHashMap<>();
-    /* register standard options */
-    options.put(
-        mApplicationContext.getString(R.string.catalyst_reloadjs), new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            handleReloadJS();
-          }
-        });
-    options.put(
-        mIsUsingJSProxy ?
-            mApplicationContext.getString(R.string.catalyst_debugjs_off) :
-            mApplicationContext.getString(R.string.catalyst_debugjs),
-        new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            mIsUsingJSProxy = !mIsUsingJSProxy;
-            handleReloadJS();
-          }
-        });
-    options.put(
-        mDevSettings.isReloadOnJSChangeEnabled()
-            ? mApplicationContext.getString(R.string.catalyst_live_reload_off)
-            : mApplicationContext.getString(R.string.catalyst_live_reload),
-        new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            mDevSettings.setReloadOnJSChangeEnabled(!mDevSettings.isReloadOnJSChangeEnabled());
-          }
-        });
-    options.put(
-        mApplicationContext.getString(R.string.catalyst_inspect_element),
-        new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            mReactInstanceCommandsHandler.toggleElementInspector();
-          }
-        });
-    options.put(
-        mDevSettings.isFpsDebugEnabled()
-            ? mApplicationContext.getString(R.string.catalyst_perf_monitor_off)
-            : mApplicationContext.getString(R.string.catalyst_perf_monitor),
-        new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            mDevSettings.setFpsDebugEnabled(!mDevSettings.isFpsDebugEnabled());
-          }
-        });
-    if (mCurrentContext != null &&
-        mCurrentContext.getCatalystInstance() != null &&
-        !mCurrentContext.getCatalystInstance().isDestroyed() &&
-        mCurrentContext.getCatalystInstance().getBridge() != null &&
-        mCurrentContext.getCatalystInstance().getBridge().supportsProfiling()) {
-      options.put(
-          mApplicationContext.getString(
-              mIsCurrentlyProfiling ? R.string.catalyst_stop_profile :
-                  R.string.catalyst_start_profile),
-          new DevOptionHandler() {
-            @Override
-            public void onOptionSelected() {
-              if (mCurrentContext != null && mCurrentContext.hasActiveCatalystInstance()) {
-                String profileName = (Environment.getExternalStorageDirectory().getPath() +
-                    "/profile_" + mProfileIndex + ".json");
-                if (mIsCurrentlyProfiling) {
-                  mIsCurrentlyProfiling = false;
-                  mProfileIndex++;
-                  Debug.stopMethodTracing();
-                  mCurrentContext.getCatalystInstance()
-                      .getBridge()
-                      .stopProfiler("profile", profileName);
-                  Toast.makeText(
-                      mCurrentContext,
-                      "Profile output to " + profileName,
-                      Toast.LENGTH_LONG).show();
-                } else {
-                  mIsCurrentlyProfiling = true;
-                  mCurrentContext.getCatalystInstance().getBridge().startProfiler("profile");
-                  Debug.startMethodTracingSampling(
-                      profileName,
-                      JAVA_SAMPLING_PROFILE_MEMORY_BYTES,
-                      JAVA_SAMPLING_PROFILE_DELTA_US);
-                }
-              }
-            }
-          });
-    }
-    options.put(
-        mApplicationContext.getString(R.string.catalyst_settings), new DevOptionHandler() {
-          @Override
-          public void onOptionSelected() {
-            Intent intent = new Intent(mApplicationContext, DevSettingsActivity.class);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mApplicationContext.startActivity(intent);
-          }
-        });
-
-    if (mCustomDevOptions.size() > 0) {
-      options.putAll(mCustomDevOptions);
-    }
-
-    final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]);
-
-    mDevOptionsDialog =
-        new AlertDialog.Builder(mApplicationContext)
-            .setItems(
-                options.keySet().toArray(new String[0]),
-                new DialogInterface.OnClickListener() {
-                  @Override
-                  public void onClick(DialogInterface dialog, int which) {
-                    optionHandlers[which].onOptionSelected();
-                    mDevOptionsDialog = null;
-                  }
-                })
-            .setOnCancelListener(new DialogInterface.OnCancelListener() {
-              @Override
-              public void onCancel(DialogInterface dialog) {
-                mDevOptionsDialog = null;
-              }
-            })
-            .create();
-    mDevOptionsDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-    mDevOptionsDialog.show();
-  }
-
-  /**
-   * {@link ReactInstanceDevCommandsHandler} is responsible for
-   * enabling/disabling dev support when a React view is attached/detached
-   * or when application state changes (e.g. the application is backgrounded).
-   */
-  public void setDevSupportEnabled(boolean isDevSupportEnabled) {
-    mIsDevSupportEnabled = isDevSupportEnabled;
-    reload();
-  }
-
-  public boolean getDevSupportEnabled() {
-    return mIsDevSupportEnabled;
-  }
-
-  public DeveloperSettings getDevSettings() {
-    return mDevSettings;
-  }
-
-  public void onNewReactContextCreated(ReactContext reactContext) {
-    resetCurrentContext(reactContext);
-  }
-
-  public void onReactInstanceDestroyed(ReactContext reactContext) {
-    if (reactContext == mCurrentContext) {
-      // only call reset context when the destroyed context matches the one that is currently set
-      // for this manager
-      resetCurrentContext(null);
-    }
-  }
-
-  public String getSourceMapUrl() {
-    if (mJSAppBundleName == null) {
-      return "";
-    }
-
-    return mDevServerHelper.getSourceMapUrl(Assertions.assertNotNull(mJSAppBundleName));
-  }
-
-  public String getSourceUrl() {
-    if (mJSAppBundleName == null) {
-      return "";
-    }
-
-    return mDevServerHelper.getSourceUrl(Assertions.assertNotNull(mJSAppBundleName));
-  }
-
-  public String getJSBundleURLForRemoteDebugging() {
-    return mDevServerHelper.getJSBundleURLForRemoteDebugging(
-        Assertions.assertNotNull(mJSAppBundleName));
-  }
-
-  public String getDownloadedJSBundleFile() {
-    return mJSBundleTempFile.getAbsolutePath();
-  }
-
-  /**
-   * @return {@code true} if {@link ReactInstanceManager} should use downloaded JS bundle file
-   * instead of using JS file from assets. This may happen when app has not been updated since
-   * the last time we fetched the bundle.
-   */
-  public boolean hasUpToDateJSBundleInCache() {
-    if (mIsDevSupportEnabled && mJSBundleTempFile.exists()) {
-      try {
-        String packageName = mApplicationContext.getPackageName();
-        PackageInfo thisPackage = mApplicationContext.getPackageManager()
-            .getPackageInfo(packageName, 0);
-        if (mJSBundleTempFile.lastModified() > thisPackage.lastUpdateTime) {
-          // Base APK has not been updated since we donwloaded JS, but if app is using exopackage
-          // it may only be a single dex that has been updated. We check for exopackage dir update
-          // time in that case.
-          File exopackageDir = new File(
-              String.format(Locale.US, EXOPACKAGE_LOCATION_FORMAT, packageName));
-          if (exopackageDir.exists()) {
-            return mJSBundleTempFile.lastModified() > exopackageDir.lastModified();
-          }
-          return true;
-        }
-      } catch (PackageManager.NameNotFoundException e) {
-        // Ignore this error and just fallback to loading JS from assets
-        FLog.e(ReactConstants.TAG, "DevSupport is unable to get current app info");
-      }
-    }
-    return false;
-  }
-
-  /**
-   * @return {@code true} if JS bundle {@param bundleAssetName} exists, in that case
-   * {@link ReactInstanceManager} should use that file from assets instead of downloading bundle
-   * from dev server
-   */
-  public boolean hasBundleInAssets(String bundleAssetName) {
-    try {
-      String[] assets = mApplicationContext.getAssets().list("");
-      for (int i = 0; i < assets.length; i++) {
-        if (assets[i].equals(bundleAssetName)) {
-          return true;
-        }
-      }
-    } catch (IOException e) {
-      // Ignore this error and just fallback to downloading JS from devserver
-      FLog.e(ReactConstants.TAG, "Error while loading assets list");
-    }
-    return false;
-  }
-
-  private void resetCurrentContext(@Nullable ReactContext reactContext) {
-    if (mCurrentContext == reactContext) {
-      // new context is the same as the old one - do nothing
-      return;
-    }
-
-    // if currently profiling stop and write the profile file
-    if (mIsCurrentlyProfiling) {
-      mIsCurrentlyProfiling = false;
-      String profileName = (Environment.getExternalStorageDirectory().getPath() +
-          "/profile_" + mProfileIndex + ".json");
-      mProfileIndex++;
-      Debug.stopMethodTracing();
-      mCurrentContext.getCatalystInstance().getBridge().stopProfiler("profile", profileName);
-    }
-
-    mCurrentContext = reactContext;
-
-    // Recreate debug overlay controller with new CatalystInstance object
-    if (mDebugOverlayController != null) {
-      mDebugOverlayController.setFpsDebugViewVisible(false);
-    }
-    if (reactContext != null) {
-      mDebugOverlayController = new DebugOverlayController(reactContext);
-    }
-
-    reloadSettings();
-  }
-
-  /* package */ void reloadSettings() {
-    reload();
-  }
-
-  public void handleReloadJS() {
-    UiThreadUtil.assertOnUiThread();
-
-    // dismiss redbox if exists
-    if (mRedBoxDialog != null) {
-      mRedBoxDialog.dismiss();
-    }
-
-    ProgressDialog progressDialog = new ProgressDialog(mApplicationContext);
-    progressDialog.setTitle(R.string.catalyst_jsload_title);
-    progressDialog.setMessage(mApplicationContext.getString(
-        mIsUsingJSProxy ? R.string.catalyst_remotedbg_message : R.string.catalyst_jsload_message));
-    progressDialog.setIndeterminate(true);
-    progressDialog.setCancelable(false);
-    progressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-    progressDialog.show();
-
-    if (mIsUsingJSProxy) {
-      reloadJSInProxyMode(progressDialog);
-    } else {
-      reloadJSFromServer(progressDialog);
-    }
-  }
-
-  public void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback) {
-    mDevServerHelper.isPackagerRunning(callback);
-  }
-
-  private void reloadJSInProxyMode(final ProgressDialog progressDialog) {
-    // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that
-    // anyway
-    mDevServerHelper.launchChromeDevtools();
-
-    final WebsocketJavaScriptExecutor webSocketJSExecutor = new WebsocketJavaScriptExecutor();
-    webSocketJSExecutor.connect(
-        mDevServerHelper.getWebsocketProxyURL(),
-        new WebsocketJavaScriptExecutor.JSExecutorConnectCallback() {
-          @Override
-          public void onSuccess() {
-            progressDialog.dismiss();
-            UiThreadUtil.runOnUiThread(
-                new Runnable() {
-                  @Override
-                  public void run() {
-                    mReactInstanceCommandsHandler.onReloadWithJSDebugger(
-                        new ProxyJavaScriptExecutor(webSocketJSExecutor));
-                  }
-                });
-          }
-
-          @Override
-          public void onFailure(final Throwable cause) {
-            progressDialog.dismiss();
-            FLog.e(ReactConstants.TAG, "Unable to connect to remote debugger", cause);
-            UiThreadUtil.runOnUiThread(
-                new Runnable() {
-                  @Override
-                  public void run() {
-                    showNewError(
-                        mApplicationContext.getString(R.string.catalyst_remotedbg_error),
-                        StackTraceHelper.convertJavaStackTrace(cause),
-                        JAVA_ERROR_COOKIE);
-                  }
-                });
-          }
-        });
-  }
-
-  private void reloadJSFromServer(final ProgressDialog progressDialog) {
-    mDevServerHelper.downloadBundleFromURL(
-        new DevServerHelper.BundleDownloadCallback() {
-          @Override
-          public void onSuccess() {
-            progressDialog.dismiss();
-            UiThreadUtil.runOnUiThread(
-                new Runnable() {
-                  @Override
-                  public void run() {
-                    mReactInstanceCommandsHandler.onJSBundleLoadedFromServer();
-                  }
-                });
-          }
-
-          @Override
-          public void onFailure(final Exception cause) {
-            progressDialog.dismiss();
-            FLog.e(ReactConstants.TAG, "Unable to download JS bundle", cause);
-            UiThreadUtil.runOnUiThread(
-                new Runnable() {
-                  @Override
-                  public void run() {
-                    if (cause instanceof DebugServerException) {
-                      DebugServerException debugServerException = (DebugServerException) cause;
-                      showNewError(
-                          debugServerException.description,
-                          StackTraceHelper.convertJavaStackTrace(cause),
-                          JAVA_ERROR_COOKIE);
-                    } else {
-                      showNewError(
-                          mApplicationContext.getString(R.string.catalyst_jsload_error),
-                          StackTraceHelper.convertJavaStackTrace(cause),
-                          JAVA_ERROR_COOKIE);
-                    }
-                  }
-                });
-          }
-        },
-        Assertions.assertNotNull(mJSAppBundleName),
-        mJSBundleTempFile);
-  }
-
-  private void reload() {
-    // reload settings, show/hide debug overlay if required & start/stop shake detector
-    if (mIsDevSupportEnabled) {
-      // update visibility of FPS debug overlay depending on the settings
-      if (mDebugOverlayController != null) {
-        mDebugOverlayController.setFpsDebugViewVisible(mDevSettings.isFpsDebugEnabled());
-      }
-
-      // start shake gesture detector
-      if (!mIsShakeDetectorStarted) {
-        mShakeDetector.start(
-            (SensorManager) mApplicationContext.getSystemService(Context.SENSOR_SERVICE));
-        mIsShakeDetectorStarted = true;
-      }
-
-      // register reload app broadcast receiver
-      if (!mIsReceiverRegistered) {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(DevServerHelper.getReloadAppAction(mApplicationContext));
-        mApplicationContext.registerReceiver(mReloadAppBroadcastReceiver, filter);
-        mIsReceiverRegistered = true;
-      }
-
-      if (mDevSettings.isReloadOnJSChangeEnabled()) {
-        mDevServerHelper.startPollingOnChangeEndpoint(
-            new DevServerHelper.OnServerContentChangeListener() {
-          @Override
-          public void onServerContentChanged() {
-            handleReloadJS();
-          }
-        });
-      } else {
-        mDevServerHelper.stopPollingOnChangeEndpoint();
-      }
-    } else {
-      // hide FPS debug overlay
-      if (mDebugOverlayController != null) {
-        mDebugOverlayController.setFpsDebugViewVisible(false);
-      }
-
-      // stop shake gesture detector
-      if (mIsShakeDetectorStarted) {
-        mShakeDetector.stop();
-        mIsShakeDetectorStarted = false;
-      }
-
-      // unregister app reload broadcast receiver
-      if (mIsReceiverRegistered) {
-        mApplicationContext.unregisterReceiver(mReloadAppBroadcastReceiver);
-        mIsReceiverRegistered = false;
-      }
-
-      // hide redbox dialog
-      if (mRedBoxDialog != null) {
-        mRedBoxDialog.dismiss();
-      }
-
-      // hide dev options dialog
-      if (mDevOptionsDialog != null) {
-        mDevOptionsDialog.dismiss();
-      }
-
-      mDevServerHelper.stopPollingOnChangeEndpoint();
-    }
-  }
+public interface DevSupportManager extends NativeModuleCallExceptionHandler {
+
+  void showNewJavaError(String message, Throwable e);
+  void addCustomDevOption(String optionName, DevOptionHandler optionHandler);
+  void showNewJSError(String message, ReadableArray details, int errorCookie);
+  void updateJSError(final String message, final ReadableArray details, final int errorCookie);
+  void hideRedboxDialog();
+  void showDevOptionsDialog();
+  void setDevSupportEnabled(boolean isDevSupportEnabled);
+  boolean getDevSupportEnabled();
+  DeveloperSettings getDevSettings();
+  void onNewReactContextCreated(ReactContext reactContext);
+  void onReactInstanceDestroyed(ReactContext reactContext);
+  String getSourceMapUrl();
+  String getSourceUrl();
+  String getJSBundleURLForRemoteDebugging();
+  String getDownloadedJSBundleFile();
+  boolean hasUpToDateJSBundleInCache();
+  void reloadSettings();
+  void handleReloadJS();
+  void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java
new file mode 100644
index 000000000000..22d925672598
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java
@@ -0,0 +1,62 @@
+package com.facebook.react.devsupport;
+
+import javax.annotation.Nullable;
+
+import java.lang.reflect.Constructor;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.build.ReactBuildConfig;
+
+/**
+ * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses
+ * reflection to create DevSupportManagerImpl if it exists. This allows ProGuard to strip that class
+ * and its dependencies in release builds. If the class isn't found,
+ * {@link DisabledDevSupportManager} is returned instead.
+ */
+public class DevSupportManagerFactory {
+
+  private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport";
+  private static final String DEVSUPPORT_IMPL_CLASS = "DevSupportManagerImpl";
+
+  public static DevSupportManager create(
+      Context applicationContext,
+      ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
+      @Nullable String packagerPathForJSBundleName,
+      boolean enableOnCreate) {
+    if (!enableOnCreate) {
+      return new DisabledDevSupportManager();
+    }
+    try {
+      // ProGuard is surprisingly smart in this case and will keep a class if it detects a call to
+      // Class.forName() with a static string. So instead we generate a quasi-dynamic string to
+      // confuse it.
+      String className =
+          new StringBuilder(DEVSUPPORT_IMPL_PACKAGE)
+              .append(".")
+              .append(DEVSUPPORT_IMPL_CLASS)
+              .toString();
+      Class<?> devSupportManagerClass =
+          Class.forName(className);
+      Constructor constructor =
+          devSupportManagerClass.getConstructor(
+              Context.class,
+              ReactInstanceDevCommandsHandler.class,
+              String.class,
+              boolean.class);
+      return (DevSupportManager) constructor.newInstance(
+          applicationContext,
+          reactInstanceCommandsHandler,
+          packagerPathForJSBundleName,
+          true);
+    } catch (Exception e) {
+      throw new RuntimeException(
+          "Requested enabled DevSupportManager, but DevSupportManagerImpl class was not found" +
+              " or could not be created",
+          e);
+    }
+  }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
new file mode 100644
index 000000000000..75d7282a7f61
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
@@ -0,0 +1,743 @@
+/**
+ * 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 javax.annotation.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.hardware.SensorManager;
+import android.os.Debug;
+import android.os.Environment;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.R;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.JavaJSExecutor;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.bridge.WebsocketJavaScriptExecutor;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.ShakeDetector;
+import com.facebook.react.common.futures.SimpleSettableFuture;
+import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
+import com.facebook.react.modules.debug.DeveloperSettings;
+
+/**
+ * Interface for accessing and interacting with development features. Following features
+ * are supported through this manager class:
+ * 1) Displaying JS errors (aka RedBox)
+ * 2) Displaying developers menu (Reload JS, Debug JS)
+ * 3) Communication with developer server in order to download updated JS bundle
+ * 4) Starting/stopping broadcast receiver for js reload signals
+ * 5) Starting/stopping motion sensor listener that recognize shake gestures which in turn may
+ *    trigger developers menu.
+ * 6) Launching developers settings view
+ *
+ * This class automatically monitors the state of registered views and activities to which they are
+ * bound to make sure that we don't display overlay or that we we don't listen for sensor events
+ * when app is backgrounded.
+ *
+ * {@link ReactInstanceDevCommandsHandler} implementation is responsible for instantiating this
+ * instance and for populating with an instance of {@link CatalystInstance} whenever instance
+ * manager recreates it (through {@link #onNewCatalystContextCreated}). Also, instance manager is
+ * responsible for enabling/disabling dev support in case when app is backgrounded or when all the
+ * views has been detached from the instance (through {@link #setDevSupportEnabled} method).
+ *
+ * IMPORTANT: In order for developer support to work correctly it is required that the
+ * manifest of your application contain the following entries:
+ * {@code <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>}
+ * {@code <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>}
+ */
+public class DevSupportManagerImpl implements DevSupportManager {
+
+  private static final int JAVA_ERROR_COOKIE = -1;
+  private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js";
+
+  private static final String EXOPACKAGE_LOCATION_FORMAT
+      = "/data/local/tmp/exopackage/%s//secondary-dex";
+
+  private static final int JAVA_SAMPLING_PROFILE_MEMORY_BYTES = 8 * 1024 * 1024;
+  private static final int JAVA_SAMPLING_PROFILE_DELTA_US = 100;
+
+  private final Context mApplicationContext;
+  private final ShakeDetector mShakeDetector;
+  private final BroadcastReceiver mReloadAppBroadcastReceiver;
+  private final DevServerHelper mDevServerHelper;
+  private final LinkedHashMap<String, DevOptionHandler> mCustomDevOptions =
+      new LinkedHashMap<>();
+  private final ReactInstanceDevCommandsHandler mReactInstanceCommandsHandler;
+  private final @Nullable String mJSAppBundleName;
+  private final File mJSBundleTempFile;
+  private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler;
+
+  private @Nullable RedBoxDialog mRedBoxDialog;
+  private @Nullable AlertDialog mDevOptionsDialog;
+  private @Nullable DebugOverlayController mDebugOverlayController;
+  private @Nullable ReactContext mCurrentContext;
+  private DevInternalSettings mDevSettings;
+  private boolean mIsUsingJSProxy = false;
+  private boolean mIsReceiverRegistered = false;
+  private boolean mIsShakeDetectorStarted = false;
+  private boolean mIsDevSupportEnabled = false;
+  private boolean mIsCurrentlyProfiling = false;
+  private int     mProfileIndex = 0;
+
+  public DevSupportManagerImpl(
+      Context applicationContext,
+      ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
+      @Nullable String packagerPathForJSBundleName,
+      boolean enableOnCreate) {
+    mReactInstanceCommandsHandler = reactInstanceCommandsHandler;
+    mApplicationContext = applicationContext;
+    mJSAppBundleName = packagerPathForJSBundleName;
+    mDevSettings = new DevInternalSettings(applicationContext, this);
+    mDevServerHelper = new DevServerHelper(mDevSettings);
+
+    // Prepare shake gesture detector (will be started/stopped from #reload)
+    mShakeDetector = new ShakeDetector(new ShakeDetector.ShakeListener() {
+      @Override
+      public void onShake() {
+        showDevOptionsDialog();
+      }
+    });
+
+    // Prepare reload APP broadcast receiver (will be registered/unregistered from #reload)
+    mReloadAppBroadcastReceiver = new BroadcastReceiver() {
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (DevServerHelper.getReloadAppAction(context).equals(action)) {
+          if (intent.getBooleanExtra(DevServerHelper.RELOAD_APP_EXTRA_JS_PROXY, false)) {
+            mIsUsingJSProxy = true;
+            mDevServerHelper.launchChromeDevtools();
+          } else {
+            mIsUsingJSProxy = false;
+          }
+          handleReloadJS();
+        }
+      }
+    };
+
+    // We store JS bundle loaded from dev server in a single destination in app's data dir.
+    // In case when someone schedule 2 subsequent reloads it may happen that JS thread will
+    // start reading first reload output while the second reload starts writing to the same
+    // file. As this should only be the case in dev mode we leave it as it is.
+    // TODO(6418010): Fix readers-writers problem in debug reload from HTTP server
+    mJSBundleTempFile = new File(applicationContext.getFilesDir(), JS_BUNDLE_FILE_NAME);
+
+    mDefaultNativeModuleCallExceptionHandler = new DefaultNativeModuleCallExceptionHandler();
+
+    setDevSupportEnabled(enableOnCreate);
+  }
+
+  @Override
+  public void handleException(Exception e) {
+    if (mIsDevSupportEnabled) {
+      FLog.e(ReactConstants.TAG, "Exception in native call from JS", e);
+      showNewJavaError(e.getMessage(), e);
+    } else {
+      mDefaultNativeModuleCallExceptionHandler.handleException(e);
+    }
+  }
+
+  @Override
+  public void showNewJavaError(String message, Throwable e) {
+    showNewError(message, StackTraceHelper.convertJavaStackTrace(e), JAVA_ERROR_COOKIE);
+  }
+
+  /**
+   * Add option item to dev settings dialog displayed by this manager. In the case user select given
+   * option from that dialog, the appropriate handler passed as {@param optionHandler} will be
+   * called.
+   */
+  @Override
+  public void addCustomDevOption(
+      String optionName,
+      DevOptionHandler optionHandler) {
+    mCustomDevOptions.put(optionName, optionHandler);
+  }
+
+  @Override
+  public void showNewJSError(String message, ReadableArray details, int errorCookie) {
+    showNewError(message, StackTraceHelper.convertJsStackTrace(details), errorCookie);
+  }
+
+  @Override
+  public void updateJSError(
+      final String message,
+      final ReadableArray details,
+      final int errorCookie) {
+    UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            // Since we only show the first JS error in a succession of JS errors, make sure we only
+            // update the error message for that error message. This assumes that updateJSError
+            // belongs to the most recent showNewJSError
+            if (mRedBoxDialog == null ||
+                !mRedBoxDialog.isShowing() ||
+                errorCookie != mRedBoxDialog.getErrorCookie()) {
+              return;
+            }
+            mRedBoxDialog.setExceptionDetails(
+                message,
+                StackTraceHelper.convertJsStackTrace(details));
+            mRedBoxDialog.show();
+          }
+        });
+  }
+
+  @Override
+  public void hideRedboxDialog() {
+    // dismiss redbox if exists
+    if (mRedBoxDialog != null) {
+      mRedBoxDialog.dismiss();
+    }
+  }
+
+  private void showNewError(
+      final String message,
+      final StackFrame[] stack,
+      final int errorCookie) {
+    UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            if (mRedBoxDialog == null) {
+              mRedBoxDialog = new RedBoxDialog(mApplicationContext, DevSupportManagerImpl.this);
+              mRedBoxDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            }
+            if (mRedBoxDialog.isShowing()) {
+              // Sometimes errors cause multiple errors to be thrown in JS in quick succession. Only
+              // show the first and most actionable one.
+              return;
+            }
+            mRedBoxDialog.setExceptionDetails(message, stack);
+            mRedBoxDialog.setErrorCookie(errorCookie);
+            mRedBoxDialog.show();
+          }
+        });
+  }
+
+  @Override
+  public void showDevOptionsDialog() {
+    if (mDevOptionsDialog != null || !mIsDevSupportEnabled) {
+      return;
+    }
+    LinkedHashMap<String, DevOptionHandler> options = new LinkedHashMap<>();
+    /* register standard options */
+    options.put(
+        mApplicationContext.getString(R.string.catalyst_reloadjs), new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            handleReloadJS();
+          }
+        });
+    options.put(
+        mIsUsingJSProxy ?
+            mApplicationContext.getString(R.string.catalyst_debugjs_off) :
+            mApplicationContext.getString(R.string.catalyst_debugjs),
+        new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            mIsUsingJSProxy = !mIsUsingJSProxy;
+            handleReloadJS();
+          }
+        });
+    options.put(
+            mDevSettings.isHotModuleReplacementEnabled()
+                    ? mApplicationContext.getString(R.string.catalyst_hot_module_replacement_off)
+                    : mApplicationContext.getString(R.string.catalyst_hot_module_replacement),
+            new DevOptionHandler() {
+              @Override
+              public void onOptionSelected() {
+                mDevSettings.setHotModuleReplacementEnabled(!mDevSettings.isHotModuleReplacementEnabled());
+                handleReloadJS();
+              }
+            });
+    options.put(
+        mDevSettings.isReloadOnJSChangeEnabled()
+            ? mApplicationContext.getString(R.string.catalyst_live_reload_off)
+            : mApplicationContext.getString(R.string.catalyst_live_reload),
+        new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            mDevSettings.setReloadOnJSChangeEnabled(!mDevSettings.isReloadOnJSChangeEnabled());
+          }
+        });
+    options.put(
+        mDevSettings.isElementInspectorEnabled()
+          ? mApplicationContext.getString(R.string.catalyst_element_inspector_off)
+          : mApplicationContext.getString(R.string.catalyst_element_inspector),
+        new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled());
+            mReactInstanceCommandsHandler.toggleElementInspector();
+          }
+        });
+    options.put(
+        mDevSettings.isFpsDebugEnabled()
+            ? mApplicationContext.getString(R.string.catalyst_perf_monitor_off)
+            : mApplicationContext.getString(R.string.catalyst_perf_monitor),
+        new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            mDevSettings.setFpsDebugEnabled(!mDevSettings.isFpsDebugEnabled());
+          }
+        });
+    if (mCurrentContext != null &&
+        mCurrentContext.getCatalystInstance() != null &&
+        !mCurrentContext.getCatalystInstance().isDestroyed() &&
+        mCurrentContext.getCatalystInstance().supportsProfiling()) {
+      options.put(
+          mApplicationContext.getString(
+              mIsCurrentlyProfiling ? R.string.catalyst_stop_profile :
+                  R.string.catalyst_start_profile),
+          new DevOptionHandler() {
+            @Override
+            public void onOptionSelected() {
+              if (mCurrentContext != null && mCurrentContext.hasActiveCatalystInstance()) {
+                String profileName = (Environment.getExternalStorageDirectory().getPath() +
+                    "/profile_" + mProfileIndex + ".json");
+                if (mIsCurrentlyProfiling) {
+                  mIsCurrentlyProfiling = false;
+                  mProfileIndex++;
+                  Debug.stopMethodTracing();
+                  mCurrentContext.getCatalystInstance()
+                      .stopProfiler("profile", profileName);
+                  Toast.makeText(
+                      mCurrentContext,
+                      "Profile output to " + profileName,
+                      Toast.LENGTH_LONG).show();
+                } else {
+                  mIsCurrentlyProfiling = true;
+                  mCurrentContext.getCatalystInstance().startProfiler("profile");
+                  Debug.startMethodTracingSampling(
+                      profileName,
+                      JAVA_SAMPLING_PROFILE_MEMORY_BYTES,
+                      JAVA_SAMPLING_PROFILE_DELTA_US);
+                }
+              }
+            }
+          });
+    }
+    options.put(
+        mApplicationContext.getString(R.string.catalyst_settings), new DevOptionHandler() {
+          @Override
+          public void onOptionSelected() {
+            Intent intent = new Intent(mApplicationContext, DevSettingsActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mApplicationContext.startActivity(intent);
+          }
+        });
+
+    if (mCustomDevOptions.size() > 0) {
+      options.putAll(mCustomDevOptions);
+    }
+
+    final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]);
+
+    mDevOptionsDialog =
+        new AlertDialog.Builder(mApplicationContext)
+            .setItems(
+                options.keySet().toArray(new String[0]),
+                new DialogInterface.OnClickListener() {
+                  @Override
+                  public void onClick(DialogInterface dialog, int which) {
+                    optionHandlers[which].onOptionSelected();
+                    mDevOptionsDialog = null;
+                  }
+                })
+            .setOnCancelListener(new DialogInterface.OnCancelListener() {
+              @Override
+              public void onCancel(DialogInterface dialog) {
+                mDevOptionsDialog = null;
+              }
+            })
+            .create();
+    mDevOptionsDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+    mDevOptionsDialog.show();
+  }
+
+  /**
+   * {@link ReactInstanceDevCommandsHandler} is responsible for
+   * enabling/disabling dev support when a React view is attached/detached
+   * or when application state changes (e.g. the application is backgrounded).
+   */
+  @Override
+  public void setDevSupportEnabled(boolean isDevSupportEnabled) {
+    mIsDevSupportEnabled = isDevSupportEnabled;
+    reload();
+  }
+
+  @Override
+  public boolean getDevSupportEnabled() {
+    return mIsDevSupportEnabled;
+  }
+
+  @Override
+  public DeveloperSettings getDevSettings() {
+    return mDevSettings;
+  }
+
+  @Override
+  public void onNewReactContextCreated(ReactContext reactContext) {
+    resetCurrentContext(reactContext);
+  }
+
+  @Override
+  public void onReactInstanceDestroyed(ReactContext reactContext) {
+    if (reactContext == mCurrentContext) {
+      // only call reset context when the destroyed context matches the one that is currently set
+      // for this manager
+      resetCurrentContext(null);
+    }
+  }
+
+  @Override
+  public String getSourceMapUrl() {
+    if (mJSAppBundleName == null) {
+      return "";
+    }
+
+    return mDevServerHelper.getSourceMapUrl(Assertions.assertNotNull(mJSAppBundleName));
+  }
+
+  @Override
+  public String getSourceUrl() {
+    if (mJSAppBundleName == null) {
+      return "";
+    }
+
+    return mDevServerHelper.getSourceUrl(Assertions.assertNotNull(mJSAppBundleName));
+  }
+
+  @Override
+  public String getJSBundleURLForRemoteDebugging() {
+    return mDevServerHelper.getJSBundleURLForRemoteDebugging(
+        Assertions.assertNotNull(mJSAppBundleName));
+  }
+
+  @Override
+  public String getDownloadedJSBundleFile() {
+    return mJSBundleTempFile.getAbsolutePath();
+  }
+
+  /**
+   * @return {@code true} if {@link ReactInstanceManager} should use downloaded JS bundle file
+   * instead of using JS file from assets. This may happen when app has not been updated since
+   * the last time we fetched the bundle.
+   */
+  @Override
+  public boolean hasUpToDateJSBundleInCache() {
+    if (mIsDevSupportEnabled && mJSBundleTempFile.exists()) {
+      try {
+        String packageName = mApplicationContext.getPackageName();
+        PackageInfo thisPackage = mApplicationContext.getPackageManager()
+            .getPackageInfo(packageName, 0);
+        if (mJSBundleTempFile.lastModified() > thisPackage.lastUpdateTime) {
+          // Base APK has not been updated since we donwloaded JS, but if app is using exopackage
+          // it may only be a single dex that has been updated. We check for exopackage dir update
+          // time in that case.
+          File exopackageDir = new File(
+              String.format(Locale.US, EXOPACKAGE_LOCATION_FORMAT, packageName));
+          if (exopackageDir.exists()) {
+            return mJSBundleTempFile.lastModified() > exopackageDir.lastModified();
+          }
+          return true;
+        }
+      } catch (PackageManager.NameNotFoundException e) {
+        // Ignore this error and just fallback to loading JS from assets
+        FLog.e(ReactConstants.TAG, "DevSupport is unable to get current app info");
+      }
+    }
+    return false;
+  }
+
+  /**
+   * @return {@code true} if JS bundle {@param bundleAssetName} exists, in that case
+   * {@link ReactInstanceManager} should use that file from assets instead of downloading bundle
+   * from dev server
+   */
+  public boolean hasBundleInAssets(String bundleAssetName) {
+    try {
+      String[] assets = mApplicationContext.getAssets().list("");
+      for (int i = 0; i < assets.length; i++) {
+        if (assets[i].equals(bundleAssetName)) {
+          return true;
+        }
+      }
+    } catch (IOException e) {
+      // Ignore this error and just fallback to downloading JS from devserver
+      FLog.e(ReactConstants.TAG, "Error while loading assets list");
+    }
+    return false;
+  }
+
+  private void resetCurrentContext(@Nullable ReactContext reactContext) {
+    if (mCurrentContext == reactContext) {
+      // new context is the same as the old one - do nothing
+      return;
+    }
+
+    // if currently profiling stop and write the profile file
+    if (mIsCurrentlyProfiling) {
+      mIsCurrentlyProfiling = false;
+      String profileName = (Environment.getExternalStorageDirectory().getPath() +
+          "/profile_" + mProfileIndex + ".json");
+      mProfileIndex++;
+      Debug.stopMethodTracing();
+      mCurrentContext.getCatalystInstance().stopProfiler("profile", profileName);
+    }
+
+    mCurrentContext = reactContext;
+
+    // Recreate debug overlay controller with new CatalystInstance object
+    if (mDebugOverlayController != null) {
+      mDebugOverlayController.setFpsDebugViewVisible(false);
+    }
+    if (reactContext != null) {
+      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();
+  }
+
+  @Override
+  public void reloadSettings() {
+    reload();
+  }
+
+  @Override
+  public void handleReloadJS() {
+    UiThreadUtil.assertOnUiThread();
+
+    // dismiss redbox if exists
+    if (mRedBoxDialog != null) {
+      mRedBoxDialog.dismiss();
+    }
+
+    ProgressDialog progressDialog = new ProgressDialog(mApplicationContext);
+    progressDialog.setTitle(R.string.catalyst_jsload_title);
+    progressDialog.setMessage(mApplicationContext.getString(
+        mIsUsingJSProxy ? R.string.catalyst_remotedbg_message : R.string.catalyst_jsload_message));
+    progressDialog.setIndeterminate(true);
+    progressDialog.setCancelable(false);
+    progressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+    progressDialog.show();
+
+    if (mIsUsingJSProxy) {
+      reloadJSInProxyMode(progressDialog);
+    } else {
+      reloadJSFromServer(progressDialog);
+    }
+  }
+
+  @Override
+  public void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback) {
+    mDevServerHelper.isPackagerRunning(callback);
+  }
+
+  private void reloadJSInProxyMode(final ProgressDialog progressDialog) {
+    // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that
+    // anyway
+    mDevServerHelper.launchChromeDevtools();
+
+    JavaJSExecutor.Factory factory = new JavaJSExecutor.Factory() {
+      @Override
+      public JavaJSExecutor create() throws Exception {
+        WebsocketJavaScriptExecutor executor = new WebsocketJavaScriptExecutor();
+        SimpleSettableFuture<Boolean> future = new SimpleSettableFuture<>();
+        executor.connect(
+            mDevServerHelper.getWebsocketProxyURL(),
+            getExecutorConnectCallback(progressDialog, future));
+        // TODO(t9349129) Don't use timeout
+        try {
+          future.get(90, TimeUnit.SECONDS);
+          return executor;
+        } catch (ExecutionException e) {
+          throw (Exception) e.getCause();
+        } catch (InterruptedException | TimeoutException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+    mReactInstanceCommandsHandler.onReloadWithJSDebugger(factory);
+  }
+
+  private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback(
+      final ProgressDialog progressDialog,
+      final SimpleSettableFuture<Boolean> future) {
+    return new WebsocketJavaScriptExecutor.JSExecutorConnectCallback() {
+      @Override
+      public void onSuccess() {
+        future.set(true);
+        progressDialog.dismiss();
+      }
+
+      @Override
+      public void onFailure(final Throwable cause) {
+        progressDialog.dismiss();
+        FLog.e(ReactConstants.TAG, "Unable to connect to remote debugger", cause);
+        future.setException(
+            new IOException(
+                mApplicationContext.getString(R.string.catalyst_remotedbg_error), cause));
+      }
+    };
+  }
+
+  private void reloadJSFromServer(final ProgressDialog progressDialog) {
+    mDevServerHelper.downloadBundleFromURL(
+        new DevServerHelper.BundleDownloadCallback() {
+          @Override
+          public void onSuccess() {
+            progressDialog.dismiss();
+            UiThreadUtil.runOnUiThread(
+                new Runnable() {
+                  @Override
+                  public void run() {
+                    mReactInstanceCommandsHandler.onJSBundleLoadedFromServer();
+                  }
+                });
+          }
+
+          @Override
+          public void onFailure(final Exception cause) {
+            progressDialog.dismiss();
+            FLog.e(ReactConstants.TAG, "Unable to download JS bundle", cause);
+            UiThreadUtil.runOnUiThread(
+                new Runnable() {
+                  @Override
+                  public void run() {
+                    if (cause instanceof DebugServerException) {
+                      DebugServerException debugServerException = (DebugServerException) cause;
+                      showNewJavaError(debugServerException.getMessage(), cause);
+                    } else {
+                      showNewJavaError(
+                          mApplicationContext.getString(R.string.catalyst_jsload_error),
+                          cause);
+                    }
+                  }
+                });
+          }
+        },
+        Assertions.assertNotNull(mJSAppBundleName),
+        mJSBundleTempFile);
+    progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
+      @Override
+      public void onCancel(DialogInterface dialog) {
+        mDevServerHelper.cancelDownloadBundleFromURL();
+      }
+    });
+    progressDialog.setCancelable(true);
+  }
+
+  private void reload() {
+    // reload settings, show/hide debug overlay if required & start/stop shake detector
+    if (mIsDevSupportEnabled) {
+      // update visibility of FPS debug overlay depending on the settings
+      if (mDebugOverlayController != null) {
+        mDebugOverlayController.setFpsDebugViewVisible(mDevSettings.isFpsDebugEnabled());
+      }
+
+      // start shake gesture detector
+      if (!mIsShakeDetectorStarted) {
+        mShakeDetector.start(
+            (SensorManager) mApplicationContext.getSystemService(Context.SENSOR_SERVICE));
+        mIsShakeDetectorStarted = true;
+      }
+
+      // register reload app broadcast receiver
+      if (!mIsReceiverRegistered) {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DevServerHelper.getReloadAppAction(mApplicationContext));
+        mApplicationContext.registerReceiver(mReloadAppBroadcastReceiver, filter);
+        mIsReceiverRegistered = true;
+      }
+
+      if (mDevSettings.isReloadOnJSChangeEnabled()) {
+        mDevServerHelper.startPollingOnChangeEndpoint(
+            new DevServerHelper.OnServerContentChangeListener() {
+          @Override
+          public void onServerContentChanged() {
+            handleReloadJS();
+          }
+        });
+      } else {
+        mDevServerHelper.stopPollingOnChangeEndpoint();
+      }
+    } else {
+      // hide FPS debug overlay
+      if (mDebugOverlayController != null) {
+        mDebugOverlayController.setFpsDebugViewVisible(false);
+      }
+
+      // stop shake gesture detector
+      if (mIsShakeDetectorStarted) {
+        mShakeDetector.stop();
+        mIsShakeDetectorStarted = false;
+      }
+
+      // unregister app reload broadcast receiver
+      if (mIsReceiverRegistered) {
+        mApplicationContext.unregisterReceiver(mReloadAppBroadcastReceiver);
+        mIsReceiverRegistered = false;
+      }
+
+      // hide redbox dialog
+      if (mRedBoxDialog != null) {
+        mRedBoxDialog.dismiss();
+      }
+
+      // hide dev options dialog
+      if (mDevOptionsDialog != null) {
+        mDevOptionsDialog.dismiss();
+      }
+
+      mDevServerHelper.stopPollingOnChangeEndpoint();
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java
new file mode 100644
index 000000000000..5388f98b7d1a
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java
@@ -0,0 +1,128 @@
+/**
+ * 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.DefaultNativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.modules.debug.DeveloperSettings;
+
+/**
+ * A dummy implementation of {@link DevSupportManager} to be used in production mode where
+ * development features aren't needed.
+ */
+public class DisabledDevSupportManager implements DevSupportManager {
+
+  private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler;
+
+  public DisabledDevSupportManager() {
+    mDefaultNativeModuleCallExceptionHandler = new DefaultNativeModuleCallExceptionHandler();
+  }
+
+  @Override
+  public void showNewJavaError(String message, Throwable e) {
+
+  }
+
+  @Override
+  public void addCustomDevOption(String optionName, DevOptionHandler optionHandler) {
+
+  }
+
+  @Override
+  public void showNewJSError(String message, ReadableArray details, int errorCookie) {
+
+  }
+
+  @Override
+  public void updateJSError(String message, ReadableArray details, int errorCookie) {
+
+  }
+
+  @Override
+  public void hideRedboxDialog() {
+
+  }
+
+  @Override
+  public void showDevOptionsDialog() {
+
+  }
+
+  @Override
+  public void setDevSupportEnabled(boolean isDevSupportEnabled) {
+
+  }
+
+  @Override
+  public boolean getDevSupportEnabled() {
+    return false;
+  }
+
+  @Override
+  public DeveloperSettings getDevSettings() {
+    return null;
+  }
+
+  @Override
+  public void onNewReactContextCreated(ReactContext reactContext) {
+
+  }
+
+  @Override
+  public void onReactInstanceDestroyed(ReactContext reactContext) {
+
+  }
+
+  @Override
+  public String getSourceMapUrl() {
+    return null;
+  }
+
+  @Override
+  public String getSourceUrl() {
+    return null;
+  }
+
+  @Override
+  public String getJSBundleURLForRemoteDebugging() {
+    return null;
+  }
+
+  @Override
+  public String getDownloadedJSBundleFile() {
+    return null;
+  }
+
+  @Override
+  public boolean hasUpToDateJSBundleInCache() {
+    return false;
+  }
+
+  @Override
+  public void reloadSettings() {
+
+  }
+
+  @Override
+  public void handleReloadJS() {
+
+  }
+
+  @Override
+  public void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback) {
+
+  }
+
+  @Override
+  public void handleException(Exception e) {
+    mDefaultNativeModuleCallExceptionHandler.handleException(e);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java
new file mode 100644
index 000000000000..8ef0e5e35bc1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java
@@ -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);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java
index 5489853b085f..ce3639be86ef 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java
@@ -9,7 +9,7 @@
 
 package com.facebook.react.devsupport;
 
-import com.facebook.react.bridge.ProxyJavaScriptExecutor;
+import com.facebook.react.bridge.JavaJSExecutor;
 
 /**
  * Interface used by {@link DevSupportManager} for requesting React instance recreation
@@ -20,7 +20,7 @@ public interface ReactInstanceDevCommandsHandler {
   /**
    * Request react instance recreation with JS debugging enabled.
    */
-  void onReloadWithJSDebugger(ProxyJavaScriptExecutor proxyExecutor);
+  void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory);
 
   /**
    * Notify react instance manager about new JS bundle version downloaded from the server.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java
new file mode 100644
index 000000000000..a3daa95a78bc
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java
@@ -0,0 +1,76 @@
+/**
+ * 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.modules.appstate;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+public class AppStateModule extends ReactContextBaseJavaModule
+        implements LifecycleEventListener {
+
+  public static final String APP_STATE_ACTIVE = "active";
+  public static final String APP_STATE_BACKGROUND = "background";
+
+  private String mAppState = "uninitialized";
+
+  public AppStateModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "AppState";
+  }
+
+  @Override
+  public void initialize() {
+    getReactApplicationContext().addLifecycleEventListener(this);
+  }
+
+  @ReactMethod
+  public void getCurrentAppState(Callback success, Callback error) {
+    success.invoke(createAppStateEventMap());
+  }
+
+  @Override
+  public void onHostResume() {
+    mAppState = APP_STATE_ACTIVE;
+    sendAppStateChangeEvent();
+  }
+
+  @Override
+  public void onHostPause() {
+    mAppState = APP_STATE_BACKGROUND;
+    sendAppStateChangeEvent();
+  }
+
+  @Override
+  public void onHostDestroy() {
+    // do not set state to destroyed, do not send an event. By the current implementation, the
+    // catalyst instance is going to be immediately dropped, and all JS calls with it.
+  }
+
+  private WritableMap createAppStateEventMap() {
+    WritableMap appState = Arguments.createMap();
+    appState.putString("app_state", mAppState);
+    return appState;
+  }
+
+  private void sendAppStateChangeEvent() {
+    getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+            .emit("appStateDidChange", createAppStateEventMap());
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK
new file mode 100644
index 000000000000..27cc8a7cdb73
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/BUCK
@@ -0,0 +1,21 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'appstate',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':appstate',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK
new file mode 100644
index 000000000000..cede97b16073
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'camera',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':camera',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java
new file mode 100644
index 000000000000..e5302355c7e4
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/CameraRollManager.java
@@ -0,0 +1,439 @@
+/**
+ * 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.modules.camera;
+
+import javax.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.text.TextUtils;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.GuardedAsyncTask;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeArray;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.common.ReactConstants;
+
+// TODO #6015104: rename to something less iOSish
+/**
+ * {@link NativeModule} that allows JS to interact with the photos on the device (i.e.
+ * {@link MediaStore.Images}).
+ */
+public class CameraRollManager extends ReactContextBaseJavaModule {
+
+  private static final String ERROR_UNABLE_TO_LOAD = "E_UNABLE_TO_LOAD";
+  private static final String ERROR_UNABLE_TO_LOAD_PERMISSION = "E_UNABLE_TO_LOAD_PERMISSION";
+  private static final String ERROR_UNABLE_TO_SAVE = "E_UNABLE_TO_SAVE";
+
+  public static final boolean IS_JELLY_BEAN_OR_LATER =
+      Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+
+  private static final String[] PROJECTION;
+  static {
+    if (IS_JELLY_BEAN_OR_LATER) {
+      PROJECTION = new String[] {
+          Images.Media._ID,
+          Images.Media.MIME_TYPE,
+          Images.Media.BUCKET_DISPLAY_NAME,
+          Images.Media.DATE_TAKEN,
+          Images.Media.WIDTH,
+          Images.Media.HEIGHT,
+          Images.Media.LONGITUDE,
+          Images.Media.LATITUDE
+      };
+    } else {
+      PROJECTION = new String[] {
+          Images.Media._ID,
+          Images.Media.MIME_TYPE,
+          Images.Media.BUCKET_DISPLAY_NAME,
+          Images.Media.DATE_TAKEN,
+          Images.Media.LONGITUDE,
+          Images.Media.LATITUDE
+      };
+    }
+  }
+
+  private static final String SELECTION_BUCKET = Images.Media.BUCKET_DISPLAY_NAME + " = ?";
+  private static final String SELECTION_DATE_TAKEN = Images.Media.DATE_TAKEN + " < ?";
+
+  public CameraRollManager(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "RKCameraRollManager";
+  }
+
+  @Override
+  public Map<String, Object> getConstants() {
+    return Collections.emptyMap();
+  }
+
+  /**
+   * Save an image to the gallery (i.e. {@link MediaStore.Images}). This copies the original file
+   * from wherever it may be to the external storage pictures directory, so that it can be scanned
+   * by the MediaScanner.
+   *
+   * @param uri the file:// URI of the image to save
+   * @param promise to be resolved or rejected
+   */
+  @ReactMethod
+  public void saveImageWithTag(String uri, Promise promise) {
+    new SaveImageTag(getReactApplicationContext(), Uri.parse(uri), promise)
+        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  private static class SaveImageTag extends GuardedAsyncTask<Void, Void> {
+
+    private final Context mContext;
+    private final Uri mUri;
+    private final Promise mPromise;
+
+    public SaveImageTag(ReactContext context, Uri uri, Promise promise) {
+      super(context);
+      mContext = context;
+      mUri = uri;
+      mPromise = promise;
+    }
+
+    @Override
+    protected void doInBackgroundGuarded(Void... params) {
+      File source = new File(mUri.getPath());
+      FileChannel input = null, output = null;
+      try {
+        File pictures =
+            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+        pictures.mkdirs();
+        if (!pictures.isDirectory()) {
+          mPromise.reject(ERROR_UNABLE_TO_LOAD, "External storage pictures directory not available");
+          return;
+        }
+        File dest = new File(pictures, source.getName());
+        int n = 0;
+        String fullSourceName = source.getName();
+        String sourceName, sourceExt;
+        if (fullSourceName.indexOf('.') >= 0) {
+          sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.'));
+          sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.'));
+        } else {
+          sourceName = fullSourceName;
+          sourceExt = "";
+        }
+        while (!dest.createNewFile()) {
+          dest = new File(pictures, sourceName + "_" + (n++) + sourceExt);
+        }
+        input = new FileInputStream(source).getChannel();
+        output = new FileOutputStream(dest).getChannel();
+        output.transferFrom(input, 0, input.size());
+        input.close();
+        output.close();
+
+        MediaScannerConnection.scanFile(
+            mContext,
+            new String[]{dest.getAbsolutePath()},
+            null,
+            new MediaScannerConnection.OnScanCompletedListener() {
+              @Override
+              public void onScanCompleted(String path, Uri uri) {
+                if (uri != null) {
+                  mPromise.resolve(uri.toString());
+                } else {
+                  mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
+                }
+              }
+            });
+      } catch (IOException e) {
+        mPromise.reject(e);
+      } finally {
+        if (input != null && input.isOpen()) {
+          try {
+            input.close();
+          } catch (IOException e) {
+            FLog.e(ReactConstants.TAG, "Could not close input channel", e);
+          }
+        }
+        if (output != null && output.isOpen()) {
+          try {
+            output.close();
+          } catch (IOException e) {
+            FLog.e(ReactConstants.TAG, "Could not close output channel", e);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get photos from {@link MediaStore.Images}, most recent first.
+   *
+   * @param params a map containing the following keys:
+   *        <ul>
+   *          <li>first (mandatory): a number representing the number of photos to fetch</li>
+   *          <li>
+   *            after (optional): a cursor that matches page_info[end_cursor] returned by a
+   *            previous call to {@link #getPhotos}
+   *          </li>
+   *          <li>groupName (optional): an album name</li>
+   *          <li>
+   *            mimeType (optional): restrict returned images to a specific mimetype (e.g.
+   *            image/jpeg)
+   *          </li>
+   *        </ul>
+   * @param promise the Promise to be resolved when the photos are loaded; for a format of the
+   *        parameters passed to this callback, see {@code getPhotosReturnChecker} in CameraRoll.js
+   */
+  @ReactMethod
+  public void getPhotos(final ReadableMap params, final Promise promise) {
+    int first = params.getInt("first");
+    String after = params.hasKey("after") ? params.getString("after") : null;
+    String groupName = params.hasKey("groupName") ? params.getString("groupName") : null;
+    ReadableArray mimeTypes = params.hasKey("mimeTypes")
+        ? params.getArray("mimeTypes")
+        : null;
+    if (params.hasKey("groupTypes")) {
+      throw new JSApplicationIllegalArgumentException("groupTypes is not supported on Android");
+    }
+
+    new GetPhotosTask(
+          getReactApplicationContext(),
+          first,
+          after,
+          groupName,
+          mimeTypes,
+          promise)
+          .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  private static class GetPhotosTask extends GuardedAsyncTask<Void, Void> {
+    private final Context mContext;
+    private final int mFirst;
+    private final @Nullable String mAfter;
+    private final @Nullable String mGroupName;
+    private final @Nullable ReadableArray mMimeTypes;
+    private final Promise mPromise;
+
+    private GetPhotosTask(
+        ReactContext context,
+        int first,
+        @Nullable String after,
+        @Nullable String groupName,
+        @Nullable ReadableArray mimeTypes,
+        Promise promise) {
+      super(context);
+      mContext = context;
+      mFirst = first;
+      mAfter = after;
+      mGroupName = groupName;
+      mMimeTypes = mimeTypes;
+      mPromise = promise;
+    }
+
+    @Override
+    protected void doInBackgroundGuarded(Void... params) {
+      StringBuilder selection = new StringBuilder("1");
+      List<String> selectionArgs = new ArrayList<>();
+      if (!TextUtils.isEmpty(mAfter)) {
+        selection.append(" AND " + SELECTION_DATE_TAKEN);
+        selectionArgs.add(mAfter);
+      }
+      if (!TextUtils.isEmpty(mGroupName)) {
+        selection.append(" AND " + SELECTION_BUCKET);
+        selectionArgs.add(mGroupName);
+      }
+      if (mMimeTypes != null && mMimeTypes.size() > 0) {
+        selection.append(" AND " + Images.Media.MIME_TYPE + " IN (");
+        for (int i = 0; i < mMimeTypes.size(); i++) {
+          selection.append("?,");
+          selectionArgs.add(mMimeTypes.getString(i));
+        }
+        selection.replace(selection.length() - 1, selection.length(), ")");
+      }
+      WritableMap response = new WritableNativeMap();
+      ContentResolver resolver = mContext.getContentResolver();
+      // using LIMIT in the sortOrder is not explicitly supported by the SDK (which does not support
+      // setting a limit at all), but it works because this specific ContentProvider is backed by
+      // an SQLite DB and forwards parameters to it without doing any parsing / validation.
+      try {
+        Cursor photos = resolver.query(
+            Images.Media.EXTERNAL_CONTENT_URI,
+            PROJECTION,
+            selection.toString(),
+            selectionArgs.toArray(new String[selectionArgs.size()]),
+            Images.Media.DATE_TAKEN + " DESC, " + Images.Media.DATE_MODIFIED + " DESC LIMIT " +
+                (mFirst + 1)); // set LIMIT to first + 1 so that we know how to populate page_info
+        if (photos == null) {
+          mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos");
+        } else {
+          try {
+            putEdges(resolver, photos, response, mFirst);
+            putPageInfo(photos, response, mFirst);
+          } finally {
+            photos.close();
+            mPromise.resolve(response);
+          }
+        }
+      } catch (SecurityException e) {
+        mPromise.reject(
+            ERROR_UNABLE_TO_LOAD_PERMISSION,
+            "Could not get photos: need READ_EXTERNAL_STORAGE permission",
+            e);
+      }
+    }
+  }
+
+  private static void putPageInfo(Cursor photos, WritableMap response, int limit) {
+    WritableMap pageInfo = new WritableNativeMap();
+    pageInfo.putBoolean("has_next_page", limit < photos.getCount());
+    if (limit < photos.getCount()) {
+      photos.moveToPosition(limit - 1);
+      pageInfo.putString(
+          "end_cursor",
+          photos.getString(photos.getColumnIndex(Images.Media.DATE_TAKEN)));
+    }
+    response.putMap("page_info", pageInfo);
+  }
+
+  private static void putEdges(
+      ContentResolver resolver,
+      Cursor photos,
+      WritableMap response,
+      int limit) {
+    WritableArray edges = new WritableNativeArray();
+    photos.moveToFirst();
+    int idIndex = photos.getColumnIndex(Images.Media._ID);
+    int mimeTypeIndex = photos.getColumnIndex(Images.Media.MIME_TYPE);
+    int groupNameIndex = photos.getColumnIndex(Images.Media.BUCKET_DISPLAY_NAME);
+    int dateTakenIndex = photos.getColumnIndex(Images.Media.DATE_TAKEN);
+    int widthIndex = IS_JELLY_BEAN_OR_LATER ? photos.getColumnIndex(Images.Media.WIDTH) : -1;
+    int heightIndex = IS_JELLY_BEAN_OR_LATER ? photos.getColumnIndex(Images.Media.HEIGHT) : -1;
+    int longitudeIndex = photos.getColumnIndex(Images.Media.LONGITUDE);
+    int latitudeIndex = photos.getColumnIndex(Images.Media.LATITUDE);
+
+    for (int i = 0; i < limit && !photos.isAfterLast(); i++) {
+      WritableMap edge = new WritableNativeMap();
+      WritableMap node = new WritableNativeMap();
+      boolean imageInfoSuccess =
+          putImageInfo(resolver, photos, node, idIndex, widthIndex, heightIndex);
+      if (imageInfoSuccess) {
+        putBasicNodeInfo(photos, node, mimeTypeIndex, groupNameIndex, dateTakenIndex);
+        putLocationInfo(photos, node, longitudeIndex, latitudeIndex);
+
+        edge.putMap("node", node);
+        edges.pushMap(edge);
+      } else {
+        // we skipped an image because we couldn't get its details (e.g. width/height), so we
+        // decrement i in order to correctly reach the limit, if the cursor has enough rows
+        i--;
+      }
+      photos.moveToNext();
+    }
+    response.putArray("edges", edges);
+  }
+
+  private static void putBasicNodeInfo(
+      Cursor photos,
+      WritableMap node,
+      int mimeTypeIndex,
+      int groupNameIndex,
+      int dateTakenIndex) {
+    node.putString("type", photos.getString(mimeTypeIndex));
+    node.putString("group_name", photos.getString(groupNameIndex));
+    node.putDouble("timestamp", photos.getLong(dateTakenIndex) / 1000d);
+  }
+
+  private static boolean putImageInfo(
+      ContentResolver resolver,
+      Cursor photos,
+      WritableMap node,
+      int idIndex,
+      int widthIndex,
+      int heightIndex) {
+    WritableMap image = new WritableNativeMap();
+    Uri photoUri = Uri.withAppendedPath(
+        Images.Media.EXTERNAL_CONTENT_URI,
+        photos.getString(idIndex));
+    image.putString("uri", photoUri.toString());
+    float width = -1;
+    float height = -1;
+    if (IS_JELLY_BEAN_OR_LATER) {
+      width = photos.getInt(widthIndex);
+      height = photos.getInt(heightIndex);
+    }
+    if (width <= 0 || height <= 0) {
+      try {
+        AssetFileDescriptor photoDescriptor = resolver.openAssetFileDescriptor(photoUri, "r");
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        // Set inJustDecodeBounds to true so we don't actually load the Bitmap, but only get its
+        // dimensions instead.
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFileDescriptor(photoDescriptor.getFileDescriptor(), null, options);
+        photoDescriptor.close();
+
+        width = options.outWidth;
+        height = options.outHeight;
+      } catch (IOException e) {
+        FLog.e(ReactConstants.TAG, "Could not get width/height for " + photoUri.toString(), e);
+        return false;
+      }
+    }
+    image.putDouble("width", width);
+    image.putDouble("height", height);
+    node.putMap("image", image);
+    return true;
+  }
+
+  private static void putLocationInfo(
+      Cursor photos,
+      WritableMap node,
+      int longitudeIndex,
+      int latitudeIndex) {
+    double longitude = photos.getDouble(longitudeIndex);
+    double latitude = photos.getDouble(latitudeIndex);
+    if (longitude > 0 || latitude > 0) {
+      WritableMap location = new WritableNativeMap();
+      location.putDouble("longitude", longitude);
+      location.putDouble("latitude", latitude);
+      node.putMap("location", location);
+    }
+  }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java
new file mode 100644
index 000000000000..be9ef2f892ea
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java
@@ -0,0 +1,521 @@
+/**
+ * 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.modules.camera;
+
+import javax.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.text.TextUtils;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.GuardedAsyncTask;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.common.ReactConstants;
+
+/**
+ * Native module that provides image cropping functionality.
+ */
+public class ImageEditingManager extends ReactContextBaseJavaModule {
+
+  private static final List<String> LOCAL_URI_PREFIXES = Arrays.asList(
+      "file://", "content://");
+
+  private static final String TEMP_FILE_PREFIX = "ReactNative_cropped_image_";
+
+  /** Compress quality of the output file. */
+  private static final int COMPRESS_QUALITY = 90;
+
+  @SuppressLint("InlinedApi") private static final String[] EXIF_ATTRIBUTES = new String[] {
+    ExifInterface.TAG_APERTURE,
+    ExifInterface.TAG_DATETIME,
+    ExifInterface.TAG_DATETIME_DIGITIZED,
+    ExifInterface.TAG_EXPOSURE_TIME,
+    ExifInterface.TAG_FLASH,
+    ExifInterface.TAG_FOCAL_LENGTH,
+    ExifInterface.TAG_GPS_ALTITUDE,
+    ExifInterface.TAG_GPS_ALTITUDE_REF,
+    ExifInterface.TAG_GPS_DATESTAMP,
+    ExifInterface.TAG_GPS_LATITUDE,
+    ExifInterface.TAG_GPS_LATITUDE_REF,
+    ExifInterface.TAG_GPS_LONGITUDE,
+    ExifInterface.TAG_GPS_LONGITUDE_REF,
+    ExifInterface.TAG_GPS_PROCESSING_METHOD,
+    ExifInterface.TAG_GPS_TIMESTAMP,
+    ExifInterface.TAG_IMAGE_LENGTH,
+    ExifInterface.TAG_IMAGE_WIDTH,
+    ExifInterface.TAG_ISO,
+    ExifInterface.TAG_MAKE,
+    ExifInterface.TAG_MODEL,
+    ExifInterface.TAG_ORIENTATION,
+    ExifInterface.TAG_SUBSEC_TIME,
+    ExifInterface.TAG_SUBSEC_TIME_DIG,
+    ExifInterface.TAG_SUBSEC_TIME_ORIG,
+    ExifInterface.TAG_WHITE_BALANCE
+  };
+
+
+  public ImageEditingManager(ReactApplicationContext reactContext) {
+    super(reactContext);
+    new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  @Override
+  public String getName() {
+    return "RKImageEditingManager";
+  }
+
+  @Override
+  public Map<String, Object> getConstants() {
+    return Collections.emptyMap();
+  }
+
+  @Override
+  public void onCatalystInstanceDestroy() {
+    new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  /**
+   * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
+   * image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting
+   * down) and when the module is instantiated, to handle the case where the app crashed.
+   */
+  private static class CleanTask extends GuardedAsyncTask<Void, Void> {
+    private final Context mContext;
+
+    private CleanTask(ReactContext context) {
+      super(context);
+      mContext = context;
+    }
+
+    @Override
+    protected void doInBackgroundGuarded(Void... params) {
+      cleanDirectory(mContext.getCacheDir());
+      File externalCacheDir = mContext.getExternalCacheDir();
+      if (externalCacheDir != null) {
+        cleanDirectory(externalCacheDir);
+      }
+    }
+
+    private void cleanDirectory(File directory) {
+      File[] toDelete = directory.listFiles(
+          new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String filename) {
+              return filename.startsWith(TEMP_FILE_PREFIX);
+            }
+          });
+      if (toDelete != null) {
+        for (File file: toDelete) {
+          file.delete();
+        }
+      }
+    }
+  }
+
+  /**
+   * Crop an image. If all goes well, the success callback will be called with the file:// URI of
+   * the new image as the only argument. This is a temporary file - consider using
+   * CameraRollManager.saveImageWithTag to save it in the gallery.
+   *
+   * @param uri the MediaStore URI of the image to crop
+   * @param options crop parameters specified as {@code {offset: {x, y}, size: {width, height}}}.
+   *        Optionally this also contains  {@code {targetSize: {width, height}}}. If this is
+   *        specified, the cropped image will be resized to that size.
+   *        All units are in pixels (not DPs).
+   * @param success callback to be invoked when the image has been cropped; the only argument that
+   *        is passed to this callback is the file:// URI of the new image
+   * @param error callback to be invoked when an error occurs (e.g. can't create file etc.)
+   */
+  @ReactMethod
+  public void cropImage(
+      String uri,
+      ReadableMap options,
+      final Callback success,
+      final Callback error) {
+    ReadableMap offset = options.hasKey("offset") ? options.getMap("offset") : null;
+    ReadableMap size = options.hasKey("size") ? options.getMap("size") : null;
+    if (offset == null || size == null ||
+        !offset.hasKey("x") || !offset.hasKey("y") ||
+        !size.hasKey("width") || !size.hasKey("height")) {
+      throw new JSApplicationIllegalArgumentException("Please specify offset and size");
+    }
+    if (uri == null || uri.isEmpty()) {
+      throw new JSApplicationIllegalArgumentException("Please specify a URI");
+    }
+
+    CropTask cropTask = new CropTask(
+        getReactApplicationContext(),
+        uri,
+        (int) offset.getDouble("x"),
+        (int) offset.getDouble("y"),
+        (int) size.getDouble("width"),
+        (int) size.getDouble("height"),
+        success,
+        error);
+    if (options.hasKey("displaySize")) {
+      ReadableMap targetSize = options.getMap("displaySize");
+      cropTask.setTargetSize(targetSize.getInt("width"), targetSize.getInt("height"));
+    }
+    cropTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  private static class CropTask extends GuardedAsyncTask<Void, Void> {
+    final Context mContext;
+    final String mUri;
+    final int mX;
+    final int mY;
+    final int mWidth;
+    final int mHeight;
+    int mTargetWidth = 0;
+    int mTargetHeight = 0;
+    final Callback mSuccess;
+    final Callback mError;
+
+    private CropTask(
+        ReactContext context,
+        String uri,
+        int x,
+        int y,
+        int width,
+        int height,
+        Callback success,
+        Callback error) {
+      super(context);
+      if (x < 0 || y < 0 || width <= 0 || height <= 0) {
+        throw new JSApplicationIllegalArgumentException(String.format(
+            "Invalid crop rectangle: [%d, %d, %d, %d]", x, y, width, height));
+      }
+      mContext = context;
+      mUri = uri;
+      mX = x;
+      mY = y;
+      mWidth = width;
+      mHeight = height;
+      mSuccess = success;
+      mError = error;
+    }
+
+    public void setTargetSize(int width, int height) {
+      if (width <= 0 || height <= 0) {
+        throw new JSApplicationIllegalArgumentException(String.format(
+            "Invalid target size: [%d, %d]", width, height));
+      }
+      mTargetWidth = width;
+      mTargetHeight = height;
+    }
+
+    private InputStream openBitmapInputStream() throws IOException {
+      InputStream stream;
+      if (isLocalUri(mUri)) {
+        stream = mContext.getContentResolver().openInputStream(Uri.parse(mUri));
+      } else {
+        URLConnection connection = new URL(mUri).openConnection();
+        stream = connection.getInputStream();
+      }
+      if (stream == null) {
+        throw new IOException("Cannot open bitmap: " + mUri);
+      }
+      return stream;
+    }
+
+    @Override
+    protected void doInBackgroundGuarded(Void... params) {
+      try {
+        BitmapFactory.Options outOptions = new BitmapFactory.Options();
+
+        // If we're downscaling, we can decode the bitmap more efficiently, using less memory
+        boolean hasTargetSize = (mTargetWidth > 0) && (mTargetHeight > 0);
+
+        Bitmap cropped;
+        if (hasTargetSize) {
+          cropped = cropAndResize(mTargetWidth, mTargetHeight, outOptions);
+        } else {
+          cropped = crop(outOptions);
+        }
+
+        String mimeType = outOptions.outMimeType;
+        if (mimeType == null || mimeType.isEmpty()) {
+          throw new IOException("Could not determine MIME type");
+        }
+
+        File tempFile = createTempFile(mContext, mimeType);
+        writeCompressedBitmapToFile(cropped, mimeType, tempFile);
+
+        if (mimeType.equals("image/jpeg")) {
+          copyExif(mContext, Uri.parse(mUri), tempFile);
+        }
+
+        mSuccess.invoke(Uri.fromFile(tempFile).toString());
+
+      } catch (Exception e) {
+        mError.invoke(e.getMessage());
+      }
+    }
+
+    /**
+     * Reads and crops the bitmap.
+     * @param outOptions Bitmap options, useful to determine {@code outMimeType}.
+     */
+    private Bitmap crop(BitmapFactory.Options outOptions) throws IOException {
+      InputStream inputStream = openBitmapInputStream();
+      try {
+        // This can use a lot of memory
+        Bitmap fullResolutionBitmap = BitmapFactory.decodeStream(inputStream, null, outOptions);
+        if (fullResolutionBitmap == null) {
+          throw new IOException("Cannot decode bitmap: " + mUri);
+        }
+        return Bitmap.createBitmap(fullResolutionBitmap, mX, mY, mWidth, mHeight);
+      } finally {
+        if (inputStream != null) {
+          inputStream.close();
+        }
+      }
+    }
+
+    /**
+     * Crop the rectangle given by {@code mX, mY, mWidth, mHeight} within the source bitmap
+     * and scale the result to {@code targetWidth, targetHeight}.
+     * @param outOptions Bitmap options, useful to determine {@code outMimeType}.
+     */
+    private Bitmap cropAndResize(
+        int targetWidth,
+        int targetHeight,
+        BitmapFactory.Options outOptions)
+        throws IOException {
+      Assertions.assertNotNull(outOptions);
+
+      // Loading large bitmaps efficiently:
+      // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
+
+      // Just decode the dimensions
+      BitmapFactory.Options options = new BitmapFactory.Options();
+      options.inJustDecodeBounds = true;
+      InputStream inputStream = openBitmapInputStream();
+      try {
+        BitmapFactory.decodeStream(inputStream, null, options);
+      } finally {
+        if (inputStream != null) {
+          inputStream.close();
+        }
+      }
+
+      // This uses scaling mode COVER
+
+      // Where would the crop rect end up within the scaled bitmap?
+      float newWidth, newHeight, newX, newY, scale;
+      float cropRectRatio = mWidth / (float) mHeight;
+      float targetRatio = targetWidth / (float) targetHeight;
+      if (cropRectRatio > targetRatio) {
+        // e.g. source is landscape, target is portrait
+        newWidth = mHeight * targetRatio;
+        newHeight = mHeight;
+        newX = mX + (mWidth - newWidth) / 2;
+        newY = mY;
+        scale = targetHeight / (float) mHeight;
+      } else {
+        // e.g. source is landscape, target is portrait
+        newWidth = mWidth;
+        newHeight = mWidth / targetRatio;
+        newX = mX;
+        newY = mY + (mHeight - newHeight) / 2;
+        scale = targetWidth / (float) mWidth;
+      }
+
+      // Decode the bitmap. We have to open the stream again, like in the example linked above.
+      // Is there a way to just continue reading from the stream?
+      outOptions.inSampleSize = getDecodeSampleSize(mWidth, mHeight, targetWidth, targetHeight);
+      options.inJustDecodeBounds = false;
+      inputStream = openBitmapInputStream();
+
+      Bitmap bitmap;
+      try {
+        // This can use significantly less memory than decoding the full-resolution bitmap
+        bitmap = BitmapFactory.decodeStream(inputStream, null, outOptions);
+        if (bitmap == null) {
+          throw new IOException("Cannot decode bitmap: " + mUri);
+        }
+      } finally {
+        if (inputStream != null) {
+          inputStream.close();
+        }
+      }
+
+      int cropX = (int) Math.floor(newX / (float) outOptions.inSampleSize);
+      int cropY = (int) Math.floor(newY / (float) outOptions.inSampleSize);
+      int cropWidth = (int) Math.floor(newWidth / (float) outOptions.inSampleSize);
+      int cropHeight = (int) Math.floor(newHeight / (float) outOptions.inSampleSize);
+      float cropScale = scale * outOptions.inSampleSize;
+
+      Matrix scaleMatrix = new Matrix();
+      scaleMatrix.setScale(cropScale, cropScale);
+      boolean filter = true;
+
+      return Bitmap.createBitmap(bitmap, cropX, cropY, cropWidth, cropHeight, scaleMatrix, filter);
+    }
+  }
+
+  // Utils
+
+  private static void copyExif(Context context, Uri oldImage, File newFile) throws IOException {
+    File oldFile = getFileFromUri(context, oldImage);
+    if (oldFile == null) {
+      FLog.w(ReactConstants.TAG, "Couldn't get real path for uri: " + oldImage);
+      return;
+    }
+
+    ExifInterface oldExif = new ExifInterface(oldFile.getAbsolutePath());
+    ExifInterface newExif = new ExifInterface(newFile.getAbsolutePath());
+    for (String attribute : EXIF_ATTRIBUTES) {
+      String value = oldExif.getAttribute(attribute);
+      if (value != null) {
+        newExif.setAttribute(attribute, value);
+      }
+    }
+    newExif.saveAttributes();
+  }
+
+  private static @Nullable File getFileFromUri(Context context, Uri uri) {
+    if (uri.getScheme().equals("file")) {
+      return new File(uri.getPath());
+    } else if (uri.getScheme().equals("content")) {
+      Cursor cursor = context.getContentResolver()
+        .query(uri, new String[] { MediaStore.MediaColumns.DATA }, null, null, null);
+      if (cursor != null) {
+        try {
+          if (cursor.moveToFirst()) {
+            String path = cursor.getString(0);
+            if (!TextUtils.isEmpty(path)) {
+              return new File(path);
+            }
+          }
+        } finally {
+          cursor.close();
+        }
+      }
+    }
+
+    return null;
+  }
+
+  private static boolean isLocalUri(String uri) {
+    for (String localPrefix : LOCAL_URI_PREFIXES) {
+      if (uri.startsWith(localPrefix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static String getFileExtensionForType(@Nullable String mimeType) {
+    if ("image/png".equals(mimeType)) {
+      return ".png";
+    }
+    if ("image/webp".equals(mimeType)) {
+      return ".webp";
+    }
+    return ".jpg";
+  }
+
+  private static Bitmap.CompressFormat getCompressFormatForType(String type) {
+    if ("image/png".equals(type)) {
+      return Bitmap.CompressFormat.PNG;
+    }
+    if ("image/webp".equals(type)) {
+      return Bitmap.CompressFormat.WEBP;
+    }
+    return Bitmap.CompressFormat.JPEG;
+  }
+
+  private static void writeCompressedBitmapToFile(Bitmap cropped, String mimeType, File tempFile)
+      throws IOException {
+    OutputStream out = new FileOutputStream(tempFile);
+    try {
+      cropped.compress(getCompressFormatForType(mimeType), COMPRESS_QUALITY, out);
+    } finally {
+      if (out != null) {
+        out.close();
+      }
+    }
+  }
+
+  /**
+   * Create a temporary file in the cache directory on either internal or external storage,
+   * whichever is available and has more free space.
+   *
+   * @param mimeType the MIME type of the file to create (image/*)
+   */
+  private static File createTempFile(Context context, @Nullable String mimeType)
+      throws IOException {
+    File externalCacheDir = context.getExternalCacheDir();
+    File internalCacheDir = context.getCacheDir();
+    File cacheDir;
+    if (externalCacheDir == null && externalCacheDir == null) {
+      throw new IOException("No cache directory available");
+    }
+    if (externalCacheDir == null) {
+      cacheDir = internalCacheDir;
+    }
+    else if (internalCacheDir == null) {
+      cacheDir = externalCacheDir;
+    } else {
+      cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
+          externalCacheDir : internalCacheDir;
+    }
+    return File.createTempFile(TEMP_FILE_PREFIX, getFileExtensionForType(mimeType), cacheDir);
+  }
+
+  /**
+   * When scaling down the bitmap, decode only every n-th pixel in each dimension.
+   * Calculate the largest {@code inSampleSize} value that is a power of 2 and keeps both
+   * {@code width, height} larger or equal to {@code targetWidth, targetHeight}.
+   * This can significantly reduce memory usage.
+   */
+  private static int getDecodeSampleSize(int width, int height, int targetWidth, int targetHeight) {
+    int inSampleSize = 1;
+    if (height > targetWidth || width > targetHeight) {
+      int halfHeight = height / 2;
+      int halfWidth = width / 2;
+      while ((halfWidth / inSampleSize) >= targetWidth
+          && (halfHeight / inSampleSize) >= targetHeight) {
+        inSampleSize *= 2;
+      }
+    }
+    return inSampleSize;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageStoreManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageStoreManager.java
new file mode 100644
index 000000000000..68329f42a105
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageStoreManager.java
@@ -0,0 +1,107 @@
+/**
+ * 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.modules.camera;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Base64;
+import android.util.Base64OutputStream;
+
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.GuardedAsyncTask;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+public class ImageStoreManager extends ReactContextBaseJavaModule {
+
+  private static final int BUFFER_SIZE = 8192;
+
+  public ImageStoreManager(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "ImageStoreManager";
+  }
+
+  /**
+   * Calculate the base64 representation for an image. The "tag" comes from iOS naming.
+   *
+   * @param uri the URI of the image, file:// or content://
+   * @param success callback to be invoked with the base64 string as the only argument
+   * @param error callback to be invoked on error (e.g. file not found, not readable etc.)
+   */
+  @ReactMethod
+  public void getBase64ForTag(String uri, Callback success, Callback error) {
+    new GetBase64Task(getReactApplicationContext(), uri, success, error)
+        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+  }
+
+  private class GetBase64Task extends GuardedAsyncTask<Void, Void> {
+    private final String mUri;
+    private final Callback mSuccess;
+    private final Callback mError;
+
+    private GetBase64Task(
+        ReactContext reactContext,
+        String uri,
+        Callback success,
+        Callback error) {
+      super(reactContext);
+      mUri = uri;
+      mSuccess = success;
+      mError = error;
+    }
+
+    @Override
+    protected void doInBackgroundGuarded(Void... params) {
+      try {
+        ContentResolver contentResolver = getReactApplicationContext().getContentResolver();
+        Uri uri = Uri.parse(mUri);
+        InputStream is = contentResolver.openInputStream(uri);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Base64OutputStream b64os = new Base64OutputStream(baos, Base64.DEFAULT);
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int bytesRead;
+        try {
+          while ((bytesRead = is.read(buffer)) > -1) {
+            b64os.write(buffer, 0, bytesRead);
+          }
+          mSuccess.invoke(baos.toString());
+        } catch (IOException e) {
+          mError.invoke(e.getMessage());
+        } finally {
+          closeQuietly(is);
+          closeQuietly(b64os); // this also closes baos
+        }
+      } catch (FileNotFoundException e) {
+        mError.invoke(e.getMessage());
+      }
+    }
+  }
+
+  private static void closeQuietly(Closeable closeable) {
+    try {
+      closeable.close();
+    } catch (IOException e) {
+      // shhh
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK
new file mode 100644
index 000000000000..262b92ef24dd
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'clipboard',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':clipboard',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java
new file mode 100644
index 000000000000..0ea6c90528c4
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/clipboard/ClipboardModule.java
@@ -0,0 +1,82 @@
+/**
+ * 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.modules.clipboard;
+
+import android.annotation.SuppressLint;
+import android.content.ClipboardManager;
+import android.content.ClipData;
+import android.os.Build;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.common.ReactConstants;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A module that allows JS to get/set clipboard contents.
+ */
+public class ClipboardModule extends ReactContextBaseJavaModule {
+
+  public ClipboardModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "Clipboard";
+  }
+
+  private ClipboardManager getClipboardService() {
+    return (ClipboardManager) getReactApplicationContext().getSystemService(getReactApplicationContext().CLIPBOARD_SERVICE);
+  }
+
+  @ReactMethod
+  public void getString(Promise promise){
+    try {
+      ClipboardManager clipboard = getClipboardService();
+      ClipData clipData = clipboard.getPrimaryClip();
+      if (clipData == null) {
+        promise.resolve("");
+      }
+      if (clipData.getItemCount() >= 1) {
+        ClipData.Item firstItem = clipboard.getPrimaryClip().getItemAt(0);
+        promise.resolve("" + firstItem.getText());
+      } else {
+        promise.resolve("");
+      }
+    } catch(Exception e) {
+      promise.reject(e);
+    }
+  }
+
+  @SuppressLint("DeprecatedMethod")
+  @ReactMethod
+  public void setString(String text) {
+    ReactApplicationContext reactContext = getReactApplicationContext();
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+      ClipData clipdata = ClipData.newPlainText(null, text);
+      ClipboardManager clipboard = getClipboardService();
+      clipboard.setPrimaryClip(clipdata);
+    } else {
+      ClipboardManager clipboard = getClipboardService();
+      clipboard.setText(text);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK
index d099b3c2efd6..ed2454fac84d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/common/BUCK
@@ -6,9 +6,9 @@ android_library(
   deps = [
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK
index 5507ede15a6b..2fff5f1efb6a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/BUCK
@@ -4,11 +4,11 @@ android_library(
   name = 'core',
   srcs = glob(['**/*.java']),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/devsupport:devsupport'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java
index 1329a5b7c6d8..37a34283c813 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/DeviceEventManagerModule.java
@@ -12,10 +12,10 @@
 import javax.annotation.Nullable;
 
 import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.SupportsWebWorkers;
 import com.facebook.react.bridge.UiThreadUtil;
 
 /**
@@ -23,7 +23,8 @@
  */
 public class DeviceEventManagerModule extends ReactContextBaseJavaModule {
 
-  public static interface RCTDeviceEventEmitter extends JavaScriptModule {
+  @SupportsWebWorkers
+  public interface RCTDeviceEventEmitter extends JavaScriptModule {
     void emit(String eventName, @Nullable Object data);
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.java
index 876a2fca2c5d..5b185d64b1d8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/ExceptionsManagerModule.java
@@ -16,6 +16,7 @@
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableType;
 import com.facebook.react.devsupport.DevSupportManager;
 import com.facebook.react.common.ReactConstants;
 
@@ -32,16 +33,17 @@ public String getName() {
     return "RKExceptionsManager";
   }
 
-  private String stackTraceToString(ReadableArray stack) {
-    StringBuilder stringBuilder = new StringBuilder();
+  private String stackTraceToString(String message, ReadableArray stack) {
+    StringBuilder stringBuilder = new StringBuilder(message).append(", stack:\n");
     for (int i = 0; i < stack.size(); i++) {
       ReadableMap frame = stack.getMap(i);
-      stringBuilder.append(frame.getString("methodName"));
-      stringBuilder.append("\n    ");
-      stringBuilder.append(new File(frame.getString("file")).getName());
-      stringBuilder.append(":");
-      stringBuilder.append(frame.getInt("lineNumber"));
-      if (frame.hasKey("column") && !frame.isNull("column")) {
+      stringBuilder
+          .append(frame.getString("methodName"))
+          .append("@")
+          .append(frame.getInt("lineNumber"));
+      if (frame.hasKey("column") &&
+          !frame.isNull("column") &&
+          frame.getType("column") == ReadableType.Number) {
         stringBuilder
             .append(":")
             .append(frame.getInt("column"));
@@ -58,14 +60,14 @@ public void reportFatalException(String title, ReadableArray details, int except
 
   @ReactMethod
   public void reportSoftException(String title, ReadableArray details, int exceptionId) {
-    FLog.e(ReactConstants.TAG, title + "\n" + stackTraceToString(details));
+    FLog.e(ReactConstants.TAG, stackTraceToString(title, details));
   }
 
   private void showOrThrowError(String title, ReadableArray details, int exceptionId) {
     if (mDevSupportManager.getDevSupportEnabled()) {
       mDevSupportManager.showNewJSError(title, details, exceptionId);
     } else {
-      throw new JavascriptException(stackTraceToString(details));
+      throw new JavascriptException(stackTraceToString(title, details));
     }
   }
 
@@ -75,4 +77,11 @@ public void updateExceptionMessage(String title, ReadableArray details, int exce
       mDevSupportManager.updateJSError(title, details, exceptionId);
     }
   }
+
+  @ReactMethod
+  public void dismissRedbox() {
+    if (mDevSupportManager.getDevSupportEnabled()) {
+      mDevSupportManager.hideRedboxDialog();
+    }
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java
index 67f5ca2312f1..9e0ba2a3d728 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JSTimersExecution.java
@@ -10,8 +10,10 @@
 package com.facebook.react.modules.core;
 
 import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.SupportsWebWorkers;
 import com.facebook.react.bridge.WritableArray;
 
+@SupportsWebWorkers
 public interface JSTimersExecution extends JavaScriptModule {
 
   public void callTimers(WritableArray timerIDs);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java
index 8b0888e0882d..b0bf332da5ea 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/Timing.java
@@ -12,36 +12,47 @@
 import javax.annotation.Nullable;
 
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.PriorityQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import android.util.SparseArray;
 import android.view.Choreographer;
 
+import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.OnExecutorUnregisteredListener;
 import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
-import com.facebook.react.bridge.LifecycleEventListener;
 import com.facebook.react.bridge.WritableArray;
-import com.facebook.react.uimanager.ReactChoreographer;
 import com.facebook.react.common.SystemClock;
-import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.uimanager.ReactChoreographer;
 
 /**
  * Native module for JS timer execution. Timers fire on frame boundaries.
  */
-public final class Timing extends ReactContextBaseJavaModule implements LifecycleEventListener {
+public final class Timing extends ReactContextBaseJavaModule implements LifecycleEventListener,
+  OnExecutorUnregisteredListener {
 
   private static class Timer {
 
+    private final ExecutorToken mExecutorToken;
     private final int mCallbackID;
     private final boolean mRepeat;
     private final int mInterval;
     private long mTargetTime;
 
-    private Timer(int callbackID, long initialTargetTime, int duration, boolean repeat) {
+    private Timer(
+        ExecutorToken executorToken,
+        int callbackID,
+        long initialTargetTime,
+        int duration,
+        boolean repeat) {
+      mExecutorToken = executorToken;
       mCallbackID = callbackID;
       mTargetTime = initialTargetTime;
       mInterval = duration;
@@ -51,6 +62,9 @@ private Timer(int callbackID, long initialTargetTime, int duration, boolean repe
 
   private class FrameCallback implements Choreographer.FrameCallback {
 
+    // Temporary map for constructing the individual arrays of timers per ExecutorToken
+    private final HashMap<ExecutorToken, WritableArray> mTimersToCall = new HashMap<>();
+
     /**
      * Calls all timers that have expired since the last time this frame callback was called.
      */
@@ -61,14 +75,15 @@ public void doFrame(long frameTimeNanos) {
       }
 
       long frameTimeMillis = frameTimeNanos / 1000000;
-      WritableArray timersToCall = null;
       synchronized (mTimerGuard) {
         while (!mTimers.isEmpty() && mTimers.peek().mTargetTime < frameTimeMillis) {
           Timer timer = mTimers.poll();
-          if (timersToCall == null) {
-            timersToCall = Arguments.createArray();
+          WritableArray timersForContext = mTimersToCall.get(timer.mExecutorToken);
+          if (timersForContext == null) {
+            timersForContext = Arguments.createArray();
+            mTimersToCall.put(timer.mExecutorToken, timersForContext);
           }
-          timersToCall.pushInt(timer.mCallbackID);
+          timersForContext.pushInt(timer.mCallbackID);
           if (timer.mRepeat) {
             timer.mTargetTime = frameTimeMillis + timer.mInterval;
             mTimers.add(timer);
@@ -78,9 +93,11 @@ public void doFrame(long frameTimeNanos) {
         }
       }
 
-      if (timersToCall != null) {
-        Assertions.assertNotNull(mJSTimersModule).callTimers(timersToCall);
+      for (Map.Entry<ExecutorToken, WritableArray> entry : mTimersToCall.entrySet()) {
+        getReactApplicationContext().getJSModule(entry.getKey(), JSTimersExecution.class)
+            .callTimers(entry.getValue());
       }
+      mTimersToCall.clear();
 
       Assertions.assertNotNull(mReactChoreographer)
           .postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, this);
@@ -89,11 +106,10 @@ public void doFrame(long frameTimeNanos) {
 
   private final Object mTimerGuard = new Object();
   private final PriorityQueue<Timer> mTimers;
-  private final SparseArray<Timer> mTimerIdsToTimers;
-  private final AtomicBoolean isPaused = new AtomicBoolean(false);
+  private final HashMap<ExecutorToken, SparseArray<Timer>> mTimerIdsToTimers;
+  private final AtomicBoolean isPaused = new AtomicBoolean(true);
   private final FrameCallback mFrameCallback = new FrameCallback();
   private @Nullable ReactChoreographer mReactChoreographer;
-  private @Nullable JSTimersExecution mJSTimersModule;
   private boolean mFrameCallbackPosted = false;
 
   public Timing(ReactApplicationContext reactContext) {
@@ -114,17 +130,14 @@ public int compare(Timer lhs, Timer rhs) {
             }
           }
         });
-    mTimerIdsToTimers = new SparseArray<Timer>();
+    mTimerIdsToTimers = new HashMap<>();
   }
 
   @Override
   public void initialize() {
     // Safe to acquire choreographer here, as initialize() is invoked from UI thread.
     mReactChoreographer = ReactChoreographer.getInstance();
-    mJSTimersModule = getReactApplicationContext().getCatalystInstance()
-        .getJSModule(JSTimersExecution.class);
     getReactApplicationContext().addLifecycleEventListener(this);
-    setChoreographerCallback();
   }
 
   @Override
@@ -174,8 +187,28 @@ public String getName() {
     return "RKTiming";
   }
 
+  @Override
+  public boolean supportsWebWorkers() {
+    return true;
+  }
+
+  @Override
+  public void onExecutorDestroyed(ExecutorToken executorToken) {
+    synchronized (mTimerGuard) {
+      SparseArray<Timer> timersForContext = mTimerIdsToTimers.remove(executorToken);
+      if (timersForContext == null) {
+        return;
+      }
+      for (int i = 0; i < timersForContext.size(); i++) {
+        Timer timer = timersForContext.get(timersForContext.keyAt(i));
+        mTimers.remove(timer);
+      }
+    }
+  }
+
   @ReactMethod
   public void createTimer(
+      ExecutorToken executorToken,
       final int callbackID,
       final int duration,
       final double jsSchedulingTime,
@@ -185,17 +218,22 @@ public void createTimer(
         0,
         jsSchedulingTime - SystemClock.currentTimeMillis() + duration);
     long initialTargetTime = SystemClock.nanoTime() / 1000000 + adjustedDuration;
-    Timer timer = new Timer(callbackID, initialTargetTime, duration, repeat);
+    Timer timer = new Timer(executorToken, callbackID, initialTargetTime, duration, repeat);
     synchronized (mTimerGuard) {
       mTimers.add(timer);
-      mTimerIdsToTimers.put(callbackID, timer);
+      SparseArray<Timer> timersForContext = mTimerIdsToTimers.get(executorToken);
+      if (timersForContext == null) {
+        timersForContext = new SparseArray<>();
+        mTimerIdsToTimers.put(executorToken, timersForContext);
+      }
+      timersForContext.put(callbackID, timer);
     }
   }
 
   @ReactMethod
-  public void deleteTimer(int timerId) {
+  public void deleteTimer(ExecutorToken executorToken, int timerId) {
     synchronized (mTimerGuard) {
-      Timer timer = mTimerIdsToTimers.get(timerId);
+      Timer timer = mTimerIdsToTimers.get(executorToken).get(timerId);
       if (timer != null) {
         // We may have already called/removed it
         mTimerIdsToTimers.remove(timerId);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK
new file mode 100644
index 000000000000..d53004753df3
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'datepicker',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':datepicker',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogFragment.java
new file mode 100644
index 000000000000..6568f35041d9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogFragment.java
@@ -0,0 +1,103 @@
+/**
+ * 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.modules.datepicker;
+
+import javax.annotation.Nullable;
+
+import java.util.Calendar;
+
+import android.annotation.SuppressLint;
+import android.app.DatePickerDialog;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.widget.DatePicker;
+
+@SuppressLint("ValidFragment")
+public class DatePickerDialogFragment extends DialogFragment {
+
+  /**
+   * Minimum date supported by {@link DatePicker}, 01 Jan 1900
+   */
+  private static final long DEFAULT_MIN_DATE = -2208988800001l;
+
+  @Nullable
+  private OnDateSetListener mOnDateSetListener;
+  @Nullable
+  private OnDismissListener mOnDismissListener;
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    Bundle args = getArguments();
+    return createDialog(args, getActivity(), mOnDateSetListener);
+  }
+
+  /*package*/ static Dialog createDialog(
+      Bundle args, Context activityContext, @Nullable OnDateSetListener onDateSetListener) {
+    final Calendar c = Calendar.getInstance();
+    if (args != null && args.containsKey(DatePickerDialogModule.ARG_DATE)) {
+      c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_DATE));
+    }
+    final int year = c.get(Calendar.YEAR);
+    final int month = c.get(Calendar.MONTH);
+    final int day = c.get(Calendar.DAY_OF_MONTH);
+
+    final DatePickerDialog dialog =
+        new DismissableDatePickerDialog(activityContext, onDateSetListener, year, month, day);
+    final DatePicker datePicker = dialog.getDatePicker();
+
+    if (args != null && args.containsKey(DatePickerDialogModule.ARG_MINDATE)) {
+      // Set minDate to the beginning of the day. We need this because of clowniness in datepicker
+      // that causes it to throw an exception if minDate is greater than the internal timestamp
+      // that it generates from the y/m/d passed in the constructor.
+      c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_MINDATE));
+      c.set(Calendar.HOUR_OF_DAY, 0);
+      c.set(Calendar.MINUTE, 0);
+      c.set(Calendar.SECOND, 0);
+      c.set(Calendar.MILLISECOND, 0);
+      datePicker.setMinDate(c.getTimeInMillis());
+    } else {
+      // This is to work around a bug in DatePickerDialog where it doesn't display a title showing
+      // the date under certain conditions.
+      datePicker.setMinDate(DEFAULT_MIN_DATE);
+    }
+    if (args != null && args.containsKey(DatePickerDialogModule.ARG_MAXDATE)) {
+      // Set maxDate to the end of the day, same reason as for minDate.
+      c.setTimeInMillis(args.getLong(DatePickerDialogModule.ARG_MAXDATE));
+      c.set(Calendar.HOUR_OF_DAY, 23);
+      c.set(Calendar.MINUTE, 59);
+      c.set(Calendar.SECOND, 59);
+      c.set(Calendar.MILLISECOND, 999);
+      datePicker.setMaxDate(c.getTimeInMillis());
+    }
+
+    return dialog;
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mOnDismissListener != null) {
+      mOnDismissListener.onDismiss(dialog);
+    }
+  }
+
+  /*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
+    mOnDateSetListener = onDateSetListener;
+  }
+
+  /*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
+    mOnDismissListener = onDismissListener;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogModule.java
new file mode 100644
index 000000000000..6f76ea7e4098
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DatePickerDialogModule.java
@@ -0,0 +1,176 @@
+/**
+ * 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.modules.datepicker;
+
+import javax.annotation.Nullable;
+
+import java.util.Map;
+
+import android.app.Activity;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.widget.DatePicker;
+
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.common.annotations.VisibleForTesting;
+
+/**
+ * {@link NativeModule} that allows JS to show a native date picker dialog and get called back when
+ * the user selects a date.
+ */
+public class DatePickerDialogModule extends ReactContextBaseJavaModule {
+
+  @VisibleForTesting
+  public static final String FRAGMENT_TAG = "DatePickerAndroid";
+
+  private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
+
+  /* package */ static final String ARG_DATE = "date";
+  /* package */ static final String ARG_MINDATE = "minDate";
+  /* package */ static final String ARG_MAXDATE = "maxDate";
+
+  /* package */ static final String ACTION_DATE_SET = "dateSetAction";
+  /* package */ static final String ACTION_DISMISSED = "dismissedAction";
+
+  public DatePickerDialogModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "DatePickerAndroid";
+  }
+
+  private class DatePickerDialogListener implements OnDateSetListener, OnDismissListener {
+
+    private final Promise mPromise;
+    private boolean mPromiseResolved = false;
+
+    public DatePickerDialogListener(final Promise promise) {
+      mPromise = promise;
+    }
+
+    @Override
+    public void onDateSet(DatePicker view, int year, int month, int day) {
+      if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
+        WritableMap result = new WritableNativeMap();
+        result.putString("action", ACTION_DATE_SET);
+        result.putInt("year", year);
+        result.putInt("month", month);
+        result.putInt("day", day);
+        mPromise.resolve(result);
+        mPromiseResolved = true;
+      }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+      if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
+        WritableMap result = new WritableNativeMap();
+        result.putString("action", ACTION_DISMISSED);
+        mPromise.resolve(result);
+        mPromiseResolved = true;
+      }
+    }
+  }
+
+  /**
+   * Show a date picker dialog.
+   *
+   * @param options a map containing options. Available keys are:
+   *
+   * <ul>
+   *   <li>{@code date} (timestamp in milliseconds) the date to show by default</li>
+   *   <li>
+   *     {@code minDate} (timestamp in milliseconds) the minimum date the user should be allowed
+   *     to select
+   *   </li>
+   *   <li>
+   *     {@code maxDate} (timestamp in milliseconds) the maximum date the user should be allowed
+   *     to select
+   *    </li>
+   * </ul>
+   *
+   * @param promise This will be invoked with parameters action, year,
+   *                month (0-11), day, where action is {@code dateSetAction} or
+   *                {@code dismissedAction}, depending on what the user did. If the action is
+   *                dismiss, year, month and date are undefined.
+   */
+  @ReactMethod
+  public void open(@Nullable final ReadableMap options, Promise promise) {
+    Activity activity = getCurrentActivity();
+    if (activity == null) {
+      promise.reject(
+          ERROR_NO_ACTIVITY,
+          "Tried to open a DatePicker dialog while not attached to an Activity");
+      return;
+    }
+    // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity
+    // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication.
+    if (activity instanceof android.support.v4.app.FragmentActivity) {
+      android.support.v4.app.FragmentManager fragmentManager =
+          ((android.support.v4.app.FragmentActivity) activity).getSupportFragmentManager();
+      android.support.v4.app.DialogFragment oldFragment =
+          (android.support.v4.app.DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+      if (oldFragment != null) {
+        oldFragment.dismiss();
+      }
+      SupportDatePickerDialogFragment fragment = new SupportDatePickerDialogFragment();
+      if (options != null) {
+        final Bundle args = createFragmentArguments(options);
+        fragment.setArguments(args);
+      }
+      final DatePickerDialogListener listener = new DatePickerDialogListener(promise);
+      fragment.setOnDismissListener(listener);
+      fragment.setOnDateSetListener(listener);
+      fragment.show(fragmentManager, FRAGMENT_TAG);
+    } else {
+      FragmentManager fragmentManager = activity.getFragmentManager();
+      DialogFragment oldFragment = (DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+      if (oldFragment != null) {
+        oldFragment.dismiss();
+      }
+      DatePickerDialogFragment fragment = new DatePickerDialogFragment();
+      if (options != null) {
+        final Bundle args = createFragmentArguments(options);
+        fragment.setArguments(args);
+      }
+      final DatePickerDialogListener listener = new DatePickerDialogListener(promise);
+      fragment.setOnDismissListener(listener);
+      fragment.setOnDateSetListener(listener);
+      fragment.show(fragmentManager, FRAGMENT_TAG);
+    }
+  }
+
+  private Bundle createFragmentArguments(ReadableMap options) {
+    final Bundle args = new Bundle();
+    if (options.hasKey(ARG_DATE) && !options.isNull(ARG_DATE)) {
+      args.putLong(ARG_DATE, (long) options.getDouble(ARG_DATE));
+    }
+    if (options.hasKey(ARG_MINDATE) && !options.isNull(ARG_MINDATE)) {
+      args.putLong(ARG_MINDATE, (long) options.getDouble(ARG_MINDATE));
+    }
+    if (options.hasKey(ARG_MAXDATE) && !options.isNull(ARG_MAXDATE)) {
+      args.putLong(ARG_MAXDATE, (long) options.getDouble(ARG_MAXDATE));
+    }
+    return args;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java
new file mode 100644
index 000000000000..57791948ea80
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/DismissableDatePickerDialog.java
@@ -0,0 +1,57 @@
+/**
+ * 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.modules.datepicker;
+
+import javax.annotation.Nullable;
+
+import android.app.DatePickerDialog;
+import android.content.Context;
+import android.os.Build;
+
+/**
+ * <p>
+ *   Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
+ *   {@link DatePickerDialog} still calls the OnDateSetListener. This class works around that issue.
+ * </p>
+ *
+ * <p>
+ *   See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
+ * </p>
+ */
+public class DismissableDatePickerDialog extends DatePickerDialog {
+
+  public DismissableDatePickerDialog(
+      Context context,
+      @Nullable OnDateSetListener callback,
+      int year,
+      int monthOfYear,
+      int dayOfMonth) {
+    super(context, callback, year, monthOfYear, dayOfMonth);
+  }
+
+  public DismissableDatePickerDialog(
+      Context context,
+      int theme,
+      @Nullable OnDateSetListener callback,
+      int year,
+      int monthOfYear,
+      int dayOfMonth) {
+    super(context, theme, callback, year, monthOfYear, dayOfMonth);
+  }
+
+  @Override
+  protected void onStop() {
+    // do *not* call super.onStop() on KitKat on lower, as that would erroneously call the
+    // OnDateSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+      super.onStop();
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/SupportDatePickerDialogFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/SupportDatePickerDialogFragment.java
new file mode 100644
index 000000000000..d935d58d84d5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/datepicker/SupportDatePickerDialogFragment.java
@@ -0,0 +1,51 @@
+/**
+ * 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.modules.datepicker;
+
+import javax.annotation.Nullable;
+
+import android.annotation.SuppressLint;
+import android.app.DatePickerDialog.OnDateSetListener;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+@SuppressLint("ValidFragment")
+public class SupportDatePickerDialogFragment extends DialogFragment {
+
+  @Nullable
+  private OnDateSetListener mOnDateSetListener;
+  @Nullable
+  private OnDismissListener mOnDismissListener;
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    final Bundle args = getArguments();
+    return DatePickerDialogFragment.createDialog(args, getActivity(), mOnDateSetListener);
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mOnDismissListener != null) {
+      mOnDismissListener.onDismiss(dialog);
+    }
+  }
+
+  /*package*/ void setOnDateSetListener(@Nullable OnDateSetListener onDateSetListener) {
+    mOnDateSetListener = onDateSetListener;
+  }
+
+  /*package*/ void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
+    mOnDismissListener = onDismissListener;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/AnimationsDebugModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/AnimationsDebugModule.java
index 6b914aae2690..dc96645815cf 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/AnimationsDebugModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/AnimationsDebugModule.java
@@ -31,7 +31,7 @@
 public class AnimationsDebugModule extends ReactContextBaseJavaModule {
 
   private @Nullable FpsDebugFrameCallback mFrameCallback;
-  private final DeveloperSettings mCatalystSettings;
+  private @Nullable final DeveloperSettings mCatalystSettings;
 
   public AnimationsDebugModule(
       ReactApplicationContext reactContext,
@@ -47,7 +47,8 @@ public String getName() {
 
   @ReactMethod
   public void startRecordingFps() {
-    if (!mCatalystSettings.isAnimationFpsDebugEnabled()) {
+    if (mCatalystSettings == null ||
+        !mCatalystSettings.isAnimationFpsDebugEnabled()) {
       return;
     }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK
index 6f5d6b9b1500..a43da0d29fb9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK
@@ -4,10 +4,10 @@ android_library(
   name = 'debug',
   srcs = glob(['**/*.java']),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DeveloperSettings.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DeveloperSettings.java
index 13a230fc35e5..5ff8dfbf76f9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DeveloperSettings.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DeveloperSettings.java
@@ -28,4 +28,9 @@ public interface DeveloperSettings {
    * @return Whether dev mode should be enabled in JS bundles.
    */
   boolean isJSDevModeEnabled();
+
+  /**
+   * @return Whether element inspector is enabled.
+   */
+  boolean isElementInspectorEnabled();
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java
index 28144b9afaf2..f15a143df7c6 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DidJSUpdateUiDuringFrameDetector.java
@@ -15,7 +15,7 @@
 import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
 import com.facebook.react.common.LongArray;
 import com.facebook.react.uimanager.UIManagerModule;
-import com.facebook.react.uimanager.debug.NotThreadSafeUiManagerDebugListener;
+import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
 
 /**
  * Debug object that listens to bridge busy/idle events and UiManagerModule dispatches and uses it
@@ -25,7 +25,7 @@
  * {@link Choreographer.FrameCallback}.
  */
 public class DidJSUpdateUiDuringFrameDetector implements NotThreadSafeBridgeIdleDebugListener,
-    NotThreadSafeUiManagerDebugListener {
+    NotThreadSafeViewHierarchyUpdateDebugListener {
 
   private final LongArray mTransitionToIdleEvents = LongArray.createWithInitialCapacity(20);
   private final LongArray mTransitionToBusyEvents = LongArray.createWithInitialCapacity(20);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java
index 71400867e640..1eac40fae1a4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.java
@@ -135,7 +135,7 @@ public void start() {
     mShouldStop = false;
     mReactContext.getCatalystInstance().addBridgeIdleDebugListener(
         mDidJSUpdateUiDuringFrameDetector);
-    mUIManagerModule.setUiManagerDebugListener(mDidJSUpdateUiDuringFrameDetector);
+    mUIManagerModule.setViewHierarchyUpdateDebugListener(mDidJSUpdateUiDuringFrameDetector);
     mChoreographer.postFrameCallback(this);
   }
 
@@ -149,7 +149,7 @@ public void stop() {
     mShouldStop = true;
     mReactContext.getCatalystInstance().removeBridgeIdleDebugListener(
         mDidJSUpdateUiDuringFrameDetector);
-    mUIManagerModule.setUiManagerDebugListener(null);
+    mUIManagerModule.setViewHierarchyUpdateDebugListener(null);
   }
 
   public double getFPS() {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/SourceCodeModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/SourceCodeModule.java
index 07022a7e76c3..3417c81946af 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/SourceCodeModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/SourceCodeModule.java
@@ -14,7 +14,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.Promise;
 import com.facebook.react.bridge.BaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.WritableMap;
@@ -35,14 +35,14 @@ public SourceCodeModule(String sourceUrl, String sourceMapUrl) {
 
   @Override
   public String getName() {
-    return "RKSourceCode";
+    return "RCTSourceCode";
   }
 
   @ReactMethod
-  public void getScriptText(final Callback onSuccess, final Callback onError) {
+  public void getScriptText(final Promise promise) {
     WritableMap map = new WritableNativeMap();
     map.putString("fullSourceMappingURL", mSourceMapUrl);
-    onSuccess.invoke(map);
+    promise.resolve(map);
   }
 
   @Override
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/AlertFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/AlertFragment.java
new file mode 100644
index 000000000000..98dd9cc0abda
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/AlertFragment.java
@@ -0,0 +1,85 @@
+/**
+ * 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.modules.dialog;
+
+import javax.annotation.Nullable;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * A fragment used to display the dialog.
+ */
+/* package */ class AlertFragment extends DialogFragment implements DialogInterface.OnClickListener {
+
+  /* package */ static final String ARG_TITLE = "title";
+  /* package */ static final String ARG_MESSAGE = "message";
+  /* package */ static final String ARG_BUTTON_POSITIVE = "button_positive";
+  /* package */ static final String ARG_BUTTON_NEGATIVE = "button_negative";
+  /* package */ static final String ARG_BUTTON_NEUTRAL = "button_neutral";
+  /* package */ static final String ARG_ITEMS = "items";
+
+  private final @Nullable DialogModule.AlertFragmentListener mListener;
+
+  public AlertFragment(@Nullable DialogModule.AlertFragmentListener listener, Bundle arguments) {
+    mListener = listener;
+    setArguments(arguments);
+  }
+
+  public static Dialog createDialog(
+      Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
+    AlertDialog.Builder builder = new AlertDialog.Builder(activityContext)
+        .setTitle(arguments.getString(ARG_TITLE));
+
+    if (arguments.containsKey(ARG_BUTTON_POSITIVE)) {
+      builder.setPositiveButton(arguments.getString(ARG_BUTTON_POSITIVE), fragment);
+    }
+    if (arguments.containsKey(ARG_BUTTON_NEGATIVE)) {
+      builder.setNegativeButton(arguments.getString(ARG_BUTTON_NEGATIVE), fragment);
+    }
+    if (arguments.containsKey(ARG_BUTTON_NEUTRAL)) {
+      builder.setNeutralButton(arguments.getString(ARG_BUTTON_NEUTRAL), fragment);
+    }
+    // if both message and items are set, Android will only show the message
+    // and ignore the items argument entirely
+    if (arguments.containsKey(ARG_MESSAGE)) {
+      builder.setMessage(arguments.getString(ARG_MESSAGE));
+    }
+    if (arguments.containsKey(ARG_ITEMS)) {
+      builder.setItems(arguments.getCharSequenceArray(ARG_ITEMS), fragment);
+    }
+
+    return builder.create();
+  }
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    return createDialog(getActivity(), getArguments(), this);
+  }
+
+  @Override
+  public void onClick(DialogInterface dialog, int which) {
+    if (mListener != null) {
+      mListener.onClick(dialog, which);
+    }
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mListener != null) {
+      mListener.onDismiss(dialog);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK
new file mode 100644
index 000000000000..45c6aeae97ab
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'dialog',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':dialog',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/DialogModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/DialogModule.java
new file mode 100644
index 000000000000..30bcedb226ac
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/DialogModule.java
@@ -0,0 +1,265 @@
+/**
+ * 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.modules.dialog;
+
+import javax.annotation.Nullable;
+
+import java.util.Map;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.MapBuilder;
+
+public class DialogModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
+
+  /* package */ static final String FRAGMENT_TAG =
+      "com.facebook.catalyst.react.dialog.DialogModule";
+
+  /* package */ static final String NAME = "DialogManagerAndroid";
+
+  /* package */ static final String ACTION_BUTTON_CLICKED = "buttonClicked";
+  /* package */ static final String ACTION_DISMISSED = "dismissed";
+  /* package */ static final String KEY_TITLE = "title";
+  /* package */ static final String KEY_MESSAGE = "message";
+  /* package */ static final String KEY_BUTTON_POSITIVE = "buttonPositive";
+  /* package */ static final String KEY_BUTTON_NEGATIVE = "buttonNegative";
+  /* package */ static final String KEY_BUTTON_NEUTRAL = "buttonNeutral";
+  /* package */ static final String KEY_ITEMS = "items";
+
+  /* package */ static final Map<String, Object> CONSTANTS = MapBuilder.<String, Object>of(
+      ACTION_BUTTON_CLICKED, ACTION_BUTTON_CLICKED,
+      ACTION_DISMISSED, ACTION_DISMISSED,
+      KEY_BUTTON_POSITIVE, DialogInterface.BUTTON_POSITIVE,
+      KEY_BUTTON_NEGATIVE, DialogInterface.BUTTON_NEGATIVE,
+      KEY_BUTTON_NEUTRAL, DialogInterface.BUTTON_NEUTRAL);
+
+  private boolean mIsInForeground;
+
+  public DialogModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  /**
+   * Helper to allow this module to work with both the standard FragmentManager
+   * and the Support FragmentManager (for apps that need to use it for legacy reasons).
+   * Since the two APIs don't share a common interface there's unfortunately some
+   * code duplication.
+   */
+  private class FragmentManagerHelper {
+
+    // Exactly one of the two is null
+    private final @Nullable android.app.FragmentManager mFragmentManager;
+    private final @Nullable android.support.v4.app.FragmentManager mSupportFragmentManager;
+
+    private @Nullable Object mFragmentToShow;
+
+    private boolean isUsingSupportLibrary() {
+      return mSupportFragmentManager != null;
+    }
+
+    public FragmentManagerHelper(android.support.v4.app.FragmentManager supportFragmentManager) {
+      mFragmentManager = null;
+      mSupportFragmentManager = supportFragmentManager;
+    }
+    public FragmentManagerHelper(android.app.FragmentManager fragmentManager) {
+      mFragmentManager = fragmentManager;
+      mSupportFragmentManager = null;
+    }
+
+    public void showPendingAlert() {
+      if (mFragmentToShow == null) {
+        return;
+      }
+      if (isUsingSupportLibrary()) {
+        ((SupportAlertFragment) mFragmentToShow).show(mSupportFragmentManager, FRAGMENT_TAG);
+      } else {
+        ((AlertFragment) mFragmentToShow).show(mFragmentManager, FRAGMENT_TAG);
+      }
+      mFragmentToShow = null;
+    }
+
+    private void dismissExisting() {
+      if (isUsingSupportLibrary()) {
+        SupportAlertFragment oldFragment =
+            (SupportAlertFragment) mSupportFragmentManager.findFragmentByTag(FRAGMENT_TAG);
+        if (oldFragment != null) {
+          oldFragment.dismiss();
+        }
+      } else {
+        AlertFragment oldFragment =
+            (AlertFragment) mFragmentManager.findFragmentByTag(FRAGMENT_TAG);
+        if (oldFragment != null) {
+          oldFragment.dismiss();
+        }
+      }
+    }
+
+    public void showNewAlert(boolean isInForeground, Bundle arguments, Callback actionCallback) {
+      dismissExisting();
+
+      AlertFragmentListener actionListener =
+          actionCallback != null ? new AlertFragmentListener(actionCallback) : null;
+
+      if (isUsingSupportLibrary()) {
+        SupportAlertFragment alertFragment = new SupportAlertFragment(actionListener, arguments);
+        if (isInForeground) {
+          alertFragment.show(mSupportFragmentManager, FRAGMENT_TAG);
+        } else {
+          mFragmentToShow = alertFragment;
+        }
+      } else {
+        AlertFragment alertFragment = new AlertFragment(actionListener, arguments);
+        if (isInForeground) {
+          alertFragment.show(mFragmentManager, FRAGMENT_TAG);
+        } else {
+          mFragmentToShow = alertFragment;
+        }
+      }
+    }
+  }
+
+  /* package */ class AlertFragmentListener implements OnClickListener, OnDismissListener {
+
+    private final Callback mCallback;
+    private boolean mCallbackConsumed = false;
+
+    public AlertFragmentListener(Callback callback) {
+      mCallback = callback;
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+      if (!mCallbackConsumed) {
+        if (getReactApplicationContext().hasActiveCatalystInstance()) {
+          mCallback.invoke(ACTION_BUTTON_CLICKED, which);
+          mCallbackConsumed = true;
+        }
+      }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+      if (!mCallbackConsumed) {
+        if (getReactApplicationContext().hasActiveCatalystInstance()) {
+          mCallback.invoke(ACTION_DISMISSED);
+          mCallbackConsumed = true;
+        }
+      }
+    }
+  }
+
+  @Override
+  public Map<String, Object> getConstants() {
+    return CONSTANTS;
+  }
+
+  @Override
+  public void initialize() {
+    getReactApplicationContext().addLifecycleEventListener(this);
+  }
+
+  @Override
+  public void onHostPause() {
+    // Don't show the dialog if the host is paused.
+    mIsInForeground = false;
+  }
+
+  @Override
+  public void onHostDestroy() {
+  }
+
+  @Override
+  public void onHostResume() {
+    mIsInForeground = true;
+    // Check if a dialog has been created while the host was paused, so that we can show it now.
+    FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
+    Assertions.assertNotNull(
+        fragmentManagerHelper,
+        "Attached DialogModule to host with pending alert but no FragmentManager " +
+        "(not attached to an Activity).");
+    fragmentManagerHelper.showPendingAlert();
+  }
+
+  @ReactMethod
+  public void showAlert(
+      ReadableMap options,
+      Callback errorCallback,
+      Callback actionCallback) {
+    FragmentManagerHelper fragmentManagerHelper = getFragmentManagerHelper();
+    if (fragmentManagerHelper == null) {
+      errorCallback.invoke("Tried to show an alert while not attached to an Activity");
+      return;
+    }
+
+    final Bundle args = new Bundle();
+    if (options.hasKey(KEY_TITLE)) {
+      args.putString(AlertFragment.ARG_TITLE, options.getString(KEY_TITLE));
+    }
+    if (options.hasKey(KEY_MESSAGE)) {
+      args.putString(AlertFragment.ARG_MESSAGE, options.getString(KEY_MESSAGE));
+    }
+    if (options.hasKey(KEY_BUTTON_POSITIVE)) {
+      args.putString(AlertFragment.ARG_BUTTON_POSITIVE, options.getString(KEY_BUTTON_POSITIVE));
+    }
+    if (options.hasKey(KEY_BUTTON_NEGATIVE)) {
+      args.putString(AlertFragment.ARG_BUTTON_NEGATIVE, options.getString(KEY_BUTTON_NEGATIVE));
+    }
+    if (options.hasKey(KEY_BUTTON_NEUTRAL)) {
+      args.putString(AlertFragment.ARG_BUTTON_NEUTRAL, options.getString(KEY_BUTTON_NEUTRAL));
+    }
+    if (options.hasKey(KEY_ITEMS)) {
+      ReadableArray items = options.getArray(KEY_ITEMS);
+      CharSequence[] itemsArray = new CharSequence[items.size()];
+      for (int i = 0; i < items.size(); i ++) {
+        itemsArray[i] = items.getString(i);
+      }
+      args.putCharSequenceArray(AlertFragment.ARG_ITEMS, itemsArray);
+    }
+
+    fragmentManagerHelper.showNewAlert(mIsInForeground, args, actionCallback);
+  }
+
+  /**
+   * Creates a new helper to work with either the FragmentManager or the legacy support
+   * FragmentManager transparently. Returns null if we're not attached to an Activity.
+   *
+   * DO NOT HOLD LONG-LIVED REFERENCES TO THE OBJECT RETURNED BY THIS METHOD, AS THIS WILL CAUSE
+   * MEMORY LEAKS.
+   */
+  private @Nullable FragmentManagerHelper getFragmentManagerHelper() {
+    Activity activity = getCurrentActivity();
+    if (activity == null) {
+      return null;
+    }
+    if (activity instanceof FragmentActivity) {
+      return new FragmentManagerHelper(((FragmentActivity) activity).getSupportFragmentManager());
+    } else {
+      return new FragmentManagerHelper(activity.getFragmentManager());
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/SupportAlertFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/SupportAlertFragment.java
new file mode 100644
index 000000000000..fb987cd05996
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/dialog/SupportAlertFragment.java
@@ -0,0 +1,52 @@
+/**
+ * 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.modules.dialog;
+
+import javax.annotation.Nullable;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+
+import android.support.v4.app.DialogFragment;
+
+/**
+ * {@link AlertFragment} for apps that use the Support FragmentActivity and FragmentManager
+ * for legacy reasons.
+ */
+/* package */ class SupportAlertFragment extends DialogFragment implements DialogInterface.OnClickListener {
+
+  private final @Nullable DialogModule.AlertFragmentListener mListener;
+
+  public SupportAlertFragment(@Nullable DialogModule.AlertFragmentListener listener, Bundle arguments) {
+    mListener = listener;
+    setArguments(arguments);
+  }
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    return AlertFragment.createDialog(getActivity(), getArguments(), this);
+  }
+
+  @Override
+  public void onClick(DialogInterface dialog, int which) {
+    if (mListener != null) {
+      mListener.onClick(dialog, which);
+    }
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mListener != null) {
+      mListener.onDismiss(dialog);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK
index 18e1e514c51a..18cae1994ea5 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/BUCK
@@ -8,16 +8,17 @@ android_library(
     react_native_target('java/com/facebook/react/modules/common:common'),
     react_native_target('java/com/facebook/react/modules/network:network'),
     react_native_dep('java/com/facebook/systrace:systrace'),
-    '//libraries/fbcore/src/main/java/com/facebook/common/internal:internal',
-    '//libraries/fbcore/src/main/java/com/facebook/common/soloader:soloader',
-    '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline',
-    '//libraries/fresco/imagepipeline-backends/imagepipeline-okhttp/src/main/java/com/facebook/imagepipeline/backends/okhttp:okhttp',
-    '//libraries/imagepipeline/src/main/java/com/facebook/cache/common:common',
-    '//libraries/imagepipeline/src/main/java/com/facebook/cache/disk:disk',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/core:core',
-    '//libraries/soloader/java/com/facebook/soloader:soloader',
-    '//third-party/java/okhttp:okhttp',
-  ],
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline-okhttp'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+    react_native_dep('third-party/android/support-annotations:android-support-annotations'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
+],
   visibility = [
     'PUBLIC',
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java
index 14b95c0aebdc..3f2302ab9ab4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.java
@@ -39,25 +39,26 @@
 public class FrescoModule extends ReactContextBaseJavaModule implements
     ModuleDataCleaner.Cleanable {
 
-  @Nullable private RequestListener mRequestListener;
-  @Nullable private DiskCacheConfig mDiskCacheConfig;
+  private @Nullable ImagePipelineConfig mConfig;
 
   public FrescoModule(ReactApplicationContext reactContext) {
-    super(reactContext);
+    this(reactContext, getDefaultConfig(reactContext, null, null));
   }
 
   public FrescoModule(ReactApplicationContext reactContext, RequestListener listener) {
-    super(reactContext);
-    mRequestListener = listener;
+    this(reactContext, getDefaultConfig(reactContext, listener, null));
   }
 
   public FrescoModule(
       ReactApplicationContext reactContext,
       RequestListener listener,
       DiskCacheConfig diskCacheConfig) {
+    this(reactContext, getDefaultConfig(reactContext, listener, diskCacheConfig));
+  }
+
+  public FrescoModule(ReactApplicationContext reactContext, ImagePipelineConfig config) {
     super(reactContext);
-    mRequestListener = listener;
-    mDiskCacheConfig = diskCacheConfig;
+    mConfig = config;
   }
 
   @Override
@@ -65,35 +66,11 @@ public void initialize() {
     super.initialize();
     // Make sure the SoLoaderShim is configured to use our loader for native libraries.
     // This code can be removed if using Fresco from Maven rather than from source
-    SoLoaderShim.setHandler(
-        new SoLoaderShim.Handler() {
-          @Override
-          public void loadLibrary(String libraryName) {
-            SoLoader.loadLibrary(libraryName);
-          }
-        });
-
-    HashSet<RequestListener> requestListeners = new HashSet<>();
-    requestListeners.add(new SystraceRequestListener());
-    if (mRequestListener != null) {
-      requestListeners.add(mRequestListener);
-    }
-
-    Context context = this.getReactApplicationContext().getApplicationContext();
-    OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient();
-    ImagePipelineConfig.Builder builder =
-        OkHttpImagePipelineConfigFactory.newBuilder(context, okHttpClient);
+    SoLoaderShim.setHandler(new FrescoHandler());
 
-    builder
-        .setDownsampleEnabled(false)
-        .setRequestListeners(requestListeners);
-
-    if (mDiskCacheConfig != null) {
-      builder.setMainDiskCacheConfig(mDiskCacheConfig);
-    }
-
-    ImagePipelineConfig config = builder.build();
-    Fresco.initialize(context, config);
+    Context context = getReactApplicationContext().getApplicationContext();
+    Fresco.initialize(context, mConfig);
+    mConfig = null;
   }
 
   @Override
@@ -110,4 +87,36 @@ public void clearSensitiveData() {
     imagePipelineFactory.getMainDiskStorageCache().clearAll();
     imagePipelineFactory.getSmallImageDiskStorageCache().clearAll();
   }
+
+  private static ImagePipelineConfig getDefaultConfig(
+      Context context,
+      @Nullable RequestListener listener,
+      @Nullable DiskCacheConfig diskCacheConfig) {
+    HashSet<RequestListener> requestListeners = new HashSet<>();
+    requestListeners.add(new SystraceRequestListener());
+    if (listener != null) {
+      requestListeners.add(listener);
+    }
+
+    OkHttpClient okHttpClient = OkHttpClientProvider.getOkHttpClient();
+    ImagePipelineConfig.Builder builder =
+        OkHttpImagePipelineConfigFactory.newBuilder(context.getApplicationContext(), okHttpClient);
+
+    builder
+        .setDownsampleEnabled(false)
+        .setRequestListeners(requestListeners);
+
+    if (diskCacheConfig != null) {
+      builder.setMainDiskCacheConfig(diskCacheConfig);
+    }
+
+    return builder.build();
+  }
+
+  private static class FrescoHandler implements SoLoaderShim.Handler {
+    @Override
+    public void loadLibrary(String libraryName) {
+      SoLoader.loadLibrary(libraryName);
+    }
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK
new file mode 100644
index 000000000000..82c5b51b47b1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/BUCK
@@ -0,0 +1,19 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'intent',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':intent',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java
index e8e6322241b2..8139797ae633 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/intent/IntentModule.java
@@ -9,15 +9,16 @@
 
 package com.facebook.react.modules.intent;
 
+import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 
-import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.Promise;
 import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
-import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 
 /**
  * Intent module. Launch other activities or open URLs.
@@ -33,53 +34,101 @@ public String getName() {
     return "IntentAndroid";
   }
 
+  /**
+   * Return the URL the activity was started with
+   *
+   * @param promise a promise which is resolved with the initial URL
+   */
+  @ReactMethod
+  public void getInitialURL(Promise promise) {
+    try {
+      Activity currentActivity = getCurrentActivity();
+      String initialURL = null;
+
+      if (currentActivity != null) {
+        Intent intent = currentActivity.getIntent();
+        String action = intent.getAction();
+        Uri uri = intent.getData();
+
+        if (Intent.ACTION_VIEW.equals(action) && uri != null) {
+          initialURL = uri.toString();
+        }
+      }
+
+      promise.resolve(initialURL);
+    } catch (Exception e) {
+      promise.reject(new JSApplicationIllegalArgumentException(
+          "Could not get the initial URL : " + e.getMessage()));
+    }
+  }
+
   /**
    * Starts a corresponding external activity for the given URL.
    *
    * For example, if the URL is "https://www.facebook.com", the system browser will be opened,
    * or the "choose application" dialog will be shown.
    *
-   * @param URL the URL to open
+   * @param url the URL to open
    */
   @ReactMethod
-  public void openURL(String url) {
+  public void openURL(String url, Promise promise) {
     if (url == null || url.isEmpty()) {
-      throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
+      promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
+      return;
     }
+
     try {
+      Activity currentActivity = getCurrentActivity();
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
-      // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns
-      // the ApplicationContext instead of the Activity context.
-      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-      getReactApplicationContext().startActivity(intent);
+
+      String selfPackageName = getReactApplicationContext().getPackageName();
+      ComponentName componentName = intent.resolveActivity(
+        getReactApplicationContext().getPackageManager());
+      String otherPackageName = (componentName != null ? componentName.getPackageName() : "");
+
+      // If there is no currentActivity or we are launching to a different package we need to set
+      // the FLAG_ACTIVITY_NEW_TASK flag
+      if (currentActivity == null || !selfPackageName.equals(otherPackageName)) {
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+      }
+
+      if (currentActivity != null) {
+        currentActivity.startActivity(intent);
+      } else {
+        getReactApplicationContext().startActivity(intent);
+      }
+
+      promise.resolve(true);
     } catch (Exception e) {
-      throw new JSApplicationIllegalArgumentException(
-          "Could not open URL '" + url + "': " + e.getMessage());
+      promise.reject(new JSApplicationIllegalArgumentException(
+          "Could not open URL '" + url + "': " + e.getMessage()));
     }
   }
 
   /**
    * Determine whether or not an installed app can handle a given URL.
    *
-   * @param URL the URL to open
+   * @param url the URL to open
    * @param promise a promise that is always resolved with a boolean argument
    */
   @ReactMethod
-  public void canOpenURL(String url, Callback callback) {
+  public void canOpenURL(String url, Promise promise) {
     if (url == null || url.isEmpty()) {
-      throw new JSApplicationIllegalArgumentException("Invalid URL: " + url);
+      promise.reject(new JSApplicationIllegalArgumentException("Invalid URL: " + url));
+      return;
     }
+
     try {
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
       // We need Intent.FLAG_ACTIVITY_NEW_TASK since getReactApplicationContext() returns
       // the ApplicationContext instead of the Activity context.
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       boolean canOpen =
-          intent.resolveActivity(this.getReactApplicationContext().getPackageManager()) != null;
-      callback.invoke(canOpen);
+          intent.resolveActivity(getReactApplicationContext().getPackageManager()) != null;
+      promise.resolve(canOpen);
     } catch (Exception e) {
-      throw new JSApplicationIllegalArgumentException(
-          "Could not check if URL '" + url + "' can be opened: " + e.getMessage());
+      promise.reject(new JSApplicationIllegalArgumentException(
+          "Could not check if URL '" + url + "' can be opened: " + e.getMessage()));
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK
new file mode 100644
index 000000000000..4fb39039c503
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/location/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'location',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':location',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java
index 188a45905642..86dec164b26a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/location/LocationModule.java
@@ -36,6 +36,7 @@
 public class LocationModule extends ReactContextBaseJavaModule {
 
   private @Nullable String mWatchedProvider;
+  private static final float RCT_DEFAULT_LOCATION_ACCURACY = 100;
 
   private final LocationListener mLocationListener = new LocationListener() {
     @Override
@@ -73,11 +74,13 @@ private static class LocationOptions {
     private final long timeout;
     private final double maximumAge;
     private final boolean highAccuracy;
+    private final float distanceFilter;
 
-    private LocationOptions(long timeout, double maximumAge, boolean highAccuracy) {
+    private LocationOptions(long timeout, double maximumAge, boolean highAccuracy, float distanceFilter) {
       this.timeout = timeout;
       this.maximumAge = maximumAge;
       this.highAccuracy = highAccuracy;
+      this.distanceFilter = distanceFilter;
     }
 
     private static LocationOptions fromReactMap(ReadableMap map) {
@@ -88,8 +91,10 @@ private static LocationOptions fromReactMap(ReadableMap map) {
           map.hasKey("maximumAge") ? map.getDouble("maximumAge") : Double.POSITIVE_INFINITY;
       boolean highAccuracy =
           map.hasKey("enableHighAccuracy") && map.getBoolean("enableHighAccuracy");
+      float distanceFilter =
+          map.hasKey("distanceFilter") ? (float) map.getDouble("distanceFilter") : RCT_DEFAULT_LOCATION_ACCURACY;
 
-      return new LocationOptions(timeout, maximumAge, highAccuracy);
+      return new LocationOptions(timeout, maximumAge, highAccuracy, distanceFilter);
     }
   }
 
@@ -107,28 +112,25 @@ public void getCurrentPosition(
       Callback error) {
     LocationOptions locationOptions = LocationOptions.fromReactMap(options);
 
-    LocationManager locationManager =
-        (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
-    String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
-    if (provider == null) {
-      error.invoke("No available location provider.");
-      return;
-    }
-
-    Location location = null;
     try {
-      location = locationManager.getLastKnownLocation(provider);
+      LocationManager locationManager =
+          (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+      String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
+      if (provider == null) {
+        error.invoke("No available location provider.");
+        return;
+      }
+      Location location = locationManager.getLastKnownLocation(provider);
+      if (location != null &&
+          SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) {
+        success.invoke(locationToMap(location));
+        return;
+      }
+      new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error)
+          .invoke();
     } catch (SecurityException e) {
       throwLocationPermissionMissing(e);
     }
-    if (location != null &&
-        SystemClock.currentTimeMillis() - location.getTime() < locationOptions.maximumAge) {
-      success.invoke(locationToMap(location));
-      return;
-    }
-
-    new SingleUpdateRequest(locationManager, provider, locationOptions.timeout, success, error)
-        .invoke();
   }
 
   /**
@@ -143,24 +145,23 @@ public void startObserving(ReadableMap options) {
       return;
     }
     LocationOptions locationOptions = LocationOptions.fromReactMap(options);
-    LocationManager locationManager =
-        (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
-    String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
-    if (provider == null) {
-      emitError("No location provider available.");
-      return;
-    }
 
     try {
+      LocationManager locationManager =
+          (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+      String provider = getValidProvider(locationManager, locationOptions.highAccuracy);
+      if (provider == null) {
+        emitError("No location provider available.");
+        return;
+      }
       if (!provider.equals(mWatchedProvider)) {
         locationManager.removeUpdates(mLocationListener);
-        locationManager.requestLocationUpdates(provider, 1000, 0, mLocationListener);
+        locationManager.requestLocationUpdates(provider, 1000, locationOptions.distanceFilter, mLocationListener);
       }
+      mWatchedProvider = provider;
     } catch (SecurityException e) {
       throwLocationPermissionMissing(e);
     }
-
-    mWatchedProvider = provider;
   }
 
   /**
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/BUCK
new file mode 100644
index 000000000000..0a23937bae7e
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/BUCK
@@ -0,0 +1,22 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'netinfo',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':netinfo',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java
new file mode 100644
index 000000000000..1b8b93cbe30f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java
@@ -0,0 +1,174 @@
+/**
+ * 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.modules.netinfo;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.support.v4.net.ConnectivityManagerCompat;
+
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+
+import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+/**
+ * Module that monitors and provides information about the connectivity state of the device.
+ */
+public class NetInfoModule extends ReactContextBaseJavaModule
+    implements LifecycleEventListener {
+
+  private static final String CONNECTION_TYPE_NONE = "NONE";
+  private static final String CONNECTION_TYPE_UNKNOWN = "UNKNOWN";
+  private static final String MISSING_PERMISSION_MESSAGE =
+      "To use NetInfo on Android, add the following to your AndroidManifest.xml:\n" +
+      "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />";
+
+  private static final String ERROR_MISSING_PERMISSION = "E_MISSING_PERMISSION";
+
+  private final ConnectivityManager mConnectivityManager;
+  private final ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver;
+  private boolean mNoNetworkPermission = false;
+
+  private String mConnectivity = "";
+
+  public NetInfoModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+    mConnectivityManager =
+        (ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+    mConnectivityBroadcastReceiver = new ConnectivityBroadcastReceiver();
+  }
+
+  @Override
+  public void onHostResume() {
+    registerReceiver();
+  }
+
+  @Override
+  public void onHostPause() {
+    unregisterReceiver();
+  }
+
+  @Override
+  public void onHostDestroy() {
+  }
+
+  @Override
+  public void initialize() {
+    getReactApplicationContext().addLifecycleEventListener(this);
+  }
+
+  @Override
+  public String getName() {
+    return "NetInfo";
+  }
+
+  @ReactMethod
+  public void getCurrentConnectivity(Promise promise) {
+    if (mNoNetworkPermission) {
+      promise.reject(ERROR_MISSING_PERMISSION, MISSING_PERMISSION_MESSAGE, null);
+      return;
+    }
+    promise.resolve(createConnectivityEventMap());
+  }
+
+  @ReactMethod
+  public void isConnectionMetered(Promise promise) {
+    if (mNoNetworkPermission) {
+      promise.reject(ERROR_MISSING_PERMISSION, MISSING_PERMISSION_MESSAGE, null);
+      return;
+    }
+    promise.resolve(ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
+  }
+
+  private void registerReceiver() {
+    IntentFilter filter = new IntentFilter();
+    filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+    getReactApplicationContext().registerReceiver(mConnectivityBroadcastReceiver, filter);
+    mConnectivityBroadcastReceiver.setRegistered(true);
+  }
+
+  private void unregisterReceiver() {
+    if (mConnectivityBroadcastReceiver.isRegistered()) {
+      getReactApplicationContext().unregisterReceiver(mConnectivityBroadcastReceiver);
+      mConnectivityBroadcastReceiver.setRegistered(false);
+    }
+  }
+
+  private void updateAndSendConnectionType() {
+    String currentConnectivity = getCurrentConnectionType();
+    // It is possible to get multiple broadcasts for the same connectivity change, so we only
+    // update and send an event when the connectivity has indeed changed.
+    if (!currentConnectivity.equalsIgnoreCase(mConnectivity)) {
+      mConnectivity = currentConnectivity;
+      sendConnectivityChangedEvent();
+    }
+  }
+
+  private String getCurrentConnectionType() {
+    try {
+      NetworkInfo networkInfo = mConnectivityManager.getActiveNetworkInfo();
+      if (networkInfo == null || !networkInfo.isConnected()) {
+        return CONNECTION_TYPE_NONE;
+      } else if (ConnectivityManager.isNetworkTypeValid(networkInfo.getType())) {
+        return networkInfo.getTypeName().toUpperCase();
+      } else {
+        return CONNECTION_TYPE_UNKNOWN;
+      }
+    } catch (SecurityException e) {
+      mNoNetworkPermission = true;
+      return CONNECTION_TYPE_UNKNOWN;
+    }
+  }
+
+  private void sendConnectivityChangedEvent() {
+    getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class)
+        .emit("networkStatusDidChange", createConnectivityEventMap());
+  }
+
+  private WritableMap createConnectivityEventMap() {
+    WritableMap event = new WritableNativeMap();
+    event.putString("network_info", mConnectivity);
+    return event;
+  }
+
+  /**
+   * Class that receives intents whenever the connection type changes.
+   * NB: It is possible on some devices to receive certain connection type changes multiple times.
+   */
+  private class ConnectivityBroadcastReceiver extends BroadcastReceiver {
+
+    //TODO: Remove registered check when source of crash is found. t9846865
+    private boolean isRegistered = false;
+
+    public void setRegistered(boolean registered) {
+      isRegistered = registered;
+    }
+
+    public boolean isRegistered() {
+      return isRegistered;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+        updateAndSendConnectionType();
+      }
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK
index 2de89f0d35db..254326d6a7bb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/BUCK
@@ -4,15 +4,17 @@ android_library(
   name = 'network',
   srcs = glob(['**/*.java']),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/modules/core:core'),
     react_native_target('java/com/facebook/react/common:common'),
-    '//third-party/java/android/support/v4:lib-support-v4',
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
-    '//third-party/java/okhttp:okhttp',
-    '//third-party/java/okio:okio',
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/stetho:stetho'),
+    react_native_dep('third-party/java/stetho:stetho-okhttp'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java
new file mode 100644
index 000000000000..d24540ceaa4d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/ForwardingCookieHandler.java
@@ -0,0 +1,230 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.modules.network;
+
+import javax.annotation.Nullable;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.webkit.CookieManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.ValueCallback;
+
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.GuardedAsyncTask;
+import com.facebook.react.bridge.GuardedResultAsyncTask;
+import com.facebook.react.bridge.ReactContext;
+
+/**
+ * Cookie handler that forwards all cookies to the WebView CookieManager.
+ *
+ * This class relies on CookieManager to persist cookies to disk so cookies may be lost if the
+ * application is terminated before it syncs.
+ */
+public class ForwardingCookieHandler extends CookieHandler {
+  private static final String VERSION_ZERO_HEADER = "Set-cookie";
+  private static final String VERSION_ONE_HEADER = "Set-cookie2";
+  private static final String COOKIE_HEADER = "Cookie";
+
+  // As CookieManager was synchronous before API 21 this class emulates the async behavior on <21.
+  private static final boolean USES_LEGACY_STORE = Build.VERSION.SDK_INT < 21;
+
+  private final CookieSaver mCookieSaver;
+  private final ReactContext mContext;
+  private @Nullable CookieManager mCookieManager;
+
+  public ForwardingCookieHandler(ReactContext context) {
+    mContext = context;
+    mCookieSaver = new CookieSaver();
+  }
+
+  @Override
+  public Map<String, List<String>> get(URI uri, Map<String, List<String>> headers)
+      throws IOException {
+    String cookies = getCookieManager().getCookie(uri.toString());
+    if (TextUtils.isEmpty(cookies)) {
+      return Collections.emptyMap();
+    }
+
+    return Collections.singletonMap(COOKIE_HEADER, Collections.singletonList(cookies));
+  }
+
+  @Override
+  public void put(URI uri, Map<String, List<String>> headers) throws IOException {
+    String url = uri.toString();
+    for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+      String key = entry.getKey();
+      if (key != null && isCookieHeader(key)) {
+        addCookies(url, entry.getValue());
+      }
+    }
+  }
+
+  public void clearCookies(final Callback callback) {
+    if (USES_LEGACY_STORE) {
+      new GuardedResultAsyncTask<Boolean>(mContext) {
+        @Override
+        protected Boolean doInBackgroundGuarded() {
+          getCookieManager().removeAllCookie();
+          mCookieSaver.onCookiesModified();
+          return true;
+        }
+
+        @Override
+        protected void onPostExecuteGuarded(Boolean result) {
+          callback.invoke(result);
+        }
+      }.execute();
+    } else {
+      clearCookiesAsync(callback);
+    }
+  }
+
+  private void clearCookiesAsync(final Callback callback) {
+    getCookieManager().removeAllCookies(
+        new ValueCallback<Boolean>() {
+          @Override
+          public void onReceiveValue(Boolean value) {
+            mCookieSaver.onCookiesModified();
+            callback.invoke(value);
+          }
+        });
+  }
+
+  public void destroy() {
+    if (USES_LEGACY_STORE) {
+      getCookieManager().removeExpiredCookie();
+      mCookieSaver.persistCookies();
+    }
+  }
+
+  private void addCookies(final String url, final List<String> cookies) {
+    if (USES_LEGACY_STORE) {
+      runInBackground(
+          new Runnable() {
+            @Override
+            public void run() {
+              for (String cookie : cookies) {
+                getCookieManager().setCookie(url, cookie);
+              }
+              mCookieSaver.onCookiesModified();
+            }
+          });
+    } else {
+      for (String cookie : cookies) {
+        addCookieAsync(url, cookie);
+      }
+      mCookieSaver.onCookiesModified();
+    }
+  }
+
+  @TargetApi(21)
+  private void addCookieAsync(String url, String cookie) {
+    getCookieManager().setCookie(url, cookie, null);
+  }
+
+  private static boolean isCookieHeader(String name) {
+    return name.equalsIgnoreCase(VERSION_ZERO_HEADER) || name.equalsIgnoreCase(VERSION_ONE_HEADER);
+  }
+
+  private void runInBackground(final Runnable runnable) {
+    new GuardedAsyncTask<Void, Void>(mContext) {
+      @Override
+      protected void doInBackgroundGuarded(Void... params) {
+        runnable.run();
+      }
+    }.execute();
+  }
+
+  /**
+   * Instantiating CookieManager in KitKat+ will load the Chromium task taking a 100ish ms so we
+   * do it lazily to make sure it's done on a background thread as needed.
+   */
+  private CookieManager getCookieManager() {
+    if (mCookieManager == null) {
+      possiblyWorkaroundSyncManager(mContext);
+      mCookieManager = CookieManager.getInstance();
+
+      if (USES_LEGACY_STORE) {
+        mCookieManager.removeExpiredCookie();
+      }
+    }
+
+    return mCookieManager;
+  }
+
+  private static void possiblyWorkaroundSyncManager(Context context) {
+    if (USES_LEGACY_STORE) {
+      // This is to work around a bug where CookieManager may fail to instantiate if
+      // CookieSyncManager has never been created. Note that the sync() may not be required but is
+      // here of legacy reasons.
+      CookieSyncManager syncManager = CookieSyncManager.createInstance(context);
+      syncManager.sync();
+    }
+  }
+
+  /**
+   * Responsible for flushing cookies to disk. Flushes to disk with a maximum delay of 30 seconds.
+   * This class is only active if we are on API < 21.
+   */
+  private class CookieSaver {
+    private static final int MSG_PERSIST_COOKIES = 1;
+
+    private static final int TIMEOUT = 30 * 1000; // 30 seconds
+
+    private final Handler mHandler;
+
+    public CookieSaver() {
+      mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+          if (msg.what == MSG_PERSIST_COOKIES) {
+            persistCookies();
+            return true;
+          } else {
+            return false;
+          }
+        }
+      });
+    }
+
+    public void onCookiesModified() {
+      if (USES_LEGACY_STORE) {
+        mHandler.sendEmptyMessageDelayed(MSG_PERSIST_COOKIES, TIMEOUT);
+      }
+    }
+
+    public void persistCookies() {
+      mHandler.removeMessages(MSG_PERSIST_COOKIES);
+      runInBackground(
+          new Runnable() {
+            @Override
+            public void run() {
+              if (USES_LEGACY_STORE) {
+                CookieSyncManager syncManager = CookieSyncManager.getInstance();
+                syncManager.sync();
+              } else {
+                flush();
+              }
+            }
+          });
+    }
+
+    @TargetApi(21)
+    private void flush() {
+      getCookieManager().flush();
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
index 39d2cb8109ce..cd9e6b98ca37 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java
@@ -15,7 +15,10 @@
 import java.io.InputStream;
 import java.io.Reader;
 
+import java.util.concurrent.TimeUnit;
+
 import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ExecutorToken;
 import com.facebook.react.bridge.GuardedAsyncTask;
 import com.facebook.react.bridge.ReactApplicationContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
@@ -25,6 +28,7 @@
 import com.facebook.react.bridge.WritableArray;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.facebook.stetho.okhttp.StethoInterceptor;
 
 import com.squareup.okhttp.Callback;
 import com.squareup.okhttp.Headers;
@@ -56,6 +60,7 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
   private static final int CHUNK_TIMEOUT_NS = 100 * 1000000; // 100ms
 
   private final OkHttpClient mClient;
+  private final ForwardingCookieHandler mCookieHandler;
   private final @Nullable String mDefaultUserAgent;
   private boolean mShuttingDown;
 
@@ -65,30 +70,37 @@ public final class NetworkingModule extends ReactContextBaseJavaModule {
       OkHttpClient client) {
     super(reactContext);
     mClient = client;
+    mClient.networkInterceptors().add(new StethoInterceptor());
+    mCookieHandler = new ForwardingCookieHandler(reactContext);
     mShuttingDown = false;
     mDefaultUserAgent = defaultUserAgent;
   }
 
   /**
-   * @param reactContext the ReactContext of the application
+   * @param context the ReactContext of the application
    */
-  public NetworkingModule(ReactApplicationContext reactContext) {
-    this(reactContext, null, OkHttpClientProvider.getOkHttpClient());
+  public NetworkingModule(final ReactApplicationContext context) {
+    this(context, null, OkHttpClientProvider.getOkHttpClient());
   }
 
   /**
-   * @param reactContext the ReactContext of the application
+   * @param context the ReactContext of the application
    * @param defaultUserAgent the User-Agent header that will be set for all requests where the
    * caller does not provide one explicitly
    */
-  public NetworkingModule(ReactApplicationContext reactContext, String defaultUserAgent) {
-    this(reactContext, defaultUserAgent, OkHttpClientProvider.getOkHttpClient());
+  public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
+    this(context, defaultUserAgent, OkHttpClientProvider.getOkHttpClient());
   }
 
   public NetworkingModule(ReactApplicationContext reactContext, OkHttpClient client) {
     this(reactContext, null, client);
   }
 
+  @Override
+  public void initialize() {
+    mClient.setCookieHandler(mCookieHandler);
+  }
+
   @Override
   public String getName() {
     return "RCTNetworking";
@@ -98,25 +110,43 @@ public String getName() {
   public void onCatalystInstanceDestroy() {
     mShuttingDown = true;
     mClient.cancel(null);
+
+    mCookieHandler.destroy();
+    mClient.setCookieHandler(null);
   }
 
   @ReactMethod
+  /**
+   * @param timeout value of 0 results in no timeout
+   */
   public void sendRequest(
+      final ExecutorToken executorToken,
       String method,
       String url,
       final int requestId,
       ReadableArray headers,
       ReadableMap data,
-      final boolean useIncrementalUpdates) {
+      final boolean useIncrementalUpdates,
+      int timeout) {
     Request.Builder requestBuilder = new Request.Builder().url(url);
 
     if (requestId != 0) {
       requestBuilder.tag(requestId);
     }
 
+    OkHttpClient client = mClient;
+    // If the current timeout does not equal the passed in timeout, we need to clone the existing
+    // client and set the timeout explicitly on the clone.  This is cheap as everything else is
+    // shared under the hood.
+    // See https://github.com/square/okhttp/wiki/Recipes#per-call-configuration for more information
+    if (timeout != mClient.getConnectTimeout()) {
+      client = mClient.clone();
+      client.setReadTimeout(timeout, TimeUnit.MILLISECONDS);
+    }
+
     Headers requestHeaders = extractHeaders(headers, data);
     if (requestHeaders == null) {
-      onRequestError(requestId, "Unrecognized headers format");
+      onRequestError(executorToken, requestId, "Unrecognized headers format");
       return;
     }
     String contentType = requestHeaders.get(CONTENT_TYPE_HEADER_NAME);
@@ -124,10 +154,13 @@ public void sendRequest(
     requestBuilder.headers(requestHeaders);
 
     if (data == null) {
-      requestBuilder.method(method, null);
+      requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
     } else if (data.hasKey(REQUEST_BODY_KEY_STRING)) {
       if (contentType == null) {
-        onRequestError(requestId, "Payload is set but no content-type header specified");
+        onRequestError(
+            executorToken,
+            requestId,
+            "Payload is set but no content-type header specified");
         return;
       }
       String body = data.getString(REQUEST_BODY_KEY_STRING);
@@ -135,7 +168,7 @@ public void sendRequest(
       if (RequestBodyUtil.isGzipEncoding(contentEncoding)) {
         RequestBody requestBody = RequestBodyUtil.createGzip(contentMediaType, body);
         if (requestBody == null) {
-          onRequestError(requestId, "Failed to gzip request body");
+          onRequestError(executorToken, requestId, "Failed to gzip request body");
           return;
         }
         requestBuilder.method(method, requestBody);
@@ -144,14 +177,17 @@ public void sendRequest(
       }
     } else if (data.hasKey(REQUEST_BODY_KEY_URI)) {
       if (contentType == null) {
-        onRequestError(requestId, "Payload is set but no content-type header specified");
+        onRequestError(
+            executorToken,
+            requestId,
+            "Payload is set but no content-type header specified");
         return;
       }
       String uri = data.getString(REQUEST_BODY_KEY_URI);
       InputStream fileInputStream =
           RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri);
       if (fileInputStream == null) {
-        onRequestError(requestId, "Could not retrieve file for uri " + uri);
+        onRequestError(executorToken, requestId, "Could not retrieve file for uri " + uri);
         return;
       }
       requestBuilder.method(
@@ -162,25 +198,25 @@ public void sendRequest(
         contentType = "multipart/form-data";
       }
       ReadableArray parts = data.getArray(REQUEST_BODY_KEY_FORMDATA);
-      MultipartBuilder multipartBuilder = constructMultipartBody(parts, contentType, requestId);
+      MultipartBuilder multipartBuilder =
+          constructMultipartBody(executorToken, parts, contentType, requestId);
       if (multipartBuilder == null) {
         return;
       }
       requestBuilder.method(method, multipartBuilder.build());
     } else {
       // Nothing in data payload, at least nothing we could understand anyway.
-      // Ignore and treat it as if it were null.
-      requestBuilder.method(method, null);
+      requestBuilder.method(method, RequestBodyUtil.getEmptyBody(method));
     }
 
-    mClient.newCall(requestBuilder.build()).enqueue(
+    client.newCall(requestBuilder.build()).enqueue(
         new Callback() {
           @Override
           public void onFailure(Request request, IOException e) {
             if (mShuttingDown) {
               return;
             }
-            onRequestError(requestId, e.getMessage());
+            onRequestError(executorToken, requestId, e.getMessage());
           }
 
           @Override
@@ -190,25 +226,28 @@ public void onResponse(Response response) throws IOException {
             }
 
             // Before we touch the body send headers to JS
-            onResponseReceived(requestId, response);
+            onResponseReceived(executorToken, requestId, response);
 
             ResponseBody responseBody = response.body();
             try {
               if (useIncrementalUpdates) {
-                readWithProgress(requestId, responseBody);
-                onRequestSuccess(requestId);
+                readWithProgress(executorToken, requestId, responseBody);
+                onRequestSuccess(executorToken, requestId);
               } else {
-                onDataReceived(requestId, responseBody.string());
-                onRequestSuccess(requestId);
+                onDataReceived(executorToken, requestId, responseBody.string());
+                onRequestSuccess(executorToken, requestId);
               }
             } catch (IOException e) {
-              onRequestError(requestId, e.getMessage());
+              onRequestError(executorToken, requestId, e.getMessage());
             }
           }
         });
   }
 
-  private void readWithProgress(int requestId, ResponseBody responseBody) throws IOException {
+  private void readWithProgress(
+      ExecutorToken executorToken,
+      int requestId,
+      ResponseBody responseBody) throws IOException {
     Reader reader = responseBody.charStream();
     try {
       StringBuilder sb = new StringBuilder(getBufferSize(responseBody));
@@ -219,14 +258,14 @@ private void readWithProgress(int requestId, ResponseBody responseBody) throws I
         sb.append(buffer, 0, read);
         long now = System.nanoTime();
         if (shouldDispatch(now, last)) {
-          onDataReceived(requestId, sb.toString());
+          onDataReceived(executorToken, requestId, sb.toString());
           sb.setLength(0);
           last = now;
         }
       }
 
       if (sb.length() > 0) {
-        onDataReceived(requestId, sb.toString());
+        onDataReceived(executorToken, requestId, sb.toString());
       }
     } finally {
       reader.close();
@@ -246,39 +285,43 @@ private static int getBufferSize(ResponseBody responseBody) throws IOException {
     }
   }
 
-  private void onDataReceived(int requestId, String data) {
+  private void onDataReceived(ExecutorToken ExecutorToken, int requestId, String data) {
     WritableArray args = Arguments.createArray();
     args.pushInt(requestId);
     args.pushString(data);
 
-    getEventEmitter().emit("didReceiveNetworkData", args);
+    getEventEmitter(ExecutorToken).emit("didReceiveNetworkData", args);
   }
 
-  private void onRequestError(int requestId, String error) {
+  private void onRequestError(ExecutorToken ExecutorToken, int requestId, String error) {
     WritableArray args = Arguments.createArray();
     args.pushInt(requestId);
     args.pushString(error);
 
-    getEventEmitter().emit("didCompleteNetworkResponse", args);
+    getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args);
   }
 
-  private void onRequestSuccess(int requestId) {
+  private void onRequestSuccess(ExecutorToken ExecutorToken, int requestId) {
     WritableArray args = Arguments.createArray();
     args.pushInt(requestId);
     args.pushNull();
 
-    getEventEmitter().emit("didCompleteNetworkResponse", args);
+    getEventEmitter(ExecutorToken).emit("didCompleteNetworkResponse", args);
   }
 
-  private void onResponseReceived(int requestId, Response response) {
+  private void onResponseReceived(
+      ExecutorToken ExecutorToken,
+      int requestId,
+      Response response) {
     WritableMap headers = translateHeaders(response.headers());
 
     WritableArray args = Arguments.createArray();
     args.pushInt(requestId);
     args.pushInt(response.code());
     args.pushMap(headers);
+    args.pushString(response.request().urlString());
 
-    getEventEmitter().emit("didReceiveNetworkResponse", args);
+    getEventEmitter(ExecutorToken).emit("didReceiveNetworkResponse", args);
   }
 
   private static WritableMap translateHeaders(Headers headers) {
@@ -298,7 +341,7 @@ private static WritableMap translateHeaders(Headers headers) {
   }
 
   @ReactMethod
-  public void abortRequest(final int requestId) {
+  public void abortRequest(ExecutorToken executorToken, final int requestId) {
     // We have to use AsyncTask since this might trigger a NetworkOnMainThreadException, this is an
     // open issue on OkHttp: https://github.com/square/okhttp/issues/869
     new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@@ -309,7 +352,22 @@ protected void doInBackgroundGuarded(Void... params) {
     }.execute();
   }
 
-  private @Nullable MultipartBuilder constructMultipartBody(
+  @ReactMethod
+  public void clearCookies(
+      ExecutorToken executorToken,
+      com.facebook.react.bridge.Callback callback) {
+    mCookieHandler.clearCookies(callback);
+  }
+
+  @Override
+  public boolean supportsWebWorkers() {
+    return true;
+  }
+
+  private
+  @Nullable
+  MultipartBuilder constructMultipartBody(
+      ExecutorToken ExecutorToken,
       ReadableArray body,
       String contentType,
       int requestId) {
@@ -323,7 +381,10 @@ protected void doInBackgroundGuarded(Void... params) {
       ReadableArray headersArray = bodyPart.getArray("headers");
       Headers headers = extractHeaders(headersArray, null);
       if (headers == null) {
-        onRequestError(requestId, "Missing or invalid header format for FormData part.");
+        onRequestError(
+            ExecutorToken,
+            requestId,
+            "Missing or invalid header format for FormData part.");
         return null;
       }
       MediaType partContentType = null;
@@ -340,19 +401,25 @@ protected void doInBackgroundGuarded(Void... params) {
         multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue));
       } else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI)) {
         if (partContentType == null) {
-          onRequestError(requestId, "Binary FormData part needs a content-type header.");
+          onRequestError(
+              ExecutorToken,
+              requestId,
+              "Binary FormData part needs a content-type header.");
           return null;
         }
         String fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI);
         InputStream fileInputStream =
             RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr);
         if (fileInputStream == null) {
-          onRequestError(requestId, "Could not retrieve file for uri " + fileContentUriStr);
+          onRequestError(
+              ExecutorToken,
+              requestId,
+              "Could not retrieve file for uri " + fileContentUriStr);
           return null;
         }
         multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream));
       } else {
-        onRequestError(requestId, "Unrecognized FormData part.");
+        onRequestError(ExecutorToken, requestId, "Unrecognized FormData part.");
       }
     }
     return multipartBuilder;
@@ -361,7 +428,9 @@ protected void doInBackgroundGuarded(Void... params) {
   /**
    * Extracts the headers from the Array. If the format is invalid, this method will return null.
    */
-  private @Nullable Headers extractHeaders(
+  private
+  @Nullable
+  Headers extractHeaders(
       @Nullable ReadableArray headersArray,
       @Nullable ReadableMap requestData) {
     if (headersArray == null) {
@@ -390,8 +459,8 @@ protected void doInBackgroundGuarded(Void... params) {
     return headersBuilder.build();
   }
 
-  private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter() {
+  private DeviceEventManagerModule.RCTDeviceEventEmitter getEventEmitter(ExecutorToken ExecutorToken) {
     return getReactApplicationContext()
-        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
+        .getJSModule(ExecutorToken, DeviceEventManagerModule.RCTDeviceEventEmitter.class);
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
index fb700201302c..c9c1b733a6d9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/OkHttpClientProvider.java
@@ -9,7 +9,10 @@
 
 package com.facebook.react.modules.network;
 
+import javax.annotation.Nullable;
+
 import java.util.concurrent.TimeUnit;
+
 import com.squareup.okhttp.OkHttpClient;
 
 /**
@@ -19,18 +22,24 @@
 public class OkHttpClientProvider {
 
   // Centralized OkHttpClient for all networking requests.
-  private static OkHttpClient sClient;
+  private static @Nullable OkHttpClient sClient;
 
   public static OkHttpClient getOkHttpClient() {
     if (sClient == null) {
-      // TODO: #7108751 plug in stetho
-      sClient = new OkHttpClient();
-
-      // No timeouts by default
-      sClient.setConnectTimeout(0, TimeUnit.MILLISECONDS);
-      sClient.setReadTimeout(0, TimeUnit.MILLISECONDS);
-      sClient.setWriteTimeout(0, TimeUnit.MILLISECONDS);
+      sClient = createClient();
     }
     return sClient;
   }
+
+  private static OkHttpClient createClient() {
+    // TODO: #7108751 plug in stetho
+    OkHttpClient client = new OkHttpClient();
+
+    // No timeouts by default
+    client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
+    client.setReadTimeout(0, TimeUnit.MILLISECONDS);
+    client.setWriteTimeout(0, TimeUnit.MILLISECONDS);
+
+    return client;
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
index 7ce69c37e2d9..f834120acaf1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/network/RequestBodyUtil.java
@@ -27,6 +27,7 @@
 import com.squareup.okhttp.RequestBody;
 import com.squareup.okhttp.internal.Util;
 import okio.BufferedSink;
+import okio.ByteString;
 import okio.Okio;
 import okio.Source;
 
@@ -112,4 +113,15 @@ public void writeTo(BufferedSink sink) throws IOException {
       }
     };
   }
+
+  /**
+   * Creates a empty RequestBody if required by the http method spec, otherwise use null
+   */
+  public static RequestBody getEmptyBody(String method) {
+    if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH")) {
+      return RequestBody.create(null, ByteString.EMPTY);
+    } else {
+      return null;
+    }
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK
new file mode 100644
index 000000000000..3cedd2172b69
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'statusbar',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':statusbar',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
new file mode 100644
index 000000000000..29d08a833f65
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ * <p/>
+ * 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.modules.statusbar;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.UiThreadUtil;
+
+public class StatusBarModule extends ReactContextBaseJavaModule {
+
+  private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
+  private static final String ERROR_NO_ACTIVITY_MESSAGE =
+    "Tried to change the status bar while not attached to an Activity";
+
+  public StatusBarModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "StatusBarManager";
+  }
+
+  @ReactMethod
+  public void setColor(final int color, final boolean animated, final Promise res) {
+    final Activity activity = getCurrentActivity();
+    if (activity == null) {
+      res.reject(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
+      return;
+    }
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      UiThreadUtil.runOnUiThread(
+        new Runnable() {
+          @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+          @Override
+          public void run() {
+            if (animated) {
+              int curColor = activity.getWindow().getStatusBarColor();
+              ValueAnimator colorAnimation = ValueAnimator.ofObject(
+                new ArgbEvaluator(), curColor, color);
+
+              colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animator) {
+                  activity.getWindow().setStatusBarColor((Integer) animator.getAnimatedValue());
+                }
+              });
+              colorAnimation
+                .setDuration(300)
+                .setStartDelay(0);
+              colorAnimation.start();
+            } else {
+              activity.getWindow().setStatusBarColor(color);
+            }
+            res.resolve(null);
+          }
+        }
+      );
+    } else {
+      res.resolve(null);
+    }
+  }
+
+  @ReactMethod
+  public void setTranslucent(final boolean translucent, final Promise res) {
+    final Activity activity = getCurrentActivity();
+    if (activity == null) {
+      res.reject(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
+      return;
+    }
+    UiThreadUtil.runOnUiThread(
+      new Runnable() {
+        @Override
+        public void run() {
+          int flags = activity.getWindow().getDecorView().getSystemUiVisibility();
+          if (translucent) {
+            flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+          } else {
+            flags &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+          }
+          activity.getWindow().getDecorView().setSystemUiVisibility(flags);
+          ViewCompat.requestApplyInsets(activity.getWindow().getDecorView());
+          res.resolve(null);
+        }
+      }
+    );
+  }
+
+  @ReactMethod
+  public void setHidden(final boolean hidden, final Promise res) {
+    final Activity activity = getCurrentActivity();
+    if (activity == null) {
+      res.reject(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
+      return;
+    }
+    UiThreadUtil.runOnUiThread(
+      new Runnable() {
+        @Override
+        public void run() {
+          if (hidden) {
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+          } else {
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+          }
+
+          res.resolve(null);
+        }
+      }
+    );
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK
index 723db50f703d..b9d5d418cbac 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/BUCK
@@ -7,9 +7,9 @@ android_library(
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/modules/common:common'),
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java
index ef27b332ff3e..87f9daf5c147 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java
@@ -30,7 +30,6 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper {
 
   private static final int DATABASE_VERSION = 1;
   private static final int SLEEP_TIME_MS = 30;
-  private static final long DEFAULT_MAX_DB_SIZE = 6L * 1024L * 1024L; // 6 MB in bytes
 
   static final String TABLE_CATALYST = "catalystLocalStorage";
   static final String KEY_COLUMN = "key";
@@ -42,9 +41,11 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper {
           VALUE_COLUMN + " TEXT NOT NULL" +
           ")";
 
+  private static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance;
+
   private Context mContext;
   private @Nullable SQLiteDatabase mDb;
-  private static @Nullable ReactDatabaseSupplier mReactDatabaseSupplierInstance;
+  private long mMaximumDatabaseSize =  6L * 1024L * 1024L; // 6 MB in bytes
 
   private ReactDatabaseSupplier(Context context) {
     super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -52,10 +53,10 @@ private ReactDatabaseSupplier(Context context) {
   }
 
   public static ReactDatabaseSupplier getInstance(Context context) {
-    if (mReactDatabaseSupplierInstance == null) {
-      mReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context);
+    if (sReactDatabaseSupplierInstance == null) {
+      sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext());
     }
-    return mReactDatabaseSupplierInstance;
+    return sReactDatabaseSupplierInstance;
   }
 
   @Override
@@ -104,7 +105,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     // This is a sane limit to protect the user from the app storing too much data in the database.
     // This also protects the database from filling up the disk cache and becoming malformed
     // (endTransaction() calls will throw an exception, not rollback, and leave the db malformed).
-    mDb.setMaximumSize(DEFAULT_MAX_DB_SIZE);
+    mDb.setMaximumSize(mMaximumDatabaseSize);
     return true;
   }
 
@@ -136,6 +137,17 @@ public synchronized void clearAndCloseDatabase() throws RuntimeException {
     get().delete(TABLE_CATALYST, null, null);
   }
 
+  /**
+   * Sets the maximum size the database will grow to. The maximum size cannot
+   * be set below the current size.
+   */
+  public synchronized void setMaximumSize(long size) {
+    mMaximumDatabaseSize = size;
+    if (mDb != null) {
+      mDb.setMaximumSize(mMaximumDatabaseSize);
+    }
+  }
+
   private synchronized boolean deleteDatabase() {
     closeDatabase();
     return mContext.deleteDatabase(DATABASE_NAME);
@@ -150,6 +162,6 @@ private synchronized void closeDatabase() {
 
   // For testing purposes only!
   public static void deleteInstance() {
-    mReactDatabaseSupplierInstance = null;
+    sReactDatabaseSupplierInstance = null;
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK
index deac058df538..a7be01fc4d7a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/BUCK
@@ -6,8 +6,8 @@ android_library(
   deps = [
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK
new file mode 100644
index 000000000000..1b7d3eac350c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'timepicker',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':timepicker',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java
new file mode 100644
index 000000000000..3baf4c924166
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/DismissableTimePickerDialog.java
@@ -0,0 +1,58 @@
+/**
+ * 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.modules.timepicker;
+
+import javax.annotation.Nullable;
+
+import android.app.TimePickerDialog;
+import android.content.Context;
+import android.os.Build;
+
+/**
+ * <p>
+ *   Certain versions of Android (Jellybean-KitKat) have a bug where when dismissed, the
+ *   {@link TimePickerDialog} still calls the OnTimeSetListener. This class works around that issue
+ *   by *not* calling super.onStop on KitKat on lower, as that would erroneously call the
+ *   OnTimeSetListener when the dialog is dismissed, or call it twice when "OK" is pressed.
+ * </p>
+ *
+ * <p>
+ *   See: <a href="https://code.google.com/p/android/issues/detail?id=34833">Issue 34833</a>
+ * </p>
+ */
+public class DismissableTimePickerDialog extends TimePickerDialog {
+
+  public DismissableTimePickerDialog(
+      Context context,
+      @Nullable OnTimeSetListener callback,
+      int hourOfDay,
+      int minute,
+      boolean is24HourView) {
+    super(context, callback, hourOfDay, minute, is24HourView);
+  }
+
+  public DismissableTimePickerDialog(
+      Context context,
+      int theme,
+      @Nullable OnTimeSetListener callback,
+      int hourOfDay,
+      int minute,
+      boolean is24HourView) {
+    super(context, theme, callback, hourOfDay, minute, is24HourView);
+  }
+
+  @Override
+  protected void onStop() {
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+      super.onStop();
+    }
+  }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/SupportTimePickerDialogFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/SupportTimePickerDialogFragment.java
new file mode 100644
index 000000000000..d745984a1d42
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/SupportTimePickerDialogFragment.java
@@ -0,0 +1,50 @@
+/**
+ * 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.modules.timepicker;
+
+import javax.annotation.Nullable;
+
+import android.app.Dialog;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+
+@SuppressWarnings("ValidFragment")
+public class SupportTimePickerDialogFragment extends DialogFragment {
+
+  @Nullable
+  private OnTimeSetListener mOnTimeSetListener;
+  @Nullable
+  private OnDismissListener mOnDismissListener;
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    final Bundle args = getArguments();
+    return TimePickerDialogFragment.createDialog(args, getActivity(), mOnTimeSetListener);
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mOnDismissListener != null) {
+      mOnDismissListener.onDismiss(dialog);
+    }
+  }
+
+  public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
+    mOnDismissListener = onDismissListener;
+  }
+
+  public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
+    mOnTimeSetListener = onTimeSetListener;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java
new file mode 100644
index 000000000000..64005ae1a845
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogFragment.java
@@ -0,0 +1,78 @@
+/**
+ * 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.modules.timepicker;
+
+import javax.annotation.Nullable;
+
+import java.util.Calendar;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.text.format.DateFormat;
+
+@SuppressWarnings("ValidFragment")
+public class TimePickerDialogFragment extends DialogFragment {
+
+  @Nullable
+  private OnTimeSetListener mOnTimeSetListener;
+  @Nullable
+  private OnDismissListener mOnDismissListener;
+
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    final Bundle args = getArguments();
+    return createDialog(args, getActivity(), mOnTimeSetListener);
+  }
+
+  /*package*/ static Dialog createDialog(
+      Bundle args, Context activityContext, @Nullable OnTimeSetListener onTimeSetListener
+  ) {
+    final Calendar now = Calendar.getInstance();
+    int hour = now.get(Calendar.HOUR_OF_DAY);
+    int minute = now.get(Calendar.MINUTE);
+    boolean is24hour = DateFormat.is24HourFormat(activityContext);
+
+    if (args != null) {
+      hour = args.getInt(TimePickerDialogModule.ARG_HOUR, now.get(Calendar.HOUR_OF_DAY));
+      minute = args.getInt(TimePickerDialogModule.ARG_MINUTE, now.get(Calendar.MINUTE));
+      is24hour = args.getBoolean(
+          TimePickerDialogModule.ARG_IS24HOUR,
+          DateFormat.is24HourFormat(activityContext));
+    }
+
+    return new DismissableTimePickerDialog(
+        activityContext,
+        onTimeSetListener,
+        hour,
+        minute,
+        is24hour);
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (mOnDismissListener != null) {
+      mOnDismissListener.onDismiss(dialog);
+    }
+  }
+
+  public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
+    mOnDismissListener = onDismissListener;
+  }
+
+  public void setOnTimeSetListener(@Nullable OnTimeSetListener onTimeSetListener) {
+    mOnTimeSetListener = onTimeSetListener;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java
new file mode 100644
index 000000000000..7d627b5833a5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/timepicker/TimePickerDialogModule.java
@@ -0,0 +1,153 @@
+/**
+ * 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.modules.timepicker;
+
+import javax.annotation.Nullable;
+
+import java.util.Map;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.TimePickerDialog.OnTimeSetListener;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.widget.TimePicker;
+
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.common.annotations.VisibleForTesting;
+
+/**
+ * {@link NativeModule} that allows JS to show a native time picker dialog and get called back when
+ * the user selects a time.
+ */
+public class TimePickerDialogModule extends ReactContextBaseJavaModule {
+
+  @VisibleForTesting
+  public static final String FRAGMENT_TAG = "TimePickerAndroid";
+
+  private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
+
+  /* package */ static final String ARG_HOUR = "hour";
+  /* package */ static final String ARG_MINUTE = "minute";
+  /* package */ static final String ARG_IS24HOUR = "is24Hour";
+  /* package */ static final String ACTION_TIME_SET = "timeSetAction";
+  /* package */ static final String ACTION_DISMISSED = "dismissedAction";
+
+  public TimePickerDialogModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "TimePickerAndroid";
+  }
+
+  private class TimePickerDialogListener implements OnTimeSetListener, OnDismissListener {
+
+    private final Promise mPromise;
+    private boolean mPromiseResolved = false;
+
+    public TimePickerDialogListener(Promise promise) {
+      mPromise = promise;
+    }
+
+    @Override
+    public void onTimeSet(TimePicker view, int hour, int minute) {
+      if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
+        WritableMap result = new WritableNativeMap();
+        result.putString("action", ACTION_TIME_SET);
+        result.putInt("hour", hour);
+        result.putInt("minute", minute);
+        mPromise.resolve(result);
+        mPromiseResolved = true;
+      }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+      if (!mPromiseResolved && getReactApplicationContext().hasActiveCatalystInstance()) {
+        WritableMap result = new WritableNativeMap();
+        result.putString("action", ACTION_DISMISSED);
+        mPromise.resolve(result);
+        mPromiseResolved = true;
+      }
+    }
+  }
+
+  @ReactMethod
+  public void open(@Nullable final ReadableMap options, Promise promise) {
+
+    Activity activity = getCurrentActivity();
+    if (activity == null) {
+      promise.reject(
+          ERROR_NO_ACTIVITY,
+          "Tried to open a TimePicker dialog while not attached to an Activity");
+      return;
+    }
+    // We want to support both android.app.Activity and the pre-Honeycomb FragmentActivity
+    // (for apps that use it for legacy reasons). This unfortunately leads to some code duplication.
+    if (activity instanceof android.support.v4.app.FragmentActivity) {
+      android.support.v4.app.FragmentManager fragmentManager =
+          ((android.support.v4.app.FragmentActivity) activity).getSupportFragmentManager();
+      android.support.v4.app.DialogFragment oldFragment =
+          (android.support.v4.app.DialogFragment)fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+      if (oldFragment != null) {
+        oldFragment.dismiss();
+      }
+      SupportTimePickerDialogFragment fragment = new SupportTimePickerDialogFragment();
+      if (options != null) {
+        Bundle args = createFragmentArguments(options);
+        fragment.setArguments(args);
+      }
+      TimePickerDialogListener listener = new TimePickerDialogListener(promise);
+      fragment.setOnDismissListener(listener);
+      fragment.setOnTimeSetListener(listener);
+      fragment.show(fragmentManager, FRAGMENT_TAG);
+    } else {
+      FragmentManager fragmentManager = activity.getFragmentManager();
+      DialogFragment oldFragment = (DialogFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
+      if (oldFragment != null) {
+        oldFragment.dismiss();
+      }
+      TimePickerDialogFragment fragment = new TimePickerDialogFragment();
+      if (options != null) {
+        final Bundle args = createFragmentArguments(options);
+        fragment.setArguments(args);
+      }
+      TimePickerDialogListener listener = new TimePickerDialogListener(promise);
+      fragment.setOnDismissListener(listener);
+      fragment.setOnTimeSetListener(listener);
+      fragment.show(fragmentManager, FRAGMENT_TAG);
+    }
+  }
+
+  private Bundle createFragmentArguments(ReadableMap options) {
+    final Bundle args = new Bundle();
+    if (options.hasKey(ARG_HOUR) && !options.isNull(ARG_HOUR)) {
+      args.putInt(ARG_HOUR, options.getInt(ARG_HOUR));
+    }
+    if (options.hasKey(ARG_MINUTE) && !options.isNull(ARG_MINUTE)) {
+      args.putInt(ARG_MINUTE, options.getInt(ARG_MINUTE));
+    }
+    if (options.hasKey(ARG_IS24HOUR) && !options.isNull(ARG_IS24HOUR)) {
+      args.putBoolean(ARG_IS24HOUR, options.getBoolean(ARG_IS24HOUR));
+    }
+    return args;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK
index 497ef5bd03c9..fdfec87eb25b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/toast/BUCK
@@ -6,8 +6,8 @@ android_library(
   deps = [
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK
new file mode 100644
index 000000000000..19ceed59d6f9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/BUCK
@@ -0,0 +1,21 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'vibration',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':vibration',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/VibrationModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/VibrationModule.java
new file mode 100644
index 000000000000..1a0023ac0524
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/vibration/VibrationModule.java
@@ -0,0 +1,37 @@
+/**
+ * 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.modules.vibration;
+
+import android.content.Context;
+import android.os.Vibrator;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+
+public class VibrationModule extends ReactContextBaseJavaModule {
+
+  public VibrationModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "Vibration";
+  }
+
+  @ReactMethod
+  public void vibrate(int duration) {
+    Vibrator v = (Vibrator) getReactApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
+    if (v != null) {
+      v.vibrate(duration);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK
index 92ab5d859e68..6289e05986f1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/BUCK
@@ -7,11 +7,13 @@ android_library(
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/modules/core:core'),
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
-    '//third-party/java/okhttp:okhttp-ws',
-  ],
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/okhttp:okhttp-ws'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('third-party/java/okio:okio'),
+],
   visibility = [
     'PUBLIC',
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java
index dfa41638cf42..61e1057bf583 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java
@@ -10,6 +10,7 @@
 package com.facebook.react.modules.websocket;
 
 import java.io.IOException;
+import javax.annotation.Nullable;
 
 import com.facebook.common.logging.FLog;
 import com.facebook.react.bridge.Arguments;
@@ -17,6 +18,10 @@
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.facebook.react.bridge.ReadableType;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.common.ReactConstants;
 import com.facebook.react.modules.core.DeviceEventManagerModule;
@@ -57,7 +62,8 @@ public String getName() {
   }
 
   @ReactMethod
-  public void connect(final String url, final int id) {
+  public void connect(final String url, @Nullable final ReadableArray protocols, @Nullable final ReadableMap options, final int id) {
+    // ignoring protocols, since OKHttp overrides them.
     OkHttpClient client = new OkHttpClient();
 
     client.setConnectTimeout(10, TimeUnit.SECONDS);
@@ -65,12 +71,21 @@ public void connect(final String url, final int id) {
     // Disable timeouts for read
     client.setReadTimeout(0, TimeUnit.MINUTES);
 
-    Request request = new Request.Builder()
+    Request.Builder builder = new Request.Builder()
         .tag(id)
-        .url(url)
-        .build();
+        .url(url);
+
+    if (options != null && options.hasKey("origin")) {
+      if (ReadableType.String.equals(options.getType("origin"))) {
+        builder.addHeader("Origin", options.getString("origin"));
+      } else {
+        FLog.w(
+          ReactConstants.TAG,
+          "Ignoring: requested origin, value not a string");
+      }
+    }
 
-    WebSocketCall.create(client, request).enqueue(new WebSocketListener() {
+    WebSocketCall.create(client, builder.build()).enqueue(new WebSocketListener() {
 
       @Override
       public void onOpen(WebSocket webSocket, Response response) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK b/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK
new file mode 100644
index 000000000000..e057630de425
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/processing/BUCK
@@ -0,0 +1,22 @@
+include_defs('//ReactAndroid/DEFS')
+
+java_library(
+  name = 'processing',
+  srcs = glob(['*.java']),
+  source = '7',
+  target = '7',
+  deps = [
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/javapoet:javapoet'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility=[
+    'PUBLIC'
+  ]
+)
+
+project_config(
+  src_target = ':processing',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java
new file mode 100644
index 000000000000..bc18bf862fe4
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/processing/ReactPropertyProcessor.java
@@ -0,0 +1,684 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.processing;
+
+import javax.annotation.Nullable;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.facebook.infer.annotation.SuppressFieldNotInitialized;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
+
+import static javax.lang.model.element.Modifier.*;
+import static javax.tools.Diagnostic.Kind.ERROR;
+import static javax.tools.Diagnostic.Kind.WARNING;
+
+/**
+ * This annotation processor crawls subclasses of ReactShadowNode and ViewManager and finds their
+ * exported properties with the @ReactProp or @ReactGroupProp annotation. It generates a class
+ * per shadow node/view manager that is named {@code <classname>$$PropSetter}. This class contains methods
+ * to retrieve the name and type of all methods and a way to set these properties without
+ * reflection.
+ */
+@SupportedAnnotationTypes("com.facebook.react.uimanager.annotations.ReactPropertyHolder")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class ReactPropertyProcessor extends AbstractProcessor {
+  private static final Map<TypeName, String> DEFAULT_TYPES;
+  private static final Set<TypeName> BOXED_PRIMITIVES;
+
+  private static final TypeName PROPS_TYPE =
+      ClassName.get("com.facebook.react.uimanager", "ReactStylesDiffMap");
+  private static final TypeName STRING_TYPE = TypeName.get(String.class);
+  private static final TypeName READABLE_MAP_TYPE = TypeName.get(ReadableMap.class);
+  private static final TypeName READABLE_ARRAY_TYPE = TypeName.get(ReadableArray.class);
+
+  private static final TypeName VIEW_MANAGER_TYPE =
+      ClassName.get("com.facebook.react.uimanager", "ViewManager");
+  private static final TypeName SHADOW_NODE_TYPE =
+      ClassName.get("com.facebook.react.uimanager", "ReactShadowNode");
+
+  private static final ClassName VIEW_MANAGER_SETTER_TYPE =
+      ClassName.get(
+          "com.facebook.react.uimanager",
+          "ViewManagerPropertyUpdater",
+          "ViewManagerSetter");
+  private static final ClassName SHADOW_NODE_SETTER_TYPE =
+      ClassName.get(
+          "com.facebook.react.uimanager",
+          "ViewManagerPropertyUpdater",
+          "ShadowNodeSetter");
+
+  private static final TypeName PROPERTY_MAP_TYPE =
+      ParameterizedTypeName.get(Map.class, String.class, String.class);
+  private static final TypeName CONCRETE_PROPERTY_MAP_TYPE =
+      ParameterizedTypeName.get(HashMap.class, String.class, String.class);
+
+  private final Map<ClassName, ClassInfo> mClasses;
+
+  @SuppressFieldNotInitialized
+  private Filer mFiler;
+  @SuppressFieldNotInitialized
+  private Messager mMessager;
+  @SuppressFieldNotInitialized
+  private Elements mElements;
+  @SuppressFieldNotInitialized
+  private Types mTypes;
+
+  static {
+    DEFAULT_TYPES = new HashMap<>();
+
+    // Primitives
+    DEFAULT_TYPES.put(TypeName.BOOLEAN, "boolean");
+    DEFAULT_TYPES.put(TypeName.DOUBLE, "number");
+    DEFAULT_TYPES.put(TypeName.FLOAT, "number");
+    DEFAULT_TYPES.put(TypeName.INT, "number");
+
+    // Boxed primitives
+    DEFAULT_TYPES.put(TypeName.BOOLEAN.box(), "boolean");
+    DEFAULT_TYPES.put(TypeName.INT.box(), "number");
+
+    // Class types
+    DEFAULT_TYPES.put(STRING_TYPE, "String");
+    DEFAULT_TYPES.put(READABLE_ARRAY_TYPE, "Array");
+    DEFAULT_TYPES.put(READABLE_MAP_TYPE, "Map");
+
+    BOXED_PRIMITIVES = new HashSet<>();
+    BOXED_PRIMITIVES.add(TypeName.BOOLEAN.box());
+    BOXED_PRIMITIVES.add(TypeName.FLOAT.box());
+    BOXED_PRIMITIVES.add(TypeName.INT.box());
+  }
+
+  public ReactPropertyProcessor() {
+    mClasses = new HashMap<>();
+  }
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+
+    mFiler = processingEnv.getFiler();
+    mMessager = processingEnv.getMessager();
+    mElements = processingEnv.getElementUtils();
+    mTypes = processingEnv.getTypeUtils();
+  }
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    // Clear properties from previous rounds
+    mClasses.clear();
+
+    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ReactPropertyHolder.class);
+    for (Element element : elements) {
+      try {
+        TypeElement classType = (TypeElement) element;
+        ClassName className = ClassName.get(classType);
+        mClasses.put(className, parseClass(className, classType));
+      } catch (Exception e) {
+        error(element, e.getMessage());
+      }
+    }
+
+    for (ClassInfo classInfo : mClasses.values()) {
+      try {
+        if (!shouldIgnoreClass(classInfo)) {
+          // Sort by name
+          Collections.sort(
+              classInfo.mProperties, new Comparator<PropertyInfo>() {
+                @Override
+                public int compare(PropertyInfo a, PropertyInfo b) {
+                  return a.mProperty.name().compareTo(b.mProperty.name());
+                }
+              });
+          generateCode(classInfo, classInfo.mProperties);
+        } else if (shouldWarnClass(classInfo)) {
+          warning(classInfo.mElement, "Class was skipped. Classes need to be non-private.");
+        }
+      } catch (IOException e) {
+        error(e.getMessage());
+      } catch (ReactPropertyException e) {
+        error(e.element, e.getMessage());
+      } catch (Exception e) {
+        error(classInfo.mElement, e.getMessage());
+      }
+    }
+
+    return true;
+  }
+
+  private ClassInfo parseClass(ClassName className, TypeElement typeElement) {
+    TypeName targetType = getTargetType(typeElement.asType());
+    TypeName viewType = targetType.equals(SHADOW_NODE_TYPE) ? null : targetType;
+
+    ClassInfo classInfo = new ClassInfo(className, typeElement, viewType);
+    findProperties(classInfo, typeElement);
+
+    return classInfo;
+  }
+
+  private void findProperties(ClassInfo classInfo, TypeElement typeElement) {
+    PropertyInfo.Builder propertyBuilder = new PropertyInfo.Builder(mTypes, mElements, classInfo);
+
+    // Recursively search class hierarchy
+    while (typeElement != null) {
+      for (Element element : typeElement.getEnclosedElements()) {
+        ReactProp prop = element.getAnnotation(ReactProp.class);
+        ReactPropGroup propGroup = element.getAnnotation(ReactPropGroup.class);
+
+        try {
+          if (prop != null || propGroup != null) {
+            checkElement(element);
+          }
+
+          if (prop != null) {
+            classInfo.addProperty(propertyBuilder.build(element, new RegularProperty(prop)));
+          } else if (propGroup != null) {
+            for (int i = 0, size = propGroup.names().length; i < size; i++) {
+              classInfo
+                  .addProperty(propertyBuilder.build(element, new GroupProperty(propGroup, i)));
+            }
+          }
+        } catch (ReactPropertyException e) {
+          error(e.element, e.getMessage());
+        }
+      }
+
+      typeElement = (TypeElement) mTypes.asElement(typeElement.getSuperclass());
+    }
+  }
+
+  private TypeName getTargetType(TypeMirror mirror) {
+    TypeName typeName = TypeName.get(mirror);
+    if (typeName instanceof ParameterizedTypeName) {
+      ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
+      if (parameterizedTypeName.rawType.equals(VIEW_MANAGER_TYPE)) {
+        return parameterizedTypeName.typeArguments.get(0);
+      }
+    } else if (typeName.equals(SHADOW_NODE_TYPE)) {
+      return SHADOW_NODE_TYPE;
+    } else if (typeName.equals(TypeName.OBJECT)) {
+      throw new IllegalArgumentException("Could not find target type");
+    }
+
+    List<? extends TypeMirror> types = mTypes.directSupertypes(mirror);
+    return getTargetType(types.get(0));
+  }
+
+  private void generateCode(ClassInfo classInfo, List<PropertyInfo> properties)
+      throws IOException, ReactPropertyException {
+    MethodSpec getMethods = MethodSpec.methodBuilder("getProperties")
+        .addModifiers(PUBLIC)
+        .addAnnotation(Override.class)
+        .returns(PROPERTY_MAP_TYPE)
+        .addCode(generateGetProperties(properties))
+        .build();
+
+    TypeName superType = getSuperType(classInfo);
+    ClassName className = classInfo.mClassName;
+
+    String holderClassName =
+        getClassName((TypeElement) classInfo.mElement, className.packageName()) + "$$PropsSetter";
+    TypeSpec holderClass = TypeSpec.classBuilder(holderClassName)
+        .addSuperinterface(superType)
+        .addModifiers(PUBLIC)
+        .addMethod(generateSetPropertySpec(classInfo, properties))
+        .addMethod(getMethods)
+        .build();
+
+    JavaFile javaFile = JavaFile.builder(className.packageName(), holderClass)
+        .addFileComment("Generated by " + getClass().getName())
+        .build();
+
+    javaFile.writeTo(mFiler);
+  }
+
+  private String getClassName(TypeElement type, String packageName) {
+    int packageLen = packageName.length() + 1;
+    return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
+  }
+
+  private static TypeName getSuperType(ClassInfo classInfo) {
+    switch (classInfo.getType()) {
+      case VIEW_MANAGER:
+        return ParameterizedTypeName.get(
+            VIEW_MANAGER_SETTER_TYPE,
+            classInfo.mClassName,
+            classInfo.mViewType);
+      case SHADOW_NODE:
+        return ParameterizedTypeName.get(SHADOW_NODE_SETTER_TYPE, classInfo.mClassName);
+      default:
+        throw new IllegalArgumentException();
+    }
+  }
+
+  private static MethodSpec generateSetPropertySpec(
+      ClassInfo classInfo,
+      List<PropertyInfo> properties) {
+    MethodSpec.Builder builder = MethodSpec.methodBuilder("setProperty")
+        .addModifiers(PUBLIC)
+        .addAnnotation(Override.class)
+        .returns(TypeName.VOID);
+
+    switch (classInfo.getType()) {
+      case VIEW_MANAGER:
+        builder
+            .addParameter(classInfo.mClassName, "manager")
+            .addParameter(classInfo.mViewType, "view");
+        break;
+      case SHADOW_NODE:
+        builder
+            .addParameter(classInfo.mClassName, "node");
+        break;
+    }
+
+    return builder
+        .addParameter(STRING_TYPE, "name")
+        .addParameter(PROPS_TYPE, "props")
+        .addCode(generateSetProperty(classInfo, properties))
+        .build();
+  }
+
+  private static CodeBlock generateSetProperty(ClassInfo info, List<PropertyInfo> properties) {
+    if (properties.isEmpty()) {
+      return CodeBlock.builder().build();
+    }
+
+    CodeBlock.Builder builder = CodeBlock.builder();
+
+    builder.add("switch (name) {\n").indent();
+    for (int i = 0, size = properties.size(); i < size; i++) {
+      PropertyInfo propertyInfo = properties.get(i);
+      builder
+          .add("case \"$L\":\n", propertyInfo.mProperty.name())
+          .indent();
+
+      switch (info.getType()) {
+        case VIEW_MANAGER:
+          builder.add("manager.$L(view, ", propertyInfo.methodName);
+          break;
+        case SHADOW_NODE:
+          builder.add("node.$L(", propertyInfo.methodName);
+          break;
+      }
+      if (propertyInfo.mProperty instanceof GroupProperty) {
+        builder.add("$L, ", ((GroupProperty) propertyInfo.mProperty).mGroupIndex);
+      }
+      if (BOXED_PRIMITIVES.contains(propertyInfo.propertyType)) {
+        builder.add("props.isNull(name) ? null : ");
+      }
+      getPropertyExtractor(propertyInfo, builder);
+      builder.addStatement(")");
+
+      builder
+          .addStatement("break")
+          .unindent();
+    }
+    builder.unindent().add("}\n");
+
+    return builder.build();
+  }
+
+  private static CodeBlock.Builder getPropertyExtractor(
+      PropertyInfo info,
+      CodeBlock.Builder builder) {
+    TypeName propertyType = info.propertyType;
+    if (propertyType.equals(STRING_TYPE)) {
+      return builder.add("props.getString(name)");
+    } else if (propertyType.equals(READABLE_ARRAY_TYPE)) {
+      return builder.add("props.getArray(name)");
+    } else if (propertyType.equals(READABLE_MAP_TYPE)) {
+      return builder.add("props.getMap(name)");
+    }
+
+    if (BOXED_PRIMITIVES.contains(propertyType)) {
+      propertyType = propertyType.unbox();
+    }
+
+    if (propertyType.equals(TypeName.BOOLEAN)) {
+      return builder.add("props.getBoolean(name, $L)", info.mProperty.defaultBoolean());
+    } if (propertyType.equals(TypeName.DOUBLE)) {
+      double defaultDouble = info.mProperty.defaultDouble();
+      if (Double.isNaN(defaultDouble)) {
+        return builder.add("props.getDouble(name, $T.NaN)", Double.class);
+      } else {
+        return builder.add("props.getDouble(name, $Lf)", defaultDouble);
+      }
+    }
+    if (propertyType.equals(TypeName.FLOAT)) {
+      float defaultFloat = info.mProperty.defaultFloat();
+      if (Float.isNaN(defaultFloat)) {
+        return builder.add("props.getFloat(name, $T.NaN)", Float.class);
+      } else {
+        return builder.add("props.getFloat(name, $Lf)", defaultFloat);
+      }
+    }
+    if (propertyType.equals(TypeName.INT)) {
+      return builder.add("props.getInt(name, $L)", info.mProperty.defaultInt());
+    }
+
+    throw new IllegalArgumentException();
+  }
+
+  private static CodeBlock generateGetProperties(List<PropertyInfo> properties)
+      throws ReactPropertyException {
+    if (properties.isEmpty()) {
+      return CodeBlock.builder()
+          .addStatement("return $T.emptyMap()", Collections.class)
+          .build();
+    }
+
+    CodeBlock.Builder builder = CodeBlock.builder()
+        .addStatement(
+            "$T props = new $T($L)",
+            PROPERTY_MAP_TYPE,
+            CONCRETE_PROPERTY_MAP_TYPE,
+            properties.size());
+
+    for (PropertyInfo propertyInfo : properties) {
+      try {
+        String typeName = getPropertypTypeName(propertyInfo.mProperty, propertyInfo.propertyType);
+        builder.addStatement("props.put($S, $S)", propertyInfo.mProperty.name(), typeName);
+      } catch (IllegalArgumentException e) {
+        throw new ReactPropertyException(e.getMessage(), propertyInfo);
+      }
+    }
+
+    return builder
+        .addStatement("return props")
+        .build();
+  }
+
+  private static String getPropertypTypeName(Property property, TypeName propertyType) {
+    String defaultType = DEFAULT_TYPES.get(propertyType);
+    String useDefaultType = property instanceof RegularProperty ?
+        ReactProp.USE_DEFAULT_TYPE : ReactPropGroup.USE_DEFAULT_TYPE;
+    return useDefaultType.equals(property.customType()) ? defaultType : property.customType();
+  }
+
+  private static void checkElement(Element element) throws ReactPropertyException {
+    if (element.getKind() == ElementKind.METHOD
+        && element.getModifiers().contains(PUBLIC)) {
+      return;
+    }
+
+    throw new ReactPropertyException(
+        "@ReactProp and @ReachPropGroup annotation must be on a public method",
+        element);
+  }
+
+  private static boolean shouldIgnoreClass(ClassInfo classInfo) {
+    return classInfo.mElement.getModifiers().contains(PRIVATE)
+        || classInfo.mElement.getModifiers().contains(ABSTRACT)
+        || classInfo.mViewType instanceof TypeVariableName;
+  }
+
+  private static boolean shouldWarnClass(ClassInfo classInfo) {
+    return classInfo.mElement.getModifiers().contains(PRIVATE);
+  }
+
+  private void error(Element element, String message) {
+    mMessager.printMessage(ERROR, message, element);
+  }
+
+  private void error(String message) {
+    mMessager.printMessage(ERROR, message);
+  }
+
+  private void warning(Element element, String message) {
+    mMessager.printMessage(WARNING, message, element);
+  }
+
+  private interface Property {
+    String name();
+    String customType();
+    double defaultDouble();
+    float defaultFloat();
+    int defaultInt();
+    boolean defaultBoolean();
+  }
+
+  private static class RegularProperty implements Property {
+    private final ReactProp mProp;
+
+    public RegularProperty(ReactProp prop) {
+      mProp = prop;
+    }
+
+    @Override
+    public String name() {
+      return mProp.name();
+    }
+
+    @Override
+    public String customType() {
+      return mProp.customType();
+    }
+
+    @Override
+    public double defaultDouble() {
+      return mProp.defaultDouble();
+    }
+
+    @Override
+    public float defaultFloat() {
+      return mProp.defaultFloat();
+    }
+
+    @Override
+    public int defaultInt() {
+      return mProp.defaultInt();
+    }
+
+    @Override
+    public boolean defaultBoolean() {
+      return mProp.defaultBoolean();
+    }
+  }
+
+  private static class GroupProperty implements Property {
+    private final ReactPropGroup mProp;
+    private final int mGroupIndex;
+
+    public GroupProperty(ReactPropGroup prop, int groupIndex) {
+      mProp = prop;
+      mGroupIndex = groupIndex;
+    }
+
+    @Override
+    public String name() {
+      return mProp.names()[mGroupIndex];
+    }
+
+    @Override
+    public String customType() {
+      return mProp.customType();
+    }
+
+    @Override
+    public double defaultDouble() {
+      return mProp.defaultDouble();
+    }
+
+    @Override
+    public float defaultFloat() {
+      return mProp.defaultFloat();
+    }
+
+    @Override
+    public int defaultInt() {
+      return mProp.defaultInt();
+    }
+
+    @Override
+    public boolean defaultBoolean() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private enum SettableType {
+    VIEW_MANAGER,
+    SHADOW_NODE
+  }
+
+  private static class ClassInfo {
+    public final ClassName mClassName;
+    public final Element mElement;
+    public final @Nullable TypeName mViewType;
+    public final List<PropertyInfo> mProperties;
+
+    public ClassInfo(ClassName className, TypeElement element, @Nullable TypeName viewType) {
+      mClassName = className;
+      mElement = element;
+      mViewType = viewType;
+      mProperties = new ArrayList<>();
+    }
+
+    public SettableType getType() {
+      return mViewType == null ? SettableType.SHADOW_NODE : SettableType.VIEW_MANAGER;
+    }
+
+    public void addProperty(PropertyInfo propertyInfo) throws ReactPropertyException {
+      String name = propertyInfo.mProperty.name();
+      if (checkPropertyExists(name)) {
+        throw new ReactPropertyException(
+            "Module " + mClassName + " has already registered a property named \"" +
+                name + '"', propertyInfo);
+      }
+
+      mProperties.add(propertyInfo);
+    }
+
+    private boolean checkPropertyExists(String name) {
+      for (PropertyInfo propertyInfo : mProperties) {
+        if (propertyInfo.mProperty.name().equals(name)) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+  }
+
+  private static class PropertyInfo {
+    public final String methodName;
+    public final TypeName propertyType;
+    public final Element element;
+    public final Property mProperty;
+
+    private PropertyInfo(
+        String methodName,
+        TypeName propertyType,
+        Element element,
+        Property property) {
+      this.methodName = methodName;
+      this.propertyType = propertyType;
+      this.element = element;
+      mProperty = property;
+    }
+
+    public static class Builder {
+      private final Types mTypes;
+      private final Elements mElements;
+      private final ClassInfo mClassInfo;
+
+      public Builder(Types types, Elements elements, ClassInfo classInfo) {
+        mTypes = types;
+        mElements = elements;
+        mClassInfo = classInfo;
+      }
+
+      public PropertyInfo build(Element element, Property property)
+          throws ReactPropertyException {
+        String methodName = element.getSimpleName().toString();
+
+        ExecutableElement method = (ExecutableElement) element;
+        List<? extends VariableElement> parameters = method.getParameters();
+
+        if (parameters.size() != getArgCount(mClassInfo.getType(), property)) {
+          throw new ReactPropertyException("Wrong number of args", element);
+        }
+
+        int index = 0;
+        if (mClassInfo.getType() == SettableType.VIEW_MANAGER) {
+          TypeMirror mirror = parameters.get(index++).asType();
+          if (!mTypes.isSubtype(mirror, mElements.getTypeElement("android.view.View").asType())) {
+            throw new ReactPropertyException("First argument must be a subclass of View", element);
+          }
+        }
+
+        if (property instanceof GroupProperty) {
+          TypeName indexType = TypeName.get(parameters.get(index++).asType());
+          if (!indexType.equals(TypeName.INT)) {
+            throw new ReactPropertyException(
+                "Argument " + index + " must be an int for @ReactPropGroup",
+                element);
+          }
+        }
+
+        TypeName propertyType = TypeName.get(parameters.get(index++).asType());
+        if (!DEFAULT_TYPES.containsKey(propertyType)) {
+          throw new ReactPropertyException(
+              "Argument " + index + " must be of a supported type",
+              element);
+        }
+
+        return new PropertyInfo(methodName, propertyType, element, property);
+      }
+
+      private static int getArgCount(SettableType type, Property property) {
+        int baseCount = type == SettableType.SHADOW_NODE ? 1 : 2;
+        return property instanceof GroupProperty ? baseCount + 1 : baseCount;
+      }
+    }
+  }
+
+  private static class ReactPropertyException extends Exception {
+    public final Element element;
+
+    public ReactPropertyException(String message, PropertyInfo propertyInfo) {
+      super(message);
+      this.element = propertyInfo.element;
+    }
+
+    public ReactPropertyException(String message, Element element) {
+      super(message);
+      this.element = element;
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
index 7043f36df502..198de5a74dc7 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
@@ -1,4 +1,3 @@
-include_defs('//instrumentation_tests/DEFS')
 include_defs('//ReactAndroid/DEFS')
 
 android_library(
@@ -10,26 +9,43 @@ android_library(
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/devsupport:devsupport'),
+    react_native_target('java/com/facebook/react/views/art:art'),
     react_native_target('java/com/facebook/react/views/drawer:drawer'),
     react_native_target('java/com/facebook/react/views/image:image'),
+    react_native_target('java/com/facebook/react/views/picker:picker'),
     react_native_target('java/com/facebook/react/views/progressbar:progressbar'),
+    react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
     react_native_target('java/com/facebook/react/views/scroll:scroll'),
+    react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
     react_native_target('java/com/facebook/react/views/switchview:switchview'),
     react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_target('java/com/facebook/react/views/text/frescosupport:frescosupport'),
     react_native_target('java/com/facebook/react/views/textinput:textinput'),
     react_native_target('java/com/facebook/react/views/toolbar:toolbar'),
     react_native_target('java/com/facebook/react/views/view:view'),
     react_native_target('java/com/facebook/react/views/viewpager:viewpager'),
+    react_native_target('java/com/facebook/react/views/webview:webview'),
+    react_native_target('java/com/facebook/react/modules/appstate:appstate'),
+    react_native_target('java/com/facebook/react/modules/vibration:vibration'),
+    react_native_target('java/com/facebook/react/modules/camera:camera'),
+    react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
     react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
     react_native_target('java/com/facebook/react/modules/debug:debug'),
+    react_native_target('java/com/facebook/react/modules/dialog:dialog'),
     react_native_target('java/com/facebook/react/modules/fresco:fresco'),
+    react_native_target('java/com/facebook/react/modules/intent:intent'),
+    react_native_target('java/com/facebook/react/modules/location:location'),
+    react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
     react_native_target('java/com/facebook/react/modules/network:network'),
+    react_native_target('java/com/facebook/react/modules/statusbar:statusbar'),
     react_native_target('java/com/facebook/react/modules/storage:storage'),
+    react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
     react_native_target('java/com/facebook/react/modules/toast:toast'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
     react_native_target('java/com/facebook/react/modules/websocket:websocket'),
     react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
-    react_native_dep('third-party/java/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
index 115c33ea63f6..f75c8d92bea2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
@@ -17,28 +17,46 @@
 import com.facebook.react.bridge.JavaScriptModule;
 import com.facebook.react.bridge.NativeModule;
 import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.modules.appstate.AppStateModule;
+import com.facebook.react.modules.camera.CameraRollManager;
+import com.facebook.react.modules.camera.ImageEditingManager;
+import com.facebook.react.modules.camera.ImageStoreManager;
+import com.facebook.react.modules.clipboard.ClipboardModule;
+import com.facebook.react.modules.datepicker.DatePickerDialogModule;
+import com.facebook.react.modules.dialog.DialogModule;
 import com.facebook.react.modules.fresco.FrescoModule;
 import com.facebook.react.modules.intent.IntentModule;
 import com.facebook.react.modules.location.LocationModule;
+import com.facebook.react.modules.netinfo.NetInfoModule;
 import com.facebook.react.modules.network.NetworkingModule;
+import com.facebook.react.modules.statusbar.StatusBarModule;
 import com.facebook.react.modules.storage.AsyncStorageModule;
+import com.facebook.react.modules.timepicker.TimePickerDialogModule;
 import com.facebook.react.modules.toast.ToastModule;
+import com.facebook.react.modules.vibration.VibrationModule;
 import com.facebook.react.modules.websocket.WebSocketModule;
 import com.facebook.react.uimanager.ViewManager;
+import com.facebook.react.views.art.ARTRenderableViewManager;
+import com.facebook.react.views.art.ARTSurfaceViewManager;
 import com.facebook.react.views.drawer.ReactDrawerLayoutManager;
 import com.facebook.react.views.image.ReactImageManager;
+import com.facebook.react.views.picker.ReactDialogPickerManager;
+import com.facebook.react.views.picker.ReactDropdownPickerManager;
 import com.facebook.react.views.progressbar.ReactProgressBarViewManager;
+import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager;
 import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager;
 import com.facebook.react.views.scroll.ReactScrollViewManager;
+import com.facebook.react.views.swiperefresh.SwipeRefreshLayoutManager;
 import com.facebook.react.views.switchview.ReactSwitchManager;
 import com.facebook.react.views.text.ReactRawTextManager;
 import com.facebook.react.views.text.ReactTextViewManager;
-import com.facebook.react.views.text.ReactTextInlineImageViewManager;
 import com.facebook.react.views.text.ReactVirtualTextViewManager;
+import com.facebook.react.views.textfrescosupport.FrescoBasedReactTextInlineImageViewManager;
 import com.facebook.react.views.textinput.ReactTextInputManager;
 import com.facebook.react.views.toolbar.ReactToolbarManager;
 import com.facebook.react.views.view.ReactViewManager;
 import com.facebook.react.views.viewpager.ReactViewPagerManager;
+import com.facebook.react.views.webview.ReactWebViewManager;
 
 /**
  * Package defining basic modules and view managers.
@@ -48,13 +66,25 @@ public class MainReactPackage implements ReactPackage {
   @Override
   public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
     return Arrays.<NativeModule>asList(
+      new AppStateModule(reactContext),
       new AsyncStorageModule(reactContext),
+      new CameraRollManager(reactContext),
+      new ClipboardModule(reactContext),
+      new DatePickerDialogModule(reactContext),
+      new DialogModule(reactContext),
       new FrescoModule(reactContext),
+      new ImageEditingManager(reactContext),
+      new ImageStoreManager(reactContext),
       new IntentModule(reactContext),
       new LocationModule(reactContext),
       new NetworkingModule(reactContext),
-      new WebSocketModule(reactContext),
-      new ToastModule(reactContext));
+      new NetInfoModule(reactContext),
+      new StatusBarModule(reactContext),
+      new TimePickerDialogModule(reactContext),
+      new ToastModule(reactContext),
+      new VibrationModule(reactContext),
+      new WebSocketModule(reactContext)
+    );
   }
 
   @Override
@@ -65,19 +95,28 @@ public List<Class<? extends JavaScriptModule>> createJSModules() {
   @Override
   public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
     return Arrays.<ViewManager>asList(
+      ARTRenderableViewManager.createARTGroupViewManager(),
+      ARTRenderableViewManager.createARTShapeViewManager(),
+      ARTRenderableViewManager.createARTTextViewManager(),
+      new ARTSurfaceViewManager(),
+      new ReactDialogPickerManager(),
       new ReactDrawerLayoutManager(),
+      new ReactDropdownPickerManager(),
       new ReactHorizontalScrollViewManager(),
       new ReactImageManager(),
       new ReactProgressBarViewManager(),
       new ReactRawTextManager(),
       new ReactScrollViewManager(),
       new ReactSwitchManager(),
+      new FrescoBasedReactTextInlineImageViewManager(),
       new ReactTextInputManager(),
       new ReactTextViewManager(),
       new ReactToolbarManager(),
       new ReactViewManager(),
       new ReactViewPagerManager(),
-      new ReactTextInlineImageViewManager(),
-      new ReactVirtualTextViewManager());
+      new ReactVirtualTextViewManager(),
+      new ReactWebViewManager(),
+      new RecyclerViewBackedScrollViewManager(),
+      new SwipeRefreshLayoutManager());
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java b/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java
index 2e8ba61f2292..7140e2c303fe 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/touch/JSResponderHandler.java
@@ -18,7 +18,7 @@
 /**
  * This class coordinates JSResponder commands for {@link UIManagerModule}. It should be set as
  * OnInterceptTouchEventListener for all newly created native views that implements
- * {@link CatalystInterceptingViewGroup} and thanks to the information whether JSResponder is set
+ * {@link ReactInterceptingViewGroup} and thanks to the information whether JSResponder is set
  * and to which view it will correctly coordinate the return values of
  * {@link OnInterceptTouchEventListener} such that touch events will be dispatched to the view
  * selected by JS gesture recognizer.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/ReactHitSlopView.java b/ReactAndroid/src/main/java/com/facebook/react/touch/ReactHitSlopView.java
new file mode 100644
index 000000000000..adda78ab020c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/touch/ReactHitSlopView.java
@@ -0,0 +1,28 @@
+/**
+ * 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.touch;
+
+import android.graphics.Rect;
+
+import javax.annotation.Nullable;
+
+/**
+ * This interface should be implemented by all {@link View} subclasses that want to use the
+ * hitSlop prop to extend their touch areas.
+ */
+public interface ReactHitSlopView {
+
+  /**
+   * Called when determining the touch area of a view.
+   * @return A {@link Rect} representing how far to extend the touch area in each direction.
+   */
+  public @Nullable Rect getHitSlopRect();
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/touch/CatalystInterceptingViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/touch/ReactInterceptingViewGroup.java
similarity index 96%
rename from ReactAndroid/src/main/java/com/facebook/react/touch/CatalystInterceptingViewGroup.java
rename to ReactAndroid/src/main/java/com/facebook/react/touch/ReactInterceptingViewGroup.java
index 6bc10f034a3b..d7b919b7f18e 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/touch/CatalystInterceptingViewGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/touch/ReactInterceptingViewGroup.java
@@ -16,7 +16,7 @@
  * which then is used to control touch event flow in cases in which they requested to be intercepted
  * by some parent view based on a JS gesture detector.
  */
-public interface CatalystInterceptingViewGroup {
+public interface ReactInterceptingViewGroup {
 
   /**
    * A {@link ViewGroup} instance that implement this interface is responsible for storing the
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java
index f0cd49855e84..d9a387b7f0db 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/AppRegistry.java
@@ -18,4 +18,6 @@
 public interface AppRegistry extends JavaScriptModule {
 
   void runApplication(String appKey, WritableMap appParameters);
+  void unmountApplicationComponentAtRootTag(int rootNodeTag);
+
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
index 128e87f67c69..b39e66c9255c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BUCK
@@ -2,18 +2,24 @@ include_defs('//ReactAndroid/DEFS')
 
 android_library(
   name = 'uimanager',
-  srcs = glob(['**/*.java']),
+  srcs = glob([
+    '*.java',
+    'debug/*.java',
+    'events/*.java',
+    'layoutanimation/*.java'
+  ]),
   deps = [
-    '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
     react_native_target('java/com/facebook/react/animation:animation'),
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/touch:touch'),
     react_native_dep('java/com/facebook/systrace:systrace'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
-    react_native_dep('third-party/java/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
index c5dcaa700736..9a697ce3a541 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java
@@ -7,6 +7,7 @@
 import android.view.View;
 
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.uimanager.annotations.ReactProp;
 
 /**
  * Base class that should be suitable for the majority of subclasses of {@link ViewManager}.
@@ -18,11 +19,14 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
   private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR;
   private static final String PROP_DECOMPOSED_MATRIX = "decomposedMatrix";
   private static final String PROP_DECOMPOSED_MATRIX_ROTATE = "rotate";
+  private static final String PROP_DECOMPOSED_MATRIX_ROTATE_X = "rotateX";
+  private static final String PROP_DECOMPOSED_MATRIX_ROTATE_Y = "rotateY";
   private static final String PROP_DECOMPOSED_MATRIX_SCALE_X = "scaleX";
   private static final String PROP_DECOMPOSED_MATRIX_SCALE_Y = "scaleY";
   private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_X = "translateX";
   private static final String PROP_DECOMPOSED_MATRIX_TRANSLATE_Y = "translateY";
   private static final String PROP_OPACITY = "opacity";
+  private static final String PROP_ELEVATION = "elevation";
   private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
   private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel";
   private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType";
@@ -60,6 +64,14 @@ public void setDecomposedMatrix(T view, ReadableMap decomposedMatrix) {
   public void setOpacity(T view, float opacity) {
     view.setAlpha(opacity);
   }
+  
+  @ReactProp(name = PROP_ELEVATION)
+  public void setElevation(T view, float elevation) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      view.setElevation(PixelUtil.toPixelFromDIP(elevation));
+    }
+    // Do nothing on API < 21
+  }
 
   @ReactProp(name = PROP_RENDER_TO_HARDWARE_TEXTURE)
   public void setRenderToHardwareTexture(T view, boolean useHWTexture) {
@@ -144,6 +156,10 @@ private static void setTransformMatrix(View view, ReadableMap matrix) {
             (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_TRANSLATE_Y)));
     view.setRotation(
         (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE));
+    view.setRotationX(
+        (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE_X));
+    view.setRotationY(
+        (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_ROTATE_Y));
     view.setScaleX(
         (float) matrix.getDouble(PROP_DECOMPOSED_MATRIX_SCALE_X));
     view.setScaleY(
@@ -154,6 +170,8 @@ private static void resetTransformMatrix(View view) {
     view.setTranslationX(PixelUtil.toPixelFromDIP(0));
     view.setTranslationY(PixelUtil.toPixelFromDIP(0));
     view.setRotation(0);
+    view.setRotationX(0);
+    view.setRotationY(0);
     view.setScaleX(1);
     view.setScaleY(1);
   }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java
index 18135146eeec..c465073f81a1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java
@@ -14,16 +14,39 @@
 /**
  * Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
  * classes that need it.
+ * Note: windowDisplayMetrics are deprecated in favor of ScreenDisplayMetrics: window metrics
+ * are supposed to return the drawable area but there's no guarantee that they correspond to the
+ * actual size of the {@link ReactRootView}. Moreover, they are not consistent with what iOS
+ * returns. Screen metrics returns the metrics of the entire screen, is consistent with iOS and
+ * should be used instead.
  */
 public class DisplayMetricsHolder {
 
-  private static DisplayMetrics sCurrentDisplayMetrics;
+  private static DisplayMetrics sWindowDisplayMetrics;
+  private static DisplayMetrics sScreenDisplayMetrics;
 
-  public static void setDisplayMetrics(DisplayMetrics displayMetrics) {
-    sCurrentDisplayMetrics = displayMetrics;
+  /**
+   * @deprecated Use {@link #setScreenDisplayMetrics(DisplayMetrics)} instead. See comment above as
+   *    to why this is not correct to use.
+   */
+  public static void setWindowDisplayMetrics(DisplayMetrics displayMetrics) {
+    sWindowDisplayMetrics = displayMetrics;
   }
 
-  public static DisplayMetrics getDisplayMetrics() {
-    return sCurrentDisplayMetrics;
+  /**
+   * @deprecated Use {@link #getScreenDisplayMetrics()} instead. See comment above as to why this
+   *    is not correct to use.
+   */
+  @Deprecated
+  public static DisplayMetrics getWindowDisplayMetrics() {
+    return sWindowDisplayMetrics;
+  }
+
+  public static void setScreenDisplayMetrics(DisplayMetrics screenDisplayMetrics) {
+    sScreenDisplayMetrics = screenDisplayMetrics;
+  }
+
+  public static DisplayMetrics getScreenDisplayMetrics() {
+    return sScreenDisplayMetrics;
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java
index 31670c7165e3..97d83ea143de 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java
@@ -12,6 +12,8 @@
 import com.facebook.csslayout.CSSJustify;
 import com.facebook.csslayout.CSSPositionType;
 import com.facebook.csslayout.CSSWrap;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
 
 /**
  * Supply setters for base view layout properties such as width, height, flex properties,
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
index 68a1e567f038..b5939823acf2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
@@ -29,9 +29,13 @@
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
 import com.facebook.react.bridge.SoftAssertions;
 import com.facebook.react.bridge.UiThreadUtil;
 import com.facebook.react.touch.JSResponderHandler;
+import com.facebook.react.uimanager.layoutanimation.LayoutAnimationController;
+import com.facebook.systrace.Systrace;
+import com.facebook.systrace.SystraceMessage;
 
 /**
  * Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between
@@ -56,57 +60,65 @@
  * TODO(5483031): Only dispatch updates when shadow views have changed
  */
 @NotThreadSafe
-/* package */ final class NativeViewHierarchyManager {
+public class NativeViewHierarchyManager {
 
   private final AnimationRegistry mAnimationRegistry;
   private final SparseArray<View> mTagsToViews;
   private final SparseArray<ViewManager> mTagsToViewManagers;
   private final SparseBooleanArray mRootTags;
-  private final SparseArray<ThemedReactContext> mRootViewsContext;
   private final ViewManagerRegistry mViewManagers;
   private final JSResponderHandler mJSResponderHandler = new JSResponderHandler();
   private final RootViewManager mRootViewManager = new RootViewManager();
+  private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController();
 
-  public NativeViewHierarchyManager(
-      AnimationRegistry animationRegistry,
-      ViewManagerRegistry viewManagers) {
-    mAnimationRegistry = animationRegistry;
+  private boolean mLayoutAnimationEnabled;
+
+  public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) {
+    mAnimationRegistry = new AnimationRegistry();
     mViewManagers = viewManagers;
     mTagsToViews = new SparseArray<>();
     mTagsToViewManagers = new SparseArray<>();
     mRootTags = new SparseBooleanArray();
-    mRootViewsContext = new SparseArray<>();
   }
 
-  public void updateProperties(int tag, CatalystStylesDiffMap props) {
-    UiThreadUtil.assertOnUiThread();
+  protected final View resolveView(int tag) {
+    View view = mTagsToViews.get(tag);
+    if (view == null) {
+      throw new IllegalViewOperationException("Trying to resolve view with tag " + tag
+          + " which doesn't exist");
+    }
+    return view;
+  }
 
+  protected final ViewManager resolveViewManager(int tag) {
     ViewManager viewManager = mTagsToViewManagers.get(tag);
     if (viewManager == null) {
       throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found");
     }
+    return viewManager;
+  }
 
-    View viewToUpdate = mTagsToViews.get(tag);
-    if (viewToUpdate == null) {
-      throw new IllegalViewOperationException("Trying to update view with tag " + tag
-          + " which doesn't exist");
-    }
+  public AnimationRegistry getAnimationRegistry() {
+    return mAnimationRegistry;
+  }
+
+  public void setLayoutAnimationEnabled(boolean enabled) {
+    mLayoutAnimationEnabled = enabled;
+  }
+
+  public void updateProperties(int tag, ReactStylesDiffMap props) {
+    UiThreadUtil.assertOnUiThread();
+
+    ViewManager viewManager = resolveViewManager(tag);
+    View viewToUpdate = resolveView(tag);
     viewManager.updateProperties(viewToUpdate, props);
   }
 
   public void updateViewExtraData(int tag, Object extraData) {
     UiThreadUtil.assertOnUiThread();
 
-    ViewManager viewManager = mTagsToViewManagers.get(tag);
-    if (viewManager == null) {
-      throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found");
-    }
-
-    View viewToUpdate = mTagsToViews.get(tag);
-    if (viewToUpdate == null) {
-      throw new IllegalViewOperationException("Trying to update view with tag " + tag + " which " +
-          "doesn't exist");
-    }
+    ViewManager viewManager = resolveViewManager(tag);
+    View viewToUpdate = resolveView(tag);
     viewManager.updateExtraData(viewToUpdate, extraData);
   }
 
@@ -118,66 +130,90 @@ public void updateLayout(
       int width,
       int height) {
     UiThreadUtil.assertOnUiThread();
-
-    View viewToUpdate = mTagsToViews.get(tag);
-    if (viewToUpdate == null) {
-      throw new IllegalViewOperationException("Trying to update view with tag " + tag + " which " +
-          "doesn't exist");
-    }
-
-    // Even though we have exact dimensions, we still call measure because some platform views (e.g.
-    // Switch) assume that method will always be called before onLayout and onDraw. They use it to
-    // calculate and cache information used in the draw pass. For most views, onMeasure can be
-    // stubbed out to only call setMeasuredDimensions. For ViewGroups, onLayout should be stubbed
-    // out to not recursively call layout on its children: React Native already handles doing that.
-    //
-    // Also, note measure and layout need to be called *after* all View properties have been updated
-    // because of caching and calculation that may occur in onMeasure and onLayout. Layout
-    // operations should also follow the native view hierarchy and go top to bottom for consistency
-    // with standard layout passes (some views may depend on this).
-
-    viewToUpdate.measure(
-        View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
-        View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
-
-    // Check if the parent of the view has to layout the view, or the child has to lay itself out.
-    if (!mRootTags.get(parentTag)) {
-      ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
-      ViewGroupManager parentViewGroupManager;
-      if (parentViewManager instanceof ViewGroupManager) {
-        parentViewGroupManager = (ViewGroupManager) parentViewManager;
+    SystraceMessage.beginSection(
+        Systrace.TRACE_TAG_REACT_VIEW,
+        "NativeViewHierarchyManager_updateLayout")
+        .arg("parentTag", parentTag)
+        .arg("tag", tag)
+        .flush();
+    try {
+      View viewToUpdate = resolveView(tag);
+
+      // Even though we have exact dimensions, we still call measure because some platform views (e.g.
+      // Switch) assume that method will always be called before onLayout and onDraw. They use it to
+      // calculate and cache information used in the draw pass. For most views, onMeasure can be
+      // stubbed out to only call setMeasuredDimensions. For ViewGroups, onLayout should be stubbed
+      // out to not recursively call layout on its children: React Native already handles doing that.
+      //
+      // Also, note measure and layout need to be called *after* all View properties have been updated
+      // because of caching and calculation that may occur in onMeasure and onLayout. Layout
+      // operations should also follow the native view hierarchy and go top to bottom for consistency
+      // with standard layout passes (some views may depend on this).
+
+      viewToUpdate.measure(
+          View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+          View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+
+      // Check if the parent of the view has to layout the view, or the child has to lay itself out.
+      if (!mRootTags.get(parentTag)) {
+        ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
+        ViewGroupManager parentViewGroupManager;
+        if (parentViewManager instanceof ViewGroupManager) {
+          parentViewGroupManager = (ViewGroupManager) parentViewManager;
+        } else {
+          throw new IllegalViewOperationException(
+              "Trying to use view with tag " + tag +
+                  " as a parent, but its Manager doesn't extends ViewGroupManager");
+        }
+        if (parentViewGroupManager != null
+            && !parentViewGroupManager.needsCustomLayoutForChildren()) {
+          updateLayout(viewToUpdate, x, y, width, height);
+        }
       } else {
-        throw new IllegalViewOperationException("Trying to use view with tag " + tag +
-            " as a parent, but its Manager doesn't extends ViewGroupManager");
-      }
-      if (parentViewGroupManager != null
-          && !parentViewGroupManager.needsCustomLayoutForChildren()) {
-        viewToUpdate.layout(x, y, x + width, y + height);
+        updateLayout(viewToUpdate, x, y, width, height);
       }
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
+    }
+  }
+
+  private void updateLayout(View viewToUpdate, int x, int y, int width, int height) {
+    if (mLayoutAnimationEnabled &&
+        mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) {
+      mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height);
     } else {
       viewToUpdate.layout(x, y, x + width, y + height);
     }
   }
 
   public void createView(
-      int rootViewTagForContext,
+      ThemedReactContext themedContext,
       int tag,
       String className,
-      @Nullable CatalystStylesDiffMap initialProps) {
+      @Nullable ReactStylesDiffMap initialProps) {
     UiThreadUtil.assertOnUiThread();
-    ViewManager viewManager = mViewManagers.get(className);
-
-    View view =
-        viewManager.createView(mRootViewsContext.get(rootViewTagForContext), mJSResponderHandler);
-    mTagsToViews.put(tag, view);
-    mTagsToViewManagers.put(tag, viewManager);
-
-    // Use android View id field to store React tag. This is possible since we don't inflate
-    // React views from layout xmls. Thus it is easier to just reuse that field instead of
-    // creating another (potentially much more expensive) mapping from view to React tag
-    view.setId(tag);
-    if (initialProps != null) {
-      viewManager.updateProperties(view, initialProps);
+    SystraceMessage.beginSection(
+        Systrace.TRACE_TAG_REACT_VIEW,
+        "NativeViewHierarchyManager_createView")
+        .arg("tag", tag)
+        .arg("className", className)
+        .flush();
+    try {
+      ViewManager viewManager = mViewManagers.get(className);
+
+      View view = viewManager.createView(themedContext, mJSResponderHandler);
+      mTagsToViews.put(tag, view);
+      mTagsToViewManagers.put(tag, viewManager);
+
+      // Use android View id field to store React tag. This is possible since we don't inflate
+      // React views from layout xmls. Thus it is easier to just reuse that field instead of
+      // creating another (potentially much more expensive) mapping from view to React tag
+      view.setId(tag);
+      if (initialProps != null) {
+        viewManager.updateProperties(view, initialProps);
+      }
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_VIEW);
     }
   }
 
@@ -258,10 +294,7 @@ public void manageChildren(
       @Nullable ViewAtIndex[] viewsToAdd,
       @Nullable int[] tagsToDelete) {
     ViewGroup viewToManage = (ViewGroup) mTagsToViews.get(tag);
-    ViewGroupManager viewManager = (ViewGroupManager) mTagsToViewManagers.get(tag);
-    if (viewManager == null) {
-      throw new IllegalViewOperationException("ViewManager for tag " + tag + " could not be found");
-    }
+    ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(tag);
     if (viewToManage == null) {
       throw new IllegalViewOperationException("Trying to manageChildren view with tag " + tag +
         " which doesn't exist\n detail: " +
@@ -363,6 +396,13 @@ public void addRootView(
       int tag,
       SizeMonitoringFrameLayout view,
       ThemedReactContext themedContext) {
+    addRootViewGroup(tag, view, themedContext);
+  }
+
+  protected final void addRootViewGroup(
+      int tag,
+      ViewGroup view,
+      ThemedReactContext themedContext) {
     UiThreadUtil.assertOnUiThread();
     if (view.getId() != View.NO_ID) {
       throw new IllegalViewOperationException(
@@ -374,20 +414,17 @@ public void addRootView(
     mTagsToViews.put(tag, view);
     mTagsToViewManagers.put(tag, mRootViewManager);
     mRootTags.put(tag, true);
-    mRootViewsContext.put(tag, themedContext);
     view.setId(tag);
   }
 
   /**
    * Releases all references to given native View.
    */
-  private void dropView(View view) {
+  protected final void dropView(View view) {
     UiThreadUtil.assertOnUiThread();
     if (!mRootTags.get(view.getId())) {
       // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance}
-      Assertions.assertNotNull(mTagsToViewManagers.get(view.getId())).onDropViewInstance(
-          (ThemedReactContext) view.getContext(),
-          view);
+      resolveViewManager(view.getId()).onDropViewInstance(view);
     }
     ViewManager viewManager = mTagsToViewManagers.get(view.getId());
     if (view instanceof ViewGroup && viewManager instanceof ViewGroupManager) {
@@ -414,7 +451,6 @@ public void removeRootView(int rootViewTag) {
     View rootView = mTagsToViews.get(rootViewTag);
     dropView(rootView);
     mRootTags.delete(rootViewTag);
-    mRootViewsContext.remove(rootViewTag);
   }
 
   /**
@@ -428,8 +464,42 @@ public void measure(int tag, int[] outputBuffer) {
       throw new NoSuchNativeViewException("No native view for " + tag + " currently exists");
     }
 
-    // Puts x/y in outputBuffer[0]/[1]
+    View rootView = (View) RootViewUtil.getRootView(v);
+    // It is possible that the RootView can't be found because this view is no longer on the screen
+    // and has been removed by clipping
+    if (rootView == null) {
+      throw new NoSuchNativeViewException("Native view " + tag + " is no longer on screen");
+    }
+    rootView.getLocationInWindow(outputBuffer);
+    int rootX = outputBuffer[0];
+    int rootY = outputBuffer[1];
+
+    v.getLocationInWindow(outputBuffer);
+
+    outputBuffer[0] = outputBuffer[0] - rootX;
+    outputBuffer[1] = outputBuffer[1] - rootY;
+    outputBuffer[2] = v.getWidth();
+    outputBuffer[3] = v.getHeight();
+  }
+
+  /**
+   * Returns the coordinates of a view relative to the entire phone screen (not just the RootView
+   * which is what measure will return)
+   *
+   * @param tag - the tag for the view
+   * @param outputBuffer - output buffer that contains [x,y,width,height] of the view in coordinates
+   *  relative to the device window
+   */
+  public void measureInWindow(int tag, int[] outputBuffer) {
+    UiThreadUtil.assertOnUiThread();
+    View v = mTagsToViews.get(tag);
+    if (v == null) {
+      throw new NoSuchNativeViewException("No native view for " + tag + " currently exists");
+    }
+
     v.getLocationOnScreen(outputBuffer);
+
+    // outputBuffer[0,1] already contain what we want
     outputBuffer[2] = v.getWidth();
     outputBuffer[3] = v.getHeight();
   }
@@ -439,7 +509,7 @@ public int findTargetTagForTouch(int reactTag, float touchX, float touchY) {
     if (view == null) {
       throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag);
     }
-    return TouchTargetHelper.findTargetTagForTouch(touchY, touchX, (ViewGroup) view);
+    return TouchTargetHelper.findTargetTagForTouch(touchX, touchY, (ViewGroup) view);
   }
 
   public void setJSResponder(int reactTag, int initialReactTag, boolean blockNativeResponder) {
@@ -468,6 +538,14 @@ public void clearJSResponder() {
     mJSResponderHandler.clearJSResponder();
   }
 
+  void configureLayoutAnimation(final ReadableMap config) {
+    mLayoutAnimator.initializeFromConfig(config);
+  }
+
+  void clearLayoutAnimation() {
+    mLayoutAnimator.reset();
+  }
+
   /* package */ void startAnimationForNativeView(
       int reactTag,
       Animation animation,
@@ -515,12 +593,7 @@ public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray
           "with tag " + reactTag);
     }
 
-    ViewManager viewManager = mTagsToViewManagers.get(reactTag);
-    if (viewManager == null) {
-      throw new IllegalViewOperationException(
-          "ViewManager for view tag " + reactTag + " could not be found");
-    }
-
+    ViewManager viewManager = resolveViewManager(reactTag);
     viewManager.receiveCommand(view, commandId, args);
   }
 
@@ -583,14 +656,10 @@ public boolean onMenuItemClick(MenuItem item) {
   }
 
   /**
-   * @return Themed React context for view with a given {@param reactTag} - in the case of root
-   * view it returns the context from {@link #mRootViewsContext} and all the other cases it gets the
+   * @return Themed React context for view with a given {@param reactTag} -  it gets the
    * context directly from the view using {@link View#getContext}.
    */
   private ThemedReactContext getReactContextForView(int reactTag) {
-    if (mRootTags.get(reactTag)) {
-      return Assertions.assertNotNull(mRootViewsContext.get(reactTag));
-    }
     View view = mTagsToViews.get(reactTag);
     if (view == null) {
       throw new JSApplicationIllegalArgumentException("Could not find view with tag " + reactTag);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java
index fd3946eeb7c9..1297cc7e37e9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyOptimizer.java
@@ -69,11 +69,15 @@ public NativeViewHierarchyOptimizer(
    */
   public void handleCreateView(
       ReactShadowNode node,
-      int rootViewTag,
-      @Nullable CatalystStylesDiffMap initialProps) {
+      ThemedReactContext themedContext,
+      @Nullable ReactStylesDiffMap initialProps) {
     if (!ENABLED) {
       int tag = node.getReactTag();
-      mUIViewOperationQueue.enqueueCreateView(rootViewTag, tag, node.getViewClass(), initialProps);
+      mUIViewOperationQueue.enqueueCreateView(
+          themedContext,
+          tag,
+          node.getViewClass(),
+          initialProps);
       return;
     }
 
@@ -83,7 +87,7 @@ public void handleCreateView(
 
     if (!isLayoutOnly) {
       mUIViewOperationQueue.enqueueCreateView(
-          rootViewTag,
+          themedContext,
           node.getReactTag(),
           node.getViewClass(),
           initialProps);
@@ -105,7 +109,7 @@ public static void handleRemoveNode(ReactShadowNode node) {
   public void handleUpdateView(
       ReactShadowNode node,
       String className,
-      CatalystStylesDiffMap props) {
+      ReactStylesDiffMap props) {
     if (!ENABLED) {
       mUIViewOperationQueue.enqueueUpdateProperties(node.getReactTag(), className, props);
       return;
@@ -373,7 +377,7 @@ private void applyLayoutRecursive(ReactShadowNode toUpdate, int x, int y) {
 
   private void transitionLayoutOnlyViewToNativeView(
       ReactShadowNode node,
-      @Nullable CatalystStylesDiffMap props) {
+      @Nullable ReactStylesDiffMap props) {
     ReactShadowNode parent = node.getParent();
     if (parent == null) {
       node.setIsLayoutOnly(false);
@@ -391,7 +395,7 @@ private void transitionLayoutOnlyViewToNativeView(
 
     // Create the view since it doesn't exist in the native hierarchy yet
     mUIViewOperationQueue.enqueueCreateView(
-        node.getRootNode().getReactTag(),
+        node.getRootNode().getThemedContext(),
         node.getReactTag(),
         node.getViewClass(),
         props);
@@ -415,7 +419,7 @@ private void transitionLayoutOnlyViewToNativeView(
     mTagsWithLayoutVisited.clear();
   }
 
-  private static boolean isLayoutOnlyAndCollapsable(@Nullable CatalystStylesDiffMap props) {
+  private static boolean isLayoutOnlyAndCollapsable(@Nullable ReactStylesDiffMap props) {
     if (props == null) {
       return true;
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java
index 393ccdf8da95..c2b5c8a6e9d8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java
@@ -9,18 +9,18 @@
 
 package com.facebook.react.uimanager;
 
-import android.os.SystemClock;
 import android.support.v4.util.Pools;
 
 import com.facebook.react.bridge.Arguments;
 import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.events.Event;
 import com.facebook.react.uimanager.events.RCTEventEmitter;
 
 /**
  * Event used to notify JS component about changes of its position or dimensions
  */
-/* package */ class OnLayoutEvent extends Event<OnLayoutEvent> {
+public class OnLayoutEvent extends Event<OnLayoutEvent> {
 
   private static final Pools.SynchronizedPool<OnLayoutEvent> EVENTS_POOL =
       new Pools.SynchronizedPool<>(20);
@@ -45,7 +45,7 @@ private OnLayoutEvent() {
   }
 
   protected void init(int viewTag, int x, int y, int width, int height) {
-    super.init(viewTag, SystemClock.uptimeMillis());
+    super.init(viewTag, SystemClock.nanoTime());
     mX = x;
     mY = y;
     mWidth = width;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.java
index bcb24667fc5d..6a3ecd0a5929 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.java
@@ -23,7 +23,7 @@ public static float toPixelFromDIP(float value) {
     return TypedValue.applyDimension(
         TypedValue.COMPLEX_UNIT_DIP,
         value,
-        DisplayMetricsHolder.getDisplayMetrics());
+        DisplayMetricsHolder.getWindowDisplayMetrics());
   }
 
   /**
@@ -40,7 +40,7 @@ public static float toPixelFromSP(float value) {
     return TypedValue.applyDimension(
         TypedValue.COMPLEX_UNIT_SP,
         value,
-        DisplayMetricsHolder.getDisplayMetrics());
+        DisplayMetricsHolder.getWindowDisplayMetrics());
   }
 
   /**
@@ -54,7 +54,7 @@ public static float toPixelFromSP(double value) {
    * Convert from PX to DP
    */
   public static float toDIPFromPixel(float value) {
-    return value / DisplayMetricsHolder.getDisplayMetrics().density;
+    return value / DisplayMetricsHolder.getWindowDisplayMetrics().density;
   }
 
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java
index 73134194fb1b..df8ad675d294 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java
@@ -12,12 +12,10 @@
 import javax.annotation.Nullable;
 
 import java.util.ArrayList;
-import java.util.Map;
 
 import com.facebook.csslayout.CSSNode;
 import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.bridge.ReadableMap;
-import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
 
 /**
  * Base node class for representing virtual tree of React nodes. Shadow nodes are used primarily
@@ -42,6 +40,7 @@
  * children (e.g. {@link #getNativeChildCount()}). See {@link NativeViewHierarchyOptimizer} for more
  * information.
  */
+@ReactPropertyHolder
 public class ReactShadowNode extends CSSNode {
 
   private int mReactTag;
@@ -106,6 +105,10 @@ protected void markUpdated() {
     }
   }
 
+  public boolean hasUnseenUpdates() {
+    return mNodeUpdated;
+  }
+
   @Override
   protected void dirty() {
     if (!isVirtual()) {
@@ -169,18 +172,8 @@ private void updateNativeChildrenCountInParent(int delta) {
   public void onBeforeLayout() {
   }
 
-  public final void updateProperties(CatalystStylesDiffMap props) {
-    Map<String, ViewManagersPropertyCache.PropSetter> propSetters =
-        ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(getClass());
-    ReadableMap propMap = props.mBackingMap;
-    ReadableMapKeySetIterator iterator = propMap.keySetIterator();
-    while (iterator.hasNextKey()) {
-      String key = iterator.nextKey();
-      ViewManagersPropertyCache.PropSetter setter = propSetters.get(key);
-      if (setter != null) {
-        setter.updateShadowNodeProp(this, props);
-      }
-    }
+  public final void updateProperties(ReactStylesDiffMap props) {
+    ViewManagerPropertyUpdater.updateProps(this, props);
     onAfterUpdateTransaction();
   }
 
@@ -256,7 +249,7 @@ public ThemedReactContext getThemedContext() {
     return Assertions.assertNotNull(mThemedContext);
   }
 
-  protected void setThemedContext(ThemedReactContext themedContext) {
+  public void setThemedContext(ThemedReactContext themedContext) {
     mThemedContext = themedContext;
   }
 
@@ -264,7 +257,7 @@ public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) {
     mShouldNotifyOnLayout = shouldNotifyOnLayout;
   }
 
-  /* package */ boolean shouldNotifyOnLayout() {
+  public boolean shouldNotifyOnLayout() {
     return mShouldNotifyOnLayout;
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/CatalystStylesDiffMap.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java
similarity index 97%
rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/CatalystStylesDiffMap.java
rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java
index e2a01ea62782..2259abb3101d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/CatalystStylesDiffMap.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactStylesDiffMap.java
@@ -34,11 +34,11 @@
  * property shouldn't be updated (whereas in all other cases it should be updated to the new value
  * or the property should be reset).
  */
-public class CatalystStylesDiffMap {
+public class ReactStylesDiffMap {
 
   /* package */ final ReadableMap mBackingMap;
 
-  public CatalystStylesDiffMap(ReadableMap props) {
+  public ReactStylesDiffMap(ReadableMap props) {
     mBackingMap = props;
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootViewUtil.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootViewUtil.java
index e12a76488a88..f9076cbe45b8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootViewUtil.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/RootViewUtil.java
@@ -26,7 +26,9 @@ public static RootView getRootView(View reactView) {
         return (RootView) current;
       }
       ViewParent next = current.getParent();
-      Assertions.assertNotNull(next);
+      if (next == null) {
+        return null;
+      }
       Assertions.assertCondition(next instanceof View);
       current = (View) next;
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java
index 0658f7005ccd..e998110b7bdc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java
@@ -11,12 +11,16 @@
 
 import javax.annotation.Nullable;
 
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.touch.ReactHitSlopView;
 
 /**
  * Class responsible for identifying which react view should handle a given {@link MotionEvent}.
@@ -25,31 +29,51 @@
 public class TouchTargetHelper {
 
   private static final float[] mEventCoords = new float[2];
+  private static final PointF mTempPoint = new PointF();
+  private static final float[] mMatrixTransformCoords = new float[2];
+  private static final Matrix mInverseMatrix = new Matrix();
 
   /**
    * Find touch event target view within the provided container given the coordinates provided
    * via {@link MotionEvent}.
    *
-   * @param eventY the Y screen coordinate of the touch location
    * @param eventX the X screen coordinate of the touch location
+   * @param eventY the Y screen coordinate of the touch location
    * @param viewGroup the container view to traverse
    * @return the react tag ID of the child view that should handle the event
    */
   public static int findTargetTagForTouch(
-      float eventY,
       float eventX,
+      float eventY,
       ViewGroup viewGroup) {
+    return findTargetTagAndCoordinatesForTouch(eventX, eventY, viewGroup, mEventCoords);
+  }
+
+  /**
+   * Find touch event target view within the provided container given the coordinates provided
+   * via {@link MotionEvent}.
+   *
+   * @param eventX the X screen coordinate of the touch location
+   * @param eventY the Y screen coordinate of the touch location
+   * @param viewGroup the container view to traverse
+   * @param viewCoords an out parameter that will return the X,Y value in the target view
+   * @return the react tag ID of the child view that should handle the event
+   */
+  public static int findTargetTagAndCoordinatesForTouch(
+      float eventX,
+      float eventY,
+      ViewGroup viewGroup,
+      float[] viewCoords) {
     UiThreadUtil.assertOnUiThread();
     int targetTag = viewGroup.getId();
     // Store eventCoords in array so that they are modified to be relative to the targetView found.
-    float[] eventCoords = mEventCoords;
-    eventCoords[0] = eventY;
-    eventCoords[1] = eventX;
-    View nativeTargetView = findTouchTargetView(eventCoords, viewGroup);
+    viewCoords[0] = eventX;
+    viewCoords[1] = eventY;
+    View nativeTargetView = findTouchTargetView(viewCoords, viewGroup);
     if (nativeTargetView != null) {
       View reactTargetView = findClosestReactAncestor(nativeTargetView);
       if (reactTargetView != null) {
-        targetTag = getTouchTargetForView(reactTargetView, eventCoords[0], eventCoords[1]);
+        targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]);
       }
     }
     return targetTag;
@@ -77,31 +101,72 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup
     int childrenCount = viewGroup.getChildCount();
     for (int i = childrenCount - 1; i >= 0; i--) {
       View child = viewGroup.getChildAt(i);
-      if (isTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child)) {
-        // Apply offset to event coordinates to transform them into the coordinate space of the
-        // child view, taken from {@link ViewGroup#dispatchTransformedTouchEvent()}.
-        eventCoords[0] += viewGroup.getScrollY() - child.getTop();
-        eventCoords[1] += viewGroup.getScrollX() - child.getLeft();
+      PointF childPoint = mTempPoint;
+      if (isTransformedTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child, childPoint)) {
+        // If it is contained within the child View, the childPoint value will contain the view
+        // coordinates relative to the child
+        // We need to store the existing X,Y for the viewGroup away as it is possible this child
+        // will not actually be the target and so we restore them if not
+        float restoreX = eventCoords[0];
+        float restoreY = eventCoords[1];
+        eventCoords[0] = childPoint.x;
+        eventCoords[1] = childPoint.y;
         View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child);
         if (targetView != null) {
           return targetView;
         }
-        eventCoords[0] -= viewGroup.getScrollY() - child.getTop();
-        eventCoords[1] -= viewGroup.getScrollX() - child.getLeft();
+        eventCoords[0] = restoreX;
+        eventCoords[1] = restoreY;
       }
     }
     return viewGroup;
-}
+  }
 
-  // Taken from {@link ViewGroup#isTransformedTouchPointInView()}
-  private static boolean isTouchPointInView(float y, float x, ViewGroup parent, View child) {
-    float localY = y + parent.getScrollY() - child.getTop();
+  /**
+   * Returns whether the touch point is within the child View
+   * It is transform aware and will invert the transform Matrix to find the true local points
+   * This code is taken from {@link ViewGroup#isTransformedTouchPointInView()}
+   */
+  private static boolean isTransformedTouchPointInView(
+      float x,
+      float y,
+      ViewGroup parent,
+      View child,
+      PointF outLocalPoint) {
     float localX = x + parent.getScrollX() - child.getLeft();
-    // Taken from {@link View#pointInView()}.
-    return localY >= 0 && localY < (child.getBottom() - child.getTop())
-        && localX >= 0 && localX < (child.getRight() - child.getLeft());
+    float localY = y + parent.getScrollY() - child.getTop();
+    Matrix matrix = child.getMatrix();
+    if (!matrix.isIdentity()) {
+      float[] localXY = mMatrixTransformCoords;
+      localXY[0] = localX;
+      localXY[1] = localY;
+      Matrix inverseMatrix = mInverseMatrix;
+      matrix.invert(inverseMatrix);
+      inverseMatrix.mapPoints(localXY);
+      localX = localXY[0];
+      localY = localXY[1];
+    }
+    if (child instanceof ReactHitSlopView && ((ReactHitSlopView) child).getHitSlopRect() != null) {
+      Rect hitSlopRect = ((ReactHitSlopView) child).getHitSlopRect();
+      if ((localX >= -hitSlopRect.left && localX < (child.getRight() - child.getLeft()) + hitSlopRect.right)
+          && (localY >= -hitSlopRect.top && localY < (child.getBottom() - child.getTop()) + hitSlopRect.bottom)) {
+        outLocalPoint.set(localX, localY);
+        return true;
+      }
+
+      return false;
+    } else {
+      if ((localX >= 0 && localX < (child.getRight() - child.getLeft()))
+          && (localY >= 0 && localY < (child.getBottom() - child.getTop()))) {
+        outLocalPoint.set(localX, localY);
+        return true;
+      }
+
+      return false;
+    }
   }
 
+
   /**
    * Returns the touch target View of the event given, or null if neither the given View nor any of
    * its descendants are the touch target.
@@ -122,7 +187,23 @@ private static boolean isTouchPointInView(float y, float x, ViewGroup parent, Vi
       // This view can't be the target, but its children might
       if (view instanceof ViewGroup) {
         View targetView = findTouchTargetView(eventCoords, (ViewGroup) view);
-        return targetView != view ? targetView : null;
+        if (targetView != view) {
+          return targetView;
+        }
+
+        // PointerEvents.BOX_NONE means that this react element cannot receive pointer events.
+        // However, there might be virtual children that can receive pointer events, in which case
+        // we still want to return this View and dispatch a pointer event to the virtual element.
+        // Note that this currently only applies to Nodes/FlatViewGroup as it's the only class that
+        // is both a ViewGroup and ReactCompoundView (ReactTextView is a ReactCompoundView but not a
+        // ViewGroup).
+        if (view instanceof ReactCompoundView) {
+          int reactTag = ((ReactCompoundView)view).reactTagForTouch(eventCoords[0], eventCoords[1]);
+          if (reactTag != view.getId()) {
+            // make sure we exclude the View itself because of the PointerEvents.BOX_NONE
+            return view;
+          }
+        }
       }
       return null;
 
@@ -139,7 +220,7 @@ private static boolean isTouchPointInView(float y, float x, ViewGroup parent, Vi
     }
   }
 
-  private static int getTouchTargetForView(View targetView, float eventY, float eventX) {
+  private static int getTouchTargetForView(View targetView, float eventX, float eventY) {
     if (targetView instanceof ReactCompoundView) {
       // Use coordinates relative to the view, which have been already computed by
       // {@link #findTouchTargetView()}.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
new file mode 100644
index 000000000000..9e63b313fbf2
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
@@ -0,0 +1,729 @@
+/**
+ * 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.uimanager;
+
+import javax.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.facebook.csslayout.CSSLayoutContext;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.animation.Animation;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
+import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.systrace.Systrace;
+import com.facebook.systrace.SystraceMessage;
+
+/**
+ * An class that is used to receive React commands from JS and translate them into a
+ * shadow node hierarchy that is then mapped to a native view hierarchy.
+ */
+public class UIImplementation {
+
+  private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry();
+  private final ViewManagerRegistry mViewManagers;
+  private final CSSLayoutContext mLayoutContext = new CSSLayoutContext();
+  private final UIViewOperationQueue mOperationsQueue;
+  private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer;
+  private final int[] mMeasureBuffer = new int[4];
+
+  public UIImplementation(ReactApplicationContext reactContext, List<ViewManager> viewManagers) {
+    this(reactContext, new ViewManagerRegistry(viewManagers));
+  }
+
+  private UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) {
+    this(
+        viewManagers,
+        new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(viewManagers)));
+  }
+
+  protected UIImplementation(
+      ViewManagerRegistry viewManagers,
+      UIViewOperationQueue operationsQueue) {
+    mViewManagers = viewManagers;
+    mOperationsQueue = operationsQueue;
+    mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer(
+        mOperationsQueue,
+        mShadowNodeRegistry);
+  }
+
+  protected ReactShadowNode createRootShadowNode() {
+    ReactShadowNode rootCSSNode = new ReactShadowNode();
+    rootCSSNode.setViewClassName("Root");
+    return rootCSSNode;
+  }
+
+  protected ReactShadowNode createShadowNode(String className) {
+    ViewManager viewManager = mViewManagers.get(className);
+    return viewManager.createShadowNodeInstance();
+  }
+
+  protected final ReactShadowNode resolveShadowNode(int reactTag) {
+    return mShadowNodeRegistry.getNode(reactTag);
+  }
+
+  protected final ViewManager resolveViewManager(String className) {
+    return mViewManagers.get(className);
+  }
+
+  /**
+   * Registers a root node with a given tag, size and ThemedReactContext
+   * and adds it to a node registry.
+   */
+  public void registerRootView(
+      SizeMonitoringFrameLayout rootView,
+      int tag,
+      int width,
+      int height,
+      ThemedReactContext context) {
+    final ReactShadowNode rootCSSNode = createRootShadowNode();
+    rootCSSNode.setReactTag(tag);
+    rootCSSNode.setThemedContext(context);
+    rootCSSNode.setStyleWidth(width);
+    rootCSSNode.setStyleHeight(height);
+    mShadowNodeRegistry.addRootNode(rootCSSNode);
+
+    // register it within NativeViewHierarchyManager
+    mOperationsQueue.addRootView(tag, rootView, context);
+  }
+
+  /**
+   * Unregisters a root node with a given tag.
+   */
+  public void removeRootView(int rootViewTag) {
+    mShadowNodeRegistry.removeRootNode(rootViewTag);
+    mOperationsQueue.enqueueRemoveRootView(rootViewTag);
+  }
+
+  /**
+   * Invoked when native view that corresponds to a root node has its size changed.
+   */
+  public void updateRootNodeSize(
+      int rootViewTag,
+      int newWidth,
+      int newHeight,
+      EventDispatcher eventDispatcher) {
+    ReactShadowNode rootCSSNode = mShadowNodeRegistry.getNode(rootViewTag);
+    rootCSSNode.setStyleWidth(newWidth);
+    rootCSSNode.setStyleHeight(newHeight);
+
+    // If we're in the middle of a batch, the change will automatically be dispatched at the end of
+    // the batch. As all batches are executed as a single runnable on the event queue this should
+    // always be empty, but that calling architecture is an implementation detail.
+    if (mOperationsQueue.isEmpty()) {
+      dispatchViewUpdates(eventDispatcher, -1); // -1 = no associated batch id
+    }
+  }
+
+  /**
+   * Invoked by React to create a new node with a given tag, class name and properties.
+   */
+  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
+    ReactShadowNode cssNode = createShadowNode(className);
+    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
+    cssNode.setReactTag(tag);
+    cssNode.setViewClassName(className);
+    cssNode.setRootNode(rootNode);
+    cssNode.setThemedContext(rootNode.getThemedContext());
+
+    mShadowNodeRegistry.addNode(cssNode);
+
+    ReactStylesDiffMap styles = null;
+    if (props != null) {
+      styles = new ReactStylesDiffMap(props);
+      cssNode.updateProperties(styles);
+    }
+
+    handleCreateView(cssNode, rootViewTag, styles);
+  }
+
+  protected void handleCreateView(
+      ReactShadowNode cssNode,
+      int rootViewTag,
+      @Nullable ReactStylesDiffMap styles) {
+    if (!cssNode.isVirtual()) {
+      mNativeViewHierarchyOptimizer.handleCreateView(cssNode, cssNode.getThemedContext(), styles);
+    }
+  }
+
+  /**
+   * Invoked by React to create a new node with a given tag has its properties changed.
+   */
+  public void updateView(int tag, String className, ReadableMap props) {
+    ViewManager viewManager = mViewManagers.get(className);
+    if (viewManager == null) {
+      throw new IllegalViewOperationException("Got unknown view type: " + className);
+    }
+    ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag);
+    if (cssNode == null) {
+      throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag);
+    }
+
+    if (props != null) {
+      ReactStylesDiffMap styles = new ReactStylesDiffMap(props);
+      cssNode.updateProperties(styles);
+      handleUpdateView(cssNode, className, styles);
+    }
+  }
+
+  protected void handleUpdateView(
+      ReactShadowNode cssNode,
+      String className,
+      ReactStylesDiffMap styles) {
+    if (!cssNode.isVirtual()) {
+      mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles);
+    }
+  }
+
+  /**
+   * Invoked when there is a mutation in a node tree.
+   *
+   * @param tag react tag of the node we want to manage
+   * @param indicesToRemove ordered (asc) list of indicies at which view should be removed
+   * @param viewsToAdd ordered (asc based on mIndex property) list of tag-index pairs that represent
+   * a view which should be added at the specified index
+   * @param tagsToDelete list of tags corresponding to views that should be removed
+   */
+  public void manageChildren(
+      int viewTag,
+      @Nullable ReadableArray moveFrom,
+      @Nullable ReadableArray moveTo,
+      @Nullable ReadableArray addChildTags,
+      @Nullable ReadableArray addAtIndices,
+      @Nullable ReadableArray removeFrom) {
+    ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
+
+    int numToMove = moveFrom == null ? 0 : moveFrom.size();
+    int numToAdd = addChildTags == null ? 0 : addChildTags.size();
+    int numToRemove = removeFrom == null ? 0 : removeFrom.size();
+
+    if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
+      throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
+    }
+
+    if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
+      throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
+    }
+
+    // We treat moves as an add and a delete
+    ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
+    int[] indicesToRemove = new int[numToMove + numToRemove];
+    int[] tagsToRemove = new int[indicesToRemove.length];
+    int[] tagsToDelete = new int[numToRemove];
+
+    if (numToMove > 0) {
+      Assertions.assertNotNull(moveFrom);
+      Assertions.assertNotNull(moveTo);
+      for (int i = 0; i < numToMove; i++) {
+        int moveFromIndex = moveFrom.getInt(i);
+        int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
+        viewsToAdd[i] = new ViewAtIndex(
+            tagToMove,
+            moveTo.getInt(i));
+        indicesToRemove[i] = moveFromIndex;
+        tagsToRemove[i] = tagToMove;
+      }
+    }
+
+    if (numToAdd > 0) {
+      Assertions.assertNotNull(addChildTags);
+      Assertions.assertNotNull(addAtIndices);
+      for (int i = 0; i < numToAdd; i++) {
+        int viewTagToAdd = addChildTags.getInt(i);
+        int indexToAddAt = addAtIndices.getInt(i);
+        viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
+      }
+    }
+
+    if (numToRemove > 0) {
+      Assertions.assertNotNull(removeFrom);
+      for (int i = 0; i < numToRemove; i++) {
+        int indexToRemove = removeFrom.getInt(i);
+        int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
+        indicesToRemove[numToMove + i] = indexToRemove;
+        tagsToRemove[numToMove + i] = tagToRemove;
+        tagsToDelete[i] = tagToRemove;
+      }
+    }
+
+    // NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
+    // moveTo and addAt are both relative to the final state of the View's children.
+    //
+    // 1) Sort the views to add and indices to remove by index
+    // 2) Iterate the indices being removed from high to low and remove them. Going high to low
+    //    makes sure we remove the correct index when there are multiple to remove.
+    // 3) Iterate the views being added by index low to high and add them. Like the view removal,
+    //    iteration direction is important to preserve the correct index.
+
+    Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
+    Arrays.sort(indicesToRemove);
+
+    // Apply changes to CSSNode hierarchy
+    int lastIndexRemoved = -1;
+    for (int i = indicesToRemove.length - 1; i >= 0; i--) {
+      int indexToRemove = indicesToRemove[i];
+      if (indexToRemove == lastIndexRemoved) {
+        throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
+            + viewTag);
+      }
+      cssNodeToManage.removeChildAt(indicesToRemove[i]);
+      lastIndexRemoved = indicesToRemove[i];
+    }
+
+    for (int i = 0; i < viewsToAdd.length; i++) {
+      ViewAtIndex viewAtIndex = viewsToAdd[i];
+      ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
+      if (cssNodeToAdd == null) {
+        throw new IllegalViewOperationException("Trying to add unknown view tag: "
+            + viewAtIndex.mTag);
+      }
+      cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
+    }
+
+    if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
+      mNativeViewHierarchyOptimizer.handleManageChildren(
+          cssNodeToManage,
+          indicesToRemove,
+          tagsToRemove,
+          viewsToAdd,
+          tagsToDelete);
+    }
+
+    for (int i = 0; i < tagsToDelete.length; i++) {
+      removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
+    }
+  }
+
+  /**
+   * Replaces the View specified by oldTag with the View specified by newTag within oldTag's parent.
+   */
+  public void replaceExistingNonRootView(int oldTag, int newTag) {
+    if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) {
+      throw new IllegalViewOperationException("Trying to add or replace a root tag!");
+    }
+
+    ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag);
+    if (oldNode == null) {
+      throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag);
+    }
+
+    ReactShadowNode parent = oldNode.getParent();
+    if (parent == null) {
+      throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag);
+    }
+
+    int oldIndex = parent.indexOf(oldNode);
+    if (oldIndex < 0) {
+      throw new IllegalStateException("Didn't find child tag in parent");
+    }
+
+    WritableArray tagsToAdd = Arguments.createArray();
+    tagsToAdd.pushInt(newTag);
+
+    WritableArray addAtIndices = Arguments.createArray();
+    addAtIndices.pushInt(oldIndex);
+
+    WritableArray indicesToRemove = Arguments.createArray();
+    indicesToRemove.pushInt(oldIndex);
+
+    manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove);
+  }
+
+  /**
+   * Method which takes a container tag and then releases all subviews for that container upon
+   * receipt.
+   * TODO: The method name is incorrect and will be renamed, #6033872
+   * @param containerTag the tag of the container for which the subviews must be removed
+   */
+  public void removeSubviewsFromContainerWithID(int containerTag) {
+    ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag);
+    if (containerNode == null) {
+      throw new IllegalViewOperationException(
+          "Trying to remove subviews of an unknown view tag: " + containerTag);
+    }
+
+    WritableArray indicesToRemove = Arguments.createArray();
+    for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) {
+      indicesToRemove.pushInt(childIndex);
+    }
+
+    manageChildren(containerTag, null, null, null, null, indicesToRemove);
+  }
+
+  /**
+   * Find the touch target child native view in  the supplied root view hierarchy, given a react
+   * target location.
+   *
+   * This method is currently used only by Element Inspector DevTool.
+   *
+   * @param reactTag the tag of the root view to traverse
+   * @param targetX target X location
+   * @param targetY target Y location
+   * @param callback will be called if with the identified child view react ID, and measurement
+   *        info. If no view was found, callback will be invoked with no data.
+   */
+  public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) {
+    mOperationsQueue.enqueueFindTargetForTouch(reactTag, targetX, targetY, callback);
+  }
+
+  /**
+   * Determines the location on screen, width, and height of the given view relative to the root
+   * view and returns the values via an async callback.
+   */
+  public void measure(int reactTag, Callback callback) {
+    // This method is called by the implementation of JS touchable interface (see Touchable.js for
+    // more details) at the moment of touch activation. That is after user starts the gesture from
+    // a touchable view with a given reactTag, or when user drag finger back into the press
+    // activation area of a touchable view that have been activated before.
+    mOperationsQueue.enqueueMeasure(reactTag, callback);
+  }
+
+  /**
+   * Determines the location on screen, width, and height of the given view relative to the device
+   * screen and returns the values via an async callback.  This is the absolute position including
+   * things like the status bar
+   */
+  public void measureInWindow(int reactTag, Callback callback) {
+    mOperationsQueue.enqueueMeasureInWindow(reactTag, callback);
+  }
+
+  /**
+   * Measures the view specified by tag relative to the given ancestorTag. This means that the
+   * returned x, y are relative to the origin x, y of the ancestor view. Results are stored in the
+   * given outputBuffer. We allow ancestor view and measured view to be the same, in which case
+   * the position always will be (0, 0) and method will only measure the view dimensions.
+   */
+  public void measureLayout(
+      int tag,
+      int ancestorTag,
+      Callback errorCallback,
+      Callback successCallback) {
+    try {
+      measureLayout(tag, ancestorTag, mMeasureBuffer);
+      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
+      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
+      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
+      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
+      successCallback.invoke(relativeX, relativeY, width, height);
+    } catch (IllegalViewOperationException e) {
+      errorCallback.invoke(e.getMessage());
+    }
+  }
+
+  /**
+   * Like {@link #measure} and {@link #measureLayout} but measures relative to the immediate parent.
+   */
+  public void measureLayoutRelativeToParent(
+      int tag,
+      Callback errorCallback,
+      Callback successCallback) {
+    try {
+      measureLayoutRelativeToParent(tag, mMeasureBuffer);
+      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
+      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
+      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
+      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
+      successCallback.invoke(relativeX, relativeY, width, height);
+    } catch (IllegalViewOperationException e) {
+      errorCallback.invoke(e.getMessage());
+    }
+  }
+
+  /**
+   * Invoked at the end of the transaction to commit any updates to the node hierarchy.
+   */
+  public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) {
+    for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
+      int tag = mShadowNodeRegistry.getRootTag(i);
+      ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
+      notifyOnBeforeLayoutRecursive(cssRoot);
+
+      calculateRootLayout(cssRoot);
+      applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher);
+    }
+
+    mNativeViewHierarchyOptimizer.onBatchComplete();
+    mOperationsQueue.dispatchViewUpdates(batchId);
+  }
+
+  /**
+   * Registers a new Animation that can then be added to a View using {@link #addAnimation}.
+   */
+  public void registerAnimation(Animation animation) {
+    mOperationsQueue.enqueueRegisterAnimation(animation);
+  }
+
+  /**
+   * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it
+   */
+  public void addAnimation(int reactTag, int animationID, Callback onSuccess) {
+    assertViewExists(reactTag, "addAnimation");
+    mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess);
+  }
+
+  /**
+   * Removes an existing Animation, canceling it if it was in progress.
+   */
+  public void removeAnimation(int reactTag, int animationID) {
+    assertViewExists(reactTag, "removeAnimation");
+    mOperationsQueue.enqueueRemoveAnimation(animationID);
+  }
+
+  /**
+   * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled
+   * explicitly in order to avoid regression in existing application written for iOS using this API.
+   *
+   * Warning : This method will be removed in future version of React Native, and layout animation
+   * will be enabled by default, so always check for its existence before invoking it.
+   *
+   * TODO(9139831) : remove this method once layout animation is fully stable.
+   *
+   * @param enabled whether layout animation is enabled or not
+   */
+  public void setLayoutAnimationEnabledExperimental(boolean enabled) {
+    mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled);
+  }
+
+  /**
+   * Configure an animation to be used for the native layout changes, and native views
+   * creation. The animation will only apply during the current batch operations.
+   *
+   * TODO(7728153) : animating view deletion is currently not supported.
+   * TODO(7613721) : callbacks are not supported, this feature will likely be killed.
+   *
+   * @param config the configuration of the animation for view addition/removal/update.
+   * @param success will be called when the animation completes, or when the animation get
+   *        interrupted. In this case, callback parameter will be false.
+   * @param error will be called if there was an error processing the animation
+   */
+  public void configureNextLayoutAnimation(
+      ReadableMap config,
+      Callback success,
+      Callback error) {
+    mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error);
+  }
+
+
+  public void setJSResponder(int reactTag, boolean blockNativeResponder) {
+    assertViewExists(reactTag, "setJSResponder");
+    ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag);
+    while (node.isVirtual() || node.isLayoutOnly()) {
+      node = node.getParent();
+    }
+    mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder);
+  }
+
+  public void clearJSResponder() {
+    mOperationsQueue.enqueueClearJSResponder();
+  }
+
+  public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {
+    assertViewExists(reactTag, "dispatchViewManagerCommand");
+    mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
+  }
+
+  /**
+   * Show a PopupMenu.
+   *
+   * @param reactTag the tag of the anchor view (the PopupMenu is displayed next to this view); this
+   *        needs to be the tag of a native view (shadow views can not be anchors)
+   * @param items the menu items as an array of strings
+   * @param error will be called if there is an error displaying the menu
+   * @param success will be called with the position of the selected item as the first argument, or
+   *        no arguments if the menu is dismissed
+   */
+  public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
+    assertViewExists(reactTag, "showPopupMenu");
+    mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success);
+  }
+
+  public void sendAccessibilityEvent(int tag, int eventType) {
+    mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType);
+  }
+
+  public void onHostResume() {
+    mOperationsQueue.resumeFrameCallback();
+  }
+
+  public void onHostPause() {
+    mOperationsQueue.pauseFrameCallback();
+  }
+
+  public void onHostDestroy() {
+  }
+
+  public void setViewHierarchyUpdateDebugListener(
+      @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
+    mOperationsQueue.setViewHierarchyUpdateDebugListener(listener);
+  }
+
+  protected final void removeShadowNode(ReactShadowNode nodeToRemove) {
+    mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove);
+    mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag());
+    for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) {
+      removeShadowNode(nodeToRemove.getChildAt(i));
+    }
+    nodeToRemove.removeAllChildren();
+  }
+
+  private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) {
+    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
+    ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag);
+    if (node == null || ancestor == null) {
+      throw new IllegalViewOperationException(
+          "Tag " + (node == null ? tag : ancestorTag) + " does not exist");
+    }
+
+    if (node != ancestor) {
+      ReactShadowNode currentParent = node.getParent();
+      while (currentParent != ancestor) {
+        if (currentParent == null) {
+          throw new IllegalViewOperationException(
+              "Tag " + ancestorTag + " is not an ancestor of tag " + tag);
+        }
+        currentParent = currentParent.getParent();
+      }
+    }
+
+    measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer);
+  }
+
+  private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) {
+    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
+    if (node == null) {
+      throw new IllegalViewOperationException("No native view for tag " + tag + " exists!");
+    }
+    ReactShadowNode parent = node.getParent();
+    if (parent == null) {
+      throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!");
+    }
+
+    measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer);
+  }
+
+  private void measureLayoutRelativeToVerifiedAncestor(
+      ReactShadowNode node,
+      ReactShadowNode ancestor,
+      int[] outputBuffer) {
+    int offsetX = 0;
+    int offsetY = 0;
+    if (node != ancestor) {
+      offsetX = Math.round(node.getLayoutX());
+      offsetY = Math.round(node.getLayoutY());
+      ReactShadowNode current = node.getParent();
+      while (current != ancestor) {
+        Assertions.assertNotNull(current);
+        assertNodeDoesNotNeedCustomLayoutForChildren(current);
+        offsetX += Math.round(current.getLayoutX());
+        offsetY += Math.round(current.getLayoutY());
+        current = current.getParent();
+      }
+      assertNodeDoesNotNeedCustomLayoutForChildren(ancestor);
+    }
+
+    outputBuffer[0] = offsetX;
+    outputBuffer[1] = offsetY;
+    outputBuffer[2] = node.getScreenWidth();
+    outputBuffer[3] = node.getScreenHeight();
+  }
+
+  private void assertViewExists(int reactTag, String operationNameForExceptionMessage) {
+    if (mShadowNodeRegistry.getNode(reactTag) == null) {
+      throw new IllegalViewOperationException(
+          "Unable to execute operation " + operationNameForExceptionMessage + " on view with " +
+              "tag: " + reactTag + ", since the view does not exists");
+    }
+  }
+
+  private void assertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) {
+    ViewManager viewManager = Assertions.assertNotNull(mViewManagers.get(node.getViewClass()));
+    ViewGroupManager viewGroupManager;
+    if (viewManager instanceof ViewGroupManager) {
+      viewGroupManager = (ViewGroupManager) viewManager;
+    } else {
+      throw new IllegalViewOperationException("Trying to use view " + node.getViewClass() +
+          " as a parent, but its Manager doesn't extends ViewGroupManager");
+    }
+    if (viewGroupManager != null && viewGroupManager.needsCustomLayoutForChildren()) {
+      throw new IllegalViewOperationException(
+          "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to" +
+              " an ancestor that requires custom layout for it's children (" + node.getViewClass() +
+              "). Use measure instead.");
+    }
+  }
+
+  private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) {
+    if (!cssNode.hasUpdates()) {
+      return;
+    }
+    for (int i = 0; i < cssNode.getChildCount(); i++) {
+      notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i));
+    }
+    cssNode.onBeforeLayout();
+  }
+
+  protected void calculateRootLayout(ReactShadowNode cssRoot) {
+    SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout")
+        .arg("rootTag", cssRoot.getReactTag())
+        .flush();
+    try {
+      cssRoot.calculateLayout(mLayoutContext);
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+  }
+
+  protected void applyUpdatesRecursive(
+      ReactShadowNode cssNode,
+      float absoluteX,
+      float absoluteY,
+      EventDispatcher eventDispatcher) {
+    if (!cssNode.hasUpdates()) {
+      return;
+    }
+
+    if (!cssNode.isVirtualAnchor()) {
+      for (int i = 0; i < cssNode.getChildCount(); i++) {
+        applyUpdatesRecursive(
+            cssNode.getChildAt(i),
+            absoluteX + cssNode.getLayoutX(),
+            absoluteY + cssNode.getLayoutY(),
+            eventDispatcher);
+      }
+    }
+
+    int tag = cssNode.getReactTag();
+    if (!mShadowNodeRegistry.isRootNode(tag)) {
+      cssNode.dispatchUpdates(
+          absoluteX,
+          absoluteY,
+          mOperationsQueue,
+          mNativeViewHierarchyOptimizer);
+      
+      // notify JS about layout event if requested
+      if (cssNode.shouldNotifyOnLayout()) {
+        eventDispatcher.dispatchEvent(
+            OnLayoutEvent.obtain(
+                tag,
+                cssNode.getScreenX(),
+                cssNode.getScreenY(),
+                cssNode.getScreenWidth(),
+                cssNode.getScreenHeight()));
+      }
+    }
+    cssNode.markUpdateSeen();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java
new file mode 100644
index 000000000000..3d1a4a2f39cd
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementationProvider.java
@@ -0,0 +1,24 @@
+/**
+ * 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.uimanager;
+
+import java.util.List;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+
+/**
+ * Provides UIImplementation to use in {@link UIManagerModule}.
+ */
+public class UIImplementationProvider {
+  public UIImplementation createUIImplementation(
+      ReactApplicationContext reactContext,
+      List<ViewManager> viewManagers) {
+    return new UIImplementation(reactContext, viewManagers);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
index 81a3bd5f6123..ddeb94e6db2e 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
@@ -1,4 +1,4 @@
-/**
+  /**
  * Copyright (c) 2015-present, Facebook, Inc.
  * All rights reserved.
  *
@@ -11,19 +11,10 @@
 
 import javax.annotation.Nullable;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-import android.util.DisplayMetrics;
 
-import com.facebook.csslayout.CSSLayoutContext;
-import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.animation.Animation;
-import com.facebook.react.animation.AnimationRegistry;
-import com.facebook.react.bridge.Arguments;
 import com.facebook.react.bridge.Callback;
 import com.facebook.react.bridge.LifecycleEventListener;
 import com.facebook.react.bridge.OnBatchCompleteListener;
@@ -32,10 +23,7 @@
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
-import com.facebook.react.bridge.SoftAssertions;
-import com.facebook.react.bridge.UiThreadUtil;
-import com.facebook.react.bridge.WritableArray;
-import com.facebook.react.uimanager.debug.NotThreadSafeUiManagerDebugListener;
+import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
 import com.facebook.react.uimanager.events.EventDispatcher;
 import com.facebook.systrace.Systrace;
 import com.facebook.systrace.SystraceMessage;
@@ -45,7 +33,7 @@
  *
  * <p>
  * <h2>== Transactional Requirement ==</h2>
- * A requirement of this class is to make sure that transactional UI updates occur all at, meaning
+ * A requirement of this class is to make sure that transactional UI updates occur all at once, meaning
  * that no intermediate state is ever rendered to the screen. For example, if a JS application
  * update changes the background of View A to blue and the width of View B to 100, both need to
  * appear at once. Practically, this means that all UI update code related to a single transaction
@@ -76,42 +64,22 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements
   // increment here is 10
   private static final int ROOT_VIEW_TAG_INCREMENT = 10;
 
-  private final NativeViewHierarchyManager mNativeViewHierarchyManager;
   private final EventDispatcher mEventDispatcher;
-  private final AnimationRegistry mAnimationRegistry = new AnimationRegistry();
-  private final ShadowNodeRegistry mShadowNodeRegistry = new ShadowNodeRegistry();
-  private final ViewManagerRegistry mViewManagers;
-  private final CSSLayoutContext mLayoutContext = new CSSLayoutContext();
   private final Map<String, Object> mModuleConstants;
-  private final UIViewOperationQueue mOperationsQueue;
-  private final NativeViewHierarchyOptimizer mNativeViewHierarchyOptimizer;
-  private final int[] mMeasureBuffer = new int[4];
+  private final UIImplementation mUIImplementation;
 
-  private @Nullable NotThreadSafeUiManagerDebugListener mUiManagerDebugListener;
   private int mNextRootViewTag = 1;
   private int mBatchId = 0;
 
-  public UIManagerModule(ReactApplicationContext reactContext, List<ViewManager> viewManagerList) {
+  public UIManagerModule(
+      ReactApplicationContext reactContext,
+      List<ViewManager> viewManagerList,
+      UIImplementation uiImplementation) {
     super(reactContext);
-    mViewManagers = new ViewManagerRegistry(viewManagerList);
     mEventDispatcher = new EventDispatcher(reactContext);
-    mNativeViewHierarchyManager = new NativeViewHierarchyManager(
-        mAnimationRegistry,
-        mViewManagers);
-    mOperationsQueue = new UIViewOperationQueue(
-        reactContext,
-        this,
-        mNativeViewHierarchyManager,
-        mAnimationRegistry);
-    mNativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer(
-        mOperationsQueue,
-        mShadowNodeRegistry);
-    DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
-
-    mModuleConstants = UIManagerModuleConstantsHelper.createConstants(
-        displayMetrics,
-        viewManagerList);
+    mModuleConstants = createConstants(viewManagerList);
+    mUIImplementation = uiImplementation;
+
     reactContext.addLifecycleEventListener(this);
   }
 
@@ -127,16 +95,17 @@ public Map<String, Object> getConstants() {
 
   @Override
   public void onHostResume() {
-    mOperationsQueue.resumeFrameCallback();
+    mUIImplementation.onHostResume();
   }
 
   @Override
   public void onHostPause() {
-    mOperationsQueue.pauseFrameCallback();
+    mUIImplementation.onHostPause();
   }
 
   @Override
   public void onHostDestroy() {
+    mUIImplementation.onHostDestroy();
   }
 
   @Override
@@ -145,6 +114,15 @@ public void onCatalystInstanceDestroy() {
     mEventDispatcher.onCatalystInstanceDestroyed();
   }
 
+  private static Map<String, Object> createConstants(List<ViewManager> viewManagerList) {
+    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants");
+    try {
+      return UIManagerModuleConstantsHelper.createConstants(viewManagerList);
+    } finally {
+      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+    }
+  }
+
   /**
    * Registers a new root view. JS can use the returned tag with manageChildren to add/remove
    * children to this view.
@@ -161,22 +139,23 @@ public int addMeasuredRootView(final SizeMonitoringFrameLayout rootView) {
     final int tag = mNextRootViewTag;
     mNextRootViewTag += ROOT_VIEW_TAG_INCREMENT;
 
-    final ReactShadowNode rootCSSNode = new ReactShadowNode();
-    rootCSSNode.setReactTag(tag);
-    final ThemedReactContext themedRootContext =
-        new ThemedReactContext(getReactApplicationContext(), rootView.getContext());
-    rootCSSNode.setThemedContext(themedRootContext);
+    final int width;
+    final int height;
     // If LayoutParams sets size explicitly, we can use that. Otherwise get the size from the view.
     if (rootView.getLayoutParams() != null &&
         rootView.getLayoutParams().width > 0 &&
         rootView.getLayoutParams().height > 0) {
-      rootCSSNode.setStyleWidth(rootView.getLayoutParams().width);
-      rootCSSNode.setStyleHeight(rootView.getLayoutParams().height);
+      width = rootView.getLayoutParams().width;
+      height = rootView.getLayoutParams().height;
     } else {
-      rootCSSNode.setStyleWidth(rootView.getWidth());
-      rootCSSNode.setStyleHeight(rootView.getHeight());
+      width = rootView.getWidth();
+      height = rootView.getHeight();
     }
-    rootCSSNode.setViewClassName("Root");
+
+    final ThemedReactContext themedRootContext =
+        new ThemedReactContext(getReactApplicationContext(), rootView.getContext());
+
+    mUIImplementation.registerRootView(rootView, tag, width, height, themedRootContext);
 
     rootView.setOnSizeChangedListener(
         new SizeMonitoringFrameLayout.OnSizeChangedListener() {
@@ -186,99 +165,34 @@ public void onSizeChanged(final int width, final int height, int oldW, int oldH)
                 new Runnable() {
                   @Override
                   public void run() {
-                    updateRootNodeSize(rootCSSNode, width, height);
+                    updateRootNodeSize(tag, width, height);
                   }
                 });
           }
         });
 
-    mShadowNodeRegistry.addRootNode(rootCSSNode);
-
-    if (UiThreadUtil.isOnUiThread()) {
-      mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext);
-    } else {
-      final Semaphore semaphore = new Semaphore(0);
-      getReactApplicationContext().runOnUiQueueThread(
-          new Runnable() {
-            @Override
-            public void run() {
-              mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext);
-              semaphore.release();
-            }
-          });
-      try {
-        SoftAssertions.assertCondition(
-            semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS),
-            "Timed out adding root view");
-      } catch (InterruptedException e) {
-        throw new RuntimeException(e);
-      }
-    }
-
     return tag;
   }
 
   @ReactMethod
   public void removeRootView(int rootViewTag) {
-    mShadowNodeRegistry.removeRootNode(rootViewTag);
-    mOperationsQueue.enqueueRemoveRootView(rootViewTag);
+    mUIImplementation.removeRootView(rootViewTag);
   }
 
-  private void updateRootNodeSize(ReactShadowNode rootCSSNode, int newWidth, int newHeight) {
+  private void updateRootNodeSize(int rootViewTag, int newWidth, int newHeight) {
     getReactApplicationContext().assertOnNativeModulesQueueThread();
 
-    rootCSSNode.setStyleWidth(newWidth);
-    rootCSSNode.setStyleHeight(newHeight);
-
-    // If we're in the middle of a batch, the change will automatically be dispatched at the end of
-    // the batch. As all batches are executed as a single runnable on the event queue this should
-    // always be empty, but that calling architecture is an implementation detail.
-    if (mOperationsQueue.isEmpty()) {
-      dispatchViewUpdates(-1); // -1 = no associated batch id
-    }
+    mUIImplementation.updateRootNodeSize(rootViewTag, newWidth, newHeight, mEventDispatcher);
   }
 
   @ReactMethod
   public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
-    ViewManager viewManager = mViewManagers.get(className);
-    ReactShadowNode cssNode = viewManager.createShadowNodeInstance();
-    ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
-    cssNode.setReactTag(tag);
-    cssNode.setViewClassName(className);
-    cssNode.setRootNode(rootNode);
-    cssNode.setThemedContext(rootNode.getThemedContext());
-
-    mShadowNodeRegistry.addNode(cssNode);
-
-    CatalystStylesDiffMap styles = null;
-    if (props != null) {
-      styles = new CatalystStylesDiffMap(props);
-      cssNode.updateProperties(styles);
-    }
-
-    if (!cssNode.isVirtual()) {
-      mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles);
-    }
+    mUIImplementation.createView(tag, className, rootViewTag, props);
   }
 
   @ReactMethod
   public void updateView(int tag, String className, ReadableMap props) {
-    ViewManager viewManager = mViewManagers.get(className);
-    if (viewManager == null) {
-      throw new IllegalViewOperationException("Got unknown view type: " + className);
-    }
-    ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag);
-    if (cssNode == null) {
-      throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag);
-    }
-
-    if (props != null) {
-      CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props);
-      cssNode.updateProperties(styles);
-      if (!cssNode.isVirtual()) {
-        mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles);
-      }
-    }
+    mUIImplementation.updateView(tag, className, props);
   }
 
   /**
@@ -300,116 +214,13 @@ public void manageChildren(
       @Nullable ReadableArray addChildTags,
       @Nullable ReadableArray addAtIndices,
       @Nullable ReadableArray removeFrom) {
-    ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
-
-    int numToMove = moveFrom == null ? 0 : moveFrom.size();
-    int numToAdd = addChildTags == null ? 0 : addChildTags.size();
-    int numToRemove = removeFrom == null ? 0 : removeFrom.size();
-
-    if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
-      throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
-    }
-
-    if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
-      throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
-    }
-
-    // We treat moves as an add and a delete
-    ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
-    int[] indicesToRemove = new int[numToMove + numToRemove];
-    int[] tagsToRemove = new int[indicesToRemove.length];
-    int[] tagsToDelete = new int[numToRemove];
-
-    if (numToMove > 0) {
-      Assertions.assertNotNull(moveFrom);
-      Assertions.assertNotNull(moveTo);
-      for (int i = 0; i < numToMove; i++) {
-        int moveFromIndex = moveFrom.getInt(i);
-        int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
-        viewsToAdd[i] = new ViewAtIndex(
-            tagToMove,
-            moveTo.getInt(i));
-        indicesToRemove[i] = moveFromIndex;
-        tagsToRemove[i] = tagToMove;
-      }
-    }
-
-    if (numToAdd > 0) {
-      Assertions.assertNotNull(addChildTags);
-      Assertions.assertNotNull(addAtIndices);
-      for (int i = 0; i < numToAdd; i++) {
-        int viewTagToAdd = addChildTags.getInt(i);
-        int indexToAddAt = addAtIndices.getInt(i);
-        viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
-      }
-    }
-
-    if (numToRemove > 0) {
-      Assertions.assertNotNull(removeFrom);
-      for (int i = 0; i < numToRemove; i++) {
-        int indexToRemove = removeFrom.getInt(i);
-        int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
-        indicesToRemove[numToMove + i] = indexToRemove;
-        tagsToRemove[numToMove + i] = tagToRemove;
-        tagsToDelete[i] = tagToRemove;
-      }
-    }
-
-    // NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
-    // moveTo and addAt are both relative to the final state of the View's children.
-    //
-    // 1) Sort the views to add and indices to remove by index
-    // 2) Iterate the indices being removed from high to low and remove them. Going high to low
-    //    makes sure we remove the correct index when there are multiple to remove.
-    // 3) Iterate the views being added by index low to high and add them. Like the view removal,
-    //    iteration direction is important to preserve the correct index.
-
-    Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
-    Arrays.sort(indicesToRemove);
-
-    // Apply changes to CSSNode hierarchy
-    int lastIndexRemoved = -1;
-    for (int i = indicesToRemove.length - 1; i >= 0; i--) {
-      int indexToRemove = indicesToRemove[i];
-      if (indexToRemove == lastIndexRemoved) {
-        throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
-            + viewTag);
-      }
-      cssNodeToManage.removeChildAt(indicesToRemove[i]);
-      lastIndexRemoved = indicesToRemove[i];
-    }
-
-    for (int i = 0; i < viewsToAdd.length; i++) {
-      ViewAtIndex viewAtIndex = viewsToAdd[i];
-      ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
-      if (cssNodeToAdd == null) {
-        throw new IllegalViewOperationException("Trying to add unknown view tag: "
-            + viewAtIndex.mTag);
-      }
-      cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
-    }
-
-    if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
-      mNativeViewHierarchyOptimizer.handleManageChildren(
-          cssNodeToManage,
-          indicesToRemove,
-          tagsToRemove,
-          viewsToAdd,
-          tagsToDelete);
-    }
-
-    for (int i = 0; i < tagsToDelete.length; i++) {
-      removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
-    }
-  }
-
-  private void removeShadowNode(ReactShadowNode nodeToRemove) {
-    mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove);
-    mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag());
-    for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) {
-      removeShadowNode(nodeToRemove.getChildAt(i));
-    }
-    nodeToRemove.removeAllChildren();
+    mUIImplementation.manageChildren(
+        viewTag,
+        moveFrom,
+        moveTo,
+        addChildTags,
+        addAtIndices,
+        removeFrom);
   }
 
   /**
@@ -419,35 +230,7 @@ private void removeShadowNode(ReactShadowNode nodeToRemove) {
    */
   @ReactMethod
   public void replaceExistingNonRootView(int oldTag, int newTag) {
-    if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) {
-      throw new IllegalViewOperationException("Trying to add or replace a root tag!");
-    }
-
-    ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag);
-    if (oldNode == null) {
-      throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag);
-    }
-
-    ReactShadowNode parent = oldNode.getParent();
-    if (parent == null) {
-      throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag);
-    }
-
-    int oldIndex = parent.indexOf(oldNode);
-    if (oldIndex < 0) {
-      throw new IllegalStateException("Didn't find child tag in parent");
-    }
-
-    WritableArray tagsToAdd = Arguments.createArray();
-    tagsToAdd.pushInt(newTag);
-
-    WritableArray addAtIndices = Arguments.createArray();
-    addAtIndices.pushInt(oldIndex);
-
-    WritableArray indicesToRemove = Arguments.createArray();
-    indicesToRemove.pushInt(oldIndex);
-
-    manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove);
+    mUIImplementation.replaceExistingNonRootView(oldTag, newTag);
   }
 
   /**
@@ -458,18 +241,7 @@ public void replaceExistingNonRootView(int oldTag, int newTag) {
    */
   @ReactMethod
   public void removeSubviewsFromContainerWithID(int containerTag) {
-    ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag);
-    if (containerNode == null) {
-      throw new IllegalViewOperationException(
-          "Trying to remove subviews of an unknown view tag: " + containerTag);
-    }
-
-    WritableArray indicesToRemove = Arguments.createArray();
-    for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) {
-      indicesToRemove.pushInt(childIndex);
-    }
-
-    manageChildren(containerTag, null, null, null, null, indicesToRemove);
+    mUIImplementation.removeSubviewsFromContainerWithID(containerTag);
   }
 
   /**
@@ -477,12 +249,18 @@ public void removeSubviewsFromContainerWithID(int containerTag) {
    * via an async callback.
    */
   @ReactMethod
-  public void measure(final int reactTag, final Callback callback) {
-    // This method is called by the implementation of JS touchable interface (see Touchable.js for
-    // more details) at the moment of touch activation. That is after user starts the gesture from
-    // a touchable view with a given reactTag, or when user drag finger back into the press
-    // activation area of a touchable view that have been activated before.
-    mOperationsQueue.enqueueMeasure(reactTag, callback);
+  public void measure(int reactTag, Callback callback) {
+    mUIImplementation.measure(reactTag, callback);
+  }
+
+  /**
+   * Determines the location on screen, width, and height of the given view relative to the device
+   * screen and returns the values via an async callback.  This is the absolute position including
+   * things like the status bar
+   */
+  @ReactMethod
+  public void measureInWindow(int reactTag, Callback callback) {
+    mUIImplementation.measureInWindow(reactTag, callback);
   }
 
   /**
@@ -501,16 +279,7 @@ public void measureLayout(
       int ancestorTag,
       Callback errorCallback,
       Callback successCallback) {
-    try {
-      measureLayout(tag, ancestorTag, mMeasureBuffer);
-      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
-      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
-      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
-      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
-      successCallback.invoke(relativeX, relativeY, width, height);
-    } catch (IllegalViewOperationException e) {
-      errorCallback.invoke(e.getMessage());
-    }
+    mUIImplementation.measureLayout(tag, ancestorTag, errorCallback, successCallback);
   }
 
   /**
@@ -525,94 +294,7 @@ public void measureLayoutRelativeToParent(
       int tag,
       Callback errorCallback,
       Callback successCallback) {
-    try {
-      measureLayoutRelativeToParent(tag, mMeasureBuffer);
-      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
-      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
-      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
-      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
-      successCallback.invoke(relativeX, relativeY, width, height);
-    } catch (IllegalViewOperationException e) {
-      errorCallback.invoke(e.getMessage());
-    }
-  }
-
-  private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) {
-    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
-    ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag);
-    if (node == null || ancestor == null) {
-      throw new IllegalViewOperationException(
-          "Tag " + (node == null ? tag : ancestorTag) + " does not exist");
-    }
-
-    if (node != ancestor) {
-      ReactShadowNode currentParent = node.getParent();
-      while (currentParent != ancestor) {
-        if (currentParent == null) {
-          throw new IllegalViewOperationException(
-              "Tag " + ancestorTag + " is not an ancestor of tag " + tag);
-        }
-        currentParent = currentParent.getParent();
-      }
-    }
-
-    measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer);
-  }
-
-  private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) {
-    ReactShadowNode node = mShadowNodeRegistry.getNode(tag);
-    if (node == null) {
-      throw new IllegalViewOperationException("No native view for tag " + tag + " exists!");
-    }
-    ReactShadowNode parent = node.getParent();
-    if (parent == null) {
-      throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!");
-    }
-
-    measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer);
-  }
-
-  private void measureLayoutRelativeToVerifiedAncestor(
-      ReactShadowNode node,
-      ReactShadowNode ancestor,
-      int[] outputBuffer) {
-    int offsetX = 0;
-    int offsetY = 0;
-    if (node != ancestor) {
-      offsetX = Math.round(node.getLayoutX());
-      offsetY = Math.round(node.getLayoutY());
-      ReactShadowNode current = node.getParent();
-      while (current != ancestor) {
-        Assertions.assertNotNull(current);
-        assertNodeDoesNotNeedCustomLayoutForChildren(current);
-        offsetX += Math.round(current.getLayoutX());
-        offsetY += Math.round(current.getLayoutY());
-        current = current.getParent();
-      }
-      assertNodeDoesNotNeedCustomLayoutForChildren(ancestor);
-    }
-
-    outputBuffer[0] = offsetX;
-    outputBuffer[1] = offsetY;
-    outputBuffer[2] = node.getScreenWidth();
-    outputBuffer[3] = node.getScreenHeight();
-  }
-
-  private void assertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) {
-    ViewManager viewManager = Assertions.assertNotNull(mViewManagers.get(node.getViewClass()));
-    ViewGroupManager viewGroupManager;
-    if (viewManager instanceof ViewGroupManager) {
-      viewGroupManager = (ViewGroupManager) viewManager;
-    } else {
-      throw new IllegalViewOperationException("Trying to use view " + node.getViewClass() +
-          " as a parent, but its Manager doesn't extends ViewGroupManager");
-    }
-    if (viewGroupManager != null && viewGroupManager.needsCustomLayoutForChildren()) {
-      throw new IllegalViewOperationException(
-          "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to" +
-              " an ancestor that requires custom layout for it's children (" + node.getViewClass() +
-              "). Use measure instead.");
-    }
+    mUIImplementation.measureLayoutRelativeToParent(tag, errorCallback, successCallback);
   }
 
   /**
@@ -631,7 +313,7 @@ public void findSubviewIn(
       final int reactTag,
       final ReadableArray point,
       final Callback callback) {
-    mOperationsQueue.enqueueFindTargetForTouch(
+    mUIImplementation.findSubviewIn(
         reactTag,
         Math.round(PixelUtil.toPixelFromDIP(point.getDouble(0))),
         Math.round(PixelUtil.toPixelFromDIP(point.getDouble(1))),
@@ -642,47 +324,36 @@ public void findSubviewIn(
    * Registers a new Animation that can then be added to a View using {@link #addAnimation}.
    */
   public void registerAnimation(Animation animation) {
-    mOperationsQueue.enqueueRegisterAnimation(animation);
+    mUIImplementation.registerAnimation(animation);
   }
 
   /**
    * Adds an Animation previously registered with {@link #registerAnimation} to a View and starts it
    */
-  public void addAnimation(final int reactTag, final int animationID, final Callback onSuccess) {
-    assertViewExists(reactTag, "addAnimation");
-    mOperationsQueue.enqueueAddAnimation(reactTag, animationID, onSuccess);
+  public void addAnimation(int reactTag, int animationID, Callback onSuccess) {
+    mUIImplementation.addAnimation(reactTag, animationID, onSuccess);
   }
 
   /**
    * Removes an existing Animation, canceling it if it was in progress.
    */
   public void removeAnimation(int reactTag, int animationID) {
-    assertViewExists(reactTag, "removeAnimation");
-    mOperationsQueue.enqueueRemoveAnimation(animationID);
+    mUIImplementation.removeAnimation(reactTag, animationID);
   }
 
   @ReactMethod
   public void setJSResponder(int reactTag, boolean blockNativeResponder) {
-    assertViewExists(reactTag, "setJSResponder");
-    ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag);
-    while (node.isVirtual() || node.isLayoutOnly()) {
-      node = node.getParent();
-    }
-    mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder);
+    mUIImplementation.setJSResponder(reactTag, blockNativeResponder);
   }
 
   @ReactMethod
   public void clearJSResponder() {
-    mOperationsQueue.enqueueClearJSResponder();
+    mUIImplementation.clearJSResponder();
   }
 
   @ReactMethod
-  public void dispatchViewManagerCommand(
-      int reactTag,
-      int commandId,
-      ReadableArray commandArgs) {
-    assertViewExists(reactTag, "dispatchViewManagerCommand");
-    mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
+  public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {
+    mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
   }
 
   /**
@@ -696,34 +367,44 @@ public void dispatchViewManagerCommand(
    *        no arguments if the menu is dismissed
    */
   @ReactMethod
-  public void showPopupMenu(
-      int reactTag,
-      ReadableArray items,
-      Callback error,
-      Callback success) {
-    assertViewExists(reactTag, "showPopupMenu");
-    mOperationsQueue.enqueueShowPopupMenu(reactTag, items, error, success);
+  public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
+    mUIImplementation.showPopupMenu(reactTag, items, error, success);
   }
 
+  /**
+   * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled
+   * explicitly in order to avoid regression in existing application written for iOS using this API.
+   *
+   * Warning : This method will be removed in future version of React Native, and layout animation
+   * will be enabled by default, so always check for its existence before invoking it.
+   *
+   * TODO(9139831) : remove this method once layout animation is fully stable.
+   *
+   * @param enabled whether layout animation is enabled or not
+   */
   @ReactMethod
-  public void setMainScrollViewTag(int reactTag) {
-    // TODO(6588266): Implement if required
+  public void setLayoutAnimationEnabledExperimental(boolean enabled) {
+    mUIImplementation.setLayoutAnimationEnabledExperimental(enabled);
   }
 
+  /**
+   * Configure an animation to be used for the native layout changes, and native views
+   * creation. The animation will only apply during the current batch operations.
+   *
+   * TODO(7728153) : animating view deletion is currently not supported.
+   * TODO(7613721) : callbacks are not supported, this feature will likely be killed.
+   *
+   * @param config the configuration of the animation for view addition/removal/update.
+   * @param success will be called when the animation completes, or when the animation get
+   *        interrupted. In this case, callback parameter will be false.
+   * @param error will be called if there was an error processing the animation
+   */
   @ReactMethod
   public void configureNextLayoutAnimation(
       ReadableMap config,
-      Callback successCallback,
-      Callback errorCallback) {
-    // TODO(6588266): Implement if required
-  }
-
-  private void assertViewExists(int reactTag, String operationNameForExceptionMessage) {
-    if (mShadowNodeRegistry.getNode(reactTag) == null) {
-      throw new IllegalViewOperationException(
-          "Unable to execute operation " + operationNameForExceptionMessage + " on view with " +
-              "tag: " + reactTag + ", since the view does not exists");
-    }
+      Callback success,
+      Callback error) {
+    mUIImplementation.configureNextLayoutAnimation(config, success, error);
   }
 
   /**
@@ -749,107 +430,23 @@ public void onBatchComplete() {
           .arg("BatchId", batchId)
           .flush();
     try {
-      dispatchViewUpdates(batchId);
+      mUIImplementation.dispatchViewUpdates(mEventDispatcher, batchId);
     } finally {
       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     }
   }
 
-  public void setUiManagerDebugListener(@Nullable NotThreadSafeUiManagerDebugListener listener) {
-    mUiManagerDebugListener = listener;
+  public void setViewHierarchyUpdateDebugListener(
+      @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
+    mUIImplementation.setViewHierarchyUpdateDebugListener(listener);
   }
 
   public EventDispatcher getEventDispatcher() {
     return mEventDispatcher;
   }
 
-  private void dispatchViewUpdates(final int batchId) {
-    for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
-      int tag = mShadowNodeRegistry.getRootTag(i);
-      ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
-      notifyOnBeforeLayoutRecursive(cssRoot);
-      cssRoot.calculateLayout(mLayoutContext);
-      applyUpdatesRecursive(cssRoot, 0f, 0f);
-    }
-
-    mNativeViewHierarchyOptimizer.onBatchComplete();
-    mOperationsQueue.dispatchViewUpdates(batchId);
-  }
-
-  private void notifyOnBeforeLayoutRecursive(ReactShadowNode cssNode) {
-    if (!cssNode.hasUpdates()) {
-      return;
-    }
-    for (int i = 0; i < cssNode.getChildCount(); i++) {
-      notifyOnBeforeLayoutRecursive(cssNode.getChildAt(i));
-    }
-    cssNode.onBeforeLayout();
-  }
-
-  private void applyUpdatesRecursive(ReactShadowNode cssNode, float absoluteX, float absoluteY) {
-    if (!cssNode.hasUpdates()) {
-      return;
-    }
-
-    if (!cssNode.isVirtualAnchor()) {
-      for (int i = 0; i < cssNode.getChildCount(); i++) {
-        applyUpdatesRecursive(
-            cssNode.getChildAt(i),
-            absoluteX + cssNode.getLayoutX(),
-            absoluteY + cssNode.getLayoutY());
-      }
-    }
-
-    int tag = cssNode.getReactTag();
-    if (!mShadowNodeRegistry.isRootNode(tag)) {
-      cssNode.dispatchUpdates(
-          absoluteX,
-          absoluteY,
-          mOperationsQueue,
-          mNativeViewHierarchyOptimizer);
-
-      // notify JS about layout event if requested
-      if (cssNode.shouldNotifyOnLayout()) {
-        mEventDispatcher.dispatchEvent(
-            OnLayoutEvent.obtain(
-                tag,
-                cssNode.getScreenX(),
-                cssNode.getScreenY(),
-                cssNode.getScreenWidth(),
-                cssNode.getScreenHeight()));
-      }
-    }
-    cssNode.markUpdateSeen();
-  }
-
-  /* package */ void notifyOnViewHierarchyUpdateEnqueued() {
-    if (mUiManagerDebugListener != null) {
-      mUiManagerDebugListener.onViewHierarchyUpdateEnqueued();
-    }
-  }
-
-  /* package */ void notifyOnViewHierarchyUpdateFinished() {
-    if (mUiManagerDebugListener != null) {
-      mUiManagerDebugListener.onViewHierarchyUpdateFinished();
-    }
-  }
-
   @ReactMethod
   public void sendAccessibilityEvent(int tag, int eventType) {
-    mOperationsQueue.enqueueSendAccessibilityEvent(tag, eventType);
-  }
-
-  /**
-   * Get the first non-virtual (i.e. native) parent view tag of the react view with the passed tag.
-   * If the passed tag represents a non-virtual view, the same tag is returned. If the passed tag
-   * doesn't map to a react view, or a non-virtual parent cannot be found, -1 is returned.
-   */
-  /* package */ int getNonVirtualParent(int reactTag) {
-    ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag);
-    while (node != null && node.isVirtual()) {
-      node = node.getParent();
-    }
-    return node == null ? -1 : node.getReactTag();
+    mUIImplementation.sendAccessibilityEvent(tag, eventType);
   }
-
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java
index 3287ebf7854c..b8363917e12c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java
@@ -12,7 +12,6 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import android.text.InputType;
 import android.util.DisplayMetrics;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
@@ -80,7 +79,7 @@
         .build();
   }
 
-  public static Map<String, Object> getConstants(DisplayMetrics displayMetrics) {
+  public static Map<String, Object> getConstants() {
     HashMap<String, Object> constants = new HashMap<String, Object>();
     constants.put(
         "UIView",
@@ -92,20 +91,8 @@ public static Map<String, Object> getConstants(DisplayMetrics displayMetrics) {
                 "ScaleAspectFill",
                 ImageView.ScaleType.CENTER_CROP.ordinal())));
 
-    constants.put(
-        "UIText",
-        MapBuilder.of(
-            "AutocapitalizationType",
-            MapBuilder.of(
-                "none",
-                InputType.TYPE_CLASS_TEXT,
-                "characters",
-                InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
-                "words",
-                InputType.TYPE_TEXT_FLAG_CAP_WORDS,
-                "sentences",
-                InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)));
-
+    DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics();
+    DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
     constants.put(
         "Dimensions",
         MapBuilder.of(
@@ -120,7 +107,19 @@ public static Map<String, Object> getConstants(DisplayMetrics displayMetrics) {
                 "fontScale",
                 displayMetrics.scaledDensity,
                 "densityDpi",
-                displayMetrics.densityDpi)));
+                displayMetrics.densityDpi),
+        "screenPhysicalPixels",
+        MapBuilder.of(
+            "width",
+            screenDisplayMetrics.widthPixels,
+            "height",
+            screenDisplayMetrics.heightPixels,
+            "scale",
+            screenDisplayMetrics.density,
+            "fontScale",
+            screenDisplayMetrics.scaledDensity,
+            "densityDpi",
+            screenDisplayMetrics.densityDpi)));
 
     constants.put(
         "StyleConstants",
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java
index bce7c37361b2..ffee1cea45d2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstantsHelper.java
@@ -9,12 +9,9 @@
 
 package com.facebook.react.uimanager;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import android.util.DisplayMetrics;
-
 import com.facebook.react.common.MapBuilder;
 
 /**
@@ -39,10 +36,8 @@
    * {@link UIManagerModuleConstants}.
    * TODO(6845124): Create a test for this
    */
-  /* package */ static Map<String, Object> createConstants(
-      DisplayMetrics displayMetrics,
-      List<ViewManager> viewManagers) {
-    Map<String, Object> constants = UIManagerModuleConstants.getConstants(displayMetrics);
+  /* package */ static Map<String, Object> createConstants(List<ViewManager> viewManagers) {
+    Map<String, Object> constants = UIManagerModuleConstants.getConstants();
     Map bubblingEventTypesConstants = UIManagerModuleConstants.getBubblingEventTypeConstants();
     Map directEventTypesConstants = UIManagerModuleConstants.getDirectEventTypeConstants();
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
index 739558938334..b3b931e0f8ef 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
@@ -13,13 +13,19 @@
 import javax.annotation.concurrent.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 import com.facebook.react.animation.Animation;
 import com.facebook.react.animation.AnimationRegistry;
 import com.facebook.react.bridge.Callback;
 import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.SoftAssertions;
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
 import com.facebook.systrace.Systrace;
 import com.facebook.systrace.SystraceMessage;
 
@@ -41,7 +47,7 @@ public class UIViewOperationQueue {
   /**
    * A mutation or animation operation on the view hierarchy.
    */
-  private interface UIOperation {
+  protected interface UIOperation {
 
     void execute();
   }
@@ -72,9 +78,9 @@ public void execute() {
 
   private final class UpdatePropertiesOperation extends ViewOperation {
 
-    private final CatalystStylesDiffMap mProps;
+    private final ReactStylesDiffMap mProps;
 
-    private UpdatePropertiesOperation(int tag, CatalystStylesDiffMap props) {
+    private UpdatePropertiesOperation(int tag, ReactStylesDiffMap props) {
       super(tag);
       mProps = props;
     }
@@ -107,35 +113,39 @@ public UpdateLayoutOperation(
       mY = y;
       mWidth = width;
       mHeight = height;
+      Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
     }
 
     @Override
     public void execute() {
+      Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "updateLayout", mTag);
       mNativeViewHierarchyManager.updateLayout(mParentTag, mTag, mX, mY, mWidth, mHeight);
     }
   }
 
   private final class CreateViewOperation extends ViewOperation {
 
-    private final int mRootViewTagForContext;
+    private final ThemedReactContext mThemedContext;
     private final String mClassName;
-    private final @Nullable CatalystStylesDiffMap mInitialProps;
+    private final @Nullable ReactStylesDiffMap mInitialProps;
 
     public CreateViewOperation(
-        int rootViewTagForContext,
+        ThemedReactContext themedContext,
         int tag,
         String className,
-        @Nullable CatalystStylesDiffMap initialProps) {
+        @Nullable ReactStylesDiffMap initialProps) {
       super(tag);
-      mRootViewTagForContext = rootViewTagForContext;
+      mThemedContext = themedContext;
       mClassName = className;
       mInitialProps = initialProps;
+      Systrace.startAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
     }
 
     @Override
     public void execute() {
+      Systrace.endAsyncFlow(Systrace.TRACE_TAG_REACT_VIEW, "createView", mTag);
       mNativeViewHierarchyManager.createView(
-          mRootViewTagForContext,
+          mThemedContext,
           mTag,
           mClassName,
           mInitialProps);
@@ -317,6 +327,32 @@ public void execute() {
     }
   }
 
+  private class SetLayoutAnimationEnabledOperation implements UIOperation {
+    private final boolean mEnabled;
+
+    private SetLayoutAnimationEnabledOperation(final boolean enabled) {
+      mEnabled = enabled;
+    }
+
+    @Override
+    public void execute() {
+      mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled);
+    }
+  }
+
+  private class ConfigureLayoutAnimationOperation implements UIOperation {
+    private final ReadableMap mConfig;
+
+    private ConfigureLayoutAnimationOperation(final ReadableMap config) {
+      mConfig = config;
+    }
+
+    @Override
+    public void execute() {
+      mNativeViewHierarchyManager.configureLayoutAnimation(mConfig);
+    }
+  }
+
   private final class MeasureOperation implements UIOperation {
 
     private final int mReactTag;
@@ -349,6 +385,38 @@ public void execute() {
     }
   }
 
+  private final class MeasureInWindowOperation implements UIOperation {
+
+    private final int mReactTag;
+    private final Callback mCallback;
+
+    private MeasureInWindowOperation(
+        final int reactTag,
+        final Callback callback) {
+      super();
+      mReactTag = reactTag;
+      mCallback = callback;
+    }
+
+    @Override
+    public void execute() {
+      try {
+        mNativeViewHierarchyManager.measureInWindow(mReactTag, mMeasureBuffer);
+      } catch (NoSuchNativeViewException e) {
+        // Invoke with no args to signal failure and to allow JS to clean up the callback
+        // handle.
+        mCallback.invoke();
+        return;
+      }
+
+      float x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
+      float y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
+      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
+      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
+      mCallback.invoke(x, y, width, height);
+    }
+  }
+
   private ArrayList<UIOperation> mOperations = new ArrayList<>();
 
   private final class FindTargetForTouchOperation implements UIOperation {
@@ -386,11 +454,10 @@ public void execute() {
       final float containerX = (float) mMeasureBuffer[0];
       final float containerY = (float) mMeasureBuffer[1];
 
-      final int touchTargetReactTag = mUIManagerModule.getNonVirtualParent(
-          mNativeViewHierarchyManager.findTargetTagForTouch(
-            mReactTag,
-            mTargetX,
-            mTargetY));
+      final int touchTargetReactTag = mNativeViewHierarchyManager.findTargetTagForTouch(
+          mReactTag,
+          mTargetX,
+          mTargetY);
 
       try {
         mNativeViewHierarchyManager.measure(
@@ -424,31 +491,70 @@ public void execute() {
     }
   }
 
-  private final UIManagerModule mUIManagerModule;
   private final NativeViewHierarchyManager mNativeViewHierarchyManager;
   private final AnimationRegistry mAnimationRegistry;
 
   private final Object mDispatchRunnablesLock = new Object();
   private final DispatchUIFrameCallback mDispatchUIFrameCallback;
+  private final ReactApplicationContext mReactApplicationContext;
 
   @GuardedBy("mDispatchRunnablesLock")
   private final ArrayList<Runnable> mDispatchUIRunnables = new ArrayList<>();
 
-  /* package */ UIViewOperationQueue(
+  private @Nullable NotThreadSafeViewHierarchyUpdateDebugListener mViewHierarchyUpdateDebugListener;
+
+  public UIViewOperationQueue(
       ReactApplicationContext reactContext,
-      UIManagerModule uiManagerModule,
-      NativeViewHierarchyManager nativeViewHierarchyManager,
-      AnimationRegistry animationRegistry) {
-    mUIManagerModule = uiManagerModule;
+      NativeViewHierarchyManager nativeViewHierarchyManager) {
     mNativeViewHierarchyManager = nativeViewHierarchyManager;
-    mAnimationRegistry = animationRegistry;
+    mAnimationRegistry = nativeViewHierarchyManager.getAnimationRegistry();
     mDispatchUIFrameCallback = new DispatchUIFrameCallback(reactContext);
+    mReactApplicationContext = reactContext;
+  }
+
+  public void setViewHierarchyUpdateDebugListener(
+      @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
+    mViewHierarchyUpdateDebugListener = listener;
   }
 
   public boolean isEmpty() {
     return mOperations.isEmpty();
   }
 
+  public void addRootView(
+      final int tag,
+      final SizeMonitoringFrameLayout rootView,
+      final ThemedReactContext themedRootContext) {
+    if (UiThreadUtil.isOnUiThread()) {
+      mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext);
+    } else {
+      final Semaphore semaphore = new Semaphore(0);
+      mReactApplicationContext.runOnUiQueueThread(
+          new Runnable() {
+            @Override
+            public void run() {
+              mNativeViewHierarchyManager.addRootView(tag, rootView, themedRootContext);
+              semaphore.release();
+            }
+          });
+      try {
+        SoftAssertions.assertCondition(
+            semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS),
+            "Timed out adding root view");
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  /**
+   * Enqueues a UIOperation to be executed in UI thread. This method should only be used by a
+   * subclass to support UIOperations not provided by UIViewOperationQueue.
+   */
+  protected void enqueueUIOperation(UIOperation operation) {
+    mOperations.add(operation);
+  }
+
   public void enqueueRemoveRootView(int rootViewTag) {
     mOperations.add(new RemoveRootViewOperation(rootViewTag));
   }
@@ -490,19 +596,19 @@ public void enqueueShowPopupMenu(
   }
 
   public void enqueueCreateView(
-      int rootViewTagForContext,
+      ThemedReactContext themedContext,
       int viewReactTag,
       String viewClassName,
-      @Nullable CatalystStylesDiffMap initialProps) {
+      @Nullable ReactStylesDiffMap initialProps) {
     mOperations.add(
         new CreateViewOperation(
-            rootViewTagForContext,
+            themedContext,
             viewReactTag,
             viewClassName,
             initialProps));
   }
 
-  public void enqueueUpdateProperties(int reactTag, String className, CatalystStylesDiffMap props) {
+  public void enqueueUpdateProperties(int reactTag, String className, ReactStylesDiffMap props) {
     mOperations.add(new UpdatePropertiesOperation(reactTag, props));
   }
 
@@ -541,6 +647,18 @@ public void enqueueRemoveAnimation(int animationID) {
     mOperations.add(new RemoveAnimationOperation(animationID));
   }
 
+  public void enqueueSetLayoutAnimationEnabled(
+      final boolean enabled) {
+    mOperations.add(new SetLayoutAnimationEnabledOperation(enabled));
+  }
+
+  public void enqueueConfigureLayoutAnimation(
+      final ReadableMap config,
+      final Callback onSuccess,
+      final Callback onError) {
+    mOperations.add(new ConfigureLayoutAnimationOperation(config));
+  }
+
   public void enqueueMeasure(
       final int reactTag,
       final Callback callback) {
@@ -548,6 +666,14 @@ public void enqueueMeasure(
         new MeasureOperation(reactTag, callback));
   }
 
+  public void enqueueMeasureInWindow(
+      final int reactTag,
+      final Callback callback) {
+    mOperations.add(
+        new MeasureInWindowOperation(reactTag, callback)
+    );
+  }
+
   public void enqueueFindTargetForTouch(
       final int reactTag,
       final float targetX,
@@ -569,7 +695,9 @@ public void enqueueSendAccessibilityEvent(int tag, int eventType) {
       mOperations = new ArrayList<>();
     }
 
-    mUIManagerModule.notifyOnViewHierarchyUpdateEnqueued();
+    if (mViewHierarchyUpdateDebugListener != null) {
+      mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateEnqueued();
+    }
 
     synchronized (mDispatchRunnablesLock) {
       mDispatchUIRunnables.add(
@@ -585,7 +713,13 @@ public void run() {
                      operations.get(i).execute();
                    }
                  }
-                 mUIManagerModule.notifyOnViewHierarchyUpdateFinished();
+
+                 // Clear layout animation, as animation only apply to current UI operations batch.
+                 mNativeViewHierarchyManager.clearLayoutAnimation();
+
+                 if (mViewHierarchyUpdateDebugListener != null) {
+                   mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished();
+                 }
                } finally {
                  Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
                }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java
index 4091eb8d8cbb..153f7b815f82 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.java
@@ -24,7 +24,7 @@ public LayoutShadowNode createShadowNodeInstance() {
   }
 
   @Override
-  public Class<LayoutShadowNode> getShadowNodeClass() {
+  public Class<? extends LayoutShadowNode> getShadowNodeClass() {
     return LayoutShadowNode.class;
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java
index f9b5e1293dad..fdfaed32fdb3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManager.java
@@ -16,30 +16,22 @@
 import android.view.View;
 
 import com.facebook.react.bridge.ReadableArray;
-import com.facebook.react.bridge.ReadableMap;
-import com.facebook.react.bridge.ReadableMapKeySetIterator;
-import com.facebook.react.touch.CatalystInterceptingViewGroup;
+import com.facebook.react.touch.ReactInterceptingViewGroup;
 import com.facebook.react.touch.JSResponderHandler;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
 
 /**
  * Class responsible for knowing how to create and update catalyst Views of a given type. It is also
  * responsible for creating and updating CSSNode subclasses used for calculating position and size
  * for the corresponding native view.
  */
+@ReactPropertyHolder
 public abstract class ViewManager<T extends View, C extends ReactShadowNode> {
 
-  public final void updateProperties(T viewToUpdate, CatalystStylesDiffMap props) {
-    Map<String, ViewManagersPropertyCache.PropSetter> propSetters =
-        ViewManagersPropertyCache.getNativePropSettersForViewManagerClass(getClass());
-    ReadableMap propMap = props.mBackingMap;
-    ReadableMapKeySetIterator iterator = propMap.keySetIterator();
-    while (iterator.hasNextKey()) {
-      String key = iterator.nextKey();
-      ViewManagersPropertyCache.PropSetter setter = propSetters.get(key);
-      if (setter != null) {
-        setter.updateViewProp(this, viewToUpdate, props);
-      }
-    }
+  public final void updateProperties(T viewToUpdate, ReactStylesDiffMap props) {
+    ViewManagerPropertyUpdater.updateProps(this, viewToUpdate, props);
     onAfterUpdateTransaction(viewToUpdate);
   }
 
@@ -51,8 +43,8 @@ public final T createView(
       JSResponderHandler jsResponderHandler) {
     T view = createViewInstance(reactContext);
     addEventEmitters(reactContext, view);
-    if (view instanceof CatalystInterceptingViewGroup) {
-      ((CatalystInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
+    if (view instanceof ReactInterceptingViewGroup) {
+      ((ReactInterceptingViewGroup) view).setOnInterceptTouchEventListener(jsResponderHandler);
     }
     return view;
   }
@@ -92,7 +84,7 @@ public final T createView(
    * Called when view is detached from view hierarchy and allows for some additional cleanup by
    * the {@link ViewManager} subclass.
    */
-  public void onDropViewInstance(ThemedReactContext reactContext, T view) {
+  public void onDropViewInstance(T view) {
   }
 
   /**
@@ -204,6 +196,6 @@ public void receiveCommand(T root, int commandId, @Nullable ReadableArray args)
   }
 
   public Map<String, String> getNativeProps() {
-    return ViewManagersPropertyCache.getNativePropsForView(getClass(), getShadowNodeClass());
+    return ViewManagerPropertyUpdater.getNativeProps(getClass(), getShadowNodeClass());
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.java
new file mode 100644
index 000000000000..b88bc1828a30
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.java
@@ -0,0 +1,163 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.view.View;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
+
+public class ViewManagerPropertyUpdater {
+  public interface Settable {
+    Map<String, String> getProperties();
+  }
+
+  public interface ViewManagerSetter<T extends ViewManager, V extends View> extends Settable {
+    void setProperty(T manager, V view, String name, ReactStylesDiffMap props);
+  }
+
+  public interface ShadowNodeSetter<T extends ReactShadowNode> extends Settable {
+    void setProperty(T node, String name, ReactStylesDiffMap props);
+  }
+
+  private static final String TAG = "ViewManagerPropertyUpdater";
+
+  private static final Map<Class<?>, ViewManagerSetter<?, ?>> VIEW_MANAGER_SETTER_MAP =
+      new HashMap<>();
+  private static final Map<Class<?>, ShadowNodeSetter<?>> SHADOW_NODE_SETTER_MAP = new HashMap<>();
+
+  public static <T extends ViewManager, V extends View> void updateProps(
+      T manager,
+      V v,
+      ReactStylesDiffMap props) {
+    ViewManagerSetter<T, V> setter = findManagerSetter(manager.getClass());
+    ReadableMap propMap = props.mBackingMap;
+    ReadableMapKeySetIterator iterator = propMap.keySetIterator();
+    while (iterator.hasNextKey()) {
+      String key = iterator.nextKey();
+      setter.setProperty(manager, v, key, props);
+    }
+  }
+
+  public static <T extends ReactShadowNode> void updateProps(T node, ReactStylesDiffMap props) {
+    ShadowNodeSetter<T> setter = findNodeSetter(node.getClass());
+    ReadableMap propMap = props.mBackingMap;
+    ReadableMapKeySetIterator iterator = propMap.keySetIterator();
+    while (iterator.hasNextKey()) {
+      String key = iterator.nextKey();
+      setter.setProperty(node, key, props);
+    }
+  }
+
+  public static Map<String, String> getNativeProps(
+      Class<? extends ViewManager> viewManagerTopClass,
+      Class<? extends ReactShadowNode> shadowNodeTopClass) {
+    Map<String, String> props = new HashMap<>();
+    props.putAll(findManagerSetter(viewManagerTopClass).getProperties());
+    props.putAll(findNodeSetter(shadowNodeTopClass).getProperties());
+    return props;
+  }
+
+  private static <T extends ViewManager, V extends View> ViewManagerSetter<T, V> findManagerSetter(
+      Class<? extends ViewManager> managerClass) {
+    @SuppressWarnings("unchecked")
+    ViewManagerSetter<T, V> setter =
+        (ViewManagerSetter<T, V>) VIEW_MANAGER_SETTER_MAP.get(managerClass);
+    if (setter == null) {
+      setter = findGeneratedSetter(managerClass);
+      if (setter == null) {
+        setter = new FallbackViewManagerSetter<>(managerClass);
+      }
+      VIEW_MANAGER_SETTER_MAP.put(managerClass, setter);
+    }
+
+    return setter;
+  }
+
+  private static <T extends ReactShadowNode> ShadowNodeSetter<T> findNodeSetter(
+      Class<? extends ReactShadowNode> nodeClass) {
+    @SuppressWarnings("unchecked")
+    ShadowNodeSetter<T> setter = (ShadowNodeSetter<T>) SHADOW_NODE_SETTER_MAP.get(nodeClass);
+    if (setter == null) {
+      setter = findGeneratedSetter(nodeClass);
+      if (setter == null) {
+        setter = new FallbackShadowNodeSetter<>(nodeClass);
+      }
+      SHADOW_NODE_SETTER_MAP.put(nodeClass, setter);
+    }
+
+    return setter;
+  }
+
+  private static <T> T findGeneratedSetter(Class<?> cls) {
+    String clsName = cls.getName();
+    try {
+      Class<?> setterClass = Class.forName(clsName + "$$PropsSetter");
+      //noinspection unchecked
+      return (T) setterClass.newInstance();
+    } catch (ClassNotFoundException e) {
+      FLog.w(TAG, "Could not find generated setter for " + cls);
+      return null;
+    } catch (InstantiationException | IllegalAccessException e) {
+      throw new RuntimeException("Unable to instantiate methods getter for " + clsName, e);
+    }
+  }
+
+  private static class FallbackViewManagerSetter<T extends ViewManager, V extends View>
+      implements ViewManagerSetter<T, V> {
+    private final Map<String, ViewManagersPropertyCache.PropSetter> mPropSetters;
+
+    private FallbackViewManagerSetter(Class<? extends ViewManager> viewManagerClass) {
+      mPropSetters =
+          ViewManagersPropertyCache.getNativePropSettersForViewManagerClass(viewManagerClass);
+    }
+
+    @Override
+    public void setProperty(T manager, V v, String name, ReactStylesDiffMap props) {
+      ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
+      if (setter != null) {
+        setter.updateViewProp(manager, v, props);
+      }
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+      Map<String, String> nativeProps = new HashMap<>();
+      for (ViewManagersPropertyCache.PropSetter setter : mPropSetters.values()) {
+        nativeProps.put(setter.getPropName(), setter.getPropType());
+      }
+      return nativeProps;
+    }
+  }
+
+  private static class FallbackShadowNodeSetter<T extends ReactShadowNode>
+      implements ShadowNodeSetter<T> {
+    private final Map<String, ViewManagersPropertyCache.PropSetter> mPropSetters;
+
+    private FallbackShadowNodeSetter(Class<? extends ReactShadowNode> shadowNodeClass) {
+      mPropSetters =
+          ViewManagersPropertyCache.getNativePropSettersForShadowNodeClass(shadowNodeClass);
+    }
+
+    @Override
+    public void setProperty(ReactShadowNode node, String name, ReactStylesDiffMap props) {
+      ViewManagersPropertyCache.PropSetter setter = mPropSetters.get(name);
+      if (setter != null) {
+        setter.updateShadowNodeProp(node, props);
+      }
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+      Map<String, String> nativeProps = new HashMap<>();
+      for (ViewManagersPropertyCache.PropSetter setter : mPropSetters.values()) {
+        nativeProps.put(setter.getPropName(), setter.getPropType());
+      }
+      return nativeProps;
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java
index 2dffc8c26ca7..3dfacf1b387e 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerRegistry.java
@@ -17,7 +17,7 @@
  * Class that stores the mapping between native view name used in JS and the corresponding instance
  * of {@link ViewManager}.
  */
-/* package */ class ViewManagerRegistry {
+public class ViewManagerRegistry {
 
   private final Map<String, ViewManager> mViewManagers = new HashMap<>();
 
@@ -27,7 +27,7 @@ public ViewManagerRegistry(List<ViewManager> viewManagerList) {
     }
   }
 
-  /* package */ ViewManager get(String className) {
+  public ViewManager get(String className) {
     ViewManager viewManager = mViewManagers.get(className);
     if (viewManager != null) {
       return viewManager;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java
index c34430cc6480..063c999daedd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagersPropertyCache.java
@@ -5,6 +5,7 @@
 import javax.annotation.Nullable;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -14,6 +15,8 @@
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
 
 /**
  * This class is responsible for holding view manager property setters and is used in a process of
@@ -66,17 +69,19 @@ public String getPropType() {
     public void updateViewProp(
         ViewManager viewManager,
         View viewToUpdate,
-        CatalystStylesDiffMap props) {
+        ReactStylesDiffMap props) {
       try {
         if (mIndex == null) {
           VIEW_MGR_ARGS[0] = viewToUpdate;
           VIEW_MGR_ARGS[1] = extractProperty(props);
           mSetter.invoke(viewManager, VIEW_MGR_ARGS);
+          Arrays.fill(VIEW_MGR_ARGS, null);
         } else {
           VIEW_MGR_GROUP_ARGS[0] = viewToUpdate;
           VIEW_MGR_GROUP_ARGS[1] = mIndex;
           VIEW_MGR_GROUP_ARGS[2] = extractProperty(props);
           mSetter.invoke(viewManager, VIEW_MGR_GROUP_ARGS);
+          Arrays.fill(VIEW_MGR_GROUP_ARGS, null);
         }
       } catch (Throwable t) {
         FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t);
@@ -87,15 +92,17 @@ public void updateViewProp(
 
     public void updateShadowNodeProp(
         ReactShadowNode nodeToUpdate,
-        CatalystStylesDiffMap props) {
+        ReactStylesDiffMap props) {
       try {
         if (mIndex == null) {
           SHADOW_ARGS[0] = extractProperty(props);
           mSetter.invoke(nodeToUpdate, SHADOW_ARGS);
+          Arrays.fill(SHADOW_ARGS, null);
         } else {
           SHADOW_GROUP_ARGS[0] = mIndex;
           SHADOW_GROUP_ARGS[1] = extractProperty(props);
           mSetter.invoke(nodeToUpdate, SHADOW_GROUP_ARGS);
+          Arrays.fill(SHADOW_GROUP_ARGS, null);
         }
       } catch (Throwable t) {
         FLog.e(ViewManager.class, "Error while updating prop " + mPropName, t);
@@ -104,7 +111,7 @@ public void updateShadowNodeProp(
       }
     }
 
-    protected abstract @Nullable Object extractProperty(CatalystStylesDiffMap props);
+    protected abstract @Nullable Object extractProperty(ReactStylesDiffMap props);
   }
 
   private static class IntPropSetter extends PropSetter {
@@ -122,7 +129,7 @@ public IntPropSetter(ReactPropGroup prop, Method setter, int index, int defaultV
     }
 
     @Override
-    protected Object extractProperty(CatalystStylesDiffMap props) {
+    protected Object extractProperty(ReactStylesDiffMap props) {
       return props.getInt(mPropName, mDefaultValue);
     }
   }
@@ -136,8 +143,13 @@ public DoublePropSetter(ReactProp prop, Method setter, double defaultValue) {
       mDefaultValue = defaultValue;
     }
 
+    public DoublePropSetter(ReactPropGroup prop, Method setter, int index, double defaultValue) {
+      super(prop, "number", setter, index);
+      mDefaultValue = defaultValue;
+    }
+
     @Override
-    protected Object extractProperty(CatalystStylesDiffMap props) {
+    protected Object extractProperty(ReactStylesDiffMap props) {
       return props.getDouble(mPropName, mDefaultValue);
     }
   }
@@ -152,7 +164,7 @@ public BooleanPropSetter(ReactProp prop, Method setter, boolean defaultValue) {
     }
 
     @Override
-    protected Object extractProperty(CatalystStylesDiffMap props) {
+    protected Object extractProperty(ReactStylesDiffMap props) {
       return props.getBoolean(mPropName, mDefaultValue) ? Boolean.TRUE : Boolean.FALSE;
     }
   }
@@ -172,7 +184,7 @@ public FloatPropSetter(ReactPropGroup prop, Method setter, int index, float defa
     }
 
     @Override
-    protected Object extractProperty(CatalystStylesDiffMap props) {
+    protected Object extractProperty(ReactStylesDiffMap props) {
       return props.getFloat(mPropName, mDefaultValue);
     }
   }
@@ -184,7 +196,7 @@ public ArrayPropSetter(ReactProp prop, Method setter) {
     }
 
     @Override
-    protected @Nullable Object extractProperty(CatalystStylesDiffMap props) {
+    protected @Nullable Object extractProperty(ReactStylesDiffMap props) {
       return props.getArray(mPropName);
     }
   }
@@ -196,7 +208,7 @@ public MapPropSetter(ReactProp prop, Method setter) {
     }
 
     @Override
-    protected @Nullable Object extractProperty(CatalystStylesDiffMap props) {
+    protected @Nullable Object extractProperty(ReactStylesDiffMap props) {
       return props.getMap(mPropName);
     }
   }
@@ -208,7 +220,7 @@ public StringPropSetter(ReactProp prop, Method setter) {
     }
 
     @Override
-    protected @Nullable Object extractProperty(CatalystStylesDiffMap props) {
+    protected @Nullable Object extractProperty(ReactStylesDiffMap props) {
       return props.getString(mPropName);
     }
   }
@@ -220,7 +232,7 @@ public BoxedBooleanPropSetter(ReactProp prop, Method setter) {
     }
 
     @Override
-    protected @Nullable Object extractProperty(CatalystStylesDiffMap props) {
+    protected @Nullable Object extractProperty(ReactStylesDiffMap props) {
       if (!props.isNull(mPropName)) {
         return props.getBoolean(mPropName, /* ignored */ false) ? Boolean.TRUE : Boolean.FALSE;
       }
@@ -239,7 +251,7 @@ public BoxedIntPropSetter(ReactPropGroup prop, Method setter, int index) {
     }
 
     @Override
-    protected @Nullable Object extractProperty(CatalystStylesDiffMap props) {
+    protected @Nullable Object extractProperty(ReactStylesDiffMap props) {
       if (!props.isNull(mPropName)) {
         return props.getInt(mPropName, /* ignored */ 0);
       }
@@ -361,6 +373,12 @@ private static void createPropSetters(
             names[i],
             new FloatPropSetter(annotation, method, i, annotation.defaultFloat()));
       }
+    } else if (propTypeClass == double.class) {
+      for (int i = 0; i < names.length; i++) {
+        props.put(
+            names[i],
+            new DoublePropSetter(annotation, method, i, annotation.defaultDouble()));
+      }
     } else if (propTypeClass == Integer.class) {
       for (int i = 0; i < names.length; i++) {
         props.put(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
index 7e87a8419d85..3cef8895ac77 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java
@@ -70,12 +70,18 @@ public class ViewProps {
   public static final String ON = "on";
   public static final String RESIZE_MODE = "resizeMode";
   public static final String TEXT_ALIGN = "textAlign";
+  public static final String TEXT_ALIGN_VERTICAL = "textAlignVertical";
 
   public static final String BORDER_WIDTH = "borderWidth";
   public static final String BORDER_LEFT_WIDTH = "borderLeftWidth";
   public static final String BORDER_TOP_WIDTH = "borderTopWidth";
   public static final String BORDER_RIGHT_WIDTH = "borderRightWidth";
   public static final String BORDER_BOTTOM_WIDTH = "borderBottomWidth";
+  public static final String BORDER_RADIUS = "borderRadius";
+  public static final String BORDER_TOP_LEFT_RADIUS = "borderTopLeftRadius";
+  public static final String BORDER_TOP_RIGHT_RADIUS = "borderTopRightRadius";
+  public static final String BORDER_BOTTOM_LEFT_RADIUS = "borderBottomLeftRadius";
+  public static final String BORDER_BOTTOM_RIGHT_RADIUS = "borderBottomRightRadius";
   public static final int[] BORDER_SPACING_TYPES = {
       Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM
   };
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK
new file mode 100644
index 000000000000..6ec00fd33ec1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/BUCK
@@ -0,0 +1,16 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'annotations',
+  srcs = glob(['*.java']),
+  deps = [
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':annotations',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactProp.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactProp.java
similarity index 95%
rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactProp.java
rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactProp.java
index d387fa71b244..ada7fc7f6aeb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactProp.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactProp.java
@@ -1,6 +1,6 @@
 // Copyright 2004-present Facebook. All Rights Reserved.
 
-package com.facebook.react.uimanager;
+package com.facebook.react.uimanager.annotations;
 
 import javax.annotation.Nullable;
 
@@ -12,7 +12,8 @@
 
 /**
  * Use this annotation to annotate properties of native views that should be exposed to JS. This
- * annotation should only be used for setter methods of subclasses of {@link ViewManager}.
+ * annotation should only be used for setter methods of subclasses of
+ * {@link com.facebook.react.uimanager.ViewManager}.
  *
  * Each annotated method should return {@code void} and take exactly two arguments: first being
  * a view instance to be updated and second a value that should be set.
@@ -21,8 +22,8 @@
  *  - primitives (int, boolean, double, float)
  *  - {@link String}
  *  - {@link Boolean}
- *  - {@link ReadableArray}
- *  - {@link ReadableMap}
+ *  - {@link com.facebook.react.bridge.ReadableArray}
+ *  - {@link com.facebook.react.bridge.ReadableMap}
  *
  * When property gets removed from the corresponding component in React, annotated setter will be
  * called with {@code null} in case of non-primitive value type or with a default value in case when
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropGroup.java
similarity index 83%
rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java
rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropGroup.java
index 13c843314a04..fa9e0d9948ea 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactPropGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropGroup.java
@@ -1,6 +1,6 @@
 // Copyright 2004-present Facebook. All Rights Reserved.
 
-package com.facebook.react.uimanager;
+package com.facebook.react.uimanager.annotations;
 
 import javax.annotation.Nullable;
 
@@ -12,9 +12,10 @@
 
 /**
  * Use this annotation to annotate group of properties of native views that should be exposed to JS.
- * This annotation should only be used for setter methods of subclasses of {@link ViewManager}. It's
- * a batched version of {@link ReactProp} annotation (please see documentation of {@link ReactProp}
- * for more details about how this annotation can be used).
+ * This annotation should only be used for setter methods of subclasses of
+ * {@link com.facebook.react.uimanager.ViewManager}. It's a batched version of {@link ReactProp}
+ * annotation (please see documentation of {@link ReactProp} for more details about how this
+ * annotation can be used).
  *
  * This annotation is meant to be used for a group of similar properties. That's why it support only
  * a set of properties of the same type. A good example is supporting "border", where we have 7
@@ -26,7 +27,8 @@
  * group of the property being updated. Last, third argument represent the value that should be set.
  *
  *
- * Currently only {@code int}, {@code float} and {@link String} value types are supported.
+ * Currently only {@code int}, {@code float}, {@code double} and {@link String} value types are
+ * supported.
  *
  * In case when property has been removed from the corresponding react component annotated setter
  * will be called and default value will be provided as a value parameter. Default value can be
@@ -68,6 +70,13 @@
    */
   float defaultFloat() default 0.0f;
 
+  /**
+   * Default value for property of type {@code double}. This value will be provided to property
+   * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
+   * from the component description in JS
+   */
+  double defaultDouble() default 0.0;
+
   /**
    * Default value for property of type {@code int}. This value will be provided to property
    * setter method annotated with {@link ReactPropGroup} if property with a given name gets removed
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropertyHolder.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropertyHolder.java
new file mode 100644
index 000000000000..f676e7e0c69c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/annotations/ReactPropertyHolder.java
@@ -0,0 +1,15 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Inherited
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface ReactPropertyHolder {
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java
similarity index 85%
rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java
rename to ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java
index 1f4a5690b558..6f35ca06588e 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeUiManagerDebugListener.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/debug/NotThreadSafeViewHierarchyUpdateDebugListener.java
@@ -12,13 +12,13 @@
 import com.facebook.react.uimanager.UIManagerModule;
 
 /**
- * A listener that is notified about {@link UIManagerModule} events. This listener should only be
+ * A listener that is notified about view hierarchy update events. This listener should only be
  * used for debug purposes and should not affect application state.
  *
  * NB: while onViewHierarchyUpdateFinished will always be called from the UI thread, there are no
  * guarantees what thread onViewHierarchyUpdateEnqueued is called on.
  */
-public interface NotThreadSafeUiManagerDebugListener {
+public interface NotThreadSafeViewHierarchyUpdateDebugListener {
 
   /**
    * Called when {@link UIManagerModule} enqueues a UI batch to be dispatched to the main thread.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
index 58dd958a6993..40c2845c8adb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
@@ -18,9 +18,12 @@
  */
 public abstract class Event<T extends Event> {
 
+  private static int sUniqueID = 0;
+
   private boolean mInitialized;
   private int mViewTag;
   private long mTimestampMs;
+  private int mUniqueID = sUniqueID++;
 
   protected Event() {
   }
@@ -80,6 +83,13 @@ public short getCoalescingKey() {
     return 0;
   }
 
+  /**
+   * @return The unique id of this event.
+   */
+  public int getUniqueID() {
+    return mUniqueID;
+  }
+
   /**
    * Called when the EventDispatcher is done with an event, either because it was dispatched or
    * because it was coalesced with another Event.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
index 35c0a2897e4b..d55401af6d29 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
@@ -75,14 +75,7 @@ public int compare(Event lhs, Event rhs) {
         return 1;
       }
 
-      long diff = lhs.getTimestampMs() - rhs.getTimestampMs();
-      if (diff == 0) {
-        return 0;
-      } else if (diff < 0) {
-        return -1;
-      } else {
-        return 1;
-      }
+      return lhs.getTimestampMs() < rhs.getTimestampMs() ? -1 : 1;
     }
   };
 
@@ -100,6 +93,7 @@ public int compare(Event lhs, Event rhs) {
   private volatile @Nullable ScheduleDispatchFrameCallback mCurrentFrameCallback;
   private short mNextEventTypeId = 0;
   private volatile boolean mHasDispatchScheduled = false;
+  private volatile int mHasDispatchScheduledCount = 0;
 
   public EventDispatcher(ReactApplicationContext reactContext) {
     mReactContext = reactContext;
@@ -113,6 +107,10 @@ public void dispatchEvent(Event event) {
     Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
     synchronized (mEventsStagingLock) {
       mEventStaging.add(event);
+      Systrace.startAsyncFlow(
+          Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+          event.getEventName(),
+          event.getUniqueID());
     }
   }
 
@@ -242,6 +240,10 @@ public void doFrame(long frameTimeNanos) {
 
         if (!mHasDispatchScheduled) {
           mHasDispatchScheduled = true;
+          Systrace.startAsyncFlow(
+              Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+              "ScheduleDispatchFrameCallback",
+              mHasDispatchScheduledCount);
           mReactContext.runOnJSQueueThread(mDispatchEventsRunnable);
         }
 
@@ -263,7 +265,12 @@ private class DispatchEventsRunnable implements Runnable {
     public void run() {
       Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "DispatchEventsRunnable");
       try {
+        Systrace.endAsyncFlow(
+            Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+            "ScheduleDispatchFrameCallback",
+            mHasDispatchScheduledCount);
         mHasDispatchScheduled = false;
+        mHasDispatchScheduledCount++;
         Assertions.assertNotNull(mRCTEventEmitter);
         synchronized (mEventsToDispatchLock) {
           // We avoid allocating an array and iterator, and "sorting" if we don't need to.
@@ -277,6 +284,10 @@ public void run() {
             if (event == null) {
               continue;
             }
+            Systrace.endAsyncFlow(
+                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+                event.getEventName(),
+                event.getUniqueID());
             event.dispatch(mRCTEventEmitter);
             event.dispose();
           }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java
index 407b8c69108e..1600b74783d0 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java
@@ -35,12 +35,14 @@ public static TouchEvent obtain(
       int viewTag,
       long timestampMs,
       TouchEventType touchEventType,
-      MotionEvent motionEventToCopy) {
+      MotionEvent motionEventToCopy,
+      float viewX,
+      float viewY) {
     TouchEvent event = EVENTS_POOL.acquire();
     if (event == null) {
       event = new TouchEvent();
     }
-    event.init(viewTag, timestampMs, touchEventType, motionEventToCopy);
+    event.init(viewTag, timestampMs, touchEventType, motionEventToCopy, viewX, viewY);
     return event;
   }
 
@@ -48,6 +50,10 @@ public static TouchEvent obtain(
   private @Nullable TouchEventType mTouchEventType;
   private short mCoalescingKey;
 
+  // Coordinates in the ViewTag coordinate space
+  private float mViewX;
+  private float mViewY;
+
   private TouchEvent() {
   }
 
@@ -55,7 +61,9 @@ private void init(
       int viewTag,
       long timestampMs,
       TouchEventType touchEventType,
-      MotionEvent motionEventToCopy) {
+      MotionEvent motionEventToCopy,
+      float viewX,
+      float viewY) {
     super.init(viewTag, timestampMs);
 
     short coalescingKey = 0;
@@ -84,6 +92,8 @@ private void init(
     mTouchEventType = touchEventType;
     mMotionEvent = MotionEvent.obtain(motionEventToCopy);
     mCoalescingKey = coalescingKey;
+    mViewX = viewX;
+    mViewY = viewY;
   }
 
   @Override
@@ -126,6 +136,19 @@ public void dispatch(RCTEventEmitter rctEventEmitter) {
         rctEventEmitter,
         Assertions.assertNotNull(mTouchEventType),
         getViewTag(),
-        Assertions.assertNotNull(mMotionEvent));
+        this);
+  }
+
+  public MotionEvent getMotionEvent() {
+    Assertions.assertNotNull(mMotionEvent);
+    return mMotionEvent;
+  }
+
+  public float getViewX() {
+    return mViewX;
+  }
+
+  public float getViewY() {
+    return mViewY;
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java
index 56c6ff0ada2f..d312a402026b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java
@@ -27,8 +27,6 @@
   private static final String TIMESTAMP_KEY = "timeStamp";
   private static final String POINTER_IDENTIFIER_KEY = "identifier";
 
-  // TODO(7351435): remove when we standardize touchEvent payload, since iOS uses locationXYZ but
-  // Android uses pageXYZ. As a temporary solution, Android currently sends both.
   private static final String LOCATION_X_KEY = "locationX";
   private static final String LOCATION_Y_KEY = "locationY";
 
@@ -37,23 +35,35 @@
    * given {@param event} instance. This method use {@param reactTarget} parameter to set as a
    * target view id associated with current gesture.
    */
-  private static WritableArray createsPointersArray(int reactTarget, MotionEvent event) {
+  private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) {
     WritableArray touches = Arguments.createArray();
+    MotionEvent motionEvent = event.getMotionEvent();
 
-    // Calculate raw-to-relative offset as getRawX() and getRawY() can only return values for the
-    // pointer at index 0. We use those value to calculate "raw" coordinates for other pointers
-    float offsetX = event.getRawX() - event.getX();
-    float offsetY = event.getRawY() - event.getY();
+    // Calculate the coordinates for the target view.
+    // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
+    // The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
+    // Subtracting them allows us to get the coordinates of the target view's top left corner
+    // We then use this when computing the view specific touches below
+    // Since only one view is actually handling even multiple touches, the values are all relative
+    // to this one target view.
+    float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
+    float targetViewCoordinateY = motionEvent.getY() - event.getViewY();
 
-    for (int index = 0; index < event.getPointerCount(); index++) {
+    for (int index = 0; index < motionEvent.getPointerCount(); index++) {
       WritableMap touch = Arguments.createMap();
-      touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index) + offsetX));
-      touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index) + offsetY));
-      touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(event.getX(index)));
-      touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(event.getY(index)));
+      // pageX,Y values are relative to the RootReactView
+      // the motionEvent already contains coordinates in that view
+      touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
+      touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
+      // locationX,Y values are relative to the target view
+      // To compute the values for the view, we subtract that views location from the event X,Y
+      float locationX = motionEvent.getX(index) - targetViewCoordinateX;
+      float locationY = motionEvent.getY(index) - targetViewCoordinateY;
+      touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
+      touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
       touch.putInt(TARGET_KEY, reactTarget);
-      touch.putDouble(TIMESTAMP_KEY, event.getEventTime());
-      touch.putDouble(POINTER_IDENTIFIER_KEY, event.getPointerId(index));
+      touch.putDouble(TIMESTAMP_KEY, motionEvent.getEventTime());
+      touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
       touches.pushMap(touch);
     }
 
@@ -67,25 +77,26 @@ private static WritableArray createsPointersArray(int reactTarget, MotionEvent e
    * @param rctEventEmitter Event emitter used to execute JS module call
    * @param type type of the touch event (see {@link TouchEventType})
    * @param reactTarget target view react id associated with this gesture
-   * @param androidMotionEvent native touch event to read pointers count and coordinates from
+   * @param touchEvent native touch event to read pointers count and coordinates from
    */
   public static void sendTouchEvent(
       RCTEventEmitter rctEventEmitter,
       TouchEventType type,
       int reactTarget,
-      MotionEvent androidMotionEvent) {
+      TouchEvent touchEvent) {
 
-    WritableArray pointers = createsPointersArray(reactTarget, androidMotionEvent);
+    WritableArray pointers = createsPointersArray(reactTarget, touchEvent);
+    MotionEvent motionEvent = touchEvent.getMotionEvent();
 
     // For START and END events send only index of the pointer that is associated with that event
     // For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
     WritableArray changedIndices = Arguments.createArray();
     if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
-      for (int i = 0; i < androidMotionEvent.getPointerCount(); i++) {
+      for (int i = 0; i < motionEvent.getPointerCount(); i++) {
         changedIndices.pushInt(i);
       }
     } else if (type == TouchEventType.START || type == TouchEventType.END) {
-      changedIndices.pushInt(androidMotionEvent.getActionIndex());
+      changedIndices.pushInt(motionEvent.getActionIndex());
     } else {
       throw new RuntimeException("Unknown touch type: " + type);
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java
new file mode 100644
index 000000000000..c29d7ae0bef9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AbstractLayoutAnimation.java
@@ -0,0 +1,108 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import javax.annotation.Nullable;
+
+import java.util.Map;
+
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.uimanager.IllegalViewOperationException;
+
+/**
+ * Class responsible for parsing and converting layout animation data into native {@link Animation}
+ * in order to animate layout when a valid configuration has been supplied by the application.
+ */
+/* package */ abstract class AbstractLayoutAnimation {
+
+  // Forces animation to be playing 10x slower, used for debug purposes.
+  private static final boolean SLOWDOWN_ANIMATION_MODE = false;
+
+  abstract boolean isValid();
+
+  /**
+   * Create an animation object for the current animation type, based on the view and final screen
+   * coordinates. If the application-supplied configuraiton does not specify an animation definition
+   * for this types, or if the animation definition is invalid, returns null.
+   */
+  abstract @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height);
+
+  private static final Map<InterpolatorType, Interpolator> INTERPOLATOR = MapBuilder.of(
+      InterpolatorType.LINEAR, new LinearInterpolator(),
+      InterpolatorType.EASE_IN, new AccelerateInterpolator(),
+      InterpolatorType.EASE_OUT, new DecelerateInterpolator(),
+      InterpolatorType.EASE_IN_EASE_OUT, new AccelerateDecelerateInterpolator(),
+      InterpolatorType.SPRING, new SimpleSpringInterpolator());
+
+  private @Nullable Interpolator mInterpolator;
+  private int mDelayMs;
+
+  protected @Nullable AnimatedPropertyType mAnimatedProperty;
+  protected int mDurationMs;
+
+  public void reset() {
+    mAnimatedProperty = null;
+    mDurationMs = 0;
+    mDelayMs = 0;
+    mInterpolator = null;
+  }
+
+  public void initializeFromConfig(ReadableMap data, int globalDuration) {
+    mAnimatedProperty = data.hasKey("property") ?
+        AnimatedPropertyType.fromString(data.getString("property")) : null;
+    mDurationMs = data.hasKey("duration") ? data.getInt("duration") : globalDuration;
+    mDelayMs = data.hasKey("delay") ? data.getInt("delay") : 0;
+    mInterpolator = data.hasKey("type") ?
+        getInterpolator(InterpolatorType.fromString(data.getString("type"))) : null;
+
+    if (!isValid()) {
+      throw new IllegalViewOperationException("Invalid layout animation : " + data);
+    }
+  }
+
+  /**
+   * Create an animation object to be used to animate the view, based on the animation config
+   * supplied at initialization time and the new view position and size.
+   *
+   * @param view the view to create the animation for
+   * @param x the new X position for the view
+   * @param y the new Y position for the view
+   * @param width the new width value for the view
+   * @param height the new height value for the view
+   */
+   public final @Nullable Animation createAnimation(
+       View view,
+       int x,
+       int y,
+       int width,
+       int height) {
+    if (!isValid()) {
+      return null;
+    }
+    Animation animation = createAnimationImpl(view, x, y, width, height);
+    if (animation != null) {
+      int slowdownFactor = SLOWDOWN_ANIMATION_MODE ? 10 : 1;
+      animation.setDuration(mDurationMs * slowdownFactor);
+      animation.setStartOffset(mDelayMs * slowdownFactor);
+      animation.setInterpolator(mInterpolator);
+    }
+    return animation;
+  }
+
+  private static Interpolator getInterpolator(InterpolatorType type) {
+    Interpolator interpolator = INTERPOLATOR.get(type);
+    if (interpolator == null) {
+      throw new IllegalArgumentException("Missing interpolator for type : " + type);
+    }
+    return interpolator;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java
new file mode 100644
index 000000000000..51a9d246c061
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/AnimatedPropertyType.java
@@ -0,0 +1,32 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+/**
+ * Enum representing the different view properties that can be used when animating layout for
+ * view creation.
+ */
+/* package */ enum AnimatedPropertyType {
+  OPACITY("opacity"),
+  SCALE_XY("scaleXY");
+
+  private final String mName;
+
+  private AnimatedPropertyType(String name) {
+    mName = name;
+  }
+
+  public static AnimatedPropertyType fromString(String name) {
+    for (AnimatedPropertyType property : AnimatedPropertyType.values()) {
+      if (property.toString().equalsIgnoreCase(name)) {
+        return property;
+      }
+    }
+    throw new IllegalArgumentException("Unsupported animated property : " + name);
+  }
+
+  @Override
+  public String toString() {
+    return mName;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java
new file mode 100644
index 000000000000..f3c846864d1e
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java
@@ -0,0 +1,48 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.ScaleAnimation;
+
+import com.facebook.react.uimanager.IllegalViewOperationException;
+
+/**
+ * Class responsible for default layout animation, i.e animation of view creation and deletion.
+ */
+/* package */ abstract class BaseLayoutAnimation extends AbstractLayoutAnimation {
+
+  abstract boolean isReverse();
+
+  @Override
+  boolean isValid() {
+    return mDurationMs > 0 && mAnimatedProperty != null;
+  }
+
+  @Override
+  Animation createAnimationImpl(View view, int x, int y, int width, int height) {
+    float fromValue = isReverse() ? 1.0f : 0.0f;
+    float toValue = isReverse() ? 0.0f : 1.0f;
+    if (mAnimatedProperty != null) {
+      switch (mAnimatedProperty) {
+        case OPACITY:
+          return new OpacityAnimation(view, fromValue, toValue);
+        case SCALE_XY:
+          return new ScaleAnimation(
+              fromValue,
+              toValue,
+              fromValue,
+              toValue,
+              Animation.RELATIVE_TO_SELF,
+              .5f,
+              Animation.RELATIVE_TO_SELF,
+              .5f);
+        default:
+          throw new IllegalViewOperationException(
+              "Missing animation for property : " + mAnimatedProperty);
+      }
+    }
+    throw new IllegalViewOperationException("Missing animated property from animation config");
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java
new file mode 100644
index 000000000000..9f3c907e97d0
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/HandlesLayout.java
@@ -0,0 +1,10 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+/**
+ * Marker interface to indicate a given animation type takes care of updating the view layout.
+ */
+/* package */ interface HandleLayout {
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java
new file mode 100644
index 000000000000..f59675a8fbe1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/InterpolatorType.java
@@ -0,0 +1,34 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+/**
+ * Enum representing the different interpolators that can be used in layout animation configuration.
+ */
+/* package */ enum InterpolatorType {
+  LINEAR("linear"),
+  EASE_IN("easeIn"),
+  EASE_OUT("easeOut"),
+  EASE_IN_EASE_OUT("easeInEaseOut"),
+  SPRING("spring");
+
+  private final String mName;
+
+  private InterpolatorType(String name) {
+    mName = name;
+  }
+
+  public static InterpolatorType fromString(String name) {
+    for (InterpolatorType type : InterpolatorType.values()) {
+      if (type.toString().equalsIgnoreCase(name)) {
+        return type;
+      }
+    }
+    throw new IllegalArgumentException("Unsupported interpolation type : " + name);
+  }
+
+  @Override
+  public String toString() {
+    return mName;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java
new file mode 100644
index 000000000000..d3522947fd36
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationController.java
@@ -0,0 +1,94 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+import android.view.View;
+import android.view.animation.Animation;
+
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.UiThreadUtil;
+
+/**
+ * Class responsible for animation layout changes, if a valid layout animation config has been
+ * supplied. If not animation is available, layout change is applied immediately instead of
+ * performing an animation.
+ *
+ * TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled.
+ */
+@NotThreadSafe
+public class LayoutAnimationController {
+
+  private static final boolean ENABLED = true;
+
+  private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation();
+  private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation();
+  private boolean mShouldAnimateLayout;
+
+  public void initializeFromConfig(final @Nullable ReadableMap config) {
+    if (!ENABLED) {
+      return;
+    }
+
+    if (config == null) {
+      reset();
+      return;
+    }
+
+    mShouldAnimateLayout = false;
+    int globalDuration = config.hasKey("duration") ? config.getInt("duration") : 0;
+    if (config.hasKey(LayoutAnimationType.CREATE.toString())) {
+      mLayoutCreateAnimation.initializeFromConfig(
+          config.getMap(LayoutAnimationType.CREATE.toString()), globalDuration);
+      mShouldAnimateLayout = true;
+    }
+    if (config.hasKey(LayoutAnimationType.UPDATE.toString())) {
+      mLayoutUpdateAnimation.initializeFromConfig(
+          config.getMap(LayoutAnimationType.UPDATE.toString()), globalDuration);
+      mShouldAnimateLayout = true;
+    }
+  }
+
+  public void reset() {
+    mLayoutCreateAnimation.reset();
+    mLayoutUpdateAnimation.reset();
+    mShouldAnimateLayout = false;
+  }
+
+  public boolean shouldAnimateLayout(View viewToAnimate) {
+    // if view parent is null, skip animation: view have been clipped, we don't want animation to
+    // resume when view is re-attached to parent, which is the standard android animation behavior.
+    return mShouldAnimateLayout && viewToAnimate.getParent() != null;
+  }
+
+  /**
+   * Update layout of given view, via immediate update or animation depending on the current batch
+   * layout animation configuration supplied during initialization.
+   *
+   * @param view the view to update layout of
+   * @param x the new X position for the view
+   * @param y the new Y position for the view
+   * @param width the new width value for the view
+   * @param height the new height value for the view
+   */
+  public void applyLayoutUpdate(View view, int x, int y, int width, int height) {
+    UiThreadUtil.assertOnUiThread();
+
+    // Determine which animation to use : if view is initially invisible, use create animation.
+    // If view is becoming invisible, use delete animation. Otherwise, use update animation.
+    // This approach is easier than maintaining a list of tags for recently created/deleted views.
+    AbstractLayoutAnimation layoutAnimation = (view.getWidth() == 0 || view.getHeight() == 0) ?
+        mLayoutCreateAnimation :
+        mLayoutUpdateAnimation;
+
+    Animation animation = layoutAnimation.createAnimation(view, x, y, width, height);
+    if (animation == null || !(animation instanceof HandleLayout)) {
+      view.layout(x, y, x + width, y + height);
+    }
+    if (animation != null) {
+      view.startAnimation(animation);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java
new file mode 100644
index 000000000000..0f3d9d7fdb2b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutAnimationType.java
@@ -0,0 +1,22 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+/**
+ * Enum representing the different animation type that can be specified in layout animation config.
+ */
+/* package */ enum LayoutAnimationType {
+  CREATE("create"),
+  UPDATE("update");
+
+  private final String mName;
+
+  private LayoutAnimationType(String name) {
+    mName = name;
+  }
+
+  @Override
+  public String toString() {
+    return mName;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java
new file mode 100644
index 000000000000..a989e625c675
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutCreateAnimation.java
@@ -0,0 +1,15 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+/**
+ * Class responsible for handling layout view creation animation, applied to view whenever a
+ * valid config was supplied for the layout animation of CREATE type.
+ */
+/* package */ class LayoutCreateAnimation extends BaseLayoutAnimation {
+
+  @Override
+  boolean isReverse() {
+    return false;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java
new file mode 100644
index 000000000000..8189716f729d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/LayoutUpdateAnimation.java
@@ -0,0 +1,42 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import javax.annotation.Nullable;
+
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+
+/**
+ * Class responsible for handling layout update animation, applied to view whenever a valid config
+ * was supplied for the layout animation of UPDATE type.
+ */
+/* package */ class LayoutUpdateAnimation extends AbstractLayoutAnimation {
+
+  // We are currently not enabling translation GPU-accelerated animated, as it creates odd
+  // artifacts with native react scrollview. This needs to be investigated.
+  private static final boolean USE_TRANSLATE_ANIMATION = false;
+
+  @Override
+  boolean isValid() {
+    return mDurationMs > 0;
+  }
+
+  @Override
+  @Nullable Animation createAnimationImpl(View view, int x, int y, int width, int height) {
+    boolean animateLocation = view.getX() != x || view.getY() != y;
+    boolean animateSize = view.getWidth() != width || view.getHeight() != height;
+    if (!animateLocation && !animateSize) {
+      return null;
+    } else if (animateLocation && !animateSize && USE_TRANSLATE_ANIMATION) {
+      // Use GPU-accelerated animation, however we loose the ability to resume interrupted
+      // animation where it was left off. We may be able to listen to animation interruption
+      // and set the layout manually in this case, so that next animation kicks off smoothly.
+      return new TranslateAnimation(view.getX() - x, 0, view.getY() - y, 0);
+    } else {
+      // Animation is sub-optimal for perf, but scale transformation can't be use in this case.
+      return new PositionAndSizeAnimation(view, x, y, width, height);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java
new file mode 100644
index 000000000000..a7ad690023c5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/OpacityAnimation.java
@@ -0,0 +1,66 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+/**
+ * Animation responsible for updating opacity of a view. It should ideally use hardware texture
+ * to optimize rendering performances.
+ */
+/* package */ class OpacityAnimation extends Animation {
+
+  static class OpacityAnimationListener implements AnimationListener {
+
+    private final View mView;
+    private boolean mLayerTypeChanged = false;
+
+    public OpacityAnimationListener(View view) {
+      mView = view;
+    }
+
+    @Override
+    public void onAnimationStart(Animation animation) {
+      if (mView.hasOverlappingRendering() &&
+          mView.getLayerType() == View.LAYER_TYPE_NONE) {
+        mLayerTypeChanged = true;
+        mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+      }
+    }
+
+    @Override
+    public void onAnimationEnd(Animation animation) {
+      if (mLayerTypeChanged) {
+        mView.setLayerType(View.LAYER_TYPE_NONE, null);
+      }
+    }
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+      // do nothing
+    }
+  }
+
+  private final View mView;
+  private final float mStartOpacity, mDeltaOpacity;
+
+  public OpacityAnimation(View view, float startOpacity, float endOpacity) {
+    mView = view;
+    mStartOpacity = startOpacity;
+    mDeltaOpacity = endOpacity - startOpacity;
+
+    setAnimationListener(new OpacityAnimationListener(view));
+  }
+
+  @Override
+  protected void applyTransformation(float interpolatedTime, Transformation t) {
+    mView.setAlpha(mStartOpacity + mDeltaOpacity * interpolatedTime);
+  }
+
+  @Override
+  public boolean willChangeBounds() {
+    return false;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java
new file mode 100644
index 000000000000..2adf81a6bb6c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/PositionAndSizeAnimation.java
@@ -0,0 +1,52 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+/**
+ * Animation responsible for updating size and position of a view. We can't use scaling as view
+ * content may not necessarily stretch. As a result, this approach is inefficient because of
+ * layout passes occurring on every frame.
+ * What we might want to try to do instead is use a combined ScaleAnimation and TranslateAnimation.
+ */
+/* package */ class PositionAndSizeAnimation extends Animation implements HandleLayout {
+
+  private final View mView;
+  private final float mStartX, mStartY, mDeltaX, mDeltaY;
+  private final int mStartWidth, mStartHeight, mDeltaWidth, mDeltaHeight;
+
+  public PositionAndSizeAnimation(View view, int x, int y, int width, int height) {
+    mView = view;
+
+    mStartX = view.getX();
+    mStartY = view.getY();
+    mStartWidth = view.getWidth();
+    mStartHeight = view.getHeight();
+
+    mDeltaX = x - mStartX;
+    mDeltaY = y - mStartY;
+    mDeltaWidth = width - mStartWidth;
+    mDeltaHeight = height - mStartHeight;
+  }
+
+  @Override
+  protected void applyTransformation(float interpolatedTime, Transformation t) {
+    float newX = mStartX + mDeltaX * interpolatedTime;
+    float newY = mStartY + mDeltaY * interpolatedTime;
+    float newWidth = mStartWidth + mDeltaWidth * interpolatedTime;
+    float newHeight = mStartHeight + mDeltaHeight * interpolatedTime;
+    mView.layout(
+        Math.round(newX),
+        Math.round(newY),
+        Math.round(newX + newWidth),
+        Math.round(newY + newHeight));
+  }
+
+  @Override
+  public boolean willChangeBounds() {
+    return true;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java
new file mode 100644
index 000000000000..9696fe90a1a3
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/SimpleSpringInterpolator.java
@@ -0,0 +1,20 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.uimanager.layoutanimation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * Simple spring interpolator
+ */
+//TODO(7613736): Improve spring interpolator with friction and damping variable support
+/* package */  class SimpleSpringInterpolator implements Interpolator {
+
+  private static final float FACTOR = 0.5f;
+
+  @Override
+  public float getInterpolation(float input) {
+    return (float)
+        (1 + Math.pow(2, -10 * input) * Math.sin((input - FACTOR / 4) * Math.PI * 2 / FACTOR));
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTGroupShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTGroupShadowNode.java
new file mode 100644
index 000000000000..f01ca1ccf97d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTGroupShadowNode.java
@@ -0,0 +1,39 @@
+/**
+ * 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.views.art;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+/**
+ * Shadow node for virtual ARTGroup view
+ */
+public class ARTGroupShadowNode extends ARTVirtualNode {
+
+  @Override
+  public boolean isVirtual() {
+    return true;
+  }
+
+  public void draw(Canvas canvas, Paint paint, float opacity) {
+    opacity *= mOpacity;
+    if (opacity > MIN_OPACITY_FOR_DRAW) {
+      saveAndSetupCanvas(canvas);
+      // TODO(6352006): apply clipping (iOS doesn't do it yet, it seems to cause issues)
+      for (int i = 0; i < getChildCount(); i++) {
+        ARTVirtualNode child = (ARTVirtualNode) getChildAt(i);
+        child.draw(canvas, paint, opacity);
+        child.markUpdateSeen();
+      }
+
+      restoreCanvas(canvas);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java
new file mode 100644
index 000000000000..44ba41ba6303
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTRenderableViewManager.java
@@ -0,0 +1,88 @@
+/**
+ * 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.views.art;
+
+import android.view.View;
+
+import com.facebook.react.uimanager.ReactStylesDiffMap;
+import com.facebook.react.uimanager.ReactShadowNode;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.ViewManager;
+
+/**
+ * ViewManager for all shadowed ART views: Group, Shape and Text. Since these never get rendered
+ * into native views and don't need any logic (all the logic is in {@link ARTSurfaceView}), this
+ * "stubbed" ViewManager is used for all of them.
+ */
+public class ARTRenderableViewManager extends ViewManager<View, ReactShadowNode> {
+
+  /* package */ static final String CLASS_GROUP = "ARTGroup";
+  /* package */ static final String CLASS_SHAPE = "ARTShape";
+  /* package */ static final String CLASS_TEXT = "ARTText";
+
+  private final String mClassName;
+
+  public static ARTRenderableViewManager createARTGroupViewManager() {
+    return new ARTRenderableViewManager(CLASS_GROUP);
+  }
+
+  public static ARTRenderableViewManager createARTShapeViewManager() {
+    return new ARTRenderableViewManager(CLASS_SHAPE);
+  }
+
+  public static ARTRenderableViewManager createARTTextViewManager() {
+    return new ARTRenderableViewManager(CLASS_TEXT);
+  }
+
+  private ARTRenderableViewManager(String className) {
+    mClassName = className;
+  }
+
+  @Override
+  public String getName() {
+    return mClassName;
+  }
+
+  @Override
+  public ReactShadowNode createShadowNodeInstance() {
+    if (mClassName == CLASS_GROUP) {
+      return new ARTGroupShadowNode();
+    } else if (mClassName == CLASS_SHAPE) {
+      return new ARTShapeShadowNode();
+    } else if (mClassName == CLASS_TEXT) {
+      return new ARTTextShadowNode();
+    } else {
+      throw new IllegalStateException("Unexpected type " + mClassName);
+    }
+  }
+
+  @Override
+  public Class<? extends ReactShadowNode> getShadowNodeClass() {
+    if (mClassName == CLASS_GROUP) {
+      return ARTGroupShadowNode.class;
+    } else if (mClassName == CLASS_SHAPE) {
+      return ARTShapeShadowNode.class;
+    } else if (mClassName == CLASS_TEXT) {
+      return ARTTextShadowNode.class;
+    } else {
+      throw new IllegalStateException("Unexpected type " + mClassName);
+    }
+  }
+
+  @Override
+  protected View createViewInstance(ThemedReactContext reactContext) {
+    throw new IllegalStateException("ARTShape does not map into a native view");
+  }
+
+  @Override
+  public void updateExtraData(View root, Object extraData) {
+    throw new IllegalStateException("ARTShape does not map into a native view");
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTShapeShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTShapeShadowNode.java
new file mode 100644
index 000000000000..b66066b41e49
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTShapeShadowNode.java
@@ -0,0 +1,251 @@
+/**
+ * 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.views.art;
+
+import javax.annotation.Nullable;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.uimanager.annotations.ReactProp;
+
+/**
+ * Shadow node for virtual ARTShape view
+ */
+public class ARTShapeShadowNode extends ARTVirtualNode {
+
+  private static final int CAP_BUTT = 0;
+  private static final int CAP_ROUND = 1;
+  private static final int CAP_SQUARE = 2;
+
+  private static final int JOIN_BEVEL = 2;
+  private static final int JOIN_MITER = 0;
+  private static final int JOIN_ROUND = 1;
+
+  private static final int PATH_TYPE_ARC = 4;
+  private static final int PATH_TYPE_CLOSE = 1;
+  private static final int PATH_TYPE_CURVETO = 3;
+  private static final int PATH_TYPE_LINETO = 2;
+  private static final int PATH_TYPE_MOVETO = 0;
+
+  protected @Nullable Path mPath;
+  private @Nullable float[] mStrokeColor;
+  private @Nullable float[] mFillColor;
+  private @Nullable float[] mStrokeDash;
+  private float mStrokeWidth = 1;
+  private int mStrokeCap = CAP_ROUND;
+  private int mStrokeJoin = JOIN_ROUND;
+
+  @ReactProp(name = "d")
+  public void setShapePath(@Nullable ReadableArray shapePath) {
+    float[] pathData = PropHelper.toFloatArray(shapePath);
+    mPath = createPath(pathData);
+    markUpdated();
+  }
+
+  @ReactProp(name = "stroke")
+  public void setStroke(@Nullable ReadableArray strokeColors) {
+    mStrokeColor = PropHelper.toFloatArray(strokeColors);
+    markUpdated();
+  }
+
+  @ReactProp(name = "strokeDash")
+  public void setStrokeDash(@Nullable ReadableArray strokeDash) {
+    mStrokeDash = PropHelper.toFloatArray(strokeDash);
+    markUpdated();
+  }
+
+  @ReactProp(name = "fill")
+  public void setFill(@Nullable ReadableArray fillColors) {
+    mFillColor = PropHelper.toFloatArray(fillColors);
+    markUpdated();
+  }
+
+  @ReactProp(name = "strokeWidth", defaultFloat = 1f)
+  public void setStrokeWidth(float strokeWidth) {
+    mStrokeWidth = strokeWidth;
+    markUpdated();
+  }
+
+  @ReactProp(name = "strokeCap", defaultInt = CAP_ROUND)
+  public void setStrokeCap(int strokeCap) {
+    mStrokeCap = strokeCap;
+    markUpdated();
+  }
+
+  @ReactProp(name = "strokeJoin", defaultInt = JOIN_ROUND)
+  public void setStrokeJoin(int strokeJoin) {
+    mStrokeJoin = strokeJoin;
+    markUpdated();
+  }
+
+  @Override
+  public void draw(Canvas canvas, Paint paint, float opacity) {
+    opacity *= mOpacity;
+    if (opacity > MIN_OPACITY_FOR_DRAW) {
+      saveAndSetupCanvas(canvas);
+      if (mPath == null) {
+        throw new JSApplicationIllegalArgumentException(
+            "Shapes should have a valid path (d) prop");
+      }
+      if (setupStrokePaint(paint, opacity)) {
+        canvas.drawPath(mPath, paint);
+      }
+      if (setupFillPaint(paint, opacity)) {
+        canvas.drawPath(mPath, paint);
+      }
+      restoreCanvas(canvas);
+    }
+    markUpdateSeen();
+  }
+
+  /**
+   * Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true}
+   * if the stroke should be drawn, {@code false} if not.
+   */
+  protected boolean setupStrokePaint(Paint paint, float opacity) {
+    if (mStrokeWidth == 0 || mStrokeColor == null || mStrokeColor.length == 0) {
+      return false;
+    }
+    paint.reset();
+    paint.setFlags(Paint.ANTI_ALIAS_FLAG);
+    paint.setStyle(Paint.Style.STROKE);
+    switch (mStrokeCap) {
+      case CAP_BUTT:
+        paint.setStrokeCap(Paint.Cap.BUTT);
+        break;
+      case CAP_SQUARE:
+        paint.setStrokeCap(Paint.Cap.SQUARE);
+        break;
+      case CAP_ROUND:
+        paint.setStrokeCap(Paint.Cap.ROUND);
+        break;
+      default:
+        throw new JSApplicationIllegalArgumentException(
+            "strokeCap " + mStrokeCap + " unrecognized");
+    }
+    switch (mStrokeJoin) {
+      case JOIN_MITER:
+        paint.setStrokeJoin(Paint.Join.MITER);
+        break;
+      case JOIN_BEVEL:
+        paint.setStrokeJoin(Paint.Join.BEVEL);
+        break;
+      case JOIN_ROUND:
+        paint.setStrokeJoin(Paint.Join.ROUND);
+        break;
+      default:
+        throw new JSApplicationIllegalArgumentException(
+            "strokeJoin " + mStrokeJoin + " unrecognized");
+    }
+    paint.setStrokeWidth(mStrokeWidth * mScale);
+    paint.setARGB(
+        (int) (mStrokeColor.length > 3 ? mStrokeColor[3] * opacity * 255 : opacity * 255),
+        (int) (mStrokeColor[0] * 255),
+        (int) (mStrokeColor[1] * 255),
+        (int) (mStrokeColor[2] * 255));
+    if (mStrokeDash != null && mStrokeDash.length > 0) {
+      // TODO(6352067): Support dashes
+      FLog.w(ReactConstants.TAG, "ART: Dashes are not supported yet!");
+    }
+    return true;
+  }
+
+  /**
+   * Sets up {@link #mPaint} according to the props set on a shadow view. Returns {@code true}
+   * if the fill should be drawn, {@code false} if not.
+   */
+  protected boolean setupFillPaint(Paint paint, float opacity) {
+    if (mFillColor != null && mFillColor.length > 0) {
+      paint.reset();
+      paint.setFlags(Paint.ANTI_ALIAS_FLAG);
+      paint.setStyle(Paint.Style.FILL);
+      int colorType = (int) mFillColor[0];
+      switch (colorType) {
+        case 0:
+          paint.setARGB(
+              (int) (mFillColor.length > 4 ? mFillColor[4] * opacity * 255 : opacity * 255),
+              (int) (mFillColor[1] * 255),
+              (int) (mFillColor[2] * 255),
+              (int) (mFillColor[3] * 255));
+          break;
+        default:
+          // TODO(6352048): Support gradients etc.
+          FLog.w(ReactConstants.TAG, "ART: Color type " + colorType + " not supported!");
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Creates a {@link Path} from an array of instructions constructed by JS
+   * (see ARTSerializablePath.js). Each instruction starts with a type (see PATH_TYPE_*) followed
+   * by arguments for that instruction. For example, to create a line the instruction will be
+   * 2 (PATH_LINE_TO), x, y. This will draw a line from the last draw point (or 0,0) to x,y.
+   *
+   * @param data the array of instructions
+   * @return the {@link Path} that can be drawn to a canvas
+   */
+  private Path createPath(float[] data) {
+    Path path = new Path();
+    path.moveTo(0, 0);
+    int i = 0;
+    while (i < data.length) {
+      int type = (int) data[i++];
+      switch (type) {
+        case PATH_TYPE_MOVETO:
+          path.moveTo(data[i++] * mScale, data[i++] * mScale);
+          break;
+        case PATH_TYPE_CLOSE:
+          path.close();
+          break;
+        case PATH_TYPE_LINETO:
+          path.lineTo(data[i++] * mScale, data[i++] * mScale);
+          break;
+        case PATH_TYPE_CURVETO:
+          path.cubicTo(
+              data[i++] * mScale,
+              data[i++] * mScale,
+              data[i++] * mScale,
+              data[i++] * mScale,
+              data[i++] * mScale,
+              data[i++] * mScale);
+          break;
+        case PATH_TYPE_ARC:
+        {
+          float x = data[i++] * mScale;
+          float y = data[i++] * mScale;
+          float r = data[i++] * mScale;
+          float start = (float) Math.toDegrees(data[i++]);
+          float end = (float) Math.toDegrees(data[i++]);
+          boolean clockwise = data[i++] == 0f;
+          if (!clockwise) {
+            end = 360 - end;
+          }
+          float sweep = start - end;
+          RectF oval = new RectF(x - r, y - r, x + r, y + r);
+          path.addArc(oval, start, sweep);
+          break;
+        }
+        default:
+          throw new JSApplicationIllegalArgumentException(
+              "Unrecognized drawing instruction " + type);
+      }
+    }
+    return path;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceView.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceView.java
new file mode 100644
index 000000000000..42aecf3927e1
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceView.java
@@ -0,0 +1,45 @@
+/**
+ * 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.views.art;
+
+import javax.annotation.Nullable;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.view.View;
+
+/**
+ * Custom {@link View} implementation that draws an ARTSurface React view and its children.
+ */
+public class ARTSurfaceView extends View {
+
+  private @Nullable Bitmap mBitmap;
+
+  public ARTSurfaceView(Context context) {
+    super(context);
+  }
+
+  public void setBitmap(Bitmap bitmap) {
+    if (mBitmap != null) {
+      mBitmap.recycle();
+    }
+    mBitmap = bitmap;
+    invalidate();
+  }
+
+  @Override
+  protected void onDraw(Canvas canvas) {
+    super.onDraw(canvas);
+    if (mBitmap != null) {
+      canvas.drawBitmap(mBitmap, 0, 0, null);
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewManager.java
new file mode 100644
index 000000000000..98fc5293b54d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewManager.java
@@ -0,0 +1,61 @@
+/**
+ * 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.views.art;
+
+import android.graphics.Bitmap;
+
+import com.facebook.csslayout.CSSNode;
+import com.facebook.csslayout.MeasureOutput;
+import com.facebook.react.uimanager.BaseViewManager;
+import com.facebook.react.uimanager.ThemedReactContext;
+
+/**
+ * ViewManager for ARTSurfaceView React views. Renders as a {@link ARTSurfaceView} and handles
+ * invalidating the native view on shadow view updates happening in the underlying tree.
+ */
+public class ARTSurfaceViewManager extends
+    BaseViewManager<ARTSurfaceView, ARTSurfaceViewShadowNode> {
+
+  private static final String REACT_CLASS = "ARTSurfaceView";
+
+  private static final CSSNode.MeasureFunction MEASURE_FUNCTION = new CSSNode.MeasureFunction() {
+    @Override
+    public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
+      throw new IllegalStateException("SurfaceView should have explicit width and height set");
+    }
+  };
+
+  @Override
+  public String getName() {
+    return REACT_CLASS;
+  }
+
+  @Override
+  public ARTSurfaceViewShadowNode createShadowNodeInstance() {
+    ARTSurfaceViewShadowNode node = new ARTSurfaceViewShadowNode();
+    node.setMeasureFunction(MEASURE_FUNCTION);
+    return node;
+  }
+
+  @Override
+  public Class<ARTSurfaceViewShadowNode> getShadowNodeClass() {
+    return ARTSurfaceViewShadowNode.class;
+  }
+
+  @Override
+  protected ARTSurfaceView createViewInstance(ThemedReactContext reactContext) {
+    return new ARTSurfaceView(reactContext);
+  }
+
+  @Override
+  public void updateExtraData(ARTSurfaceView root, Object extraData) {
+    root.setBitmap((Bitmap) extraData);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewShadowNode.java
new file mode 100644
index 000000000000..b6004efa88b2
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTSurfaceViewShadowNode.java
@@ -0,0 +1,56 @@
+/**
+ * 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.views.art;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.facebook.react.uimanager.LayoutShadowNode;
+import com.facebook.react.uimanager.UIViewOperationQueue;
+
+/**
+ * Shadow node for ART virtual tree root - ARTSurfaceView
+ */
+public class ARTSurfaceViewShadowNode extends LayoutShadowNode {
+
+  @Override
+  public boolean isVirtual() {
+    return false;
+  }
+
+  @Override
+  public boolean isVirtualAnchor() {
+    return true;
+  }
+
+  @Override
+  public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
+    super.onCollectExtraUpdates(uiUpdater);
+    uiUpdater.enqueueUpdateExtraData(getReactTag(), drawOutput());
+  }
+
+  private Object drawOutput() {
+    // TODO(7255985): Use TextureView and pass Surface from the view to draw on it asynchronously
+    // instead of passing the bitmap (which is inefficient especially in terms of memory usage)
+    Bitmap bitmap = Bitmap.createBitmap(
+        (int) getLayoutWidth(),
+        (int) getLayoutHeight(),
+        Bitmap.Config.ARGB_8888);
+    Canvas canvas = new Canvas(bitmap);
+    Paint paint = new Paint();
+    for (int i = 0; i < getChildCount(); i++) {
+      ARTVirtualNode child = (ARTVirtualNode) getChildAt(i);
+      child.draw(canvas, paint, 1f);
+      child.markUpdateSeen();
+    }
+    return bitmap;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTTextShadowNode.java
new file mode 100644
index 000000000000..d6e1583529cc
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTTextShadowNode.java
@@ -0,0 +1,141 @@
+/**
+ * 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.views.art;
+
+import javax.annotation.Nullable;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.text.TextUtils;
+
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.uimanager.annotations.ReactProp;
+
+/**
+ * Shadow node for virtual ARTText view
+ */
+public class ARTTextShadowNode extends ARTShapeShadowNode {
+
+  private static final String PROP_LINES = "lines";
+
+  private static final String PROP_FONT = "font";
+  private static final String PROP_FONT_FAMILY = "fontFamily";
+  private static final String PROP_FONT_SIZE = "fontSize";
+  private static final String PROP_FONT_STYLE = "fontStyle";
+  private static final String PROP_FONT_WEIGHT = "fontWeight";
+
+  private static final int DEFAULT_FONT_SIZE = 12;
+
+  private static final int TEXT_ALIGNMENT_CENTER = 2;
+  private static final int TEXT_ALIGNMENT_LEFT = 0;
+  private static final int TEXT_ALIGNMENT_RIGHT = 1;
+
+  private @Nullable ReadableMap mFrame;
+  private int mTextAlignment = TEXT_ALIGNMENT_LEFT;
+
+  @ReactProp(name = "frame")
+  public void setFrame(@Nullable ReadableMap frame) {
+    mFrame = frame;
+  }
+
+  @ReactProp(name = "alignment", defaultInt = TEXT_ALIGNMENT_LEFT)
+  public void setAlignment(int alignment) {
+    mTextAlignment = alignment;
+  }
+
+  @Override
+  public void draw(Canvas canvas, Paint paint, float opacity) {
+    if (mFrame == null) {
+      return;
+    }
+    opacity *= mOpacity;
+    if (opacity <= MIN_OPACITY_FOR_DRAW) {
+      return;
+    }
+    if (!mFrame.hasKey(PROP_LINES)) {
+      return;
+    }
+    ReadableArray linesProp = mFrame.getArray(PROP_LINES);
+    if (linesProp == null || linesProp.size() == 0) {
+      return;
+    }
+
+    // only set up the canvas if we have something to draw
+    saveAndSetupCanvas(canvas);
+    String[] lines = new String[linesProp.size()];
+    for (int i = 0; i < lines.length; i++) {
+      lines[i] = linesProp.getString(i);
+    }
+    String text = TextUtils.join("\n", lines);
+    if (setupStrokePaint(paint, opacity)) {
+      applyTextPropertiesToPaint(paint);
+      if (mPath == null) {
+        canvas.drawText(text, 0, -paint.ascent(), paint);
+      } else {
+        canvas.drawTextOnPath(text, mPath, 0, 0, paint);
+      }
+    }
+    if (setupFillPaint(paint, opacity)) {
+      applyTextPropertiesToPaint(paint);
+      if (mPath == null) {
+        canvas.drawText(text, 0, -paint.ascent(), paint);
+      } else {
+        canvas.drawTextOnPath(text, mPath, 0, 0, paint);
+      }
+    }
+    restoreCanvas(canvas);
+    markUpdateSeen();
+  }
+
+  private void applyTextPropertiesToPaint(Paint paint) {
+    int alignment = mTextAlignment;
+    switch (alignment) {
+      case TEXT_ALIGNMENT_LEFT:
+        paint.setTextAlign(Paint.Align.LEFT);
+        break;
+      case TEXT_ALIGNMENT_RIGHT:
+        paint.setTextAlign(Paint.Align.RIGHT);
+        break;
+      case TEXT_ALIGNMENT_CENTER:
+        paint.setTextAlign(Paint.Align.CENTER);
+        break;
+    }
+    if (mFrame != null) {
+      if (mFrame.hasKey(PROP_FONT)) {
+        ReadableMap font = mFrame.getMap(PROP_FONT);
+        if (font != null) {
+          float fontSize = DEFAULT_FONT_SIZE;
+          if (font.hasKey(PROP_FONT_SIZE)) {
+            fontSize = (float) font.getDouble(PROP_FONT_SIZE);
+          }
+          paint.setTextSize(fontSize * mScale);
+          boolean isBold =
+              font.hasKey(PROP_FONT_WEIGHT) && "bold".equals(font.getString(PROP_FONT_WEIGHT));
+          boolean isItalic =
+              font.hasKey(PROP_FONT_STYLE) && "italic".equals(font.getString(PROP_FONT_STYLE));
+          int fontStyle;
+          if (isBold && isItalic) {
+            fontStyle = Typeface.BOLD_ITALIC;
+          } else if (isBold) {
+            fontStyle = Typeface.BOLD;
+          } else if (isItalic) {
+            fontStyle = Typeface.ITALIC;
+          } else {
+            fontStyle = Typeface.NORMAL;
+          }
+          // NB: if the font family is null / unsupported, the default one will be used
+          paint.setTypeface(Typeface.create(font.getString(PROP_FONT_FAMILY), fontStyle));
+        }
+      }
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java
new file mode 100644
index 000000000000..70dc6faaf2af
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java
@@ -0,0 +1,113 @@
+/**
+ * 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.views.art;
+
+import javax.annotation.Nullable;
+
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.ReactShadowNode;
+
+/**
+ * Base class for ARTView virtual nodes: {@link ARTGroupShadowNode}, {@link ARTShapeShadowNode} and
+ * indirectly for {@link ARTTextShadowNode}.
+ */
+public abstract class ARTVirtualNode extends ReactShadowNode {
+
+  protected static final float MIN_OPACITY_FOR_DRAW = 0.01f;
+
+  private static final float[] sMatrixData = new float[9];
+  private static final float[] sRawMatrix = new float[9];
+
+  protected float mOpacity = 1f;
+  private @Nullable Matrix mMatrix = new Matrix();
+
+  protected final float mScale;
+
+  public ARTVirtualNode() {
+    mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
+  }
+
+  @Override
+  public boolean isVirtual() {
+    return true;
+  }
+
+  public abstract void draw(Canvas canvas, Paint paint, float opacity);
+
+  /**
+   * Sets up the transform matrix on the canvas before an element is drawn.
+   *
+   * NB: for perf reasons this does not apply opacity, as that would mean creating a new canvas
+   * layer (which allocates an offscreen bitmap) and having it composited afterwards. Instead, the
+   * drawing code should apply opacity recursively.
+   *
+   * @param canvas the canvas to set up
+   */
+  protected final void saveAndSetupCanvas(Canvas canvas) {
+    canvas.save();
+    if (mMatrix != null) {
+      canvas.concat(mMatrix);
+    }
+  }
+
+  /**
+   * Restore the canvas after an element was drawn. This is always called in mirror with
+   * {@link #saveAndSetupCanvas}.
+   *
+   * @param canvas the canvas to restore
+   */
+  protected void restoreCanvas(Canvas canvas) {
+    canvas.restore();
+  }
+
+  @ReactProp(name = "opacity", defaultFloat = 1f)
+  public void setOpacity(float opacity) {
+    mOpacity = opacity;
+    markUpdated();
+  }
+
+  @ReactProp(name = "transform")
+  public void setTransform(@Nullable ReadableArray transformArray) {
+    if (transformArray != null) {
+      int matrixSize = PropHelper.toFloatArray(transformArray, sMatrixData);
+      if (matrixSize == 6) {
+        setupMatrix();
+      } else if (matrixSize != -1) {
+        throw new JSApplicationIllegalArgumentException("Transform matrices must be of size 6");
+      }
+    } else {
+      mMatrix = null;
+    }
+    markUpdated();
+  }
+
+  protected void setupMatrix() {
+    sRawMatrix[0] = sMatrixData[0];
+    sRawMatrix[1] = sMatrixData[2];
+    sRawMatrix[2] = sMatrixData[4] * mScale;
+    sRawMatrix[3] = sMatrixData[1];
+    sRawMatrix[4] = sMatrixData[3];
+    sRawMatrix[5] = sMatrixData[5] * mScale;
+    sRawMatrix[6] = 0;
+    sRawMatrix[7] = 0;
+    sRawMatrix[8] = 1;
+    if (mMatrix == null) {
+      mMatrix = new Matrix();
+    }
+    mMatrix.setValues(sRawMatrix);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK
new file mode 100644
index 000000000000..d7612a1e463b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/BUCK
@@ -0,0 +1,22 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'art',
+  srcs = glob(['*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':art',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/art/PropHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/art/PropHelper.java
new file mode 100644
index 000000000000..04497fc4d88d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/PropHelper.java
@@ -0,0 +1,54 @@
+/**
+ * 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.views.art;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.uimanager.ReactStylesDiffMap;
+
+/**
+ * Contains static helper methods for accessing props.
+ */
+/* package */ class PropHelper {
+
+  /**
+   * Converts {@link ReadableArray} to an array of {@code float}. Returns newly created array.
+   *
+   * @return a {@code float[]} if converted successfully, or {@code null} if {@param value} was
+   * {@code null}.
+   */
+  /*package*/ static @Nullable float[] toFloatArray(@Nullable ReadableArray value) {
+    if (value != null) {
+      float[] result = new float[value.size()];
+      toFloatArray(value, result);
+      return result;
+    }
+    return null;
+  }
+
+  /**
+   * Converts given {@link ReadableArray} to an array of {@code float}. Writes result to the array
+   * passed in {@param into}. This method will write to the output array up to the number of items
+   * from the input array. If the input array is longer than output the remaining part of the input
+   * will not be converted.
+   *
+   * @param value input array
+   * @param into output array
+   * @return number of items copied from input to the output array
+   */
+  /*package*/ static int toFloatArray(ReadableArray value, float[] into) {
+    int length = value.size() > into.length ? into.length : value.size();
+    for (int i = 0; i < length; i++) {
+      into[i] = (float) value.getDouble(i);
+    }
+    return value.size();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK
index 1bef740699d7..6698d3393e54 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/BUCK
@@ -8,9 +8,10 @@ android_library(
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_target('java/com/facebook/react/views/scroll:scroll'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
-    '//third-party/java/android/support/v4:lib-support-v4',
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java
index 75ab360c3aad..1926c110abcd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/drawer/ReactDrawerLayoutManager.java
@@ -13,7 +13,6 @@
 
 import java.util.Map;
 
-import android.os.SystemClock;
 import android.support.v4.widget.DrawerLayout;
 import android.view.Gravity;
 import android.view.View;
@@ -21,11 +20,12 @@
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ViewGroupManager;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.events.EventDispatcher;
 import com.facebook.react.views.drawer.events.DrawerClosedEvent;
 import com.facebook.react.views.drawer.events.DrawerOpenedEvent;
@@ -76,6 +76,19 @@ public void getDrawerWidth(ReactDrawerLayout view, float width) {
     view.setDrawerWidth(widthInPx);
   }
 
+  @ReactProp(name = "drawerLockMode")
+  public void setDrawerLockMode(ReactDrawerLayout view, @Nullable String drawerLockMode) {
+    if (drawerLockMode == null || "unlocked".equals(drawerLockMode)) {
+      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+    } else if ("locked-closed".equals(drawerLockMode)) {
+      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+    } else if ("locked-open".equals(drawerLockMode)) {
+      view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
+    } else {
+      throw new JSApplicationIllegalArgumentException("Unknown drawerLockMode " + drawerLockMode);
+    }
+  }
+
   @Override
   public boolean needsCustomLayoutForChildren() {
     // Return true, since DrawerLayout will lay out it's own children.
@@ -151,25 +164,25 @@ public DrawerEventEmitter(DrawerLayout drawerLayout, EventDispatcher eventDispat
     @Override
     public void onDrawerSlide(View view, float v) {
       mEventDispatcher.dispatchEvent(
-          new DrawerSlideEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis(), v));
+          new DrawerSlideEvent(mDrawerLayout.getId(), SystemClock.nanoTime(), v));
     }
 
     @Override
     public void onDrawerOpened(View view) {
       mEventDispatcher.dispatchEvent(
-        new DrawerOpenedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis()));
+        new DrawerOpenedEvent(mDrawerLayout.getId(), SystemClock.nanoTime()));
     }
 
     @Override
     public void onDrawerClosed(View view) {
       mEventDispatcher.dispatchEvent(
-          new DrawerClosedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis()));
+          new DrawerClosedEvent(mDrawerLayout.getId(), SystemClock.nanoTime()));
     }
 
     @Override
     public void onDrawerStateChanged(int i) {
       mEventDispatcher.dispatchEvent(
-          new DrawerStateChangedEvent(mDrawerLayout.getId(), SystemClock.uptimeMillis(), i));
+          new DrawerStateChangedEvent(mDrawerLayout.getId(), SystemClock.nanoTime(), i));
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK
index 52c2e0b1e8d0..abafb1b59eaa 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/BUCK
@@ -5,20 +5,17 @@ android_library(
   srcs = glob(['*.java']),
   deps = [
     react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
-    '//libraries/drawee/src/main/java/com/facebook/drawee/controller:controller',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/drawable:drawable',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/generic:generic',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/interfaces:interfaces',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/view:view',
-    '//libraries/fbcore/src/main/java/com/facebook/common/references:references',
-    '//libraries/fbcore/src/main/java/com/facebook/common/util:util',
-    '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/bitmaps:bitmaps',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/common:common',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/request:request',
-    '//third-party/java/jsr-305:jsr-305',
-  ],
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/android/support-annotations:android-support-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+],
   visibility = [
     'PUBLIC',
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java
new file mode 100644
index 000000000000..fc183507a63c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageLoadEvent.java
@@ -0,0 +1,73 @@
+/**
+ * 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.views.image;
+
+import android.support.annotation.IntDef;
+
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class ImageLoadEvent extends Event<ImageLoadEvent> {
+  @IntDef({ON_ERROR, ON_LOAD, ON_LOAD_END, ON_LOAD_START, ON_PROGRESS})
+  @Retention(RetentionPolicy.SOURCE)
+  @interface ImageEventType {}
+
+  // Currently ON_ERROR and ON_PROGRESS are not implemented, these can be added
+  // easily once support exists in fresco.
+  public static final int ON_ERROR = 1;
+  public static final int ON_LOAD = 2;
+  public static final int ON_LOAD_END = 3;
+  public static final int ON_LOAD_START = 4;
+  public static final int ON_PROGRESS = 5;
+
+  private final int mEventType;
+
+  public ImageLoadEvent(int viewId, long timestampMs, @ImageEventType int eventType) {
+    super(viewId, timestampMs);
+    mEventType = eventType;
+  }
+
+  public static String eventNameForType(@ImageEventType int eventType) {
+    switch(eventType) {
+      case ON_ERROR:
+        return "topError";
+      case ON_LOAD:
+        return "topLoad";
+      case ON_LOAD_END:
+        return "topLoadEnd";
+      case ON_LOAD_START:
+        return "topLoadStart";
+      case ON_PROGRESS:
+        return "topProgress";
+      default:
+        throw new IllegalStateException("Invalid image event: " + Integer.toString(eventType));
+    }
+  }
+
+  @Override
+  public String getEventName() {
+    return ImageLoadEvent.eventNameForType(mEventType);
+  }
+
+  @Override
+  public short getCoalescingKey() {
+    // Intentionally casting mEventType because it is guaranteed to be small
+    // enough to fit into short.
+    return (short) mEventType;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java
index 0158ff072c40..6ebd2e8c8cda 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.java
@@ -11,11 +11,15 @@
 
 import javax.annotation.Nullable;
 
+import java.util.Map;
+
 import android.graphics.Color;
+import android.graphics.PorterDuff.Mode;
 
 import com.facebook.drawee.backends.pipeline.Fresco;
 import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.SimpleViewManager;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewProps;
@@ -29,7 +33,8 @@ public String getName() {
     return REACT_CLASS;
   }
 
-  private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
+  private ResourceDrawableIdHelper mResourceDrawableIdHelper;
+  private @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
   private final @Nullable Object mCallerContext;
 
   public ReactImageManager(
@@ -37,26 +42,45 @@ public ReactImageManager(
       Object callerContext) {
     mDraweeControllerBuilder = draweeControllerBuilder;
     mCallerContext = callerContext;
+    mResourceDrawableIdHelper = new ResourceDrawableIdHelper();
   }
 
   public ReactImageManager() {
+    // Lazily initialize as FrescoModule have not been initialized yet
     mDraweeControllerBuilder = null;
     mCallerContext = null;
+    mResourceDrawableIdHelper = new ResourceDrawableIdHelper();
+  }
+
+  public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
+    if (mDraweeControllerBuilder == null) {
+      mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder();
+    }
+    return mDraweeControllerBuilder;
+  }
+
+  public Object getCallerContext() {
+    return mCallerContext;
   }
 
   @Override
   public ReactImageView createViewInstance(ThemedReactContext context) {
     return new ReactImageView(
         context,
-        mDraweeControllerBuilder == null ?
-            Fresco.newDraweeControllerBuilder() : mDraweeControllerBuilder,
-        mCallerContext);
+        getDraweeControllerBuilder(),
+        getCallerContext());
   }
 
   // In JS this is Image.props.source.uri
   @ReactProp(name = "src")
   public void setSource(ReactImageView view, @Nullable String source) {
-    view.setSource(source);
+    view.setSource(source, mResourceDrawableIdHelper);
+  }
+
+  // In JS this is Image.props.loadingIndicatorSource.uri
+  @ReactProp(name = "loadingIndicatorSrc")
+  public void setLoadingIndicatorSource(ReactImageView view, @Nullable String source) {
+    view.setLoadingIndicatorSource(source, mResourceDrawableIdHelper);
   }
 
   @ReactProp(name = "borderColor", customType = "Color")
@@ -68,6 +92,15 @@ public void setBorderColor(ReactImageView view, @Nullable Integer borderColor) {
     }
   }
 
+  @ReactProp(name = "overlayColor")
+  public void setOverlayColor(ReactImageView view, @Nullable Integer overlayColor) {
+    if (overlayColor == null) {
+      view.setOverlayColor(Color.TRANSPARENT);
+    } else {
+      view.setOverlayColor(overlayColor);
+    }
+  }
+
   @ReactProp(name = "borderWidth")
   public void setBorderWidth(ReactImageView view, float borderWidth) {
     view.setBorderWidth(borderWidth);
@@ -88,7 +121,7 @@ public void setTintColor(ReactImageView view, @Nullable Integer tintColor) {
     if (tintColor == null) {
       view.clearColorFilter();
     } else {
-      view.setColorFilter(tintColor);
+      view.setColorFilter(tintColor, Mode.SRC_IN);
     }
   }
 
@@ -102,6 +135,23 @@ public void setFadeDuration(ReactImageView view, int durationMs) {
     view.setFadeDuration(durationMs);
   }
 
+  @ReactProp(name = "shouldNotifyLoadEvents")
+  public void setLoadHandlersRegistered(ReactImageView view, boolean shouldNotifyLoadEvents) {
+    view.setShouldNotifyLoadEvents(shouldNotifyLoadEvents);
+  }
+
+  @Override
+  public @Nullable Map getExportedCustomDirectEventTypeConstants() {
+    return MapBuilder.of(
+      ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_START),
+        MapBuilder.of("registrationName", "onLoadStart"),
+      ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD),
+        MapBuilder.of("registrationName", "onLoad"),
+      ImageLoadEvent.eventNameForType(ImageLoadEvent.ON_LOAD_END),
+        MapBuilder.of("registrationName", "onLoadEnd")
+    );
+  }
+
   @Override
   protected void onAfterUpdateTransaction(ReactImageView view) {
     super.onAfterUpdateTransaction(view);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
index d741356fe7f1..c45c0403b237 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.java
@@ -15,28 +15,38 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 
 import com.facebook.common.util.UriUtil;
 import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
+import com.facebook.drawee.controller.BaseControllerListener;
 import com.facebook.drawee.controller.ControllerListener;
+import com.facebook.drawee.controller.ForwardingControllerListener;
+import com.facebook.drawee.drawable.AutoRotateDrawable;
 import com.facebook.drawee.drawable.ScalingUtils;
 import com.facebook.drawee.generic.GenericDraweeHierarchy;
 import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
 import com.facebook.drawee.generic.RoundingParams;
-import com.facebook.drawee.interfaces.DraweeController;
 import com.facebook.drawee.view.GenericDraweeView;
 import com.facebook.imagepipeline.common.ResizeOptions;
+import com.facebook.imagepipeline.image.ImageInfo;
 import com.facebook.imagepipeline.request.BasePostprocessor;
 import com.facebook.imagepipeline.request.ImageRequest;
 import com.facebook.imagepipeline.request.ImageRequestBuilder;
 import com.facebook.imagepipeline.request.Postprocessor;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.PixelUtil;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.events.EventDispatcher;
 
 /**
  * Wrapper class around Fresco's GenericDraweeView, enabling persisting props across multiple view
@@ -44,7 +54,7 @@
  */
 public class ReactImageView extends GenericDraweeView {
 
-  private static final int REMOTE_IMAGE_FADE_DURATION_MS = 300;
+  public static final int REMOTE_IMAGE_FADE_DURATION_MS = 300;
 
   /*
    * Implementation note re rounded corners:
@@ -96,7 +106,9 @@ public void process(Bitmap output, Bitmap source) {
   }
 
   private @Nullable Uri mUri;
+  private @Nullable Drawable mLoadingImageDrawable;
   private int mBorderColor;
+  private int mOverlayColor;
   private float mBorderWidth;
   private float mBorderRadius;
   private ScalingUtils.ScaleType mScaleType;
@@ -104,8 +116,9 @@ public void process(Bitmap output, Bitmap source) {
   private boolean mIsLocalImage;
   private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
   private final RoundedCornerPostprocessor mRoundedCornerPostprocessor;
-  private final @Nullable Object mCallerContext;
   private @Nullable ControllerListener mControllerListener;
+  private @Nullable ControllerListener mControllerForTesting;
+  private final @Nullable Object mCallerContext;
   private int mFadeDurationMs = -1;
   private boolean mProgressiveRenderingEnabled;
 
@@ -127,11 +140,58 @@ public ReactImageView(
     mCallerContext = callerContext;
   }
 
+  public void setShouldNotifyLoadEvents(boolean shouldNotify) {
+    if (!shouldNotify) {
+      mControllerListener = null;
+    } else {
+      final EventDispatcher mEventDispatcher = ((ReactContext) getContext()).
+          getNativeModule(UIManagerModule.class).getEventDispatcher();
+
+      mControllerListener = new BaseControllerListener<ImageInfo>() {
+        @Override
+        public void onSubmit(String id, Object callerContext) {
+          mEventDispatcher.dispatchEvent(
+              new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_START)
+          );
+        }
+
+        @Override
+        public void onFinalImageSet(
+            String id,
+            @Nullable final ImageInfo imageInfo,
+            @Nullable Animatable animatable) {
+          if (imageInfo != null) {
+            mEventDispatcher.dispatchEvent(
+                new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)
+            );
+            mEventDispatcher.dispatchEvent(
+                new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD)
+            );
+          }
+        }
+
+        @Override
+        public void onFailure(String id, Throwable throwable) {
+          mEventDispatcher.dispatchEvent(
+              new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)
+          );
+        }
+      };
+    }
+
+    mIsDirty = true;
+  }
+
   public void setBorderColor(int borderColor) {
     mBorderColor = borderColor;
     mIsDirty = true;
   }
 
+  public void setOverlayColor(int overlayColor) {
+    mOverlayColor = overlayColor;
+    mIsDirty = true;
+  }
+
   public void setBorderWidth(float borderWidth) {
     mBorderWidth = PixelUtil.toPixelFromDIP(borderWidth);
     mIsDirty = true;
@@ -147,7 +207,9 @@ public void setScaleType(ScalingUtils.ScaleType scaleType) {
     mIsDirty = true;
   }
 
-  public void setSource(@Nullable String source) {
+  public void setSource(
+      @Nullable String source,
+      ResourceDrawableIdHelper resourceDrawableIdHelper) {
     mUri = null;
     if (source != null) {
       try {
@@ -160,7 +222,7 @@ public void setSource(@Nullable String source) {
         // ignore malformed uri, then attempt to extract resource ID.
       }
       if (mUri == null) {
-        mUri = getResourceDrawableUri(getContext(), source);
+        mUri = resourceDrawableIdHelper.getResourceDrawableUri(getContext(), source);
         mIsLocalImage = true;
       } else {
         mIsLocalImage = false;
@@ -169,6 +231,15 @@ public void setSource(@Nullable String source) {
     mIsDirty = true;
   }
 
+  public void setLoadingIndicatorSource(
+      @Nullable String name,
+      ResourceDrawableIdHelper resourceDrawableIdHelper) {
+    Drawable drawable = resourceDrawableIdHelper.getResourceDrawable(getContext(), name);
+    mLoadingImageDrawable =
+        drawable != null ? (Drawable) new AutoRotateDrawable(drawable, 1000) : null;
+    mIsDirty = true;
+  }
+
   public void setProgressiveRenderingEnabled(boolean enabled) {
     mProgressiveRenderingEnabled = enabled;
     // no worth marking as dirty if it already rendered..
@@ -193,6 +264,10 @@ public void maybeUpdateView() {
     GenericDraweeHierarchy hierarchy = getHierarchy();
     hierarchy.setActualImageScaleType(mScaleType);
 
+    if (mLoadingImageDrawable != null) {
+      hierarchy.setPlaceholderImage(mLoadingImageDrawable, ScalingUtils.ScaleType.CENTER);
+    }
+
     boolean usePostprocessorScaling =
         mScaleType != ScalingUtils.ScaleType.CENTER_CROP &&
         mScaleType != ScalingUtils.ScaleType.FOCUS_CROP;
@@ -201,6 +276,12 @@ public void maybeUpdateView() {
     RoundingParams roundingParams = hierarchy.getRoundingParams();
     roundingParams.setCornersRadius(hierarchyRadius);
     roundingParams.setBorder(mBorderColor, mBorderWidth);
+    if (mOverlayColor != Color.TRANSPARENT) {
+        roundingParams.setOverlayColor(mOverlayColor);
+    } else {
+        // make sure the default rounding method is used.
+        roundingParams.setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY);
+    }
     hierarchy.setRoundingParams(roundingParams);
     hierarchy.setFadeDuration(
         mFadeDurationMs >= 0
@@ -214,24 +295,37 @@ public void maybeUpdateView() {
     ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
         .setPostprocessor(postprocessor)
         .setResizeOptions(resizeOptions)
+        .setAutoRotateEnabled(true)
         .setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
         .build();
 
-    DraweeController draweeController = mDraweeControllerBuilder
-        .reset()
+    // This builder is reused
+    mDraweeControllerBuilder.reset();
+
+    mDraweeControllerBuilder
         .setAutoPlayAnimations(true)
         .setCallerContext(mCallerContext)
         .setOldController(getController())
-        .setImageRequest(imageRequest)
-        .setControllerListener(mControllerListener)
-        .build();
-    setController(draweeController);
+        .setImageRequest(imageRequest);
+
+    if (mControllerListener != null && mControllerForTesting != null) {
+      ForwardingControllerListener combinedListener = new ForwardingControllerListener();
+      combinedListener.addListener(mControllerListener);
+      combinedListener.addListener(mControllerForTesting);
+      mDraweeControllerBuilder.setControllerListener(combinedListener);
+    } else if (mControllerForTesting != null) {
+      mDraweeControllerBuilder.setControllerListener(mControllerForTesting);
+    } else if (mControllerListener != null) {
+      mDraweeControllerBuilder.setControllerListener(mControllerListener);
+    }
+
+    setController(mDraweeControllerBuilder.build());
     mIsDirty = false;
   }
 
   // VisibleForTesting
   public void setControllerListener(ControllerListener controllerListener) {
-    mControllerListener = controllerListener;
+    mControllerForTesting = controllerListener;
     mIsDirty = true;
     maybeUpdateView();
   }
@@ -258,19 +352,4 @@ private static boolean shouldResize(@Nullable Uri uri) {
     // has no control over the original size
     return uri != null && (UriUtil.isLocalContentUri(uri) || UriUtil.isLocalFileUri(uri));
   }
-
-  private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
-    if (name == null || name.isEmpty()) {
-      return null;
-    }
-    name = name.toLowerCase().replace("-", "_");
-    int resId = context.getResources().getIdentifier(
-        name,
-        "drawable",
-        context.getPackageName());
-    return new Uri.Builder()
-        .scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
-        .path(String.valueOf(resId))
-        .build();
-  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/image/ResourceDrawableIdHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ResourceDrawableIdHelper.java
new file mode 100644
index 000000000000..17d930601b64
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ResourceDrawableIdHelper.java
@@ -0,0 +1,55 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.views.image;
+
+import javax.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.facebook.common.util.UriUtil;
+
+/**
+ * Helper class for obtaining information about local images.
+ */
+/* package */ class ResourceDrawableIdHelper {
+
+  private Map<String, Integer> mResourceDrawableIdMap;
+
+  public ResourceDrawableIdHelper() {
+    mResourceDrawableIdMap = new HashMap<String, Integer>();
+  }
+
+  public int getResourceDrawableId(Context context, @Nullable String name) {
+    if (name == null || name.isEmpty()) {
+      return 0;
+    }
+    name = name.toLowerCase().replace("-", "_");
+    if (mResourceDrawableIdMap.containsKey(name)) {
+      return mResourceDrawableIdMap.get(name);
+    }
+    int id = context.getResources().getIdentifier(
+        name,
+        "drawable",
+        context.getPackageName());
+    mResourceDrawableIdMap.put(name, id);
+    return id;
+  }
+
+  public @Nullable Drawable getResourceDrawable(Context context, @Nullable String name) {
+    int resId = getResourceDrawableId(context, name);
+    return resId > 0 ? context.getResources().getDrawable(resId) : null;
+  }
+
+  public Uri getResourceDrawableUri(Context context, @Nullable String name) {
+    int resId = getResourceDrawableId(context, name);
+    return resId > 0 ? new Uri.Builder()
+        .scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
+        .path(String.valueOf(resId))
+        .build() : Uri.EMPTY;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK
new file mode 100644
index 000000000000..24ab25dbbadf
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/BUCK
@@ -0,0 +1,21 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'picker',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':picker',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java
new file mode 100644
index 000000000000..c86709467177
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDialogPickerManager.java
@@ -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.views.picker;
+
+import android.widget.Spinner;
+
+import com.facebook.react.uimanager.ThemedReactContext;
+
+/**
+ * {@link ReactPickerManager} for {@link ReactPicker} with {@link Spinner#MODE_DIALOG}.
+ */
+public class ReactDialogPickerManager extends ReactPickerManager {
+
+  private static final String REACT_CLASS = "AndroidDialogPicker";
+
+  @Override
+  public String getName() {
+    return REACT_CLASS;
+  }
+
+  @Override
+  protected ReactPicker createViewInstance(ThemedReactContext reactContext) {
+    return new ReactPicker(reactContext, Spinner.MODE_DIALOG);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java
new file mode 100644
index 000000000000..c8be5600265c
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactDropdownPickerManager.java
@@ -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.views.picker;
+
+import android.widget.Spinner;
+
+import com.facebook.react.uimanager.ThemedReactContext;
+
+/**
+ * {@link ReactPickerManager} for {@link ReactPicker} with {@link Spinner#MODE_DROPDOWN}.
+ */
+public class ReactDropdownPickerManager extends ReactPickerManager {
+
+  private static final String REACT_CLASS = "AndroidDropdownPicker";
+
+  @Override
+  public String getName() {
+    return REACT_CLASS;
+  }
+
+  @Override
+  protected ReactPicker createViewInstance(ThemedReactContext reactContext) {
+    return new ReactPicker(reactContext, Spinner.MODE_DROPDOWN);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java
new file mode 100644
index 000000000000..2871cd21286b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPicker.java
@@ -0,0 +1,148 @@
+/**
+ * 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.views.picker;
+
+import javax.annotation.Nullable;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+
+import com.facebook.react.common.annotations.VisibleForTesting;
+
+public class ReactPicker extends Spinner {
+
+  private int mMode = MODE_DIALOG;
+  private @Nullable Integer mPrimaryColor;
+  private boolean mSuppressNextEvent;
+  private @Nullable OnSelectListener mOnSelectListener;
+  private @Nullable Integer mStagedSelection;
+
+  /**
+   * Listener interface for ReactPicker events.
+   */
+  public interface OnSelectListener {
+    void onItemSelected(int position);
+  }
+
+  public ReactPicker(Context context) {
+    super(context);
+  }
+
+  public ReactPicker(Context context, int mode) {
+    super(context, mode);
+    mMode = mode;
+  }
+
+  public ReactPicker(Context context, AttributeSet attrs) {
+    super(context, attrs);
+  }
+
+  public ReactPicker(Context context, AttributeSet attrs, int defStyle) {
+    super(context, attrs, defStyle);
+  }
+
+  public ReactPicker(Context context, AttributeSet attrs, int defStyle, int mode) {
+    super(context, attrs, defStyle, mode);
+    mMode = mode;
+  }
+
+  private final Runnable measureAndLayout = new Runnable() {
+    @Override
+    public void run() {
+      measure(
+          MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
+          MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
+      layout(getLeft(), getTop(), getRight(), getBottom());
+    }
+  };
+
+  @Override
+  public void requestLayout() {
+    super.requestLayout();
+
+    // The spinner relies on a measure + layout pass happening after it calls requestLayout().
+    // Without this, the widget never actually changes the selection and doesn't call the
+    // appropriate listeners. Since we override onLayout in our ViewGroups, a layout pass never
+    // happens after a call to requestLayout, so we simulate one here.
+    post(measureAndLayout);
+  }
+
+  public void setOnSelectListener(@Nullable OnSelectListener onSelectListener) {
+    if (getOnItemSelectedListener() == null) {
+      setOnItemSelectedListener(
+          new OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+              if (!mSuppressNextEvent && mOnSelectListener != null) {
+                mOnSelectListener.onItemSelected(position);
+              }
+              mSuppressNextEvent = false;
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> parent) {
+              if (!mSuppressNextEvent && mOnSelectListener != null) {
+                mOnSelectListener.onItemSelected(-1);
+              }
+              mSuppressNextEvent = false;
+            }
+          });
+    }
+    mOnSelectListener = onSelectListener;
+  }
+
+  @Nullable public OnSelectListener getOnSelectListener() {
+    return mOnSelectListener;
+  }
+
+  /**
+   * Will cache "selection" value locally and set it only once {@link #updateStagedSelection} is
+   * called
+   */
+  public void setStagedSelection(int selection) {
+    mStagedSelection = selection;
+  }
+
+  public void updateStagedSelection() {
+    if (mStagedSelection != null) {
+      setSelectionWithSuppressEvent(mStagedSelection);
+      mStagedSelection = null;
+    }
+  }
+
+  /**
+   * Set the selection while suppressing the follow-up {@link OnSelectListener#onItemSelected(int)}
+   * event. This is used so we don't get an event when changing the selection ourselves.
+   *
+   * @param position the position of the selected item
+   */
+  private void setSelectionWithSuppressEvent(int position) {
+    if (position != getSelectedItemPosition()) {
+      mSuppressNextEvent = true;
+      setSelection(position);
+    }
+  }
+
+  public @Nullable Integer getPrimaryColor() {
+    return mPrimaryColor;
+  }
+
+  public void setPrimaryColor(@Nullable Integer primaryColor) {
+    mPrimaryColor = primaryColor;
+  }
+
+  @VisibleForTesting
+  public int getMode() {
+    return mMode;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java
new file mode 100644
index 000000000000..11ea4c839fc9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java
@@ -0,0 +1,163 @@
+/**
+ * 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.views.picker;
+
+import javax.annotation.Nullable;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.SystemClock;
+import com.facebook.react.uimanager.SimpleViewManager;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.react.views.picker.events.PickerItemSelectEvent;
+
+/**
+ * {@link ViewManager} for the {@link ReactPicker} view. This is abstract because the
+ * {@link Spinner} doesn't support setting the mode (dropdown/dialog) outside the constructor, so
+ * that is delegated to the separate {@link ReactDropdownPickerManager} and
+ * {@link ReactDialogPickerManager} components. These are merged back on the JS side into one
+ * React component.
+ */
+public abstract class ReactPickerManager extends SimpleViewManager<ReactPicker> {
+
+  @ReactProp(name = "items")
+  public void setItems(ReactPicker view, @Nullable ReadableArray items) {
+    if (items != null) {
+      ReadableMap[] data = new ReadableMap[items.size()];
+      for (int i = 0; i < items.size(); i++) {
+        data[i] = items.getMap(i);
+      }
+      ReactPickerAdapter adapter = new ReactPickerAdapter(view.getContext(), data);
+      adapter.setPrimaryTextColor(view.getPrimaryColor());
+      view.setAdapter(adapter);
+    } else {
+      view.setAdapter(null);
+    }
+  }
+
+  @ReactProp(name = ViewProps.COLOR, customType = "Color")
+  public void setColor(ReactPicker view, @Nullable Integer color) {
+    view.setPrimaryColor(color);
+    ReactPickerAdapter adapter = (ReactPickerAdapter) view.getAdapter();
+    if (adapter != null) {
+      adapter.setPrimaryTextColor(color);
+    }
+  }
+
+  @ReactProp(name = "prompt")
+  public void setPrompt(ReactPicker view, @Nullable String prompt) {
+    view.setPrompt(prompt);
+  }
+
+  @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
+  public void setEnabled(ReactPicker view, boolean enabled) {
+    view.setEnabled(enabled);
+  }
+
+  @ReactProp(name = "selected")
+  public void setSelected(ReactPicker view, int selected) {
+    view.setStagedSelection(selected);
+  }
+
+  @Override
+  protected void onAfterUpdateTransaction(ReactPicker view) {
+    super.onAfterUpdateTransaction(view);
+    view.updateStagedSelection();
+  }
+
+  @Override
+  protected void addEventEmitters(
+      final ThemedReactContext reactContext,
+      final ReactPicker picker) {
+    picker.setOnSelectListener(
+            new PickerEventEmitter(
+                    picker,
+                    reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()));
+  }
+
+  private static class ReactPickerAdapter extends ArrayAdapter<ReadableMap> {
+
+    private final LayoutInflater mInflater;
+    private @Nullable Integer mPrimaryTextColor;
+
+    public ReactPickerAdapter(Context context, ReadableMap[] data) {
+      super(context, 0, data);
+
+      mInflater = (LayoutInflater) Assertions.assertNotNull(
+          context.getSystemService(Context.LAYOUT_INFLATER_SERVICE));
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      return getView(position, convertView, parent, false);
+    }
+
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+      return getView(position, convertView, parent, true);
+    }
+
+    private View getView(int position, View convertView, ViewGroup parent, boolean isDropdown) {
+      ReadableMap item = getItem(position);
+
+      if (convertView == null) {
+        int layoutResId = isDropdown
+            ? android.R.layout.simple_spinner_dropdown_item
+            : android.R.layout.simple_spinner_item;
+        convertView = mInflater.inflate(layoutResId, parent, false);
+      }
+
+      TextView textView = (TextView) convertView;
+      textView.setText(item.getString("label"));
+      if (!isDropdown && mPrimaryTextColor != null) {
+        textView.setTextColor(mPrimaryTextColor);
+      } else if (item.hasKey("color") && !item.isNull("color")) {
+        textView.setTextColor(item.getInt("color"));
+      }
+
+      return convertView;
+    }
+
+    public void setPrimaryTextColor(@Nullable Integer primaryTextColor) {
+      mPrimaryTextColor = primaryTextColor;
+      notifyDataSetChanged();
+    }
+  }
+
+  private static class PickerEventEmitter implements ReactPicker.OnSelectListener {
+
+    private final ReactPicker mReactPicker;
+    private final EventDispatcher mEventDispatcher;
+
+    public PickerEventEmitter(ReactPicker reactPicker, EventDispatcher eventDispatcher) {
+      mReactPicker = reactPicker;
+      mEventDispatcher = eventDispatcher;
+    }
+
+    @Override
+    public void onItemSelected(int position) {
+      mEventDispatcher.dispatchEvent( new PickerItemSelectEvent(
+              mReactPicker.getId(), SystemClock.nanoTime(), position));
+    }
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java
new file mode 100644
index 000000000000..cabe53fdc41f
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/events/PickerItemSelectEvent.java
@@ -0,0 +1,42 @@
+/**
+ * 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.views.picker.events;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+public class PickerItemSelectEvent extends Event<PickerItemSelectEvent> {
+  public static final String EVENT_NAME = "topSelect";
+
+  private final int mPosition;
+
+  public PickerItemSelectEvent(int id, long uptimeMillis, int position) {
+    super(id, uptimeMillis);
+    mPosition = position;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
+  }
+
+  private WritableMap serializeEventData() {
+    WritableMap eventData = Arguments.createMap();
+    eventData.putInt("position", mPosition);
+    return eventData;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK
index ef7fc96092cd..fed1ed6a0503 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/BUCK
@@ -8,8 +8,9 @@ android_library(
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java
index dc49c4aa1327..fffba6ab867d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ProgressBarShadowNode.java
@@ -22,7 +22,7 @@
 import com.facebook.csslayout.CSSNode;
 import com.facebook.csslayout.MeasureOutput;
 import com.facebook.react.uimanager.LayoutShadowNode;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactProp;
 
 /**
  * Node responsible for holding the style of the ProgressBar, see under
@@ -51,7 +51,7 @@ public void setStyle(@Nullable String style) {
   }
 
   @Override
-  public void measure(CSSNode node, float width, MeasureOutput measureOutput) {
+  public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
     final int style = ReactProgressBarViewManager.getStyleFromString(getStyle());
     if (!mMeasured.contains(style)) {
       ProgressBar progressBar = new ProgressBar(getThemedContext(), null, style);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java
index 01aa4a0b8c8a..82601ceddcc2 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/progressbar/ReactProgressBarViewManager.java
@@ -13,7 +13,7 @@
 
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.uimanager.BaseViewManager;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewProps;
 
@@ -31,7 +31,7 @@ public class ReactProgressBarViewManager extends
   /* package */ static final String PROP_PROGRESS = "progress";
 
   /* package */ static final String REACT_CLASS = "AndroidProgressBar";
-  /* package */ static final String DEFAULT_STYLE = "Large";
+  /* package */ static final String DEFAULT_STYLE = "Normal";
 
   @Override
   public String getName() {
@@ -99,6 +99,8 @@ protected void onAfterUpdateTransaction(ProgressBarContainerView view) {
       return android.R.attr.progressBarStyleSmallInverse;
     } else if (styleStr.equals("LargeInverse")) {
       return android.R.attr.progressBarStyleLargeInverse;
+    } else if (styleStr.equals("Normal")) {
+      return android.R.attr.progressBarStyle;
     } else {
       throw new JSApplicationIllegalArgumentException("Unknown ProgressBar style: " + styleStr);
     }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK
index 54414c312e23..1457bc8e6ced 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK
@@ -8,12 +8,13 @@ android_library(
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/touch:touch'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_target('java/com/facebook/react/views/scroll:scroll'),
     react_native_target('java/com/facebook/react/views/view:view'),
-    '//third-party/android-support-v7/recyclerview:recyclerview',
-    '//third-party/java/android/support/v4:lib-support-v4',
-    '//third-party/java/infer-annotations:infer-annotations',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/android/support/v7/recyclerview:recyclerview'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java
new file mode 100644
index 000000000000..e7941872c1ba
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/ContentSizeChangeEvent.java
@@ -0,0 +1,40 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.views.recyclerview;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.PixelUtil;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event dispatched by {@link RecyclerViewBackedScrollView} when total height of it's children
+ * changes
+ */
+public class ContentSizeChangeEvent extends Event<ContentSizeChangeEvent> {
+
+  public static final String EVENT_NAME = "topContentSizeChange";
+
+  private final int mWidth;
+  private final int mHeight;
+
+  public ContentSizeChangeEvent(int viewTag, long timestampMs, int width, int height) {
+    super(viewTag, timestampMs);
+    mWidth = width;
+    mHeight = height;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    WritableMap data = Arguments.createMap();
+    data.putDouble("width", PixelUtil.toDIPFromPixel(mWidth));
+    data.putDouble("height", PixelUtil.toDIPFromPixel(mHeight));
+    rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java
index a419b58c38e8..337fd5cfb393 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java
@@ -6,7 +6,6 @@
 import java.util.List;
 
 import android.content.Context;
-import android.os.SystemClock;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.MotionEvent;
@@ -15,10 +14,12 @@
 
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.events.NativeGestureUtil;
 import com.facebook.react.views.scroll.ScrollEvent;
+import com.facebook.react.views.scroll.ScrollEventType;
 
 /**
  * Wraps {@link RecyclerView} providing interface similar to `ScrollView.js` where each children
@@ -113,24 +114,57 @@ public void onHeightChange(int index, int oldHeight, int newHeight) {
     }
 
     public int getTopOffsetForItem(int index) {
+      // This method is frequently called from the "onScroll" handler of the "RecyclerView" with an
+      // index of first visible item of the view. Implementation of this method takes advantage of
+      // that fact by caching the value for the last index that this method has been called with.
+      //
+      // There are a 2 cases that we optimize for:
+      // 1) The visible item doesn't change between subsequent "onScroll" calls, in that case we
+      //    don't need to calculate anything, just return the cached value
+      // 2) The next visible item will be the one that is adjacent to the item that we store the
+      //    cached value for: index + 1 when scrolling down or index - 1 when scrolling up. Then it
+      //    is sufficient to add/subtract height of item at the "last index"
+      //
+      // The implementation accounts for the cases when next index is not necessarily a subsequent
+      // number of the cached one. In which case we try to minimize the number of rows we will loop
+      // through.
       if (mLastRequestedPosition != index) {
-        int sum = 0;
-        int startIndex = 0;
+        int sum;
+
         if (mLastRequestedPosition < index) {
+          // This can either happen when we're scrolling down or if the cached value has never been
+          // calculated
+          int startIndex;
+
           if (mLastRequestedPosition != -1) {
+            // We already have the value cached, let's use it and only add heights of the items
+            // starting at the index we have the cached value for
             sum = mOffsetForLastPosition;
             startIndex = mLastRequestedPosition;
+          } else {
+            sum = 0;
+            startIndex = 0;
           }
+
           for (int i = startIndex; i < index; i++) {
             sum += mReactListAdapter.mViews.get(i).getMeasuredHeight();
           }
-        }
-        else {
+        } else {
+          // We are scrolling up, we can either use cached value and subtract heights of rows
+          // between mLastRequestPosition and index, or we can calculate the height starting from 0
+          // (this can be quite a frequent case as well, when the list implements "jump to the top"
+          // action). We just go for the option that require less calculations
           if (index < (mLastRequestedPosition - index)) {
+            // index is relatively small, it's faster to calculate the sum starting from 0
+            sum = 0;
             for (int i = 0; i < index; i++) {
               sum += mReactListAdapter.mViews.get(i).getMeasuredHeight();
             }
           } else {
+            // index is "closer" to the last cached index than it is to 0. We can reuse cached sum
+            // and calculate the new sum by subtracting heights of the elements between
+            // "mLastRequestPosition" and "index"
+            sum = mOffsetForLastPosition;
             for (int i = mLastRequestedPosition - 1; i >= index; i--) {
               sum -= mReactListAdapter.mViews.get(i).getMeasuredHeight();
             }
@@ -148,6 +182,7 @@ public int getTopOffsetForItem(int index) {
 
     private final List<View> mViews = new ArrayList<>();
     private final ScrollOffsetTracker mScrollOffsetTracker;
+    private final RecyclerViewBackedScrollView mScrollView;
     private int mTotalChildrenHeight = 0;
 
     // The following `OnLayoutChangeListsner` is attached to the views stored in the adapter
@@ -173,7 +208,7 @@ public void onLayoutChange(
         int newHeight = (bottom - top);
 
         if (oldHeight != newHeight) {
-          mTotalChildrenHeight = mTotalChildrenHeight - oldHeight + newHeight;
+          updateTotalChildrenHeight(newHeight - oldHeight);
           mScrollOffsetTracker.onHeightChange(mViews.indexOf(v), oldHeight, newHeight);
 
           // Since "wrapper" view position +dimensions are not managed by NativeViewHierarchyManager
@@ -200,7 +235,8 @@ public void onLayoutChange(
       }
     };
 
-    public ReactListAdapter() {
+    public ReactListAdapter(RecyclerViewBackedScrollView scrollView) {
+      mScrollView = scrollView;
       mScrollOffsetTracker = new ScrollOffsetTracker(this);
       setHasStableIds(true);
     }
@@ -208,7 +244,7 @@ public ReactListAdapter() {
     public void addView(View child, int index) {
       mViews.add(index, child);
 
-      mTotalChildrenHeight += child.getMeasuredHeight();
+      updateTotalChildrenHeight(child.getMeasuredHeight());
       child.addOnLayoutChangeListener(mChildLayoutChangeListener);
 
       notifyItemInserted(index);
@@ -219,12 +255,19 @@ public void removeViewAt(int index) {
       if (child != null) {
         mViews.remove(index);
         child.removeOnLayoutChangeListener(mChildLayoutChangeListener);
-        mTotalChildrenHeight -= child.getMeasuredHeight();
+        updateTotalChildrenHeight(-child.getMeasuredHeight());
 
         notifyItemRemoved(index);
       }
     }
 
+    private void updateTotalChildrenHeight(int delta) {
+      if (delta != 0) {
+        mTotalChildrenHeight += delta;
+        mScrollView.onTotalChildrenHeightChange(mTotalChildrenHeight);
+      }
+    }
+
     @Override
     public ConcreteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       return new ConcreteViewHolder(new RecyclableWrapperViewGroup(parent.getContext()));
@@ -268,6 +311,12 @@ public int getTopOffsetForItem(int index) {
     }
   }
 
+  private boolean mSendContentSizeChangeEvents;
+
+  public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
+    mSendContentSizeChangeEvents = sendContentSizeChangeEvents;
+  }
+
   private int calculateAbsoluteOffset() {
     int offsetY = 0;
     if (getChildCount() > 0) {
@@ -295,7 +344,8 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
     ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher()
         .dispatchEvent(ScrollEvent.obtain(
                 getId(),
-                SystemClock.uptimeMillis(),
+                SystemClock.nanoTime(),
+                ScrollEventType.SCROLL,
                 0, /* offsetX = 0, horizontal scrolling only */
                 calculateAbsoluteOffset(),
                 getWidth(),
@@ -304,12 +354,23 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
                 getHeight()));
   }
 
+  private void onTotalChildrenHeightChange(int newTotalChildrenHeight) {
+    if (mSendContentSizeChangeEvents) {
+      ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher()
+          .dispatchEvent(new ContentSizeChangeEvent(
+                  getId(),
+                  SystemClock.nanoTime(),
+                  getWidth(),
+                  newTotalChildrenHeight));
+    }
+  }
+
   public RecyclerViewBackedScrollView(Context context) {
     super(context);
     setHasFixedSize(true);
     setItemAnimator(new NotAnimatedItemAnimator());
     setLayoutManager(new LinearLayoutManager(context));
-    setAdapter(new ReactListAdapter());
+    setAdapter(new ReactListAdapter(this));
   }
 
   /*package*/ void addViewToAdapter(View child, int index) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java
index 8c0134dad921..dfac0da4e105 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java
@@ -4,12 +4,17 @@
 
 import javax.annotation.Nullable;
 
+import java.util.Map;
+
 import android.view.View;
 
 import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewGroupManager;
 import com.facebook.react.views.scroll.ReactScrollViewCommandHelper;
+import com.facebook.react.views.scroll.ScrollEventType;
 
 /**
  * View manager for {@link RecyclerViewBackedScrollView}.
@@ -27,6 +32,11 @@ public String getName() {
 
   // TODO(8624925): Implement removeClippedSubviews support for native ListView
 
+  @ReactProp(name = "onContentSizeChange")
+  public void setOnContentSizeChange(RecyclerViewBackedScrollView view, boolean value) {
+    view.setSendContentSizeChangeEvents(value);
+  }
+
   @Override
   protected RecyclerViewBackedScrollView createViewInstance(ThemedReactContext reactContext) {
     return new RecyclerViewBackedScrollView(reactContext);
@@ -65,15 +75,19 @@ public void receiveCommand(
 
   @Override
   public void scrollTo(
-      RecyclerViewBackedScrollView view,
+      RecyclerViewBackedScrollView scrollView,
       ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    view.scrollTo(data.mDestX, data.mDestY, true);
+    scrollView.scrollTo(data.mDestX, data.mDestY, data.mAnimated);
   }
 
   @Override
-  public void scrollWithoutAnimationTo(
-      RecyclerViewBackedScrollView view,
-      ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    view.scrollTo(data.mDestX, data.mDestY, false);
+  public @Nullable
+  Map getExportedCustomDirectEventTypeConstants() {
+    return MapBuilder.builder()
+        .put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"))
+        .put(
+            ContentSizeChangeEvent.EVENT_NAME,
+            MapBuilder.of("registrationName", "onContentSizeChange"))
+        .build();
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK
index d087195129da..22caa2763be5 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK
@@ -9,9 +9,10 @@ android_library(
     react_native_target('java/com/facebook/react/views/view:view'),
     react_native_target('java/com/facebook/react/touch:touch'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
-    react_native_dep('third-party/java/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java
index ec528cedf287..e35dd064755b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java
@@ -9,24 +9,62 @@
 
 package com.facebook.react.views.scroll;
 
+import javax.annotation.Nullable;
+
 import android.content.Context;
+import android.graphics.Rect;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.HorizontalScrollView;
 
+import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.uimanager.MeasureSpecAssertions;
 import com.facebook.react.uimanager.events.NativeGestureUtil;
+import com.facebook.react.views.view.ReactClippingViewGroup;
+import com.facebook.react.views.view.ReactClippingViewGroupHelper;
 
 /**
  * Similar to {@link ReactScrollView} but only supports horizontal scrolling.
  */
-public class ReactHorizontalScrollView extends HorizontalScrollView {
+public class ReactHorizontalScrollView extends HorizontalScrollView implements
+    ReactClippingViewGroup {
 
   private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
 
+  private @Nullable Rect mClippingRect;
+  private boolean mDoneFlinging;
+  private boolean mDragging;
+  private boolean mFlinging;
+  private boolean mRemoveClippedSubviews;
+  private boolean mScrollEnabled = true;
+  private boolean mSendMomentumEvents;
+
   public ReactHorizontalScrollView(Context context) {
     super(context);
   }
 
+  @Override
+  public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
+    if (removeClippedSubviews && mClippingRect == null) {
+      mClippingRect = new Rect();
+    }
+    mRemoveClippedSubviews = removeClippedSubviews;
+    updateClippingRect();
+  }
+
+  @Override
+  public boolean getRemoveClippedSubviews() {
+    return mRemoveClippedSubviews;
+  }
+
+  public void setSendMomentumEvents(boolean sendMomentumEvents) {
+    mSendMomentumEvents = sendMomentumEvents;
+  }
+
+  public void setScrollEnabled(boolean scrollEnabled) {
+    mScrollEnabled = scrollEnabled;
+  }
+
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
@@ -47,17 +85,104 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) {
     super.onScrollChanged(x, y, oldX, oldY);
 
     if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
-      ReactScrollViewHelper.emitScrollEvent(this, x, y);
+      if (mRemoveClippedSubviews) {
+        updateClippingRect();
+      }
+
+      if (mFlinging) {
+        mDoneFlinging = false;
+      }
+
+      ReactScrollViewHelper.emitScrollEvent(this);
     }
   }
 
   @Override
   public boolean onInterceptTouchEvent(MotionEvent ev) {
+    if (!mScrollEnabled) {
+      return false;
+    }
+
     if (super.onInterceptTouchEvent(ev)) {
       NativeGestureUtil.notifyNativeGestureStarted(this, ev);
+      ReactScrollViewHelper.emitScrollBeginDragEvent(this);
+      mDragging = true;
       return true;
     }
 
     return false;
   }
+
+  @Override
+  public boolean onTouchEvent(MotionEvent ev) {
+    if (!mScrollEnabled) {
+      return false;
+    }
+
+    int action = ev.getAction() & MotionEvent.ACTION_MASK;
+    if (action == MotionEvent.ACTION_UP && mDragging) {
+      ReactScrollViewHelper.emitScrollEndDragEvent(this);
+      mDragging = false;
+    }
+    return super.onTouchEvent(ev);
+  }
+
+  @Override
+  public void fling(int velocityX) {
+    super.fling(velocityX);
+    if (mSendMomentumEvents) {
+      mFlinging = true;
+      ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
+      Runnable r = new Runnable() {
+        @Override
+        public void run() {
+          if (mDoneFlinging) {
+            mFlinging = false;
+            ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this);
+          } else {
+            mDoneFlinging = true;
+            ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
+          }
+        }
+      };
+      postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
+    }
+  }
+
+
+  @Override
+  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+    super.onSizeChanged(w, h, oldw, oldh);
+    if (mRemoveClippedSubviews) {
+      updateClippingRect();
+    }
+  }
+
+  @Override
+  protected void onAttachedToWindow() {
+    super.onAttachedToWindow();
+    if (mRemoveClippedSubviews) {
+      updateClippingRect();
+    }
+  }
+
+  @Override
+  public void updateClippingRect() {
+    if (!mRemoveClippedSubviews) {
+      return;
+    }
+
+    Assertions.assertNotNull(mClippingRect);
+
+    ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
+    View contentView = getChildAt(0);
+    if (contentView instanceof ReactClippingViewGroup) {
+      ((ReactClippingViewGroup) contentView).updateClippingRect();
+    }
+  }
+
+  @Override
+  public void getClippingRect(Rect outClippingRect) {
+    outClippingRect.set(Assertions.assertNotNull(mClippingRect));
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java
index e8ee9fff88e4..69185fef89cf 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java
@@ -12,9 +12,10 @@
 import javax.annotation.Nullable;
 
 import com.facebook.react.bridge.ReadableArray;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewGroupManager;
+import com.facebook.react.views.view.ReactClippingViewGroupHelper;
 
 /**
  * View manager for {@link ReactHorizontalScrollView} components.
@@ -38,11 +39,34 @@ public ReactHorizontalScrollView createViewInstance(ThemedReactContext context)
     return new ReactHorizontalScrollView(context);
   }
 
+  @ReactProp(name = "scrollEnabled", defaultBoolean = true)
+  public void setScrollEnabled(ReactHorizontalScrollView view, boolean value) {
+    view.setScrollEnabled(value);
+  }
+
   @ReactProp(name = "showsHorizontalScrollIndicator")
   public void setShowsHorizontalScrollIndicator(ReactHorizontalScrollView view, boolean value) {
     view.setHorizontalScrollBarEnabled(value);
   }
 
+  @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
+  public void setRemoveClippedSubviews(ReactHorizontalScrollView view, boolean removeClippedSubviews) {
+    view.setRemoveClippedSubviews(removeClippedSubviews);
+  }
+
+  /**
+   * Computing momentum events is potentially expensive since we post a runnable on the UI thread
+   * to see when it is done.  We only do that if {@param sendMomentumEvents} is set to true.  This
+   * is handled automatically in js by checking if there is a listener on the momentum events.
+   *
+   * @param view
+   * @param sendMomentumEvents
+   */
+  @ReactProp(name = "sendMomentumEvents")
+  public void setSendMomentumEvents(ReactHorizontalScrollView view, boolean sendMomentumEvents) {
+    view.setSendMomentumEvents(sendMomentumEvents);
+  }
+
   @Override
   public void receiveCommand(
       ReactHorizontalScrollView scrollView,
@@ -55,13 +79,10 @@ public void receiveCommand(
   public void scrollTo(
       ReactHorizontalScrollView scrollView,
       ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    scrollView.smoothScrollTo(data.mDestX, data.mDestY);
-  }
-
-  @Override
-  public void scrollWithoutAnimationTo(
-      ReactHorizontalScrollView scrollView,
-      ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    scrollView.scrollTo(data.mDestX, data.mDestY);
+    if (data.mAnimated) {
+      scrollView.smoothScrollTo(data.mDestX, data.mDestY);
+    } else {
+      scrollView.scrollTo(data.mDestX, data.mDestY);
+    }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
index cc8098efcd16..5e87db3bf5d8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
@@ -34,13 +34,26 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
 
   private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
 
-  private boolean mRemoveClippedSubviews;
   private @Nullable Rect mClippingRect;
+  private boolean mDoneFlinging;
+  private boolean mDragging;
+  private boolean mFlinging;
+  private boolean mRemoveClippedSubviews;
+  private boolean mScrollEnabled = true;
+  private boolean mSendMomentumEvents;
 
   public ReactScrollView(Context context) {
     super(context);
   }
 
+  public void setSendMomentumEvents(boolean sendMomentumEvents) {
+    mSendMomentumEvents = sendMomentumEvents;
+  }
+
+  public void setScrollEnabled(boolean scrollEnabled) {
+    mScrollEnabled = scrollEnabled;
+  }
+
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
@@ -64,6 +77,14 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
     }
   }
 
+  @Override
+  protected void onAttachedToWindow() {
+    super.onAttachedToWindow();
+    if (mRemoveClippedSubviews) {
+      updateClippingRect();
+    }
+  }
+
   @Override
   protected void onScrollChanged(int x, int y, int oldX, int oldY) {
     super.onScrollChanged(x, y, oldX, oldY);
@@ -73,20 +94,44 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) {
         updateClippingRect();
       }
 
-      ReactScrollViewHelper.emitScrollEvent(this, x, y);
+      if (mFlinging) {
+        mDoneFlinging = false;
+      }
+
+      ReactScrollViewHelper.emitScrollEvent(this);
     }
   }
 
   @Override
   public boolean onInterceptTouchEvent(MotionEvent ev) {
+    if (!mScrollEnabled) {
+      return false;
+    }
+
     if (super.onInterceptTouchEvent(ev)) {
       NativeGestureUtil.notifyNativeGestureStarted(this, ev);
+      ReactScrollViewHelper.emitScrollBeginDragEvent(this);
+      mDragging = true;
       return true;
     }
 
     return false;
   }
 
+  @Override
+  public boolean onTouchEvent(MotionEvent ev) {
+    if (!mScrollEnabled) {
+      return false;
+    }
+
+    int action = ev.getAction() & MotionEvent.ACTION_MASK;
+    if (action == MotionEvent.ACTION_UP && mDragging) {
+      ReactScrollViewHelper.emitScrollEndDragEvent(this);
+      mDragging = false;
+    }
+    return super.onTouchEvent(ev);
+  }
+
   @Override
   public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
     if (removeClippedSubviews && mClippingRect == null) {
@@ -120,4 +165,26 @@ public void updateClippingRect() {
   public void getClippingRect(Rect outClippingRect) {
     outClippingRect.set(Assertions.assertNotNull(mClippingRect));
   }
+
+  @Override
+  public void fling(int velocityY) {
+    super.fling(velocityY);
+    if (mSendMomentumEvents) {
+      mFlinging = true;
+      ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
+      Runnable r = new Runnable() {
+        @Override
+        public void run() {
+          if (mDoneFlinging) {
+            mFlinging = false;
+            ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
+          } else {
+            mDoneFlinging = true;
+            ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
+          }
+        }
+      };
+      postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
+    }
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java
index a5eb1421c089..aa9560df071a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java
@@ -25,29 +25,27 @@
 public class ReactScrollViewCommandHelper {
 
   public static final int COMMAND_SCROLL_TO = 1;
-  public static final int COMMAND_SCROLL_WITHOUT_ANIMATION_TO = 2;
 
   public interface ScrollCommandHandler<T> {
     void scrollTo(T scrollView, ScrollToCommandData data);
-    void scrollWithoutAnimationTo(T scrollView, ScrollToCommandData data);
   }
 
   public static class ScrollToCommandData {
 
     public final int mDestX, mDestY;
+    public final boolean mAnimated;
 
-    ScrollToCommandData(int destX, int destY) {
+    ScrollToCommandData(int destX, int destY, boolean animated) {
       mDestX = destX;
       mDestY = destY;
+      mAnimated = animated;
     }
   }
 
   public static Map<String,Integer> getCommandsMap() {
     return MapBuilder.of(
         "scrollTo",
-        COMMAND_SCROLL_TO,
-        "scrollWithoutAnimationTo",
-        COMMAND_SCROLL_WITHOUT_ANIMATION_TO);
+        COMMAND_SCROLL_TO);
   }
 
   public static <T> void receiveCommand(
@@ -62,13 +60,8 @@ public static <T> void receiveCommand(
       case COMMAND_SCROLL_TO: {
         int destX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0)));
         int destY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1)));
-        viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY));
-        return;
-      }
-      case COMMAND_SCROLL_WITHOUT_ANIMATION_TO: {
-        int destX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0)));
-        int destY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1)));
-        viewManager.scrollWithoutAnimationTo(scrollView, new ScrollToCommandData(destX, destY));
+        boolean animated = args.getBoolean(2);
+        viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, animated));
         return;
       }
       default:
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java
index 12f5606fc8a6..fd46f677b795 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java
@@ -9,11 +9,11 @@
 
 package com.facebook.react.views.scroll;
 
-import android.os.SystemClock;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.UIManagerModule;
 
 /**
@@ -21,18 +21,46 @@
  */
 public class ReactScrollViewHelper {
 
+  public static final long MOMENTUM_DELAY = 20;
+
   /**
    * Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}.
    */
-  /* package */ static void emitScrollEvent(ViewGroup scrollView, int scrollX, int scrollY) {
+  public static void emitScrollEvent(ViewGroup scrollView) {
+    emitScrollEvent(scrollView, ScrollEventType.SCROLL);
+  }
+
+  public static void emitScrollBeginDragEvent(ViewGroup scrollView) {
+    emitScrollEvent(scrollView, ScrollEventType.BEGIN_DRAG);
+  }
+
+  public static void emitScrollEndDragEvent(ViewGroup scrollView) {
+    emitScrollEvent(scrollView, ScrollEventType.END_DRAG);
+  }
+
+  public static void emitScrollMomentumBeginEvent(ViewGroup scrollView) {
+    emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_BEGIN);
+  }
+
+  public static void emitScrollMomentumEndEvent(ViewGroup scrollView) {
+    emitScrollEvent(scrollView, ScrollEventType.MOMENTUM_END);
+  }
+
+  private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scrollEventType) {
     View contentView = scrollView.getChildAt(0);
+
+    if (contentView == null) {
+      return;
+    }
+
     ReactContext reactContext = (ReactContext) scrollView.getContext();
     reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
         ScrollEvent.obtain(
             scrollView.getId(),
-            SystemClock.uptimeMillis(),
-            scrollX,
-            scrollY,
+            SystemClock.nanoTime(),
+            scrollEventType,
+            scrollView.getScrollX(),
+            scrollView.getScrollY(),
             contentView.getWidth(),
             contentView.getHeight(),
             scrollView.getWidth(),
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java
index 2c3363f8ec66..be023a76a2e3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java
@@ -15,7 +15,7 @@
 
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.common.MapBuilder;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewGroupManager;
 import com.facebook.react.views.view.ReactClippingViewGroupHelper;
@@ -42,6 +42,11 @@ public ReactScrollView createViewInstance(ThemedReactContext context) {
     return new ReactScrollView(context);
   }
 
+  @ReactProp(name = "scrollEnabled", defaultBoolean = true)
+  public void setScrollEnabled(ReactScrollView view, boolean value) {
+    view.setScrollEnabled(value);
+  }
+
   @ReactProp(name = "showsVerticalScrollIndicator")
   public void setShowsVerticalScrollIndicator(ReactScrollView view, boolean value) {
     view.setVerticalScrollBarEnabled(value);
@@ -52,6 +57,19 @@ public void setRemoveClippedSubviews(ReactScrollView view, boolean removeClipped
     view.setRemoveClippedSubviews(removeClippedSubviews);
   }
 
+  /**
+   * Computing momentum events is potentially expensive since we post a runnable on the UI thread
+   * to see when it is done.  We only do that if {@param sendMomentumEvents} is set to true.  This
+   * is handled automatically in js by checking if there is a listener on the momentum events.
+   *
+   * @param view
+   * @param sendMomentumEvents
+   */
+  @ReactProp(name = "sendMomentumEvents")
+  public void setSendMomentumEvents(ReactScrollView view, boolean sendMomentumEvents) {
+    view.setSendMomentumEvents(sendMomentumEvents);
+  }
+
   @Override
   public @Nullable Map<String, Integer> getCommandsMap() {
     return ReactScrollViewCommandHelper.getCommandsMap();
@@ -69,25 +87,26 @@ public void receiveCommand(
   public void scrollTo(
       ReactScrollView scrollView,
       ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    scrollView.smoothScrollTo(data.mDestX, data.mDestY);
+    if (data.mAnimated) {
+      scrollView.smoothScrollTo(data.mDestX, data.mDestY);
+    } else {
+      scrollView.scrollTo(data.mDestX, data.mDestY);
+    }
   }
 
   @Override
-  public void scrollWithoutAnimationTo(
-      ReactScrollView scrollView,
-      ReactScrollViewCommandHelper.ScrollToCommandData data) {
-    scrollView.scrollTo(data.mDestX, data.mDestY);
+  public @Nullable Map getExportedCustomDirectEventTypeConstants() {
+    return createExportedCustomDirectEventTypeConstants();
   }
 
-  @Override
-  public @Nullable Map getExportedCustomDirectEventTypeConstants() {
+  public static Map createExportedCustomDirectEventTypeConstants() {
     return MapBuilder.builder()
-        .put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll"))
-        .put("topScrollBeginDrag", MapBuilder.of("registrationName", "onScrollBeginDrag"))
-        .put("topScrollEndDrag", MapBuilder.of("registrationName", "onScrollEndDrag"))
-        .put("topScrollAnimationEnd", MapBuilder.of("registrationName", "onScrollAnimationEnd"))
-        .put("topMomentumScrollBegin", MapBuilder.of("registrationName", "onMomentumScrollBegin"))
-        .put("topMomentumScrollEnd", MapBuilder.of("registrationName", "onMomentumScrollEnd"))
+        .put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll"))
+        .put(ScrollEventType.BEGIN_DRAG.getJSEventName(), MapBuilder.of("registrationName", "onScrollBeginDrag"))
+        .put(ScrollEventType.END_DRAG.getJSEventName(), MapBuilder.of("registrationName", "onScrollEndDrag"))
+        .put(ScrollEventType.ANIMATION_END.getJSEventName(), MapBuilder.of("registrationName", "onScrollAnimationEnd"))
+        .put(ScrollEventType.MOMENTUM_BEGIN.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollBegin"))
+        .put(ScrollEventType.MOMENTUM_END.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollEnd"))
         .build();
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java
index fdf8cd5a2faf..dde0a6018a27 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java
@@ -9,10 +9,13 @@
 
 package com.facebook.react.views.scroll;
 
+import javax.annotation.Nullable;
+
 import java.lang.Override;
 
 import android.support.v4.util.Pools;
 
+import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.Arguments;
 import com.facebook.react.bridge.WritableMap;
 import com.facebook.react.uimanager.PixelUtil;
@@ -27,18 +30,18 @@ public class ScrollEvent extends Event<ScrollEvent> {
   private static final Pools.SynchronizedPool<ScrollEvent> EVENTS_POOL =
       new Pools.SynchronizedPool<>(3);
 
-  public static final String EVENT_NAME = "topScroll";
-
   private int mScrollX;
   private int mScrollY;
   private int mContentWidth;
   private int mContentHeight;
   private int mScrollViewWidth;
   private int mScrollViewHeight;
+  private @Nullable ScrollEventType mScrollEventType;
 
   public static ScrollEvent obtain(
       int viewTag,
       long timestampMs,
+      ScrollEventType scrollEventType,
       int scrollX,
       int scrollY,
       int contentWidth,
@@ -52,6 +55,7 @@ public static ScrollEvent obtain(
     event.init(
         viewTag,
         timestampMs,
+        scrollEventType,
         scrollX,
         scrollY,
         contentWidth,
@@ -72,6 +76,7 @@ private ScrollEvent() {
   private void init(
       int viewTag,
       long timestampMs,
+      ScrollEventType scrollEventType,
       int scrollX,
       int scrollY,
       int contentWidth,
@@ -79,6 +84,7 @@ private void init(
       int scrollViewWidth,
       int scrollViewHeight) {
     super.init(viewTag, timestampMs);
+    mScrollEventType = scrollEventType;
     mScrollX = scrollX;
     mScrollY = scrollY;
     mContentWidth = contentWidth;
@@ -89,7 +95,7 @@ private void init(
 
   @Override
   public String getEventName() {
-    return EVENT_NAME;
+    return Assertions.assertNotNull(mScrollEventType).getJSEventName();
   }
 
   @Override
@@ -98,6 +104,15 @@ public short getCoalescingKey() {
     return 0;
   }
 
+  @Override
+  public boolean canCoalesce() {
+    // Only SCROLL events can be coalesced, all others can not be
+    if (mScrollEventType == ScrollEventType.SCROLL) {
+      return true;
+    }
+    return false;
+  }
+
   @Override
   public void dispatch(RCTEventEmitter rctEventEmitter) {
     rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEventType.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEventType.java
new file mode 100644
index 000000000000..db37f20d5db5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEventType.java
@@ -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.views.scroll;
+
+/**
+ * Scroll event types that JS module RCTEventEmitter can understand
+ */
+public enum ScrollEventType {
+  BEGIN_DRAG("topScrollBeginDrag"),
+  END_DRAG("topScrollEndDrag"),
+  SCROLL("topScroll"),
+  MOMENTUM_BEGIN("topMomentumScrollBegin"),
+  MOMENTUM_END("topMomentumScrollEnd"),
+  ANIMATION_END("topScrollAnimationEnd");
+
+  private final String mJSEventName;
+
+  ScrollEventType(String jsEventName) {
+    mJSEventName = jsEventName;
+  }
+
+  public String getJSEventName() {
+    return mJSEventName;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK
new file mode 100644
index 000000000000..214d47a55776
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/BUCK
@@ -0,0 +1,22 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'swiperefresh',
+  srcs = glob(['*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/views/scroll:scroll'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':swiperefresh',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java
new file mode 100644
index 000000000000..f458c0ab06b9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.java
@@ -0,0 +1,35 @@
+/**
+ * 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.views.swiperefresh;
+
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.view.MotionEvent;
+
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.uimanager.events.NativeGestureUtil;
+
+/**
+ * Basic extension of {@link SwipeRefreshLayout} with ReactNative-specific functionality.
+ */
+public class ReactSwipeRefreshLayout extends SwipeRefreshLayout {
+
+  public ReactSwipeRefreshLayout(ReactContext reactContext) {
+    super(reactContext);
+  }
+
+  @Override
+  public boolean onInterceptTouchEvent(MotionEvent ev) {
+    if (super.onInterceptTouchEvent(ev)) {
+      NativeGestureUtil.notifyNativeGestureStarted(this, ev);
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java
new file mode 100644
index 000000000000..d8325d2bce49
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/RefreshEvent.java
@@ -0,0 +1,30 @@
+/**
+ * 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.views.swiperefresh;
+
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+public class RefreshEvent extends Event<RefreshEvent> {
+
+    protected RefreshEvent(int viewTag, long timestampMs) {
+        super(viewTag, timestampMs);
+    }
+
+    @Override
+    public String getEventName() {
+        return "topRefresh";
+    }
+
+    @Override
+    public void dispatch(RCTEventEmitter rctEventEmitter) {
+        rctEventEmitter.receiveEvent(getViewTag(), getEventName(), null);
+    }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java
new file mode 100644
index 000000000000..009f5a126509
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java
@@ -0,0 +1,113 @@
+/**
+ * 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.views.swiperefresh;
+
+import javax.annotation.Nullable;
+
+import java.util.Map;
+
+import android.graphics.Color;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
+
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SystemClock;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewGroupManager;
+import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
+
+/**
+ * ViewManager for {@link ReactSwipeRefreshLayout} which allows the user to "pull to refresh" a
+ * child view. Emits an {@code onRefresh} event when this happens.
+ */
+public class SwipeRefreshLayoutManager extends ViewGroupManager<ReactSwipeRefreshLayout> {
+
+  @Override
+  protected ReactSwipeRefreshLayout createViewInstance(ThemedReactContext reactContext) {
+    return new ReactSwipeRefreshLayout(reactContext);
+  }
+
+  @Override
+  public String getName() {
+    return "AndroidSwipeRefreshLayout";
+  }
+
+  @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
+  public void setEnabled(ReactSwipeRefreshLayout view, boolean enabled) {
+    view.setEnabled(enabled);
+  }
+
+  @ReactProp(name = "colors", customType = "ColorArray")
+  public void setColors(ReactSwipeRefreshLayout view, @Nullable ReadableArray colors) {
+    if (colors != null) {
+      int[] colorValues = new int[colors.size()];
+      for (int i = 0; i < colors.size(); i++) {
+        colorValues[i] = colors.getInt(i);
+      }
+      view.setColorSchemeColors(colorValues);
+    } else {
+      view.setColorSchemeColors();
+    }
+  }
+
+  @ReactProp(name = "progressBackgroundColor", defaultInt = Color.TRANSPARENT, customType = "Color")
+  public void setProgressBackgroundColor(ReactSwipeRefreshLayout view, int color) {
+    view.setProgressBackgroundColorSchemeColor(color);
+  }
+
+  @ReactProp(name = "size", defaultInt = SwipeRefreshLayout.DEFAULT)
+  public void setSize(ReactSwipeRefreshLayout view, int size) {
+    view.setSize(size);
+  }
+
+  @ReactProp(name = "refreshing")
+  public void setRefreshing(final ReactSwipeRefreshLayout view, final boolean refreshing) {
+    // Use `post` otherwise the control won't start refreshing if refreshing is true when
+    // the component gets mounted.
+    view.post(new Runnable() {
+      @Override
+      public void run() {
+        view.setRefreshing(refreshing);
+      }
+    });
+  }
+
+  @Override
+  protected void addEventEmitters(
+      final ThemedReactContext reactContext,
+      final ReactSwipeRefreshLayout view) {
+    view.setOnRefreshListener(
+        new OnRefreshListener() {
+          @Override
+          public void onRefresh() {
+            reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
+                .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.nanoTime()));
+          }
+        });
+  }
+
+  @Nullable
+  @Override
+  public Map<String, Object> getExportedViewConstants() {
+    return MapBuilder.<String, Object>of(
+        "SIZE",
+        MapBuilder.of("DEFAULT", SwipeRefreshLayout.DEFAULT, "LARGE", SwipeRefreshLayout.LARGE));
+  }
+
+  @Override
+  public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
+    return MapBuilder.<String, Object>builder()
+        .put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))
+        .build();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK
index 6c353cbe81ba..f7ff35e9d822 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/BUCK
@@ -5,11 +5,12 @@ android_library(
   srcs = glob(['*.java']),
   deps = [
     react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
-    '//third-party/android-support-for-standalone-apps/v7/appcompat:appcompat-23.1',
-    '//third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java
index 115cc9621cc6..f99b3c7a7a4c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/switchview/ReactSwitchManager.java
@@ -10,7 +10,6 @@
 // switchview because switch is a keyword
 package com.facebook.react.views.switchview;
 
-import android.os.SystemClock;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CompoundButton;
@@ -18,13 +17,13 @@
 import com.facebook.csslayout.CSSNode;
 import com.facebook.csslayout.MeasureOutput;
 import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.LayoutShadowNode;
-import com.facebook.react.uimanager.ReactProp;
-import com.facebook.react.uimanager.ReactShadowNode;
 import com.facebook.react.uimanager.SimpleViewManager;
-import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
 
 /**
  * View manager for {@link ReactSwitch} components.
@@ -33,7 +32,7 @@ public class ReactSwitchManager extends SimpleViewManager<ReactSwitch> {
 
   private static final String REACT_CLASS = "AndroidSwitch";
 
-  private static class ReactSwitchShadowNode extends LayoutShadowNode implements
+  static class ReactSwitchShadowNode extends LayoutShadowNode implements
       CSSNode.MeasureFunction {
 
     private int mWidth;
@@ -45,7 +44,7 @@ private ReactSwitchShadowNode() {
     }
 
     @Override
-    public void measure(CSSNode node, float width, MeasureOutput measureOutput) {
+    public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
       if (!mMeasured) {
         // Create a switch with the default config and measure it; since we don't (currently)
         // support setting custom switch text, this is fine, as all switches will measure the same
@@ -72,7 +71,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
           reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
               new ReactSwitchEvent(
                   buttonView.getId(),
-                  SystemClock.uptimeMillis(),
+                  SystemClock.nanoTime(),
                   isChecked));
         }
       };
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
index cd0f60c1b3c5..8d1e02e6c3d8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
@@ -8,6 +8,7 @@ android_library(
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/DefaultStyleValuesUtil.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/DefaultStyleValuesUtil.java
index 78aa65a3d1e9..51edb5a32c87 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/DefaultStyleValuesUtil.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/DefaultStyleValuesUtil.java
@@ -30,17 +30,7 @@ private DefaultStyleValuesUtil() {
    * @return The ColorStateList for the hint text as defined in the style
    */
   public static ColorStateList getDefaultTextColorHint(Context context) {
-    Resources.Theme theme = context.getTheme();
-    TypedArray textAppearances = null;
-    try {
-      textAppearances = theme.obtainStyledAttributes(new int[]{android.R.attr.textColorHint});
-      ColorStateList textColorHint = textAppearances.getColorStateList(0);
-      return textColorHint;
-    } finally {
-      if (textAppearances != null) {
-        textAppearances.recycle();
-      }
-    }
+    return getDefaultTextAttribute(context, android.R.attr.textColorHint);
   }
 
   /**
@@ -50,10 +40,24 @@ public static ColorStateList getDefaultTextColorHint(Context context) {
    * @return The ColorStateList for the text as defined in the style
    */
   public static ColorStateList getDefaultTextColor(Context context) {
+    return getDefaultTextAttribute(context, android.R.attr.textColor);
+  }
+
+  /**
+   * Utility method that returns the default text highlight color as define by the theme
+   *
+   * @param context The Context
+   * @return The int for the highlight color as defined in the style
+   */
+  public static int getDefaultTextColorHighlight(Context context) {
+    return getDefaultTextAttribute(context, android.R.attr.textColorHighlight).getDefaultColor();
+  }
+
+  private static ColorStateList getDefaultTextAttribute(Context context, int attribute) {
     Resources.Theme theme = context.getTheme();
     TypedArray textAppearances = null;
     try {
-      textAppearances = theme.obtainStyledAttributes(new int[]{android.R.attr.textColor});
+      textAppearances = theme.obtainStyledAttributes(new int[]{attribute});
       ColorStateList textColor = textAppearances.getColorStateList(0);
       return textColor;
     } finally {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
index 10e2a4d7b9ff..1e63a2094923 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.java
@@ -72,6 +72,25 @@ public static ReactFontManager getInstance() {
     return typeface;
   }
 
+  /**
+   * Add additional font family, or replace the exist one in the font memory cache.
+   * @param style
+   * @see {@link Typeface#DEFAULT}
+   * @see {@link Typeface#BOLD}
+   * @see {@link Typeface#ITALIC}
+   * @see {@link Typeface#BOLD_ITALIC}
+   */
+  public void setTypeface(String fontFamilyName, int style, Typeface typeface) {
+    if (typeface != null) {
+      FontFamily fontFamily = mFontCache.get(fontFamilyName);
+      if (fontFamily == null) {
+        fontFamily = new FontFamily();
+        mFontCache.put(fontFamilyName, fontFamily);
+      }
+      fontFamily.setTypeface(style, typeface);
+    }
+  }
+
   private static
   @Nullable Typeface createTypeface(
       String fontFamilyName,
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java
index 9635adfcd8b6..98e02829ae6d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java
@@ -10,7 +10,6 @@
 package com.facebook.react.views.text;
 
 import com.facebook.react.common.annotations.VisibleForTesting;
-import com.facebook.react.uimanager.CatalystStylesDiffMap;
 import com.facebook.react.uimanager.ThemedReactContext;
 
 /**
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java
index 9dc4132abc2d..4e986644e16b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageShadowNode.java
@@ -9,72 +9,17 @@
 
 package com.facebook.react.views.text;
 
-import javax.annotation.Nullable;
-
-import java.util.Locale;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.facebook.common.util.UriUtil;
 import com.facebook.react.uimanager.LayoutShadowNode;
-import com.facebook.react.uimanager.ReactProp;
-import com.facebook.react.uimanager.ReactShadowNode;
 
 /**
- * {@link ReactShadowNode} class for Image embedded within a TextView.
- *
+ * Base class for {@link com.facebook.csslayout.CSSNode}s that represent inline images.
  */
-public class ReactTextInlineImageShadowNode extends LayoutShadowNode {
-
-  private @Nullable Uri mUri;
-
-  @ReactProp(name = "src")
-  public void setSource(@Nullable String source) {
-    Uri uri = null;
-    if (source != null) {
-      try {
-        uri = Uri.parse(source);
-        // Verify scheme is set, so that relative uri (used by static resources) are not handled.
-        if (uri.getScheme() == null) {
-          uri = null;
-        }
-      } catch (Exception e) {
-        // ignore malformed uri, then attempt to extract resource ID.
-      }
-      if (uri == null) {
-        uri = getResourceDrawableUri(getThemedContext(), source);
-      }
-    }
-    if (uri != mUri) {
-      markUpdated();
-    }
-    mUri = uri;
-  }
-
-  public @Nullable Uri getUri() {
-    return mUri;
-  }
-
-  // TODO: t9053573 is tracking that this code should be shared
-  private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
-    if (name == null || name.isEmpty()) {
-      return null;
-    }
-    name = name.toLowerCase(Locale.getDefault()).replace("-", "_");
-    int resId = context.getResources().getIdentifier(
-        name,
-        "drawable",
-        context.getPackageName());
-    return new Uri.Builder()
-        .scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
-        .path(String.valueOf(resId))
-        .build();
-  }
+public abstract class ReactTextInlineImageShadowNode extends LayoutShadowNode {
 
-  @Override
-  public boolean isVirtual() {
-    return true;
-  }
+  /**
+   * Build a {@link TextInlineImageSpan} from this node. This will be added to the TextView in
+   * place of this node.
+   */
+  public abstract TextInlineImageSpan buildInlineImageSpan();
 
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java
deleted file mode 100644
index 8df8645da987..000000000000
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * 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.views.text;
-
-import android.view.View;
-
-import com.facebook.react.uimanager.ThemedReactContext;
-import com.facebook.react.uimanager.ViewManager;
-
-/**
- * Manages Images embedded in Text nodes. Since they are used only as a virtual nodes any type of
- * native view operation will throw an {@link IllegalStateException}
- */
-public class ReactTextInlineImageViewManager
-    extends ViewManager<View, ReactTextInlineImageShadowNode> {
-
-  static final String REACT_CLASS = "RCTTextInlineImage";
-
-  @Override
-  public String getName() {
-    return REACT_CLASS;
-  }
-
-  @Override
-  public View createViewInstance(ThemedReactContext context) {
-    throw new IllegalStateException("RCTTextInlineImage doesn't map into a native view");
-  }
-
-  @Override
-  public ReactTextInlineImageShadowNode createShadowNodeInstance() {
-    return new ReactTextInlineImageShadowNode();
-  }
-
-  @Override
-  public Class<ReactTextInlineImageShadowNode> getShadowNodeClass() {
-    return ReactTextInlineImageShadowNode.class;
-  }
-
-  @Override
-  public void updateExtraData(View root, Object extraData) {
-  }
-
-}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
index 11c09a24a514..2d98703c2f67 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
@@ -14,7 +14,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import android.content.res.Resources;
 import android.graphics.Typeface;
 import android.text.BoringLayout;
 import android.text.Layout;
@@ -32,15 +31,16 @@
 import com.facebook.csslayout.CSSNode;
 import com.facebook.csslayout.MeasureOutput;
 import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.ReadableMap;
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.react.uimanager.IllegalViewOperationException;
 import com.facebook.react.uimanager.LayoutShadowNode;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.ReactProp;
 import com.facebook.react.uimanager.ReactShadowNode;
 import com.facebook.react.uimanager.UIViewOperationQueue;
 import com.facebook.react.uimanager.ViewDefaults;
 import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
 
 /**
  * {@link ReactShadowNode} class for spannable text view.
@@ -63,6 +63,11 @@ public class ReactTextShadowNode extends LayoutShadowNode {
   @VisibleForTesting
   public static final String PROP_TEXT = "text";
 
+  public static final String PROP_SHADOW_OFFSET = "textShadowOffset";
+  public static final String PROP_SHADOW_RADIUS = "textShadowRadius";
+  public static final String PROP_SHADOW_COLOR = "textShadowColor";
+  public static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000;
+
   private static final TextPaint sTextPaintInstance = new TextPaint();
 
   static {
@@ -88,7 +93,7 @@ public void execute(SpannableStringBuilder sb) {
     }
   }
 
-  private static final void buildSpannedFromTextCSSNode(
+  private static void buildSpannedFromTextCSSNode(
       ReactTextShadowNode textCSSNode,
       SpannableStringBuilder sb,
       List<SetSpanOperation> ops) {
@@ -101,7 +106,14 @@ private static final void buildSpannedFromTextCSSNode(
       if (child instanceof ReactTextShadowNode) {
         buildSpannedFromTextCSSNode((ReactTextShadowNode) child, sb, ops);
       } else if (child instanceof ReactTextInlineImageShadowNode) {
-        buildSpannedFromImageNode((ReactTextInlineImageShadowNode) child, sb, ops);
+        // We make the image take up 1 character in the span and put a corresponding character into
+        // the text so that the image doesn't run over any following text.
+        sb.append(INLINE_IMAGE_PLACEHOLDER);
+        ops.add(
+          new SetSpanOperation(
+            sb.length() - INLINE_IMAGE_PLACEHOLDER.length(),
+            sb.length(),
+            ((ReactTextInlineImageShadowNode) child).buildInlineImageSpan()));
       } else {
         throw new IllegalViewOperationException("Unexpected view type nested under text node: "
                 + child.getClass());
@@ -114,8 +126,7 @@ private static final void buildSpannedFromTextCSSNode(
         ops.add(new SetSpanOperation(start, end, new ForegroundColorSpan(textCSSNode.mColor)));
       }
       if (textCSSNode.mIsBackgroundColorSet) {
-        ops.add(
-            new SetSpanOperation(
+        ops.add(new SetSpanOperation(
                 start,
                 end,
                 new BackgroundColorSpan(textCSSNode.mBackgroundColor)));
@@ -135,35 +146,28 @@ private static final void buildSpannedFromTextCSSNode(
                     textCSSNode.mFontFamily,
                     textCSSNode.getThemedContext().getAssets())));
       }
+      if (textCSSNode.mTextShadowOffsetDx != 0 || textCSSNode.mTextShadowOffsetDy != 0) {
+        ops.add(new SetSpanOperation(
+                start,
+                end,
+                new ShadowStyleSpan(
+                    textCSSNode.mTextShadowOffsetDx,
+                    textCSSNode.mTextShadowOffsetDy,
+                    textCSSNode.mTextShadowRadius,
+                    textCSSNode.mTextShadowColor)));
+      }
       ops.add(new SetSpanOperation(start, end, new ReactTagSpan(textCSSNode.getReactTag())));
     }
   }
 
-  private static final void buildSpannedFromImageNode(
-      ReactTextInlineImageShadowNode node,
-      SpannableStringBuilder sb,
-      List<SetSpanOperation> ops) {
-    int start = sb.length();
-    // Create our own internal ImageSpan which will allow us to correctly layout the Image
-    Resources resources = node.getThemedContext().getResources();
-    int height = (int) PixelUtil.toDIPFromPixel(node.getStyleHeight());
-    int width = (int) PixelUtil.toDIPFromPixel(node.getStyleWidth());
-    TextInlineImageSpan imageSpan =
-        new TextInlineImageSpan(resources, height, width, node.getUri());
-    // We make the image take up 1 character in the span and put a corresponding character into the
-    // text so that the image doesn't run over any following text.
-    sb.append(INLINE_IMAGE_PLACEHOLDER);
-    ops.add(new SetSpanOperation(start, sb.length(), imageSpan));
-  }
-
-  protected static final Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) {
+  protected static Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode) {
     SpannableStringBuilder sb = new SpannableStringBuilder();
     // TODO(5837930): Investigate whether it's worth optimizing this part and do it if so
 
     // The {@link SpannableStringBuilder} implementation require setSpan operation to be called
     // up-to-bottom, otherwise all the spannables that are withing the region for which one may set
     // a new spannable will be wiped out
-    List<SetSpanOperation> ops = new ArrayList<SetSpanOperation>();
+    List<SetSpanOperation> ops = new ArrayList<>();
     buildSpannedFromTextCSSNode(textCSSNode, sb, ops);
     if (textCSSNode.mFontSize == UNSET) {
       sb.setSpan(
@@ -189,7 +193,7 @@ protected static final Spannable fromTextCSSNode(ReactTextShadowNode textCSSNode
   private static final CSSNode.MeasureFunction TEXT_MEASURE_FUNCTION =
       new CSSNode.MeasureFunction() {
         @Override
-        public void measure(CSSNode node, float width, MeasureOutput measureOutput) {
+        public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
           // TODO(5578671): Handle text direction (see View#getTextDirectionHeuristic)
           ReactTextShadowNode reactCSSNode = (ReactTextShadowNode) node;
           TextPaint textPaint = sTextPaintInstance;
@@ -274,6 +278,11 @@ private static int parseNumericFontWeight(String fontWeightString) {
   protected int mNumberOfLines = UNSET;
   protected int mFontSize = UNSET;
 
+  private float mTextShadowOffsetDx = 0;
+  private float mTextShadowOffsetDy = 0;
+  private float mTextShadowRadius = 1;
+  private int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR;
+
   /**
    * mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}.
    * mFontWeight can be {@link Typeface#NORMAL} or {@link Typeface#BOLD}.
@@ -305,6 +314,13 @@ private static int parseNumericFontWeight(String fontWeightString) {
 
   protected boolean mContainsImages = false;
 
+  public ReactTextShadowNode(boolean isVirtual) {
+    mIsVirtual = isVirtual;
+    if (!isVirtual) {
+      setMeasureFunction(TEXT_MEASURE_FUNCTION);
+    }
+  }
+
   @Override
   public void onBeforeLayout() {
     if (mIsVirtual) {
@@ -408,6 +424,34 @@ public void setFontStyle(@Nullable String fontStyleString) {
     }
   }
 
+  @ReactProp(name = PROP_SHADOW_OFFSET)
+  public void setTextShadowOffset(ReadableMap offsetMap) {
+    if (offsetMap == null) {
+      mTextShadowOffsetDx = 0;
+      mTextShadowOffsetDy = 0;
+    } else {
+      mTextShadowOffsetDx = PixelUtil.toPixelFromDIP(offsetMap.getDouble("width"));
+      mTextShadowOffsetDy = PixelUtil.toPixelFromDIP(offsetMap.getDouble("height"));
+    }
+    markUpdated();
+  }
+
+  @ReactProp(name = PROP_SHADOW_RADIUS, defaultInt = 1)
+  public void setTextShadowRadius(float textShadowRadius) {
+    if (textShadowRadius != mTextShadowRadius) {
+      mTextShadowRadius = textShadowRadius;
+      markUpdated();
+    }
+  }
+
+  @ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color")
+  public void setTextShadowColor(int textShadowColor) {
+    if (textShadowColor != mTextShadowColor) {
+      mTextShadowColor = textShadowColor;
+      markUpdated();
+    }
+  }
+
   @Override
   public boolean isVirtualAnchor() {
     return !mIsVirtual;
@@ -430,11 +474,4 @@ public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
       uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
     }
   }
-
-  public ReactTextShadowNode(boolean isVirtual) {
-    mIsVirtual = isVirtual;
-    if (!isVirtual) {
-      setMeasureFunction(TEXT_MEASURE_FUNCTION);
-    }
-  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
index faea8bad4a08..8c7e7f948d67 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java
@@ -39,6 +39,11 @@ public int reactTagForTouch(float touchX, float touchY) {
     int y = (int) touchY;
 
     Layout layout = getLayout();
+    if (layout == null) {
+      // If the layout is null, the view hasn't been properly laid out yet. Therefore, we can't find
+      // the exact text tag that has been touched, and the correct tag to return is the default one.
+      return target;
+    }
     int line = layout.getLineForVertical(y);
 
     int lineStartX = (int) layout.getLineLeft(line);
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
index 68a1a7ea8cd1..2bffd39695fa 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java
@@ -19,7 +19,7 @@
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.uimanager.BaseViewManager;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewDefaults;
 import com.facebook.react.uimanager.ViewProps;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java
new file mode 100644
index 000000000000..9e030f838ca9
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ShadowStyleSpan.java
@@ -0,0 +1,31 @@
+/**
+ * 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.views.text;
+
+
+import android.text.TextPaint;
+import android.text.style.CharacterStyle;
+
+public class ShadowStyleSpan extends CharacterStyle {
+  private final float mDx, mDy, mRadius;
+  private final int mColor;
+
+  public ShadowStyleSpan(float dx, float dy, float radius, int color) {
+    mDx = dx;
+    mDy = dy;
+    mRadius = radius;
+    mColor = color;
+  }
+
+  @Override
+  public void updateDrawState(TextPaint textPaint) {
+    textPaint.setShadowLayer(mRadius, mDx, mDy, mColor);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java
index e0a124109109..0308ee20c28c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/TextInlineImageSpan.java
@@ -7,150 +7,64 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-package com.facebook.react.views.text;
-
-import javax.annotation.Nullable;
-
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.text.Spannable;
-import android.text.style.ReplacementSpan;
-import android.widget.TextView;
-
-import com.facebook.drawee.backends.pipeline.Fresco;
-import com.facebook.drawee.generic.GenericDraweeHierarchy;
-import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
-import com.facebook.drawee.interfaces.DraweeController;
-import com.facebook.drawee.view.DraweeHolder;
-import com.facebook.imagepipeline.request.ImageRequest;
-import com.facebook.imagepipeline.request.ImageRequestBuilder;
-
-/**
- * TextInlineImageSpan is a span for Images that are inside <Text/>.  It computes it's size based
- * on the input size.  When it is time to draw, it will use the Fresco framework to get the right
- * Drawable and let that draw.
- *
- * Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must
- * tell the Span about the TextView
- *
- * Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or
- * draws, we need to update this as well.
- */
-public class TextInlineImageSpan extends ReplacementSpan {
-
-  private @Nullable Drawable mDrawable;
-  private DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
-
-  private int mHeight;
-  private Uri mUri;
-  private int mWidth;
-
-  private @Nullable TextView mTextView;
-
-  public TextInlineImageSpan(Resources resources, int height, int width, @Nullable Uri uri) {
-    mDraweeHolder = new DraweeHolder(
-        GenericDraweeHierarchyBuilder.newInstance(resources)
-            .build()
-    );
-
-    mHeight = height;
-    mWidth = width;
-    mUri = (uri != null) ? uri : Uri.EMPTY;
-  }
-
-  /**
-   * The ReactTextView that holds this ImageSpan is responsible for passing these methods on so
-   * that we can do proper lifetime management for Fresco
-   */
-  public void onDetachedFromWindow() {
-    mDraweeHolder.onDetach();
-  }
-
-  public void onStartTemporaryDetach() {
-    mDraweeHolder.onDetach();
-  }
-
-  public void onAttachedToWindow() {
-    mDraweeHolder.onAttach();
-  }
-
-  public void onFinishTemporaryDetach() {
-    mDraweeHolder.onAttach();
-  }
-
-  public @Nullable Drawable getDrawable() {
-    return mDrawable;
-  }
-
-  @Override
-  public int getSize(
-      Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
-    // NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable
-
-    if (fm != null) {
-      fm.ascent = -mHeight;
-      fm.descent = 0;
-
-      fm.top = fm.ascent;
-      fm.bottom = 0;
-    }
-
-    return mWidth;
-  }
-
-  @Override
-  public void draw(
-      Canvas canvas,
-      CharSequence text,
-      int start,
-      int end,
-      float x,
-      int top,
-      int y,
-      int bottom,
-      Paint paint) {
-    if (mDrawable == null) {
-      ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
-          .build();
-
-      DraweeController draweeController = Fresco.newDraweeControllerBuilder()
-          .setOldController(mDraweeHolder.getController())
-          .setImageRequest(imageRequest)
-          .build();
-      mDraweeHolder.setController(draweeController);
-
-      mDrawable = mDraweeHolder.getTopLevelDrawable();
-      mDrawable.setBounds(0, 0, mWidth, mHeight);
-      mDrawable.setCallback(mTextView);
-    }
-
-    // NOTE: This drawing code is copied from DynamicDrawableSpan
-
-    canvas.save();
-
-    int transY = bottom - mDrawable.getBounds().bottom;
-
-    canvas.translate(x, transY);
-    mDrawable.draw(canvas);
-    canvas.restore();
-  }
-
-  /**
-   * For TextInlineImageSpan we need to update the Span to know that the window is attached and
-   * the TextView that we will set as the callback on the Drawable.
-   *
-   * @param spannable The spannable that may contain TextInlineImageSpans
-   * @param view The view which will be set as the callback for the Drawable
-   */
-  public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView view) {
-    TextInlineImageSpan[] spans =
-        spannable.getSpans(0, spannable.length(), TextInlineImageSpan.class);
-    for (TextInlineImageSpan span : spans) {
-      span.onAttachedToWindow();
-      span.mTextView = view;
-    }
-  };
-}
+ package com.facebook.react.views.text;
+
+ import javax.annotation.Nullable;
+
+ import android.graphics.drawable.Drawable;
+ import android.text.Spannable;
+ import android.text.style.ReplacementSpan;
+ import android.view.View;
+ import android.widget.TextView;
+
+ /**
+  * Base class for inline image spans.
+  */
+ public abstract class TextInlineImageSpan extends ReplacementSpan {
+
+   /**
+    * For TextInlineImageSpan we need to update the Span to know that the window is attached and
+    * the TextView that we will set as the callback on the Drawable.
+    *
+    * @param spannable The spannable that may contain TextInlineImageSpans
+    * @param view The view which will be set as the callback for the Drawable
+    */
+   public static void possiblyUpdateInlineImageSpans(Spannable spannable, TextView view) {
+     TextInlineImageSpan[] spans =
+       spannable.getSpans(0, spannable.length(), TextInlineImageSpan.class);
+     for (TextInlineImageSpan span : spans) {
+       span.onAttachedToWindow();
+       span.setTextView(view);
+     }
+   }
+
+   /**
+    * Get the drawable that is span represents.
+    */
+   public abstract @Nullable Drawable getDrawable();
+
+   /**
+    * Called by the text view from {@link View#onDetachedFromWindow()},
+    */
+   public abstract void onDetachedFromWindow();
+
+   /**
+    * Called by the text view from {@link View#onStartTemporaryDetach()}.
+    */
+   public abstract void onStartTemporaryDetach();
+
+   /**
+    * Called by the text view from {@link View#onAttachedToWindow()}.
+    */
+   public abstract void onAttachedToWindow();
+
+   /**
+    * Called by the text view from {@link View#onFinishTemporaryDetach()}.
+    */
+   public abstract void onFinishTemporaryDetach();
+
+   /**
+    * Set the textview that will contain this span.
+    */
+   public abstract void setTextView(TextView textView);
+ }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK
new file mode 100644
index 000000000000..e8ddbceb6d95
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/BUCK
@@ -0,0 +1,27 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'frescosupport',
+  srcs = glob(['*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':frescosupport',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java
new file mode 100644
index 000000000000..643a2c136fe5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java
@@ -0,0 +1,114 @@
+/**
+ * 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.views.textfrescosupport;
+
+import javax.annotation.Nullable;
+
+import java.util.Locale;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+
+import com.facebook.common.util.UriUtil;
+import com.facebook.csslayout.CSSNode;
+import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.views.text.ReactTextInlineImageShadowNode;
+import com.facebook.react.views.text.TextInlineImageSpan;
+
+/**
+ * {@link CSSNode} that represents an inline image. Loading is done using Fresco.
+ *
+ */
+public class FrescoBasedReactTextInlineImageShadowNode extends ReactTextInlineImageShadowNode {
+
+  private @Nullable Uri mUri;
+  private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
+  private final @Nullable Object mCallerContext;
+
+  public FrescoBasedReactTextInlineImageShadowNode(
+    AbstractDraweeControllerBuilder draweeControllerBuilder,
+    @Nullable Object callerContext) {
+    mDraweeControllerBuilder = draweeControllerBuilder;
+    mCallerContext = callerContext;
+  }
+
+  @ReactProp(name = "src")
+  public void setSource(@Nullable String source) {
+    Uri uri = null;
+    if (source != null) {
+      try {
+        uri = Uri.parse(source);
+        // Verify scheme is set, so that relative uri (used by static resources) are not handled.
+        if (uri.getScheme() == null) {
+          uri = null;
+        }
+      } catch (Exception e) {
+        // ignore malformed uri, then attempt to extract resource ID.
+      }
+      if (uri == null) {
+        uri = getResourceDrawableUri(getThemedContext(), source);
+      }
+    }
+    if (uri != mUri) {
+      markUpdated();
+    }
+    mUri = uri;
+  }
+
+  public @Nullable Uri getUri() {
+    return mUri;
+  }
+
+  // TODO: t9053573 is tracking that this code should be shared
+  private static @Nullable Uri getResourceDrawableUri(Context context, @Nullable String name) {
+    if (name == null || name.isEmpty()) {
+      return null;
+    }
+    name = name.toLowerCase(Locale.getDefault()).replace("-", "_");
+    int resId = context.getResources().getIdentifier(
+      name,
+      "drawable",
+      context.getPackageName());
+    return new Uri.Builder()
+      .scheme(UriUtil.LOCAL_RESOURCE_SCHEME)
+      .path(String.valueOf(resId))
+      .build();
+  }
+
+  @Override
+  public boolean isVirtual() {
+    return true;
+  }
+
+  @Override
+  public TextInlineImageSpan buildInlineImageSpan() {
+    Resources resources = getThemedContext().getResources();
+    int height = (int) Math.ceil(getStyleHeight());
+    int width = (int) Math.ceil(getStyleWidth());
+    return new FrescoBasedReactTextInlineImageSpan(
+      resources,
+      height,
+      width,
+      getUri(),
+      getDraweeControllerBuilder(),
+      getCallerContext());
+  }
+
+  public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
+    return mDraweeControllerBuilder;
+  }
+
+  public @Nullable Object getCallerContext() {
+    return mCallerContext;
+  }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java
new file mode 100644
index 000000000000..d62ecf0ce4c4
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageSpan.java
@@ -0,0 +1,155 @@
+/**
+ * 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.views.textfrescosupport;
+
+import javax.annotation.Nullable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.widget.TextView;
+
+import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.interfaces.DraweeController;
+import com.facebook.drawee.view.DraweeHolder;
+import com.facebook.imagepipeline.request.ImageRequest;
+import com.facebook.imagepipeline.request.ImageRequestBuilder;
+import com.facebook.react.views.text.TextInlineImageSpan;
+
+/**
+ * FrescoBasedTextInlineImageSpan is a span for Images that are inside <Text/>. It computes
+ * its size based on the input size. When it is time to draw, it will use the Fresco framework to
+ * get the right Drawable and let that draw.
+ *
+ * Since Fresco needs to callback to the TextView that contains this, in the ViewManager, you must
+ * tell the Span about the TextView
+ *
+ * Note: It borrows code from DynamicDrawableSpan and if that code updates how it computes size or
+ * draws, we need to update this as well.
+ */
+public class FrescoBasedReactTextInlineImageSpan extends TextInlineImageSpan {
+
+  private @Nullable Drawable mDrawable;
+  private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
+  private final DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
+  private final @Nullable Object mCallerContext;
+
+  private int mHeight;
+  private Uri mUri;
+  private int mWidth;
+
+  private @Nullable TextView mTextView;
+
+  public FrescoBasedReactTextInlineImageSpan(
+      Resources resources,
+      int height,
+      int width,
+      @Nullable Uri uri,
+      AbstractDraweeControllerBuilder draweeControllerBuilder,
+      @Nullable Object callerContext) {
+    mDraweeHolder = new DraweeHolder(
+        GenericDraweeHierarchyBuilder.newInstance(resources)
+            .build()
+    );
+    mDraweeControllerBuilder = draweeControllerBuilder;
+    mCallerContext = callerContext;
+
+    mHeight = height;
+    mWidth = width;
+    mUri = (uri != null) ? uri : Uri.EMPTY;
+  }
+
+  /**
+   * The ReactTextView that holds this ImageSpan is responsible for passing these methods on so
+   * that we can do proper lifetime management for Fresco
+   */
+  public void onDetachedFromWindow() {
+    mDraweeHolder.onDetach();
+  }
+
+  public void onStartTemporaryDetach() {
+    mDraweeHolder.onDetach();
+  }
+
+  public void onAttachedToWindow() {
+    mDraweeHolder.onAttach();
+  }
+
+  public void onFinishTemporaryDetach() {
+    mDraweeHolder.onAttach();
+  }
+
+  public @Nullable Drawable getDrawable() {
+    return mDrawable;
+  }
+
+  @Override
+  public int getSize(
+      Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
+    // NOTE: This getSize code is copied from DynamicDrawableSpan and modified to not use a Drawable
+
+    if (fm != null) {
+      fm.ascent = -mHeight;
+      fm.descent = 0;
+
+      fm.top = fm.ascent;
+      fm.bottom = 0;
+    }
+
+    return mWidth;
+  }
+
+  public void setTextView(TextView textView) {
+    mTextView = textView;
+  }
+
+  @Override
+  public void draw(
+      Canvas canvas,
+      CharSequence text,
+      int start,
+      int end,
+      float x,
+      int top,
+      int y,
+      int bottom,
+      Paint paint) {
+    if (mDrawable == null) {
+      ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
+          .build();
+
+      DraweeController draweeController = mDraweeControllerBuilder
+          .reset()
+          .setOldController(mDraweeHolder.getController())
+          .setCallerContext(mCallerContext)
+          .setImageRequest(imageRequest)
+          .build();
+      mDraweeHolder.setController(draweeController);
+
+      mDrawable = mDraweeHolder.getTopLevelDrawable();
+      mDrawable.setBounds(0, 0, mWidth, mHeight);
+      mDrawable.setCallback(mTextView);
+    }
+
+    // NOTE: This drawing code is copied from DynamicDrawableSpan
+
+    canvas.save();
+
+    int transY = bottom - mDrawable.getBounds().bottom;
+
+    canvas.translate(x, transY);
+    mDrawable.draw(canvas);
+    canvas.restore();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java
new file mode 100644
index 000000000000..c272bf39d89a
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java
@@ -0,0 +1,73 @@
+/**
+ * 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.views.textfrescosupport;
+
+import javax.annotation.Nullable;
+
+import android.view.View;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.ViewManager;
+
+/**
+ * Manages Images embedded in Text nodes using Fresco. Since they are used only as a virtual nodes
+ * any type of native view operation will throw an {@link IllegalStateException}.
+ */
+public class FrescoBasedReactTextInlineImageViewManager
+  extends ViewManager<View, FrescoBasedReactTextInlineImageShadowNode> {
+
+  static final String REACT_CLASS = "RCTTextInlineImage";
+
+  private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
+  private final @Nullable Object mCallerContext;
+
+  public FrescoBasedReactTextInlineImageViewManager() {
+    this(null, null);
+  }
+
+  public FrescoBasedReactTextInlineImageViewManager(
+    @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder,
+    @Nullable Object callerContext) {
+    mDraweeControllerBuilder = draweeControllerBuilder;
+    mCallerContext = callerContext;
+  }
+
+  @Override
+  public String getName() {
+    return REACT_CLASS;
+  }
+
+  @Override
+  public View createViewInstance(ThemedReactContext context) {
+    throw new IllegalStateException("RCTTextInlineImage doesn't map into a native view");
+  }
+
+  @Override
+  public FrescoBasedReactTextInlineImageShadowNode createShadowNodeInstance() {
+    return new FrescoBasedReactTextInlineImageShadowNode(
+      (mDraweeControllerBuilder != null) ?
+        mDraweeControllerBuilder :
+        Fresco.newDraweeControllerBuilder(),
+      mCallerContext
+    );
+  }
+
+  @Override
+  public Class<FrescoBasedReactTextInlineImageShadowNode> getShadowNodeClass() {
+    return FrescoBasedReactTextInlineImageShadowNode.class;
+  }
+
+  @Override
+  public void updateExtraData(View root, Object extraData) {
+  }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK
index 4c7142f68d38..925dc15d3b0c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/BUCK
@@ -10,6 +10,7 @@ android_library(
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/modules/core:core'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
index 3ac44ed41fb7..14752e1589fc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java
@@ -21,11 +21,14 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextWatcher;
+import android.text.method.KeyListener;
+import android.text.method.QwertyKeyListener;
 import android.text.style.AbsoluteSizeSpan;
 import android.text.style.BackgroundColorSpan;
 import android.text.style.ForegroundColorSpan;
 import android.view.Gravity;
 import android.view.KeyEvent;
+import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
@@ -64,6 +67,11 @@ public class ReactEditText extends EditText {
   private @Nullable TextWatcherDelegator mTextWatcherDelegator;
   private int mStagedInputType;
   private boolean mContainsImages;
+  private boolean mBlurOnSubmit;
+  private @Nullable SelectionWatcher mSelectionWatcher;
+  private final InternalKeyListener mKeyListener;
+
+  private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard();
 
   public ReactEditText(Context context) {
     super(context);
@@ -77,9 +85,11 @@ public ReactEditText(Context context) {
     mNativeEventCount = 0;
     mIsSettingTextFromJS = false;
     mIsJSSettingFocus = false;
+    mBlurOnSubmit = true;
     mListeners = null;
     mTextWatcherDelegator = null;
     mStagedInputType = getInputType();
+    mKeyListener = new InternalKeyListener();
   }
 
   // After the text changes inside an EditText, TextView checks if a layout() has been requested.
@@ -150,6 +160,35 @@ public void removeTextChangedListener(TextWatcher watcher) {
     }
   }
 
+  @Override
+  protected void onSelectionChanged(int selStart, int selEnd) {
+    super.onSelectionChanged(selStart, selEnd);
+    if (mSelectionWatcher != null && hasFocus()) {
+      mSelectionWatcher.onSelectionChanged(selStart, selEnd);
+    }
+  }
+
+  @Override
+  protected void onFocusChanged(
+      boolean focused, int direction, Rect previouslyFocusedRect) {
+    super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    if (focused && mSelectionWatcher != null) {
+      mSelectionWatcher.onSelectionChanged(getSelectionStart(), getSelectionEnd());
+    }
+  }
+
+  public void setSelectionWatcher(SelectionWatcher selectionWatcher) {
+    mSelectionWatcher = selectionWatcher;
+  }
+
+  public void setBlurOnSubmit(boolean blurOnSubmit) {
+    mBlurOnSubmit = blurOnSubmit;
+  }
+
+  public boolean getBlurOnSubmit() {
+    return mBlurOnSubmit;
+  }
+
   /*protected*/ int getStagedInputType() {
     return mStagedInputType;
   }
@@ -168,9 +207,16 @@ public void removeTextChangedListener(TextWatcher watcher) {
   public void setInputType(int type) {
     super.setInputType(type);
     mStagedInputType = type;
+
+    // We override the KeyListener so that all keys on the soft input keyboard as well as hardware
+    // keyboards work. Some KeyListeners like DigitsKeyListener will display the keyboard but not
+    // accept all input from it
+    mKeyListener.setInputType(type);
+    setKeyListener(mKeyListener);
   }
 
-  /* package */ void requestFocusFromJS() {
+  // VisibleForTesting from {@link TextInputEventsTestCase}.
+  public void requestFocusFromJS() {
     mIsJSSettingFocus = true;
     requestFocus();
     mIsJSSettingFocus = false;
@@ -396,4 +442,57 @@ public void afterTextChanged(Editable s) {
       }
     }
   }
+
+  /*
+   * This class is set as the KeyListener for the underlying TextView
+   * It does two things
+   *  1) Provides the same answer to getInputType() as the real KeyListener would have which allows
+   *     the proper keyboard to pop up on screen
+   *  2) Permits all keyboard input through
+   */
+  private static class InternalKeyListener implements KeyListener {
+
+    private int mInputType = 0;
+
+    public InternalKeyListener() {
+    }
+
+    public void setInputType(int inputType) {
+      mInputType = inputType;
+    }
+
+    /*
+     * getInputType will return whatever value is passed in.  This will allow the proper keyboard
+     * to be shown on screen but without the actual filtering done by other KeyListeners
+     */
+    @Override
+    public int getInputType() {
+      return mInputType;
+    }
+
+    /*
+     * All overrides of key handling defer to the underlying KeyListener which is shared by all
+     * ReactEditText instances.  It will basically allow any/all keyboard input whether from
+     * physical keyboard or from soft input.
+     */
+    @Override
+    public boolean onKeyDown(View view, Editable text, int keyCode, KeyEvent event) {
+      return sKeyListener.onKeyDown(view, text, keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(View view, Editable text, int keyCode, KeyEvent event) {
+      return sKeyListener.onKeyUp(view, text, keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyOther(View view, Editable text, KeyEvent event) {
+      return sKeyListener.onKeyOther(view, text, event);
+    }
+
+    @Override
+    public void clearMetaKeyState(View view, Editable content, int states) {
+      sKeyListener.clearMetaKeyState(view, content, states);
+    }
+  }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
index 6e4a3f449701..8376c2d6bfdb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
@@ -11,11 +11,12 @@
 
 import javax.annotation.Nullable;
 
+import java.util.LinkedList;
 import java.util.Map;
 
 import android.graphics.PorterDuff;
-import android.os.SystemClock;
 import android.text.Editable;
+import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Spannable;
 import android.text.TextWatcher;
@@ -25,38 +26,43 @@
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
-import android.text.InputFilter;
 
 import com.facebook.infer.annotation.Assertions;
-import com.facebook.react.bridge.JSApplicationCausedNativeException;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.BaseViewManager;
+import com.facebook.react.uimanager.LayoutShadowNode;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ViewDefaults;
 import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.events.EventDispatcher;
 import com.facebook.react.views.text.DefaultStyleValuesUtil;
-import com.facebook.react.views.text.ReactTextUpdate;
 import com.facebook.react.views.text.TextInlineImageSpan;
+import com.facebook.react.views.text.ReactTextUpdate;
 
 /**
  * Manages instances of TextInput.
  */
-public class ReactTextInputManager extends
-    BaseViewManager<ReactEditText, ReactTextInputShadowNode> {
+public class ReactTextInputManager extends BaseViewManager<ReactEditText, LayoutShadowNode> {
 
   /* package */ static final String REACT_CLASS = "AndroidTextInput";
 
   private static final int FOCUS_TEXT_INPUT = 1;
   private static final int BLUR_TEXT_INPUT = 2;
 
+  private static final int INPUT_TYPE_KEYBOARD_NUMBERED =
+      InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL |
+          InputType.TYPE_NUMBER_FLAG_SIGNED;
+
   private static final String KEYBOARD_TYPE_EMAIL_ADDRESS = "email-address";
   private static final String KEYBOARD_TYPE_NUMERIC = "numeric";
+  private static final String KEYBOARD_TYPE_PHONE_PAD = "phone-pad";
   private static final InputFilter[] EMPTY_FILTERS = new InputFilter[0];
 
   @Override
@@ -77,12 +83,12 @@ public ReactEditText createViewInstance(ThemedReactContext context) {
   }
 
   @Override
-  public ReactTextInputShadowNode createShadowNodeInstance() {
+  public LayoutShadowNode createShadowNodeInstance() {
     return new ReactTextInputShadowNode();
   }
 
   @Override
-  public Class<ReactTextInputShadowNode> getShadowNodeClass() {
+  public Class<? extends LayoutShadowNode> getShadowNodeClass() {
     return ReactTextInputShadowNode.class;
   }
 
@@ -166,6 +172,20 @@ public void setFontSize(ReactEditText view, float fontSize) {
         (int) Math.ceil(PixelUtil.toPixelFromSP(fontSize)));
   }
 
+  @ReactProp(name = "onSelectionChange", defaultBoolean = false)
+  public void setOnSelectionChange(final ReactEditText view, boolean onSelectionChange) {
+    if (onSelectionChange) {
+      view.setSelectionWatcher(new ReactSelectionWatcher(view));
+    } else {
+      view.setSelectionWatcher(null);
+    }
+  }
+
+  @ReactProp(name = "blurOnSubmit", defaultBoolean = true)
+  public void setBlurOnSubmit(ReactEditText view, boolean blurOnSubmit) {
+    view.setBlurOnSubmit(blurOnSubmit);
+  }
+
   @ReactProp(name = "placeholder")
   public void setPlaceholder(ReactEditText view, @Nullable String placeholder) {
     view.setHint(placeholder);
@@ -180,6 +200,15 @@ public void setPlaceholderTextColor(ReactEditText view, @Nullable Integer color)
     }
   }
 
+  @ReactProp(name = "selectionColor", customType = "Color")
+  public void setSelectionColor(ReactEditText view, @Nullable Integer color) {
+    if (color == null) {
+      view.setHighlightColor(DefaultStyleValuesUtil.getDefaultTextColorHighlight(view.getContext()));
+    } else {
+      view.setHighlightColor(color);
+    }
+  }
+
   @ReactProp(name = "underlineColorAndroid", customType = "Color")
   public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineColor) {
     if (underlineColor == null) {
@@ -189,14 +218,34 @@ public void setUnderlineColor(ReactEditText view, @Nullable Integer underlineCol
     }
   }
 
-  @ReactProp(name = "textAlign")
-  public void setTextAlign(ReactEditText view, int gravity) {
-    view.setGravityHorizontal(gravity);
+  @ReactProp(name = ViewProps.TEXT_ALIGN)
+  public void setTextAlign(ReactEditText view, @Nullable String textAlign) {
+    if (textAlign == null || "auto".equals(textAlign)) {
+      view.setGravityHorizontal(Gravity.NO_GRAVITY);
+    } else if ("left".equals(textAlign)) {
+      view.setGravityHorizontal(Gravity.LEFT);
+    } else if ("right".equals(textAlign)) {
+      view.setGravityHorizontal(Gravity.RIGHT);
+    } else if ("center".equals(textAlign)) {
+      view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
+    } else {
+      throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
+    }
   }
 
-  @ReactProp(name = "textAlignVertical")
-  public void setTextAlignVertical(ReactEditText view, int gravity) {
-    view.setGravityVertical(gravity);
+  @ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL)
+  public void setTextAlignVertical(ReactEditText view, @Nullable String textAlignVertical) {
+    if (textAlignVertical == null || "auto".equals(textAlignVertical)) {
+      view.setGravityVertical(Gravity.NO_GRAVITY);
+    } else if ("top".equals(textAlignVertical)) {
+      view.setGravityVertical(Gravity.TOP);
+    } else if ("bottom".equals(textAlignVertical)) {
+      view.setGravityVertical(Gravity.BOTTOM);
+    } else if ("center".equals(textAlignVertical)) {
+      view.setGravityVertical(Gravity.CENTER_VERTICAL);
+    } else {
+      throw new JSApplicationIllegalArgumentException("Invalid textAlignVertical: " + textAlignVertical);
+    }
   }
 
   @ReactProp(name = "editable", defaultBoolean = true)
@@ -211,13 +260,43 @@ public void setNumLines(ReactEditText view, int numLines) {
 
   @ReactProp(name = "maxLength")
   public void setMaxLength(ReactEditText view, @Nullable Integer maxLength) {
+    InputFilter [] currentFilters = view.getFilters();
+    InputFilter[] newFilters = EMPTY_FILTERS;
+
     if (maxLength == null) {
-      view.setFilters(EMPTY_FILTERS);
+      if (currentFilters.length > 0) {
+        LinkedList<InputFilter> list = new LinkedList<>();
+        for (int i = 0; i < currentFilters.length; i++) {
+          if (!(currentFilters[i] instanceof InputFilter.LengthFilter)) {
+            list.add(currentFilters[i]);
+          }
+        }
+        if (!list.isEmpty()) {
+          newFilters = (InputFilter[]) list.toArray();
+        }
+      }
     } else {
-      InputFilter[] filterArray = new InputFilter[1];
-      filterArray[0] = new InputFilter.LengthFilter(maxLength);
-      view.setFilters(filterArray);
+      if (currentFilters.length > 0) {
+        newFilters = currentFilters;
+        boolean replaced = false;
+        for (int i = 0; i < currentFilters.length; i++) {
+          if (currentFilters[i] instanceof InputFilter.LengthFilter) {
+            currentFilters[i] = new InputFilter.LengthFilter(maxLength);
+            replaced = true;
+          }
+        }
+        if (!replaced) {
+          newFilters = new InputFilter[currentFilters.length + 1];
+          System.arraycopy(currentFilters, 0, newFilters, 0, currentFilters.length);
+          currentFilters[currentFilters.length] = new InputFilter.LengthFilter(maxLength);
+        }
+      } else {
+        newFilters = new InputFilter[1];
+        newFilters[0] = new InputFilter.LengthFilter(maxLength);
+      }
     }
+
+    view.setFilters(newFilters);
   }
 
   @ReactProp(name = "autoCorrect")
@@ -244,43 +323,37 @@ public void setMultiline(ReactEditText view, boolean multiline) {
   public void setPassword(ReactEditText view, boolean password) {
     updateStagedInputTypeFlag(
         view,
-        password ? 0 : InputType.TYPE_TEXT_VARIATION_PASSWORD,
+        password ? 0 :
+            InputType.TYPE_NUMBER_VARIATION_PASSWORD | InputType.TYPE_TEXT_VARIATION_PASSWORD,
         password ? InputType.TYPE_TEXT_VARIATION_PASSWORD : 0);
+    checkPasswordType(view);
   }
 
-  @ReactProp(name = "autoCapitalize", defaultInt = InputType.TYPE_CLASS_TEXT)
+  @ReactProp(name = "autoCapitalize")
   public void setAutoCapitalize(ReactEditText view, int autoCapitalize) {
-    int flagsToSet = 0;
-    switch (autoCapitalize) {
-      case InputType.TYPE_TEXT_FLAG_CAP_SENTENCES:
-      case InputType.TYPE_TEXT_FLAG_CAP_WORDS:
-      case InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS:
-      case InputType.TYPE_CLASS_TEXT:
-        flagsToSet = autoCapitalize;
-        break;
-      default:
-        throw new
-            JSApplicationCausedNativeException("Invalid autoCapitalize value: " + autoCapitalize);
-    }
     updateStagedInputTypeFlag(
         view,
         InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_CAP_WORDS |
             InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
-        flagsToSet);
+        autoCapitalize);
   }
 
   @ReactProp(name = "keyboardType")
   public void setKeyboardType(ReactEditText view, @Nullable String keyboardType) {
-    int flagsToSet = 0;
+    int flagsToSet = InputType.TYPE_CLASS_TEXT;
     if (KEYBOARD_TYPE_NUMERIC.equalsIgnoreCase(keyboardType)) {
-      flagsToSet = InputType.TYPE_CLASS_NUMBER;
+      flagsToSet = INPUT_TYPE_KEYBOARD_NUMBERED;
     } else if (KEYBOARD_TYPE_EMAIL_ADDRESS.equalsIgnoreCase(keyboardType)) {
-      flagsToSet = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+      flagsToSet = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS | InputType.TYPE_CLASS_TEXT;
+    } else if (KEYBOARD_TYPE_PHONE_PAD.equalsIgnoreCase(keyboardType)) {
+      flagsToSet = InputType.TYPE_CLASS_PHONE;
     }
     updateStagedInputTypeFlag(
         view,
-        InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
+        INPUT_TYPE_KEYBOARD_NUMBERED | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS |
+            InputType.TYPE_CLASS_TEXT,
         flagsToSet);
+    checkPasswordType(view);
   }
 
   @Override
@@ -289,6 +362,18 @@ protected void onAfterUpdateTransaction(ReactEditText view) {
     view.commitStagedInputType();
   }
 
+  // Sets the correct password type, since numeric and text passwords have different types
+  private static void checkPasswordType(ReactEditText view) {
+    if ((view.getStagedInputType() & INPUT_TYPE_KEYBOARD_NUMBERED) != 0 &&
+        (view.getStagedInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) != 0) {
+      // Text input type is numbered password, remove text password variation, add numeric one
+      updateStagedInputTypeFlag(
+          view,
+          InputType.TYPE_TEXT_VARIATION_PASSWORD,
+          InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+    }
+  }
+
   private static void updateStagedInputTypeFlag(
       ReactEditText view,
       int flagsToUnset,
@@ -347,7 +432,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
       mEventDispatcher.dispatchEvent(
           new ReactTextChangedEvent(
               mEditText.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               s.toString(),
               (int) PixelUtil.toDIPFromPixel(contentWidth),
               (int) PixelUtil.toDIPFromPixel(contentHeight),
@@ -356,11 +441,11 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
       mEventDispatcher.dispatchEvent(
           new ReactTextInputEvent(
               mEditText.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               newText,
               oldText,
               start,
-              count > 0 ? start + count - 1 : start + before));
+              start + before));
     }
 
     @Override
@@ -382,17 +467,17 @@ public void onFocusChange(View v, boolean hasFocus) {
               eventDispatcher.dispatchEvent(
                   new ReactTextInputFocusEvent(
                       editText.getId(),
-                      SystemClock.uptimeMillis()));
+                      SystemClock.nanoTime()));
             } else {
               eventDispatcher.dispatchEvent(
                   new ReactTextInputBlurEvent(
                       editText.getId(),
-                      SystemClock.uptimeMillis()));
+                      SystemClock.nanoTime()));
 
               eventDispatcher.dispatchEvent(
                   new ReactTextInputEndEditingEvent(
                       editText.getId(),
-                      SystemClock.uptimeMillis(),
+                      SystemClock.nanoTime(),
                       editText.getText().toString()));
             }
           }
@@ -410,26 +495,60 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent keyEvent) {
               eventDispatcher.dispatchEvent(
                   new ReactTextInputSubmitEditingEvent(
                       editText.getId(),
-                      SystemClock.uptimeMillis(),
+                      SystemClock.nanoTime(),
                       editText.getText().toString()));
             }
-            return false;
+            return !editText.getBlurOnSubmit();
           }
         });
   }
 
+  private class ReactSelectionWatcher implements SelectionWatcher {
+
+    private ReactEditText mReactEditText;
+    private EventDispatcher mEventDispatcher;
+    private int mPreviousSelectionStart;
+    private int mPreviousSelectionEnd;
+
+    public ReactSelectionWatcher(ReactEditText editText) {
+      mReactEditText = editText;
+      ReactContext reactContext = (ReactContext) editText.getContext();
+      mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
+    }
+
+    @Override
+    public void onSelectionChanged(int start, int end) {
+      // Android will call us back for both the SELECTION_START span and SELECTION_END span in text
+      // To prevent double calling back into js we cache the result of the previous call and only
+      // forward it on if we have new values
+      if (mPreviousSelectionStart != start || mPreviousSelectionEnd != end) {
+        mEventDispatcher.dispatchEvent(
+            new ReactTextInputSelectionEvent(
+                mReactEditText.getId(),
+                SystemClock.nanoTime(),
+                start,
+                end
+            )
+        );
+
+        mPreviousSelectionStart = start;
+        mPreviousSelectionEnd = end;
+      }
+    }
+  }
+
   @Override
   public @Nullable Map getExportedViewConstants() {
     return MapBuilder.of(
-        "TextAlign",
+        "AutoCapitalizationType",
         MapBuilder.of(
-            "start", Gravity.START,
-            "center", Gravity.CENTER_HORIZONTAL,
-            "end", Gravity.END),
-        "TextAlignVertical",
-        MapBuilder.of(
-            "top", Gravity.TOP,
-            "center", Gravity.CENTER_VERTICAL,
-            "bottom", Gravity.BOTTOM));
+            "none",
+            0,
+            "characters",
+            InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
+            "words",
+            InputType.TYPE_TEXT_FLAG_CAP_WORDS,
+            "sentences",
+            InputType.TYPE_TEXT_FLAG_CAP_SENTENCES));
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java
new file mode 100644
index 000000000000..8c6b18d9ba6b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputSelectionEvent.java
@@ -0,0 +1,58 @@
+/**
+ * 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.views.textinput;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted by EditText native view when the text selection changes.
+ */
+/* package */ class ReactTextInputSelectionEvent
+    extends Event<ReactTextInputSelectionEvent> {
+
+  private static final String EVENT_NAME = "topSelectionChange";
+
+  private int mSelectionStart;
+  private int mSelectionEnd;
+
+  public ReactTextInputSelectionEvent(
+      int viewId,
+      long timestampMs,
+      int selectionStart,
+      int selectionEnd) {
+    super(viewId, timestampMs);
+    mSelectionStart = selectionStart;
+    mSelectionEnd = selectionEnd;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
+  }
+
+  private WritableMap serializeEventData() {
+    WritableMap eventData = Arguments.createMap();
+
+    WritableMap selectionData = Arguments.createMap();
+    selectionData.putInt("start", mSelectionStart);
+    selectionData.putInt("end", mSelectionEnd);
+
+    eventData.putMap("selection", selectionData);
+    return eventData;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
index 47b3f48fdba3..69bb727740cd 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputShadowNode.java
@@ -13,7 +13,6 @@
 
 import android.text.Spannable;
 import android.util.TypedValue;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.EditText;
 
@@ -23,10 +22,10 @@
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.ReactProp;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.UIViewOperationQueue;
 import com.facebook.react.uimanager.ViewDefaults;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.views.text.ReactTextShadowNode;
 import com.facebook.react.views.text.ReactTextUpdate;
 
@@ -34,10 +33,6 @@
 public class ReactTextInputShadowNode extends ReactTextShadowNode implements
     CSSNode.MeasureFunction {
 
-  private static final int MEASURE_SPEC = View.MeasureSpec.makeMeasureSpec(
-      ViewGroup.LayoutParams.WRAP_CONTENT,
-      View.MeasureSpec.UNSPECIFIED);
-
   private @Nullable EditText mEditText;
   private @Nullable float[] mComputedPadding;
   private int mJsEventCount = UNSET;
@@ -48,7 +43,7 @@ public ReactTextInputShadowNode() {
   }
 
   @Override
-  protected void setThemedContext(ThemedReactContext themedContext) {
+  public void setThemedContext(ThemedReactContext themedContext) {
     super.setThemedContext(themedContext);
 
     // TODO #7120264: cache this stuff better
@@ -68,7 +63,7 @@ protected void setThemedContext(ThemedReactContext themedContext) {
   }
 
   @Override
-  public void measure(CSSNode node, float width, MeasureOutput measureOutput) {
+  public void measure(CSSNode node, float width, float height, MeasureOutput measureOutput) {
     // measure() should never be called before setThemedContext()
     EditText editText = Assertions.assertNotNull(mEditText);
 
@@ -88,7 +83,7 @@ public void measure(CSSNode node, float width, MeasureOutput measureOutput) {
       editText.setLines(mNumberOfLines);
     }
 
-    editText.measure(MEASURE_SPEC, MEASURE_SPEC);
+    editText.measure(0 /* unspecified */, 0 /* unspecified */);
     measureOutput.height = editText.getMeasuredHeight();
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/SelectionWatcher.java
similarity index 51%
rename from ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java
rename to ReactAndroid/src/main/java/com/facebook/react/views/textinput/SelectionWatcher.java
index 2d99e79f5698..f569067ab916 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactNative.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/SelectionWatcher.java
@@ -7,13 +7,12 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-package com.facebook.react.uimanager;
-
-import com.facebook.react.bridge.JavaScriptModule;
+package com.facebook.react.views.textinput;
 
 /**
- * JS module interface - used by UIManager to communicate with main React JS module methods
+ * Implement this interface to be informed of selection changes in the ReactTextEdit
+ * This is used by the ReactTextInputManager to forward events from the EditText to JS
  */
-public interface ReactNative extends JavaScriptModule {
-  void unmountComponentAtNodeAndRemoveContainer(int rootNodeTag);
+interface SelectionWatcher {
+  public void onSelectionChanged(int start, int end);
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
index 35a06f331104..df52d70d0753 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
@@ -6,23 +6,17 @@ android_library(
   deps = [
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/bridge:bridge'),
-    react_native_target('java/com/facebook/react/uimanager:uimanager'),
     react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_dep('android_res/android/support/v7/appcompat-orig:res-for-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
+    react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'),
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/bitmaps:bitmaps',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/common:common',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/request:request',
-    '//libraries/imagepipeline/src/main/java/com/facebook/imagepipeline/image:image',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/controller:controller',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/drawable:drawable',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/generic:generic',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/interfaces:interfaces',
-    '//libraries/drawee/src/main/java/com/facebook/drawee/view:view',
-    '//libraries/fresco/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline:pipeline',
-    '//third-party/android-support-for-standalone-apps/v7/appcompat:appcompat-23.1',
-    '//third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native',
-    '//third-party/java/android/support/v4:lib-support-v4',
-  ],
+],
   visibility = [
     'PUBLIC',
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/DrawableWithIntrinsicSize.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/DrawableWithIntrinsicSize.java
index 49a9a4b390b6..8b92242914c9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/DrawableWithIntrinsicSize.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/DrawableWithIntrinsicSize.java
@@ -1,4 +1,11 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
+/**
+ * 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.views.toolbar;
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java
index 5fe30921a695..92c585d71bd3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbar.java
@@ -1,9 +1,14 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
+/**
+ * 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.views.toolbar;
 
-import javax.annotation.Nullable;
-
 import android.content.Context;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
@@ -14,7 +19,6 @@
 
 import com.facebook.drawee.backends.pipeline.Fresco;
 import com.facebook.drawee.controller.BaseControllerListener;
-import com.facebook.drawee.controller.ControllerListener;
 import com.facebook.drawee.drawable.ScalingUtils;
 import com.facebook.drawee.generic.GenericDraweeHierarchy;
 import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
@@ -22,88 +26,110 @@
 import com.facebook.drawee.view.DraweeHolder;
 import com.facebook.drawee.view.MultiDraweeHolder;
 import com.facebook.imagepipeline.image.ImageInfo;
+import com.facebook.imagepipeline.image.QualityInfo;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.uimanager.PixelUtil;
+
+import javax.annotation.Nullable;
 
 /**
  * Custom implementation of the {@link Toolbar} widget that adds support for remote images in logo
  * and navigationIcon using fresco.
  */
 public class ReactToolbar extends Toolbar {
+
   private static final String PROP_ACTION_ICON = "icon";
   private static final String PROP_ACTION_SHOW = "show";
   private static final String PROP_ACTION_SHOW_WITH_TEXT = "showWithText";
   private static final String PROP_ACTION_TITLE = "title";
 
+  private static final String PROP_ICON_URI = "uri";
+  private static final String PROP_ICON_WIDTH = "width";
+  private static final String PROP_ICON_HEIGHT = "height";
+
   private final DraweeHolder mLogoHolder;
   private final DraweeHolder mNavIconHolder;
   private final DraweeHolder mOverflowIconHolder;
   private final MultiDraweeHolder<GenericDraweeHierarchy> mActionsHolder =
-      new MultiDraweeHolder<>();
-
-  private final ControllerListener<ImageInfo> mLogoControllerListener =
-      new BaseControllerListener<ImageInfo>() {
-        @Override
-        public void onFinalImageSet(
-            String id,
-            @Nullable final ImageInfo imageInfo,
-            @Nullable Animatable animatable) {
-          if (imageInfo != null) {
-            final DrawableWithIntrinsicSize logoDrawable =
-                new DrawableWithIntrinsicSize(mLogoHolder.getTopLevelDrawable(), imageInfo);
-            setLogo(logoDrawable);
-          }
-        }
-      };
-
-  private final ControllerListener<ImageInfo> mNavIconControllerListener =
-      new BaseControllerListener<ImageInfo>() {
-        @Override
-        public void onFinalImageSet(
-            String id,
-            @Nullable final ImageInfo imageInfo,
-            @Nullable Animatable animatable) {
-          if (imageInfo != null) {
-            final DrawableWithIntrinsicSize navIconDrawable =
-                new DrawableWithIntrinsicSize(mNavIconHolder.getTopLevelDrawable(), imageInfo);
-            setNavigationIcon(navIconDrawable);
-          }
-        }
-      };
-
-  private final ControllerListener<ImageInfo> mOverflowIconControllerListener =
-      new BaseControllerListener<ImageInfo>() {
-        @Override
-        public void onFinalImageSet(
-            String id,
-            @Nullable final ImageInfo imageInfo,
-            @Nullable Animatable animatable) {
-          if (imageInfo != null) {
-            final DrawableWithIntrinsicSize overflowIconDrawable =
-                new DrawableWithIntrinsicSize(mOverflowIconHolder.getTopLevelDrawable(), imageInfo);
-            setOverflowIcon(overflowIconDrawable);
-          }
-        }
-      };
+          new MultiDraweeHolder<>();
+
+  private IconControllerListener mLogoControllerListener;
+  private IconControllerListener mNavIconControllerListener;
+  private IconControllerListener mOverflowIconControllerListener;
+
+  /**
+   * Attaches specific icon width & height to a BaseControllerListener which will be used to
+   * create the Drawable
+   */
+  private abstract class IconControllerListener extends BaseControllerListener<ImageInfo> {
 
-  private static class ActionIconControllerListener extends BaseControllerListener<ImageInfo> {
-    private final MenuItem mItem;
     private final DraweeHolder mHolder;
 
+    private IconImageInfo mIconImageInfo;
+
+    public IconControllerListener(DraweeHolder holder) {
+      mHolder = holder;
+    }
+
+    public void setIconImageInfo(IconImageInfo iconImageInfo) {
+      mIconImageInfo = iconImageInfo;
+    }
+
+    @Override
+    public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {
+      super.onFinalImageSet(id, imageInfo, animatable);
+
+      final ImageInfo info = mIconImageInfo != null ? mIconImageInfo : imageInfo;
+      setDrawable(new DrawableWithIntrinsicSize(mHolder.getTopLevelDrawable(), info));
+    }
+
+    protected abstract void setDrawable(Drawable d);
+
+  }
+
+  private class ActionIconControllerListener extends IconControllerListener {
+    private final MenuItem mItem;
+
     ActionIconControllerListener(MenuItem item, DraweeHolder holder) {
+      super(holder);
       mItem = item;
-      mHolder = holder;
     }
 
     @Override
-    public void onFinalImageSet(
-        String id,
-        @Nullable ImageInfo imageInfo,
-        @Nullable Animatable animatable) {
-      if (imageInfo != null) {
-        mItem.setIcon(new DrawableWithIntrinsicSize(mHolder.getTopLevelDrawable(), imageInfo));
-      }
+    protected void setDrawable(Drawable d) {
+      mItem.setIcon(d);
+    }
+  }
+
+  /**
+   * Simple implementation of ImageInfo, only providing width & height
+   */
+  private static class IconImageInfo implements ImageInfo {
+
+    private int mWidth;
+    private int mHeight;
+
+    public IconImageInfo(int width, int height) {
+      mWidth = width;
+      mHeight = height;
+    }
+
+    @Override
+    public int getWidth() {
+      return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+      return mHeight;
+    }
+
+    @Override
+    public QualityInfo getQualityInfo() {
+      return null;
     }
+
   }
 
   public ReactToolbar(Context context) {
@@ -112,6 +138,28 @@ public ReactToolbar(Context context) {
     mLogoHolder = DraweeHolder.create(createDraweeHierarchy(), context);
     mNavIconHolder = DraweeHolder.create(createDraweeHierarchy(), context);
     mOverflowIconHolder = DraweeHolder.create(createDraweeHierarchy(), context);
+
+    mLogoControllerListener = new IconControllerListener(mLogoHolder) {
+      @Override
+      protected void setDrawable(Drawable d) {
+        setLogo(d);
+      }
+    };
+
+    mNavIconControllerListener = new IconControllerListener(mNavIconHolder) {
+      @Override
+      protected void setDrawable(Drawable d) {
+        setNavigationIcon(d);
+      }
+    };
+
+    mOverflowIconControllerListener = new IconControllerListener(mOverflowIconHolder) {
+      @Override
+      protected void setDrawable(Drawable d) {
+        setOverflowIcon(d);
+      }
+    };
+
   }
 
   private final Runnable mLayoutRunnable = new Runnable() {
@@ -172,51 +220,15 @@ private void attachDraweeHolders() {
   }
 
   /* package */ void setLogoSource(@Nullable ReadableMap source) {
-    String uri = source != null ? source.getString("uri") : null;
-    if (uri == null) {
-      setLogo(null);
-    } else if (uri.startsWith("http://") || uri.startsWith("https://")) {
-      DraweeController controller = Fresco.newDraweeControllerBuilder()
-          .setUri(Uri.parse(uri))
-          .setControllerListener(mLogoControllerListener)
-          .setOldController(mLogoHolder.getController())
-          .build();
-      mLogoHolder.setController(controller);
-    } else {
-      setLogo(getDrawableResourceByName(uri));
-    }
+    setIconSource(source, mLogoControllerListener, mLogoHolder);
   }
 
   /* package */ void setNavIconSource(@Nullable ReadableMap source) {
-    String uri = source != null ? source.getString("uri") : null;
-    if (uri == null) {
-      setNavigationIcon(null);
-    } else if (uri.startsWith("http://") || uri.startsWith("https://")) {
-      DraweeController controller = Fresco.newDraweeControllerBuilder()
-          .setUri(Uri.parse(uri))
-          .setControllerListener(mNavIconControllerListener)
-          .setOldController(mNavIconHolder.getController())
-          .build();
-      mNavIconHolder.setController(controller);
-    } else {
-      setNavigationIcon(getDrawableResourceByName(uri));
-    }
+    setIconSource(source, mNavIconControllerListener, mNavIconHolder);
   }
 
   /* package */ void setOverflowIconSource(@Nullable ReadableMap source) {
-    String uri = source != null ? source.getString("uri") : null;
-    if (uri == null) {
-      setOverflowIcon(null);
-    } else if (uri.startsWith("http://") || uri.startsWith("https://")) {
-      DraweeController controller = Fresco.newDraweeControllerBuilder()
-          .setUri(Uri.parse(uri))
-          .setControllerListener(mOverflowIconControllerListener)
-          .setOldController(mOverflowIconHolder.getController())
-          .build();
-      mOverflowIconHolder.setController(controller);
-    } else {
-      setOverflowIcon(getDrawableByName(uri));
-    }
+    setIconSource(source, mOverflowIconControllerListener, mOverflowIconHolder);
   }
 
   /* package */ void setActions(@Nullable ReadableArray actions) {
@@ -226,16 +238,13 @@ private void attachDraweeHolders() {
     if (actions != null) {
       for (int i = 0; i < actions.size(); i++) {
         ReadableMap action = actions.getMap(i);
+
         MenuItem item = menu.add(Menu.NONE, Menu.NONE, i, action.getString(PROP_ACTION_TITLE));
-        ReadableMap icon = action.hasKey(PROP_ACTION_ICON) ? action.getMap(PROP_ACTION_ICON) : null;
-        if (icon != null) {
-          String iconSource = icon.getString("uri");
-          if (iconSource.startsWith("http://") || iconSource.startsWith("https://")) {
-            setMenuItemIcon(item, icon);
-          } else {
-            item.setIcon(getDrawableResourceByName(iconSource));
-          }
+
+        if (action.hasKey(PROP_ACTION_ICON)) {
+          setMenuItemIcon(item, action.getMap(PROP_ACTION_ICON));
         }
+
         int showAsAction = action.hasKey(PROP_ACTION_SHOW)
             ? action.getInt(PROP_ACTION_SHOW)
             : MenuItem.SHOW_AS_ACTION_NEVER;
@@ -248,24 +257,43 @@ private void attachDraweeHolders() {
     }
   }
 
-  /**
-   * This is only used when the icon is remote (http/s). Creates & adds a new {@link DraweeHolder}
-   * to {@link #mActionsHolder} and attaches a {@link ActionIconControllerListener} that just sets
-   * the top level drawable when it's loaded.
-   */
-  private void setMenuItemIcon(MenuItem item, ReadableMap icon) {
-    String iconSource = icon.getString("uri");
+  private void setMenuItemIcon(final MenuItem item, ReadableMap iconSource) {
 
     DraweeHolder<GenericDraweeHierarchy> holder =
-        DraweeHolder.create(createDraweeHierarchy(), getContext());
-    DraweeController controller = Fresco.newDraweeControllerBuilder()
-        .setUri(Uri.parse(iconSource))
-        .setControllerListener(new ActionIconControllerListener(item, holder))
-        .setOldController(holder.getController())
-        .build();
-    holder.setController(controller);
+            DraweeHolder.create(createDraweeHierarchy(), getContext());
+    ActionIconControllerListener controllerListener = new ActionIconControllerListener(item, holder);
+    controllerListener.setIconImageInfo(getIconImageInfo(iconSource));
+
+    setIconSource(iconSource, controllerListener, holder);
 
     mActionsHolder.add(holder);
+
+  }
+
+  /**
+   * Sets an icon for a specific icon source. If the uri indicates an icon
+   * to be somewhere remote (http/https) or on the local filesystem, it uses fresco to load it.
+   * Otherwise it loads the Drawable from the Resources and directly returns it via a callback
+   */
+  private void setIconSource(ReadableMap source, IconControllerListener controllerListener, DraweeHolder holder) {
+
+    String uri = source != null ? source.getString(PROP_ICON_URI) : null;
+
+    if (uri == null) {
+      controllerListener.setIconImageInfo(null);
+      controllerListener.setDrawable(null);
+    } else if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("file://")) {
+      controllerListener.setIconImageInfo(getIconImageInfo(source));
+      DraweeController controller = Fresco.newDraweeControllerBuilder()
+              .setUri(Uri.parse(uri))
+              .setControllerListener(controllerListener)
+              .setOldController(holder.getController())
+              .build();
+      holder.setController(controller);
+    } else {
+      controllerListener.setDrawable(getDrawableByName(uri));
+    }
+
   }
 
   private GenericDraweeHierarchy createDraweeHierarchy() {
@@ -283,7 +311,22 @@ private int getDrawableResourceByName(String name) {
   }
 
   private Drawable getDrawableByName(String name) {
-    return getResources().getDrawable(getDrawableResourceByName(name));
+    int drawableResId = getDrawableResourceByName(name);
+    if (drawableResId != 0) {
+      return getResources().getDrawable(getDrawableResourceByName(name));
+    } else {
+      return null;
+    }
+  }
+
+  private IconImageInfo getIconImageInfo(ReadableMap source) {
+    if (source.hasKey(PROP_ICON_WIDTH) && source.hasKey(PROP_ICON_HEIGHT)) {
+      final int width = Math.round(PixelUtil.toPixelFromDIP(source.getInt(PROP_ICON_WIDTH)));
+      final int height = Math.round(PixelUtil.toPixelFromDIP(source.getInt(PROP_ICON_HEIGHT)));
+      return new IconImageInfo(width, height);
+    } else {
+      return null;
+    }
   }
 
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java
index f55d6d8694c9..9f162f9f0b41 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/ReactToolbarManager.java
@@ -17,7 +17,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.os.SystemClock;
+import android.util.LayoutDirection;
 import android.view.MenuItem;
 import android.view.View;
 
@@ -25,10 +25,12 @@
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
 import com.facebook.react.common.MapBuilder;
-import com.facebook.react.uimanager.ReactProp;
+import com.facebook.react.common.SystemClock;
+import com.facebook.react.uimanager.PixelUtil;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.ViewGroupManager;
+import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.events.EventDispatcher;
 import com.facebook.react.views.toolbar.events.ToolbarClickEvent;
 
@@ -64,6 +66,11 @@ public void setOverflowIcon(ReactToolbar view, @Nullable ReadableMap overflowIco
     view.setOverflowIconSource(overflowIcon);
   }
 
+  @ReactProp(name = "rtl")
+  public void setRtl(ReactToolbar view, boolean rtl) {
+    view.setLayoutDirection(rtl ? LayoutDirection.LTR : LayoutDirection.RTL);
+  }
+
   @ReactProp(name = "subtitle")
   public void setSubtitle(ReactToolbar view, @Nullable String subtitle) {
     view.setSubtitle(subtitle);
@@ -94,6 +101,22 @@ public void setTitleColor(ReactToolbar view, @Nullable Integer titleColor) {
     }
   }
 
+  @ReactProp(name = "contentInsetStart", defaultFloat = Float.NaN)
+  public void setContentInsetStart(ReactToolbar view, float insetStart) {
+    int inset = Float.isNaN(insetStart) ?
+        getDefaultContentInsets(view.getContext())[0] :
+        Math.round(PixelUtil.toPixelFromDIP(insetStart));
+    view.setContentInsetsRelative(inset, view.getContentInsetEnd());
+  }
+
+  @ReactProp(name = "contentInsetEnd", defaultFloat = Float.NaN)
+  public void setContentInsetEnd(ReactToolbar view, float insetEnd) {
+    int inset = Float.isNaN(insetEnd) ?
+        getDefaultContentInsets(view.getContext())[1] :
+        Math.round(PixelUtil.toPixelFromDIP(insetEnd));
+    view.setContentInsetsRelative(view.getContentInsetStart(), inset);
+  }
+
   @ReactProp(name = "nativeActions")
   public void setActions(ReactToolbar view, @Nullable ReadableArray actions) {
     view.setActions(actions);
@@ -108,7 +131,7 @@ protected void addEventEmitters(final ThemedReactContext reactContext, final Rea
           @Override
           public void onClick(View v) {
             mEventDispatcher.dispatchEvent(
-                new ToolbarClickEvent(view.getId(), SystemClock.uptimeMillis(), -1));
+                new ToolbarClickEvent(view.getId(), SystemClock.nanoTime(), -1));
           }
         });
 
@@ -119,7 +142,7 @@ public boolean onMenuItemClick(MenuItem menuItem) {
             mEventDispatcher.dispatchEvent(
                 new ToolbarClickEvent(
                     view.getId(),
-                    SystemClock.uptimeMillis(),
+                    SystemClock.nanoTime(),
                     menuItem.getOrder()));
             return true;
           }
@@ -142,6 +165,34 @@ public boolean needsCustomLayoutForChildren() {
     return true;
   }
 
+  private int[] getDefaultContentInsets(Context context) {
+    Resources.Theme theme = context.getTheme();
+    TypedArray toolbarStyle = null;
+    TypedArray contentInsets = null;
+
+    try {
+      toolbarStyle = theme
+              .obtainStyledAttributes(new int[]{R.attr.toolbarStyle});
+
+      int toolbarStyleResId = toolbarStyle.getResourceId(0, 0);
+
+      contentInsets = theme.obtainStyledAttributes(
+              toolbarStyleResId, new int[]{
+                      R.attr.contentInsetStart,
+                      R.attr.contentInsetEnd,
+              });
+
+      int contentInsetStart = contentInsets.getDimensionPixelSize(0, 0);
+      int contentInsetEnd = contentInsets.getDimensionPixelSize(1, 0);
+
+      return new int[] {contentInsetStart, contentInsetEnd};
+    } finally {
+      recycleQuietly(toolbarStyle);
+      recycleQuietly(contentInsets);
+    }
+
+  }
+
   private static int[] getDefaultColors(Context context) {
     Resources.Theme theme = context.getTheme();
     TypedArray toolbarStyle = null;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK
index 428372baa2e4..dc06dec1873b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/BUCK
@@ -9,6 +9,7 @@ android_library(
     react_native_target('java/com/facebook/csslayout:csslayout'),
     react_native_target('java/com/facebook/react/touch:touch'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_dep('third-party/java/infer-annotations:infer-annotations'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroup.java
index af73bda94c10..37482c2bce7f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroup.java
@@ -12,7 +12,7 @@
 import android.graphics.Rect;
 import android.view.View;
 
-import com.facebook.react.uimanager.CatalystStylesDiffMap;
+import com.facebook.react.uimanager.ReactStylesDiffMap;
 
 /**
  * Interface that should be implemented by {@link View} subclasses that support
@@ -52,7 +52,7 @@ public interface ReactClippingViewGroup {
    *
    * Helper method {@link ReactClippingViewGroupHelper#applyRemoveClippedSubviewsProperty} may be
    * used by {@link ViewManager} subclass to apply this property based on property update map
-   * {@link CatalystStylesDiffMap}.
+   * {@link ReactStylesDiffMap}.
    */
   void setRemoveClippedSubviews(boolean removeClippedSubviews);
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroupHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroupHelper.java
index 1345bdd89188..0751b9f7a19b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroupHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewGroupHelper.java
@@ -15,7 +15,7 @@
 import android.view.View;
 import android.view.ViewParent;
 
-import com.facebook.react.uimanager.CatalystStylesDiffMap;
+import com.facebook.react.uimanager.ReactStylesDiffMap;
 
 /**
  * Provides implementation of common tasks for view and it's view manager supporting property
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java
index 8cf2d3e26b65..a4dda0d9d3d8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.java
@@ -27,7 +27,7 @@
  * Utility class that helps with converting android drawable description used in JS to an actual
  * instance of {@link Drawable}.
  */
-/* package */ class ReactDrawableHelper {
+public class ReactDrawableHelper {
 
   private static final TypedValue sResolveOutValue = new TypedValue();
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java
index 84289b62d605..fd8961cb8628 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundDrawable.java
@@ -11,18 +11,21 @@
 
 import javax.annotation.Nullable;
 
+import java.util.Arrays;
 import java.util.Locale;
 
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.DashPathEffect;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PathEffect;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.csslayout.CSSConstants;
@@ -76,7 +79,10 @@ private static enum BorderStyle {
   /* Used for rounded border and rounded background */
   private @Nullable PathEffect mPathEffectForBorderStyle;
   private @Nullable Path mPathForBorderRadius;
+  private @Nullable Path mPathForBorderRadiusOutline;
+  private @Nullable Path mPathForBorder;
   private @Nullable RectF mTempRectForBorderRadius;
+  private @Nullable RectF mTempRectForBorderRadiusOutline;
   private boolean mNeedUpdatePathForBorderRadius = false;
   private float mBorderRadius = CSSConstants.UNDEFINED;
 
@@ -85,9 +91,11 @@ private static enum BorderStyle {
   private int mColor = Color.TRANSPARENT;
   private int mAlpha = 255;
 
+  private @Nullable float[] mBorderCornerRadii;
+
   @Override
   public void draw(Canvas canvas) {
-    if (!CSSConstants.isUndefined(mBorderRadius) && mBorderRadius > 0) {
+    if ((!CSSConstants.isUndefined(mBorderRadius) && mBorderRadius > 0) || mBorderCornerRadii != null) {
       drawRoundedBackgroundWithBorders(canvas);
     } else {
       drawRectangularBackgroundWithBorders(canvas);
@@ -123,6 +131,22 @@ public int getOpacity() {
     return ColorUtil.getOpacityFromColor(ColorUtil.multiplyColorAlpha(mColor, mAlpha));
   }
 
+  /* Android's elevation implementation requires this to be implemented to know where to draw the shadow. */
+  @Override
+  public void getOutline(Outline outline) {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+      super.getOutline(outline);
+      return;
+    }
+    if((!CSSConstants.isUndefined(mBorderRadius) && mBorderRadius > 0) || mBorderCornerRadii != null) {
+      updatePath();
+
+      outline.setConvexPath(mPathForBorderRadiusOutline);
+    } else {
+      outline.setRect(getBounds());
+    }
+  }
+
   public void setBorderWidth(int position, float width) {
     if (mBorderWidth == null) {
       mBorderWidth = new Spacing();
@@ -162,8 +186,22 @@ public void setBorderStyle(@Nullable String style) {
   }
 
   public void setRadius(float radius) {
-    if (mBorderRadius != radius) {
+    if (!FloatUtil.floatsEqual(mBorderRadius,radius)) {
       mBorderRadius = radius;
+      mNeedUpdatePathForBorderRadius = true;
+      invalidateSelf();
+    }
+  }
+
+  public void setRadius(float radius, int position) {
+    if (mBorderCornerRadii == null) {
+      mBorderCornerRadii = new float[4];
+      Arrays.fill(mBorderCornerRadii, CSSConstants.UNDEFINED);
+    }
+
+    if (!FloatUtil.floatsEqual(mBorderCornerRadii[position], radius)) {
+      mBorderCornerRadii[position] = radius;
+      mNeedUpdatePathForBorderRadius = true;
       invalidateSelf();
     }
   }
@@ -206,19 +244,61 @@ private void updatePath() {
     if (mPathForBorderRadius == null) {
       mPathForBorderRadius = new Path();
       mTempRectForBorderRadius = new RectF();
+      mPathForBorderRadiusOutline = new Path();
+      mTempRectForBorderRadiusOutline = new RectF();
     }
+
     mPathForBorderRadius.reset();
+    mPathForBorderRadiusOutline.reset();
+
     mTempRectForBorderRadius.set(getBounds());
+    mTempRectForBorderRadiusOutline.set(getBounds());
     float fullBorderWidth = getFullBorderWidth();
     if (fullBorderWidth > 0) {
       mTempRectForBorderRadius.inset(fullBorderWidth * 0.5f, fullBorderWidth * 0.5f);
     }
+
+    float defaultBorderRadius = !CSSConstants.isUndefined(mBorderRadius) ? mBorderRadius : 0;
+    float topLeftRadius = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[0]) ? mBorderCornerRadii[0] : defaultBorderRadius;
+    float topRightRadius = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[1]) ? mBorderCornerRadii[1] : defaultBorderRadius;
+    float bottomRightRadius = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[2]) ? mBorderCornerRadii[2] : defaultBorderRadius;
+    float bottomLeftRadius = mBorderCornerRadii != null && !CSSConstants.isUndefined(mBorderCornerRadii[3]) ? mBorderCornerRadii[3] : defaultBorderRadius;
+
+
     mPathForBorderRadius.addRoundRect(
         mTempRectForBorderRadius,
-        mBorderRadius,
-        mBorderRadius,
+        new float[] {
+          topLeftRadius,
+          topLeftRadius,
+          topRightRadius,
+          topRightRadius,
+          bottomRightRadius,
+          bottomRightRadius,
+          bottomLeftRadius,
+          bottomLeftRadius
+        },
         Path.Direction.CW);
 
+    float extraRadiusForOutline = 0;
+
+    if (mBorderWidth != null) {
+      extraRadiusForOutline = mBorderWidth.get(Spacing.ALL) / 2f;
+    }
+
+    mPathForBorderRadiusOutline.addRoundRect(
+      mTempRectForBorderRadiusOutline,
+      new float[] {
+        topLeftRadius + extraRadiusForOutline,
+        topLeftRadius + extraRadiusForOutline,
+        topRightRadius + extraRadiusForOutline,
+        topRightRadius + extraRadiusForOutline,
+        bottomRightRadius + extraRadiusForOutline,
+        bottomRightRadius + extraRadiusForOutline,
+        bottomLeftRadius + extraRadiusForOutline,
+        bottomLeftRadius + extraRadiusForOutline
+      },
+      Path.Direction.CW);
+
     mPathEffectForBorderStyle = mBorderStyle != null
         ? mBorderStyle.getPathEffect(getFullBorderWidth())
         : null;
@@ -264,30 +344,64 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) {
       int width = getBounds().width();
       int height = getBounds().height();
 
+      // If the path drawn previously is of the same color,
+      // there would be a slight white space between borders
+      // with anti-alias set to true.
+      // Therefore we need to disable anti-alias, and
+      // after drawing is done, we will re-enable it.
+
+      mPaint.setAntiAlias(false);
+
+      if (mPathForBorder == null) {
+        mPathForBorder = new Path();
+      }
+
       if (borderLeft > 0 && colorLeft != Color.TRANSPARENT) {
         mPaint.setColor(colorLeft);
-        canvas.drawRect(0, borderTop, borderLeft, height - borderBottom, mPaint);
+        mPathForBorder.reset();
+        mPathForBorder.moveTo(0, 0);
+        mPathForBorder.lineTo(borderLeft, borderTop);
+        mPathForBorder.lineTo(borderLeft, height - borderBottom);
+        mPathForBorder.lineTo(0, height);
+        mPathForBorder.lineTo(0, 0);
+        canvas.drawPath(mPathForBorder, mPaint);
       }
 
       if (borderTop > 0 && colorTop != Color.TRANSPARENT) {
         mPaint.setColor(colorTop);
-        canvas.drawRect(0, 0, width, borderTop, mPaint);
+        mPathForBorder.reset();
+        mPathForBorder.moveTo(0, 0);
+        mPathForBorder.lineTo(borderLeft, borderTop);
+        mPathForBorder.lineTo(width - borderRight, borderTop);
+        mPathForBorder.lineTo(width, 0);
+        mPathForBorder.lineTo(0, 0);
+        canvas.drawPath(mPathForBorder, mPaint);
       }
 
       if (borderRight > 0 && colorRight != Color.TRANSPARENT) {
         mPaint.setColor(colorRight);
-        canvas.drawRect(
-            width - borderRight,
-            borderTop,
-            width,
-            height - borderBottom,
-            mPaint);
+        mPathForBorder.reset();
+        mPathForBorder.moveTo(width, 0);
+        mPathForBorder.lineTo(width, height);
+        mPathForBorder.lineTo(width - borderRight, height - borderBottom);
+        mPathForBorder.lineTo(width - borderRight, borderTop);
+        mPathForBorder.lineTo(width, 0);
+        canvas.drawPath(mPathForBorder, mPaint);
       }
 
       if (borderBottom > 0 && colorBottom != Color.TRANSPARENT) {
         mPaint.setColor(colorBottom);
-        canvas.drawRect(0, height - borderBottom, width, height, mPaint);
+        mPathForBorder.reset();
+        mPathForBorder.moveTo(0, height);
+        mPathForBorder.lineTo(width, height);
+        mPathForBorder.lineTo(width - borderRight, height - borderBottom);
+        mPathForBorder.lineTo(borderLeft, height - borderBottom);
+        mPathForBorder.lineTo(0, height);
+        canvas.drawPath(mPathForBorder, mPaint);
       }
+
+      // re-enable anti alias
+      mPaint.setAntiAlias(true);
     }
   }
 
@@ -296,7 +410,7 @@ private int getBorderWidth(int position) {
   }
 
   private int getBorderColor(int position) {
-    // Check CatalystStylesDiffMap#getColorInt() to see why this is needed
+    // Check ReactStylesDiffMap#getColorInt() to see why this is needed
     return mBorderColor != null ? (int) (long) mBorderColor.get(position) : DEFAULT_BORDER_COLOR;
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
index 134b6492b9bf..fcd2fd5eb6f7 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
@@ -16,13 +16,15 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.view.animation.Animation;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.common.annotations.VisibleForTesting;
-import com.facebook.react.touch.CatalystInterceptingViewGroup;
+import com.facebook.react.touch.ReactHitSlopView;
+import com.facebook.react.touch.ReactInterceptingViewGroup;
 import com.facebook.react.touch.OnInterceptTouchEventListener;
 import com.facebook.react.uimanager.MeasureSpecAssertions;
 import com.facebook.react.uimanager.PointerEvents;
@@ -33,7 +35,7 @@
  * initializes most of the storage needed for them.
  */
 public class ReactViewGroup extends ViewGroup implements
-    CatalystInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView {
+    ReactInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView {
 
   private static final int ARRAY_CAPACITY_INCREMENT = 12;
   private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
@@ -86,6 +88,7 @@ public void onLayoutChange(
   private @Nullable View[] mAllChildren = null;
   private int mAllChildrenCount;
   private @Nullable Rect mClippingRect;
+  private @Nullable Rect mHitSlopRect;
   private PointerEvents mPointerEvents = PointerEvents.AUTO;
   private @Nullable ChildrenLayoutChangeListener mChildrenLayoutChangeListener;
   private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable;
@@ -206,6 +209,10 @@ public void setBorderRadius(float borderRadius) {
     getOrCreateReactViewBackground().setRadius(borderRadius);
   }
 
+  public void setBorderRadius(float borderRadius, int position) {
+    getOrCreateReactViewBackground().setRadius(borderRadius, position);
+  }
+
   public void setBorderStyle(@Nullable String style) {
     getOrCreateReactViewBackground().setBorderStyle(style);
   }
@@ -286,7 +293,15 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa
     boolean intersects = clippingRect
         .intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
     boolean needUpdateClippingRecursive = false;
-    if (!intersects && child.getParent() != null) {
+    // We never want to clip children that are being animated, as this can easily break layout :
+    // when layout animation changes size and/or position of views contained inside a listview that
+    // clips offscreen children, we need to ensure that, when view exits the viewport, final size
+    // and position is set prior to removing the view from its listview parent.
+    // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll,
+    // it won't be size and located properly.
+    Animation animation = child.getAnimation();
+    boolean isAnimating = animation != null && !animation.hasEnded();
+    if (!intersects && child.getParent() != null && !isAnimating) {
       // We can try saving on invalidate call here as the view that we remove is out of visible area
       // therefore invalidation is not necessary.
       super.removeViewsInLayout(idx - clippedSoFar, 1);
@@ -295,8 +310,8 @@ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFa
       super.addViewInLayout(child, idx - clippedSoFar, sDefaultLayoutParam, true);
       invalidate();
       needUpdateClippingRecursive = true;
-    } else if (intersects && !clippingRect.contains(sHelperRect)) {
-      // View is partially clipped.
+    } else if (intersects) {
+      // If there is any intersection we need to inform the child to update its clipping rect
       needUpdateClippingRecursive = true;
     }
     if (needUpdateClippingRecursive) {
@@ -344,7 +359,17 @@ private void updateSubviewClipStatus(View subview) {
   @Override
   protected void onSizeChanged(int w, int h, int oldw, int oldh) {
     super.onSizeChanged(w, h, oldw, oldh);
-    updateClippingRect();
+    if (mRemoveClippedSubviews) {
+      updateClippingRect();
+    }
+  }
+
+  @Override
+  protected void onAttachedToWindow() {
+    super.onAttachedToWindow();
+    if (mRemoveClippedSubviews) {
+      updateClippingRect();
+    }
   }
 
   @Override
@@ -490,4 +515,13 @@ private ReactViewBackgroundDrawable getOrCreateReactViewBackground() {
     return mReactBackgroundDrawable;
   }
 
+  @Override
+  public @Nullable Rect getHitSlopRect() {
+    return mHitSlopRect;
+  }
+
+  public void setHitSlopRect(@Nullable Rect rect) {
+    mHitSlopRect = rect;
+  }
+
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
index 12c691a616c0..6a4bd5d26491 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
@@ -14,7 +14,7 @@
 import java.util.Locale;
 import java.util.Map;
 
-import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Build;
 import android.view.View;
 
@@ -25,11 +25,10 @@
 import com.facebook.react.bridge.ReadableMap;
 import com.facebook.react.common.MapBuilder;
 import com.facebook.react.common.annotations.VisibleForTesting;
-import com.facebook.react.uimanager.CatalystStylesDiffMap;
 import com.facebook.react.uimanager.PixelUtil;
 import com.facebook.react.uimanager.PointerEvents;
-import com.facebook.react.uimanager.ReactProp;
-import com.facebook.react.uimanager.ReactPropGroup;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
 import com.facebook.react.uimanager.ThemedReactContext;
 import com.facebook.react.uimanager.ViewGroupManager;
 import com.facebook.react.uimanager.ViewProps;
@@ -47,16 +46,29 @@ public class ReactViewManager extends ViewGroupManager<ReactViewGroup> {
   };
   private static final int CMD_HOTSPOT_UPDATE = 1;
   private static final int CMD_SET_PRESSED = 2;
-  private static final int[] sLocationBuf = new int[2];
 
   @ReactProp(name = "accessible")
   public void setAccessible(ReactViewGroup view, boolean accessible) {
     view.setFocusable(accessible);
   }
 
-  @ReactProp(name = "borderRadius")
-  public void setBorderRadius(ReactViewGroup view, float borderRadius) {
-    view.setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
+  @ReactPropGroup(names = {
+      ViewProps.BORDER_RADIUS,
+      ViewProps.BORDER_TOP_LEFT_RADIUS,
+      ViewProps.BORDER_TOP_RIGHT_RADIUS,
+      ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
+      ViewProps.BORDER_BOTTOM_LEFT_RADIUS
+  }, defaultFloat = CSSConstants.UNDEFINED)
+  public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) {
+    if (!CSSConstants.isUndefined(borderRadius)) {
+      borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
+    }
+
+    if (index == 0) {
+      view.setBorderRadius(borderRadius);
+    } else {
+      view.setBorderRadius(borderRadius, index - 1);
+    }
   }
 
   @ReactProp(name = "borderStyle")
@@ -64,6 +76,20 @@ public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) {
     view.setBorderStyle(borderStyle);
   }
 
+  @ReactProp(name = "hitSlop")
+  public void setHitSlop(final ReactViewGroup view, @Nullable ReadableMap hitSlop) {
+    if (hitSlop == null) {
+      view.setHitSlopRect(null);
+    } else {
+      view.setHitSlopRect(new Rect(
+          (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")),
+          (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")),
+          (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")),
+          (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom"))
+      ));
+    }
+  }
+
   @ReactProp(name = "pointerEvents")
   public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEventsStr) {
     if (pointerEventsStr != null) {
@@ -79,14 +105,6 @@ public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
             null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
   }
 
-  @ReactProp(name = ViewProps.BORDER_WIDTH, defaultFloat = CSSConstants.UNDEFINED)
-  public void setBorderWidth(ReactViewGroup view, float width) {
-    if (!CSSConstants.isUndefined(width)) {
-      width = PixelUtil.toPixelFromDIP(width);
-    }
-    view.setBorderWidth(Spacing.ALL, width);
-  }
-
   @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
   public void setRemoveClippedSubviews(ReactViewGroup view, boolean removeClippedSubviews) {
     view.setRemoveClippedSubviews(removeClippedSubviews);
@@ -122,6 +140,12 @@ public void setBorderColor(ReactViewGroup view, int index, Integer color) {
         color == null ? CSSConstants.UNDEFINED : (float) color);
   }
 
+  @ReactProp(name = ViewProps.COLLAPSABLE)
+  public void setCollapsable(ReactViewGroup view, boolean collapsable) {
+    // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
+    // handled in NativeViewHierarchyOptimizer
+  }
+
   @Override
   public String getName() {
     return REACT_CLASS;
@@ -146,9 +170,8 @@ public void receiveCommand(ReactViewGroup root, int commandId, @Nullable Readabl
               "Illegal number of arguments for 'updateHotspot' command");
         }
         if (Build.VERSION.SDK_INT >= 21) {
-          root.getLocationOnScreen(sLocationBuf);
-          float x = PixelUtil.toPixelFromDIP(args.getDouble(0)) - sLocationBuf[0];
-          float y = PixelUtil.toPixelFromDIP(args.getDouble(1)) - sLocationBuf[1];
+          float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
+          float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
           root.drawableHotspotChanged(x, y);
         }
         break;
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK
index 8b0b881fa0a4..23bcd06f3f2f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/BUCK
@@ -7,9 +7,11 @@ android_library(
     react_native_target('java/com/facebook/react/bridge:bridge'),
     react_native_target('java/com/facebook/react/common:common'),
     react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
     react_native_target('java/com/facebook/react/views/scroll:scroll'),
-    '//third-party/java/android/support/v4:lib-support-v4',
-    '//third-party/java/jsr-305:jsr-305',
+    react_native_dep('third-party/android/support/v4:lib-support-v4'),
+    react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
     'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java
new file mode 100644
index 000000000000..668c0337ef69
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/PageScrollStateChangedEvent.java
@@ -0,0 +1,49 @@
+/**
+ * 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.views.viewpager;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted by {@link ReactViewPager} when user scrolling state changed.
+ *
+ * Additional data provided by this event:
+ *  - pageScrollState - {Idle,Dragging,Settling}
+ */
+class PageScrollStateChangedEvent extends Event<PageScrollStateChangedEvent> {
+
+  public static final String EVENT_NAME = "topPageScrollStateChanged";
+
+  private final String mPageScrollState;
+
+  protected PageScrollStateChangedEvent(int viewTag, long timestampMs, String pageScrollState) {
+    super(viewTag, timestampMs);
+    mPageScrollState = pageScrollState;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
+  }
+
+  private WritableMap serializeEventData() {
+    WritableMap eventData = Arguments.createMap();
+    eventData.putString("pageScrollState", mPageScrollState);
+    return eventData;
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java
index e2a4b0fcd42c..c522e0391560 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPager.java
@@ -12,7 +12,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import android.os.SystemClock;
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.view.MotionEvent;
@@ -20,6 +19,7 @@
 import android.view.ViewGroup;
 
 import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.UIManagerModule;
 import com.facebook.react.uimanager.events.EventDispatcher;
 import com.facebook.react.uimanager.events.NativeGestureUtil;
@@ -91,20 +91,35 @@ private class PageChangeListener implements OnPageChangeListener {
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
       mEventDispatcher.dispatchEvent(
-          new PageScrollEvent(getId(), SystemClock.uptimeMillis(), position, positionOffset));
+          new PageScrollEvent(getId(), SystemClock.nanoTime(), position, positionOffset));
     }
 
     @Override
     public void onPageSelected(int position) {
       if (!mIsCurrentItemFromJs) {
         mEventDispatcher.dispatchEvent(
-            new PageSelectedEvent(getId(), SystemClock.uptimeMillis(), position));
+            new PageSelectedEvent(getId(), SystemClock.nanoTime(), position));
       }
     }
 
     @Override
     public void onPageScrollStateChanged(int state) {
-      // don't send events
+      String pageScrollState;
+      switch (state) {
+        case SCROLL_STATE_IDLE:
+          pageScrollState = "idle";
+          break;
+        case SCROLL_STATE_DRAGGING:
+          pageScrollState = "dragging";
+          break;
+        case SCROLL_STATE_SETTLING:
+          pageScrollState = "settling";
+          break;
+        default:
+          throw new IllegalStateException("Unsupported pageScrollState");
+      }
+      mEventDispatcher.dispatchEvent(
+        new PageScrollStateChangedEvent(getId(), SystemClock.nanoTime(), pageScrollState));
     }
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java
index 10b131d9924e..0ae266e38020 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/viewpager/ReactViewPagerManager.java
@@ -50,6 +50,7 @@ public boolean needsCustomLayoutForChildren() {
   public Map getExportedCustomDirectEventTypeConstants() {
     return MapBuilder.of(
         PageScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScroll"),
+        PageScrollStateChangedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageScrollStateChanged"),
         PageSelectedEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPageSelected")
     );
   }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK
new file mode 100644
index 000000000000..8da222d8dadd
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/BUCK
@@ -0,0 +1,20 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'webview',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':webview',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
new file mode 100644
index 000000000000..616418bba35b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
@@ -0,0 +1,369 @@
+/**
+ * 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.views.webview;
+
+import javax.annotation.Nullable;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.text.TextUtils;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import com.facebook.catalyst.views.webview.events.TopLoadingErrorEvent;
+import com.facebook.catalyst.views.webview.events.TopLoadingFinishEvent;
+import com.facebook.catalyst.views.webview.events.TopLoadingStartEvent;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.LifecycleEventListener;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SystemClock;
+import com.facebook.react.common.build.ReactBuildConfig;
+import com.facebook.react.uimanager.SimpleViewManager;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.EventDispatcher;
+
+/**
+ * Manages instances of {@link WebView}
+ *
+ * Can accept following commands:
+ *  - GO_BACK
+ *  - GO_FORWARD
+ *  - RELOAD
+ *
+ * {@link WebView} instances could emit following direct events:
+ *  - topLoadingFinish
+ *  - topLoadingStart
+ *  - topLoadingError
+ *
+ * Each event will carry the following properties:
+ *  - target - view's react tag
+ *  - url - url set for the webview
+ *  - loading - whether webview is in a loading state
+ *  - title - title of the current page
+ *  - canGoBack - boolean, whether there is anything on a history stack to go back
+ *  - canGoForward - boolean, whether it is possible to request GO_FORWARD command
+ */
+public class ReactWebViewManager extends SimpleViewManager<WebView> {
+
+  private static final String REACT_CLASS = "RCTWebView";
+
+  private static final String HTML_ENCODING = "UTF-8";
+  private static final String HTML_MIME_TYPE = "text/html; charset=utf-8";
+
+  private static final String HTTP_METHOD_POST = "POST";
+
+  public static final int COMMAND_GO_BACK = 1;
+  public static final int COMMAND_GO_FORWARD = 2;
+  public static final int COMMAND_RELOAD = 3;
+
+  // Use `webView.loadUrl("about:blank")` to reliably reset the view
+  // state and release page resources (including any running JavaScript).
+  private static final String BLANK_URL = "about:blank";
+
+  private WebViewConfig mWebViewConfig;
+
+  private static class ReactWebViewClient extends WebViewClient {
+
+    private boolean mLastLoadFailed = false;
+
+    @Override
+    public void onPageFinished(WebView webView, String url) {
+      super.onPageFinished(webView, url);
+
+      if (!mLastLoadFailed) {
+        ReactWebView reactWebView = (ReactWebView) webView;
+        reactWebView.callInjectedJavaScript();
+        emitFinishEvent(webView, url);
+      }
+    }
+
+    @Override
+    public void onPageStarted(WebView webView, String url, Bitmap favicon) {
+      super.onPageStarted(webView, url, favicon);
+      mLastLoadFailed = false;
+
+      dispatchEvent(
+          webView,
+          new TopLoadingStartEvent(
+              webView.getId(),
+              SystemClock.nanoTime(),
+              createWebViewEvent(webView, url)));
+    }
+
+    @Override
+    public void onReceivedError(
+        WebView webView,
+        int errorCode,
+        String description,
+        String failingUrl) {
+      super.onReceivedError(webView, errorCode, description, failingUrl);
+      mLastLoadFailed = true;
+
+      // In case of an error JS side expect to get a finish event first, and then get an error event
+      // Android WebView does it in the opposite way, so we need to simulate that behavior
+      emitFinishEvent(webView, failingUrl);
+
+      WritableMap eventData = createWebViewEvent(webView, failingUrl);
+      eventData.putDouble("code", errorCode);
+      eventData.putString("description", description);
+
+      dispatchEvent(
+          webView,
+          new TopLoadingErrorEvent(webView.getId(), SystemClock.nanoTime(), eventData));
+    }
+
+    @Override
+    public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload) {
+      super.doUpdateVisitedHistory(webView, url, isReload);
+
+      dispatchEvent(
+          webView,
+          new TopLoadingStartEvent(
+              webView.getId(),
+              SystemClock.nanoTime(),
+              createWebViewEvent(webView, url)));
+    }
+
+    private void emitFinishEvent(WebView webView, String url) {
+      dispatchEvent(
+          webView,
+          new TopLoadingFinishEvent(
+              webView.getId(),
+              SystemClock.nanoTime(),
+              createWebViewEvent(webView, url)));
+    }
+
+    private static void dispatchEvent(WebView webView, Event event) {
+      ReactContext reactContext = (ReactContext) webView.getContext();
+      EventDispatcher eventDispatcher =
+          reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
+      eventDispatcher.dispatchEvent(event);
+    }
+
+    private WritableMap createWebViewEvent(WebView webView, String url) {
+      WritableMap event = Arguments.createMap();
+      event.putDouble("target", webView.getId());
+      // Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
+      // like onPageFinished
+      event.putString("url", url);
+      event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
+      event.putString("title", webView.getTitle());
+      event.putBoolean("canGoBack", webView.canGoBack());
+      event.putBoolean("canGoForward", webView.canGoForward());
+      return event;
+    }
+  }
+
+  /**
+   * Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
+   * to call {@link WebView#destroy} on activty destroy event and also to clear the client
+   */
+  private static class ReactWebView extends WebView implements LifecycleEventListener {
+    private @Nullable String injectedJS;
+
+    /**
+     * WebView must be created with an context of the current activity
+     *
+     * Activity Context is required for creation of dialogs internally by WebView
+     * Reactive Native needed for access to ReactNative internal system functionality
+     *
+     */
+    public ReactWebView(ThemedReactContext reactContext) {
+      super(reactContext);
+    }
+
+    @Override
+    public void onHostResume() {
+      // do nothing
+    }
+
+    @Override
+    public void onHostPause() {
+      // do nothing
+    }
+
+    @Override
+    public void onHostDestroy() {
+      cleanupCallbacksAndDestroy();
+    }
+
+    public void setInjectedJavaScript(@Nullable String js) {
+      injectedJS = js;
+    }
+
+    public void callInjectedJavaScript() {
+      if (getSettings().getJavaScriptEnabled() &&
+          injectedJS != null &&
+          !TextUtils.isEmpty(injectedJS)) {
+        loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();");
+      }
+    }
+
+    private void cleanupCallbacksAndDestroy() {
+      setWebViewClient(null);
+      destroy();
+    }
+  }
+
+  public ReactWebViewManager() {
+    mWebViewConfig = new WebViewConfig() {
+      public void configWebView(WebView webView) {
+      }
+    };
+  }
+
+  public ReactWebViewManager(WebViewConfig webViewConfig) {
+    mWebViewConfig = webViewConfig;
+  }
+
+  @Override
+  public String getName() {
+    return REACT_CLASS;
+  }
+
+  @Override
+  protected WebView createViewInstance(ThemedReactContext reactContext) {
+    ReactWebView webView = new ReactWebView(reactContext);
+    reactContext.addLifecycleEventListener(webView);
+    mWebViewConfig.configWebView(webView);
+
+    if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+      WebView.setWebContentsDebuggingEnabled(true);
+    }
+
+    return webView;
+  }
+
+  @ReactProp(name = "javaScriptEnabled")
+  public void setJavaScriptEnabled(WebView view, boolean enabled) {
+    view.getSettings().setJavaScriptEnabled(enabled);
+  }
+
+  @ReactProp(name = "scalesPageToFit")
+  public void setScalesPageToFit(WebView view, boolean enabled) {
+    view.getSettings().setUseWideViewPort(!enabled);
+  }
+
+  @ReactProp(name = "domStorageEnabled")
+  public void setDomStorageEnabled(WebView view, boolean enabled) {
+    view.getSettings().setDomStorageEnabled(enabled);
+  }
+
+  @ReactProp(name = "userAgent")
+  public void setUserAgent(WebView view, @Nullable String userAgent) {
+    if (userAgent != null) {
+      // TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
+      view.getSettings().setUserAgentString(userAgent);
+    }
+  }
+
+  @ReactProp(name = "injectedJavaScript")
+  public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
+    ((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
+  }
+
+  @ReactProp(name = "source")
+  public void setSource(WebView view, @Nullable ReadableMap source) {
+    if (source != null) {
+      if (source.hasKey("html")) {
+        String html = source.getString("html");
+        if (source.hasKey("baseUrl")) {
+          view.loadDataWithBaseURL(
+              source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
+        } else {
+          view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING);
+        }
+        return;
+      }
+      if (source.hasKey("uri")) {
+        String url = source.getString("uri");
+        if (source.hasKey("method")) {
+          String method = source.getString("method");
+          if (method.equals(HTTP_METHOD_POST)) {
+            byte[] postData = null;
+            if (source.hasKey("body")) {
+              String body = source.getString("body");
+              try {
+                postData = body.getBytes("UTF-8");
+              } catch (UnsupportedEncodingException e) {
+                postData = body.getBytes();
+              }
+            }
+            if (postData == null) {
+              postData = new byte[0];
+            }
+            view.postUrl(url, postData);
+            return;
+          }
+        }
+        HashMap<String, String> headerMap = new HashMap<>();
+        if (source.hasKey("headers")) {
+          ReadableMap headers = source.getMap("headers");
+          ReadableMapKeySetIterator iter = headers.keySetIterator();
+          while (iter.hasNextKey()) {
+            String key = iter.nextKey();
+            headerMap.put(key, headers.getString(key));
+          }
+        }
+        view.loadUrl(url, headerMap);
+        return;
+      }
+    }
+    view.loadUrl(BLANK_URL);
+  }
+
+  @Override
+  protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
+    // Do not register default touch emitter and let WebView implementation handle touches
+    view.setWebViewClient(new ReactWebViewClient());
+  }
+
+  @Override
+  public @Nullable Map<String, Integer> getCommandsMap() {
+    return MapBuilder.of(
+        "goBack", COMMAND_GO_BACK,
+        "goForward", COMMAND_GO_FORWARD,
+        "reload", COMMAND_RELOAD);
+  }
+
+  @Override
+  public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
+    switch (commandId) {
+      case COMMAND_GO_BACK:
+        root.goBack();
+        break;
+      case COMMAND_GO_FORWARD:
+        root.goForward();
+        break;
+      case COMMAND_RELOAD:
+        root.reload();
+        break;
+    }
+  }
+
+  @Override
+  public void onDropViewInstance(WebView webView) {
+    super.onDropViewInstance(webView);
+    ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
+    ((ReactWebView) webView).cleanupCallbacksAndDestroy();
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java
new file mode 100644
index 000000000000..7b2f1241efad
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/WebViewConfig.java
@@ -0,0 +1,21 @@
+/**
+ * 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.views.webview;
+
+import android.webkit.WebView;
+
+/**
+ * Implement this interface in order to config your {@link WebView}. An instance of that
+ * implementation will have to be given as a constructor argument to {@link ReactWebViewManager}.
+ */
+public interface WebViewConfig {
+
+  void configWebView(WebView webView);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java
new file mode 100644
index 000000000000..f97b30e9007a
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java
@@ -0,0 +1,49 @@
+/**
+ * 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.catalyst.views.webview.events;
+
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted when there is an error in loading.
+ */
+public class TopLoadingErrorEvent extends Event<TopLoadingErrorEvent> {
+
+  public static final String EVENT_NAME = "topLoadingError";
+  private WritableMap mEventData;
+
+  public TopLoadingErrorEvent(int viewId, long timestampMs, WritableMap eventData) {
+    super(viewId, timestampMs);
+    mEventData = eventData;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public boolean canCoalesce() {
+    return false;
+  }
+
+  @Override
+  public short getCoalescingKey() {
+    // All events for a given view can be coalesced.
+    return 0;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java
new file mode 100644
index 000000000000..b03447043cbe
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingFinishEvent.java
@@ -0,0 +1,49 @@
+/**
+ * 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.catalyst.views.webview.events;
+
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted when loading is completed.
+ */
+public class TopLoadingFinishEvent extends Event<TopLoadingFinishEvent> {
+
+  public static final String EVENT_NAME = "topLoadingFinish";
+  private WritableMap mEventData;
+
+  public TopLoadingFinishEvent(int viewId, long timestampMs, WritableMap eventData) {
+    super(viewId, timestampMs);
+    mEventData = eventData;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public boolean canCoalesce() {
+    return false;
+  }
+
+  @Override
+  public short getCoalescingKey() {
+    // All events for a given view can be coalesced.
+    return 0;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java
new file mode 100644
index 000000000000..5154ab807747
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingStartEvent.java
@@ -0,0 +1,49 @@
+/**
+ * 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.catalyst.views.webview.events;
+
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted when loading has started
+ */
+public class TopLoadingStartEvent extends Event<TopLoadingStartEvent> {
+
+  public static final String EVENT_NAME = "topLoadingStart";
+  private WritableMap mEventData;
+
+  public TopLoadingStartEvent(int viewId, long timestampMs, WritableMap eventData) {
+    super(viewId, timestampMs);
+    mEventData = eventData;
+  }
+
+  @Override
+  public String getEventName() {
+    return EVENT_NAME;
+  }
+
+  @Override
+  public boolean canCoalesce() {
+    return false;
+  }
+
+  @Override
+  public short getCoalescingKey() {
+    // All events for a given view can be coalesced.
+    return 0;
+  }
+
+  @Override
+  public void dispatch(RCTEventEmitter rctEventEmitter) {
+    rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
+  }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java
index 20867d7e248d..a3d7699ce7d4 100644
--- a/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java
+++ b/ReactAndroid/src/main/java/com/facebook/systrace/Systrace.java
@@ -20,6 +20,8 @@ public class Systrace {
 
   public static final long TRACE_TAG_REACT_JAVA_BRIDGE = 0L;
   public static final long TRACE_TAG_REACT_FRESCO = 0L;
+  public static final long TRACE_TAG_REACT_APPS = 0L;
+  public static final long TRACE_TAG_REACT_VIEW = 0L;
 
   public enum EventScope {
     THREAD('t'),
@@ -82,4 +84,22 @@ public static void traceCounter(
       final String counterName,
       final int counterValue) {
   }
+
+  public static void startAsyncFlow(
+      long tag,
+      final String sectionName,
+      final int cookie){
+  }
+
+  public static void stepAsyncFlow(
+      long tag,
+      final String sectionName,
+      final int cookie){
+  }
+
+  public static void endAsyncFlow(
+      long tag,
+      final String sectionName,
+      final int cookie){
+  }
 }
diff --git a/ReactAndroid/src/main/jni/first-party/fb/BUCK b/ReactAndroid/src/main/jni/first-party/fb/BUCK
new file mode 100644
index 000000000000..2890f37a14b6
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/fb/BUCK
@@ -0,0 +1,28 @@
+include_defs('//ReactAndroid/DEFS')
+
+cxx_library(
+  name = 'fb',
+  soname = 'libfb.so',
+  srcs = [
+    'assert.cpp',
+    'log.cpp',
+  ],
+  header_namespace='fb',
+  exported_headers=subdir_glob([
+    ('', '*.h'),
+    ('include/fb', '*.h'),
+  ]),
+  preprocessor_flags=[
+    '-DLOG_TAG="libfb"',
+  ],
+  # We want to use this library during bootstrap
+  can_be_asset = False,
+  deps = [
+    react_native_target('jni/third-party/android-ndk:android'),
+    react_native_target('jni/third-party/android-ndk:log'),
+    react_native_target('jni/third-party/glibc:dl'),
+  ],
+  visibility = [
+    'PUBLIC'
+  ],
+)
\ No newline at end of file
diff --git a/ReactAndroid/src/main/jni/first-party/jni/Android.mk b/ReactAndroid/src/main/jni/first-party/jni/Android.mk
index e77eaf1a0aad..1ee4d08986ca 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/Android.mk
+++ b/ReactAndroid/src/main/jni/first-party/jni/Android.mk
@@ -9,6 +9,7 @@ LOCAL_SRC_FILES:= \
        LocalString.cpp \
        OnLoad.cpp \
        WeakReference.cpp \
+       fbjni/ByteBuffer.cpp \
        fbjni/Exceptions.cpp \
        fbjni/Hybrid.cpp \
        fbjni/References.cpp
diff --git a/ReactAndroid/src/main/jni/first-party/jni/BUCK b/ReactAndroid/src/main/jni/first-party/jni/BUCK
new file mode 100644
index 000000000000..36574b0b39cb
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/BUCK
@@ -0,0 +1,51 @@
+include_defs('//ReactAndroid/DEFS')
+
+cxx_library(
+  name = 'jni',
+  srcs = [
+    'Countable.cpp',
+    'Environment.cpp',
+    'fbjni.cpp',
+    'jni_helpers.cpp',
+    'LocalString.cpp',
+    'OnLoad.cpp',
+    'WeakReference.cpp',
+    'fbjni/ByteBuffer.cpp',
+    'fbjni/Exceptions.cpp',
+    'fbjni/Hybrid.cpp',
+    'fbjni/References.cpp',
+  ],
+  platform_srcs = [
+    (r'.*\bandroid\b.*', [
+      'fbjni/android/ReferenceChecking.cpp',
+      ]),
+  ],
+  exported_headers = glob(['*.h', 'fbjni/*.h']),
+  header_namespace = 'jni',
+  soname = 'libfbjni.so',
+  preprocessor_flags = [
+    '-fexceptions',
+    '-DLOG_TAG="fbjni"',
+  ],
+  compiler_flags = [
+    '-fexceptions', 
+    '-frtti',
+  ],
+  # We want to use this library during bootstrap
+  can_be_asset = False,
+  exported_deps = [
+    react_native_target('jni/first-party/fb:fb'),
+    react_native_target('jni/third-party/android-ndk:log'),
+  ],
+  deps = [
+    # TODO Is this needed? Not used in the Gradle build.
+    # '//native/jni-hack:jni-hack',
+    # TODO should be react_native_target('java/com/facebook/jni:jni') but might not be needed as the bridge already depends on it.
+    # '//native/jni/java:java',
+  ],
+  visibility = ['PUBLIC'],
+)
+
+project_config(
+  src_target = ':jni',
+)
diff --git a/ReactAndroid/src/main/jni/first-party/jni/Countable.cpp b/ReactAndroid/src/main/jni/first-party/jni/Countable.cpp
index 6ff7efe9068b..3ed52f782484 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/Countable.cpp
+++ b/ReactAndroid/src/main/jni/first-party/jni/Countable.cpp
@@ -41,7 +41,7 @@ void setCountableForJava(JNIEnv* env, jobject obj, RefPtr<Countable>&& countable
  *
  * This method deletes the corresponding native object on whatever thread the method is called
  * on. In the common case when this is called by Countable#finalize(), this will be called on the
- * system finalizer thread. If you manually call dispose on the Java object, the native object
+ * system finalizer thread. If you manually call dispose on the Java object, the native object 
  * will be deleted synchronously on that thread.
  */
 void dispose(JNIEnv* env, jobject obj) {
diff --git a/ReactAndroid/src/main/jni/first-party/jni/LocalReference.h b/ReactAndroid/src/main/jni/first-party/jni/LocalReference.h
index b5d9c54a13b0..09aa641fd290 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/LocalReference.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/LocalReference.h
@@ -27,7 +27,7 @@ struct LocalReferenceDeleter {
     if (localReference != nullptr) {
       Environment::current()->DeleteLocalRef(localReference);
     }
-  }
+  } 
  };
 
 template<class T>
diff --git a/ReactAndroid/src/main/jni/first-party/jni/LocalString.cpp b/ReactAndroid/src/main/jni/first-party/jni/LocalString.cpp
index 5fc8d0c84828..a268072677d0 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/LocalString.cpp
+++ b/ReactAndroid/src/main/jni/first-party/jni/LocalString.cpp
@@ -18,6 +18,12 @@ namespace jni {
 
 namespace {
 
+const uint16_t kUtf8OneByteBoundary       = 0x80;
+const uint16_t kUtf8TwoBytesBoundary      = 0x800;
+const uint16_t kUtf16HighSubLowBoundary   = 0xD800;
+const uint16_t kUtf16HighSubHighBoundary  = 0xDC00;
+const uint16_t kUtf16LowSubHighBoundary   = 0xE000;
+
 inline void encode3ByteUTF8(char32_t code, uint8_t* out) {
   FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits");
 
@@ -194,6 +200,72 @@ std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) {
   return utf8;
 }
 
+// Calculate how many bytes are needed to convert an UTF16 string into UTF8
+// UTF16 string
+size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) {
+  if (!utf16String || utf16StringLen == 0) {
+    return 0;
+  }
+
+  uint32_t utf8StringLen = 0;
+  auto utf16StringEnd = utf16String + utf16StringLen;
+  auto idx16 = utf16String;
+  while (idx16 < utf16StringEnd) {
+    auto ch = *idx16++;
+    if (ch < kUtf8OneByteBoundary) {
+      utf8StringLen++;
+    } else if (ch < kUtf8TwoBytesBoundary) {
+      utf8StringLen += 2;
+    } else if (
+        (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+        (idx16 < utf16StringEnd) &&
+        (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+      utf8StringLen += 4;
+      idx16++;
+    } else {
+      utf8StringLen += 3;
+    }
+  }
+
+  return utf8StringLen;
+}
+
+std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) {
+  if (!utf16String || utf16StringLen <= 0) {
+    return "";
+  }
+
+  std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0');
+  auto idx8 = utf8String.begin();
+  auto idx16 = utf16String;
+  auto utf16StringEnd = utf16String + utf16StringLen;
+  while (idx16 < utf16StringEnd) {
+    auto ch = *idx16++;
+    if (ch < kUtf8OneByteBoundary) {
+      *idx8++ = (ch & 0x7F);
+    } else if (ch < kUtf8TwoBytesBoundary) {
+      *idx8++ = 0b11000000 | (ch >> 6);
+      *idx8++ = 0b10000000 | (ch & 0x3F);
+    } else if (
+        (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) &&
+        (idx16 < utf16StringEnd) &&
+        (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) {
+      auto ch2 = *idx16++;
+      uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1);
+      *idx8++ = 0b11110000 | (trunc_byte >> 2);
+      *idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F);
+      *idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F);
+      *idx8++ = 0b10000000 | (ch2 & 0x3F);
+    } else {
+      *idx8++ = 0b11100000 | (ch >> 12);
+      *idx8++ = 0b10000000 | ((ch >> 6) & 0x3F);
+      *idx8++ = 0b10000000 | (ch & 0x3F);
+    }
+  }
+
+  return utf8String;
+}
+
 }
 
 LocalString::LocalString(const std::string& str)
@@ -232,11 +304,9 @@ LocalString::~LocalString() {
 }
 
 std::string fromJString(JNIEnv* env, jstring str) {
-  const char* modified = env->GetStringUTFChars(str, NULL);
-  jsize length = env->GetStringUTFLength(str);
-  std::string s = detail::modifiedUTF8ToUTF8(reinterpret_cast<const uint8_t*>(modified), length);
-  env->ReleaseStringUTFChars(str, modified);
-  return s;
+  auto utf16String = JStringUtf16Extractor(env, str);
+  auto length = env->GetStringLength(str);
+  return detail::utf16toUTF8(utf16String, length);
 }
 
 } }
diff --git a/ReactAndroid/src/main/jni/first-party/jni/LocalString.h b/ReactAndroid/src/main/jni/first-party/jni/LocalString.h
index a85efa48adaf..2290af10dc97 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/LocalString.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/LocalString.h
@@ -20,6 +20,7 @@ void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, siz
 size_t modifiedLength(const std::string& str);
 size_t modifiedLength(const uint8_t* str, size_t* length);
 std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len);
+std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len);
 
 }
 
@@ -54,6 +55,34 @@ class LocalString {
   jstring m_string;
 };
 
+// JString to UTF16 extractor using RAII idiom
+class JStringUtf16Extractor {
+public:
+  JStringUtf16Extractor(JNIEnv* env, jstring javaString)
+  : env_(env)
+  , javaString_(javaString)
+  , utf16String_(nullptr) {
+    if (env_ && javaString_) {
+      utf16String_ = env_->GetStringCritical(javaString_, nullptr);
+    }
+  }
+
+  ~JStringUtf16Extractor() {
+    if (utf16String_) {
+      env_->ReleaseStringCritical(javaString_, utf16String_);
+    }
+  }
+
+  operator const jchar* () const {
+    return utf16String_;
+  }
+
+private:
+  JNIEnv* env_;
+  jstring javaString_;
+  const jchar* utf16String_;
+};
+
 // The string from JNI is converted to standard UTF-8 if the string contains supplementary
 // characters.
 std::string fromJString(JNIEnv* env, jstring str);
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni.cpp b/ReactAndroid/src/main/jni/first-party/jni/fbjni.cpp
index a083371b2695..22d4dc2a4d19 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni.cpp
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni.cpp
@@ -16,36 +16,33 @@
 namespace facebook {
 namespace jni {
 
-template<typename... Args>
-static void log(Args... args) {
-// TODO (7623232) Migrate to glog
-#ifdef __ANDROID__
-  facebook::alog::loge("fbjni", args...);
-#endif
-}
-
-jint initialize(JavaVM* vm, void(*init_fn)()) noexcept {
-  static std::once_flag init_flag;
-  static auto failed = false;
+jint initialize(JavaVM* vm, std::function<void()>&& init_fn) noexcept {
+  static std::once_flag flag{};
+  // TODO (t7832883): DTRT when we have exception pointers
+  static auto error_msg = std::string{"Failed to initialize fbjni"};
+  static auto error_occured = false;
 
-  std::call_once(init_flag, [vm] {
+  std::call_once(flag, [vm] {
     try {
       Environment::initialize(vm);
       internal::initExceptionHelpers();
     } catch (std::exception& ex) {
-      log("Failed to initialize fbjni: %s", ex.what());
-      failed = true;
+      error_occured = true;
+      try {
+        error_msg = std::string{"Failed to initialize fbjni: "} + ex.what();
+      } catch (...) {
+        // Ignore, we already have a fall back message
+      }
     } catch (...) {
-      log("Failed to initialize fbjni");
-      failed = true;
+      error_occured = true;
     }
   });
 
-  if (failed) {
-    return JNI_ERR;
-  }
-
   try {
+    if (error_occured) {
+      throw std::runtime_error(error_msg);
+    }
+
     init_fn();
   } catch (...) {
     translatePendingCppExceptionToJavaException();
@@ -55,7 +52,7 @@ jint initialize(JavaVM* vm, void(*init_fn)()) noexcept {
   return JNI_VERSION_1_6;
 }
 
-alias_ref<jclass> findClassStatic(const char* name) {
+alias_ref<JClass> findClassStatic(const char* name) {
   const auto env = internal::getEnv();
   auto cls = env->FindClass(name);
   FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
@@ -64,7 +61,7 @@ alias_ref<jclass> findClassStatic(const char* name) {
   return wrap_alias(leaking_ref);
 }
 
-local_ref<jclass> findClassLocal(const char* name) {
+local_ref<JClass> findClassLocal(const char* name) {
   const auto env = internal::getEnv();
   auto cls = env->FindClass(name);
   FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls);
@@ -74,16 +71,14 @@ local_ref<jclass> findClassLocal(const char* name) {
 
 // jstring /////////////////////////////////////////////////////////////////////////////////////////
 
-std::string JObjectWrapper<jstring>::toStdString() const {
+std::string JString::toStdString() const {
   const auto env = internal::getEnv();
-  auto modified = env->GetStringUTFChars(self(), nullptr);
-  auto length = env->GetStringUTFLength(self());
-  auto string = detail::modifiedUTF8ToUTF8(reinterpret_cast<const uint8_t*>(modified), length);
-  env->ReleaseStringUTFChars(self(), modified);
-  return string;
+  auto utf16String = JStringUtf16Extractor(env, self());
+  auto length = env->GetStringLength(self());
+  return detail::utf16toUTF8(utf16String, length);
 }
 
-local_ref<jstring> make_jstring(const char* utf8) {
+local_ref<JString> make_jstring(const char* utf8) {
   if (!utf8) {
     return {};
   }
@@ -111,96 +106,76 @@ local_ref<jstring> make_jstring(const char* utf8) {
 }
 
 
-// PinnedPrimitiveArray ///////////////////////////////////////////////////////////////////////////
+// JniPrimitiveArrayFunctions //////////////////////////////////////////////////////////////////////
 
-// TODO(T7847300): Allow array to be specified as constant so that JNI_ABORT can be passed
-// on release, as opposed to 0, which results in unnecessary copying.
 #pragma push_macro("DEFINE_PRIMITIVE_METHODS")
 #undef DEFINE_PRIMITIVE_METHODS
-#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME)                                                \
-template<>                                                                                  \
-TYPE* PinnedPrimitiveArray<TYPE>::get() {                                                   \
-  FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr);                                 \
-  const auto env = internal::getEnv();                                                      \
-  elements_ = env->Get ## NAME ## ArrayElements(                                            \
-      static_cast<TYPE ## Array>(array_.get()), &isCopy_);                                  \
-  size_ = array_->size();                                                                   \
-  return elements_;                                                                         \
-}                                                                                           \
-template<>                                                                                  \
-void PinnedPrimitiveArray<TYPE>::release() {                                                \
-  FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr);                                 \
-  const auto env = internal::getEnv();                                                      \
-  env->Release ## NAME ## ArrayElements(                                                    \
-      static_cast<TYPE ## Array>(array_.get()), elements_, 0);                              \
-  elements_ = nullptr;                                                                      \
-  size_ = 0;                                                                                \
-}
-
-DEFINE_PRIMITIVE_METHODS(jboolean, Boolean)
-DEFINE_PRIMITIVE_METHODS(jbyte, Byte)
-DEFINE_PRIMITIVE_METHODS(jchar, Char)
-DEFINE_PRIMITIVE_METHODS(jshort, Short)
-DEFINE_PRIMITIVE_METHODS(jint, Int)
-DEFINE_PRIMITIVE_METHODS(jlong, Long)
-DEFINE_PRIMITIVE_METHODS(jfloat, Float)
-DEFINE_PRIMITIVE_METHODS(jdouble, Double)
+#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME)                        \
+                                                                               \
+template<>                                                                     \
+TYPE* JPrimitiveArray<TYPE ## Array>::getElements(jboolean* isCopy) {          \
+  auto env = internal::getEnv();                                               \
+  TYPE* res =  env->Get ## NAME ## ArrayElements(self(), isCopy);              \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+  return res;                                                                  \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+void JPrimitiveArray<TYPE ## Array>::releaseElements(                          \
+    TYPE* elements, jint mode) {                                               \
+  auto env = internal::getEnv();                                               \
+  env->Release ## NAME ## ArrayElements(self(), elements, mode);               \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+void JPrimitiveArray<TYPE ## Array>::getRegion(                                \
+    jsize start, jsize length, TYPE* buf) {                                    \
+  auto env = internal::getEnv();                                               \
+  env->Get ## NAME ## ArrayRegion(self(), start, length, buf);                 \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+void JPrimitiveArray<TYPE ## Array>::setRegion(                                \
+    jsize start, jsize length, const TYPE* elements) {                         \
+  auto env = internal::getEnv();                                               \
+  env->Set ## NAME ## ArrayRegion(self(), start, length, elements);            \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                      \
+}                                                                              \
+                                                                               \
+local_ref<TYPE ## Array> make_ ## SMALLNAME ## _array(jsize size) {            \
+  auto array = internal::getEnv()->New ## NAME ## Array(size);                 \
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(!array);                                     \
+  return adopt_local(array);                                                   \
+}                                                                              \
+                                                                               \
+template<>                                                                     \
+local_ref<TYPE ## Array> JArray ## NAME::newArray(size_t count) {              \
+  return make_ ## SMALLNAME ## _array(count);                                  \
+}                                                                              \
+                                                                               \
+
+DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean)
+DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte)
+DEFINE_PRIMITIVE_METHODS(jchar, Char, char)
+DEFINE_PRIMITIVE_METHODS(jshort, Short, short)
+DEFINE_PRIMITIVE_METHODS(jint, Int, int)
+DEFINE_PRIMITIVE_METHODS(jlong, Long, long)
+DEFINE_PRIMITIVE_METHODS(jfloat, Float, float)
+DEFINE_PRIMITIVE_METHODS(jdouble, Double, double)
 #pragma pop_macro("DEFINE_PRIMITIVE_METHODS")
 
-
-#define DEFINE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME)                                                \
-                                                                                                \
-local_ref<j ## TYPE ## Array> make_ ## TYPE ## _array(jsize size) {                             \
-  auto array = internal::getEnv()->New ## NAME ## Array(size);                                  \
-  FACEBOOK_JNI_THROW_EXCEPTION_IF(!array);                                                      \
-  return adopt_local(array);                                                                    \
-}                                                                                               \
-                                                                                                \
-local_ref<j ## TYPE ## Array> JArray ## NAME::newArray(size_t count) {                          \
-  return make_ ## TYPE ## _array(count);                                                        \
-}                                                                                               \
-                                                                                                \
-j ## TYPE* JArray ## NAME::getRegion(jsize start, jsize length, j ## TYPE* buf) {               \
-  internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf);                   \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                       \
-  return buf;                                                                                   \
-}                                                                                               \
-                                                                                                \
-std::unique_ptr<j ## TYPE[]> JArray ## NAME::getRegion(jsize start, jsize length) {             \
-  auto buf = std::unique_ptr<j ## TYPE[]>{new j ## TYPE[length]};                               \
-  internal::getEnv()->Get ## NAME ## ArrayRegion(self(), start, length, buf.get());             \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                       \
-  return buf;                                                                                   \
-}                                                                                               \
-                                                                                                \
-void JArray ## NAME::setRegion(jsize start, jsize length, const j ## TYPE* buf) {               \
-  internal::getEnv()->Set ## NAME ## ArrayRegion(self(), start, length, buf);                   \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                       \
-}                                                                                               \
-                                                                                                \
-PinnedPrimitiveArray<j ## TYPE> JArray ## NAME::pin() {                                         \
-  return PinnedPrimitiveArray<j ## TYPE>{self()};                                               \
-}                                                                                               \
-
-DEFINE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean)
-DEFINE_PRIMITIVE_ARRAY_UTILS(byte, Byte)
-DEFINE_PRIMITIVE_ARRAY_UTILS(char, Char)
-DEFINE_PRIMITIVE_ARRAY_UTILS(short, Short)
-DEFINE_PRIMITIVE_ARRAY_UTILS(int, Int)
-DEFINE_PRIMITIVE_ARRAY_UTILS(long, Long)
-DEFINE_PRIMITIVE_ARRAY_UTILS(float, Float)
-DEFINE_PRIMITIVE_ARRAY_UTILS(double, Double)
-
-
 // Internal debug /////////////////////////////////////////////////////////////////////////////////
 
 namespace internal {
+
 ReferenceStats g_reference_stats;
 
 void facebook::jni::internal::ReferenceStats::reset() noexcept {
   locals_deleted = globals_deleted = weaks_deleted = 0;
 }
+
 }
 
 }}
-
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni.h
index 7ea816b60e58..7728153cd629 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni.h
@@ -19,5 +19,6 @@
 #include "fbjni/References.h"
 #include "fbjni/Meta.h"
 #include "fbjni/CoreClasses.h"
+#include "fbjni/Iterator.h"
 #include "fbjni/Hybrid.h"
 #include "fbjni/Registration.h"
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.cpp b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.cpp
new file mode 100644
index 000000000000..a41f9106ed92
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016-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.
+ */
+
+#include "ByteBuffer.h"
+
+#include "References.h"
+#include <stdexcept>
+
+namespace facebook {
+namespace jni {
+
+namespace {
+local_ref<JByteBuffer> createEmpty() {
+  static auto cls = JByteBuffer::javaClassStatic();
+  static auto meth = cls->getStaticMethod<JByteBuffer::javaobject(int)>("allocateDirect");
+  return meth(cls, 0);
+}
+}
+
+local_ref<JByteBuffer> JByteBuffer::wrapBytes(uint8_t* data, size_t size) {
+  // env->NewDirectByteBuffer requires that size is positive. Android's
+  // dalvik returns an invalid result and Android's art aborts if size == 0.
+  // Workaround this by using a slow path through Java in that case.
+  if (!size) {
+    return createEmpty();
+  }
+  auto res = adopt_local(static_cast<javaobject>(Environment::current()->NewDirectByteBuffer(data, size)));
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (!res) {
+    throw std::runtime_error("Direct byte buffers are unsupported.");
+  }
+  return res;
+}
+
+uint8_t* JByteBuffer::getDirectBytes() {
+  if (!self()) {
+    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+  }
+  void* bytes = Environment::current()->GetDirectBufferAddress(self());
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (!bytes) {
+    throw std::runtime_error(
+        isDirect() ?
+          "Attempt to get direct bytes of non-direct byte buffer." :
+          "Error getting direct bytes of byte buffer.");
+  }
+  return static_cast<uint8_t*>(bytes);
+}
+
+size_t JByteBuffer::getDirectSize() {
+  if (!self()) {
+    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+  }
+  int size = Environment::current()->GetDirectBufferCapacity(self());
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+  if (size < 0) {
+    throw std::runtime_error(
+        isDirect() ?
+          "Attempt to get direct size of non-direct byte buffer." :
+          "Error getting direct size of byte buffer.");
+  }
+  return static_cast<size_t>(size);
+}
+
+bool JByteBuffer::isDirect() {
+  static auto meth = javaClassStatic()->getMethod<jboolean()>("isDirect");
+  return meth(self());
+}
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.h
new file mode 100644
index 000000000000..b10573752b03
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ByteBuffer.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016-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.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+#include "References-forward.h"
+
+namespace facebook {
+namespace jni {
+
+// JNI's NIO support has some awkward preconditions and error reporting. This
+// class provides much more user-friendly access.
+class JByteBuffer : public JavaClass<JByteBuffer> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/nio/ByteBuffer;";
+
+  static local_ref<JByteBuffer> wrapBytes(uint8_t* data, size_t size);
+
+  bool isDirect();
+
+  uint8_t* getDirectBytes();
+  size_t getDirectSize();
+};
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Common.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Common.h
index 479f43713a3b..9fa80c7a4df0 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Common.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Common.h
@@ -14,10 +14,19 @@
 
 #pragma once
 
+#include <functional>
+
 #include <jni.h>
 
 #include <jni/Environment.h>
-#include <jni/ALog.h>
+
+#ifdef FBJNI_DEBUG_REFS
+# ifdef __ANDROID__
+#  include <android/log.h>
+# else
+#  include <cstdio>
+# endif
+#endif
 
 /// @cond INTERNAL
 
@@ -38,7 +47,7 @@ namespace jni {
  * unhelpful way (typically a segfault) while trying to handle an exception
  * which occurs later.
  */
-jint initialize(JavaVM*, void(*)()) noexcept;
+jint initialize(JavaVM*, std::function<void()>&&) noexcept;
 
 namespace internal {
 
@@ -53,14 +62,22 @@ inline JNIEnv* getEnv() noexcept {
 }
 
 // Define to get extremely verbose logging of references and to enable reference stats
-#if defined(__ANDROID__) && defined(FBJNI_DEBUG_REFS)
+#ifdef FBJNI_DEBUG_REFS
 template<typename... Args>
-inline void dbglog(Args... args) noexcept {
-  facebook::alog::logv("fbjni_ref", args...);
+inline void dbglog(const char* msg, Args... args) {
+# ifdef __ANDROID__
+  __android_log_print(ANDROID_LOG_VERBOSE, "fbjni_dbg", msg, args...);
+# else
+  std::fprintf(stderr, msg, args...);
+# endif
 }
+
 #else
+
 template<typename... Args>
-inline void dbglog(Args...) noexcept {}
+inline void dbglog(const char*, Args...) {
+}
+
 #endif
 
 }}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Context.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Context.h
new file mode 100644
index 000000000000..136ca82f1917
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Context.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2016-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.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+#include "File.h"
+
+namespace facebook {
+namespace jni {
+
+class AContext : public JavaClass<AContext> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Landroid/content/Context;";
+
+  // Define a method that calls into the represented Java class
+  local_ref<JFile::javaobject> getCacheDir() {
+    static auto method = getClass()->getMethod<JFile::javaobject()>("getCacheDir");
+    return method(self());
+  }
+
+};
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h
index 134acc87e27e..f5f861ba3c6d 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses-inl.h
@@ -11,75 +11,134 @@
 
 #include <string.h>
 #include <type_traits>
+#include <stdlib.h>
 
 #include "Common.h"
 #include "Exceptions.h"
+#include "Meta.h"
+#include "MetaConvert.h"
 
 namespace facebook {
 namespace jni {
 
-inline bool isSameObject(alias_ref<jobject> lhs, alias_ref<jobject> rhs) noexcept {
-  return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE;
-}
-
-
 // jobject /////////////////////////////////////////////////////////////////////////////////////////
 
-inline JObjectWrapper<jobject>::JObjectWrapper(jobject reference) noexcept
-  : this_{reference}
-{}
-
-inline JObjectWrapper<jobject>::JObjectWrapper(const JObjectWrapper<jobject>& other) noexcept
-  : this_{other.this_} {
-  internal::dbglog("wrapper copy from this=%p ref=%p other=%p", this, other.this_, &other);
+inline bool isSameObject(alias_ref<JObject> lhs, alias_ref<JObject> rhs) noexcept {
+  return internal::getEnv()->IsSameObject(lhs.get(), rhs.get()) != JNI_FALSE;
 }
 
-inline local_ref<jclass> JObjectWrapper<jobject>::getClass() const noexcept {
+inline local_ref<JClass> JObject::getClass() const noexcept {
   return adopt_local(internal::getEnv()->GetObjectClass(self()));
 }
 
-inline bool JObjectWrapper<jobject>::isInstanceOf(alias_ref<jclass> cls) const noexcept {
+inline bool JObject::isInstanceOf(alias_ref<JClass> cls) const noexcept {
   return internal::getEnv()->IsInstanceOf(self(), cls.get()) != JNI_FALSE;
 }
 
 template<typename T>
-inline T JObjectWrapper<jobject>::getFieldValue(JField<T> field) const noexcept {
+inline T JObject::getFieldValue(JField<T> field) const noexcept {
   return field.get(self());
 }
 
 template<typename T>
-inline local_ref<T*> JObjectWrapper<jobject>::getFieldValue(JField<T*> field) noexcept {
+inline local_ref<T*> JObject::getFieldValue(JField<T*> field) const noexcept {
   return adopt_local(field.get(self()));
 }
 
 template<typename T>
-inline void JObjectWrapper<jobject>::setFieldValue(JField<T> field, T value) noexcept {
+inline void JObject::setFieldValue(JField<T> field, T value) noexcept {
   field.set(self(), value);
 }
 
-inline std::string JObjectWrapper<jobject>::toString() const {
+inline std::string JObject::toString() const {
   static auto method = findClassLocal("java/lang/Object")->getMethod<jstring()>("toString");
 
   return method(self())->toStdString();
 }
 
-inline void JObjectWrapper<jobject>::set(jobject reference) noexcept {
-  this_ = reference;
+
+// Class is here instead of CoreClasses.h because we need
+// alias_ref to be complete.
+class MonitorLock {
+ public:
+  inline MonitorLock() noexcept;
+  inline MonitorLock(alias_ref<JObject> object) noexcept;
+  inline ~MonitorLock() noexcept;
+
+  inline MonitorLock(MonitorLock&& other) noexcept;
+  inline MonitorLock& operator=(MonitorLock&& other) noexcept;
+
+  inline MonitorLock(const MonitorLock&) = delete;
+  inline MonitorLock& operator=(const MonitorLock&) = delete;
+
+ private:
+  inline void reset() noexcept;
+  alias_ref<JObject> owned_;
+};
+
+MonitorLock::MonitorLock() noexcept : owned_(nullptr) {}
+
+MonitorLock::MonitorLock(alias_ref<JObject> object) noexcept
+    : owned_(object) {
+  internal::getEnv()->MonitorEnter(object.get());
 }
 
-inline jobject JObjectWrapper<jobject>::get() const noexcept {
-  return this_;
+void MonitorLock::reset() noexcept {
+  if (owned_) {
+    internal::getEnv()->MonitorExit(owned_.get());
+    if (internal::getEnv()->ExceptionCheck()) {
+      abort(); // Lock mismatch
+    }
+    owned_ = nullptr;
+  }
 }
 
-inline jobject JObjectWrapper<jobject>::self() const noexcept {
+MonitorLock::~MonitorLock() noexcept {
+  reset();
+}
+
+MonitorLock::MonitorLock(MonitorLock&& other) noexcept
+    : owned_(other.owned_)
+{
+  other.owned_ = nullptr;
+}
+
+MonitorLock& MonitorLock::operator=(MonitorLock&& other) noexcept {
+  reset();
+  owned_ = other.owned_;
+  other.owned_ = nullptr;
+  return *this;
+}
+
+inline MonitorLock JObject::lock() const noexcept {
+  return MonitorLock(this_);
+}
+
+inline jobject JObject::self() const noexcept {
   return this_;
 }
 
-inline void swap(JObjectWrapper<jobject>& a, JObjectWrapper<jobject>& b) noexcept {
+inline void swap(JObject& a, JObject& b) noexcept {
   using std::swap;
   swap(a.this_, b.this_);
 }
 
+// JavaClass ///////////////////////////////////////////////////////////////////////////////////////
+
+namespace detail {
+template<typename JC, typename... Args>
+static local_ref<JC> newInstance(Args... args) {
+  static auto cls = JC::javaClassStatic();
+  static auto constructor = cls->template getConstructor<typename JC::javaobject(Args...)>();
+  return cls->newObject(constructor, args...);
+}
+}
+
+
+template <typename T, typename B, typename J>
+auto JavaClass<T, B, J>::self() const noexcept -> javaobject {
+  return static_cast<javaobject>(JObject::self());
+}
 
 // jclass //////////////////////////////////////////////////////////////////////////////////////////
 
@@ -89,7 +148,7 @@ namespace detail {
 // use a void* to initialize a NativeMethod.
 struct NativeMethodWrapper;
 
-};
+}
 
 struct NativeMethod {
   const char* name;
@@ -97,11 +156,11 @@ struct NativeMethod {
   detail::NativeMethodWrapper* wrapper;
 };
 
-inline local_ref<jclass> JObjectWrapper<jclass>::getSuperclass() const noexcept {
+inline local_ref<JClass> JClass::getSuperclass() const noexcept {
   return adopt_local(internal::getEnv()->GetSuperclass(self()));
 }
 
-inline void JObjectWrapper<jclass>::registerNatives(std::initializer_list<NativeMethod> methods) {
+inline void JClass::registerNatives(std::initializer_list<NativeMethod> methods) {
   const auto env = internal::getEnv();
 
   JNINativeMethod jnimethods[methods.size()];
@@ -116,30 +175,30 @@ inline void JObjectWrapper<jclass>::registerNatives(std::initializer_list<Native
   FACEBOOK_JNI_THROW_EXCEPTION_IF(result != JNI_OK);
 }
 
-inline bool JObjectWrapper<jclass>::isAssignableFrom(alias_ref<jclass> other) const noexcept {
+inline bool JClass::isAssignableFrom(alias_ref<JClass> other) const noexcept {
   const auto env = internal::getEnv();
   const auto result = env->IsAssignableFrom(self(), other.get());
   return result;
 }
 
 template<typename F>
-inline JConstructor<F> JObjectWrapper<jclass>::getConstructor() const {
-  return getConstructor<F>(jmethod_traits<F>::constructor_descriptor().c_str());
+inline JConstructor<F> JClass::getConstructor() const {
+  return getConstructor<F>(jmethod_traits_from_cxx<F>::constructor_descriptor().c_str());
 }
 
 template<typename F>
-inline JConstructor<F> JObjectWrapper<jclass>::getConstructor(const char* descriptor) const {
+inline JConstructor<F> JClass::getConstructor(const char* descriptor) const {
   constexpr auto constructor_method_name = "<init>";
   return getMethod<F>(constructor_method_name, descriptor);
 }
 
 template<typename F>
-inline JMethod<F> JObjectWrapper<jclass>::getMethod(const char* name) const {
-  return getMethod<F>(name, jmethod_traits<F>::descriptor().c_str());
+inline JMethod<F> JClass::getMethod(const char* name) const {
+  return getMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
 }
 
 template<typename F>
-inline JMethod<F> JObjectWrapper<jclass>::getMethod(
+inline JMethod<F> JClass::getMethod(
     const char* name,
     const char* descriptor) const {
   const auto env = internal::getEnv();
@@ -149,12 +208,12 @@ inline JMethod<F> JObjectWrapper<jclass>::getMethod(
 }
 
 template<typename F>
-inline JStaticMethod<F> JObjectWrapper<jclass>::getStaticMethod(const char* name) const {
-  return getStaticMethod<F>(name, jmethod_traits<F>::descriptor().c_str());
+inline JStaticMethod<F> JClass::getStaticMethod(const char* name) const {
+  return getStaticMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
 }
 
 template<typename F>
-inline JStaticMethod<F> JObjectWrapper<jclass>::getStaticMethod(
+inline JStaticMethod<F> JClass::getStaticMethod(
     const char* name,
     const char* descriptor) const {
   const auto env = internal::getEnv();
@@ -164,12 +223,12 @@ inline JStaticMethod<F> JObjectWrapper<jclass>::getStaticMethod(
 }
 
 template<typename F>
-inline JNonvirtualMethod<F> JObjectWrapper<jclass>::getNonvirtualMethod(const char* name) const {
-  return getNonvirtualMethod<F>(name, jmethod_traits<F>::descriptor().c_str());
+inline JNonvirtualMethod<F> JClass::getNonvirtualMethod(const char* name) const {
+  return getNonvirtualMethod<F>(name, jmethod_traits_from_cxx<F>::descriptor().c_str());
 }
 
 template<typename F>
-inline JNonvirtualMethod<F> JObjectWrapper<jclass>::getNonvirtualMethod(
+inline JNonvirtualMethod<F> JClass::getNonvirtualMethod(
     const char* name,
     const char* descriptor) const {
   const auto env = internal::getEnv();
@@ -180,12 +239,12 @@ inline JNonvirtualMethod<F> JObjectWrapper<jclass>::getNonvirtualMethod(
 
 template<typename T>
 inline JField<enable_if_t<IsJniScalar<T>(), T>>
-JObjectWrapper<jclass>::getField(const char* name) const {
+JClass::getField(const char* name) const {
   return getField<T>(name, jtype_traits<T>::descriptor().c_str());
 }
 
 template<typename T>
-inline JField<enable_if_t<IsJniScalar<T>(), T>> JObjectWrapper<jclass>::getField(
+inline JField<enable_if_t<IsJniScalar<T>(), T>> JClass::getField(
     const char* name,
     const char* descriptor) const {
   const auto env = internal::getEnv();
@@ -195,13 +254,13 @@ inline JField<enable_if_t<IsJniScalar<T>(), T>> JObjectWrapper<jclass>::getField
 }
 
 template<typename T>
-inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JObjectWrapper<jclass>::getStaticField(
+inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JClass::getStaticField(
     const char* name) const {
   return getStaticField<T>(name, jtype_traits<T>::descriptor().c_str());
 }
 
 template<typename T>
-inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JObjectWrapper<jclass>::getStaticField(
+inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JClass::getStaticField(
     const char* name,
     const char* descriptor) const {
   const auto env = internal::getEnv();
@@ -211,32 +270,34 @@ inline JStaticField<enable_if_t<IsJniScalar<T>(), T>> JObjectWrapper<jclass>::ge
 }
 
 template<typename T>
-inline T JObjectWrapper<jclass>::getStaticFieldValue(JStaticField<T> field) const noexcept {
+inline T JClass::getStaticFieldValue(JStaticField<T> field) const noexcept {
   return field.get(self());
 }
 
 template<typename T>
-inline local_ref<T*> JObjectWrapper<jclass>::getStaticFieldValue(JStaticField<T*> field) noexcept {
+inline local_ref<T*> JClass::getStaticFieldValue(JStaticField<T*> field) noexcept {
   return adopt_local(field.get(self()));
 }
 
 template<typename T>
-inline void JObjectWrapper<jclass>::setStaticFieldValue(JStaticField<T> field, T value) noexcept {
+inline void JClass::setStaticFieldValue(JStaticField<T> field, T value) noexcept {
   field.set(self(), value);
 }
 
 template<typename R, typename... Args>
-inline local_ref<R> JObjectWrapper<jclass>::newObject(
+inline local_ref<R> JClass::newObject(
     JConstructor<R(Args...)> constructor,
     Args... args) const {
   const auto env = internal::getEnv();
-  auto object = env->NewObject(self(), constructor.getId(), args...);
+  auto object = env->NewObject(self(), constructor.getId(),
+      detail::callToJni(
+        detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
   FACEBOOK_JNI_THROW_EXCEPTION_IF(!object);
   return adopt_local(static_cast<R>(object));
 }
 
-inline jclass JObjectWrapper<jclass>::self() const noexcept {
-  return static_cast<jclass>(this_);
+inline jclass JClass::self() const noexcept {
+  return static_cast<jclass>(JObject::self());
 }
 
 inline void registerNatives(const char* name, std::initializer_list<NativeMethod> methods) {
@@ -246,197 +307,349 @@ inline void registerNatives(const char* name, std::initializer_list<NativeMethod
 
 // jstring /////////////////////////////////////////////////////////////////////////////////////////
 
-inline local_ref<jstring> make_jstring(const std::string& modifiedUtf8) {
+inline local_ref<JString> make_jstring(const std::string& modifiedUtf8) {
   return make_jstring(modifiedUtf8.c_str());
 }
 
-inline jstring JObjectWrapper<jstring>::self() const noexcept {
-  return static_cast<jstring>(this_);
-}
+namespace detail {
+// convert to std::string from jstring
+template <>
+struct Convert<std::string> {
+  typedef jstring jniType;
+  static std::string fromJni(jniType t) {
+    return wrap_alias(t)->toStdString();
+  }
+  static jniType toJniRet(const std::string& t) {
+    return make_jstring(t).release();
+  }
+  static local_ref<JString> toCall(const std::string& t) {
+    return make_jstring(t);
+  }
+};
 
+// convert return from const char*
+template <>
+struct Convert<const char*> {
+  typedef jstring jniType;
+  // no automatic synthesis of const char*.  (It can't be freed.)
+  static jniType toJniRet(const char* t) {
+    return make_jstring(t).release();
+  }
+  static local_ref<JString> toCall(const char* t) {
+    return make_jstring(t);
+  }
+};
+}
 
-// jthrowable //////////////////////////////////////////////////////////////////////////////////////
+// jtypeArray //////////////////////////////////////////////////////////////////////////////////////
 
-inline jthrowable JObjectWrapper<jthrowable>::self() const noexcept {
-  return static_cast<jthrowable>(this_);
+namespace detail {
+inline size_t JArray::size() const noexcept {
+  const auto env = internal::getEnv();
+  return env->GetArrayLength(self());
+}
 }
 
-
-// jtypeArray //////////////////////////////////////////////////////////////////////////////////////
-template<typename T>
-inline ElementProxy<T>::ElementProxy(
-    JObjectWrapper<_jtypeArray<T>*>* target,
+namespace detail {
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy(
+    Target* target,
     size_t idx)
     : target_{target}, idx_{idx} {}
 
-template<typename T>
-inline ElementProxy<T>& ElementProxy<T>::operator=(const T& o) {
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(const T& o) {
   target_->setElement(idx_, o);
   return *this;
 }
 
-template<typename T>
-inline ElementProxy<T>& ElementProxy<T>::operator=(alias_ref<T>& o) {
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(alias_ref<T>& o) {
   target_->setElement(idx_, o.get());
   return *this;
 }
 
-template<typename T>
-inline ElementProxy<T>& ElementProxy<T>::operator=(alias_ref<T>&& o) {
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(alias_ref<T>&& o) {
   target_->setElement(idx_, o.get());
   return *this;
 }
 
-template<typename T>
-inline ElementProxy<T>& ElementProxy<T>::operator=(const ElementProxy<T>& o) {
+template<typename Target>
+inline ElementProxy<Target>& ElementProxy<Target>::operator=(const ElementProxy<Target>& o) {
   auto src = o.target_->getElement(o.idx_);
   target_->setElement(idx_, src.get());
   return *this;
 }
 
-template<typename T>
-inline ElementProxy<T>::ElementProxy::operator const local_ref<T> () const {
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy::operator const local_ref<T> () const {
   return target_->getElement(idx_);
 }
 
-template<typename T>
-inline ElementProxy<T>::ElementProxy::operator local_ref<T> () {
+template<typename Target>
+inline ElementProxy<Target>::ElementProxy::operator local_ref<T> () {
   return target_->getElement(idx_);
 }
+}
+
+template <typename T>
+std::string JArrayClass<T>::get_instantiated_java_descriptor() {
+  return "[" + jtype_traits<T>::descriptor();
+};
+
+template <typename T>
+std::string JArrayClass<T>::get_instantiated_base_name() {
+  return get_instantiated_java_descriptor();
+};
 
 template<typename T>
-local_ref<jtypeArray<T>> JObjectWrapper<jtypeArray<T>>::newArray(size_t size) {
+auto JArrayClass<T>::newArray(size_t size) -> local_ref<javaobject> {
   static auto elementClass = findClassStatic(jtype_traits<T>::base_name().c_str());
   const auto env = internal::getEnv();
   auto rawArray = env->NewObjectArray(size, elementClass.get(), nullptr);
   FACEBOOK_JNI_THROW_EXCEPTION_IF(!rawArray);
-  return adopt_local(static_cast<jtypeArray<T>>(rawArray));
+  return adopt_local(static_cast<javaobject>(rawArray));
 }
 
 template<typename T>
-inline void JObjectWrapper<jtypeArray<T>>::setElement(size_t idx, const T& value) {
+inline void JArrayClass<T>::setElement(size_t idx, const T& value) {
   const auto env = internal::getEnv();
-  env->SetObjectArrayElement(static_cast<jobjectArray>(self()), idx, value);
+  env->SetObjectArrayElement(this->self(), idx, value);
 }
 
 template<typename T>
-inline local_ref<T> JObjectWrapper<jtypeArray<T>>::getElement(size_t idx) {
+inline local_ref<T> JArrayClass<T>::getElement(size_t idx) {
   const auto env = internal::getEnv();
-  auto rawElement = env->GetObjectArrayElement(static_cast<jobjectArray>(self()), idx);
+  auto rawElement = env->GetObjectArrayElement(this->self(), idx);
   return adopt_local(static_cast<T>(rawElement));
 }
 
 template<typename T>
-inline size_t JObjectWrapper<jtypeArray<T>>::size() {
-  const auto env = internal::getEnv();
-  return env->GetArrayLength(static_cast<jobjectArray>(self()));
+inline detail::ElementProxy<JArrayClass<T>> JArrayClass<T>::operator[](size_t index) {
+  return detail::ElementProxy<JArrayClass<T>>(this, index);
 }
 
-template<typename T>
-inline ElementProxy<T> JObjectWrapper<jtypeArray<T>>::operator[](size_t index) {
-  return ElementProxy<T>(this, index);
-}
+// jarray /////////////////////////////////////////////////////////////////////////////////////////
 
-template<typename T>
-inline jtypeArray<T> JObjectWrapper<jtypeArray<T>>::self() const noexcept {
-  return static_cast<jtypeArray<T>>(this_);
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::getRegion(jsize start, jsize length)
+    -> std::unique_ptr<T[]> {
+  using T = typename jtype_traits<JArrayType>::entry_type;
+  auto buf = std::unique_ptr<T[]>{new T[length]};
+  getRegion(start, length, buf.get());
+  return buf;
 }
 
+template <typename JArrayType>
+std::string JPrimitiveArray<JArrayType>::get_instantiated_java_descriptor() {
+  return jtype_traits<JArrayType>::descriptor();
+}
+template <typename JArrayType>
+std::string JPrimitiveArray<JArrayType>::get_instantiated_base_name() {
+  return JPrimitiveArray::get_instantiated_java_descriptor();
+}
 
-// jarray /////////////////////////////////////////////////////////////////////////////////////////
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pin() -> PinnedPrimitiveArray<T, PinnedArrayAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedArrayAlloc<T>>{this->self(), 0, 0};
+}
 
-inline size_t JObjectWrapper<jarray>::size() const noexcept {
-  const auto env = internal::getEnv();
-  return env->GetArrayLength(self());
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pinRegion(jsize start, jsize length)
+    -> PinnedPrimitiveArray<T, PinnedRegionAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedRegionAlloc<T>>{this->self(), start, length};
 }
 
-inline jarray JObjectWrapper<jarray>::self() const noexcept {
-  return static_cast<jarray>(this_);
+template <typename JArrayType>
+auto JPrimitiveArray<JArrayType>::pinCritical()
+    -> PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>> {
+  return PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>>{this->self(), 0, 0};
 }
 
+template <typename T>
+class PinnedArrayAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    (void) start;
+    (void) length;
+    *elements = array->getElements(isCopy);
+    *size = array->size();
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    (void) start;
+    (void) size;
+    array->releaseElements(elements, mode);
+  }
+};
+
+template <typename T>
+class PinnedCriticalAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    const auto env = internal::getEnv();
+    *elements = static_cast<T*>(env->GetPrimitiveArrayCritical(array.get(), isCopy));
+    FACEBOOK_JNI_THROW_EXCEPTION_IF(!elements);
+    *size = array->size();
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    const auto env = internal::getEnv();
+    env->ReleasePrimitiveArrayCritical(array.get(), elements, mode);
+  }
+};
+
+template <typename T>
+class PinnedRegionAlloc {
+ public:
+  static void allocate(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      jsize start,
+      jsize length,
+      T** elements,
+      size_t* size,
+      jboolean* isCopy) {
+    auto buf = array->getRegion(start, length);
+    FACEBOOK_JNI_THROW_EXCEPTION_IF(!buf);
+    *elements = buf.release();
+    *size = length;
+    *isCopy = true;
+  }
+  static void release(
+      alias_ref<typename jtype_traits<T>::array_type> array,
+      T* elements,
+      jint start,
+      jint size,
+      jint mode) {
+    std::unique_ptr<T[]> holder;
+    if (mode == 0 || mode == JNI_ABORT) {
+      holder.reset(elements);
+    }
+    if (mode == 0 || mode == JNI_COMMIT) {
+      array->setRegion(start, size, elements);
+    }
+  }
+};
 
 // PinnedPrimitiveArray ///////////////////////////////////////////////////////////////////////////
 
-template<typename T>
-inline PinnedPrimitiveArray<T>::PinnedPrimitiveArray(alias_ref<jarray> array) noexcept
-  : array_{array} {
-  get();
+template<typename T, typename Alloc>
+PinnedPrimitiveArray<T, Alloc>::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) {
+  *this = std::move(o);
 }
 
-template<typename T>
-PinnedPrimitiveArray<T>::PinnedPrimitiveArray(PinnedPrimitiveArray&& o) noexcept {
+template<typename T, typename Alloc>
+PinnedPrimitiveArray<T, Alloc>&
+PinnedPrimitiveArray<T, Alloc>::operator=(PinnedPrimitiveArray&& o) {
+  if (array_) {
+    release();
+  }
   array_ = std::move(o.array_);
   elements_ = o.elements_;
   isCopy_ = o.isCopy_;
   size_ = o.size_;
-  o.elements_ = nullptr;
-  o.isCopy_ = false;
-  o.size_ = 0;
+  start_ = o.start_;
+  o.clear();
+  return *this;
 }
 
-template<typename T>
-PinnedPrimitiveArray<T>&
-PinnedPrimitiveArray<T>::operator=(PinnedPrimitiveArray&& o) noexcept {
-  array_ = std::move(o.array_);
-  elements_ = o.elements_;
-  isCopy_ = o.isCopy_;
-  size_ = o.size_;
-  o.elements_ = nullptr;
-  o.isCopy_ = false;
-  o.size_ = 0;
-  return *this;
+template<typename T, typename Alloc>
+T* PinnedPrimitiveArray<T, Alloc>::get() {
+  return elements_;
 }
 
-template<typename T>
-inline T& PinnedPrimitiveArray<T>::operator[](size_t index) {
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::release() {
+  releaseImpl(0);
+  clear();
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::commit() {
+  releaseImpl(JNI_COMMIT);
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::abort() {
+  releaseImpl(JNI_ABORT);
+  clear();
+}
+
+template <typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::releaseImpl(jint mode) {
+  FACEBOOK_JNI_THROW_EXCEPTION_IF(array_.get() == nullptr);
+  Alloc::release(array_, elements_, start_, size_, mode);
+}
+
+template<typename T, typename Alloc>
+inline void PinnedPrimitiveArray<T, Alloc>::clear() noexcept {
+  array_ = nullptr;
+  elements_ = nullptr;
+  isCopy_ = false;
+  start_ = 0;
+  size_ = 0;
+}
+
+template<typename T, typename Alloc>
+inline T& PinnedPrimitiveArray<T, Alloc>::operator[](size_t index) {
   FACEBOOK_JNI_THROW_EXCEPTION_IF(elements_ == nullptr);
   return elements_[index];
 }
 
-template<typename T>
-inline bool PinnedPrimitiveArray<T>::isCopy() const noexcept {
+template<typename T, typename Alloc>
+inline bool PinnedPrimitiveArray<T, Alloc>::isCopy() const noexcept {
   return isCopy_ == JNI_TRUE;
 }
 
-template<typename T>
-inline size_t PinnedPrimitiveArray<T>::size() const noexcept {
+template<typename T, typename Alloc>
+inline size_t PinnedPrimitiveArray<T, Alloc>::size() const noexcept {
   return size_;
 }
 
-template<typename T>
-inline PinnedPrimitiveArray<T>::~PinnedPrimitiveArray() noexcept {
+template<typename T, typename Alloc>
+inline PinnedPrimitiveArray<T, Alloc>::~PinnedPrimitiveArray() noexcept {
   if (elements_) {
     release();
   }
 }
 
-#pragma push_macro("DECLARE_PRIMITIVE_METHODS")
-#undef DECLARE_PRIMITIVE_METHODS
-#define DECLARE_PRIMITIVE_METHODS(TYPE, NAME)          \
-template<> TYPE* PinnedPrimitiveArray<TYPE>::get();    \
-template<> void PinnedPrimitiveArray<TYPE>::release(); \
-
-DECLARE_PRIMITIVE_METHODS(jboolean, Boolean)
-DECLARE_PRIMITIVE_METHODS(jbyte, Byte)
-DECLARE_PRIMITIVE_METHODS(jchar, Char)
-DECLARE_PRIMITIVE_METHODS(jshort, Short)
-DECLARE_PRIMITIVE_METHODS(jint, Int)
-DECLARE_PRIMITIVE_METHODS(jlong, Long)
-DECLARE_PRIMITIVE_METHODS(jfloat, Float)
-DECLARE_PRIMITIVE_METHODS(jdouble, Double)
-#pragma pop_macro("DECLARE_PRIMITIVE_METHODS")
-
+template<typename T, typename Alloc>
+inline PinnedPrimitiveArray<T, Alloc>::PinnedPrimitiveArray(alias_ref<typename jtype_traits<T>::array_type> array, jint start, jint length) {
+  array_ = array;
+  start_ = start;
+  Alloc::allocate(array, start, length, &elements_, &size_, &isCopy_);
+}
 
-template<typename T, typename Base>
-inline alias_ref<jclass> JavaClass<T, Base>::javaClassStatic() {
-  static auto cls = findClassStatic(
-    std::string(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2).c_str());
+template<typename T, typename Base, typename JType>
+inline alias_ref<JClass> JavaClass<T, Base, JType>::javaClassStatic() {
+  static auto cls = findClassStatic(jtype_traits<typename T::javaobject>::base_name().c_str());
   return cls;
 }
 
-template<typename T, typename Base>
-inline local_ref<jclass> JavaClass<T, Base>::javaClassLocal() {
-  std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2);
+template<typename T, typename Base, typename JType>
+inline local_ref<JClass> JavaClass<T, Base, JType>::javaClassLocal() {
+  std::string className(jtype_traits<typename T::javaobject>::base_name().c_str());
   return findClassLocal(className.c_str());
 }
 
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses.h
index 457b79b55d67..ed129af1bdce 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/CoreClasses.h
@@ -15,8 +15,9 @@
  * to provide access to corresponding JNI functions + some conveniance.
  */
 
-#include "Meta.h"
-#include "References.h"
+#include "References-forward.h"
+#include "Meta-forward.h"
+#include "TypeTraits.h"
 
 #include <memory>
 
@@ -25,6 +26,9 @@
 namespace facebook {
 namespace jni {
 
+class JClass;
+class JObject;
+
 /// Lookup a class by name. Note this functions returns an alias_ref that
 /// points to a leaked global reference.  This is appropriate for classes
 /// that are never unloaded (which is any class in an Android app and most
@@ -34,7 +38,7 @@ namespace jni {
 /// in a "static auto" variable, or a static global.
 ///
 /// @return Returns a leaked global reference to the class
-alias_ref<jclass> findClassStatic(const char* name);
+alias_ref<JClass> findClassStatic(const char* name);
 
 /// Lookup a class by name. Note this functions returns a local reference,
 /// which means that it must not be stored in a static variable.
@@ -43,34 +47,66 @@ alias_ref<jclass> findClassStatic(const char* name);
 /// (like caching method ids).
 ///
 /// @return Returns a global reference to the class
-local_ref<jclass> findClassLocal(const char* name);
+local_ref<JClass> findClassLocal(const char* name);
 
 /// Check to see if two references refer to the same object. Comparison with nullptr
 /// returns true if and only if compared to another nullptr. A weak reference that
 /// refers to a reclaimed object count as nullptr.
-bool isSameObject(alias_ref<jobject> lhs, alias_ref<jobject> rhs) noexcept;
+bool isSameObject(alias_ref<JObject> lhs, alias_ref<JObject> rhs) noexcept;
 
+// Together, these classes allow convenient use of any class with the fbjni
+// helpers.  To use:
+//
+// struct MyClass : public JavaClass<MyClass> {
+//   constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;";
+// };
+//
+// Then, an alias_ref<MyClass::javaobject> will be backed by an instance of
+// MyClass. JavaClass provides a convenient way to add functionality to these
+// smart references.
+//
+// For example:
+//
+// struct MyClass : public JavaClass<MyClass> {
+//   constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;";
+//
+//   void foo() {
+//     static auto method = javaClassStatic()->getMethod<void()>("foo");
+//     method(self());
+//   }
+//
+//   static local_ref<javaobject> create(int i) {
+//     return newInstance(i);
+//   }
+// };
+//
+// auto obj = MyClass::create(10);
+// obj->foo();
+//
+// While users of a JavaClass-type can lookup methods and fields through the
+// underlying JClass, those calls can only be checked at runtime. It is recommended
+// that the JavaClass-type instead explicitly expose it's methods as in the example
+// above.
 
-/// Wrapper to provide functionality to jobject references
-template<>
-class JObjectWrapper<jobject> {
- public:
-  /// Java type descriptor
-  static constexpr const char* kJavaDescriptor = "Ljava/lang/Object;";
+namespace detail {
+template<typename JC, typename... Args>
+static local_ref<JC> newInstance(Args... args);
+}
 
-  static constexpr const char* get_instantiated_java_descriptor() { return nullptr; }
+class MonitorLock;
 
-  /// Wrap an existing JNI reference
-  JObjectWrapper(jobject reference = nullptr) noexcept;
+class JObject : detail::JObjectBase {
+public:
+  static constexpr auto kJavaDescriptor = "Ljava/lang/Object;";
 
-  // Copy constructor
-  JObjectWrapper(const JObjectWrapper& other) noexcept;
+  static constexpr const char* get_instantiated_java_descriptor() { return nullptr; }
+  static constexpr const char* get_instantiated_base_name() { return nullptr; }
 
   /// Get a @ref local_ref of the object's class
-  local_ref<jclass> getClass() const noexcept;
+  local_ref<JClass> getClass() const noexcept;
 
   /// Checks if the object is an instance of a class
-  bool isInstanceOf(alias_ref<jclass> cls) const noexcept;
+  bool isInstanceOf(alias_ref<JClass> cls) const noexcept;
 
   /// Get the primitive value of a field
   template<typename T>
@@ -78,7 +114,7 @@ class JObjectWrapper<jobject> {
 
   /// Get and wrap the value of a field in a @ref local_ref
   template<typename T>
-  local_ref<T*> getFieldValue(JField<T*> field) noexcept;
+  local_ref<T*> getFieldValue(JField<T*> field) const noexcept;
 
   /// Set the value of field. Any Java type is accepted, including the primitive types
   /// and raw reference types.
@@ -88,41 +124,108 @@ class JObjectWrapper<jobject> {
   /// Convenience method to create a std::string representing the object
   std::string toString() const;
 
- protected:
-  jobject this_;
+  // Take this object's monitor lock
+  MonitorLock lock() const noexcept;
 
- private:
-  template<typename T, typename A>
-  friend class base_owned_ref;
+  typedef _jobject _javaobject;
+  typedef _javaobject* javaobject;
 
-  template<typename T>
-  friend class alias_ref;
+protected:
+  jobject self() const noexcept;
+private:
+  friend void swap(JObject& a, JObject& b) noexcept;
+  template<typename>
+  friend struct detail::ReprAccess;
+  template<typename, typename, typename>
+  friend class JavaClass;
+
+  template <typename, typename>
+  friend class JObjectWrapper;
+};
 
-  friend void swap(JObjectWrapper<jobject>& a, JObjectWrapper<jobject>& b) noexcept;
+// This is only to maintain backwards compatibility with things that are
+// already providing a specialization of JObjectWrapper. Any such instances
+// should be updated to use a JavaClass.
+template<>
+class JObjectWrapper<jobject> : public JObject {
+};
 
-  void set(jobject reference) noexcept;
-  jobject get() const noexcept;
-  jobject self() const noexcept;
+
+namespace detail {
+template <typename, typename Base, typename JType>
+struct JTypeFor {
+  static_assert(
+      std::is_base_of<
+        std::remove_pointer<jobject>::type,
+        typename std::remove_pointer<JType>::type
+      >::value, "");
+  using _javaobject = typename std::remove_pointer<JType>::type;
+  using javaobject = JType;
 };
 
-using JObject = JObjectWrapper<jobject>;
+template <typename T, typename Base>
+struct JTypeFor<T, Base, void> {
+  // JNI pattern for jobject assignable pointer
+  struct _javaobject :  Base::_javaobject {
+    // This allows us to map back to the defining type (in ReprType, for
+    // example).
+    typedef T JniRefRepr;
+  };
+  using javaobject = _javaobject*;
+};
+}
 
-void swap(JObjectWrapper<jobject>& a, JObjectWrapper<jobject>& b) noexcept;
+// JavaClass provides a method to inform fbjni about user-defined Java types.
+// Given a class:
+// struct Foo : JavaClass<Foo> {
+//   static constexpr auto kJavaDescriptor = "Lcom/example/package/Foo;";
+// };
+// fbjni can determine the java type/method signatures for Foo::javaobject and
+// smart refs (like alias_ref<Foo::javaobject>) will hold an instance of Foo
+// and provide access to it through the -> and * operators.
+//
+// The "Base" template argument can be used to specify the JavaClass superclass
+// of this type (for instance, JString's Base is JObject).
+//
+// The "JType" template argument is used to provide a jni type (like jstring,
+// jthrowable) to be used as javaobject. This should only be necessary for
+// built-in jni types and not user-defined ones.
+template <typename T, typename Base = JObject, typename JType = void>
+class JavaClass : public Base {
+  using JObjType = typename detail::JTypeFor<T, Base, JType>;
+public:
+  using _javaobject = typename JObjType::_javaobject;
+  using javaobject = typename JObjType::javaobject;
+
+  using JavaBase = JavaClass;
+
+  static alias_ref<JClass> javaClassStatic();
+  static local_ref<JClass> javaClassLocal();
+protected:
+  /// Allocates a new object and invokes the specified constructor
+  /// Like JClass's getConstructor, this function can only check at runtime if
+  /// the class actually has a constructor that accepts the corresponding types.
+  /// While a JavaClass-type can expose this function directly, it is recommended
+  /// to instead to use this to explicitly only expose those constructors that
+  /// the Java class actually has (i.e. with static create() functions).
+  template<typename... Args>
+  static local_ref<T> newInstance(Args... args) {
+    return detail::newInstance<JavaClass>(args...);
+  }
 
+  javaobject self() const noexcept;
+};
 
 /// Wrapper to provide functionality to jclass references
 struct NativeMethod;
 
-template<>
-class JObjectWrapper<jclass> : public JObjectWrapper<jobject> {
+class JClass : public JavaClass<JClass, JObject, jclass> {
  public:
   /// Java type descriptor
   static constexpr const char* kJavaDescriptor = "Ljava/lang/Class;";
 
-  using JObjectWrapper<jobject>::JObjectWrapper;
-
   /// Get a @local_ref to the super class of this class
-  local_ref<jclass> getSuperclass() const noexcept;
+  local_ref<JClass> getSuperclass() const noexcept;
 
   /// Register native methods for the class.  Usage looks like this:
   ///
@@ -141,7 +244,7 @@ class JObjectWrapper<jclass> : public JObjectWrapper<jobject> {
 
   /// Check to see if the class is assignable from another class
   /// @pre cls != nullptr
-  bool isAssignableFrom(alias_ref<jclass> cls) const noexcept;
+  bool isAssignableFrom(alias_ref<JClass> cls) const noexcept;
 
   /// Convenience method to lookup the constructor with descriptor as specified by the
   /// type arguments
@@ -212,95 +315,96 @@ class JObjectWrapper<jclass> : public JObjectWrapper<jobject> {
   template<typename F>
   JNonvirtualMethod<F> getNonvirtualMethod(const char* name, const char* descriptor) const;
 
- private:
+private:
   jclass self() const noexcept;
 };
 
-using JClass = JObjectWrapper<jclass>;
-
 // Convenience method to register methods on a class without holding
 // onto the class object.
 void registerNatives(const char* name, std::initializer_list<NativeMethod> methods);
 
 /// Wrapper to provide functionality to jstring references
-template<>
-class JObjectWrapper<jstring> : public JObjectWrapper<jobject> {
+class JString : public JavaClass<JString, JObject, jstring> {
  public:
   /// Java type descriptor
   static constexpr const char* kJavaDescriptor = "Ljava/lang/String;";
 
-  using JObjectWrapper<jobject>::JObjectWrapper;
-
   /// Convenience method to convert a jstring object to a std::string
   std::string toStdString() const;
-
- private:
-  jstring self() const noexcept;
 };
 
 /// Convenience functions to convert a std::string or const char* into a @ref local_ref to a
 /// jstring
-local_ref<jstring> make_jstring(const char* modifiedUtf8);
-local_ref<jstring> make_jstring(const std::string& modifiedUtf8);
-
-using JString = JObjectWrapper<jstring>;
+local_ref<JString> make_jstring(const char* modifiedUtf8);
+local_ref<JString> make_jstring(const std::string& modifiedUtf8);
 
 /// Wrapper to provide functionality to jthrowable references
-template<>
-class JObjectWrapper<jthrowable> : public JObjectWrapper<jobject> {
+class JThrowable : public JavaClass<JThrowable, JObject, jthrowable> {
  public:
-  /// Java type descriptor
   static constexpr const char* kJavaDescriptor = "Ljava/lang/Throwable;";
+};
 
-  using JObjectWrapper<jobject>::JObjectWrapper;
-
+namespace detail {
+template<typename Target>
+class ElementProxy {
  private:
-  jthrowable self() const noexcept;
-};
+  Target* target_;
+  size_t idx_;
 
+ public:
+  using T = typename Target::javaentry;
+  ElementProxy(Target* target, size_t idx);
 
-/// @cond INTERNAL
-template<class T> class _jtypeArray : public _jobjectArray {};
-// @endcond
-/// Wrapper to provide functionality for arrays of j-types
-template<class T> using jtypeArray = _jtypeArray<T>*;
+  ElementProxy& operator=(const T& o);
 
-template<typename T>
-class ElementProxy {
-   private:
-    JObjectWrapper<_jtypeArray<T>*>* target_;
-    size_t idx_;
-
-   public:
-    ElementProxy(JObjectWrapper<_jtypeArray<T>*>* target, size_t idx);
+  ElementProxy& operator=(alias_ref<T>& o);
 
-    ElementProxy<T>& operator=(const T& o);
+  ElementProxy& operator=(alias_ref<T>&& o);
 
-    ElementProxy<T>& operator=(alias_ref<T>& o);
+  ElementProxy& operator=(const ElementProxy& o);
 
-    ElementProxy<T>& operator=(alias_ref<T>&& o);
+  operator const local_ref<T> () const;
 
-    ElementProxy<T>& operator=(const ElementProxy<T>& o);
+  operator local_ref<T> ();
+};
+}
 
-    operator const local_ref<T> () const;
+namespace detail {
+class JArray : public JavaClass<JArray, JObject, jarray> {
+ public:
+  // This cannot be used in a scope that derives a descriptor (like in a method
+  // signature). Use a more derived type instead (like JArrayInt or
+  // JArrayClass<T>).
+  static constexpr const char* kJavaDescriptor = nullptr;
+  size_t size() const noexcept;
+};
 
-    operator local_ref<T> ();
-  };
+// This is used so that the JArrayClass<T> javaobject extends jni's
+// jobjectArray. This class should not be used directly. A general Object[]
+// should use JArrayClass<jobject>.
+class JTypeArray : public JavaClass<JTypeArray, JArray, jobjectArray> {
+  // This cannot be used in a scope that derives a descriptor (like in a method
+  // signature).
+  static constexpr const char* kJavaDescriptor = nullptr;
+};
+}
 
 template<typename T>
-class JObjectWrapper<jtypeArray<T>> : public JObjectWrapper<jobject> {
+class JArrayClass : public JavaClass<JArrayClass<T>, detail::JTypeArray> {
  public:
+  static_assert(is_plain_jni_reference<T>(), "");
+  // javaentry is the jni type of an entry in the array (i.e. jint).
+  using javaentry = T;
+  // javaobject is the jni type of the array.
+  using javaobject = typename JavaClass<JArrayClass<T>, detail::JTypeArray>::javaobject;
   static constexpr const char* kJavaDescriptor = nullptr;
-  static std::string get_instantiated_java_descriptor() {
-    return "[" + jtype_traits<T>::descriptor();
-  };
-
-  using JObjectWrapper<jobject>::JObjectWrapper;
+  static std::string get_instantiated_java_descriptor();
+  static std::string get_instantiated_base_name();
 
   /// Allocate a new array from Java heap, for passing as a JNI parameter or return value.
   /// NOTE: if using as a return value, you want to call release() instead of get() on the
   /// smart pointer.
-  static local_ref<jtypeArray<T>> newArray(size_t count);
+  static local_ref<javaobject> newArray(size_t count);
 
   /// Assign an object to the array.
   /// Typically you will use the shorthand (*ref)[idx]=value;
@@ -312,9 +416,6 @@ class JObjectWrapper<jtypeArray<T>> : public JObjectWrapper<jobject> {
   /// If you use auto, you'll get an ElementProxy, which may need to be cast.
   local_ref<T> getElement(size_t idx);
 
-  /// Get the size of the array.
-  size_t size();
-
   /// EXPERIMENTAL SUBSCRIPT SUPPORT
   /// This implementation of [] returns a proxy object which then has a bunch of specializations
   /// (adopt_local free function, operator= and casting overloads on the ElementProxy) that can
@@ -324,187 +425,167 @@ class JObjectWrapper<jtypeArray<T>> : public JObjectWrapper<jobject> {
   /// by using idioms that haven't been tried yet. Consider yourself warned. On the other hand,
   /// it does make for some idiomatic assignment code; see TestBuildStringArray in fbjni_tests
   /// for some examples.
-  ElementProxy<T> operator[](size_t idx);
-
- private:
-  jtypeArray<T> self() const noexcept;
+  detail::ElementProxy<JArrayClass> operator[](size_t idx);
 };
 
-template <class T>
-using JArrayClass = JObjectWrapper<jtypeArray<T>>;
+template <typename T>
+using jtypeArray = typename JArrayClass<T>::javaobject;
 
 template<typename T>
-local_ref<jtypeArray<T>> adopt_local_array(jobjectArray ref) {
-  return adopt_local(static_cast<jtypeArray<T>>(ref));
+local_ref<typename JArrayClass<T>::javaobject> adopt_local_array(jobjectArray ref) {
+  return adopt_local(static_cast<typename JArrayClass<T>::javaobject>(ref));
 }
 
-template<typename T>
-local_ref<T> adopt_local(ElementProxy<T> elementProxy) {
-  return static_cast<local_ref<T>>(elementProxy);
+template<typename Target>
+local_ref<typename Target::javaentry> adopt_local(detail::ElementProxy<Target> elementProxy) {
+  return static_cast<local_ref<typename Target::javaentry>>(elementProxy);
 }
 
+template <typename T, typename PinAlloc>
+class PinnedPrimitiveArray;
+
+template <typename T> class PinnedArrayAlloc;
+template <typename T> class PinnedRegionAlloc;
+template <typename T> class PinnedCriticalAlloc;
+
 /// Wrapper to provide functionality to jarray references.
 /// This is an empty holder by itself. Construct a PinnedPrimitiveArray to actually interact with
 /// the elements of the array.
-template<>
-class JObjectWrapper<jarray> : public JObjectWrapper<jobject> {
+template <typename JArrayType>
+class JPrimitiveArray :
+    public JavaClass<JPrimitiveArray<JArrayType>, detail::JArray, JArrayType> {
+  static_assert(is_jni_primitive_array<JArrayType>(), "");
  public:
   static constexpr const char* kJavaDescriptor = nullptr;
-
-  using JObjectWrapper<jobject>::JObjectWrapper;
-  size_t size() const noexcept;
-
- private:
-  jarray self() const noexcept;
+  static std::string get_instantiated_java_descriptor();
+  static std::string get_instantiated_base_name();
+
+  using T = typename jtype_traits<JArrayType>::entry_type;
+
+  static local_ref<JArrayType> newArray(size_t count);
+
+  void getRegion(jsize start, jsize length, T* buf);
+  std::unique_ptr<T[]> getRegion(jsize start, jsize length);
+  void setRegion(jsize start, jsize length, const T* buf);
+
+  /// Returns a view of the underlying array. This will either be a "pinned"
+  /// version of the array (in which case changes to one immediately affect the
+  /// other) or a copy of the array (in which cases changes to the view will take
+  /// affect when destroyed or on calls to release()/commit()).
+  PinnedPrimitiveArray<T, PinnedArrayAlloc<T>> pin();
+
+  /// Returns a view of part of the underlying array. A pinned region is always
+  /// backed by a copy of the region.
+  PinnedPrimitiveArray<T, PinnedRegionAlloc<T>> pinRegion(jsize start, jsize length);
+
+  /// Returns a view of the underlying array like pin(). However, while the pin
+  /// is held, the code is considered within a "critical region". In a critical
+  /// region, native code must not call JNI functions or make any calls that may
+  /// block on other Java threads. These restrictions make it more likely that
+  /// the view will be "pinned" rather than copied (for example, the VM may
+  /// suspend garbage collection within a critical region).
+  PinnedPrimitiveArray<T, PinnedCriticalAlloc<T>> pinCritical();
+
+private:
+  friend class PinnedArrayAlloc<T>;
+  T* getElements(jboolean* isCopy);
+  void releaseElements(T* elements, jint mode);
 };
 
-using JArray = JObjectWrapper<jarray>;
-
-template <typename T>
-class PinnedPrimitiveArray;
-
-#pragma push_macro("DECLARE_PRIMITIVE_ARRAY_UTILS")
-#undef DECLARE_PRIMITIVE_ARRAY_UTILS
-#define DECLARE_PRIMITIVE_ARRAY_UTILS(TYPE, NAME, DESC)                \
-                                                                       \
-local_ref<j ## TYPE ## Array> make_ ## TYPE ## _array(jsize size);     \
-                                                                       \
-template<> class JObjectWrapper<j ## TYPE ## Array> : public JArray {  \
- public:                                                               \
-  static constexpr const char* kJavaDescriptor = "[" # DESC;           \
-                                                                       \
-  using JArray::JArray;                                                \
-                                                                       \
-  static local_ref<j ## TYPE ## Array> newArray(size_t count);         \
-                                                                       \
-  j ## TYPE* getRegion(jsize start, jsize length, j ## TYPE* buf);     \
-  std::unique_ptr<j ## TYPE[]> getRegion(jsize start, jsize length);   \
-  void setRegion(jsize start, jsize length, const j ## TYPE* buf);     \
-  PinnedPrimitiveArray<j ## TYPE> pin();                               \
-                                                                       \
- private:                                                              \
-  j ## TYPE ## Array self() const noexcept {                           \
-    return static_cast<j ## TYPE ## Array>(this_);                     \
-  }                                                                    \
-};                                                                     \
-                                                                       \
-using JArray ## NAME = JObjectWrapper<j ## TYPE ## Array>              \
-
-
-DECLARE_PRIMITIVE_ARRAY_UTILS(boolean, Boolean, "Z");
-DECLARE_PRIMITIVE_ARRAY_UTILS(byte, Byte, "B");
-DECLARE_PRIMITIVE_ARRAY_UTILS(char, Char, "C");
-DECLARE_PRIMITIVE_ARRAY_UTILS(short, Short, "S");
-DECLARE_PRIMITIVE_ARRAY_UTILS(int, Int, "I");
-DECLARE_PRIMITIVE_ARRAY_UTILS(long, Long, "J");
-DECLARE_PRIMITIVE_ARRAY_UTILS(float, Float, "F");
-DECLARE_PRIMITIVE_ARRAY_UTILS(double, Double, "D");
-
-#pragma pop_macro("DECLARE_PRIMITIVE_ARRAY_UTILS")
-
+local_ref<jbooleanArray> make_boolean_array(jsize size);
+local_ref<jbyteArray> make_byte_array(jsize size);
+local_ref<jcharArray> make_char_array(jsize size);
+local_ref<jshortArray> make_short_array(jsize size);
+local_ref<jintArray> make_int_array(jsize size);
+local_ref<jlongArray> make_long_array(jsize size);
+local_ref<jfloatArray> make_float_array(jsize size);
+local_ref<jdoubleArray> make_double_array(jsize size);
+
+using JArrayBoolean = JPrimitiveArray<jbooleanArray>;
+using JArrayByte = JPrimitiveArray<jbyteArray>;
+using JArrayChar = JPrimitiveArray<jcharArray>;
+using JArrayShort = JPrimitiveArray<jshortArray>;
+using JArrayInt = JPrimitiveArray<jintArray>;
+using JArrayLong = JPrimitiveArray<jlongArray>;
+using JArrayFloat = JPrimitiveArray<jfloatArray>;
+using JArrayDouble = JPrimitiveArray<jdoubleArray>;
 
 /// RAII class for pinned primitive arrays
 /// This currently only supports read/write access to existing java arrays. You can't create a
 /// primitive array this way yet. This class also pins the entire array into memory during the
 /// lifetime of the PinnedPrimitiveArray. If you need to unpin the array manually, call the
-/// release() function. During a long-running block of code, you should unpin the array as soon
-/// as you're done with it, to avoid holding up the Java garbage collector.
-template <typename T>
+/// release() or abort() functions. During a long-running block of code, you
+/// should unpin the array as soon as you're done with it, to avoid holding up
+/// the Java garbage collector.
+template <typename T, typename PinAlloc>
 class PinnedPrimitiveArray {
   public:
    static_assert(is_jni_primitive<T>::value,
        "PinnedPrimitiveArray requires primitive jni type.");
 
-   PinnedPrimitiveArray(PinnedPrimitiveArray&&) noexcept;
+   using ArrayType = typename jtype_traits<T>::array_type;
+
+   PinnedPrimitiveArray(PinnedPrimitiveArray&&);
    PinnedPrimitiveArray(const PinnedPrimitiveArray&) = delete;
    ~PinnedPrimitiveArray() noexcept;
 
-   PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&) noexcept;
+   PinnedPrimitiveArray& operator=(PinnedPrimitiveArray&&);
    PinnedPrimitiveArray& operator=(const PinnedPrimitiveArray&) = delete;
 
    T* get();
    void release();
+   /// Unpins the array. If the array is a copy, pending changes are discarded.
+   void abort();
+   /// If the array is a copy, copies pending changes to the underlying java array.
+   void commit();
+
+   bool isCopy() const noexcept;
 
    const T& operator[](size_t index) const;
    T& operator[](size_t index);
-   bool isCopy() const noexcept;
    size_t size() const noexcept;
 
   private:
-   alias_ref<jarray> array_;
+   alias_ref<ArrayType> array_;
+   size_t start_;
    T* elements_;
    jboolean isCopy_;
    size_t size_;
 
-   PinnedPrimitiveArray(alias_ref<jarray>) noexcept;
+   void allocate(alias_ref<ArrayType>, jint start, jint length);
+   void releaseImpl(jint mode);
+   void clear() noexcept;
 
-   friend class JObjectWrapper<jbooleanArray>;
-   friend class JObjectWrapper<jbyteArray>;
-   friend class JObjectWrapper<jcharArray>;
-   friend class JObjectWrapper<jshortArray>;
-   friend class JObjectWrapper<jintArray>;
-   friend class JObjectWrapper<jlongArray>;
-   friend class JObjectWrapper<jfloatArray>;
-   friend class JObjectWrapper<jdoubleArray>;
-};
-
-
-namespace detail {
+   PinnedPrimitiveArray(alias_ref<ArrayType>, jint start, jint length);
 
-class BaseJavaClass {
-public:
-  typedef _jobject _javaobject;
-  typedef _javaobject* javaobject;
+   friend class JPrimitiveArray<typename jtype_traits<T>::array_type>;
 };
 
+#pragma push_macro("PlainJniRefMap")
+#undef PlainJniRefMap
+#define PlainJniRefMap(rtype, jtype) \
+namespace detail { \
+template<> \
+struct RefReprType<jtype> { \
+  using type = rtype; \
+}; \
 }
 
-// Together, these classes allow convenient use of any class with the fbjni
-// helpers.  To use:
-//
-// struct MyClass : public JavaClass<MyClass> {
-//   constexpr static auto kJavaDescriptor = "Lcom/example/package/MyClass;";
-// };
-//
-// alias_ref<MyClass::javaobject> myClass = foo();
-
-template <typename T, typename Base = detail::BaseJavaClass>
-class JavaClass {
-public:
-  // JNI pattern for jobject assignable pointer
-  struct _javaobject : public Base::_javaobject {
-    typedef T javaClass;
-  };
-  typedef _javaobject* javaobject;
-
-  static alias_ref<jclass> javaClassStatic();
-  static local_ref<jclass> javaClassLocal();
-};
-
-template <typename T>
-class JObjectWrapper<T,
-    typename std::enable_if<
-      is_plain_jni_reference<T>::value &&
-      std::is_class<typename std::remove_pointer<T>::type::javaClass>::value
-    >::type>
-  : public JObjectWrapper<jobject> {
-public:
-  static constexpr const char* kJavaDescriptor =
-    std::remove_pointer<T>::type::javaClass::kJavaDescriptor;
-
-  using JObjectWrapper<jobject>::JObjectWrapper;
-
-  template<typename U>
-  JObjectWrapper(const JObjectWrapper<U>& w)
-    : JObjectWrapper<jobject>(w) {
-    static_assert(std::is_convertible<U, T>::value,
-                  "U must be convertible to T");
-  }
-};
+PlainJniRefMap(JArrayBoolean, jbooleanArray);
+PlainJniRefMap(JArrayByte, jbyteArray);
+PlainJniRefMap(JArrayChar, jcharArray);
+PlainJniRefMap(JArrayShort, jshortArray);
+PlainJniRefMap(JArrayInt, jintArray);
+PlainJniRefMap(JArrayLong, jlongArray);
+PlainJniRefMap(JArrayFloat, jfloatArray);
+PlainJniRefMap(JArrayDouble, jdoubleArray);
+PlainJniRefMap(JObject, jobject);
+PlainJniRefMap(JClass, jclass);
+PlainJniRefMap(JString, jstring);
+PlainJniRefMap(JThrowable, jthrowable);
+
+#pragma pop_macro("PlainJniRefMap")
 
 }}
 
 #include "CoreClasses-inl.h"
-// This is here because code in Meta-inl.h uses alias_ref, which
-// requires JObjectWrapper<jobject> to be concrete before it can work.
-#include "Meta-inl.h"
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.cpp b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.cpp
index 20925ae80724..6a3516cd3f7d 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.cpp
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.cpp
@@ -7,9 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "Exceptions.h"
-#include "CoreClasses.h"
-#include <jni/ALog.h>
+#include "jni/fbjni.h"
 
 #include <fb/assert.h>
 
@@ -193,7 +191,7 @@ void setNewJavaException(jclass exceptionClass, const char* fmt, ARGS... args) {
   JNIEnv* env = internal::getEnv();
 
   try {
-    char *msg = (char*) alloca(msgSize);
+    char *msg = (char*) alloca(msgSize + 1);
     snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...);
     env->ThrowNew(exceptionClass, msg);
   } catch (...) {
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.h
index 9ba9367f67e5..1892fd49c87b 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Exceptions.h
@@ -114,7 +114,7 @@ template<typename... Args>
   assertIfExceptionsNotInitialized();
   int msgSize = snprintf(nullptr, 0, fmt, args...);
 
-  char *msg = (char*) alloca(msgSize);
+  char *msg = (char*) alloca(msgSize + 1);
   snprintf(msg, kMaxExceptionMessageBufferSize, fmt, args...);
   throwNewJavaException(throwableName, msg);
 }
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/File.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/File.h
new file mode 100644
index 000000000000..29fc9850a95c
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/File.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016-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.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+class JFile : public JavaClass<JFile> {
+ public:
+  static constexpr const char* kJavaDescriptor = "Ljava/io/File;";
+
+  // Define a method that calls into the represented Java class
+  std::string getAbsolutePath() {
+    static auto method = getClass()->getMethod<jstring()>("getAbsolutePath");
+    return method(self())->toStdString();
+  }
+
+};
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.cpp b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.cpp
index ebcb778de9e1..c779cde4ef79 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.cpp
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.cpp
@@ -7,20 +7,17 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-#include "Hybrid.h"
+#include "jni/fbjni.h"
 
-#include "Exceptions.h"
-#include "Registration.h"
 
 namespace facebook {
 namespace jni {
 
 namespace detail {
 
-void setNativePointer(alias_ref<HybridData::javaobject> hybridData,
-                      std::unique_ptr<BaseHybridClass> new_value) {
-  static auto pointerField = hybridData->getClass()->getField<jlong>("mNativePointer");
-  auto* old_value = reinterpret_cast<BaseHybridClass*>(hybridData->getFieldValue(pointerField));
+void HybridData::setNativePointer(std::unique_ptr<BaseHybridClass> new_value) {
+  static auto pointerField = getClass()->getField<jlong>("mNativePointer");
+  auto* old_value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
   if (new_value) {
     // Modify should only ever be called once with a non-null
     // new_value.  If this happens again it's a programmer error, so
@@ -35,35 +32,28 @@ void setNativePointer(alias_ref<HybridData::javaobject> hybridData,
   // ownership of it, to HybridData which is managed by the java GC.  The
   // finalizer on hybridData calls resetNative which will delete the object, if
   // reseetNative has not already been called.
-  hybridData->setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
+  setFieldValue(pointerField, reinterpret_cast<jlong>(new_value.release()));
 }
 
-BaseHybridClass* getNativePointer(alias_ref<HybridData::javaobject> hybridData) {
-  static auto pointerField = hybridData->getClass()->getField<jlong>("mNativePointer");
-  auto* value = reinterpret_cast<BaseHybridClass*>(hybridData->getFieldValue(pointerField));
+BaseHybridClass* HybridData::getNativePointer() {
+  static auto pointerField = getClass()->getField<jlong>("mNativePointer");
+  auto* value = reinterpret_cast<BaseHybridClass*>(getFieldValue(pointerField));
   if (!value) {
     throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
   }
   return value;
 }
 
-local_ref<HybridData::javaobject> getHybridData(alias_ref<jobject> jthis,
-                                                JField<HybridData::javaobject> field) {
-  auto hybridData = jthis->getFieldValue(field);
-  if (!hybridData) {
-    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
-  }
-  return hybridData;
+local_ref<HybridData> HybridData::create() {
+  return newInstance();
 }
 
 }
 
 namespace {
-
-void resetNative(alias_ref<detail::HybridData::javaobject> jthis) {
-  detail::setNativePointer(jthis, nullptr);
+void resetNative(alias_ref<detail::HybridData> jthis) {
+  jthis->setNativePointer(nullptr);
 }
-
 }
 
 void HybridDataOnLoad() {
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.h
index 8b62ccdedec4..70fc670f5c8e 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Hybrid.h
@@ -19,169 +19,108 @@ namespace jni {
 
 namespace detail {
 
-class BaseHybridClass : public BaseJavaClass {
+class BaseHybridClass {
 public:
   virtual ~BaseHybridClass() {}
 };
 
 struct HybridData : public JavaClass<HybridData> {
   constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/HybridData;";
+  void setNativePointer(std::unique_ptr<BaseHybridClass> new_value);
+  BaseHybridClass* getNativePointer();
+  static local_ref<HybridData> create();
 };
 
-void setNativePointer(alias_ref<HybridData::javaobject> hybridData,
-                      std::unique_ptr<BaseHybridClass> new_value);
-BaseHybridClass* getNativePointer(alias_ref<HybridData::javaobject> hybridData);
-local_ref<HybridData::javaobject> getHybridData(alias_ref<jobject> jthis,
-                                                JField<HybridData::javaobject> field);
-
-// Normally, pass through types unmolested.
-template <typename T, typename Enabled = void>
-struct Convert {
-  typedef T jniType;
-  static jniType fromJni(jniType t) {
-    return t;
-  }
-  static jniType toJniRet(jniType t) {
-    return t;
-  }
-  static jniType toCall(jniType t) {
-    return t;
-  }
+template <typename Base, typename Enabled = void>
+struct HybridTraits {
+  // This static assert should actually always fail if we don't use one of the
+  // specializations below.
+  static_assert(
+      std::is_base_of<JObject, Base>::value ||
+      std::is_base_of<BaseHybridClass, Base>::value,
+      "The base of a HybridClass must be either another HybridClass or derived from JObject.");
 };
 
-// This is needed for return conversion
 template <>
-struct Convert<void> {
-  typedef void jniType;
+struct HybridTraits<BaseHybridClass> {
+ using CxxBase = BaseHybridClass;
+ using JavaBase = JObject;
 };
 
-// convert to std::string from jstring
-template <>
-struct Convert<std::string> {
-  typedef jstring jniType;
-  static std::string fromJni(jniType t) {
-    return wrap_alias(t)->toStdString();
-  }
-  static jniType toJniRet(const std::string& t) {
-    return make_jstring(t).release();
-  }
-  static local_ref<jstring> toCall(const std::string& t) {
-    return make_jstring(t);
-  }
+template <typename Base>
+struct HybridTraits<
+    Base,
+    typename std::enable_if<std::is_base_of<BaseHybridClass, Base>::value>::type> {
+ using CxxBase = Base;
+ using JavaBase = typename Base::JavaPart;
 };
 
-// convert return from const char*
-template <>
-struct Convert<const char*> {
-  typedef jstring jniType;
-  // no automatic synthesis of const char*.  (It can't be freed.)
-  static jniType toJniRet(const char* t) {
-    return make_jstring(t).release();
-  }
-  static local_ref<jstring> toCall(const char* t) {
-    return make_jstring(t);
-  }
-};
-
-// jboolean is an unsigned char, not a bool. Allow it to work either way.
-template<>
-struct Convert<bool> {
-  typedef jboolean jniType;
-  static bool fromJni(jniType t) {
-    return t;
-  }
-  static jniType toJniRet(bool t) {
-    return t;
-  }
-  static jniType toCall(bool t) {
-    return t;
-  }
+template <typename Base>
+struct HybridTraits<
+    Base,
+    typename std::enable_if<std::is_base_of<JObject, Base>::value>::type> {
+ using CxxBase = BaseHybridClass;
+ using JavaBase = Base;
 };
 
-// convert to alias_ref<T> from T
+// convert to HybridClass* from jhybridobject
 template <typename T>
-struct Convert<alias_ref<T>> {
-  typedef T jniType;
-  static alias_ref<jniType> fromJni(jniType t) {
-    return wrap_alias(t);
-  }
-  static jniType toJniRet(alias_ref<jniType> t) {
-    return t.get();
-  }
-  static jniType toCall(alias_ref<jniType> t) {
-    return t.get();
-  }
-};
-
-// convert return from local_ref<T>
-template <typename T>
-struct Convert<local_ref<T>> {
-  typedef T jniType;
-  // No automatic synthesis of local_ref
-  static jniType toJniRet(local_ref<jniType> t) {
-    return t.release();
-  }
-  static jniType toCall(local_ref<jniType> t) {
-    return t.get();
-  }
+struct Convert<
+  T, typename std::enable_if<
+    std::is_base_of<BaseHybridClass, typename std::remove_pointer<T>::type>::value>::type> {
+  typedef typename std::remove_pointer<T>::type::jhybridobject jniType;
+  static T fromJni(jniType t) {
+    if (t == nullptr) {
+      return nullptr;
+    }
+    return wrap_alias(t)->cthis();
+  }
+  // There is no automatic return conversion for objects.
 };
 
-// convert return from global_ref<T>
-template <typename T>
-struct Convert<global_ref<T>> {
-  typedef T jniType;
-  // No automatic synthesis of global_ref
-  static jniType toJniRet(global_ref<jniType> t) {
-    return t.get();
-  }
-  static jniType toCall(global_ref<jniType> t) {
-    return t.get();
-  }
+template<typename T>
+struct RefReprType<T, typename std::enable_if<std::is_base_of<BaseHybridClass, T>::value, void>::type> {
+  static_assert(std::is_same<T, void>::value,
+      "HybridFoo (where HybridFoo derives from HybridClass<HybridFoo>) is not supported in this context. "
+      "For an xxx_ref<HybridFoo>, you may want: xxx_ref<HybridFoo::javaobject> or HybridFoo*.");
+  using Repr = T;
 };
 
-// In order to avoid potentially filling the jni locals table,
-// temporary objects (right now, this is just jstrings) need to be
-// released.  This is done by returning a holder which autoconverts to
-// jstring.  This is only relevant when the jniType is passed down, as
-// in newObjectJavaArgs.
-
-template <typename T>
-inline T callToJni(T&& t) {
-  return t;
-}
 
-inline jstring callToJni(local_ref<jstring>&& sref) {
-  return sref.get();
 }
 
-struct jstring_holder {
-  local_ref<jstring> s_;
-  jstring_holder(const char* s) : s_(make_jstring(s)) {}
-  operator jstring() { return s_.get(); }
-};
+template <typename T, typename Base = detail::BaseHybridClass>
+class HybridClass : public detail::HybridTraits<Base>::CxxBase {
+public:
+  struct JavaPart : JavaClass<JavaPart, typename detail::HybridTraits<Base>::JavaBase> {
+    // At this point, T is incomplete, and so we cannot access
+    // T::kJavaDescriptor directly. jtype_traits support this escape hatch for
+    // such a case.
+    static constexpr const char* kJavaDescriptor = nullptr;
+    static std::string get_instantiated_java_descriptor();
+    static std::string get_instantiated_base_name();
 
-template <typename T, typename Enabled = void>
-struct HybridRoot {};
+    using HybridType = T;
 
-template <typename T>
-struct HybridRoot<T,
-                  typename std::enable_if<!std::is_base_of<BaseHybridClass, T>::value>::type>
-    : public BaseHybridClass {};
+    // This will reach into the java object and extract the C++ instance from
+    // the mHybridData and return it.
+    T* cthis();
 
-}
+    friend class HybridClass;
+  };
 
-template <typename T, typename Base = detail::BaseHybridClass>
-class HybridClass : public Base
-                  , public detail::HybridRoot<Base>
-                  , public JavaClass<T, Base> {
-public:
+  using jhybridobject = typename JavaPart::javaobject;
+  using javaobject = typename JavaPart::javaobject;
   typedef detail::HybridData::javaobject jhybriddata;
-  typedef typename JavaClass<T, Base>::javaobject jhybridobject;
 
-  using JavaClass<T, Base>::javaClassStatic;
-  using JavaClass<T, Base>::javaClassLocal;
-  using JavaClass<T, Base>::javaobject;
-  typedef typename JavaClass<T, Base>::_javaobject _javaobject;
+  static alias_ref<JClass> javaClassStatic() {
+    return JavaPart::javaClassStatic();
+  }
+
+  static local_ref<JClass> javaClassLocal() {
+    std::string className(T::kJavaDescriptor + 1, strlen(T::kJavaDescriptor) - 2);
+    return findClassLocal(className.c_str());
+  }
 
 protected:
   typedef HybridClass HybridBase;
@@ -189,21 +128,20 @@ class HybridClass : public Base
   // This ensures that a C++ hybrid part cannot be created on its own
   // by default.  If a hybrid wants to enable this, it can provide its
   // own public ctor, or change the accessibility of this to public.
-  using Base::Base;
+  using detail::HybridTraits<Base>::CxxBase::CxxBase;
 
   static void registerHybrid(std::initializer_list<NativeMethod> methods) {
     javaClassStatic()->registerNatives(methods);
   }
 
-  static local_ref<jhybriddata> makeHybridData(std::unique_ptr<T> cxxPart) {
-    static auto dataCtor = detail::HybridData::javaClassStatic()->getConstructor<jhybriddata()>();
-    auto hybridData = detail::HybridData::javaClassStatic()->newObject(dataCtor);
-    detail::setNativePointer(hybridData, std::move(cxxPart));
+  static local_ref<detail::HybridData> makeHybridData(std::unique_ptr<T> cxxPart) {
+    auto hybridData = detail::HybridData::create();
+    hybridData->setNativePointer(std::move(cxxPart));
     return hybridData;
   }
 
   template <typename... Args>
-  static local_ref<jhybriddata> makeCxxInstance(Args&&... args) {
+  static local_ref<detail::HybridData> makeCxxInstance(Args&&... args) {
     return makeHybridData(std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
   }
 
@@ -219,31 +157,27 @@ class HybridClass : public Base
   // Exception behavior: This can throw an exception if creating the
   // C++ object fails, or any JNI methods throw.
   template <typename... Args>
-  static local_ref<jhybridobject> newObjectCxxArgs(Args&&... args) {
+  static local_ref<JavaPart> newObjectCxxArgs(Args&&... args) {
     auto hybridData = makeCxxInstance(std::forward<Args>(args)...);
-    static auto ctor = javaClassStatic()->template getConstructor<jhybridobject(jhybriddata)>();
-    return javaClassStatic()->newObject(ctor, hybridData.get());
+    return JavaPart::newInstance(hybridData);
+  }
+
+  // TODO? Create reusable interface for Allocatable classes and use it to
+  // strengthen type-checking (and possibly provide a default
+  // implementation of allocate().)
+  template <typename... Args>
+  static local_ref<jhybridobject> allocateWithCxxArgs(Args&&... args) {
+    auto hybridData = makeCxxInstance(std::forward<Args>(args)...);
+    static auto allocateMethod =
+        javaClassStatic()->template getStaticMethod<jhybridobject(jhybriddata)>("allocate");
+    return allocateMethod(javaClassStatic(), hybridData.get());
   }
 
   // Factory method for creating a hybrid object where the arguments
   // are passed to the java ctor.
   template <typename... Args>
-  static local_ref<jhybridobject> newObjectJavaArgs(Args&&... args) {
-    static auto ctor =
-      javaClassStatic()->template getConstructor<
-        jhybridobject(typename detail::Convert<typename std::decay<Args>::type>::jniType...)>();
-    // This can't use the same impl as Convert::toJniRet because that
-    // function sometimes creates and then releases local_refs, which
-    // could potentially cause the locals table to fill.  Instead, we
-    // use two calls, one which can return a local_ref if needed, and
-    // a second which extracts its value.  The lifetime of the
-    // local_ref is the expression, after which it is destroyed and
-    // the local_ref is cleaned up.
-    auto lref =
-      javaClassStatic()->newObject(
-        ctor, detail::callToJni(
-          detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
-    return lref;
+  static local_ref<JavaPart> newObjectJavaArgs(Args&&... args) {
+    return JavaPart::newInstance(std::move(args)...);
   }
 
   // If a hybrid class throws an exception which derives from
@@ -256,19 +190,38 @@ class HybridClass : public Base
   static void mapException(const std::exception& ex) {}
 };
 
-// Given a *_ref object which refers to a hybrid class, this will reach inside
-// of it, find the mHybridData, extract the C++ instance pointer, cast it to
-// the appropriate type, and return it.
-template <typename T>
-inline typename std::remove_pointer<typename T::PlainJniType>::type::javaClass* cthis(T jthis) {
-  static auto dataField =
-    jthis->getClass()->template getField<detail::HybridData::javaobject>("mHybridData");
+template <typename T, typename B>
+inline T* HybridClass<T, B>::JavaPart::cthis() {
+  static auto field =
+    HybridClass<T, B>::JavaPart::javaClassStatic()->template getField<detail::HybridData::javaobject>("mHybridData");
+  auto hybridData = this->getFieldValue(field);
+  if (!hybridData) {
+    throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException");
+  }
   // I'd like to use dynamic_cast here, but -fno-rtti is the default.
-  auto* value = static_cast<typename std::remove_pointer<typename T::PlainJniType>::type::javaClass*>(
-    detail::getNativePointer(detail::getHybridData(jthis, dataField)));
+  T* value = static_cast<T*>(hybridData->getNativePointer());
   // This would require some serious programmer error.
   FBASSERTMSGF(value != 0, "Incorrect C++ type in hybrid field");
   return value;
+};
+
+template <typename T, typename B>
+/* static */ inline std::string HybridClass<T, B>::JavaPart::get_instantiated_java_descriptor() {
+  return T::kJavaDescriptor;
+}
+
+template <typename T, typename B>
+/* static */ inline std::string HybridClass<T, B>::JavaPart::get_instantiated_base_name() {
+  auto name = get_instantiated_java_descriptor();
+  return name.substr(1, name.size() - 2);
+}
+
+// Given a *_ref object which refers to a hybrid class, this will reach inside
+// of it, find the mHybridData, extract the C++ instance pointer, cast it to
+// the appropriate type, and return it.
+template <typename T>
+inline auto cthis(T jthis) -> decltype(jthis->cthis()) {
+  return jthis->cthis();
 }
 
 void HybridDataOnLoad();
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator-inl.h
new file mode 100644
index 000000000000..206d2b4b17a5
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator-inl.h
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+template <typename E>
+struct IteratorHelper : public JavaClass<IteratorHelper<E>> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/IteratorHelper;";
+
+  typedef local_ref<E> value_type;
+  typedef ptrdiff_t difference_type;
+  typedef value_type* pointer;
+  typedef value_type& reference;
+  typedef std::forward_iterator_tag iterator_category;
+
+  typedef JavaClass<IteratorHelper<E>> JavaBase_;
+
+  bool hasNext() const {
+    static auto hasNextMethod =
+      JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
+    return hasNextMethod(JavaBase_::self());
+  }
+
+  value_type next() {
+    static auto elementField =
+      JavaBase_::javaClassStatic()->template getField<jobject>("mElement");
+    return dynamic_ref_cast<E>(JavaBase_::getFieldValue(elementField));
+  }
+
+  static void reset(value_type& v) {
+    v.reset();
+  }
+};
+
+template <typename K, typename V>
+struct MapIteratorHelper : public JavaClass<MapIteratorHelper<K,V>> {
+  constexpr static auto kJavaDescriptor = "Lcom/facebook/jni/MapIteratorHelper;";
+
+  typedef std::pair<local_ref<K>, local_ref<V>> value_type;
+
+  typedef JavaClass<MapIteratorHelper<K,V>> JavaBase_;
+
+  bool hasNext() const {
+    static auto hasNextMethod =
+      JavaBase_::javaClassStatic()->template getMethod<jboolean()>("hasNext");
+    return hasNextMethod(JavaBase_::self());
+  }
+
+  value_type next() {
+    static auto keyField = JavaBase_::javaClassStatic()->template getField<jobject>("mKey");
+    static auto valueField = JavaBase_::javaClassStatic()->template getField<jobject>("mValue");
+    return std::make_pair(dynamic_ref_cast<K>(JavaBase_::getFieldValue(keyField)),
+                          dynamic_ref_cast<V>(JavaBase_::getFieldValue(valueField)));
+  }
+
+  static void reset(value_type& v) {
+    v.first.reset();
+    v.second.reset();
+  }
+};
+
+template <typename T>
+class Iterator {
+ public:
+  typedef typename T::value_type value_type;
+  typedef ptrdiff_t difference_type;
+  typedef value_type* pointer;
+  typedef value_type& reference;
+  typedef std::input_iterator_tag iterator_category;
+
+  // begin ctor
+  Iterator(global_ref<typename T::javaobject>&& helper)
+      : helper_(std::move(helper))
+      , i_(-1) {
+    ++(*this);
+  }
+
+  // end ctor
+  Iterator()
+      : i_(-1) {}
+
+  bool operator==(const Iterator& it) const { return i_ == it.i_; }
+  bool operator!=(const Iterator& it) const { return !(*this == it); }
+  const value_type& operator*() const { assert(i_ != -1); return entry_; }
+  const value_type* operator->() const { assert(i_ != -1); return &entry_; }
+  Iterator& operator++() {  // preincrement
+    bool hasNext = helper_->hasNext();
+    if (hasNext) {
+      ++i_;
+      entry_ = helper_->next();
+    } else {
+      i_ = -1;
+      helper_->reset(entry_);
+    }
+    return *this;
+  }
+  Iterator operator++(int) {  // postincrement
+    Iterator ret;
+    ret.i_ = i_;
+    ret.entry_ = std::move(entry_);
+    ++(*this);
+    return ret;
+  }
+
+  global_ref<typename T::javaobject> helper_;
+  // set to -1 at end
+  std::ptrdiff_t i_;
+  value_type entry_;
+};
+
+}
+
+template <typename E>
+struct JIterator<E>::Iterator : public detail::Iterator<detail::IteratorHelper<E>> {
+  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
+};
+
+template <typename E>
+typename JIterator<E>::Iterator JIterator<E>::begin() const {
+  static auto ctor = detail::IteratorHelper<E>::javaClassStatic()->
+    template getConstructor<typename detail::IteratorHelper<E>::javaobject(
+                              typename JIterator<E>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::IteratorHelper<E>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename E>
+typename JIterator<E>::Iterator JIterator<E>::end() const {
+  return Iterator();
+}
+
+template <typename E>
+struct JIterable<E>::Iterator : public detail::Iterator<detail::IteratorHelper<E>> {
+  using detail::Iterator<detail::IteratorHelper<E>>::Iterator;
+};
+
+template <typename E>
+typename JIterable<E>::Iterator JIterable<E>::begin() const {
+  static auto ctor = detail::IteratorHelper<E>::javaClassStatic()->
+    template getConstructor<typename detail::IteratorHelper<E>::javaobject(
+                              typename JIterable<E>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::IteratorHelper<E>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename E>
+typename JIterable<E>::Iterator JIterable<E>::end() const {
+  return Iterator();
+}
+
+template <typename E>
+size_t JCollection<E>::size() const {
+  static auto sizeMethod =
+    JCollection<E>::javaClassStatic()->template getMethod<jint()>("size");
+  return sizeMethod(this->self());
+}
+
+template <typename K, typename V>
+struct JMap<K,V>::Iterator : public detail::Iterator<detail::MapIteratorHelper<K,V>> {
+  using detail::Iterator<detail::MapIteratorHelper<K,V>>::Iterator;
+};
+
+template <typename K, typename V>
+size_t JMap<K,V>::size() const {
+  static auto sizeMethod =
+    JMap<K,V>::javaClassStatic()->template getMethod<jint()>("size");
+  return sizeMethod(this->self());
+}
+
+template <typename K, typename V>
+typename JMap<K,V>::Iterator JMap<K,V>::begin() const {
+  static auto ctor = detail::MapIteratorHelper<K,V>::javaClassStatic()->
+    template getConstructor<typename detail::MapIteratorHelper<K,V>::javaobject(
+                              typename JMap<K,V>::javaobject)>();
+  return Iterator(
+    make_global(
+      detail::MapIteratorHelper<K,V>::javaClassStatic()->newObject(ctor, this->self())));
+}
+
+template <typename K, typename V>
+typename JMap<K,V>::Iterator JMap<K,V>::end() const {
+  return Iterator();
+}
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator.h
new file mode 100644
index 000000000000..aa3652666e7d
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Iterator.h
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "CoreClasses.h"
+
+namespace facebook {
+namespace jni {
+
+/**
+ * JavaClass which represents a reference to a java.util.Iterator instance.  It
+ * provides begin()/end() methods to provide C++-style iteration over the
+ * underlying collection.  The class has a template parameter for the element
+ * type, which defaults to jobject.  For example:
+ *
+ * alias_ref<JIterator<jstring>::javaobject> my_iter = ...;
+ *
+ * In the simplest case, it can be used just as alias_ref<JIterator<>::javaobject>,
+ * for example in a method declaration.
+ */
+template <typename E = jobject>
+struct JIterator : JavaClass<JIterator<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Iterator;";
+
+  struct Iterator;
+
+  /**
+   * To iterate:
+   *
+   * for (const auto& element : *jiter) { ... }
+   *
+   * The JIterator iterator value_type is local_ref<E>, containing a reference
+   * to an element instance.
+   *
+   * If the Iterator returns objects whch are not convertible to the given
+   * element type, iteration will throw a java ClassCastException.
+   *
+   * For example, to convert an iterator over a collection of java strings to
+   * an std::vector of std::strings:
+   *
+   * std::vector<std::string> vs;
+   * for (const auto& elem : *jiter) {
+   *    vs.push_back(elem->toStdString());
+   * }
+   *
+   * Or if you prefer using std algorithms:
+   *
+   * std::vector<std::string> vs;
+   * std::transform(jiter->begin(), jiter->end(), std::back_inserter(vs),
+   *                [](const local_ref<jstring>& elem) { return elem->toStdString(); });
+   *
+   * The iterator is a InputIterator.
+   */
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+/**
+ * Similar to JIterator, except this represents any object which implements the
+ * java.lang.Iterable interface. It will create the Java Iterator as a part of
+ * begin().
+ */
+template <typename E = jobject>
+struct JIterable : JavaClass<JIterable<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/lang/Iterable;";
+
+  struct Iterator;
+
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+/**
+ * JavaClass types which represent Collection, List, and Set are also provided.
+ * These preserve the Java class heirarchy.
+ */
+template <typename E = jobject>
+struct JCollection : JavaClass<JCollection<E>, JIterable<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Collection;";
+
+  /**
+   * Returns the number of elements in the collection.
+   */
+  size_t size() const;
+};
+
+template <typename E = jobject>
+struct JList : JavaClass<JList<E>, JCollection<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/List;";
+};
+
+template <typename E = jobject>
+struct JSet : JavaClass<JSet<E>, JCollection<E>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Set;";
+};
+
+/**
+ * JavaClass which represents a reference to a java.util.Map instance.  It adds
+ * wrappers around Java methods, including begin()/end() methods to provide
+ * C++-style iteration over the Java Map.  The class has template parameters
+ * for the key and value types, which default to jobject.  For example:
+ *
+ * alias_ref<JMap<jstring, MyJClass::javaobject>::javaobject> my_map = ...;
+ *
+ * In the simplest case, it can be used just as alias_ref<JMap<>::javaobject>,
+ * for example in a method declaration.
+ */
+template <typename K = jobject, typename V = jobject>
+struct JMap : JavaClass<JMap<K,V>> {
+  constexpr static auto kJavaDescriptor = "Ljava/util/Map;";
+
+  struct Iterator;
+
+  /**
+   * Returns the number of pairs in the map.
+   */
+  size_t size() const;
+
+  /**
+   * To iterate over the Map:
+   *
+   * for (const auto& entry : *jmap) { ... }
+   *
+   * The JMap iterator value_type is std::pair<local_ref<K>, local_ref<V>>
+   * containing references to key and value instances.
+   *
+   * If the Map contains objects whch are not convertible to the given key and
+   * value types, iteration will throw a java ClassCastException.
+   *
+   * The iterator is a InputIterator.
+   */
+  Iterator begin() const;
+  Iterator end() const;
+};
+
+}
+}
+
+#include "Iterator-inl.h"
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-forward.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-forward.h
new file mode 100644
index 000000000000..2f524ad062af
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-forward.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace facebook {
+namespace jni {
+
+template<typename F>
+class JMethod;
+template<typename F>
+class JStaticMethod;
+template<typename F>
+class JNonvirtualMethod;
+template<typename F>
+class JConstructor;
+template<typename F>
+class JField;
+template<typename F>
+class JStaticField;
+
+/// Type traits for Java types (currently providing Java type descriptors)
+template<typename T>
+struct jtype_traits;
+
+/// Type traits for Java methods (currently providing Java type descriptors)
+template<typename F>
+struct jmethod_traits;
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-inl.h
index d51157b16072..38fabc500e35 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-inl.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta-inl.h
@@ -13,6 +13,8 @@
 
 #include "Common.h"
 #include "Exceptions.h"
+#include "MetaConvert.h"
+#include "References.h"
 
 namespace facebook {
 namespace jni {
@@ -34,19 +36,25 @@ inline jmethodID JMethodBase::getId() const noexcept {
 template<typename... Args>
 inline void JMethod<void(Args...)>::operator()(alias_ref<jobject> self, Args... args) {
   const auto env = internal::getEnv();
-        env->CallVoidMethod(self.get(), getId(), args...);
+  env->CallVoidMethod(
+        self.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
   FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
 }
 
 #pragma push_macro("DEFINE_PRIMITIVE_CALL")
 #undef DEFINE_PRIMITIVE_CALL
-#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD)                                                 \
-template<typename... Args>                                                                  \
-inline TYPE JMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, Args... args) {     \
-  const auto env = internal::getEnv();                                                      \
-  auto result = env->Call ## METHOD ## Method(self.get(), getId(), args...);                \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                   \
-  return result;                                                                            \
+#define DEFINE_PRIMITIVE_CALL(TYPE, METHOD)                                                    \
+template<typename... Args>                                                                     \
+inline TYPE JMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, Args... args) {        \
+  const auto env = internal::getEnv();                                                         \
+  auto result = env->Call ## METHOD ## Method(                                                 \
+        self.get(),                                                                            \
+        getId(),                                                                               \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...); \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                      \
+  return result;                                                                               \
 }
 
 DEFINE_PRIMITIVE_CALL(jboolean, Boolean)
@@ -59,30 +67,54 @@ DEFINE_PRIMITIVE_CALL(jfloat, Float)
 DEFINE_PRIMITIVE_CALL(jdouble, Double)
 #pragma pop_macro("DEFINE_PRIMITIVE_CALL")
 
-template<typename T, typename... Args>
-inline local_ref<T*> JMethod<T*(Args...)>::operator()(alias_ref<jobject> self, Args... args) {
-  const auto env = internal::getEnv();
-        auto result = env->CallObjectMethod(self.get(), getId(), args...);
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
-        return adopt_local(static_cast<T*>(result));
-}
+
+/// JMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JMethod<R(Args...)> : public JMethodBase {
+ public:
+   // TODO: static_assert is jobject-derived or local_ref jobject
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "JniRet must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JMethod() noexcept {};
+  JMethod(const JMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jobject> self, Args... args) {
+    const auto env = internal::getEnv();
+    auto result = env->CallObjectMethod(
+        self.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    return adopt_local(static_cast<JniRet>(result));
+  }
+
+  friend class JClass;
+};
 
 template<typename... Args>
 inline void JStaticMethod<void(Args...)>::operator()(alias_ref<jclass> cls, Args... args) {
   const auto env = internal::getEnv();
-  env->CallStaticVoidMethod(cls.get(), getId(), args...);
+  env->CallStaticVoidMethod(
+        cls.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
   FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
 }
 
 #pragma push_macro("DEFINE_PRIMITIVE_STATIC_CALL")
 #undef DEFINE_PRIMITIVE_STATIC_CALL
-#define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD)                                          \
-template<typename... Args>                                                                  \
-inline TYPE JStaticMethod<TYPE(Args...)>::operator()(alias_ref<jclass> cls, Args... args) { \
-  const auto env = internal::getEnv();                                                      \
-  auto result = env->CallStatic ## METHOD ## Method(cls.get(), getId(), args...);           \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                   \
-        return result;                                                                      \
+#define DEFINE_PRIMITIVE_STATIC_CALL(TYPE, METHOD)                                             \
+template<typename... Args>                                                                     \
+inline TYPE JStaticMethod<TYPE(Args...)>::operator()(alias_ref<jclass> cls, Args... args) {    \
+  const auto env = internal::getEnv();                                                         \
+  auto result = env->CallStatic ## METHOD ## Method(                                           \
+        cls.get(),                                                                             \
+        getId(),                                                                               \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...); \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                      \
+        return result;                                                                         \
 }
 
 DEFINE_PRIMITIVE_STATIC_CALL(jboolean, Boolean)
@@ -95,33 +127,57 @@ DEFINE_PRIMITIVE_STATIC_CALL(jfloat, Float)
 DEFINE_PRIMITIVE_STATIC_CALL(jdouble, Double)
 #pragma pop_macro("DEFINE_PRIMITIVE_STATIC_CALL")
 
-template<typename T, typename... Args>
-inline local_ref<T*> JStaticMethod<T*(Args...)>::operator()(alias_ref<jclass> cls, Args... args) {
-  const auto env = internal::getEnv();
-  auto result = env->CallStaticObjectMethod(cls.get(), getId(), args...);
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
-  return adopt_local(static_cast<T*>(result));
-}
+/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JStaticMethod<R(Args...)> : public JMethodBase {
+
+ public:
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "T* must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JStaticMethod() noexcept {};
+  JStaticMethod(const JStaticMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jclass> cls, Args... args) {
+    const auto env = internal::getEnv();
+    auto result = env->CallStaticObjectMethod(
+          cls.get(),
+          getId(),
+          detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    return adopt_local(static_cast<JniRet>(result));
+  }
 
+  friend class JClass;
+};
 
 template<typename... Args>
 inline void
-JNonvirtualMethod<void(Args...)>::operator()(alias_ref<jobject> self, jclass cls, Args... args) {
+JNonvirtualMethod<void(Args...)>::operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args) {
   const auto env = internal::getEnv();
-  env->CallNonvirtualVoidMethod(self.get(), cls, getId(), args...);
+  env->CallNonvirtualVoidMethod(
+        self.get(),
+        cls.get(),
+        getId(),
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
   FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
 }
 
 #pragma push_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL")
 #undef DEFINE_PRIMITIVE_NON_VIRTUAL_CALL
-#define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD)                                           \
-template<typename... Args>                                                                        \
-inline TYPE                                                                                       \
-JNonvirtualMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, jclass cls, Args... args) { \
-  const auto env = internal::getEnv();                                                            \
-  auto result = env->CallNonvirtual ## METHOD ## Method(self.get(), cls, getId(), args...);       \
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                         \
-  return result;                                                                                  \
+#define DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(TYPE, METHOD)                                                      \
+template<typename... Args>                                                                                   \
+inline TYPE                                                                                                  \
+JNonvirtualMethod<TYPE(Args...)>::operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args) { \
+  const auto env = internal::getEnv();                                                                       \
+  auto result = env->CallNonvirtual ## METHOD ## Method(                                                     \
+        self.get(),                                                                                          \
+        cls.get(),                                                                                           \
+        getId(),                                                                                             \
+        detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);               \
+  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();                                                                    \
+  return result;                                                                                             \
 }
 
 DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jboolean, Boolean)
@@ -134,77 +190,31 @@ DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jfloat, Float)
 DEFINE_PRIMITIVE_NON_VIRTUAL_CALL(jdouble, Double)
 #pragma pop_macro("DEFINE_PRIMITIVE_NON_VIRTUAL_CALL")
 
-template<typename T, typename... Args>
-inline local_ref<T*> JNonvirtualMethod<T*(Args...)>::operator()(
-    alias_ref<jobject> self,
-    jclass cls,
-    Args... args) {
-  const auto env = internal::getEnv();
-  auto result = env->CallNonvirtualObjectMethod(self.get(), cls, getId(), args...);
-  FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
-  return adopt_local(static_cast<T*>(result));
-}
-
-
-// jtype_traits ////////////////////////////////////////////////////////////////////////////////////
-
-/// The generic way to associate a descriptor to a type is to look it up in the
-/// corresponding @ref JObjectWrapper specialization. This makes it easy to add
-/// support for your user defined type.
-template<typename T>
-struct jtype_traits {
-  // The jni type signature (described at
-  // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html).
-  static std::string descriptor() {
-    static const auto descriptor = JObjectWrapper<T>::kJavaDescriptor != nullptr ?
-      std::string{JObjectWrapper<T>::kJavaDescriptor} :
-      JObjectWrapper<T>::get_instantiated_java_descriptor();
-    return descriptor;
-  }
-
-  // The signature used for class lookups. See
-  // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName().
-  static std::string base_name() {
-    if (JObjectWrapper<T>::kJavaDescriptor != nullptr) {
-      std::string base_name = JObjectWrapper<T>::kJavaDescriptor;
-      return base_name.substr(1, base_name.size() - 2);
-    }
-    return JObjectWrapper<T>::get_instantiated_java_descriptor();
+/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref
+template<typename R, typename... Args>
+class JNonvirtualMethod<R(Args...)> : public JMethodBase {
+ public:
+  using JniRet = typename detail::Convert<typename std::decay<R>::type>::jniType;
+  static_assert(IsPlainJniReference<JniRet>(), "T* must be a JNI reference");
+  using JMethodBase::JMethodBase;
+  JNonvirtualMethod() noexcept {};
+  JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default;
+
+  /// Invoke a method and return a local reference wrapping the result
+  local_ref<JniRet> operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args){
+    const auto env = internal::getEnv();
+    auto result = env->CallNonvirtualObjectMethod(
+          self.get(),
+          cls.get(),
+          getId(),
+          detail::callToJni(detail::Convert<typename std::decay<Args>::type>::toCall(args))...);
+    FACEBOOK_JNI_THROW_PENDING_EXCEPTION();
+    return adopt_local(static_cast<JniRet>(result));
   }
-};
 
-#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
-#undef DEFINE_FIELD_AND_ARRAY_TRAIT
-
-#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC)                     \
-template<>                                                          \
-struct jtype_traits<TYPE> {                                         \
-  static std::string descriptor() { return std::string{#DSC}; }     \
-  static std::string base_name() { return descriptor(); }           \
-};                                                                  \
-template<>                                                          \
-struct jtype_traits<TYPE ## Array> {                                \
-  static std::string descriptor() { return std::string{"[" #DSC}; } \
-  static std::string base_name() { return descriptor(); }           \
+  friend class JClass;
 };
 
-// There is no voidArray, handle that without the macro.
-template<>
-struct jtype_traits<void> {
-  static std::string descriptor() { return std::string{"V"}; };
-};
-
-DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte,    B)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jchar,    C)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jshort,   S)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jint,     I)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jlong,    J)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat,   F)
-DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble,  D)
-
-#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
-
 
 // JField<T> ///////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta.h
index de1bde0d542b..23c7450506bb 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Meta.h
@@ -19,11 +19,25 @@
 
 #include <jni.h>
 
-#include "References.h"
+#include "References-forward.h"
+
+#ifdef __ANDROID__
+# include <android/log.h>
+# define XLOG_TAG "fb-jni"
+# define XLOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, XLOG_TAG, __VA_ARGS__)
+# define XLOGD(...) __android_log_print(ANDROID_LOG_DEBUG, XLOG_TAG, __VA_ARGS__)
+# define XLOGI(...) __android_log_print(ANDROID_LOG_INFO, XLOG_TAG, __VA_ARGS__)
+# define XLOGW(...) __android_log_print(ANDROID_LOG_WARN, XLOG_TAG, __VA_ARGS__)
+# define XLOGE(...) __android_log_print(ANDROID_LOG_ERROR, XLOG_TAG, __VA_ARGS__)
+# define XLOGWTF(...) __android_log_print(ANDROID_LOG_FATAL, XLOG_TAG, __VA_ARGS__)
+#endif
 
 namespace facebook {
 namespace jni {
 
+class JObject;
+
+
 /// Wrapper of a jmethodID. Provides a common base for JMethod specializations
 class JMethodBase {
  public:
@@ -65,7 +79,7 @@ class JMethod<TYPE(Args...)> : public JMethodBase {
                                                                                  \
   TYPE operator()(alias_ref<jobject> self, Args... args);                        \
                                                                                  \
-  friend class JObjectWrapper<jclass>;                                           \
+  friend class JClass;                                                           \
 }
 
 DEFINE_PRIMITIVE_METHOD_CLASS(void);
@@ -82,26 +96,15 @@ DEFINE_PRIMITIVE_METHOD_CLASS(jdouble);
 /// @endcond
 
 
-/// JMethod specialization for references that wraps the return value in a @ref local_ref
-template<typename T, typename... Args>
-class JMethod<T*(Args...)> : public JMethodBase {
- public:
-  static_assert(IsPlainJniReference<T*>(), "T* must be a JNI reference");
-
-  using JMethodBase::JMethodBase;
-  JMethod() noexcept {};
-  JMethod(const JMethod& other) noexcept = default;
-
-  /// Invoke a method and return a local reference wrapping the result
-  local_ref<T*> operator()(alias_ref<jobject> self, Args... args);
-
-  friend class JObjectWrapper<jclass>;
-};
-
-
 /// Convenience type representing constructors
+/// These should only be used with JClass::getConstructor and JClass::newObject.
 template<typename F>
-using JConstructor = JMethod<F>;
+struct JConstructor : private JMethod<F> {
+  using JMethod<F>::JMethod;
+ private:
+  JConstructor(const JMethod<F>& other) : JMethod<F>(other.getId()) {}
+  friend class JClass;
+};
 
 /// Representation of a jStaticMethodID
 template<typename F>
@@ -126,7 +129,7 @@ class JStaticMethod<TYPE(Args...)> : public JMethodBase {                   \
                                                                             \
   TYPE operator()(alias_ref<jclass> cls, Args... args);                     \
                                                                             \
-  friend class JObjectWrapper<jclass>;                                      \
+  friend class JClass;                                                      \
 }
 
 DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(void);
@@ -143,22 +146,6 @@ DEFINE_PRIMITIVE_STATIC_METHOD_CLASS(jdouble);
 /// @endcond
 
 
-/// JStaticMethod specialization for references that wraps the return value in a @ref local_ref
-template<typename T, typename... Args>
-class JStaticMethod<T*(Args...)> : public JMethodBase {
-  static_assert(IsPlainJniReference<T*>(), "T* must be a JNI reference");
-
- public:
-  using JMethodBase::JMethodBase;
-  JStaticMethod() noexcept {};
-  JStaticMethod(const JStaticMethod& other) noexcept = default;
-
-  /// Invoke a method and return a local reference wrapping the result
-  local_ref<T*> operator()(alias_ref<jclass> cls, Args... args);
-
-  friend class JObjectWrapper<jclass>;
-};
-
 /// Representation of a jNonvirtualMethodID
 template<typename F>
 class JNonvirtualMethod;
@@ -180,9 +167,9 @@ class JNonvirtualMethod<TYPE(Args...)> : public JMethodBase {               \
   JNonvirtualMethod() noexcept {};                                          \
   JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default;     \
                                                                             \
-  TYPE operator()(alias_ref<jobject> self, jclass cls, Args... args);       \
+  TYPE operator()(alias_ref<jobject> self, alias_ref<jclass> cls, Args... args);       \
                                                                             \
-  friend class JObjectWrapper<jclass>;                                      \
+  friend class JClass;                                                      \
 }
 
 DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(void);
@@ -199,23 +186,6 @@ DEFINE_PRIMITIVE_NON_VIRTUAL_METHOD_CLASS(jdouble);
 /// @endcond
 
 
-/// JNonvirtualMethod specialization for references that wraps the return value in a @ref local_ref
-template<typename T, typename... Args>
-class JNonvirtualMethod<T*(Args...)> : public JMethodBase {
-  static_assert(IsPlainJniReference<T*>(), "T* must be a JNI reference");
-
- public:
-  using JMethodBase::JMethodBase;
-  JNonvirtualMethod() noexcept {};
-  JNonvirtualMethod(const JNonvirtualMethod& other) noexcept = default;
-
-  /// Invoke a method and return a local reference wrapping the result
-  local_ref<T*> operator()(alias_ref<jobject> self, jclass cls, Args... args);
-
-  friend class JObjectWrapper<jclass>;
-};
-
-
 /**
  * JField represents typed fields and simplifies their access. Note that object types return
  * raw pointers which generally should promptly get a wrap_local treatment.
@@ -245,7 +215,7 @@ class JField {
   /// @pre object != nullptr
   void set(jobject object, T value) noexcept;
 
-  friend class JObjectWrapper<jobject>;
+  friend class JObject;
 };
 
 
@@ -278,20 +248,11 @@ class JStaticField {
   /// @pre object != nullptr
   void set(jclass jcls, T value) noexcept;
 
-  friend class JObjectWrapper<jclass>;
-
+  friend class JClass;
+  friend class JObject;
 };
 
 
-/// Type traits for Java types (currently providing Java type descriptors)
-template<typename T>
-struct jtype_traits;
-
-
-/// Type traits for Java methods (currently providing Java type descriptors)
-template<typename F>
-struct jmethod_traits;
-
 /// Template magic to provide @ref jmethod_traits
 template<typename R, typename... Args>
 struct jmethod_traits<R(Args...)> {
@@ -299,4 +260,75 @@ struct jmethod_traits<R(Args...)> {
   static std::string constructor_descriptor();
 };
 
+
+// jtype_traits ////////////////////////////////////////////////////////////////////////////////////
+
+template<typename T>
+struct jtype_traits {
+private:
+  using Repr = ReprType<T>;
+public:
+  // The jni type signature (described at
+  // http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html).
+  static std::string descriptor() {
+    std::string descriptor;
+    if (Repr::kJavaDescriptor == nullptr) {
+      descriptor = Repr::get_instantiated_java_descriptor();
+    } else {
+      descriptor = Repr::kJavaDescriptor;
+    }
+    return descriptor;
+  }
+
+  // The signature used for class lookups. See
+  // http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#getName().
+  static std::string base_name() {
+    if (Repr::kJavaDescriptor != nullptr) {
+      std::string base_name = Repr::kJavaDescriptor;
+      return base_name.substr(1, base_name.size() - 2);
+    }
+    return Repr::get_instantiated_base_name();
+  }
+};
+
+#pragma push_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
+#undef DEFINE_FIELD_AND_ARRAY_TRAIT
+
+#define DEFINE_FIELD_AND_ARRAY_TRAIT(TYPE, DSC)                     \
+template<>                                                          \
+struct jtype_traits<TYPE> {                                         \
+  static std::string descriptor() { return std::string{#DSC}; }     \
+  static std::string base_name() { return descriptor(); }           \
+  using array_type = TYPE ## Array;                                 \
+};                                                                  \
+template<>                                                          \
+struct jtype_traits<TYPE ## Array> {                                \
+  static std::string descriptor() { return std::string{"[" #DSC}; } \
+  static std::string base_name() { return descriptor(); }           \
+  using entry_type = TYPE;                                          \
+};
+
+// There is no voidArray, handle that without the macro.
+template<>
+struct jtype_traits<void> {
+  static std::string descriptor() { return std::string{"V"}; };
+};
+
+DEFINE_FIELD_AND_ARRAY_TRAIT(jboolean, Z)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jbyte,    B)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jchar,    C)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jshort,   S)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jint,     I)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jlong,    J)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jfloat,   F)
+DEFINE_FIELD_AND_ARRAY_TRAIT(jdouble,  D)
+
+#pragma pop_macro("DEFINE_FIELD_AND_ARRAY_TRAIT")
+
+
+template <typename T>
+struct jmethod_traits_from_cxx;
+
 }}
+
+#include "Meta-inl.h"
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/MetaConvert.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/MetaConvert.h
new file mode 100644
index 000000000000..33027c7e96b3
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/MetaConvert.h
@@ -0,0 +1,122 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <jni.h>
+
+#include "Common.h"
+#include "References.h"
+
+namespace facebook {
+namespace jni {
+
+namespace detail {
+
+// In order to avoid potentially filling the jni locals table,
+// temporary objects (right now, this is just jstrings) need to be
+// released. This is done by returning a holder which autoconverts to
+// jstring.
+template <typename T>
+inline T callToJni(T&& t) {
+  return t;
+}
+
+template <typename T>
+inline JniType<T> callToJni(local_ref<T>&& sref) {
+  return sref.get();
+}
+
+// Normally, pass through types unmolested.
+template <typename T, typename Enabled = void>
+struct Convert {
+  typedef T jniType;
+  static jniType fromJni(jniType t) {
+    return t;
+  }
+  static jniType toJniRet(jniType t) {
+    return t;
+  }
+  static jniType toCall(jniType t) {
+    return t;
+  }
+};
+
+// This is needed for return conversion
+template <>
+struct Convert<void> {
+  typedef void jniType;
+};
+
+// jboolean is an unsigned char, not a bool. Allow it to work either way.
+template<>
+struct Convert<bool> {
+  typedef jboolean jniType;
+  static bool fromJni(jniType t) {
+    return t;
+  }
+  static jniType toJniRet(bool t) {
+    return t;
+  }
+  static jniType toCall(bool t) {
+    return t;
+  }
+};
+
+// convert to alias_ref<T> from T
+template <typename T>
+struct Convert<alias_ref<T>> {
+  typedef JniType<T> jniType;
+  static alias_ref<jniType> fromJni(jniType t) {
+    return wrap_alias(t);
+  }
+  static jniType toJniRet(alias_ref<jniType> t) {
+    return t.get();
+  }
+  static jniType toCall(alias_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+// convert return from local_ref<T>
+template <typename T>
+struct Convert<local_ref<T>> {
+  typedef JniType<T> jniType;
+  // No automatic synthesis of local_ref
+  static jniType toJniRet(local_ref<jniType> t) {
+    return t.release();
+  }
+  static jniType toCall(local_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+// convert return from global_ref<T>
+template <typename T>
+struct Convert<global_ref<T>> {
+  typedef JniType<T> jniType;
+  // No automatic synthesis of global_ref
+  static jniType toJniRet(global_ref<jniType> t) {
+    return t.get();
+  }
+  static jniType toCall(global_ref<jniType> t) {
+    return t.get();
+  }
+};
+
+template <typename T> struct jni_sig_from_cxx_t;
+template <typename R, typename... Args>
+struct jni_sig_from_cxx_t<R(Args...)> {
+  using JniRet = typename Convert<typename std::decay<R>::type>::jniType;
+  using JniSig = JniRet(typename Convert<typename std::decay<Args>::type>::jniType...);
+};
+
+template <typename T>
+using jni_sig_from_cxx = typename jni_sig_from_cxx_t<T>::JniSig;
+
+} // namespace detail
+
+template <typename R, typename... Args>
+struct jmethod_traits_from_cxx<R(Args...)> : jmethod_traits<detail::jni_sig_from_cxx<R(Args...)>> {
+};
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h
index d60c90022723..5f69a3b41503 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/ReferenceAllocators-inl.h
@@ -14,7 +14,6 @@
 #include <atomic>
 
 #include "Exceptions.h"
-#include "References.h"
 
 namespace facebook {
 namespace jni {
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-forward.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-forward.h
new file mode 100644
index 000000000000..8dabf67cb62e
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-forward.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "ReferenceAllocators.h"
+
+namespace facebook {
+namespace jni {
+
+template<typename T, typename Enable = void>
+class JObjectWrapper;
+
+namespace detail {
+struct JObjectBase {
+  jobject get() const noexcept;
+  void set(jobject reference) noexcept;
+  jobject this_;
+};
+
+// RefReprType maps a type to the representation used by fbjni smart references.
+template <typename T, typename Enable = void>
+struct RefReprType;
+
+template <typename T>
+struct JavaObjectType;
+
+template <typename T>
+struct ReprAccess;
+}
+
+// Given T, either a jobject-like type or a JavaClass-derived type, ReprType<T>
+// is the corresponding JavaClass-derived type and JniType<T> is the
+// jobject-like type.
+template <typename T>
+using ReprType = typename detail::RefReprType<T>::type;
+
+template <typename T>
+using JniType = typename detail::JavaObjectType<T>::type;
+
+template<typename T, typename Alloc>
+class base_owned_ref;
+
+template<typename T, typename Alloc>
+class basic_strong_ref;
+
+template<typename T>
+class weak_ref;
+
+template<typename T>
+class alias_ref;
+
+/// A smart unique reference owning a local JNI reference
+template<typename T>
+using local_ref = basic_strong_ref<T, LocalReferenceAllocator>;
+
+/// A smart unique reference owning a global JNI reference
+template<typename T>
+using global_ref = basic_strong_ref<T, GlobalReferenceAllocator>;
+
+}} // namespace facebook::jni
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-inl.h
index bae3d5d636d1..2176d1e022ea 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-inl.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References-inl.h
@@ -16,48 +16,86 @@ namespace facebook {
 namespace jni {
 
 template<typename T>
-inline enable_if_t<IsPlainJniReference<T>(), local_ref<T>> adopt_local(T ref) noexcept {
-  return local_ref<T>{ref};
+inline enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref) {
+  return ref;
 }
 
 template<typename T>
-inline enable_if_t<IsPlainJniReference<T>(), global_ref<T>> adopt_global(T ref) noexcept {
-  return global_ref<T>{ref};
+inline JniType<T> getPlainJniReference(alias_ref<T> ref) {
+  return ref.get();
 }
 
-template<typename T>
-inline enable_if_t<IsPlainJniReference<T>(), weak_ref<T>> adopt_weak_global(T ref) noexcept {
-  return weak_ref<T>{ref};
+template<typename T, typename A>
+inline JniType<T> getPlainJniReference(const base_owned_ref<T, A>& ref) {
+  return ref.get();
 }
 
 
-template<typename T>
-inline enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept {
-  return alias_ref<T>(ref);
+namespace detail {
+template <typename Repr>
+struct ReprAccess {
+  using javaobject = JniType<Repr>;
+  static void set(Repr& repr, javaobject obj) noexcept {
+    repr.JObjectBase::set(obj);
+  }
+  static javaobject get(const Repr& repr) {
+    return static_cast<javaobject>(repr.JObject::get());
+  }
+};
+
+namespace {
+template <typename Repr>
+void StaticAssertValidRepr() noexcept {
+  static_assert(std::is_base_of<JObject, Repr>::value,
+      "A smart ref representation must be derived from JObject.");
+  static_assert(IsPlainJniReference<JniType<Repr>>(), "T must be a JNI reference");
+  static_assert(sizeof(Repr) == sizeof(JObjectBase), "");
+  static_assert(alignof(Repr) == alignof(JObjectBase), "");
+}
 }
 
+template <typename Repr>
+ReprStorage<Repr>::ReprStorage(JniType<Repr> obj) noexcept {
+  StaticAssertValidRepr<Repr>();
+  set(obj);
+}
 
-template<typename T>
-enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept;
+template <typename Repr>
+void ReprStorage<Repr>::set(JniType<Repr> obj) noexcept {
+  new (&storage_) Repr;
+  ReprAccess<Repr>::set(get(), obj);
+}
 
+template <typename Repr>
+Repr& ReprStorage<Repr>::get() noexcept {
+  return *reinterpret_cast<Repr*>(&storage_);
+}
 
-template<typename T>
-inline enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref) {
-  return ref;
+template <typename Repr>
+const Repr& ReprStorage<Repr>::get() const noexcept {
+  return *reinterpret_cast<const Repr*>(&storage_);
 }
 
-template<typename T>
-inline T getPlainJniReference(alias_ref<T> ref) {
-  return ref.get();
+template <typename Repr>
+JniType<Repr> ReprStorage<Repr>::jobj() const noexcept {
+  ReprAccess<Repr>::get(get());
+  return ReprAccess<Repr>::get(get());
 }
 
-template<typename T, typename A>
-inline T getPlainJniReference(const base_owned_ref<T, A>& ref) {
-  return ref.getPlainJniReference();
+template <typename Repr>
+void ReprStorage<Repr>::swap(ReprStorage& other) noexcept {
+  StaticAssertValidRepr<Repr>();
+  using std::swap;
+  swap(get(), other.get());
 }
 
+inline void JObjectBase::set(jobject reference) noexcept {
+  this_ = reference;
+}
 
-namespace internal {
+inline jobject JObjectBase::get() const noexcept {
+  return this_;
+}
 
 template<typename T, typename Alloc>
 enable_if_t<IsNonWeakReference<T>(), plain_jni_reference_t<T>> make_ref(const T& reference) {
@@ -77,24 +115,53 @@ enable_if_t<IsNonWeakReference<T>(), plain_jni_reference_t<T>> make_ref(const T&
   return static_cast<plain_jni_reference_t<T>>(ref);
 }
 
+} // namespace detail
+
+template<typename T>
+inline local_ref<T> adopt_local(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return local_ref<T>{ref};
+}
+
+template<typename T>
+inline global_ref<T> adopt_global(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return global_ref<T>{ref};
+}
+
+template<typename T>
+inline weak_ref<T> adopt_weak_global(T ref) noexcept {
+  static_assert(IsPlainJniReference<T>(), "T must be a plain jni reference");
+  return weak_ref<T>{ref};
+}
+
+
+template<typename T>
+inline enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept {
+  return alias_ref<T>(ref);
 }
 
+
+template<typename T>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept;
+
+
 template<typename T>
 enable_if_t<IsNonWeakReference<T>(), local_ref<plain_jni_reference_t<T>>>
 make_local(const T& ref) {
-  return adopt_local(internal::make_ref<T, LocalReferenceAllocator>(ref));
+  return adopt_local(detail::make_ref<T, LocalReferenceAllocator>(ref));
 }
 
 template<typename T>
 enable_if_t<IsNonWeakReference<T>(), global_ref<plain_jni_reference_t<T>>>
 make_global(const T& ref) {
-  return adopt_global(internal::make_ref<T, GlobalReferenceAllocator>(ref));
+  return adopt_global(detail::make_ref<T, GlobalReferenceAllocator>(ref));
 }
 
 template<typename T>
 enable_if_t<IsNonWeakReference<T>(), weak_ref<plain_jni_reference_t<T>>>
 make_weak(const T& ref) {
-  return adopt_weak_global(internal::make_ref<T, WeakGlobalReferenceAllocator>(ref));
+  return adopt_weak_global(detail::make_ref<T, WeakGlobalReferenceAllocator>(ref));
 }
 
 template<typename T1, typename T2>
@@ -113,69 +180,67 @@ operator!=(const T1& a, const T2& b) {
 // base_owned_ref ///////////////////////////////////////////////////////////////////////
 
 template<typename T, typename Alloc>
-inline constexpr base_owned_ref<T, Alloc>::base_owned_ref() noexcept
-  : object_{nullptr}
+inline base_owned_ref<T, Alloc>::base_owned_ref() noexcept
+  : base_owned_ref(nullptr)
 {}
 
 template<typename T, typename Alloc>
-inline constexpr base_owned_ref<T, Alloc>::base_owned_ref(
-    std::nullptr_t t) noexcept
-  : object_{nullptr}
+inline base_owned_ref<T, Alloc>::base_owned_ref(std::nullptr_t t) noexcept
+  : base_owned_ref(static_cast<javaobject>(nullptr))
 {}
 
 template<typename T, typename Alloc>
-inline base_owned_ref<T, Alloc>::base_owned_ref(
-    const base_owned_ref& other)
-  : object_{Alloc{}.newReference(other.getPlainJniReference())}
+inline base_owned_ref<T, Alloc>::base_owned_ref(const base_owned_ref& other)
+  : storage_{static_cast<javaobject>(Alloc{}.newReference(other.get()))}
 {}
 
 template<typename T, typename Alloc>
 template<typename U>
 inline base_owned_ref<T, Alloc>::base_owned_ref(const base_owned_ref<U, Alloc>& other)
-  : object_{Alloc{}.newReference(other.getPlainJniReference())}
+  : storage_{static_cast<javaobject>(Alloc{}.newReference(other.get()))}
 {}
 
 template<typename T, typename Alloc>
 inline facebook::jni::base_owned_ref<T, Alloc>::base_owned_ref(
-    T reference) noexcept
-  : object_{reference} {
+    javaobject reference) noexcept
+  : storage_(reference) {
   assert(Alloc{}.verifyReference(reference));
-  internal::dbglog("New wrapped ref=%p this=%p", getPlainJniReference(), this);
+  internal::dbglog("New wrapped ref=%p this=%p", get(), this);
 }
 
 template<typename T, typename Alloc>
 inline base_owned_ref<T, Alloc>::base_owned_ref(
     base_owned_ref<T, Alloc>&& other) noexcept
-  : object_{other.object_} {
-  internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other);
-  internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this);
-  // JObjectWrapper is a simple type and does not support move semantics so we explicitly
+  : storage_(other.get()) {
+  internal::dbglog("New move from ref=%p other=%p", other.get(), &other);
+  internal::dbglog("New move to ref=%p this=%p", get(), this);
+  // JObject is a simple type and does not support move semantics so we explicitly
   // clear other
-  other.object_.set(nullptr);
+  other.set(nullptr);
 }
 
 template<typename T, typename Alloc>
 template<typename U>
 base_owned_ref<T, Alloc>::base_owned_ref(base_owned_ref<U, Alloc>&& other) noexcept
-  : object_{other.object_} {
-  internal::dbglog("New move from ref=%p other=%p", other.getPlainJniReference(), &other);
-  internal::dbglog("New move to ref=%p this=%p", getPlainJniReference(), this);
-  // JObjectWrapper is a simple type and does not support move semantics so we explicitly
+  : storage_(other.get()) {
+  internal::dbglog("New move from ref=%p other=%p", other.get(), &other);
+  internal::dbglog("New move to ref=%p this=%p", get(), this);
+  // JObject is a simple type and does not support move semantics so we explicitly
   // clear other
-  other.object_.set(nullptr);
+  other.set(nullptr);
 }
 
 template<typename T, typename Alloc>
 inline base_owned_ref<T, Alloc>::~base_owned_ref() noexcept {
   reset();
-  internal::dbglog("Ref destruct ref=%p this=%p", getPlainJniReference(), this);
+  internal::dbglog("Ref destruct ref=%p this=%p", get(), this);
 }
 
 template<typename T, typename Alloc>
-inline T base_owned_ref<T, Alloc>::release() noexcept {
-  auto value = getPlainJniReference();
+inline auto base_owned_ref<T, Alloc>::release() noexcept -> javaobject {
+  auto value = get();
   internal::dbglog("Ref release ref=%p this=%p", value, this);
-  object_.set(nullptr);
+  set(nullptr);
   return value;
 }
 
@@ -185,17 +250,22 @@ inline void base_owned_ref<T,Alloc>::reset() noexcept {
 }
 
 template<typename T, typename Alloc>
-inline void base_owned_ref<T,Alloc>::reset(T reference) noexcept {
-  if (getPlainJniReference()) {
+inline void base_owned_ref<T,Alloc>::reset(javaobject reference) noexcept {
+  if (get()) {
     assert(Alloc{}.verifyReference(reference));
-    Alloc{}.deleteReference(getPlainJniReference());
+    Alloc{}.deleteReference(get());
   }
-  object_.set(reference);
+  set(reference);
+}
+
+template<typename T, typename Alloc>
+inline auto base_owned_ref<T, Alloc>::get() const noexcept -> javaobject {
+  return storage_.jobj();
 }
 
 template<typename T, typename Alloc>
-inline T base_owned_ref<T, Alloc>::getPlainJniReference() const noexcept {
-  return static_cast<T>(object_.get());
+inline void base_owned_ref<T, Alloc>::set(javaobject ref) noexcept {
+  storage_.set(ref);
 }
 
 
@@ -213,19 +283,21 @@ template<typename T>
 inline weak_ref<T>& weak_ref<T>::operator=(
     weak_ref<T>&& other) noexcept {
   internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p",
-      getPlainJniReference(), this, other.getPlainJniReference(), &other);
+      get(), this, other.get(), &other);
   reset(other.release());
   return *this;
 }
 
 template<typename T>
-local_ref<T> weak_ref<T>::lockLocal() {
-  return adopt_local(static_cast<T>(LocalReferenceAllocator{}.newReference(getPlainJniReference())));
+local_ref<T> weak_ref<T>::lockLocal() const {
+  return adopt_local(
+      static_cast<javaobject>(LocalReferenceAllocator{}.newReference(get())));
 }
 
 template<typename T>
-global_ref<T> weak_ref<T>::lockGlobal() {
-  return adopt_global(static_cast<T>(GlobalReferenceAllocator{}.newReference(getPlainJniReference())));
+global_ref<T> weak_ref<T>::lockGlobal() const {
+  return adopt_global(
+      static_cast<javaobject>(GlobalReferenceAllocator{}.newReference(get())));
 }
 
 template<typename T>
@@ -233,9 +305,8 @@ inline void swap(
     weak_ref<T>& a,
     weak_ref<T>& b) noexcept {
   internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p",
-      a.getPlainJniReference(), &a, b.getPlainJniReference(), &b);
-  using std::swap;
-  swap(a.object_, b.object_);
+      a.get(), &a, b.get(), &b);
+  a.storage_.swap(b.storage_);
 }
 
 
@@ -253,7 +324,7 @@ template<typename T, typename Alloc>
 inline basic_strong_ref<T, Alloc>& basic_strong_ref<T, Alloc>::operator=(
     basic_strong_ref<T, Alloc>&& other) noexcept {
   internal::dbglog("Op= move ref=%p this=%p oref=%p other=%p",
-      getPlainJniReference(), this, other.getPlainJniReference(), &other);
+      get(), this, other.get(), &other);
   reset(other.release());
   return *this;
 }
@@ -269,28 +340,23 @@ inline basic_strong_ref<T, Alloc>::operator bool() const noexcept {
 }
 
 template<typename T, typename Alloc>
-inline T basic_strong_ref<T, Alloc>::get() const noexcept {
-  return getPlainJniReference();
+inline auto basic_strong_ref<T, Alloc>::operator->() noexcept -> Repr* {
+  return &storage_.get();
 }
 
 template<typename T, typename Alloc>
-inline JObjectWrapper<T>* basic_strong_ref<T, Alloc>::operator->() noexcept {
-  return &object_;
+inline auto basic_strong_ref<T, Alloc>::operator->() const noexcept -> const Repr* {
+  return &storage_.get();
 }
 
 template<typename T, typename Alloc>
-inline const JObjectWrapper<T>* basic_strong_ref<T, Alloc>::operator->() const noexcept {
-  return &object_;
+inline auto basic_strong_ref<T, Alloc>::operator*() noexcept -> Repr& {
+  return storage_.get();
 }
 
 template<typename T, typename Alloc>
-inline JObjectWrapper<T>& basic_strong_ref<T, Alloc>::operator*() noexcept {
-  return object_;
-}
-
-template<typename T, typename Alloc>
-inline const JObjectWrapper<T>& basic_strong_ref<T, Alloc>::operator*() const noexcept {
-  return object_;
+inline auto basic_strong_ref<T, Alloc>::operator*() const noexcept -> const Repr& {
+  return storage_.get();
 }
 
 template<typename T, typename Alloc>
@@ -298,33 +364,32 @@ inline void swap(
     basic_strong_ref<T, Alloc>& a,
     basic_strong_ref<T, Alloc>& b) noexcept {
   internal::dbglog("Ref swap a.ref=%p a=%p b.ref=%p b=%p",
-      a.getPlainJniReference(), &a, b.getPlainJniReference(), &b);
+      a.get(), &a, b.get(), &b);
   using std::swap;
-  swap(a.object_, b.object_);
+  a.storage_.swap(b.storage_);
 }
 
 
 // alias_ref //////////////////////////////////////////////////////////////////////////////
 
 template<typename T>
-inline constexpr alias_ref<T>::alias_ref() noexcept
-  : object_{nullptr}
+inline alias_ref<T>::alias_ref() noexcept
+  : storage_{nullptr}
 {}
 
 template<typename T>
-inline constexpr alias_ref<T>::alias_ref(std::nullptr_t) noexcept
-  : object_{nullptr}
+inline alias_ref<T>::alias_ref(std::nullptr_t) noexcept
+  : storage_{nullptr}
 {}
 
 template<typename T>
 inline alias_ref<T>::alias_ref(const alias_ref& other) noexcept
-  : object_{other.object_}
+  : storage_{other.get()}
 {}
 
-
 template<typename T>
-inline alias_ref<T>::alias_ref(T ref) noexcept
-  : object_{ref} {
+inline alias_ref<T>::alias_ref(javaobject ref) noexcept
+  : storage_(ref) {
   assert(
       LocalReferenceAllocator{}.verifyReference(ref) ||
       GlobalReferenceAllocator{}.verifyReference(ref));
@@ -333,13 +398,13 @@ inline alias_ref<T>::alias_ref(T ref) noexcept
 template<typename T>
 template<typename TOther, typename /* for SFINAE */>
 inline alias_ref<T>::alias_ref(alias_ref<TOther> other) noexcept
-  : object_{other.get()}
+  : storage_{other.get()}
 {}
 
 template<typename T>
 template<typename TOther, typename AOther, typename /* for SFINAE */>
 inline alias_ref<T>::alias_ref(const basic_strong_ref<TOther, AOther>& other) noexcept
-  : object_{other.get()}
+  : storage_{other.get()}
 {}
 
 template<typename T>
@@ -354,34 +419,90 @@ inline alias_ref<T>::operator bool() const noexcept {
 }
 
 template<typename T>
-inline T facebook::jni::alias_ref<T>::get() const noexcept {
-  return static_cast<T>(object_.get());
+inline auto facebook::jni::alias_ref<T>::get() const noexcept -> javaobject {
+  return storage_.jobj();
+}
+
+template<typename T>
+inline auto alias_ref<T>::operator->() noexcept -> Repr* {
+  return &(**this);
 }
 
 template<typename T>
-inline JObjectWrapper<T>* alias_ref<T>::operator->() noexcept {
-  return &object_;
+inline auto alias_ref<T>::operator->() const noexcept -> const Repr* {
+  return &(**this);
 }
 
 template<typename T>
-inline const JObjectWrapper<T>* alias_ref<T>::operator->() const noexcept {
-  return &object_;
+inline auto alias_ref<T>::operator*() noexcept -> Repr& {
+  return storage_.get();
 }
 
 template<typename T>
-inline JObjectWrapper<T>& alias_ref<T>::operator*() noexcept {
-  return object_;
+inline auto alias_ref<T>::operator*() const noexcept -> const Repr& {
+  return storage_.get();
 }
 
 template<typename T>
-inline const JObjectWrapper<T>& alias_ref<T>::operator*() const noexcept {
-  return object_;
+inline void alias_ref<T>::set(javaobject ref) noexcept {
+  storage_.set(ref);
 }
 
 template<typename T>
 inline void swap(alias_ref<T>& a, alias_ref<T>& b) noexcept {
-  using std::swap;
-  swap(a.object_, b.object_);
+  a.storage_.swap(b.storage_);
+}
+
+// Could reduce code duplication by using a pointer-to-function
+// template argument.  I'm not sure whether that would make the code
+// more maintainable (DRY), or less (too clever/confusing.).
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), local_ref<T>>
+static_ref_cast(const local_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return make_local(p);
+}
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), global_ref<T>>
+static_ref_cast(const global_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return make_global(p);
+}
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>>
+static_ref_cast(const alias_ref<U>& ref) noexcept
+{
+  T p = static_cast<T>(ref.get());
+  return wrap_alias(p);
+}
+
+template<typename T, typename RefType>
+auto dynamic_ref_cast(const RefType& ref) ->
+enable_if_t<IsPlainJniReference<T>(), decltype(static_ref_cast<T>(ref))>
+{
+  if (! ref) {
+    return decltype(static_ref_cast<T>(ref))();
+  }
+
+  std::string target_class_name{jtype_traits<T>::base_name()};
+
+  // If not found, will throw an exception.
+  alias_ref<jclass> target_class = findClassStatic(target_class_name.c_str());
+
+  local_ref<jclass> source_class = ref->getClass();
+
+  if ( ! source_class->isAssignableFrom(target_class)) {
+    throwNewJavaException("java/lang/ClassCastException",
+                          "Tried to cast from %s to %s.",
+                          source_class->toString().c_str(),
+                          target_class_name.c_str());
+  }
+
+  return static_ref_cast<T>(ref);
 }
 
 }}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/References.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References.h
index 575f2cc6e559..b578c5c91507 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/References.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/References.h
@@ -79,53 +79,131 @@
 
 #include "ReferenceAllocators.h"
 #include "TypeTraits.h"
+#include "References-forward.h"
 
 namespace facebook {
 namespace jni {
 
-/**
- * The JObjectWrapper is specialized to provide functionality for various Java classes, some
- * specializations are provided, and it is easy to add your own. See example
- * @sample WrapperSample.cpp
- */
-template<typename T, typename Enable = void>
-class JObjectWrapper;
+/// Convenience function to wrap an existing local reference
+template<typename T>
+local_ref<T> adopt_local(T ref) noexcept;
 
+/// Convenience function to wrap an existing global reference
+template<typename T>
+global_ref<T> adopt_global(T ref) noexcept;
 
-template<typename T, typename Alloc>
-class base_owned_ref;
+/// Convenience function to wrap an existing weak reference
+template<typename T>
+weak_ref<T> adopt_weak_global(T ref) noexcept;
+
+
+/// Swaps two owning references of the same type
+template<typename T>
+void swap(weak_ref<T>& a, weak_ref<T>& b) noexcept;
 
+/// Swaps two owning references of the same type
 template<typename T, typename Alloc>
-class basic_strong_ref;
+void swap(basic_strong_ref<T, Alloc>& a, basic_strong_ref<T, Alloc>& b) noexcept;
 
+/**
+ * Retrieve the plain reference from a plain reference.
+ */
 template<typename T>
-class weak_ref;
+enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref);
 
+/**
+ * Retrieve the plain reference from an alias reference.
+ */
 template<typename T>
-class alias_ref;
+JniType<T> getPlainJniReference(alias_ref<T> ref);
+
+/**
+ * Retrieve the plain JNI reference from any reference owned reference.
+ */
+template<typename T, typename Alloc>
+JniType<T> getPlainJniReference(const base_owned_ref<T, Alloc>& ref);
 
+class JObject;
+class JClass;
 
-/// A smart unique reference owning a local JNI reference
-template<typename T>
-using local_ref = basic_strong_ref<T, LocalReferenceAllocator>;
+namespace detail {
 
-/// A smart unique reference owning a global JNI reference
-template<typename T>
-using global_ref = basic_strong_ref<T, GlobalReferenceAllocator>;
+template <typename T, typename Enable = void>
+struct HasJniRefRepr : std::false_type {};
 
+template <typename T>
+struct HasJniRefRepr<T, typename std::enable_if<!std::is_same<typename T::JniRefRepr, void>::value, void>::type> : std::true_type {
+  using type = typename T::JniRefRepr;
+};
 
-/// Convenience function to wrap an existing local reference
-template<typename T>
-enable_if_t<IsPlainJniReference<T>(), local_ref<T>> adopt_local(T ref) noexcept;
+template <typename T>
+struct RefReprType<T*> {
+  using type = typename std::conditional<HasJniRefRepr<T>::value, typename HasJniRefRepr<T>::type, JObjectWrapper<T*>>::type;
+  static_assert(std::is_base_of<JObject, type>::value,
+      "Repr type missing JObject base.");
+  static_assert(std::is_same<type, typename RefReprType<type>::type>::value,
+      "RefReprType<T> not idempotent");
+};
 
-/// Convenience function to wrap an existing global reference
-template<typename T>
-enable_if_t<IsPlainJniReference<T>(), global_ref<T>> adopt_global(T ref) noexcept;
+template <typename T>
+struct RefReprType<T, typename std::enable_if<std::is_base_of<JObject, T>::value, void>::type> {
+  using type = T;
+  static_assert(std::is_base_of<JObject, type>::value,
+      "Repr type missing JObject base.");
+  static_assert(std::is_same<type, typename RefReprType<type>::type>::value,
+      "RefReprType<T> not idempotent");
+};
 
-/// Convenience function to wrap an existing weak reference
-template<typename T>
-enable_if_t<IsPlainJniReference<T>(), weak_ref<T>> adopt_weak_global(T ref) noexcept;
+template <typename T>
+struct JavaObjectType {
+  using type = typename RefReprType<T>::type::javaobject;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
 
+template <typename T>
+struct JavaObjectType<JObjectWrapper<T>> {
+  using type = T;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
+
+template <typename T>
+struct JavaObjectType<T*> {
+  using type = T*;
+  static_assert(IsPlainJniReference<type>(),
+      "JavaObjectType<T> not a plain jni reference");
+  static_assert(std::is_same<type, typename JavaObjectType<type>::type>::value,
+      "JavaObjectType<T> not idempotent");
+};
+
+template <typename Repr>
+struct ReprStorage {
+  explicit ReprStorage(JniType<Repr> obj) noexcept;
+
+  void set(JniType<Repr> obj) noexcept;
+
+  Repr& get() noexcept;
+  const Repr& get() const noexcept;
+  JniType<Repr> jobj() const noexcept;
+
+  void swap(ReprStorage& other) noexcept;
+ private:
+  ReprStorage() = delete;
+  ReprStorage(const ReprStorage&) = delete;
+  ReprStorage(ReprStorage&&) = delete;
+  ReprStorage& operator=(const ReprStorage&) = delete;
+  ReprStorage& operator=(ReprStorage&&) = delete;
+
+  using Storage = typename std::aligned_storage<sizeof(JObjectBase), alignof(JObjectBase)>::type;
+  Storage storage_;
+};
+
+} // namespace detail
 
 /**
  * Create a new local reference from an existing reference
@@ -160,33 +238,6 @@ template<typename T>
 enable_if_t<IsNonWeakReference<T>(), weak_ref<plain_jni_reference_t<T>>>
 make_weak(const T& r);
 
-
-/// Swaps two owning references of the same type
-template<typename T>
-void swap(weak_ref<T>& a, weak_ref<T>& b) noexcept;
-
-/// Swaps two owning references of the same type
-template<typename T, typename Alloc>
-void swap(basic_strong_ref<T, Alloc>& a, basic_strong_ref<T, Alloc>& b) noexcept;
-
-/**
- * Retrieve the plain reference from a plain reference.
- */
-template<typename T>
-enable_if_t<IsPlainJniReference<T>(), T> getPlainJniReference(T ref);
-
-/**
- * Retrieve the plain reference from an alias reference.
- */
-template<typename T>
-T getPlainJniReference(alias_ref<T> ref);
-
-/**
- * Retrieve the plain JNI reference from any reference owned reference.
- */
-template<typename T, typename Alloc>
-T getPlainJniReference(const base_owned_ref<T, Alloc>& ref);
-
 /**
  * Compare two references to see if they refer to the same object
  */
@@ -201,19 +252,16 @@ template<typename T1, typename T2>
 enable_if_t<IsNonWeakReference<T1>() && IsNonWeakReference<T2>(), bool>
 operator!=(const T1& a, const T2& b);
 
-
 template<typename T, typename Alloc>
 class base_owned_ref {
-
-  static_assert(IsPlainJniReference<T>(), "T must be a JNI reference");
-
  public:
+  using javaobject = JniType<T>;
 
   /**
    * Release the ownership and set the reference to null. Thus no deleter is invoked.
    * @return Returns the reference
    */
-  T release() noexcept;
+  javaobject release() noexcept;
 
   /**
    * Reset the reference to refer to nullptr.
@@ -221,20 +269,23 @@ class base_owned_ref {
   void reset() noexcept;
 
  protected:
+  using Repr = ReprType<T>;
+  detail::ReprStorage<Repr> storage_;
 
-  JObjectWrapper<T> object_;
+  javaobject get() const noexcept;
+  void set(javaobject ref) noexcept;
 
   /*
    * Wrap an existing reference and transfers its ownership to the newly created unique reference.
    * NB! Does not create a new reference
    */
-  explicit base_owned_ref(T reference) noexcept;
+  explicit base_owned_ref(javaobject reference) noexcept;
 
   /// Create a null reference
-  constexpr base_owned_ref() noexcept;
+  base_owned_ref() noexcept;
 
   /// Create a null reference
-  constexpr explicit base_owned_ref(std::nullptr_t) noexcept;
+  explicit base_owned_ref(std::nullptr_t) noexcept;
 
   /// Copy constructor (note creates a new reference)
   base_owned_ref(const base_owned_ref& other);
@@ -256,13 +307,9 @@ class base_owned_ref {
   /// Assignment by moving a reference thus not creating a new reference
   base_owned_ref& operator=(base_owned_ref&& rhs) noexcept;
 
+  void reset(javaobject reference) noexcept;
 
-  T getPlainJniReference() const noexcept;
-
-  void reset(T reference) noexcept;
-
-
-  friend T jni::getPlainJniReference<>(const base_owned_ref& ref);
+  friend javaobject jni::getPlainJniReference<>(const base_owned_ref& ref);
 
   template<typename U, typename UAlloc>
   friend class base_owned_ref;
@@ -278,29 +325,31 @@ class base_owned_ref {
  */
 template<typename T>
 class weak_ref : public base_owned_ref<T, WeakGlobalReferenceAllocator> {
-
-  static_assert(IsPlainJniReference<T>(), "T must be a JNI reference");
-
  public:
+  using javaobject = JniType<T>;
 
-  using PlainJniType = T;
   using Allocator = WeakGlobalReferenceAllocator;
 
   // This inherits non-default, non-copy, non-move ctors.
   using base_owned_ref<T, Allocator>::base_owned_ref;
 
   /// Create a null reference
-  constexpr weak_ref() noexcept
+  weak_ref() noexcept
     : base_owned_ref<T, Allocator>{} {}
 
   /// Create a null reference
-  constexpr explicit weak_ref(std::nullptr_t) noexcept
+  explicit weak_ref(std::nullptr_t) noexcept
     : base_owned_ref<T, Allocator>{nullptr} {}
 
   /// Copy constructor (note creates a new reference)
   weak_ref(const weak_ref& other)
     : base_owned_ref<T, Allocator>{other} {}
 
+  // This needs to be explicit to change its visibility.
+  template<typename U>
+  weak_ref(const weak_ref<U>& other)
+    : base_owned_ref<T, Allocator>{other} {}
+
   /// Transfers ownership of an underlying reference from one unique reference to another
   weak_ref(weak_ref&& other) noexcept
     : base_owned_ref<T, Allocator>{std::move(other)} {}
@@ -312,28 +361,26 @@ class weak_ref : public base_owned_ref<T, WeakGlobalReferenceAllocator> {
   /// Assignment by moving a reference thus not creating a new reference
   weak_ref& operator=(weak_ref&& rhs) noexcept;
 
-
   // Creates an owned local reference to the referred object or to null if the object is reclaimed
-  local_ref<T> lockLocal();
+  local_ref<T> lockLocal() const;
 
   // Creates an owned global reference to the referred object or to null if the object is reclaimed
-  global_ref<T> lockGlobal();
+  global_ref<T> lockGlobal() const;
 
  private:
-
-  using base_owned_ref<T, Allocator>::getPlainJniReference;
-
+  // get/release/reset on weak_ref are not exposed to users.
+  using base_owned_ref<T, Allocator>::get;
+  using base_owned_ref<T, Allocator>::release;
+  using base_owned_ref<T, Allocator>::reset;
   /*
    * Wrap an existing reference and transfers its ownership to the newly created unique reference.
    * NB! Does not create a new reference
    */
-  explicit weak_ref(T reference) noexcept
+  explicit weak_ref(javaobject reference) noexcept
     : base_owned_ref<T, Allocator>{reference} {}
 
-
   template<typename T2> friend class weak_ref;
-  friend weak_ref<enable_if_t<IsPlainJniReference<T>(), T>>
-    adopt_weak_global<T>(T ref) noexcept;
+  friend weak_ref<javaobject> adopt_weak_global<javaobject>(javaobject ref) noexcept;
   friend void swap<T>(weak_ref& a, weak_ref& b) noexcept;
 };
 
@@ -344,12 +391,10 @@ class weak_ref : public base_owned_ref<T, WeakGlobalReferenceAllocator> {
  */
 template<typename T, typename Alloc>
 class basic_strong_ref : public base_owned_ref<T, Alloc> {
-
-  static_assert(IsPlainJniReference<T>(), "T must be a JNI reference");
-
+  using typename base_owned_ref<T, Alloc>::Repr;
  public:
+  using javaobject = JniType<T>;
 
-  using PlainJniType = T;
   using Allocator = Alloc;
 
   // This inherits non-default, non-copy, non-move ctors.
@@ -358,17 +403,22 @@ class basic_strong_ref : public base_owned_ref<T, Alloc> {
   using base_owned_ref<T, Alloc>::reset;
 
   /// Create a null reference
-  constexpr basic_strong_ref() noexcept
+  basic_strong_ref() noexcept
     : base_owned_ref<T, Alloc>{} {}
 
   /// Create a null reference
-  constexpr explicit basic_strong_ref(std::nullptr_t) noexcept
+  explicit basic_strong_ref(std::nullptr_t) noexcept
     : base_owned_ref<T, Alloc>{nullptr} {}
 
   /// Copy constructor (note creates a new reference)
   basic_strong_ref(const basic_strong_ref& other)
     : base_owned_ref<T, Alloc>{other} {}
 
+  // This needs to be explicit to change its visibility.
+  template<typename U>
+  basic_strong_ref(const basic_strong_ref<U, Alloc>& other)
+    : base_owned_ref<T, Alloc>{other} {}
+
   /// Transfers ownership of an underlying reference from one unique reference to another
   basic_strong_ref(basic_strong_ref&& other) noexcept
     : base_owned_ref<T, Alloc>{std::move(other)} {}
@@ -379,6 +429,8 @@ class basic_strong_ref : public base_owned_ref<T, Alloc> {
   /// Assignment by moving a reference thus not creating a new reference
   basic_strong_ref& operator=(basic_strong_ref&& rhs) noexcept;
 
+  /// Get the plain JNI reference
+  using base_owned_ref<T, Allocator>::get;
 
   /// Release the ownership of the reference and return the wrapped reference in an alias
   alias_ref<T> releaseAlias() noexcept;
@@ -386,37 +438,33 @@ class basic_strong_ref : public base_owned_ref<T, Alloc> {
   /// Checks if the reference points to a non-null object
   explicit operator bool() const noexcept;
 
-  /// Get the plain JNI reference
-  T get() const noexcept;
-
   /// Access the functionality provided by the object wrappers
-  JObjectWrapper<T>* operator->() noexcept;
+  Repr* operator->() noexcept;
 
   /// Access the functionality provided by the object wrappers
-  const JObjectWrapper<T>* operator->() const noexcept;
+  const Repr* operator->() const noexcept;
 
   /// Provide a reference to the underlying wrapper (be sure that it is non-null before invoking)
-  JObjectWrapper<T>& operator*() noexcept;
+  Repr& operator*() noexcept;
 
   /// Provide a const reference to the underlying wrapper (be sure that it is non-null
   /// before invoking)
-  const JObjectWrapper<T>& operator*() const noexcept;
+  const Repr& operator*() const noexcept;
 
  private:
 
-  using base_owned_ref<T, Alloc>::object_;
-  using base_owned_ref<T, Alloc>::getPlainJniReference;
+  using base_owned_ref<T, Alloc>::storage_;
 
   /*
    * Wrap an existing reference and transfers its ownership to the newly created unique reference.
    * NB! Does not create a new reference
    */
-  explicit basic_strong_ref(T reference) noexcept
+  explicit basic_strong_ref(javaobject reference) noexcept
     : base_owned_ref<T, Alloc>{reference} {}
 
 
-  friend enable_if_t<IsPlainJniReference<T>(), local_ref<T>> adopt_local<T>(T ref) noexcept;
-  friend enable_if_t<IsPlainJniReference<T>(), global_ref<T>> adopt_global<T>(T ref) noexcept;
+  friend local_ref<T> adopt_local<T>(T ref) noexcept;
+  friend global_ref<T> adopt_global<T>(T ref) noexcept;
   friend void swap<T, Alloc>(basic_strong_ref& a, basic_strong_ref& b) noexcept;
 };
 
@@ -424,7 +472,7 @@ class basic_strong_ref : public base_owned_ref<T, Alloc> {
 template<typename T>
 enable_if_t<IsPlainJniReference<T>(), alias_ref<T>> wrap_alias(T ref) noexcept;
 
-/// Swaps to alias referencec of the same type
+/// Swaps to alias reference of the same type
 template<typename T>
 void swap(alias_ref<T>& a, alias_ref<T>& b) noexcept;
 
@@ -437,35 +485,40 @@ void swap(alias_ref<T>& a, alias_ref<T>& b) noexcept;
  */
 template<typename T>
 class alias_ref {
-
-  static_assert(IsPlainJniReference<T>(), "T must be a JNI reference");
+  using Repr = ReprType<T>;
 
  public:
-
-  using PlainJniType = T;
-
+  using javaobject = JniType<T>;
 
   /// Create a null reference
-  constexpr alias_ref() noexcept;
+  alias_ref() noexcept;
 
   /// Create a null reference
-  constexpr alias_ref(std::nullptr_t) noexcept;
+  alias_ref(std::nullptr_t) noexcept;
 
   /// Copy constructor
   alias_ref(const alias_ref& other) noexcept;
 
   /// Wrap an existing plain JNI reference
-  alias_ref(T ref) noexcept;
+  /* implicit */ alias_ref(javaobject ref) noexcept;
 
   /// Wrap an existing smart reference of any type convertible to T
-  template<typename TOther, typename = enable_if_t<IsConvertible<TOther, T>(), T>>
+  template<
+    typename TOther,
+    typename = enable_if_t<
+      IsConvertible<JniType<TOther>, javaobject>(), T>
+    >
   alias_ref(alias_ref<TOther> other) noexcept;
 
   /// Wrap an existing alias reference of a type convertible to T
-  template<typename TOther, typename AOther, typename = enable_if_t<IsConvertible<TOther, T>(), T>>
+  template<
+    typename TOther,
+    typename AOther,
+    typename = enable_if_t<
+      IsConvertible<JniType<TOther>, javaobject>(), T>
+    >
   alias_ref(const basic_strong_ref<TOther, AOther>& other) noexcept;
 
-
   /// Assignment operator
   alias_ref& operator=(alias_ref other) noexcept;
 
@@ -473,22 +526,24 @@ class alias_ref {
   explicit operator bool() const noexcept;
 
   /// Converts back to a plain JNI reference
-  T get() const noexcept;
+  javaobject get() const noexcept;
 
   /// Access the functionality provided by the object wrappers
-  JObjectWrapper<T>* operator->() noexcept;
+  Repr* operator->() noexcept;
 
   /// Access the functionality provided by the object wrappers
-  const JObjectWrapper<T>* operator->() const noexcept;
+  const Repr* operator->() const noexcept;
 
   /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking)
-  JObjectWrapper<T>& operator*() noexcept;
+  Repr& operator*() noexcept;
 
   /// Provide a guaranteed non-null reference (be sure that it is non-null before invoking)
-  const JObjectWrapper<T>& operator*() const noexcept;
+  const Repr& operator*() const noexcept;
 
  private:
-  JObjectWrapper<T> object_;
+  void set(javaobject ref) noexcept;
+
+  detail::ReprStorage<Repr> storage_;
 
   friend void swap<T>(alias_ref& a, alias_ref& b) noexcept;
 };
@@ -510,6 +565,22 @@ class JniLocalScope {
   bool hasFrame_;
 };
 
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), local_ref<T>>
+static_ref_cast(const local_ref<U>& ref) noexcept;
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), global_ref<T>>
+static_ref_cast(const global_ref<U>& ref) noexcept;
+
+template<typename T, typename U>
+enable_if_t<IsPlainJniReference<T>(), alias_ref<T>>
+static_ref_cast(const alias_ref<U>& ref) noexcept;
+
+template<typename T, typename RefType>
+auto dynamic_ref_cast(const RefType& ref) ->
+enable_if_t<IsPlainJniReference<T>(), decltype(static_ref_cast<T>(ref))> ;
+
 }}
 
 #include "References-inl.h"
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Registration-inl.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Registration-inl.h
index 29414d195fee..2ac6abaf4de8 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/Registration-inl.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/Registration-inl.h
@@ -17,27 +17,24 @@ namespace jni {
 
 namespace detail {
 
-// convert to HybridClass* from jhybridobject
-template <typename T>
-struct Convert<
-  T, typename std::enable_if<
-    std::is_base_of<BaseHybridClass, typename std::remove_pointer<T>::type>::value>::type> {
-  typedef typename std::remove_pointer<T>::type::jhybridobject jniType;
-  static T fromJni(jniType t) {
-    if (t == nullptr) {
-      return nullptr;
-    }
-    return facebook::jni::cthis(wrap_alias(t));
-  }
-  // There is no automatic return conversion for objects.
-};
+#ifdef __i386__
+// X86 ABI forces 16 byte stack allignment on calls. Unfortunately
+// sometimes Dalvik chooses not to obey the ABI:
+// - https://code.google.com/p/android/issues/detail?id=61012
+// - https://android.googlesource.com/platform/ndk/+/81696d2%5E!/
+// Therefore, we tell the compiler to re-align the stack on entry
+// to our JNI functions.
+#define JNI_ENTRY_POINT __attribute__((force_align_arg_pointer))
+#else
+#define JNI_ENTRY_POINT
+#endif
 
 // registration wrapper for legacy JNI-style functions
 
 template<typename F, F func, typename C, typename... Args>
 inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(JNIEnv*, C, Args... args)) {
   struct funcWrapper {
-    static void call(JNIEnv* env, jobject obj, Args... args) {
+    JNI_ENTRY_POINT static void call(JNIEnv* env, jobject obj, Args... args) {
       // Note that if func was declared noexcept, then both gcc and clang are smart
       // enough to elide the try/catch.
       try {
@@ -55,9 +52,9 @@ inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(JNIEnv*, C, Args...
 template<typename F, F func, typename C, typename R, typename... Args>
 inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... args)) {
   struct funcWrapper {
-    static R call(JNIEnv* env, jobject obj, Args... args) {
+    JNI_ENTRY_POINT static R call(JNIEnv* env, jobject obj, Args... args) {
       try {
-        return (*func)(env, static_cast<C>(obj), args...);
+        return (*func)(env, static_cast<JniType<C>>(obj), args...);
       } catch (...) {
         translatePendingCppExceptionToJavaException();
         return R{};
@@ -74,10 +71,10 @@ inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(JNIEnv*, C, Args... arg
 template<typename F, F func, typename C, typename... Args>
 inline NativeMethodWrapper* exceptionWrapJNIMethod(void (*)(alias_ref<C>, Args... args)) {
   struct funcWrapper {
-    static void call(JNIEnv*, jobject obj,
-                     typename Convert<typename std::decay<Args>::type>::jniType... args) {
+    JNI_ENTRY_POINT static void call(JNIEnv*, jobject obj,
+                                     typename Convert<typename std::decay<Args>::type>::jniType... args) {
       try {
-        (*func)(static_cast<C>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...);
+        (*func)(static_cast<JniType<C>>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...);
       } catch (...) {
         translatePendingCppExceptionToJavaException();
       }
@@ -93,11 +90,11 @@ inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref<C>, Args... a
   struct funcWrapper {
     typedef typename Convert<typename std::decay<R>::type>::jniType jniRet;
 
-    static jniRet call(JNIEnv*, jobject obj,
-                       typename Convert<typename std::decay<Args>::type>::jniType... args) {
+    JNI_ENTRY_POINT static jniRet call(JNIEnv*, jobject obj,
+                                       typename Convert<typename std::decay<Args>::type>::jniType... args) {
       try {
         return Convert<typename std::decay<R>::type>::toJniRet(
-          (*func)(static_cast<C>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...));
+          (*func)(static_cast<JniType<C>>(obj), Convert<typename std::decay<Args>::type>::fromJni(args)...));
       } catch (...) {
         translatePendingCppExceptionToJavaException();
         return jniRet{};
@@ -114,8 +111,8 @@ inline NativeMethodWrapper* exceptionWrapJNIMethod(R (*)(alias_ref<C>, Args... a
 template<typename M, M method, typename C, typename... Args>
 inline NativeMethodWrapper* exceptionWrapJNIMethod(void (C::*method0)(Args... args)) {
   struct funcWrapper {
-    static void call(JNIEnv* env, jobject obj,
-                     typename Convert<typename std::decay<Args>::type>::jniType... args) {
+    JNI_ENTRY_POINT static void call(JNIEnv* env, jobject obj,
+                                     typename Convert<typename std::decay<Args>::type>::jniType... args) {
       try {
         try {
           auto aref = wrap_alias(static_cast<typename C::jhybridobject>(obj));
@@ -143,8 +140,8 @@ inline NativeMethodWrapper* exceptionWrapJNIMethod(R (C::*method0)(Args... args)
   struct funcWrapper {
     typedef typename Convert<typename std::decay<R>::type>::jniType jniRet;
 
-    static jniRet call(JNIEnv* env, jobject obj,
-                       typename Convert<typename std::decay<Args>::type>::jniType... args) {
+    JNI_ENTRY_POINT static jniRet call(JNIEnv* env, jobject obj,
+                                       typename Convert<typename std::decay<Args>::type>::jniType... args) {
       try {
         try {
           auto aref = wrap_alias(static_cast<typename C::jhybridobject>(obj));
@@ -176,16 +173,12 @@ inline std::string makeDescriptor(R (*)(JNIEnv*, C, Args... args)) {
 
 template<typename R, typename C, typename... Args>
 inline std::string makeDescriptor(R (*)(alias_ref<C>, Args... args)) {
-  typedef typename Convert<typename std::decay<R>::type>::jniType jniRet;
-  return jmethod_traits<jniRet(typename Convert<typename std::decay<Args>::type>::jniType...)>
-    ::descriptor();
+  return jmethod_traits_from_cxx<R(Args...)>::descriptor();
 }
 
 template<typename R, typename C, typename... Args>
 inline std::string makeDescriptor(R (C::*)(Args... args)) {
-  typedef typename Convert<typename std::decay<R>::type>::jniType jniRet;
-  return jmethod_traits<jniRet(typename Convert<typename std::decay<Args>::type>::jniType...)>
-    ::descriptor();
+  return jmethod_traits_from_cxx<R(Args...)>::descriptor();
 }
 
 }
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/TypeTraits.h b/ReactAndroid/src/main/jni/first-party/jni/fbjni/TypeTraits.h
index b4bdd15ea2f1..26472669dc71 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/fbjni/TypeTraits.h
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/TypeTraits.h
@@ -11,6 +11,8 @@
 
 #include <type_traits>
 
+#include "References-forward.h"
+
 namespace facebook {
 namespace jni {
 
@@ -69,6 +71,25 @@ constexpr bool IsJniPrimitive() {
   return is_jni_primitive<T>::value;
 }
 
+/// Metafunction to determine whether a type is a JNI array of primitives or not
+template <typename T>
+struct is_jni_primitive_array :
+  std::integral_constant<bool,
+    std::is_same<jbooleanArray, T>::value ||
+    std::is_same<jbyteArray, T>::value ||
+    std::is_same<jcharArray, T>::value ||
+    std::is_same<jshortArray, T>::value ||
+    std::is_same<jintArray, T>::value ||
+    std::is_same<jlongArray, T>::value ||
+    std::is_same<jfloatArray, T>::value ||
+    std::is_same<jdoubleArray, T>::value> {};
+
+/// Helper to simplify use of is_jni_primitive_array
+template <typename T>
+constexpr bool IsJniPrimitiveArray() {
+  return is_jni_primitive_array<T>::value;
+}
+
 /// Metafunction to determine if a type is a scalar (primitive or reference) JNI type
 template<typename T>
 struct is_jni_scalar :
@@ -95,15 +116,6 @@ constexpr bool IsJniType() {
   return is_jni_type<T>::value;
 }
 
-template<typename T>
-class weak_global_ref;
-
-template<typename T, typename Alloc>
-class basic_strong_ref;
-
-template<typename T>
-class alias_ref;
-
 template<typename T>
 struct is_non_weak_reference :
   std::integral_constant<bool,
@@ -120,7 +132,7 @@ template<typename T>
 struct is_any_reference :
   std::integral_constant<bool,
     IsPlainJniReference<T>() ||
-    IsInstantiationOf<weak_global_ref, T>() ||
+    IsInstantiationOf<weak_ref, T>() ||
     IsInstantiationOf<basic_strong_ref, T>() ||
     IsInstantiationOf<alias_ref, T>()> {};
 
@@ -131,19 +143,18 @@ constexpr bool IsAnyReference() {
 
 template<typename T>
 struct reference_traits {
-  static_assert(IsPlainJniReference<T>(), "Need a plain JNI reference");
-  using plain_jni_reference_t = T;
+  using plain_jni_reference_t = JniType<T>;
+  static_assert(IsPlainJniReference<plain_jni_reference_t>(), "Need a plain JNI reference");
 };
 
 template<template <typename...> class R, typename T, typename... A>
 struct reference_traits<R<T, A...>> {
-  static_assert(IsAnyReference<T>(), "Need an fbjni reference");
-  using plain_jni_reference_t = T;
+  using plain_jni_reference_t = JniType<T>;
+  static_assert(IsPlainJniReference<plain_jni_reference_t>(), "Need a plain JNI reference");
 };
 
 template<typename T>
 using plain_jni_reference_t = typename reference_traits<T>::plain_jni_reference_t;
 
-}
-}
-
+} // namespace jni
+} // namespace facebook
diff --git a/ReactAndroid/src/main/jni/first-party/jni/fbjni/android/ReferenceChecking.cpp b/ReactAndroid/src/main/jni/first-party/jni/fbjni/android/ReferenceChecking.cpp
new file mode 100644
index 000000000000..e17eb2af5b8c
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/jni/fbjni/android/ReferenceChecking.cpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef __ANDROID__
+#error "This file should only be compiled for Android."
+#endif
+
+#include <jni/fbjni/References.h>
+#include <jni/fbjni/CoreClasses.h>
+
+namespace facebook {
+namespace jni {
+namespace internal {
+
+static int32_t getApiLevel() {
+  auto cls = findClassLocal("android/os/Build$VERSION");
+  auto fld = cls->getStaticField<int32_t>("SDK_INT");
+  if (fld) {
+    return cls->getStaticFieldValue(fld);
+  }
+  return 0;
+}
+
+bool doesGetObjectRefTypeWork() {
+  static auto level = getApiLevel();
+  return level >= 14;
+}
+
+}
+}
+}
\ No newline at end of file
diff --git a/ReactAndroid/src/main/jni/prebuilt/BUCK b/ReactAndroid/src/main/jni/prebuilt/BUCK
new file mode 100644
index 000000000000..69e71a637360
--- /dev/null
+++ b/ReactAndroid/src/main/jni/prebuilt/BUCK
@@ -0,0 +1,22 @@
+include_defs('//ReactAndroid/DEFS')
+
+# Temp workaround to get the build working e2e, Gradle builds them for us
+
+prebuilt_native_library(
+  name = 'reactnative-libs',
+  native_libs = 'lib',
+  visibility = ['PUBLIC'],
+)
+
+
+android_prebuilt_aar(
+  name = 'android-jsc',
+  aar = ':android-jsc-aar',
+  visibility = ['PUBLIC'],
+)
+
+remote_file(
+  name = 'android-jsc-aar',
+  url = 'mvn:org.webkit:android-jsc:aar:r174650',
+  sha1 = '880cedd93f43e0fc841f01f2fa185a63d9230f85',
+)
diff --git a/ReactAndroid/src/main/jni/prebuilt/lib/DUMMY b/ReactAndroid/src/main/jni/prebuilt/lib/DUMMY
new file mode 100644
index 000000000000..dc29e4d637e6
--- /dev/null
+++ b/ReactAndroid/src/main/jni/prebuilt/lib/DUMMY
@@ -0,0 +1 @@
+# just a dummy temporarily to make BUCK happy about folder not present before Gradle built it
diff --git a/ReactAndroid/src/main/jni/react/Android.mk b/ReactAndroid/src/main/jni/react/Android.mk
index e0fff007099b..f72729bdce26 100644
--- a/ReactAndroid/src/main/jni/react/Android.mk
+++ b/ReactAndroid/src/main/jni/react/Android.mk
@@ -6,10 +6,11 @@ LOCAL_MODULE := libreactnative
 
 LOCAL_SRC_FILES := \
   Bridge.cpp \
-  Value.cpp \
-  MethodCall.cpp \
-  JSCHelpers.cpp \
   JSCExecutor.cpp \
+  JSCHelpers.cpp \
+  MethodCall.cpp \
+  Platform.cpp \
+  Value.cpp \
 
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/..
 LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
@@ -17,16 +18,18 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
 LOCAL_CFLAGS := \
   -DLOG_TAG=\"ReactNative\"
 
-LOCAL_CFLAGS += -Wall -Werror -fexceptions
+LOCAL_LDLIBS += -landroid
+LOCAL_CFLAGS += -Wall -Werror -fexceptions -frtti
 CXX11_FLAGS := -std=c++11
 LOCAL_CFLAGS += $(CXX11_FLAGS)
 LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS)
 
-LOCAL_SHARED_LIBRARIES := libfb libfbjni libfolly_json libjsc
+LOCAL_SHARED_LIBRARIES := libfb libfbjni libfolly_json libjsc libglog
 
 include $(BUILD_STATIC_LIBRARY)
 
 $(call import-module,fb)
 $(call import-module,jni)
 $(call import-module,folly)
-$(call import-module,jsc)
\ No newline at end of file
+$(call import-module,jsc)
+$(call import-module,glog)
diff --git a/ReactAndroid/src/main/jni/react/BUCK b/ReactAndroid/src/main/jni/react/BUCK
index 14e7e8ffe550..292463593df2 100644
--- a/ReactAndroid/src/main/jni/react/BUCK
+++ b/ReactAndroid/src/main/jni/react/BUCK
@@ -3,8 +3,47 @@ include_defs('//ReactAndroid/DEFS')
 # We depend on JSC, support the same platforms
 SUPPORTED_PLATFORMS = '^android-(armv7|x86)$'
 
-cxx_library(
-  name = 'react',
+DEPS = [
+  '//native/third-party/android-ndk:android',
+  '//xplat/fbsystrace:fbsystrace',
+  '//xplat/folly:molly',
+  '//xplat/third-party/glog:glog',
+]
+
+PREPROCESSOR_FLAGS = [
+  '-DLOG_TAG="ReactNative"',
+  '-DWITH_JSC_EXTRA_TRACING=1',
+  '-DWITH_FBSYSTRACE=1',
+]
+
+def react_library(**kwargs):
+  cxx_library(
+    name = 'react',
+    visibility = [
+      react_native_target('jni/react/jni:jni'),
+    ],
+    deps = DEPS + JSC_DEPS,
+    preprocessor_flags = PREPROCESSOR_FLAGS,
+    **kwargs
+  )
+
+  cxx_library(
+    name = 'react-internal',
+    visibility = [
+      react_native_target('jni/react/jni:jni-internal'),
+    ],
+    deps = DEPS + JSC_INTERNAL_DEPS,
+    preprocessor_flags = PREPROCESSOR_FLAGS + [
+      '-DWITH_FB_JSC_TUNING=1',
+      '-DWITH_JSC_MEMORY_PRESSURE=1',
+      '-DWITH_FBJSCEXTENSIONS=1',
+      '-DWITH_JSC_INTERNAL=1',
+      '-DWITH_FB_MEMORY_PROFILING=1',
+    ],
+    **kwargs
+  )
+
+react_library(
   soname = 'libreactnative.so',
   header_namespace = 'react',
   supported_platforms_regex = SUPPORTED_PLATFORMS,
@@ -16,42 +55,34 @@ cxx_library(
     'JSCHelpers.cpp',
     'JSCExecutor.cpp',
     'JSCTracing.cpp',
-    'JSCPerfLogging.cpp',
+    'JSCMemory.cpp',
     'JSCLegacyProfiler.cpp',
+    'Platform.cpp',
   ],
   headers = [
     'JSCTracing.h',
-    'JSCPerfLogging.h',
     'JSCLegacyProfiler.h',
+    'JSCMemory.h',
   ],
   exported_headers = [
     'Bridge.h',
+    'ExecutorToken.h',
+    'ExecutorTokenFactory.h',
     'Executor.h',
     'JSCExecutor.h',
     'JSCHelpers.h',
+    'MessageQueueThread.h',
     'MethodCall.h',
+    'JSModulesUnbundle.h',
     'Value.h',
-  ],
-  preprocessor_flags = [
-    '-DLOG_TAG="ReactNative"',
-    '-DWITH_JSC_EXTRA_TRACING=1',
-    '-DWITH_FBSYSTRACE=1',
+    'Platform.h',
+    'noncopyable.h',
   ],
   compiler_flags = [
     '-Wall',
     '-std=c++11',
     '-fexceptions',
     '-fvisibility=hidden',
-  ],
-  visibility = [
-    react_native_target('jni/react/jni:jni'),
-  ],
-  deps = [
-    '//native/fb:fb',
-    '//xplat/fbsystrace:fbsystrace',
-    '//native/jni:jni',
-    '//native/third-party/jsc:jsc',
-    '//native/third-party/jsc:jsc_legacy_profiler',
-    '//xplat/folly:json',
+    '-frtti',
   ],
 )
diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp
index e3643e6cf41c..6654e8bd45d8 100644
--- a/ReactAndroid/src/main/jni/react/Bridge.cpp
+++ b/ReactAndroid/src/main/jni/react/Bridge.cpp
@@ -2,111 +2,251 @@
 
 #include "Bridge.h"
 
-#include "Executor.h"
-#include "MethodCall.h"
-
 #ifdef WITH_FBSYSTRACE
 #include <fbsystrace.h>
 using fbsystrace::FbSystraceSection;
+using fbsystrace::FbSystraceAsyncFlow;
 #endif
+#include <folly/Memory.h>
+
+#include "Platform.h"
 
 namespace facebook {
 namespace react {
 
-class JSThreadState {
-public:
-  JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
-    m_callback(callback)
-  {
-    m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
-      m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
-    });
-  }
+Bridge::Bridge(
+    JSExecutorFactory* jsExecutorFactory,
+    std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
+    std::unique_ptr<BridgeCallback> callback) :
+  m_callback(std::move(callback)),
+  m_destroyed(std::make_shared<bool>(false)),
+  m_executorTokenFactory(std::move(executorTokenFactory)) {
+  std::unique_ptr<JSExecutor> mainExecutor = jsExecutorFactory->createJSExecutor(this);
+  // cached to avoid locked map lookup in the common case
+  m_mainExecutor = mainExecutor.get();
+  m_mainExecutorToken = folly::make_unique<ExecutorToken>(registerExecutor(
+      std::move(mainExecutor),
+      MessageQueues::getCurrentMessageQueueThread()));
+}
 
-  void executeApplicationScript(const std::string& script, const std::string& sourceURL) {
-    m_jsExecutor->executeApplicationScript(script, sourceURL);
-  }
+// This must be called on the same thread on which the constructor was called.
+Bridge::~Bridge() {
+  CHECK(*m_destroyed) << "Bridge::destroy() must be called before deallocating the Bridge!";
+}
 
-  void executeJSCall(
-      const std::string& moduleName,
-      const std::string& methodName,
-      const std::vector<folly::dynamic>& arguments) {
-    auto returnedJSON = m_jsExecutor->executeJSCall(moduleName, methodName, arguments);
-    m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
-  }
+void Bridge::loadApplicationScript(const std::string& script, const std::string& sourceURL) {
+  m_mainExecutor->loadApplicationScript(script, sourceURL);
+}
 
-  void setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
-    m_jsExecutor->setGlobalVariable(propName, jsonValue);
-  }
+void Bridge::loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> unbundle,
+    const std::string& startupCode,
+    const std::string& sourceURL) {
+  m_mainExecutor->loadApplicationUnbundle(std::move(unbundle), startupCode, sourceURL);
+}
 
-  bool supportsProfiling() {
-    return m_jsExecutor->supportsProfiling();
+void Bridge::callFunction(
+    ExecutorToken executorToken,
+    const double moduleId,
+    const double methodId,
+    const folly::dynamic& arguments,
+    const std::string& tracingName) {
+  if (*m_destroyed) {
+    return;
   }
 
-  void startProfiler(const std::string& title) {
-    m_jsExecutor->startProfiler(title);
-  }
+  #ifdef WITH_FBSYSTRACE
+  int systraceCookie = m_systraceCookie++;
+  FbSystraceAsyncFlow::begin(
+      TRACE_TAG_REACT_CXX_BRIDGE,
+      tracingName.c_str(),
+      systraceCookie);
+  #endif
 
-  void stopProfiler(const std::string& title, const std::string& filename) {
-    m_jsExecutor->stopProfiler(title, filename);
+  auto executorMessageQueueThread = getMessageQueueThread(executorToken);
+  if (executorMessageQueueThread == nullptr) {
+    LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
+    return;
   }
 
-private:
-  std::unique_ptr<JSExecutor> m_jsExecutor;
-  Bridge::Callback m_callback;
-};
-
-Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
-  m_callback(callback),
-  m_destroyed(std::shared_ptr<bool>(new bool(false)))
-{
-  auto destroyed = m_destroyed;
-  auto proxyCallback = [this, destroyed] (std::vector<MethodCall> calls, bool isEndOfBatch) {
-    if (*destroyed) {
+  std::shared_ptr<bool> isDestroyed = m_destroyed;
+  executorMessageQueueThread->runOnQueue([=] () {
+    #ifdef WITH_FBSYSTRACE
+    FbSystraceAsyncFlow::end(
+        TRACE_TAG_REACT_CXX_BRIDGE,
+        tracingName.c_str(),
+        systraceCookie);
+    FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str());
+    #endif
+
+    if (*isDestroyed) {
       return;
     }
-    m_callback(std::move(calls), isEndOfBatch);
-  };
-  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));
-}
 
-// This must be called on the same thread on which the constructor was called.
-Bridge::~Bridge() {
-  *m_destroyed = true;
-  m_threadState.reset();
-}
+    JSExecutor *executor = getExecutor(executorToken);
+    if (executor == nullptr) {
+      LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
+      return;
+    }
 
-void Bridge::executeApplicationScript(const std::string& script, const std::string& sourceURL) {
-  m_threadState->executeApplicationScript(script, sourceURL);
+    // This is safe because we are running on the executor's thread: it won't
+    // destruct until after it's been unregistered (which we check above) and
+    // that will happen on this thread
+    executor->callFunction(moduleId, methodId, arguments);
+  });
 }
 
-void Bridge::executeJSCall(
-    const std::string& script,
-    const std::string& sourceURL,
-    const std::vector<folly::dynamic>& arguments) {
+void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {
   if (*m_destroyed) {
     return;
   }
+
   #ifdef WITH_FBSYSTRACE
-  FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.executeJSCall");
+  int systraceCookie = m_systraceCookie++;
+  FbSystraceAsyncFlow::begin(
+      TRACE_TAG_REACT_CXX_BRIDGE,
+      "<callback>",
+      systraceCookie);
   #endif
-  m_threadState->executeJSCall(script, sourceURL, arguments);
+
+  auto executorMessageQueueThread = getMessageQueueThread(executorToken);
+  if (executorMessageQueueThread == nullptr) {
+    LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
+    return;
+  }
+
+  std::shared_ptr<bool> isDestroyed = m_destroyed;
+  executorMessageQueueThread->runOnQueue([=] () {
+    #ifdef WITH_FBSYSTRACE
+    FbSystraceAsyncFlow::end(
+        TRACE_TAG_REACT_CXX_BRIDGE,
+        "<callback>",
+        systraceCookie);
+    FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.invokeCallback");
+    #endif
+
+    if (*isDestroyed) {
+      return;
+    }
+
+    JSExecutor *executor = getExecutor(executorToken);
+    if (executor == nullptr) {
+      LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
+      return;
+    }
+
+    // This is safe because we are running on the executor's thread: it won't
+    // destruct until after it's been unregistered (which we check above) and
+    // that will happen on this thread
+    executor->invokeCallback(callbackId, arguments);
+  });
 }
 
 void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
-  m_threadState->setGlobalVariable(propName, jsonValue);
+  m_mainExecutor->setGlobalVariable(propName, jsonValue);
+}
+
+void* Bridge::getJavaScriptContext() {
+  return m_mainExecutor->getJavaScriptContext();
 }
 
 bool Bridge::supportsProfiling() {
-  return m_threadState->supportsProfiling();
+  return m_mainExecutor->supportsProfiling();
 }
 
 void Bridge::startProfiler(const std::string& title) {
-  m_threadState->startProfiler(title);
+  m_mainExecutor->startProfiler(title);
 }
 
 void Bridge::stopProfiler(const std::string& title, const std::string& filename) {
-  m_threadState->stopProfiler(title, filename);
+  m_mainExecutor->stopProfiler(title, filename);
+}
+
+void Bridge::handleMemoryPressureModerate() {
+  m_mainExecutor->handleMemoryPressureModerate();
+}
+
+void Bridge::handleMemoryPressureCritical() {
+  m_mainExecutor->handleMemoryPressureCritical();
+}
+
+void Bridge::callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch) {
+  if (*m_destroyed) {
+    return;
+  }
+  m_callback->onCallNativeModules(getTokenForExecutor(executor), parseMethodCalls(callJSON), isEndOfBatch);
+}
+
+ExecutorToken Bridge::getMainExecutorToken() const {
+  return *m_mainExecutorToken.get();
+}
+
+ExecutorToken Bridge::registerExecutor(
+    std::unique_ptr<JSExecutor> executor,
+    std::shared_ptr<MessageQueueThread> messageQueueThread) {
+  auto token = m_executorTokenFactory->createExecutorToken();
+
+  std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
+
+  CHECK(m_executorTokenMap.find(executor.get()) == m_executorTokenMap.end())
+      << "Trying to register an already registered executor!";
+
+  m_executorTokenMap.emplace(executor.get(), token);
+  m_executorMap.emplace(
+      token,
+      folly::make_unique<ExecutorRegistration>(std::move(executor), std::move(messageQueueThread)));
+
+  return token;
+}
+
+std::unique_ptr<JSExecutor> Bridge::unregisterExecutor(ExecutorToken executorToken) {
+  std::unique_ptr<JSExecutor> executor;
+
+  {
+    std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
+
+    auto it = m_executorMap.find(executorToken);
+    CHECK(it != m_executorMap.end())
+        << "Trying to unregister an executor that was never registered!";
+
+    executor = std::move(it->second->executor_);
+    m_executorMap.erase(it);
+    m_executorTokenMap.erase(executor.get());
+  }
+
+  m_callback->onExecutorUnregistered(executorToken);
+
+  return executor;
+}
+
+MessageQueueThread* Bridge::getMessageQueueThread(const ExecutorToken& executorToken) {
+  std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
+  auto it = m_executorMap.find(executorToken);
+  if (it == m_executorMap.end()) {
+    return nullptr;
+  }
+  return it->second->messageQueueThread_.get();
+}
+
+JSExecutor* Bridge::getExecutor(const ExecutorToken& executorToken) {
+  std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
+  auto it = m_executorMap.find(executorToken);
+  if (it == m_executorMap.end()) {
+    return nullptr;
+  }
+  return it->second->executor_.get();
+}
+
+ExecutorToken Bridge::getTokenForExecutor(JSExecutor& executor) {
+  std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
+  return m_executorTokenMap.at(&executor);
+}
+
+void Bridge::destroy() {
+  *m_destroyed = true;
+  std::unique_ptr<JSExecutor> mainExecutor = unregisterExecutor(*m_mainExecutorToken);
+  m_mainExecutor->destroy();
+  mainExecutor.reset();
 }
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h
index 9f81f529d9d3..3618ef04fe48 100644
--- a/ReactAndroid/src/main/jni/react/Bridge.h
+++ b/ReactAndroid/src/main/jni/react/Bridge.h
@@ -2,15 +2,18 @@
 
 #pragma once
 
+#include <atomic>
 #include <functional>
 #include <map>
 #include <vector>
-#include <jni.h>
-#include <fb/Countable.h>
-#include <fb/RefPtr.h>
-#include "Value.h"
+
+#include "ExecutorToken.h"
+#include "ExecutorTokenFactory.h"
 #include "Executor.h"
+#include "MessageQueueThread.h"
 #include "MethodCall.h"
+#include "JSModulesUnbundle.h"
+#include "Value.h"
 
 namespace folly {
 
@@ -21,30 +24,138 @@ struct dynamic;
 namespace facebook {
 namespace react {
 
-class JSThreadState;
-class Bridge : public Countable {
+class BridgeCallback {
+public:
+  virtual ~BridgeCallback() {};
+
+  virtual void onCallNativeModules(
+      ExecutorToken executorToken,
+      std::vector<MethodCall>&& calls,
+      bool isEndOfBatch) = 0;
+
+  virtual void onExecutorUnregistered(ExecutorToken executorToken) = 0;
+};
+
+class Bridge;
+class ExecutorRegistration {
 public:
-  typedef std::function<void(std::vector<MethodCall>, bool isEndOfBatch)> Callback;
+  ExecutorRegistration(
+      std::unique_ptr<JSExecutor> executor,
+      std::shared_ptr<MessageQueueThread> executorMessageQueueThread) :
+    executor_(std::move(executor)),
+    messageQueueThread_(executorMessageQueueThread) {}
+
+  std::unique_ptr<JSExecutor> executor_;
+  std::shared_ptr<MessageQueueThread> messageQueueThread_;
+};
 
-  Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback);
+class Bridge {
+public:
+  /**
+   * This must be called on the main JS thread.
+   */
+  Bridge(
+      JSExecutorFactory* jsExecutorFactory,
+      std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
+      std::unique_ptr<BridgeCallback> callback);
   virtual ~Bridge();
 
-  void executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& values);
-  void executeApplicationScript(const std::string& script, const std::string& sourceURL);
+  /**
+   * Executes a function with the module ID and method ID and any additional
+   * arguments in JS.
+   */
+  void callFunction(
+    ExecutorToken executorToken,
+    const double moduleId,
+    const double methodId,
+    const folly::dynamic& args,
+    const std::string& tracingName);
+
+  /**
+   * Invokes a callback with the cbID, and optional additional arguments in JS.
+   */
+  void invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& args);
+
+  /**
+   * Starts the JS application from an "bundle", i.e. a JavaScript file that
+   * contains code for all modules and a runtime that resolves and
+   * executes modules.
+   */
+  void loadApplicationScript(const std::string& script, const std::string& sourceURL);
+
+  /**
+   * Starts the JS application from an "unbundle", i.e. a backend that stores
+   * and injects each module as individual file.
+   */
+  void loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> unbundle,
+    const std::string& startupCode,
+    const std::string& sourceURL);
   void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
+  void* getJavaScriptContext();
   bool supportsProfiling();
   void startProfiler(const std::string& title);
   void stopProfiler(const std::string& title, const std::string& filename);
+  void handleMemoryPressureModerate();
+  void handleMemoryPressureCritical();
+
+  /**
+   * Invokes a set of native module calls on behalf of the given executor.
+   *
+   * TODO: get rid of isEndOfBatch
+   */
+  void callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch);
+
+  /**
+   * Returns the ExecutorToken corresponding to the main JSExecutor.
+   */
+  ExecutorToken getMainExecutorToken() const;
+
+  /**
+   * Registers the given JSExecutor which runs on the given MessageQueueThread
+   * with the Bridge. Part of this registration is transfering ownership of this
+   * JSExecutor to the Bridge for the duration of the registration.
+   *
+   * Returns a ExecutorToken which can be used to refer to this JSExecutor
+   * in the Bridge.
+   */
+  ExecutorToken registerExecutor(
+      std::unique_ptr<JSExecutor> executor,
+      std::shared_ptr<MessageQueueThread> executorMessageQueueThread);
+
+  /**
+   * Unregisters a JSExecutor that was previously registered with this Bridge
+   * using registerExecutor. Use the ExecutorToken returned from this
+   * registerExecutor call. This method will return ownership of the unregistered
+   * executor to the caller for it to retain or tear down.
+   *
+   * Returns ownership of the unregistered executor.
+   */
+  std::unique_ptr<JSExecutor> unregisterExecutor(ExecutorToken executorToken);
+
+  /**
+   * Synchronously tears down the bridge and the main executor.
+   */
+  void destroy();
 private:
-  Callback m_callback;
-  std::unique_ptr<JSThreadState> m_threadState;
+  std::unique_ptr<BridgeCallback> m_callback;
   // This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
   // on the same thread. In that case, the callback will try to run the task on m_callback which
   // will have been destroyed within ~Bridge(), thus causing a SIGSEGV.
   std::shared_ptr<bool> m_destroyed;
+  JSExecutor* m_mainExecutor;
+  std::unique_ptr<ExecutorToken> m_mainExecutorToken;
+  std::unique_ptr<ExecutorTokenFactory> m_executorTokenFactory;
+  std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
+  std::unordered_map<ExecutorToken, std::unique_ptr<ExecutorRegistration>> m_executorMap;
+  std::mutex m_registrationMutex;
+  #ifdef WITH_FBSYSTRACE
+  std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
+  #endif
+
+  MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
+  JSExecutor* getExecutor(const ExecutorToken& executorToken);
+  inline ExecutorToken getTokenForExecutor(JSExecutor& executor);
 };
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h
index 1f4f54cc5a89..6f392a3cd8fa 100644
--- a/ReactAndroid/src/main/jni/react/Executor.h
+++ b/ReactAndroid/src/main/jni/react/Executor.h
@@ -5,7 +5,8 @@
 #include <string>
 #include <vector>
 #include <memory>
-#include <jni/Countable.h>
+
+#include "JSModulesUnbundle.h"
 
 namespace folly {
 
@@ -16,33 +17,62 @@ struct dynamic;
 namespace facebook {
 namespace react {
 
+class Bridge;
 class JSExecutor;
-
-typedef std::function<void(std::string)> FlushImmediateCallback;
-
-class JSExecutorFactory : public Countable {
+class JSExecutorFactory {
 public:
-  virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) = 0;
+  virtual std::unique_ptr<JSExecutor> createJSExecutor(Bridge *bridge) = 0;
   virtual ~JSExecutorFactory() {};
 };
 
 class JSExecutor {
 public:
-  virtual void executeApplicationScript(
+  /**
+   * Execute an application script bundle in the JS context.
+   */
+  virtual void loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) = 0;
-  virtual std::string executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& arguments) = 0;
+
+  /**
+   * Add an application "unbundle" file
+   */
+  virtual void loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> bundle,
+    const std::string& startupCode,
+    const std::string& sourceURL) = 0;
+
+  /**
+   * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
+   * method ID and optional additional arguments in JS. The executor is responsible
+   * for using Bridge->callNativeModules to invoke any necessary native modules methods.
+   */
+  virtual void callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) = 0;
+
+  /**
+   * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID,
+   * and optional additional arguments in JS and returns the next queue. The executor
+   * is responsible for using Bridge->callNativeModules to invoke any necessary
+   * native modules methods.
+   */
+  virtual void invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0;
+
   virtual void setGlobalVariable(
     const std::string& propName,
     const std::string& jsonValue) = 0;
+  virtual void* getJavaScriptContext() {
+    return nullptr;
+  };
   virtual bool supportsProfiling() {
     return false;
   };
   virtual void startProfiler(const std::string &titleString) {};
   virtual void stopProfiler(const std::string &titleString, const std::string &filename) {};
+  virtual void handleMemoryPressureModerate() {};
+  virtual void handleMemoryPressureCritical() {
+    handleMemoryPressureModerate();
+  };
+  virtual void destroy() {};
   virtual ~JSExecutor() {};
 };
 
diff --git a/ReactAndroid/src/main/jni/react/ExecutorToken.h b/ReactAndroid/src/main/jni/react/ExecutorToken.h
new file mode 100644
index 000000000000..a630e90d34a1
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/ExecutorToken.h
@@ -0,0 +1,54 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include "Executor.h"
+
+namespace facebook {
+namespace react {
+
+/**
+ * This class exists so that we have a type for the shared_ptr on ExecutorToken
+ * that implements a virtual destructor.
+ */
+class PlatformExecutorToken {
+public:
+  virtual ~PlatformExecutorToken() {}
+};
+
+/**
+ * Class corresponding to a JS VM that can call into native modules. This is
+ * passed to native modules to allow their JS module calls/callbacks to be
+ * routed back to the proper JS VM on the proper thread.
+ */
+class ExecutorToken {
+public:
+  /**
+   * This should only be used by the implementation of the platform ExecutorToken.
+   * Do not use as a client of ExecutorToken.
+   */
+  explicit ExecutorToken(std::shared_ptr<PlatformExecutorToken> platformToken) :
+      platformToken_(platformToken) {}
+
+  std::shared_ptr<PlatformExecutorToken> getPlatformExecutorToken() const {
+    return platformToken_;
+  }
+
+  bool operator==(const ExecutorToken& other) const {
+    return platformToken_.get() == other.platformToken_.get();
+  }
+
+private:
+  std::shared_ptr<PlatformExecutorToken> platformToken_;
+};
+
+} }
+
+namespace std {
+  template<>
+  struct hash<facebook::react::ExecutorToken> {
+    const size_t operator()(const facebook::react::ExecutorToken& token) const {
+      return (size_t) token.getPlatformExecutorToken().get();
+    }
+  };
+}
diff --git a/ReactAndroid/src/main/jni/react/ExecutorTokenFactory.h b/ReactAndroid/src/main/jni/react/ExecutorTokenFactory.h
new file mode 100644
index 000000000000..5d2d42179073
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/ExecutorTokenFactory.h
@@ -0,0 +1,25 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include "ExecutorToken.h"
+#include "Executor.h"
+
+namespace facebook {
+namespace react {
+
+/**
+ * Class that knows how to create the platform-specific implementation
+ * of ExecutorToken.
+ */
+class ExecutorTokenFactory {
+public:
+  virtual ~ExecutorTokenFactory() {}
+
+  /**
+   * Creates a new ExecutorToken.
+   */
+  virtual ExecutorToken createExecutorToken() const = 0;
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
index ca7afa29a30b..0341677d973c 100644
--- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
@@ -3,42 +3,59 @@
 #include "JSCExecutor.h"
 
 #include <algorithm>
+#include <condition_variable>
+#include <mutex>
 #include <sstream>
 #include <string>
-#include <fb/log.h>
+#include <glog/logging.h>
 #include <folly/json.h>
 #include <folly/String.h>
-#include <jni/fbjni/Exceptions.h>
+#include <sys/time.h>
+
+#include "Bridge.h"
+#include "JSCHelpers.h"
+#include "Platform.h"
 #include "Value.h"
 
 #ifdef WITH_JSC_EXTRA_TRACING
-#include <react/JSCTracing.h>
-#include <react/JSCLegacyProfiler.h>
+#include "JSCTracing.h"
+#include "JSCLegacyProfiler.h"
 #include <JavaScriptCore/API/JSProfilerPrivate.h>
 #endif
 
+#ifdef WITH_JSC_MEMORY_PRESSURE
+#include <jsc_memory.h>
+#endif
+
 #ifdef WITH_FBSYSTRACE
 #include <fbsystrace.h>
 using fbsystrace::FbSystraceSection;
 #endif
 
-// Add native performance markers support
-#include <react/JSCPerfLogging.h>
+#ifdef WITH_FB_MEMORY_PROFILING
+#include "JSCMemory.h"
+#endif
+
+#ifdef WITH_FB_JSC_TUNING
+#include <jsc_config_android.h>
+#endif
 
-using namespace facebook::jni;
+static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL;
+static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL;
 
 namespace facebook {
 namespace react {
 
 static std::unordered_map<JSContextRef, JSCExecutor*> s_globalContextRefToJSCExecutor;
-static JSValueRef nativeFlushQueueImmediate(
+
+static JSValueRef nativePerformanceNow(
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
     size_t argumentCount,
     const JSValueRef arguments[],
     JSValueRef *exception);
-static JSValueRef nativeLoggingHook(
+static JSValueRef nativeInjectHMRUpdate(
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
@@ -46,84 +63,188 @@ static JSValueRef nativeLoggingHook(
     const JSValueRef arguments[],
     JSValueRef *exception);
 
-static JSValueRef evaluateScriptWithJSC(
+static std::string executeJSCallWithJSC(
     JSGlobalContextRef ctx,
-    JSStringRef script,
-    JSStringRef sourceURL) {
-  JSValueRef exn;
-  auto result = JSEvaluateScript(ctx, script, nullptr, sourceURL, 0, &exn);
-  if (result == nullptr) {
-    JSValueProtect(ctx, exn);
-    std::string exceptionText = Value(ctx, exn).toString().str();
-    FBLOGE("Got JS Exception: %s", exceptionText.c_str());
-    auto line = Value(ctx, JSObjectGetProperty(ctx,
-      JSValueToObject(ctx, exn, nullptr),
-      JSStringCreateWithUTF8CString("line"), nullptr
-    ));
-    std::ostringstream lineInfo;
-    if (line != nullptr && line.isNumber()) {
-      lineInfo << " (line " << line.asInteger() << " in the generated bundle)";
-    } else {
-      lineInfo << " (no line info)";
+    const std::string& methodName,
+    const std::vector<folly::dynamic>& arguments) {
+  #ifdef WITH_FBSYSTRACE
+  FbSystraceSection s(
+      TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall",
+      "method", methodName);
+  #endif
+
+  // Evaluate script with JSC
+  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
+  auto js = folly::to<folly::fbstring>(
+      "__fbBatchedBridge.", methodName, ".apply(null, ",
+      folly::toJson(jsonArgs), ")");
+  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
+  return Value(ctx, result).toJSONString();
+}
+
+std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(Bridge *bridge) {
+  return std::unique_ptr<JSExecutor>(new JSCExecutor(bridge, cacheDir_, m_jscConfig));
+}
+
+JSCExecutor::JSCExecutor(Bridge *bridge, const std::string& cacheDir, const folly::dynamic& jscConfig) :
+    m_bridge(bridge),
+    m_deviceCacheDir(cacheDir),
+    m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()),
+    m_jscConfig(jscConfig) {
+  initOnJSVMThread();
+}
+
+JSCExecutor::JSCExecutor(
+    Bridge *bridge,
+    int workerId,
+    JSCExecutor *owner,
+    const std::string& script,
+    const std::unordered_map<std::string, std::string>& globalObjAsJSON,
+    const folly::dynamic& jscConfig) :
+    m_bridge(bridge),
+    m_workerId(workerId),
+    m_owner(owner),
+    m_deviceCacheDir(owner->m_deviceCacheDir),
+    m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()),
+    m_jscConfig(jscConfig) {
+  // We post initOnJSVMThread here so that the owner doesn't have to wait for
+  // initialization on its own thread
+  m_messageQueueThread->runOnQueue([this, script, globalObjAsJSON] () {
+    initOnJSVMThread();
+
+    installGlobalFunction(m_context, "postMessage", nativePostMessage);
+
+    for (auto& it : globalObjAsJSON) {
+      setGlobalVariable(it.first, it.second);
     }
-    throwNewJavaException("com/facebook/react/bridge/JSExecutionException", (exceptionText + lineInfo.str()).c_str());
-  }
-  return result;
+
+    // TODO(9604438): Protect against script does not exist
+    std::string scriptSrc = WebWorkerUtil::loadScriptFromAssets(script);
+    // TODO(9994180): Throw on error
+    loadApplicationScript(scriptSrc, script);
+  });
 }
 
-std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
-  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb));
+JSCExecutor::~JSCExecutor() {
+  CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
 }
 
-JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
-    m_flushImmediateCallback(cb) {
+void JSCExecutor::destroy() {
+  *m_isDestroyed = true;
+  if (m_messageQueueThread->isOnThread()) {
+    terminateOnJSVMThread();
+  } else {
+    m_messageQueueThread->runOnQueueSync([this] () {
+      terminateOnJSVMThread();
+    });
+  }
+}
+
+void JSCExecutor::initOnJSVMThread() {
+  #if defined(WITH_FB_JSC_TUNING) && !defined(WITH_JSC_INTERNAL)
+  // TODO: Find a way to pass m_jscConfig to configureJSCForAndroid()
+  configureJSCForAndroid(m_jscConfig.getDefault("GCTimers", false).asBool());
+  #endif
   m_context = JSGlobalContextCreateInGroup(nullptr, nullptr);
   s_globalContextRefToJSCExecutor[m_context] = this;
   installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
-  installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook);
+  installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow);
+  installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
+  installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
+  installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);
+  installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
+
+  installGlobalFunction(m_context, "nativeLoggingHook", JSLogging::nativeHook);
+
+  #if defined(WITH_JSC_INTERNAL) && defined(WITH_FB_JSC_TUNING)
+  configureJSCForAndroid();
+  #endif
+
   #ifdef WITH_JSC_EXTRA_TRACING
   addNativeTracingHooks(m_context);
   addNativeProfilingHooks(m_context);
-  addNativePerfLoggingHooks(m_context);
+  PerfLogging::installNativeHooks(m_context);
+  #endif
+
+  #ifdef WITH_FB_MEMORY_PROFILING
+  addNativeMemoryHooks(m_context);
   #endif
 }
 
-JSCExecutor::~JSCExecutor() {
+void JSCExecutor::terminateOnJSVMThread() {
+  // terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers
+  // to terminate first
+  std::vector<int> workerIds;
+  for (auto& it : m_ownedWorkers) {
+    workerIds.push_back(it.first);
+  }
+  for (int workerId : workerIds) {
+    terminateOwnedWebWorker(workerId);
+  }
+
   s_globalContextRefToJSCExecutor.erase(m_context);
   JSGlobalContextRelease(m_context);
+  m_context = nullptr;
 }
 
-void JSCExecutor::executeApplicationScript(
+void JSCExecutor::loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) {
-  String jsScript(script.c_str());
+  ReactMarker::logMarker("loadApplicationScript_startStringConvert");
+  String jsScript = String::createExpectingAscii(script);
+  ReactMarker::logMarker("loadApplicationScript_endStringConvert");
+
   String jsSourceURL(sourceURL.c_str());
   #ifdef WITH_FBSYSTRACE
-  FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::executeApplicationScript",
+  FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor::loadApplicationScript",
     "sourceURL", sourceURL);
   #endif
-  evaluateScriptWithJSC(m_context, jsScript, jsSourceURL);
+  if (!jsSourceURL) {
+    evaluateScript(m_context, jsScript, jsSourceURL);
+  } else {
+    // If we're evaluating a script, get the device's cache dir
+    //  in which a cache file for that script will be stored.
+    evaluateScript(m_context, jsScript, jsSourceURL, m_deviceCacheDir.c_str());
+  }
+  flush();
 }
 
-std::string JSCExecutor::executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& arguments) {
-  #ifdef WITH_FBSYSTRACE
-  FbSystraceSection s(
-      TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall",
-      "module", moduleName,
-      "method", methodName);
-  #endif
+void JSCExecutor::loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> unbundle,
+    const std::string& startupCode,
+    const std::string& sourceURL) {
+  if (!m_unbundle) {
+    installGlobalFunction(m_context, "nativeRequire", nativeRequire);
+  }
+  m_unbundle = std::move(unbundle);
+  loadApplicationScript(startupCode, sourceURL);
+}
 
-  // Evaluate script with JSC
-  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
-  auto js = folly::to<folly::fbstring>(
-      "require('", moduleName, "').", methodName, ".apply(null, ",
-      folly::toJson(jsonArgs), ")");
-  auto result = evaluateScriptWithJSC(m_context, String(js.c_str()), nullptr);
-  JSValueProtect(m_context, result);
-  return Value(m_context, result).toJSONString();
+void JSCExecutor::flush() {
+  // TODO: Make this a first class function instead of evaling. #9317773
+  std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>());
+  m_bridge->callNativeModules(*this, calls, true);
+}
+
+void JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
+  // TODO:  Make this a first class function instead of evaling. #9317773
+  std::vector<folly::dynamic> call{
+    (double) moduleId,
+    (double) methodId,
+    std::move(arguments),
+  };
+  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, calls, true);
+}
+
+void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
+  // TODO: Make this a first class function instead of evaling. #9317773
+  std::vector<folly::dynamic> call{
+    (double) callbackId,
+    std::move(arguments)
+  };
+  std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, calls, true);
 }
 
 void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
@@ -136,6 +257,10 @@ void JSCExecutor::setGlobalVariable(const std::string& propName, const std::stri
   JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
 }
 
+void* JSCExecutor::getJavaScriptContext() {
+  return m_context;
+}
+
 bool JSCExecutor::supportsProfiling() {
   #ifdef WITH_FBSYSTRACE
   return true;
@@ -147,7 +272,11 @@ bool JSCExecutor::supportsProfiling() {
 void JSCExecutor::startProfiler(const std::string &titleString) {
   #ifdef WITH_JSC_EXTRA_TRACING
   JSStringRef title = JSStringCreateWithUTF8CString(titleString.c_str());
+  #if WITH_JSC_INTERNAL
+  JSStartProfiling(m_context, title, false);
+  #else
   JSStartProfiling(m_context, title);
+  #endif
   JSStringRelease(title);
   #endif
 }
@@ -160,15 +289,200 @@ void JSCExecutor::stopProfiler(const std::string &titleString, const std::string
   #endif
 }
 
+void JSCExecutor::handleMemoryPressureModerate() {
+  #ifdef WITH_JSC_MEMORY_PRESSURE
+  JSHandleMemoryPressure(this, m_context, JSMemoryPressure::MODERATE);
+  #endif
+}
+
+void JSCExecutor::handleMemoryPressureCritical() {
+  #ifdef WITH_JSC_MEMORY_PRESSURE
+  JSHandleMemoryPressure(this, m_context, JSMemoryPressure::CRITICAL);
+  #endif
+}
+
 void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
-  m_flushImmediateCallback(queueJSON);
+  m_bridge->callNativeModules(*this, queueJSON, false);
+}
+
+void JSCExecutor::loadModule(uint32_t moduleId) {
+  auto module = m_unbundle->getModule(moduleId);
+  auto sourceUrl = String::createExpectingAscii(module.name);
+  auto source = String::createExpectingAscii(module.code);
+  evaluateScript(m_context, source, sourceUrl);
+}
+
+int JSCExecutor::addWebWorker(
+    const std::string& script,
+    JSValueRef workerRef,
+    JSValueRef globalObjRef) {
+  static std::atomic_int nextWorkerId(1);
+  int workerId = nextWorkerId++;
+
+  Object globalObj = Value(m_context, globalObjRef).asObject();
+
+  auto workerMQT = WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
+  std::unique_ptr<JSCExecutor> worker;
+  workerMQT->runOnQueueSync([this, &worker, &script, &globalObj, workerId] () {
+    worker.reset(new JSCExecutor(m_bridge, workerId, this, script, globalObj.toJSONMap(), m_jscConfig));
+  });
+
+  Object workerObj = Value(m_context, workerRef).asObject();
+  workerObj.makeProtected();
+
+  JSCExecutor *workerPtr = worker.get();
+  std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
+  ExecutorToken token = m_bridge->registerExecutor(
+      std::move(worker),
+      std::move(sharedMessageQueueThread));
+
+  m_ownedWorkers.emplace(
+      std::piecewise_construct,
+      std::forward_as_tuple(workerId),
+      std::forward_as_tuple(workerPtr, token, std::move(workerObj)));
+
+  return workerId;
+}
+
+void JSCExecutor::postMessageToOwnedWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
+  auto worker = m_ownedWorkers.at(workerId).executor;
+  std::string msgString = Value(m_context, message).toJSONString();
+
+  std::shared_ptr<bool> isWorkerDestroyed = worker->m_isDestroyed;
+  worker->m_messageQueueThread->runOnQueue([isWorkerDestroyed, worker, msgString] () {
+    if (*isWorkerDestroyed) {
+      return;
+    }
+    worker->receiveMessageFromOwner(msgString);
+  });
+}
+
+void JSCExecutor::postMessageToOwner(JSValueRef msg) {
+  std::string msgString = Value(m_context, msg).toJSONString();
+  std::shared_ptr<bool> ownerIsDestroyed = m_owner->m_isDestroyed;
+  m_owner->m_messageQueueThread->runOnQueue([workerId=m_workerId, owner=m_owner, ownerIsDestroyed, msgString] () {
+    if (*ownerIsDestroyed) {
+      return;
+    }
+    owner->receiveMessageFromOwnedWebWorker(workerId, msgString);
+  });
+}
+
+void JSCExecutor::receiveMessageFromOwnedWebWorker(int workerId, const std::string& json) {
+  Object* workerObj;
+  try {
+    workerObj = &m_ownedWorkers.at(workerId).jsObj;
+  } catch (std::out_of_range& e) {
+    // Worker was already terminated
+    return;
+  }
+
+  Value onmessageValue = workerObj->getProperty("onmessage");
+  if (onmessageValue.isUndefined()) {
+    return;
+  }
+
+  JSValueRef args[] = { createMessageObject(json) };
+  onmessageValue.asObject().callAsFunction(1, args);
+
+  flush();
+}
+
+void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
+  CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!";
+
+  JSValueRef args[] = { createMessageObject(msgString) };
+  Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage");
+  onmessageValue.asObject().callAsFunction(1, args);
+}
+
+void JSCExecutor::terminateOwnedWebWorker(int workerId) {
+  auto& workerRegistration = m_ownedWorkers.at(workerId);
+  std::shared_ptr<MessageQueueThread> workerMQT = workerRegistration.executor->m_messageQueueThread;
+  ExecutorToken workerExecutorToken = workerRegistration.executorToken;
+  m_ownedWorkers.erase(workerId);
+
+  std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken);
+  worker->destroy();
+  worker.reset();
+  workerMQT->quitSynchronous();
+}
+
+Object JSCExecutor::createMessageObject(const std::string& msgJson) {
+  Value rebornJSMsg = Value::fromJSON(m_context, String(msgJson.c_str()));
+  Object messageObject = Object::create(m_context);
+  messageObject.setProperty("data", rebornJSMsg);
+  return messageObject;
+}
+
+// Native JS hooks
+JSValueRef JSCExecutor::nativePostMessage(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount != 1) {
+    *exception = makeJSCException(ctx, "postMessage got wrong number of arguments");
+    return JSValueMakeUndefined(ctx);
+  }
+  JSValueRef msg = arguments[0];
+  JSCExecutor *webWorker = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+
+  webWorker->postMessageToOwner(msg);
+
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef makeInvalidModuleIdJSCException(
+    JSContextRef ctx,
+    const JSValueRef id,
+    JSValueRef *exception) {
+  std::string message = "Received invalid module ID: ";
+  message += String::adopt(JSValueToStringCopy(ctx, id, exception)).str();
+  return makeJSCException(ctx, message.c_str());
+}
+
+JSValueRef JSCExecutor::nativeRequire(
+  JSContextRef ctx,
+  JSObjectRef function,
+  JSObjectRef thisObject,
+  size_t argumentCount,
+  const JSValueRef arguments[],
+  JSValueRef *exception) {
+
+  if (argumentCount != 1) {
+    *exception = makeJSCException(ctx, "Got wrong number of args");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSCExecutor *executor;
+  try {
+    executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+  } catch (std::out_of_range& e) {
+    *exception = makeJSCException(ctx, "Global JS context didn't map to a valid executor");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  double moduleId = JSValueToNumber(ctx, arguments[0], exception);
+  if (moduleId <= (double) std::numeric_limits<uint32_t>::max() && moduleId >= 0.0) {
+    try {
+      executor->loadModule(moduleId);
+    } catch (JSModulesUnbundle::ModuleNotFound&) {
+      *exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception);
+    }
+  } else {
+    *exception = makeInvalidModuleIdJSCException(ctx, arguments[0], exception);
+  }
+  return JSValueMakeUndefined(ctx);
 }
 
 static JSValueRef createErrorString(JSContextRef ctx, const char *msg) {
   return JSValueMakeString(ctx, String(msg));
 }
 
-static JSValueRef nativeFlushQueueImmediate(
+JSValueRef JSCExecutor::nativeFlushQueueImmediate(
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
@@ -188,7 +502,6 @@ static JSValueRef nativeFlushQueueImmediate(
     return JSValueMakeUndefined(ctx);
   }
 
-  JSValueProtect(ctx, arguments[0]);
   std::string resStr = Value(ctx, arguments[0]).toJSONString();
 
   executor->flushQueueImmediate(resStr);
@@ -196,26 +509,120 @@ static JSValueRef nativeFlushQueueImmediate(
   return JSValueMakeUndefined(ctx);
 }
 
-static JSValueRef nativeLoggingHook(
+JSValueRef JSCExecutor::nativeStartWorker(
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
     size_t argumentCount,
-    const JSValueRef arguments[], JSValueRef *exception) {
-  android_LogPriority logLevel = ANDROID_LOG_DEBUG;
-  if (argumentCount > 1) {
-    int level = (int) JSValueToNumber(ctx, arguments[1], NULL);
-    // The lowest log level we get from JS is 0. We shift and cap it to be
-    // in the range the Android logging method expects.
-    logLevel = std::min(
-        static_cast<android_LogPriority>(level + ANDROID_LOG_DEBUG),
-        ANDROID_LOG_FATAL);
-  }
-  if (argumentCount > 0) {
-    JSStringRef jsString = JSValueToStringCopy(ctx, arguments[0], NULL);
-    String message = String::adopt(jsString);
-    FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str());
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount != 3) {
+    *exception = createErrorString(ctx, "Got wrong number of args");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  std::string scriptFile = Value(ctx, arguments[0]).toString().str();
+
+  JSValueRef worker = arguments[1];
+  JSValueRef globalObj = arguments[2];
+
+  JSCExecutor *executor;
+  try {
+    executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+  } catch (std::out_of_range& e) {
+    *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
+    return JSValueMakeUndefined(ctx);
   }
+
+  int workerId = executor->addWebWorker(scriptFile, worker, globalObj);
+
+  return JSValueMakeNumber(ctx, workerId);
+}
+
+JSValueRef JSCExecutor::nativePostMessageToWorker(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount != 2) {
+    *exception = createErrorString(ctx, "Got wrong number of args");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  double workerDouble = JSValueToNumber(ctx, arguments[0], exception);
+  if (workerDouble != workerDouble) {
+    *exception = createErrorString(ctx, "Got invalid worker id");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSCExecutor *executor;
+  try {
+    executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+  } catch (std::out_of_range& e) {
+    *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  executor->postMessageToOwnedWebWorker((int) workerDouble, arguments[1], exception);
+
+  return JSValueMakeUndefined(ctx);
+}
+
+JSValueRef JSCExecutor::nativeTerminateWorker(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef *exception) {
+  if (argumentCount != 1) {
+    *exception = createErrorString(ctx, "Got wrong number of args");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  double workerDouble = JSValueToNumber(ctx, arguments[0], exception);
+  if (workerDouble != workerDouble) {
+    *exception = createErrorString(ctx, "Got invalid worker id");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSCExecutor *executor;
+  try {
+    executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+  } catch (std::out_of_range& e) {
+    *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
+    return JSValueMakeUndefined(ctx);
+  }
+
+  executor->terminateOwnedWebWorker((int) workerDouble);
+
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativePerformanceNow(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[], JSValueRef *exception) {
+  // This is equivalent to android.os.SystemClock.elapsedRealtime() in native
+  struct timespec now;
+  clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+  int64_t nano = now.tv_sec * NANOSECONDS_IN_SECOND + now.tv_nsec;
+  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);
 }
 
diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h
index 849148bfaec8..7f41170c8c44 100644
--- a/ReactAndroid/src/main/jni/react/JSCExecutor.h
+++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h
@@ -2,42 +2,160 @@
 
 #pragma once
 
+#include <cstdint>
+#include <memory>
+#include <unordered_map>
+#include <folly/json.h>
 #include <JavaScriptCore/JSContextRef.h>
+
+#include "ExecutorToken.h"
 #include "Executor.h"
 #include "JSCHelpers.h"
+#include "Value.h"
 
 namespace facebook {
 namespace react {
 
+class MessageQueueThread;
+
 class JSCExecutorFactory : public JSExecutorFactory {
 public:
-  virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) override;
+  JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) :
+  cacheDir_(cacheDir),
+  m_jscConfig(jscConfig) {}
+  virtual std::unique_ptr<JSExecutor> createJSExecutor(Bridge *bridge) override;
+private:
+  std::string cacheDir_;
+  folly::dynamic m_jscConfig;
+};
+
+class JSCExecutor;
+class WorkerRegistration : public noncopyable {
+public:
+  explicit WorkerRegistration(JSCExecutor* executor, ExecutorToken executorToken, Object jsObj) :
+      executor(executor),
+      executorToken(executorToken),
+      jsObj(std::move(jsObj)) {}
+
+  JSCExecutor *executor;
+  ExecutorToken executorToken;
+  Object jsObj;
 };
 
 class JSCExecutor : public JSExecutor {
 public:
-  explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback);
+  /**
+   * Must be invoked from thread this Executor will run on.
+   */
+  explicit JSCExecutor(Bridge *bridge, const std::string& cacheDir, const folly::dynamic& jscConfig);
   ~JSCExecutor() override;
-  virtual void executeApplicationScript(
+
+  virtual void loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) override;
-  virtual std::string executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& arguments) override;
+  virtual void loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> unbundle,
+    const std::string& startupCode,
+    const std::string& sourceURL) override;
+  virtual void callFunction(
+    const double moduleId,
+    const double methodId,
+    const folly::dynamic& arguments) override;
+  virtual void invokeCallback(
+    const double callbackId,
+    const folly::dynamic& arguments) override;
   virtual void setGlobalVariable(
     const std::string& propName,
     const std::string& jsonValue) override;
+  virtual void* getJavaScriptContext() override;
   virtual bool supportsProfiling() override;
   virtual void startProfiler(const std::string &titleString) override;
   virtual void stopProfiler(const std::string &titleString, const std::string &filename) override;
-  
-  void flushQueueImmediate(std::string queueJSON);
+  virtual void handleMemoryPressureModerate() override;
+  virtual void handleMemoryPressureCritical() override;
+  virtual void destroy() override;
+
   void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
 
 private:
   JSGlobalContextRef m_context;
-  FlushImmediateCallback m_flushImmediateCallback;
+  Bridge *m_bridge;
+  int m_workerId = 0; // if this is a worker executor, this is non-zero
+  JSCExecutor *m_owner = nullptr; // if this is a worker executor, this is non-null
+  std::shared_ptr<bool> m_isDestroyed = std::shared_ptr<bool>(new bool(false));
+  std::unordered_map<int, WorkerRegistration> m_ownedWorkers;
+  std::string m_deviceCacheDir;
+  std::shared_ptr<MessageQueueThread> m_messageQueueThread;
+  std::unique_ptr<JSModulesUnbundle> m_unbundle;
+  folly::dynamic m_jscConfig;
+
+  /**
+   * WebWorker constructor. Must be invoked from thread this Executor will run on.
+   */
+  explicit JSCExecutor(
+      Bridge *bridge,
+      int workerId,
+      JSCExecutor *owner,
+      const std::string& script,
+      const std::unordered_map<std::string, std::string>& globalObjAsJSON,
+      const folly::dynamic& jscConfig);
+
+  void initOnJSVMThread();
+  void terminateOnJSVMThread();
+  void flush();
+  void flushQueueImmediate(std::string queueJSON);
+  void loadModule(uint32_t moduleId);
+
+  int addWebWorker(const std::string& script, JSValueRef workerRef, JSValueRef globalObjRef);
+  void postMessageToOwnedWebWorker(int worker, JSValueRef message, JSValueRef *exn);
+  void postMessageToOwner(JSValueRef result);
+  void receiveMessageFromOwnedWebWorker(int workerId, const std::string& message);
+  void receiveMessageFromOwner(const std::string &msgString);
+  void terminateOwnedWebWorker(int worker);
+  Object createMessageObject(const std::string& msgData);
+
+  static JSValueRef nativeStartWorker(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
+  static JSValueRef nativePostMessageToWorker(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
+  static JSValueRef nativeTerminateWorker(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
+  static JSValueRef nativePostMessage(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
+  static JSValueRef nativeRequire(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
+  static JSValueRef nativeFlushQueueImmediate(
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[],
+      JSValueRef *exception);
 };
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/JSCHelpers.cpp b/ReactAndroid/src/main/jni/react/JSCHelpers.cpp
index b3ef3aa57c73..b18e965c6b7d 100644
--- a/ReactAndroid/src/main/jni/react/JSCHelpers.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCHelpers.cpp
@@ -3,6 +3,13 @@
 #include "JSCHelpers.h"
 
 #include <JavaScriptCore/JSStringRef.h>
+#include <glog/logging.h>
+
+#include "Value.h"
+
+#if WITH_FBJSCEXTENSIONS
+#include <jsc_function_info_cache.h>
+#endif
 
 namespace facebook {
 namespace react {
@@ -19,4 +26,44 @@ void installGlobalFunction(
   JSStringRelease(jsName);
 }
 
+JSValueRef makeJSCException(
+    JSContextRef ctx,
+    const char* exception_text) {
+  JSStringRef message = JSStringCreateWithUTF8CString(exception_text);
+  JSValueRef exceptionString = JSValueMakeString(ctx, message);
+  JSStringRelease(message);
+  return JSValueToObject(ctx, exceptionString, NULL);
+}
+
+JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source, const char *cachePath) {
+  JSValueRef exn, result;
+#if WITH_FBJSCEXTENSIONS
+  if (source){
+    // If evaluating an application script, send it through `JSEvaluateScriptWithCache()`
+    //  to add cache support.
+    result = JSEvaluateScriptWithCache(context, script, NULL, source, 0, &exn, cachePath);
+  } else {
+    result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
+  }
+#else
+  result = JSEvaluateScript(context, script, NULL, source, 0, &exn);
+#endif
+  if (result == nullptr) {
+    Value exception = Value(context, exn);
+    std::string exceptionText = exception.toString().str();
+    LOG(ERROR) << "Got JS Exception: " << exceptionText;
+    auto line = exception.asObject().getProperty("line");
+
+    std::ostringstream locationInfo;
+    std::string file = source != nullptr ? String::ref(source).str() : "";
+    locationInfo << "(" << (file.length() ? file : "<unknown file>");
+    if (line != nullptr && line.isNumber()) {
+      locationInfo << ":" << line.asInteger();
+    }
+    locationInfo << ")";
+    throwJSExecutionException("%s %s", exceptionText.c_str(), locationInfo.str().c_str());
+  }
+  return result;
+}
+
 } }
diff --git a/ReactAndroid/src/main/jni/react/JSCHelpers.h b/ReactAndroid/src/main/jni/react/JSCHelpers.h
index 7209b2ce3a1b..9b61a566b970 100644
--- a/ReactAndroid/src/main/jni/react/JSCHelpers.h
+++ b/ReactAndroid/src/main/jni/react/JSCHelpers.h
@@ -4,13 +4,44 @@
 
 #include <JavaScriptCore/JSContextRef.h>
 #include <JavaScriptCore/JSObjectRef.h>
+#include <JavaScriptCore/JSValueRef.h>
+
+#include <stdexcept>
+#include <algorithm>
 
 namespace facebook {
 namespace react {
 
+struct JsException : std::runtime_error {
+  using std::runtime_error::runtime_error;
+};
+
+inline void throwJSExecutionException(const char* msg) {
+  throw JsException(msg);
+}
+
+template <typename... Args>
+inline void throwJSExecutionException(const char* fmt, Args... args) {
+  int msgSize = snprintf(nullptr, 0, fmt, args...);
+  msgSize = std::min(512, msgSize + 1);
+  char *msg = (char*) alloca(msgSize);
+  snprintf(msg, msgSize, fmt, args...);
+  throw JsException(msg);
+}
+
 void installGlobalFunction(
     JSGlobalContextRef ctx,
     const char* name,
     JSObjectCallAsFunctionCallback callback);
 
+JSValueRef makeJSCException(
+    JSContextRef ctx,
+    const char* exception_text);
+
+JSValueRef evaluateScript(
+    JSContextRef ctx,
+    JSStringRef script,
+    JSStringRef sourceURL,
+    const char* cachePath = nullptr);
+
 } }
diff --git a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp
index 64f47b00bc3d..e1e2d8b4bdcd 100644
--- a/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCLegacyProfiler.cpp
@@ -7,6 +7,7 @@
 #include <jsc_legacy_profiler.h>
 #include "JSCHelpers.h"
 #include "JSCLegacyProfiler.h"
+#include "Value.h"
 
 static JSValueRef nativeProfilerStart(
     JSContextRef ctx,
@@ -16,12 +17,20 @@ static JSValueRef nativeProfilerStart(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (argumentCount < 1) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeProfilerStart: requires at least 1 argument");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
-  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
+  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception);
+  #if WITH_JSC_INTERNAL
+  JSStartProfiling(ctx, title, false);
+  #else
   JSStartProfiling(ctx, title);
+  #endif
   JSStringRelease(title);
   return JSValueMakeUndefined(ctx);
 }
@@ -34,12 +43,24 @@ static JSValueRef nativeProfilerEnd(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (argumentCount < 1) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeProfilerEnd: requires at least 1 argument");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
-  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL);
-  JSEndProfilingAndRender(ctx, title, "/sdcard/profile.json");
+  std::string writeLocation("/sdcard/");
+  if (argumentCount > 1) {
+    JSStringRef fileName = JSValueToStringCopy(ctx, arguments[1], exception);
+    writeLocation += facebook::react::String::ref(fileName).str();
+    JSStringRelease(fileName);
+  } else {
+    writeLocation += "profile.json";
+  }
+  JSStringRef title = JSValueToStringCopy(ctx, arguments[0], exception);
+  JSEndProfilingAndRender(ctx, title, writeLocation.c_str());
   JSStringRelease(title);
   return JSValueMakeUndefined(ctx);
 }
diff --git a/ReactAndroid/src/main/jni/react/JSCMemory.cpp b/ReactAndroid/src/main/jni/react/JSCMemory.cpp
new file mode 100644
index 000000000000..f0316bd2a4a8
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/JSCMemory.cpp
@@ -0,0 +1,157 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include <stdio.h>
+#include <string.h>
+#include <JavaScriptCore/JavaScript.h>
+#include <JavaScriptCore/API/JSProfilerPrivate.h>
+#include "JSCHelpers.h"
+
+#include "Value.h"
+
+#ifdef WITH_FB_MEMORY_PROFILING
+
+static JSValueRef nativeEnableAllocationTag(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+
+  if (argumentCount < 1) {
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeEnableAllocationTag requires a single boolean argument");
+    }
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSEnableAllocationTag(ctx, JSValueToBoolean(ctx, arguments[0]));
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeAllocationPushTag(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  std::string marker;
+
+  if (argumentCount < 1) {
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeAllocationPushTag requires at least 1 argument");
+    }
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSStringRef tag = JSValueToStringCopy(ctx, arguments[0], exception);
+  JSPushAllocationTag(ctx, facebook::react::String::ref(tag).str().c_str());
+  JSStringRelease(tag);
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeAllocationPopTag(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  JSPopAllocationTag(ctx);
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeForceSyncGC(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  JSSynchronousGarbageCollectForDebugging(ctx);
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeCaptureStart(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  if (argumentCount < 1) {
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeCaptureStart requires at least 1 argument");
+    }
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSStringRef outputFilename = JSValueToStringCopy(ctx, arguments[0], exception);
+  std::string finalFilename =
+    std::string("/sdcard/") +
+    facebook::react::String::ref(outputFilename).str();
+  JSHeapCaptureStart(ctx, finalFilename.c_str());
+  JSStringRelease(outputFilename);
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeCaptureEnd(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  JSHeapCaptureEnd(ctx);
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeHeapDump(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  if (argumentCount < 1) {
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeHeapDump requires at least 1 argument");
+    }
+    return JSValueMakeUndefined(ctx);
+  }
+
+  JSStringRef outputFilename = JSValueToStringCopy(ctx, arguments[0], exception);
+  std::string finalFilename =
+    std::string("/sdcard/") +
+    facebook::react::String::ref(outputFilename).str();
+  JSHeapDump(ctx, finalFilename.c_str());
+  JSStringRelease(outputFilename);
+  return JSValueMakeUndefined(ctx);
+}
+#endif
+
+namespace facebook {
+namespace react {
+
+void addNativeMemoryHooks(JSGlobalContextRef ctx) {
+#ifdef WITH_FB_MEMORY_PROFILING
+  installGlobalFunction(ctx, "nativeEnableAllocationTag", nativeEnableAllocationTag);
+  installGlobalFunction(ctx, "nativeAllocationPushTag", nativeAllocationPushTag);
+  installGlobalFunction(ctx, "nativeAllocationPopTag", nativeAllocationPopTag);
+  installGlobalFunction(ctx, "nativeForceSyncGC", nativeForceSyncGC);
+  installGlobalFunction(ctx, "nativeCaptureStart", nativeCaptureStart);
+  installGlobalFunction(ctx, "nativeCaptureEnd", nativeCaptureEnd);
+  installGlobalFunction(ctx, "nativeHeapDump", nativeHeapDump);
+#endif
+}
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCMemory.h b/ReactAndroid/src/main/jni/react/JSCMemory.h
new file mode 100644
index 000000000000..6e05bba709ec
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/JSCMemory.h
@@ -0,0 +1,11 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <JavaScriptCore/JSContextRef.h>
+namespace facebook {
+namespace react {
+
+void addNativeMemoryHooks(JSGlobalContextRef ctx);
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCTracing.cpp b/ReactAndroid/src/main/jni/react/JSCTracing.cpp
index fa786005ef62..70d79d478691 100644
--- a/ReactAndroid/src/main/jni/react/JSCTracing.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCTracing.cpp
@@ -2,6 +2,7 @@
 
 #include <algorithm>
 #include <JavaScriptCore/JavaScript.h>
+#include <JavaScriptCore/API/JSProfilerPrivate.h>
 #include <fbsystrace.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -9,6 +10,8 @@
 
 using std::min;
 
+static const char *ENABLED_FBSYSTRACE_PROFILE_NAME = "__fbsystrace__";
+
 static uint64_t tagFromJSValue(
     JSContextRef ctx,
     JSValueRef value,
@@ -92,7 +95,11 @@ static JSValueRef nativeTraceBeginSection(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (FBSYSTRACE_UNLIKELY(argumentCount < 2)) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeTraceBeginSection: requires at least 2 arguments");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
@@ -128,7 +135,11 @@ static JSValueRef nativeTraceEndSection(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeTraceEndSection: requires at least 1 argument");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
@@ -160,6 +171,7 @@ static JSValueRef nativeTraceEndSection(
 
 static JSValueRef beginOrEndAsync(
     bool isEnd,
+    bool isFlow,
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
@@ -167,7 +179,11 @@ static JSValueRef beginOrEndAsync(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "beginOrEndAsync: requires at least 3 arguments");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
@@ -181,7 +197,7 @@ static JSValueRef beginOrEndAsync(
 
   // This uses an if-then-else instruction in ARMv7, which should be cheaper
   // than a full branch.
-  buf[pos++] = (isEnd ? 'F' : 'S');
+  buf[pos++] = ((isFlow) ? (isEnd ? 'f' : 's') : (isEnd ? 'F' : 'S'));
   pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d|", getpid());
   // Skip the overflow check here because the int will be small.
   pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
@@ -190,7 +206,8 @@ static JSValueRef beginOrEndAsync(
   // I tried some trickery to avoid a branch here, but gcc did not cooperate.
   // We could consider changing the implementation to be lest branchy in the
   // future.
-  if (!isEnd) {
+  // This is not required for flow use an or to avoid introducing another branch
+  if (!(isEnd | isFlow)) {
     buf[pos++] = '<';
     buf[pos++] = '0';
     buf[pos++] = '>';
@@ -216,6 +233,47 @@ static JSValueRef beginOrEndAsync(
   return JSValueMakeUndefined(ctx);
 }
 
+static JSValueRef stageAsync(
+    bool isFlow,
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) {
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "stageAsync: requires at least 4 arguments");
+    }
+    return JSValueMakeUndefined(ctx);
+  }
+
+  uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
+  if (!fbsystrace_is_tracing(tag)) {
+    return JSValueMakeUndefined(ctx);
+  }
+
+  char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
+  size_t pos = 0;
+
+  buf[pos++] = (isFlow ? 't' : 'T');
+  pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d", getpid());
+  // Skip the overflow check here because the int will be small.
+
+  // Arguments are section name, cookie, and stage name.
+  // All added together, they still cannot cause an overflow.
+  for (int i = 1; i < 4; i++) {
+    buf[pos++] = '|';
+    pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
+  }
+
+  fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
+
+  return JSValueMakeUndefined(ctx);
+}
+
 static JSValueRef nativeTraceBeginAsyncSection(
     JSContextRef ctx,
     JSObjectRef function,
@@ -225,6 +283,7 @@ static JSValueRef nativeTraceBeginAsyncSection(
     JSValueRef* exception) {
   return beginOrEndAsync(
       false /* isEnd */,
+      false /* isFlow */,
       ctx,
       function,
       thisObject,
@@ -242,6 +301,7 @@ static JSValueRef nativeTraceEndAsyncSection(
     JSValueRef* exception) {
   return beginOrEndAsync(
       true /* isEnd */,
+      false /* isFlow */,
       ctx,
       function,
       thisObject,
@@ -257,32 +317,67 @@ static JSValueRef nativeTraceAsyncSectionStage(
     size_t argumentCount,
     const JSValueRef arguments[],
     JSValueRef* exception) {
-  if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) {
-    // Could raise an exception here.
-    return JSValueMakeUndefined(ctx);
-  }
-
-  uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
-  if (!fbsystrace_is_tracing(tag)) {
-    return JSValueMakeUndefined(ctx);
-  }
-
-  char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
-  size_t pos = 0;
-
-  pos += snprintf(buf + pos, sizeof(buf) - pos, "T|%d", getpid());
-  // Skip the overflow check here because the int will be small.
+  return stageAsync(
+      false /* isFlow */,
+      ctx,
+      function,
+      thisObject,
+      argumentCount,
+      arguments,
+      exception);
+}
 
-  // Arguments are section name, cookie, and stage name.
-  // All added together, they still cannot cause an overflow.
-  for (int i = 1; i < 4; i++) {
-    buf[pos++] = '|';
-    pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
-  }
+static JSValueRef nativeTraceBeginAsyncFlow(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  return beginOrEndAsync(
+      false /* isEnd */,
+      true /* isFlow */,
+      ctx,
+      function,
+      thisObject,
+      argumentCount,
+      arguments,
+      exception);
+}
 
-  fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
+static JSValueRef nativeTraceEndAsyncFlow(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  return beginOrEndAsync(
+      true /* isEnd */,
+      true /* isFlow */,
+      ctx,
+      function,
+      thisObject,
+      argumentCount,
+      arguments,
+      exception);
+}
 
-  return JSValueMakeUndefined(ctx);
+static JSValueRef nativeTraceAsyncFlowStage(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  return stageAsync(
+      true /* isFlow */,
+      ctx,
+      function,
+      thisObject,
+      argumentCount,
+      arguments,
+      exception);
 }
 
 static JSValueRef nativeTraceCounter(
@@ -293,7 +388,11 @@ static JSValueRef nativeTraceCounter(
     const JSValueRef arguments[],
     JSValueRef* exception) {
   if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
-    // Could raise an exception here.
+    if (exception) {
+      *exception = facebook::react::makeJSCException(
+        ctx,
+        "nativeTraceCounter: requires at least 3 arguments");
+    }
     return JSValueMakeUndefined(ctx);
   }
 
@@ -313,15 +412,66 @@ static JSValueRef nativeTraceCounter(
   return JSValueMakeUndefined(ctx);
 }
 
+static JSValueRef nativeTraceBeginLegacy(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
+    uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
+    if (!fbsystrace_is_tracing(tag)) {
+      return JSValueMakeUndefined(ctx);
+    }
+  }
+
+  JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
+  #if WITH_JSC_INTERNAL
+  JSStartProfiling(ctx, title, true);
+  #else
+  JSStartProfiling(ctx, title);
+  #endif
+  JSStringRelease(title);
+
+  return JSValueMakeUndefined(ctx);
+}
+
+static JSValueRef nativeTraceEndLegacy(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
+    uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
+    if (!fbsystrace_is_tracing(tag)) {
+      return JSValueMakeUndefined(ctx);
+    }
+  }
+
+  JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
+  JSEndProfiling(ctx, title);
+  JSStringRelease(title);
+
+  return JSValueMakeUndefined(ctx);
+}
+
 namespace facebook {
 namespace react {
 
 void addNativeTracingHooks(JSGlobalContextRef ctx) {
   installGlobalFunction(ctx, "nativeTraceBeginSection", nativeTraceBeginSection);
   installGlobalFunction(ctx, "nativeTraceEndSection", nativeTraceEndSection);
+  installGlobalFunction(ctx, "nativeTraceBeginLegacy", nativeTraceBeginLegacy);
+  installGlobalFunction(ctx, "nativeTraceEndLegacy", nativeTraceEndLegacy);
   installGlobalFunction(ctx, "nativeTraceBeginAsyncSection", nativeTraceBeginAsyncSection);
   installGlobalFunction(ctx, "nativeTraceEndAsyncSection", nativeTraceEndAsyncSection);
   installGlobalFunction(ctx, "nativeTraceAsyncSectionStage", nativeTraceAsyncSectionStage);
+  installGlobalFunction(ctx, "nativeTraceBeginAsyncFlow", nativeTraceBeginAsyncFlow);
+  installGlobalFunction(ctx, "nativeTraceEndAsyncFlow", nativeTraceEndAsyncFlow);
+  installGlobalFunction(ctx, "nativeTraceAsyncFlowStage", nativeTraceAsyncFlowStage);
   installGlobalFunction(ctx, "nativeTraceCounter", nativeTraceCounter);
 }
 
diff --git a/ReactAndroid/src/main/jni/react/JSModulesUnbundle.h b/ReactAndroid/src/main/jni/react/JSModulesUnbundle.h
new file mode 100644
index 000000000000..d1c2f12547f1
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/JSModulesUnbundle.h
@@ -0,0 +1,35 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <stdexcept>
+
+#include "noncopyable.h"
+
+namespace facebook {
+namespace react {
+
+class JSModulesUnbundle : noncopyable {
+  /**
+   * Represents the set of JavaScript modules that the application consists of.
+   * The source code of each module can be retrieved by module ID.
+   *
+   * The class is non-copyable because copying instances might involve copying
+   * several megabytes of memory.
+   */
+public:
+  class ModuleNotFound : public std::out_of_range {
+    using std::out_of_range::out_of_range;
+  };
+  struct Module {
+    std::string name;
+    std::string code;
+  };
+  virtual ~JSModulesUnbundle() {}
+  virtual Module getModule(uint32_t moduleId) const = 0;
+};
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/react/MessageQueueThread.h b/ReactAndroid/src/main/jni/react/MessageQueueThread.h
new file mode 100644
index 000000000000..f6352ab0d90a
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/MessageQueueThread.h
@@ -0,0 +1,39 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+
+namespace facebook {
+namespace react {
+
+class MessageQueueThread {
+ public:
+  virtual ~MessageQueueThread() {}
+  virtual void runOnQueue(std::function<void()>&&) = 0;
+  virtual bool isOnThread() = 0;
+  // quitSynchronous() should synchronously ensure that no further tasks will run on the queue.
+  virtual void quitSynchronous() = 0;
+
+  void runOnQueueSync(std::function<void()>&& runnable) {
+    std::mutex signalMutex;
+    std::condition_variable signalCv;
+    bool runnableComplete = false;
+
+    runOnQueue([&] () mutable {
+      std::lock_guard<std::mutex> lock(signalMutex);
+
+      runnable();
+      runnableComplete = true;
+
+      signalCv.notify_one();
+    });
+
+    std::unique_lock<std::mutex> lock(signalMutex);
+    signalCv.wait(lock, [&runnableComplete] { return runnableComplete; });
+  }
+};
+
+}}
diff --git a/ReactAndroid/src/main/jni/react/MethodCall.cpp b/ReactAndroid/src/main/jni/react/MethodCall.cpp
index ce12d368381e..2782e05b6f9f 100644
--- a/ReactAndroid/src/main/jni/react/MethodCall.cpp
+++ b/ReactAndroid/src/main/jni/react/MethodCall.cpp
@@ -2,9 +2,8 @@
 
 #include "MethodCall.h"
 
-#include <jni/fbjni.h>
-
 #include <folly/json.h>
+#include <stdexcept>
 
 namespace facebook {
 namespace react {
@@ -12,6 +11,7 @@ namespace react {
 #define REQUEST_MODULE_IDS 0
 #define REQUEST_METHOD_IDS 1
 #define REQUEST_PARAMSS 2
+#define REQUEST_CALLID 3
 
 std::vector<MethodCall> parseMethodCalls(const std::string& json) {
   folly::dynamic jsonData = folly::parseJson(json);
@@ -21,37 +21,50 @@ std::vector<MethodCall> parseMethodCalls(const std::string& json) {
   }
 
   if (!jsonData.isArray()) {
-    jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,
-                               "Did not get valid calls back from JS: %s", jsonData.typeName());
+    throw std::invalid_argument(
+        folly::to<std::string>("Did not get valid calls back from JS: ", jsonData.typeName()));
   }
 
   if (jsonData.size() < REQUEST_PARAMSS + 1) {
-    jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,
-                               "Did not get valid calls back from JS: size == %d", jsonData.size());
+    throw std::invalid_argument(
+          folly::to<std::string>("Did not get valid calls back from JS: size == ", jsonData.size()));
   }
 
   auto moduleIds = jsonData[REQUEST_MODULE_IDS];
   auto methodIds = jsonData[REQUEST_METHOD_IDS];
   auto params = jsonData[REQUEST_PARAMSS];
+  int  callId = -1;
 
   if (!moduleIds.isArray() || !methodIds.isArray() || !params.isArray()) {
-    jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,
-                               "Did not get valid calls back from JS: %s",
-                               json.c_str());
+    throw std::invalid_argument(
+          folly::to<std::string>("Did not get valid calls back from JS: ", json.c_str()));
+  }
+
+  if (jsonData.size() > REQUEST_CALLID) {
+    if (!jsonData[REQUEST_CALLID].isInt()) {
+      throw std::invalid_argument(
+          folly::to<std::string>("Did not get valid calls back from JS: %s", json.c_str()));
+    } else {
+      callId = jsonData[REQUEST_CALLID].getInt();
+    }
   }
 
   std::vector<MethodCall> methodCalls;
   for (size_t i = 0; i < moduleIds.size(); i++) {
     auto paramsValue = params[i];
     if (!paramsValue.isArray()) {
-      jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,
-                                 "Call argument isn't an array");
+      throw std::invalid_argument(
+          folly::to<std::string>("Call argument isn't an array"));
     }
 
     methodCalls.emplace_back(
       moduleIds[i].getInt(),
       methodIds[i].getInt(),
-      std::move(params[i]));
+      std::move(params[i]),
+      callId);
+
+    // only incremement callid if contains valid callid as callid is optional
+    callId += (callId != -1) ? 1 : 0;
   }
 
   return methodCalls;
diff --git a/ReactAndroid/src/main/jni/react/MethodCall.h b/ReactAndroid/src/main/jni/react/MethodCall.h
index 7bb47dca9d7c..02e86eb98760 100644
--- a/ReactAndroid/src/main/jni/react/MethodCall.h
+++ b/ReactAndroid/src/main/jni/react/MethodCall.h
@@ -15,11 +15,13 @@ struct MethodCall {
   int moduleId;
   int methodId;
   folly::dynamic arguments;
+  int callId;
 
-  MethodCall(int mod, int meth, folly::dynamic args)
+  MethodCall(int mod, int meth, folly::dynamic args, int cid)
     : moduleId(mod)
     , methodId(meth)
-    , arguments(std::move(args)) {}
+    , arguments(std::move(args))
+    , callId(cid) {}
 };
 
 std::vector<MethodCall> parseMethodCalls(const std::string& json);
diff --git a/ReactAndroid/src/main/jni/react/Platform.cpp b/ReactAndroid/src/main/jni/react/Platform.cpp
new file mode 100644
index 000000000000..ace85bd92423
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/Platform.cpp
@@ -0,0 +1,29 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "Platform.h"
+
+namespace facebook {
+namespace react {
+
+namespace ReactMarker {
+LogMarker logMarker;
+};
+
+namespace MessageQueues {
+GetCurrentMessageQueueThread getCurrentMessageQueueThread;
+};
+
+namespace WebWorkerUtil {
+WebWorkerQueueFactory createWebWorkerThread;
+LoadScriptFromAssets loadScriptFromAssets;
+};
+
+namespace PerfLogging {
+InstallNativeHooks installNativeHooks;
+};
+
+namespace JSLogging {
+JSCNativeHook nativeHook = nullptr;
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/Platform.h b/ReactAndroid/src/main/jni/react/Platform.h
new file mode 100644
index 000000000000..608fbc1a854d
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/Platform.h
@@ -0,0 +1,49 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "MessageQueueThread.h"
+
+#include <JavaScriptCore/JSContextRef.h>
+
+namespace facebook {
+namespace react {
+
+namespace ReactMarker {
+using LogMarker = std::function<void(const std::string&)>;
+extern LogMarker logMarker;
+};
+
+namespace MessageQueues {
+using GetCurrentMessageQueueThread = std::function<std::unique_ptr<MessageQueueThread>()>;
+extern GetCurrentMessageQueueThread getCurrentMessageQueueThread;
+};
+
+namespace WebWorkerUtil {
+using WebWorkerQueueFactory = std::function<std::unique_ptr<MessageQueueThread>(int id, MessageQueueThread* ownerMessageQueue)>;
+extern WebWorkerQueueFactory createWebWorkerThread;
+
+using LoadScriptFromAssets = std::function<std::string(const std::string& assetName)>;
+extern LoadScriptFromAssets loadScriptFromAssets;
+};
+
+namespace PerfLogging {
+using InstallNativeHooks = std::function<void(JSGlobalContextRef)>;
+extern InstallNativeHooks installNativeHooks;
+};
+
+namespace JSLogging {
+  using JSCNativeHook = JSValueRef (*) (
+      JSContextRef ctx,
+      JSObjectRef function,
+      JSObjectRef thisObject,
+      size_t argumentCount,
+      const JSValueRef arguments[], JSValueRef *exception);
+  extern JSCNativeHook nativeHook;
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/Value.cpp b/ReactAndroid/src/main/jni/react/Value.cpp
index 7d2d8667396f..6c1c393ded78 100644
--- a/ReactAndroid/src/main/jni/react/Value.cpp
+++ b/ReactAndroid/src/main/jni/react/Value.cpp
@@ -2,8 +2,7 @@
 
 #include "Value.h"
 
-#include <jni/fbjni.h>
-#include <fb/log.h>
+#include "JSCHelpers.h"
 
 namespace facebook {
 namespace react {
@@ -21,12 +20,6 @@ Value::Value(Value&& other) :
   other.m_value = nullptr;
 }
 
-Value::~Value() {
-  if (m_value) {
-    JSValueUnprotect(m_context, m_value);
-  }
-}
-
 JSContextRef Value::context() const {
   return m_context;
 }
@@ -35,19 +28,115 @@ std::string Value::toJSONString(unsigned indent) const {
   JSValueRef exn;
   auto stringToAdopt = JSValueCreateJSONString(m_context, m_value, indent, &exn);
   if (stringToAdopt == nullptr) {
-    JSValueProtect(m_context, exn);
     std::string exceptionText = Value(m_context, exn).toString().str();
-    jni::throwNewJavaException(
-        "java/lang/IllegalArgumentException",
-        "Exception creating JSON string: %s",
-        exceptionText.c_str());
+    throwJSExecutionException("Exception creating JSON string: %s", exceptionText.c_str());
   }
   return String::adopt(stringToAdopt).str();
 }
 
 /* static */
-Value Value::fromJSON(JSContextRef& ctx, const String& json) {
-  return Value(ctx, JSValueMakeFromJSONString(ctx, json));
+Value Value::fromJSON(JSContextRef ctx, const String& json) {
+  auto result = JSValueMakeFromJSONString(ctx, json);
+  if (!result) {
+    throwJSExecutionException("Failed to create String from JSON");
+  }
+  return Value(ctx, result);
+}
+
+Object Value::asObject() {
+  JSValueRef exn;
+  JSObjectRef jsObj = JSValueToObject(context(), m_value, &exn);
+  if (!jsObj) {
+    std::string exceptionText = Value(m_context, exn).toString().str();
+    throwJSExecutionException("Failed to convert to object: %s", exceptionText.c_str());
+  }
+  Object ret = Object(context(), jsObj);
+  m_value = nullptr;
+  return std::move(ret);
+}
+
+Object::operator Value() const {
+  return Value(m_context, m_obj);
+}
+
+Value Object::callAsFunction(int nArgs, JSValueRef args[]) {
+  JSValueRef exn;
+  JSValueRef result = JSObjectCallAsFunction(m_context, m_obj, NULL, nArgs, args, &exn);
+  if (!result) {
+    std::string exceptionText = Value(m_context, exn).toString().str();
+    throwJSExecutionException("Exception calling JS function: %s", exceptionText.c_str());
+  }
+  return Value(m_context, result);
+}
+
+Value Object::getProperty(const String& propName) const {
+  JSValueRef exn;
+  JSValueRef property = JSObjectGetProperty(m_context, m_obj, propName, &exn);
+  if (!property) {
+    std::string exceptionText = Value(m_context, exn).toString().str();
+    throwJSExecutionException("Failed to get property: %s", exceptionText.c_str());
+  }
+  return Value(m_context, property);
+}
+
+Value Object::getPropertyAtIndex(unsigned index) const {
+  JSValueRef exn;
+  JSValueRef property = JSObjectGetPropertyAtIndex(m_context, m_obj, index, &exn);
+  if (!property) {
+    std::string exceptionText = Value(m_context, exn).toString().str();
+    throwJSExecutionException("Failed to get property at index %u: %s", index, exceptionText.c_str());
+  }
+  return Value(m_context, property);
+}
+
+Value Object::getProperty(const char *propName) const {
+  return getProperty(String(propName));
+}
+
+void Object::setProperty(const String& propName, const Value& value) const {
+  JSValueRef exn = NULL;
+  JSObjectSetProperty(m_context, m_obj, propName, value, kJSPropertyAttributeNone, &exn);
+  if (exn) {
+    std::string exceptionText = Value(m_context, exn).toString().str();
+    throwJSExecutionException("Failed to set property: %s", exceptionText.c_str());
+  }
+}
+
+void Object::setProperty(const char *propName, const Value& value) const {
+  setProperty(String(propName), value);
+}
+
+std::vector<std::string> Object::getPropertyNames() const {
+  std::vector<std::string> names;
+  auto namesRef = JSObjectCopyPropertyNames(m_context, m_obj);
+  size_t count = JSPropertyNameArrayGetCount(namesRef);
+  for (size_t i = 0; i < count; i++) {
+    auto string = String::ref(JSPropertyNameArrayGetNameAtIndex(namesRef, i));
+    names.emplace_back(string.str());
+  }
+  JSPropertyNameArrayRelease(namesRef);
+  return names;
+}
+
+std::unordered_map<std::string, std::string> Object::toJSONMap() const {
+  std::unordered_map<std::string, std::string> map;
+  auto namesRef = JSObjectCopyPropertyNames(m_context, m_obj);
+  size_t count = JSPropertyNameArrayGetCount(namesRef);
+  for (size_t i = 0; i < count; i++) {
+    auto key = String::ref(JSPropertyNameArrayGetNameAtIndex(namesRef, i));
+    map.emplace(key.str(), getProperty(key).toJSONString());
+  }
+  JSPropertyNameArrayRelease(namesRef);
+  return map;
+}
+
+/* static */
+Object Object::create(JSContextRef ctx) {
+  JSObjectRef newObj = JSObjectMake(
+      ctx,
+      NULL, // create instance of default object class
+      NULL); // no private data
+  return Object(ctx, newObj);
 }
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/Value.h b/ReactAndroid/src/main/jni/react/Value.h
index bb10f9a85713..5fcb89e3812f 100644
--- a/ReactAndroid/src/main/jni/react/Value.h
+++ b/ReactAndroid/src/main/jni/react/Value.h
@@ -4,14 +4,24 @@
 
 #include <memory>
 #include <sstream>
+#include <unordered_map>
+#include <vector>
+
+#include <JavaScriptCore/JSObjectRef.h>
 #include <JavaScriptCore/JSRetainPtr.h>
 #include <JavaScriptCore/JSStringRef.h>
 #include <JavaScriptCore/JSValueRef.h>
-#include <fb/noncopyable.h>
+
+#include "noncopyable.h"
+
+#if WITH_FBJSCEXTENSIONS
+#include <jsc_stringref.h>
+#endif
 
 namespace facebook {
 namespace react {
 
+class Value;
 class Context;
 
 class String : public noncopyable {
@@ -19,9 +29,11 @@ class String : public noncopyable {
   explicit String(const char* utf8) :
     m_string(Adopt, JSStringCreateWithUTF8CString(utf8))
   {}
+
   String(String&& other) :
     m_string(Adopt, other.m_string.leakRef())
   {}
+
   String(const String& other) :
     m_string(other.m_string)
   {}
@@ -53,6 +65,14 @@ class String : public noncopyable {
     return JSStringIsEqualToUTF8CString(m_string.get(), utf8);
   }
 
+  static String createExpectingAscii(std::string const &utf8) {
+  #if WITH_FBJSCEXTENSIONS
+    return String(Adopt, JSStringCreateWithUTF8CStringExpectAscii(utf8.c_str(), utf8.size()));
+  #else
+    return String(Adopt, JSStringCreateWithUTF8CString(utf8.c_str()));
+  #endif
+  }
+
   static String ref(JSStringRef string) {
     return String(string);
   }
@@ -73,11 +93,74 @@ class String : public noncopyable {
   JSRetainPtr<JSStringRef> m_string;
 };
 
+class Object : public noncopyable {
+public:
+  Object(JSContextRef context, JSObjectRef obj) :
+    m_context(context),
+    m_obj(obj)
+  {}
+
+  Object(Object&& other) :
+      m_context(other.m_context),
+      m_obj(other.m_obj),
+      m_isProtected(other.m_isProtected) {
+    other.m_obj = nullptr;
+    other.m_isProtected = false;
+  }
+
+  ~Object() {
+    if (m_isProtected && m_obj) {
+      JSValueUnprotect(m_context, m_obj);
+    }
+  }
+
+  operator JSObjectRef() const {
+    return m_obj;
+  }
+
+  operator Value() const;
+
+  bool isFunction() const {
+    return JSObjectIsFunction(m_context, m_obj);
+  }
+
+  Value callAsFunction(int nArgs, JSValueRef args[]);
+
+  Value getProperty(const String& propName) const;
+  Value getProperty(const char *propName) const;
+  Value getPropertyAtIndex(unsigned index) const;
+  void setProperty(const String& propName, const Value& value) const;
+  void setProperty(const char *propName, const Value& value) const;
+  std::vector<std::string> getPropertyNames() const;
+  std::unordered_map<std::string, std::string> toJSONMap() const;
+
+  void makeProtected() {
+    if (!m_isProtected && m_obj) {
+      JSValueProtect(m_context, m_obj);
+      m_isProtected = true;
+    }
+  }
+
+  static Object getGlobalObject(JSContextRef ctx) {
+    auto globalObj = JSContextGetGlobalObject(ctx);
+    return Object(ctx, globalObj);
+  }
+
+  /**
+   * Creates an instance of the default object class.
+   */
+  static Object create(JSContextRef ctx);
+
+private:
+  JSContextRef m_context;
+  JSObjectRef m_obj;
+  bool m_isProtected = false;
+};
+
 class Value : public noncopyable {
 public:
   Value(JSContextRef context, JSValueRef value);
   Value(Value&&);
-  ~Value();
 
   operator JSValueRef() const {
     return m_value;
@@ -99,6 +182,10 @@ class Value : public noncopyable {
     return JSValueIsNull(context(), m_value);
   }
 
+  bool isUndefined() const {
+    return JSValueIsUndefined(context(), m_value);
+  }
+
   double asNumber() const {
     if (isNumber()) {
       return JSValueToNumber(context(), m_value, nullptr);
@@ -119,6 +206,8 @@ class Value : public noncopyable {
     return JSValueIsObject(context(), m_value);
   }
 
+  Object asObject();
+
   bool isString() const {
     return JSValueIsString(context(), m_value);
   }
@@ -128,7 +217,7 @@ class Value : public noncopyable {
   }
 
   std::string toJSONString(unsigned indent = 0) const;
-  static Value fromJSON(JSContextRef& ctx, const String& json);
+  static Value fromJSON(JSContextRef ctx, const String& json);
 protected:
   JSContextRef context() const;
   JSContextRef m_context;
diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk
index 1a829fad5aa4..7d46f9ef8baf 100644
--- a/ReactAndroid/src/main/jni/react/jni/Android.mk
+++ b/ReactAndroid/src/main/jni/react/jni/Android.mk
@@ -5,10 +5,15 @@ include $(CLEAR_VARS)
 LOCAL_MODULE := reactnativejni
 
 LOCAL_SRC_FILES := \
+	JExecutorToken.cpp \
+  JMessageQueueThread.cpp \
+  JSCPerfLogging.cpp \
+  JSLoader.cpp \
+  JSLogging.cpp \
+  JniJSModulesUnbundle.cpp \
+  NativeArray.cpp \
   OnLoad.cpp \
   ProxyExecutor.cpp \
-  NativeArray.cpp \
-  JSLoader.cpp \
 
 LOCAL_C_INCLUDES := $(LOCAL_PATH)
 
@@ -27,4 +32,4 @@ $(call import-module,react)
 $(call import-module,jsc)
 $(call import-module,folly)
 $(call import-module,jni)
-$(call import-module,jsc)
\ No newline at end of file
+$(call import-module,jsc)
diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK
index b70ecf239e74..34c030641a8f 100644
--- a/ReactAndroid/src/main/jni/react/jni/BUCK
+++ b/ReactAndroid/src/main/jni/react/jni/BUCK
@@ -3,20 +3,59 @@ include_defs('//ReactAndroid/DEFS')
 # We depend on JSC, support the same platforms
 SUPPORTED_PLATFORMS = '^android-(armv7|x86)$'
 
-cxx_library(
-  name = 'jni',
+DEPS = [
+  '//native/jni:jni',
+  '//native/third-party/android-ndk:android',
+  '//xplat/folly:molly',
+]
+
+def jni_library(**kwargs):
+  cxx_library(
+    name='jni',
+    visibility = [
+      'PUBLIC',
+    ],
+    deps = DEPS + JSC_DEPS + [
+      react_native_target('jni/react:react'),
+    ],
+    **kwargs
+  )
+
+  cxx_library(
+    name='jni-internal',
+    visibility = [INTERNAL_APP],
+    deps = DEPS + JSC_INTERNAL_DEPS + [
+      react_native_target('jni/react:react-internal'),
+    ],
+    **kwargs
+  )
+
+jni_library(
   soname = 'libreactnativejni.so',
   header_namespace = 'react/jni',
   supported_platforms_regex = SUPPORTED_PLATFORMS,
   srcs = [
+    'JExecutorToken.cpp',
+    'JMessageQueueThread.cpp',
+    'JSCPerfLogging.cpp',
+    'JSLoader.cpp',
+    'JniJSModulesUnbundle.cpp',
     'NativeArray.cpp',
     'OnLoad.cpp',
     'ProxyExecutor.cpp',
-    'JSLoader.cpp',
+    'JSLogging.cpp',
   ],
   headers = [
-    'ProxyExecutor.h',
     'JSLoader.h',
+    'JExecutorToken.h',
+    'JExecutorTokenFactory.h',
+    'JMessageQueueThread.h',
+    'JNativeRunnable.h',
+    'JniJSModulesUnbundle.h',
+    'JSCPerfLogging.h',
+    'JSLogging.h',
+    'ProxyExecutor.h',
+    'WebWorkers.h',
   ],
   exported_headers = [
     'NativeArray.h',
@@ -25,6 +64,7 @@ cxx_library(
   preprocessor_flags = [
     '-DLOG_TAG="ReactNativeJNI"',
     '-DWITH_FBSYSTRACE=1',
+    '-DWITH_FBJSCEXTENSIONS=1',
   ],
   compiler_flags = [
     '-Wall',
@@ -34,18 +74,6 @@ cxx_library(
     '-fvisibility=hidden',
     '-frtti',
   ],
-  visibility = [
-    react_native_target('java/com/facebook/react/bridge:bridge'),
-    react_native_target('jni/react/...'),
-    react_native_dep('native/react/...')
-  ],
-  deps = [
-    react_native_target('jni/react:react'),
-    '//native/jni:jni',
-    '//native/third-party/jsc:jsc',
-    '//native/third-party/jsc:jsc_legacy_profiler',
-    '//xplat/folly:json',
-  ],
 )
 
 project_config(
diff --git a/ReactAndroid/src/main/jni/react/jni/JExecutorToken.cpp b/ReactAndroid/src/main/jni/react/jni/JExecutorToken.cpp
new file mode 100644
index 000000000000..97e80948b196
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JExecutorToken.cpp
@@ -0,0 +1,20 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "JExecutorToken.h"
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+ExecutorToken JExecutorToken::getExecutorToken(alias_ref<JExecutorToken::javaobject> jobj) {
+  std::lock_guard<std::mutex> guard(createTokenGuard_);
+  auto sharedOwner = owner_.lock();
+  if (!sharedOwner) {
+    sharedOwner = std::shared_ptr<PlatformExecutorToken>(new JExecutorTokenHolder(jobj));
+    owner_ = sharedOwner;
+  }
+  return ExecutorToken(sharedOwner);
+}
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/jni/JExecutorToken.h b/ReactAndroid/src/main/jni/react/jni/JExecutorToken.h
new file mode 100644
index 000000000000..ac348f0ecd73
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JExecutorToken.h
@@ -0,0 +1,59 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <mutex>
+
+#include <jni/fbjni.h>
+
+#include <react/ExecutorToken.h>
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+class JExecutorTokenHolder;
+class JExecutorToken : public HybridClass<JExecutorToken> {
+public:
+  static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/ExecutorToken;";
+
+  ExecutorToken getExecutorToken(alias_ref<JExecutorToken::javaobject> jobj);
+
+private:
+  friend HybridBase;
+  friend JExecutorTokenHolder;
+
+  JExecutorToken() {}
+
+  std::weak_ptr<PlatformExecutorToken> owner_;
+  std::mutex createTokenGuard_;
+};
+
+/**
+ * Wrapper class to hold references to both the c++ and Java parts of the
+ * ExecutorToken object. The goal is to allow a reference to a token from either
+ * c++ or Java to keep both the Java object and c++ hybrid part alive. For c++
+ * references, we accomplish this by having JExecutorTokenHolder keep a reference
+ * to the Java object (which has a reference to the JExecutorToken hybrid part).
+ * For Java references, we allow the JExecutorTokenHolder to be deallocated if there
+ * are no references to it in c++ from a PlatformExecutorToken, but will dynamically
+ * create a new one in JExecutorToken.getExecutorToken if needed.
+ */
+class JExecutorTokenHolder : public PlatformExecutorToken, public noncopyable {
+public:
+  explicit JExecutorTokenHolder(alias_ref<JExecutorToken::javaobject> jobj) :
+    jobj_(make_global(jobj)),
+    impl_(cthis(jobj)) {
+  }
+
+  JExecutorToken::javaobject getJobj() {
+    return jobj_.get();
+  }
+
+private:
+  global_ref<JExecutorToken::javaobject> jobj_;
+  JExecutorToken *impl_;
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/jni/JExecutorTokenFactory.h b/ReactAndroid/src/main/jni/react/jni/JExecutorTokenFactory.h
new file mode 100644
index 000000000000..d7d923111395
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JExecutorTokenFactory.h
@@ -0,0 +1,24 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <jni/fbjni.h>
+#include <react/ExecutorTokenFactory.h>
+
+#include "JExecutorToken.h"
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+class JExecutorTokenFactory : public ExecutorTokenFactory {
+public:
+  virtual ExecutorToken createExecutorToken() const override {
+    auto jExecutorToken = JExecutorToken::newObjectCxxArgs();
+    auto jExecutorTokenNativePart = cthis(jExecutorToken);
+    return jExecutorTokenNativePart->getExecutorToken(jExecutorToken);
+  }
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.cpp b/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.cpp
new file mode 100644
index 000000000000..0c068ab19de4
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.cpp
@@ -0,0 +1,44 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "JMessageQueueThread.h"
+
+#include <fb/log.h>
+#include <folly/Memory.h>
+#include <jni/fbjni.h>
+
+#include "JNativeRunnable.h"
+
+namespace facebook {
+namespace react {
+
+JMessageQueueThread::JMessageQueueThread(alias_ref<JavaMessageQueueThread::javaobject> jobj) :
+    m_jobj(make_global(jobj)) {
+}
+
+void JMessageQueueThread::runOnQueue(std::function<void()>&& runnable) {
+  static auto method = JavaMessageQueueThread::javaClassStatic()->
+    getMethod<void(Runnable::javaobject)>("runOnQueue");
+  method(m_jobj, JNativeRunnable::newObjectCxxArgs(runnable).get());
+}
+
+bool JMessageQueueThread::isOnThread() {
+  static auto method = JavaMessageQueueThread::javaClassStatic()->
+    getMethod<jboolean()>("isOnThread");
+  return method(m_jobj);
+}
+
+void JMessageQueueThread::quitSynchronous() {
+  static auto method = JavaMessageQueueThread::javaClassStatic()->
+    getMethod<void()>("quitSynchronous");
+  method(m_jobj);
+}
+
+/* static */
+std::unique_ptr<JMessageQueueThread> JMessageQueueThread::currentMessageQueueThread() {
+  static auto method = MessageQueueThreadRegistry::javaClassStatic()->
+      getStaticMethod<JavaMessageQueueThread::javaobject()>("myMessageQueueThread");
+  return folly::make_unique<JMessageQueueThread>(method(MessageQueueThreadRegistry::javaClassStatic()));
+}
+
+} }
+
diff --git a/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.h b/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.h
new file mode 100644
index 000000000000..e81d5dead294
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JMessageQueueThread.h
@@ -0,0 +1,58 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <functional>
+
+#include <react/MessageQueueThread.h>
+
+#include <jni/fbjni.h>
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+class JavaMessageQueueThread : public jni::JavaClass<JavaMessageQueueThread> {
+public:
+  static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/queue/MessageQueueThread;";
+};
+
+class JMessageQueueThread : public MessageQueueThread {
+public:
+  JMessageQueueThread(alias_ref<JavaMessageQueueThread::javaobject> jobj);
+
+  /**
+   * Enqueues the given function to run on this MessageQueueThread.
+   */
+  void runOnQueue(std::function<void()>&& runnable) override;
+
+  /**
+   * Returns whether the currently executing thread is this MessageQueueThread.
+   */
+  bool isOnThread() override;
+
+  /**
+   * Synchronously quits the current MessageQueueThread. Can be called from any thread, but will
+   * block if not called on this MessageQueueThread.
+   */
+  void quitSynchronous() override;
+
+  JavaMessageQueueThread::javaobject jobj() {
+    return m_jobj.get();
+  }
+
+  /**
+   * Returns the current MessageQueueThread that owns this thread.
+   */
+  static std::unique_ptr<JMessageQueueThread> currentMessageQueueThread();
+private:
+  global_ref<JavaMessageQueueThread::javaobject> m_jobj;
+};
+
+class MessageQueueThreadRegistry : public jni::JavaClass<MessageQueueThreadRegistry> {
+public:
+  static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/queue/MessageQueueThreadRegistry;";
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/jni/JNativeRunnable.h b/ReactAndroid/src/main/jni/react/jni/JNativeRunnable.h
new file mode 100644
index 000000000000..5550af34299b
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JNativeRunnable.h
@@ -0,0 +1,44 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <functional>
+
+#include <jni.h>
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+class Runnable : public JavaClass<Runnable> {
+public:
+  static constexpr auto kJavaDescriptor = "Ljava/lang/Runnable;";
+};
+
+/**
+ * The c++ interface for the Java NativeRunnable class
+ */
+class JNativeRunnable : public HybridClass<JNativeRunnable, Runnable> {
+public:
+  static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/queue/NativeRunnable;";
+
+  void run() {
+    m_runnable();
+  }
+
+  static void registerNatives() {
+    javaClassStatic()->registerNatives({
+        makeNativeMethod("run", JNativeRunnable::run),
+    });
+  }
+private:
+  friend HybridBase;
+
+  JNativeRunnable(std::function<void()> runnable)
+      : m_runnable(std::move(runnable)) {}
+
+  std::function<void()> m_runnable;
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp b/ReactAndroid/src/main/jni/react/jni/JSCPerfLogging.cpp
similarity index 99%
rename from ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp
rename to ReactAndroid/src/main/jni/react/jni/JSCPerfLogging.cpp
index 3700b14c050f..d480a8833ae9 100644
--- a/ReactAndroid/src/main/jni/react/JSCPerfLogging.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/JSCPerfLogging.cpp
@@ -1,8 +1,10 @@
 // Copyright 2004-present Facebook. All Rights Reserved.
 
-#include "JSCHelpers.h"
+#include "JSCPerfLogging.h"
+
 #include <fb/log.h>
 #include <jni/fbjni.h>
+#include <react/JSCHelpers.h>
 
 using namespace facebook::jni;
 
diff --git a/ReactAndroid/src/main/jni/react/JSCPerfLogging.h b/ReactAndroid/src/main/jni/react/jni/JSCPerfLogging.h
similarity index 100%
rename from ReactAndroid/src/main/jni/react/JSCPerfLogging.h
rename to ReactAndroid/src/main/jni/react/jni/JSCPerfLogging.h
diff --git a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp
index 416640a371de..a96d0c3ded6c 100644
--- a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp
@@ -2,14 +2,13 @@
 
 #include "JSLoader.h"
 
-#include <android/asset_manager.h>
 #include <android/asset_manager_jni.h>
+#include <jni/Environment.h>
 #include <fstream>
 #include <sstream>
 #include <streambuf>
 #include <string>
 #include <fb/log.h>
-
 #ifdef WITH_FBSYSTRACE
 #include <fbsystrace.h>
 using fbsystrace::FbSystraceSection;
@@ -18,16 +17,26 @@ using fbsystrace::FbSystraceSection;
 namespace facebook {
 namespace react {
 
+static jclass gApplicationHolderClass;
+static jmethodID gGetApplicationMethod;
+static jmethodID gGetAssetManagerMethod;
+
+std::string loadScriptFromAssets(const std::string& assetName) {
+  JNIEnv *env = jni::Environment::current();
+  jobject application = env->CallStaticObjectMethod(
+    gApplicationHolderClass,
+    gGetApplicationMethod);
+  jobject assetManager = env->CallObjectMethod(application, gGetAssetManagerMethod);
+  return loadScriptFromAssets(AAssetManager_fromJava(env, assetManager), assetName);
+}
+
 std::string loadScriptFromAssets(
-    JNIEnv *env,
-    jobject assetManager,
-    std::string assetName) {
+    AAssetManager *manager,
+    const std::string& assetName) {
   #ifdef WITH_FBSYSTRACE
   FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromAssets",
     "assetName", assetName);
   #endif
-
-  auto manager = AAssetManager_fromJava(env, assetManager);
   if (manager) {
     auto asset = AAssetManager_open(
       manager,
@@ -50,7 +59,7 @@ std::string loadScriptFromAssets(
   return "";
 }
 
-std::string loadScriptFromFile(std::string fileName) {
+std::string loadScriptFromFile(const std::string& fileName) {
   #ifdef WITH_FBSYSTRACE
   FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_loadScriptFromFile",
     "fileName", fileName);
@@ -71,4 +80,14 @@ std::string loadScriptFromFile(std::string fileName) {
   return "";
 }
 
-} }
\ No newline at end of file
+void registerJSLoaderNatives() {
+  JNIEnv *env = jni::Environment::current();
+  jclass applicationHolderClass = env->FindClass("com/facebook/react/common/ApplicationHolder");
+  gApplicationHolderClass = (jclass)env->NewGlobalRef(applicationHolderClass);
+  gGetApplicationMethod = env->GetStaticMethodID(applicationHolderClass, "getApplication", "()Landroid/app/Application;");
+
+  jclass appClass = env->FindClass("android/app/Application");
+  gGetAssetManagerMethod = env->GetMethodID(appClass, "getAssets", "()Landroid/content/res/AssetManager;");
+}
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/jni/JSLoader.h b/ReactAndroid/src/main/jni/react/jni/JSLoader.h
index c7381fe7e8f7..8b716beef17d 100644
--- a/ReactAndroid/src/main/jni/react/jni/JSLoader.h
+++ b/ReactAndroid/src/main/jni/react/jni/JSLoader.h
@@ -2,20 +2,29 @@
 
 #pragma once
 
+#include <android/asset_manager.h>
 #include <string>
 #include <jni.h>
 
 namespace facebook {
 namespace react {
 
+/**
+ * Helper method for loading a JS script from Android assets without
+ * a reference to an AssetManager.
+ */
+std::string loadScriptFromAssets(const std::string& assetName);
+
 /**
  * Helper method for loading JS script from android asset
  */
-std::string loadScriptFromAssets(JNIEnv *env, jobject assetManager, std::string assetName);
+std::string loadScriptFromAssets(AAssetManager *assetManager, const std::string& assetName);
 
 /**
  * Helper method for loading JS script from a file
  */
-std::string loadScriptFromFile(std::string fileName);
+std::string loadScriptFromFile(const std::string& fileName);
+
+void registerJSLoaderNatives();
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/jni/JSLogging.cpp b/ReactAndroid/src/main/jni/react/jni/JSLogging.cpp
new file mode 100644
index 000000000000..7690a5656ebd
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JSLogging.cpp
@@ -0,0 +1,36 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "JSLogging.h"
+
+#include <android/log.h>
+#include <algorithm>
+#include <react/Value.h>
+#include <fb/log.h>
+
+namespace facebook {
+namespace react {
+
+JSValueRef nativeLoggingHook(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[], JSValueRef *exception) {
+  android_LogPriority logLevel = ANDROID_LOG_DEBUG;
+  if (argumentCount > 1) {
+    int level = (int) JSValueToNumber(ctx, arguments[1], NULL);
+    // The lowest log level we get from JS is 0. We shift and cap it to be
+    // in the range the Android logging method expects.
+    logLevel = std::min(
+        static_cast<android_LogPriority>(level + ANDROID_LOG_DEBUG),
+        ANDROID_LOG_FATAL);
+  }
+  if (argumentCount > 0) {
+    JSStringRef jsString = JSValueToStringCopy(ctx, arguments[0], NULL);
+    String message = String::adopt(jsString);
+    FBLOG_PRI(logLevel, "ReactNativeJS", "%s", message.str().c_str());
+  }
+  return JSValueMakeUndefined(ctx);
+}
+
+}};
diff --git a/ReactAndroid/src/main/jni/react/jni/JSLogging.h b/ReactAndroid/src/main/jni/react/jni/JSLogging.h
new file mode 100644
index 000000000000..27756b665076
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JSLogging.h
@@ -0,0 +1,15 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <JavaScriptCore/JSContextRef.h>
+
+namespace facebook {
+namespace react {
+JSValueRef nativeLoggingHook(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[], JSValueRef *exception);
+}}
diff --git a/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.cpp b/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.cpp
new file mode 100644
index 000000000000..20c71bc8c11a
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.cpp
@@ -0,0 +1,83 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "JniJSModulesUnbundle.h"
+
+#include <cstdint>
+#include <fb/assert.h>
+#include <libgen.h>
+#include <memory>
+#include <sstream>
+#include <sys/endian.h>
+#include <utility>
+
+using magic_number_t = uint32_t;
+const magic_number_t MAGIC_FILE_HEADER = 0xFB0BD1E5;
+const std::string MAGIC_FILE_NAME = "UNBUNDLE";
+
+namespace facebook {
+namespace react {
+
+using asset_ptr =
+  std::unique_ptr<AAsset, std::function<decltype(AAsset_close)>>;
+
+static std::string jsModulesDir(const std::string& entryFile) {
+  std::string dir = dirname(entryFile.c_str());
+
+  // android's asset manager does not work with paths that start with a dot
+  return dir == "." ? "js-modules/" : dir + "/js-modules/";
+}
+
+static asset_ptr openAsset(
+    AAssetManager *manager,
+    const std::string& fileName,
+    int mode = AASSET_MODE_STREAMING) {
+  return asset_ptr(
+    AAssetManager_open(manager, fileName.c_str(), mode),
+    AAsset_close);
+}
+
+JniJSModulesUnbundle::JniJSModulesUnbundle(AAssetManager *assetManager, const std::string& entryFile) :
+  m_assetManager(assetManager),
+  m_moduleDirectory(jsModulesDir(entryFile)) {}
+
+bool JniJSModulesUnbundle::isUnbundle(
+    AAssetManager *assetManager,
+    const std::string& assetName) {
+  if (!assetManager) {
+    return false;
+  }
+
+  auto magicFileName = jsModulesDir(assetName) + MAGIC_FILE_NAME;
+  auto asset = openAsset(assetManager, magicFileName.c_str());
+  if (asset == nullptr) {
+    return false;
+  }
+
+  magic_number_t fileHeader = 0;
+  AAsset_read(asset.get(), &fileHeader, sizeof(fileHeader));
+  return fileHeader == htole32(MAGIC_FILE_HEADER);
+}
+
+JSModulesUnbundle::Module JniJSModulesUnbundle::getModule(uint32_t moduleId) const {
+  // can be nullptr for default constructor.
+  FBASSERTMSGF(m_assetManager != nullptr, "Unbundle has not been initialized with an asset manager");
+
+  std::ostringstream sourceUrlBuilder;
+  sourceUrlBuilder << moduleId << ".js";
+  auto sourceUrl = sourceUrlBuilder.str();
+
+  auto fileName = m_moduleDirectory + sourceUrl;
+  auto asset = openAsset(m_assetManager, fileName, AASSET_MODE_BUFFER);
+
+  const char *buffer = nullptr;
+  if (asset != nullptr) {
+    buffer = static_cast<const char *>(AAsset_getBuffer(asset.get()));
+  }
+  if (buffer == nullptr) {
+    throw ModuleNotFound("Module not found: " + sourceUrl);
+  }
+  return {sourceUrl, std::string(buffer, AAsset_getLength(asset.get()))};
+}
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.h b/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.h
new file mode 100644
index 000000000000..9f75175e47dc
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/JniJSModulesUnbundle.h
@@ -0,0 +1,31 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <android/asset_manager.h>
+#include <react/JSModulesUnbundle.h>
+
+namespace facebook {
+namespace react {
+
+class JniJSModulesUnbundle : public JSModulesUnbundle {
+  /**
+   * This implementation reads modules as single file from the assets of an apk.
+   */
+public:
+  JniJSModulesUnbundle() = default;
+  JniJSModulesUnbundle(AAssetManager *assetManager, const std::string& entryFile);
+  JniJSModulesUnbundle(JniJSModulesUnbundle&& other) = delete;
+  JniJSModulesUnbundle& operator= (JSModulesUnbundle&& other) = delete;
+
+  static bool isUnbundle(
+    AAssetManager *assetManager,
+    const std::string& assetName);
+  virtual Module getModule(uint32_t moduleId) const override;
+private:
+  AAssetManager *m_assetManager = nullptr;
+  std::string m_moduleDirectory;
+};
+
+}
+}
diff --git a/ReactAndroid/src/main/jni/react/jni/NativeArray.h b/ReactAndroid/src/main/jni/react/jni/NativeArray.h
index 452f6b1d054e..f30ef107da2d 100644
--- a/ReactAndroid/src/main/jni/react/jni/NativeArray.h
+++ b/ReactAndroid/src/main/jni/react/jni/NativeArray.h
@@ -3,7 +3,7 @@
 #pragma once
 
 #include <jni/fbjni.h>
-#include <react/MethodCall.h>
+#include <folly/dynamic.h>
 
 namespace facebook {
 namespace react {
diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
index bc35feeb2bd2..4748d825c89a 100644
--- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
@@ -1,5 +1,6 @@
 // Copyright 2004-present Facebook. All Rights Reserved.
 
+#include <android/asset_manager_jni.h>
 #include <android/input.h>
 #include <fb/log.h>
 #include <folly/json.h>
@@ -13,9 +14,21 @@
 #include <react/Bridge.h>
 #include <react/Executor.h>
 #include <react/JSCExecutor.h>
+#include <react/JSModulesUnbundle.h>
+#include <react/Platform.h>
+#include "JExecutorToken.h"
+#include "JExecutorTokenFactory.h"
+#include "JNativeRunnable.h"
 #include "JSLoader.h"
 #include "ReadableNativeArray.h"
 #include "ProxyExecutor.h"
+#include "OnLoad.h"
+#include "JMessageQueueThread.h"
+#include "JniJSModulesUnbundle.h"
+#include "JSLogging.h"
+#include "JSCPerfLogging.h"
+#include "WebWorkers.h"
+#include <algorithm>
 
 #ifdef WITH_FBSYSTRACE
 #include <fbsystrace.h>
@@ -60,10 +73,6 @@ struct ReadableNativeMapKeySetIterator : public Countable {
     , mapRef(mapRef_) {}
 };
 
-struct NativeRunnable : public Countable {
-  std::function<void()> callable;
-};
-
 static jobject createReadableNativeMapWithContents(JNIEnv* env, folly::dynamic map) {
   if (map.isNull()) {
     return nullptr;
@@ -515,8 +524,14 @@ static jstring getNextKey(JNIEnv* env, jobject obj) {
 } // namespace iterator
 } // namespace map
 
+namespace {
+
 namespace runnable {
 
+struct NativeRunnable : public Countable {
+  std::function<void()> callable;
+};
+
 static jclass gNativeRunnableClass;
 static jmethodID gNativeRunnableCtor;
 
@@ -552,46 +567,92 @@ namespace bridge {
 
 static jmethodID gCallbackMethod;
 static jmethodID gOnBatchCompleteMethod;
+static jmethodID gOnExecutorUnregisteredMethod;
 static jmethodID gLogMarkerMethod;
 
-static void makeJavaCall(JNIEnv* env, jobject callback, MethodCall&& call) {
+struct CountableBridge : Bridge, Countable {
+  using Bridge::Bridge;
+};
+
+static void logMarker(const std::string& marker) {
+  JNIEnv* env = Environment::current();
+  jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
+  jstring jmarker = env->NewStringUTF(marker.c_str());
+  env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, jmarker);
+  env->DeleteLocalRef(markerClass);
+  env->DeleteLocalRef(jmarker);
+}
+
+static void makeJavaCall(JNIEnv* env, ExecutorToken executorToken, jobject callback, const MethodCall& call) {
   if (call.arguments.isNull()) {
     return;
   }
+
+  #ifdef WITH_FBSYSTRACE
+  if (call.callId != -1) {
+    fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", call.callId);
+  }
+  #endif
+
   auto newArray = ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments));
-  env->CallVoidMethod(callback, gCallbackMethod, call.moduleId, call.methodId, newArray.get());
+  env->CallVoidMethod(
+      callback,
+      gCallbackMethod,
+      static_cast<JExecutorTokenHolder*>(executorToken.getPlatformExecutorToken().get())->getJobj(),
+      call.moduleId,
+      call.methodId,
+      newArray.get());
 }
 
 static void signalBatchComplete(JNIEnv* env, jobject callback) {
   env->CallVoidMethod(callback, gOnBatchCompleteMethod);
 }
 
-static void dispatchCallbacksToJava(const RefPtr<WeakReference>& weakCallback,
-                                    const RefPtr<WeakReference>& weakCallbackQueueThread,
-                                    std::vector<MethodCall>&& calls,
-                                    bool isEndOfBatch) {
-  auto env = Environment::current();
-  if (env->ExceptionCheck()) {
-    FBLOGW("Dropped calls because of pending exception");
-    return;
-  }
-
-  ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread);
-  if (!callbackQueueThread) {
-    FBLOGW("Dropped calls because of callback queue thread went away");
-    return;
-  }
+class PlatformBridgeCallback : public BridgeCallback {
+public:
+  PlatformBridgeCallback(
+      RefPtr<WeakReference> weakCallback_,
+      RefPtr<WeakReference> weakCallbackQueueThread_) :
+    weakCallback_(std::move(weakCallback_)),
+    weakCallbackQueueThread_(std::move(weakCallbackQueueThread_)) {}
 
-  auto runnableFunction = std::bind([weakCallback, isEndOfBatch] (std::vector<MethodCall>& calls) {
+  void executeCallbackOnCallbackQueueThread(std::function<void(ResolvedWeakReference&)>&& runnable) {
     auto env = Environment::current();
     if (env->ExceptionCheck()) {
-      FBLOGW("Dropped calls because of pending exception");
+      FBLOGW("Dropped callback because of pending exception");
+      return;
+    }
+
+    ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread_);
+    if (!callbackQueueThread) {
+      FBLOGW("Dropped callback because callback queue thread went away");
       return;
     }
-    ResolvedWeakReference callback(weakCallback);
-    if (callback) {
-      for (auto&& call : calls) {
-        makeJavaCall(env, callback, std::move(call));
+
+    auto runnableWrapper = std::bind([weakCallback=weakCallback_] (std::function<void(ResolvedWeakReference&)>& runnable) {
+      auto env = Environment::current();
+      if (env->ExceptionCheck()) {
+        FBLOGW("Dropped calls because of pending exception");
+        return;
+      }
+      ResolvedWeakReference callback(weakCallback);
+      if (callback) {
+        runnable(callback);
+      }
+    }, std::move(runnable));
+
+    auto jNativeRunnable = runnable::createNativeRunnable(env, std::move(runnableWrapper));
+    queue::enqueueNativeRunnableOnQueue(env, callbackQueueThread, jNativeRunnable.get());
+  }
+
+  virtual void onCallNativeModules(
+      ExecutorToken executorToken,
+      std::vector<MethodCall>&& calls,
+      bool isEndOfBatch) override {
+    executeCallbackOnCallbackQueueThread([executorToken, calls, isEndOfBatch] (ResolvedWeakReference& callback) {
+      JNIEnv* env = Environment::current();
+      for (auto& call : calls) {
+        makeJavaCall(env, executorToken, callback, call);
         if (env->ExceptionCheck()) {
           return;
         }
@@ -599,33 +660,65 @@ static void dispatchCallbacksToJava(const RefPtr<WeakReference>& weakCallback,
       if (isEndOfBatch) {
         signalBatchComplete(env, callback);
       }
-    }
-  }, std::move(calls));
+    });
+  }
 
-  auto jNativeRunnable = runnable::createNativeRunnable(env, std::move(runnableFunction));
-  queue::enqueueNativeRunnableOnQueue(env, callbackQueueThread, jNativeRunnable.get());
-}
+  virtual void onExecutorUnregistered(ExecutorToken executorToken) override {
+    executeCallbackOnCallbackQueueThread([executorToken] (ResolvedWeakReference& callback) {
+      JNIEnv *env = Environment::current();
+      env->CallVoidMethod(
+          callback,
+          gOnExecutorUnregisteredMethod,
+          static_cast<JExecutorTokenHolder*>(executorToken.getPlatformExecutorToken().get())->getJobj());
+    });
+  }
+private:
+  RefPtr<WeakReference> weakCallback_;
+  RefPtr<WeakReference> weakCallbackQueueThread_;
+};
 
 static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
                    jobject callbackQueueThread) {
   auto weakCallback = createNew<WeakReference>(callback);
   auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
-  auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std::vector<MethodCall> calls, bool isEndOfBatch) {
-    dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch);
-  };
-  auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
-  auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
+  auto bridgeCallback = folly::make_unique<PlatformBridgeCallback>(weakCallback, weakCallbackQueueThread);
+  auto nativeExecutorFactory = extractRefPtr<CountableJSExecutorFactory>(env, executor);
+  auto executorTokenFactory = folly::make_unique<JExecutorTokenFactory>();
+  auto bridge = createNew<CountableBridge>(nativeExecutorFactory.get(), std::move(executorTokenFactory), std::move(bridgeCallback));
   setCountableForJava(env, obj, std::move(bridge));
 }
 
-static void executeApplicationScript(
-    const RefPtr<Bridge>& bridge,
-    const std::string script,
-    const std::string sourceUri) {
+static void destroy(JNIEnv* env, jobject jbridge) {
+  auto bridge = extractRefPtr<CountableBridge>(env, jbridge);
+  try {
+    bridge->destroy();
+  } catch (...) {
+    translatePendingCppExceptionToJavaException();
+  }
+}
+
+static void loadApplicationScript(
+    const RefPtr<CountableBridge>& bridge,
+    const std::string& script,
+    const std::string& sourceUri) {
   try {
-    // Execute the application script and collect/dispatch any native calls that might have occured
-    bridge->executeApplicationScript(script, sourceUri);
-    bridge->executeJSCall("BatchedBridge", "flushedQueue", std::vector<folly::dynamic>());
+    bridge->loadApplicationScript(script, sourceUri);
+  } catch (...) {
+    translatePendingCppExceptionToJavaException();
+  }
+}
+
+static void loadApplicationUnbundle(
+    const RefPtr<CountableBridge>& bridge,
+    AAssetManager *assetManager,
+    const std::string& startupCode,
+    const std::string& startupFileName) {
+  try {
+    bridge->loadApplicationUnbundle(
+      std::unique_ptr<JSModulesUnbundle>(
+        new JniJSModulesUnbundle(assetManager, startupFileName)),
+      startupCode,
+      startupFileName);
   } catch (...) {
     translatePendingCppExceptionToJavaException();
   }
@@ -634,20 +727,24 @@ static void executeApplicationScript(
 static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager,
                                  jstring assetName) {
   jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
-
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto manager = AAssetManager_fromJava(env, assetManager);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   auto assetNameStr = fromJString(env, assetName);
 
   env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromAssets_start"));
-  auto script = react::loadScriptFromAssets(env, assetManager, assetNameStr);
+  auto script = react::loadScriptFromAssets(manager, assetNameStr);
   #ifdef WITH_FBSYSTRACE
   FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
-    "executeApplicationScript",
+    "loadApplicationScript",
     "assetName", assetNameStr);
   #endif
 
   env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromAssets_read"));
-  executeApplicationScript(bridge, script, assetNameStr);
+  if (JniJSModulesUnbundle::isUnbundle(manager, assetNameStr)) {
+    loadApplicationUnbundle(bridge, manager, script, "file://" + assetNameStr);
+  } else {
+    loadApplicationScript(bridge, script, "file://" + assetNameStr);
+  }
   if (env->ExceptionCheck()) {
     return;
   }
@@ -657,81 +754,140 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager,
 static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstring sourceURL) {
   jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
 
-  auto bridge = jni::extractRefPtr<Bridge>(env, obj);
+  auto bridge = jni::extractRefPtr<CountableBridge>(env, obj);
   auto fileNameStr = fileName == NULL ? "" : fromJString(env, fileName);
   env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_start"));
   auto script = fileName == NULL ? "" : react::loadScriptFromFile(fileNameStr);
   #ifdef WITH_FBSYSTRACE
   auto sourceURLStr = sourceURL == NULL ? fileNameStr : fromJString(env, sourceURL);
   FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "reactbridge_jni_"
-    "executeApplicationScript",
+    "loadApplicationScript",
     "sourceURL", sourceURLStr);
   #endif
   env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_read"));
-  executeApplicationScript(bridge, script, jni::fromJString(env, sourceURL));
+  loadApplicationScript(bridge, script, jni::fromJString(env, sourceURL));
   if (env->ExceptionCheck()) {
     return;
   }
   env->CallStaticVoidMethod(markerClass, gLogMarkerMethod, env->NewStringUTF("loadScriptFromFile_exec"));
 }
 
-static void callFunction(JNIEnv* env, jobject obj, jint moduleId, jint methodId,
-                         NativeArray::jhybridobject args) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint moduleId, jint methodId,
+                         NativeArray::jhybridobject args, jstring tracingName) {
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   auto arguments = cthis(wrap_alias(args));
-  std::vector<folly::dynamic> call{
-    (double) moduleId,
-    (double) methodId,
-    std::move(arguments->array),
-  };
   try {
-    bridge->executeJSCall("BatchedBridge", "callFunctionReturnFlushedQueue", std::move(call));
+    bridge->callFunction(
+      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
+      (double) moduleId,
+      (double) methodId,
+      std::move(arguments->array),
+      fromJString(env, tracingName)
+    );
   } catch (...) {
     translatePendingCppExceptionToJavaException();
   }
 }
 
-static void invokeCallback(JNIEnv* env, jobject obj, jint callbackId,
+static void invokeCallback(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint callbackId,
                            NativeArray::jhybridobject args) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   auto arguments = cthis(wrap_alias(args));
-  std::vector<folly::dynamic> call{
-    (double) callbackId,
-    std::move(arguments->array)
-  };
   try {
-    bridge->executeJSCall("BatchedBridge", "invokeCallbackAndReturnFlushedQueue", std::move(call));
+    bridge->invokeCallback(
+      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
+      (double) callbackId,
+      std::move(arguments->array)
+    );
   } catch (...) {
     translatePendingCppExceptionToJavaException();
   }
 }
 
 static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
 }
 
+static jlong getJavaScriptContext(JNIEnv *env, jobject obj) {
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
+  return (uintptr_t) bridge->getJavaScriptContext();
+}
+
+static jobject getMainExecutorToken(JNIEnv* env, jobject obj) {
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
+  auto token = bridge->getMainExecutorToken();
+  return static_cast<JExecutorTokenHolder*>(token.getPlatformExecutorToken().get())->getJobj();
+}
+
 static jboolean supportsProfiling(JNIEnv* env, jobject obj) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   return bridge->supportsProfiling() ? JNI_TRUE : JNI_FALSE;
 }
 
 static void startProfiler(JNIEnv* env, jobject obj, jstring title) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   bridge->startProfiler(fromJString(env, title));
 }
 
 static void stopProfiler(JNIEnv* env, jobject obj, jstring title, jstring filename) {
-  auto bridge = extractRefPtr<Bridge>(env, obj);
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
   bridge->stopProfiler(fromJString(env, title), fromJString(env, filename));
 }
 
+static void handleMemoryPressureModerate(JNIEnv* env, jobject obj) {
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
+  bridge->handleMemoryPressureModerate();
+}
+
+static void handleMemoryPressureCritical(JNIEnv* env, jobject obj) {
+  auto bridge = extractRefPtr<CountableBridge>(env, obj);
+  bridge->handleMemoryPressureCritical();
+}
+
 } // namespace bridge
 
 namespace executors {
 
-static void createJSCExecutor(JNIEnv *env, jobject obj) {
-  auto executor = createNew<JSCExecutorFactory>();
+std::string getDeviceCacheDir() {
+  // Get the Application Context object
+  auto getApplicationClass = findClassLocal(
+                              "com/facebook/react/common/ApplicationHolder");
+  auto getApplicationMethod = getApplicationClass->getStaticMethod<jobject()>(
+                              "getApplication",
+                              "()Landroid/app/Application;"
+                              );
+  auto application = getApplicationMethod(getApplicationClass);
+
+  // Get getCacheDir() from the context
+  auto getCacheDirMethod = findClassLocal("android/app/Application")
+                            ->getMethod<jobject()>("getCacheDir",
+                                                   "()Ljava/io/File;"
+                                                  );
+  auto cacheDirObj = getCacheDirMethod(application);
+
+  // Call getAbsolutePath() on the returned File object
+  auto getAbsolutePathMethod = findClassLocal("java/io/File")
+                                ->getMethod<jstring()>("getAbsolutePath");
+  return getAbsolutePathMethod(cacheDirObj)->toStdString();
+}
+
+struct CountableJSCExecutorFactory : CountableJSExecutorFactory  {
+public:
+  CountableJSCExecutorFactory(folly::dynamic jscConfig) : m_jscConfig(jscConfig) {}
+  virtual std::unique_ptr<JSExecutor> createJSExecutor(Bridge *bridge) override {
+    return JSCExecutorFactory(getDeviceCacheDir(), m_jscConfig).createJSExecutor(bridge);
+  }
+
+private:
+  folly::dynamic m_jscConfig;
+};
+
+static void createJSCExecutor(JNIEnv *env, jobject obj, jobject jscConfig) {
+  auto nativeMap = extractRefPtr<NativeMap>(env, jscConfig);
+  exceptions::throwIfObjectAlreadyConsumed(nativeMap, "Map to push already consumed");
+  auto executor = createNew<CountableJSCExecutorFactory>(std::move(nativeMap->map));
+  nativeMap->isConsumed = true;
   setCountableForJava(env, obj, std::move(executor));
 }
 
@@ -743,8 +899,29 @@ static void createProxyExecutor(JNIEnv *env, jobject obj, jobject executorInstan
 
 } // namespace executors
 
+}
+
+jmethodID getLogMarkerMethod() {
+  return bridge::gLogMarkerMethod;
+}
+
 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
   return initialize(vm, [] {
+    // Inject some behavior into react/
+    ReactMarker::logMarker = bridge::logMarker;
+    WebWorkerUtil::createWebWorkerThread = WebWorkers::createWebWorkerThread;
+    WebWorkerUtil::loadScriptFromAssets =
+      [] (const std::string& assetName) {
+        return loadScriptFromAssets(assetName);
+      };
+    MessageQueues::getCurrentMessageQueueThread =
+      [] {
+        return std::unique_ptr<MessageQueueThread>(
+            JMessageQueueThread::currentMessageQueueThread().release());
+      };
+    PerfLogging::installNativeHooks = addNativePerfLoggingHooks;
+    JSLogging::nativeHook = nativeLoggingHook;
+
     // get the current env
     JNIEnv* env = Environment::current();
 
@@ -755,6 +932,8 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
     NativeArray::registerNatives();
     ReadableNativeArray::registerNatives();
     WritableNativeArray::registerNatives();
+    JNativeRunnable::registerNatives();
+    registerJSLoaderNatives();
 
     registerNatives("com/facebook/react/bridge/NativeMap", {
         makeNativeMethod("initialize", map::initialize),
@@ -803,24 +982,27 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
     });
 
     registerNatives("com/facebook/react/bridge/JSCJavaScriptExecutor", {
-      makeNativeMethod("initialize", executors::createJSCExecutor),
+      makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/WritableNativeMap;)V",
+        executors::createJSCExecutor),
     });
 
     registerNatives("com/facebook/react/bridge/ProxyJavaScriptExecutor", {
         makeNativeMethod(
-          "initialize", "(Lcom/facebook/react/bridge/ProxyJavaScriptExecutor$JavaJSExecutor;)V",
+          "initialize", "(Lcom/facebook/react/bridge/JavaJSExecutor;)V",
           executors::createProxyExecutor),
     });
 
     jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
-    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(IILcom/facebook/react/bridge/ReadableNativeArray;)V");
+    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(Lcom/facebook/react/bridge/ExecutorToken;IILcom/facebook/react/bridge/ReadableNativeArray;)V");
     bridge::gOnBatchCompleteMethod = env->GetMethodID(callbackClass, "onBatchComplete", "()V");
+    bridge::gOnExecutorUnregisteredMethod = env->GetMethodID(callbackClass, "onExecutorUnregistered", "(Lcom/facebook/react/bridge/ExecutorToken;)V");
 
     jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
     bridge::gLogMarkerMethod = env->GetStaticMethodID(markerClass, "logMarker", "(Ljava/lang/String;)V");
 
     registerNatives("com/facebook/react/bridge/ReactBridge", {
         makeNativeMethod("initialize", "(Lcom/facebook/react/bridge/JavaScriptExecutor;Lcom/facebook/react/bridge/ReactCallback;Lcom/facebook/react/bridge/queue/MessageQueueThread;)V", bridge::create),
+        makeNativeMethod("destroy", bridge::destroy),
         makeNativeMethod(
           "loadScriptFromAssets", "(Landroid/content/res/AssetManager;Ljava/lang/String;)V",
           bridge::loadScriptFromAssets),
@@ -828,12 +1010,16 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
         makeNativeMethod("callFunction", bridge::callFunction),
         makeNativeMethod("invokeCallback", bridge::invokeCallback),
         makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
+        makeNativeMethod("getMainExecutorToken", "()Lcom/facebook/react/bridge/ExecutorToken;", bridge::getMainExecutorToken),
         makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
         makeNativeMethod("startProfiler", bridge::startProfiler),
         makeNativeMethod("stopProfiler", bridge::stopProfiler),
+        makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
+        makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),
+        makeNativeMethod("getJavaScriptContextNativePtrExperimental", bridge::getJavaScriptContext),
     });
 
-    jclass nativeRunnableClass = env->FindClass("com/facebook/react/bridge/queue/NativeRunnable");
+    jclass nativeRunnableClass = env->FindClass("com/facebook/react/bridge/queue/NativeRunnableDeprecated");
     runnable::gNativeRunnableClass = (jclass)env->NewGlobalRef(nativeRunnableClass);
     runnable::gNativeRunnableCtor = env->GetMethodID(nativeRunnableClass, "<init>", "()V");
     wrap_alias(nativeRunnableClass)->registerNatives({
diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.h b/ReactAndroid/src/main/jni/react/jni/OnLoad.h
new file mode 100644
index 000000000000..e7a7d04a46fb
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.h
@@ -0,0 +1,19 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <jni.h>
+#include <jni/Countable.h>
+#include <react/Executor.h>
+
+namespace facebook {
+namespace react {
+
+jmethodID getLogMarkerMethod();
+
+struct CountableJSExecutorFactory : JSExecutorFactory, Countable {
+  using JSExecutorFactory::JSExecutorFactory;
+};
+
+} // namespace react
+} // namespace facebook
diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
index 129d737fc55e..07743bed896a 100644
--- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
@@ -2,53 +2,79 @@
 
 #include "ProxyExecutor.h"
 
+#include <fb/assert.h>
 #include <jni/Environment.h>
 #include <jni/LocalReference.h>
 #include <jni/LocalString.h>
 #include <folly/json.h>
 
+#include <react/Bridge.h>
 namespace facebook {
 namespace react {
 
-const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/ProxyJavaScriptExecutor$JavaJSExecutor";
+const auto EXECUTOR_BASECLASS = "com/facebook/react/bridge/JavaJSExecutor";
 
-std::unique_ptr<JSExecutor> ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) {
+static std::string executeJSCallWithProxy(
+    jobject executor,
+    const std::string& methodName,
+    const std::vector<folly::dynamic>& arguments) {
+  static auto executeJSCall =
+    jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<jstring(jstring, jstring)>("executeJSCall");
+
+  auto result = executeJSCall(
+    executor,
+    jni::make_jstring(methodName).get(),
+    jni::make_jstring(folly::toJson(arguments).c_str()).get());
+  return result->toString();
+}
+
+std::unique_ptr<JSExecutor> ProxyExecutorOneTimeFactory::createJSExecutor(Bridge *bridge) {
   FBASSERTMSGF(
     m_executor.get() != nullptr,
     "Proxy instance should not be null. Did you attempt to call createJSExecutor() on this factory "
     "instance more than once?");
-  return std::unique_ptr<JSExecutor>(new ProxyExecutor(std::move(m_executor)));
+  return std::unique_ptr<JSExecutor>(new ProxyExecutor(std::move(m_executor), bridge));
 }
 
 ProxyExecutor::~ProxyExecutor() {
   m_executor.reset();
 }
 
-void ProxyExecutor::executeApplicationScript(
+void ProxyExecutor::loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) {
-  static auto executeApplicationScript =
-    jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<void(jstring, jstring)>("executeApplicationScript");
+  static auto loadApplicationScript =
+    jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<void(jstring, jstring)>("loadApplicationScript");
 
-  executeApplicationScript(
+  loadApplicationScript(
     m_executor.get(),
     jni::make_jstring(script).get(),
     jni::make_jstring(sourceURL).get());
 }
 
-std::string ProxyExecutor::executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& arguments) {
-  static auto executeJSCall =
-    jni::findClassStatic(EXECUTOR_BASECLASS)->getMethod<jstring(jstring, jstring, jstring)>("executeJSCall");
+void ProxyExecutor::loadApplicationUnbundle(std::unique_ptr<JSModulesUnbundle>, const std::string&, const std::string&) {
+  jni::throwNewJavaException(
+    "java/lang/UnsupportedOperationException",
+    "Loading application unbundles is not supported for proxy executors");
+}
 
-  auto result = executeJSCall(
-    m_executor.get(),
-    jni::make_jstring(moduleName).get(),
-    jni::make_jstring(methodName).get(),
-    jni::make_jstring(folly::toJson(arguments).c_str()).get());
-  return result->toString();
+void ProxyExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
+  std::vector<folly::dynamic> call{
+    (double) moduleId,
+    (double) methodId,
+    std::move(arguments),
+  };
+  std::string result = executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, result, true);
+}
+
+void ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
+  std::vector<folly::dynamic> call{
+    (double) callbackId,
+    std::move(arguments)
+  };
+  std::string result = executeJSCallWithProxy(m_executor.get(), "invokeCallbackAndReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, result, true);
 }
 
 void ProxyExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h
index 805eda532b2f..c66346424920 100644
--- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h
+++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h
@@ -6,6 +6,7 @@
 #include <jni/fbjni.h>
 #include <jni.h>
 #include <jni/GlobalReference.h>
+#include "OnLoad.h"
 
 namespace facebook {
 namespace react {
@@ -14,11 +15,11 @@ namespace react {
  * This executor factory can only create a single executor instance because it moves
  * executorInstance global reference to the executor instance it creates.
  */
-class ProxyExecutorOneTimeFactory : public JSExecutorFactory {
+class ProxyExecutorOneTimeFactory : public CountableJSExecutorFactory {
 public:
   ProxyExecutorOneTimeFactory(jni::global_ref<jobject>&& executorInstance) :
     m_executor(std::move(executorInstance)) {}
-  virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback ignoredCallback) override;
+  virtual std::unique_ptr<JSExecutor> createJSExecutor(Bridge *bridge) override;
 
 private:
   jni::global_ref<jobject> m_executor;
@@ -26,22 +27,31 @@ class ProxyExecutorOneTimeFactory : public JSExecutorFactory {
 
 class ProxyExecutor : public JSExecutor {
 public:
-  ProxyExecutor(jni::global_ref<jobject>&& executorInstance) :
-    m_executor(std::move(executorInstance)) {}
+  ProxyExecutor(jni::global_ref<jobject>&& executorInstance, Bridge *bridge) :
+    m_executor(std::move(executorInstance)),
+    m_bridge(bridge) {}
   virtual ~ProxyExecutor() override;
-  virtual void executeApplicationScript(
+  virtual void loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) override;
-  virtual std::string executeJSCall(
-    const std::string& moduleName,
-    const std::string& methodName,
-    const std::vector<folly::dynamic>& arguments) override;
+  virtual void loadApplicationUnbundle(
+    std::unique_ptr<JSModulesUnbundle> bundle,
+    const std::string& startupCode,
+    const std::string& sourceURL) override;
+  virtual void callFunction(
+    const double moduleId,
+    const double methodId,
+    const folly::dynamic& arguments) override;
+  virtual void invokeCallback(
+    const double callbackId,
+    const folly::dynamic& arguments) override;
   virtual void setGlobalVariable(
     const std::string& propName,
     const std::string& jsonValue) override;
 
 private:
   jni::global_ref<jobject> m_executor;
+  Bridge *m_bridge;
 };
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/jni/WebWorkers.h b/ReactAndroid/src/main/jni/react/jni/WebWorkers.h
new file mode 100644
index 000000000000..e243fa8bfb32
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/jni/WebWorkers.h
@@ -0,0 +1,29 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <memory>
+
+#include <jni.h>
+#include <folly/Memory.h>
+
+#include "JMessageQueueThread.h"
+
+using namespace facebook::jni;
+
+namespace facebook {
+namespace react {
+
+class WebWorkers : public JavaClass<WebWorkers> {
+public:
+  static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/webworkers/WebWorkers;";
+
+  static std::unique_ptr<JMessageQueueThread> createWebWorkerThread(int id, MessageQueueThread *ownerMessageQueueThread) {
+    static auto method = WebWorkers::javaClassStatic()->
+        getStaticMethod<JavaMessageQueueThread::javaobject(jint, JavaMessageQueueThread::javaobject)>("createWebWorkerThread");
+    auto res = method(WebWorkers::javaClassStatic(), id, static_cast<JMessageQueueThread*>(ownerMessageQueueThread)->jobj());
+    return folly::make_unique<JMessageQueueThread>(res);
+  }
+};
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/noncopyable.h b/ReactAndroid/src/main/jni/react/noncopyable.h
new file mode 100644
index 000000000000..96b32d44b835
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/noncopyable.h
@@ -0,0 +1,12 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+namespace facebook {
+namespace react {
+struct noncopyable {
+  noncopyable(const noncopyable&) = delete;
+  noncopyable& operator=(const noncopyable&) = delete;
+ protected:
+  noncopyable() = default;
+};
+}}
diff --git a/ReactAndroid/src/main/jni/react/perftests/BUCK b/ReactAndroid/src/main/jni/react/perftests/BUCK
index 6358592fb5ee..2b0c46a7c134 100644
--- a/ReactAndroid/src/main/jni/react/perftests/BUCK
+++ b/ReactAndroid/src/main/jni/react/perftests/BUCK
@@ -7,13 +7,13 @@ cxx_library(
   preprocessor_flags = [
     '-DLOG_TAG=\"ReactPerftests\"',
   ],
-  visibility = [
-    '//instrumentation_tests/com/facebook/catalyst/...',
-  ],
   deps = [
     '//native:base',
     '//native/jni:jni',
   ],
+  visibility = [
+    '//instrumentation_tests/com/facebook/catalyst/...',
+  ],
 )
 
 project_config(
diff --git a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
index 40d21797485b..1de276f2093e 100644
--- a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
@@ -20,24 +20,20 @@ static std::vector<MethodCall> executeForMethodCalls(
     int moduleId,
     int methodId,
     std::vector<MethodArgument> args = std::vector<MethodArgument>()) {
-  std::vector<MethodArgument> call;
-  call.emplace_back((double) moduleId);
-  call.emplace_back((double) methodId);
-  call.emplace_back(std::move(args));
-  return parseMethodCalls(e.executeJSCall("Bridge", "callFunction", call));
+  return parseMethodCalls(e.callFunction(moduleId, methodId, std::move(args)));
 }
 
 TEST(JSCExecutor, CallFunction) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    return [[module + 1], [method + 1], [args]];"
   "  },"
   "};"
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   std::vector<MethodArgument> args;
   args.emplace_back(true);
   args.emplace_back(0.4);
@@ -58,7 +54,7 @@ TEST(JSCExecutor, CallFunction) {
 TEST(JSCExecutor, CallFunctionWithMap) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    var s = args[0].foo + args[0].bar + args[0].baz;"
   "    return [[module], [method], [[s]]];"
   "  },"
@@ -66,7 +62,7 @@ TEST(JSCExecutor, CallFunctionWithMap) {
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   std::vector<MethodArgument> args;
   std::map<std::string, MethodArgument> map {
     { "foo", MethodArgument("hello") },
@@ -85,7 +81,7 @@ TEST(JSCExecutor, CallFunctionWithMap) {
 TEST(JSCExecutor, CallFunctionReturningMap) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    var s = { foo: 4, bar: true };"
   "    return [[module], [method], [[s]]];"
   "  },"
@@ -93,7 +89,7 @@ TEST(JSCExecutor, CallFunctionReturningMap) {
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   auto returnedCalls = executeForMethodCalls(e, 10, 9);
   ASSERT_EQ(1, returnedCalls.size());
   auto returnedCall = returnedCalls[0];
@@ -111,7 +107,7 @@ TEST(JSCExecutor, CallFunctionReturningMap) {
 TEST(JSCExecutor, CallFunctionWithArray) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    var s = args[0][0]+ args[0][1] + args[0][2] + args[0].length;"
   "    return [[module], [method], [[s]]];"
   "  },"
@@ -119,7 +115,7 @@ TEST(JSCExecutor, CallFunctionWithArray) {
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   std::vector<MethodArgument> args;
   std::vector<MethodArgument> array {
     MethodArgument("hello"),
@@ -138,7 +134,7 @@ TEST(JSCExecutor, CallFunctionWithArray) {
 TEST(JSCExecutor, CallFunctionReturningNumberArray) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    var s = [3, 1, 4];"
   "    return [[module], [method], [[s]]];"
   "  },"
@@ -146,7 +142,7 @@ TEST(JSCExecutor, CallFunctionReturningNumberArray) {
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   auto returnedCalls = executeForMethodCalls(e, 10, 9);
   ASSERT_EQ(1, returnedCalls.size());
   auto returnedCall = returnedCalls[0];
@@ -162,14 +158,14 @@ TEST(JSCExecutor, CallFunctionReturningNumberArray) {
 TEST(JSCExecutor, SetSimpleGlobalVariable) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    return [[module], [method], [[__foo]]];"
   "  },"
   "};"
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   e.setGlobalVariable("__foo", "42");
   auto returnedCalls = executeForMethodCalls(e, 10, 9);
   ASSERT_EQ(1, returnedCalls.size());
@@ -182,14 +178,14 @@ TEST(JSCExecutor, SetSimpleGlobalVariable) {
 TEST(JSCExecutor, SetObjectGlobalVariable) {
   auto jsText = ""
   "var Bridge = {"
-  "  callFunction: function (module, method, args) {"
+  "  callFunctionReturnFlushedQueue: function (module, method, args) {"
   "    return [[module], [method], [[__foo]]];"
   "  },"
   "};"
   "function require() { return Bridge; }"
   "";
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
   auto jsonObject = ""
   "{"
   "  \"foo\": \"hello\","
diff --git a/ReactAndroid/src/main/jni/react/test/jsclogging.cpp b/ReactAndroid/src/main/jni/react/test/jsclogging.cpp
index 0bf49e33d6fa..56ad010bc2e8 100644
--- a/ReactAndroid/src/main/jni/react/test/jsclogging.cpp
+++ b/ReactAndroid/src/main/jni/react/test/jsclogging.cpp
@@ -38,7 +38,7 @@ TEST_F(JSCLoggingTest, LogException) {
   expectedLogMessageSubstring = "I am a banana!";
 
   JSCExecutor e;
-  e.executeApplicationScript(jsText, "");
+  e.loadApplicationScript(jsText, "");
 
   ASSERT_TRUE(hasSeenExpectedLogMessage);
 }
diff --git a/ReactAndroid/src/main/jni/third-party/android-ndk/BUCK b/ReactAndroid/src/main/jni/third-party/android-ndk/BUCK
new file mode 100644
index 000000000000..eb32890ccade
--- /dev/null
+++ b/ReactAndroid/src/main/jni/third-party/android-ndk/BUCK
@@ -0,0 +1,24 @@
+# NOTE(agallagher): The platform should really be responsible for providing
+# this type of implicit dependency.  As it is now, we need to setup a dummy
+# rules to model the Android NDK's built in libraries.
+
+LIBS = (
+  'android',
+  'EGL',
+  'GLESv2',
+  'jnigraphics',
+  'log',
+  'z',
+)
+
+for lib in LIBS:
+  prebuilt_cxx_library(
+    name = lib,
+    header_only = True,
+    exported_platform_linker_flags = [
+      ('android.*', ['-l' + lib]),
+    ],
+    visibility = [
+      'PUBLIC',
+    ],
+  )
\ No newline at end of file
diff --git a/ReactAndroid/src/main/jni/third-party/glibc/BUCK b/ReactAndroid/src/main/jni/third-party/glibc/BUCK
new file mode 100644
index 000000000000..847eed2711c5
--- /dev/null
+++ b/ReactAndroid/src/main/jni/third-party/glibc/BUCK
@@ -0,0 +1,52 @@
+# NOTE(agallagher): The platform really should be responsible for providing
+# this type of provided dependency.  As it is now, we need to setup dummy
+# rules to model glibc's libraries.
+
+# libpthread is implicitly included in the android runtime so, when building
+# on an android platform, we don't do anything.
+prebuilt_cxx_library(
+  name = 'pthread',
+  header_only = True,
+  exported_platform_linker_flags = [
+    ('android', []),
+    ('default', ['-lpthread']),
+    ('static', ['-lpthread']),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+prebuilt_cxx_library(
+  name = 'dl',
+  header_only = True,
+  exported_linker_flags = [
+    '-ldl',
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+prebuilt_cxx_library(
+  name = 'm',
+  header_only = True,
+  exported_linker_flags = [
+    '-lm',
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+prebuilt_cxx_library(
+  name = 'rt',
+  header_only = True,
+  exported_platform_linker_flags = [
+    ('android', []),  # Empty, since `-lc` is implicit
+    ('default', ['-lrt']),
+  ],
+  visibility = [
+      'PUBLIC',
+  ],
+)
\ No newline at end of file
diff --git a/ReactAndroid/src/main/libraries/fbcore/src/main/java/com/facebook/common/logging/BUCK b/ReactAndroid/src/main/libraries/fbcore/src/main/java/com/facebook/common/logging/BUCK
new file mode 100644
index 000000000000..93661768e1bd
--- /dev/null
+++ b/ReactAndroid/src/main/libraries/fbcore/src/main/java/com/facebook/common/logging/BUCK
@@ -0,0 +1,10 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+    name = 'logging',
+    exported_deps = [
+        react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
+    ],
+    visibility = ['//ReactAndroid/...',],
+)
+
diff --git a/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK b/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK
new file mode 100644
index 000000000000..c5d9b9f497b6
--- /dev/null
+++ b/ReactAndroid/src/main/libraries/fbcore/src/test/java/com/facebook/powermock/BUCK
@@ -0,0 +1,162 @@
+android_library(
+    name = 'powermock',
+    exported_deps = [
+        ':powermock-api-mockito',
+        ':mockito-all',
+        ':powermock-api-support',
+        ':powermock-module-junit4-rule',
+        ':powermock-classloading-base',
+        ':powermock-classloading-xstream',
+        ':xstream',
+        ':powermock-core',
+        ':powermock-reflect',
+        ':javassist',
+        ':xmlpull',
+        ':xpp3'
+    ],
+    visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'powermock-core',
+    binary_jar = ':download-powermock-core',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-core',
+    url = 'mvn:org.powermock:powermock-core:jar:1.6.2',
+    sha1 = 'ea04e79244e19dcf0c3ccf6863c5b028b4b58c9c',
+)
+
+prebuilt_jar(
+    name = 'powermock-api-mockito',
+    binary_jar = ':download-powermock-api-mockito',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-api-mockito',
+    url = 'mvn:org.powermock:powermock-api-mockito:jar:1.6.2',
+    sha1 = 'c213230ae20a7b422f3d622a261d0e3427d2464c',
+)
+
+prebuilt_jar(
+    name = 'powermock-api-support',
+    binary_jar = ':download-powermock-api-support',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-api-support',
+    url = 'mvn:org.powermock:powermock-api-support:jar:1.6.2',
+    sha1 = '93b21413b4ee99b7bc0dd34e1416fdca96866aaf',
+)
+
+prebuilt_jar(
+    name = 'powermock-module-junit4-rule',
+    binary_jar = ':download-powermock-module-junit4-rule',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-module-junit4-rule',
+    url = 'mvn:org.powermock:powermock-module-junit4-rule:jar:1.6.2',
+    sha1 = '4847638c5729b9f203e21144b0bdb5d34d888473',
+)
+
+prebuilt_jar(
+    name = 'powermock-classloading-xstream',
+    binary_jar = ':download-powermock-classloading-xstream',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-classloading-xstream',
+    url = 'mvn:org.powermock:powermock-classloading-xstream:jar:1.6.2',
+    sha1 = '3ced31cd7024fe365b9f3c8082d22c02434577da',
+)
+
+prebuilt_jar(
+    name = 'powermock-classloading-base',
+    binary_jar = ':download-powermock-classloading-base',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-classloading-base',
+    url = 'mvn:org.powermock:powermock-classloading-base:jar:1.6.2',
+    sha1 = 'c8bfc10731a02d3b241892cf2c334a754d473ca7',
+)
+
+prebuilt_jar(
+    name = 'xstream',
+    binary_jar = ':download-xstream',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-xstream',
+    url = 'mvn:com.thoughtworks.xstream:xstream:jar:1.4.2',
+    sha1 = '97e5013f391487cce4de6b0eebcde21549e91872',
+)
+
+prebuilt_jar(
+    name = 'powermock-reflect',
+    binary_jar = ':download-powermock-reflect',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-powermock-reflect',
+    url = 'mvn:org.powermock:powermock-reflect:jar:1.6.2',
+    sha1 = '1af1bbd1207c3ecdcf64973e6f9d57dcd17cc145',
+)
+
+prebuilt_jar(
+    name = 'javassist',
+    binary_jar = ':download-javassist',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-javassist',
+    url = 'mvn:org.javassist:javassist:jar:3.20.0-GA',
+    sha1 = 'a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0',
+)
+
+prebuilt_jar(
+    name = 'mockito-all',
+    binary_jar = ':download-mockito-all',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-mockito-all',
+    url = 'mvn:org.mockito:mockito-all:jar:1.10.19',
+    sha1 = '539df70269cc254a58cccc5d8e43286b4a73bf30',
+)
+
+prebuilt_jar(
+    name = 'xmlpull',
+    binary_jar = ':download-xmlpull',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-xmlpull',
+    url = 'mvn:xmlpull:xmlpull:jar:1.1.3.1',
+    sha1 = '2b8e230d2ab644e4ecaa94db7cdedbc40c805dfa',
+)
+
+prebuilt_jar(
+    name = 'xpp3',
+    binary_jar = ':download-xpp3',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'download-xpp3',
+    url = 'mvn:xpp3:xpp3_min:jar:1.1.4c',
+    sha1 = '19d4e90b43059058f6e056f794f0ea4030d60b86',
+)
diff --git a/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK b/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK
new file mode 100644
index 000000000000..7b410d076afe
--- /dev/null
+++ b/ReactAndroid/src/main/libraries/fresco/fresco-react-native/BUCK
@@ -0,0 +1,81 @@
+android_prebuilt_aar(
+    name = 'fresco-react-native',
+    aar = ':fresco-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'fresco-binary-aar',
+    url = 'mvn:com.facebook.fresco:fresco:aar:0.8.1',
+    sha1 = 'f0a4f04318123e1597514b2abf56b7e66581f3f8',
+)
+
+android_prebuilt_aar(
+    name = 'fresco-drawee',
+    aar = ':drawee-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'drawee-binary-aar',
+    url = 'mvn:com.facebook.fresco:drawee:aar:0.8.1',
+    sha1 = 'a944015ddf50fdad79302e42a85a351633c24472',
+)
+
+android_library(
+  name = 'imagepipeline',
+  exported_deps = [
+      ':imagepipeline-core',
+      ':bolts',
+  ],
+  visibility = ['//ReactAndroid/...',],
+)
+
+
+android_prebuilt_aar(
+    name = 'imagepipeline-core',
+    aar = ':imagepipeline-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'imagepipeline-aar',
+    url = 'mvn:com.facebook.fresco:imagepipeline:aar:0.8.1',
+    sha1 = '93fe3e629c03aea8f63dabd80a0e616b0caef65b',
+)
+
+prebuilt_jar(
+  name = 'bolts',
+  binary_jar = ':download-bolts',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-bolts',
+  url = 'mvn:com.parse.bolts:bolts-tasks:jar:1.4.0',
+  sha1 = 'd85884acf6810a3bbbecb587f239005cbc846dc4',
+)
+
+android_prebuilt_aar(
+    name = 'fbcore',
+    aar = ':fbcore-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'fbcore-aar',
+    url = 'mvn:com.facebook.fresco:fbcore:aar:0.8.1',
+    sha1 = 'cc46b3d564139bf63bb41534c7a723ee8119ae5f',
+)
+
+android_prebuilt_aar(
+    name = 'imagepipeline-okhttp',
+    aar = ':imagepipeline-okhttp-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'imagepipeline-okhttp-binary-aar',
+    url = 'mvn:com.facebook.fresco:imagepipeline-okhttp:aar:0.8.1',
+    sha1 = 'd6b16dbaab8b810620347355a425fb2982e33ef8',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/ApkSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ApkSoSource.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/ApkSoSource.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ApkSoSource.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/BUCK b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/BUCK
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/BUCK
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/DirectorySoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/DirectorySoSource.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/DirectorySoSource.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/DirectorySoSource.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Dyn.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Dyn.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Dyn.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Dyn.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Ehdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Ehdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Ehdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Ehdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Phdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Phdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Phdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Phdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Shdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Shdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf32_Shdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf32_Shdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Dyn.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Dyn.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Dyn.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Dyn.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Ehdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Ehdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Ehdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Ehdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Phdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Phdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Phdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Phdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Shdr.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Shdr.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/Elf64_Shdr.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/Elf64_Shdr.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/ExoSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ExoSoSource.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/ExoSoSource.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/ExoSoSource.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/FileLocker.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/FileLocker.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/FileLocker.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/FileLocker.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/MinElf.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/MinElf.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/MinElf.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/MinElf.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/NativeLibrary.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NativeLibrary.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/NativeLibrary.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NativeLibrary.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/NoopSoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NoopSoSource.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/NoopSoSource.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/NoopSoSource.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/SoLoader.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoLoader.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/SoLoader.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoLoader.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/SoSource.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoSource.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/SoSource.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SoSource.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/SysUtil.java b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SysUtil.java
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/SysUtil.java
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/SysUtil.java
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/genstructs.sh b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/genstructs.sh
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/genstructs.sh
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/genstructs.sh
diff --git a/ReactAndroid/src/main/java/com/facebook/soloader/soloader.pro b/ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/soloader.pro
similarity index 100%
rename from ReactAndroid/src/main/java/com/facebook/soloader/soloader.pro
rename to ReactAndroid/src/main/libraries/soloader/java/com/facebook/soloader/soloader.pro
diff --git a/ReactAndroid/src/main/res/devsupport/values-cs/strings.xml b/ReactAndroid/src/main/res/devsupport/values-cs/strings.xml
index c3432a9a8c09..426afdffd3be 100644
--- a/ReactAndroid/src/main/res/devsupport/values-cs/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-cs/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Čekejte prosím...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Prozkoumat prvek</string>
+    <string name="catalyst_element_inspector">Prozkoumat prvek</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-da/strings.xml b/ReactAndroid/src/main/res/devsupport/values-da/strings.xml
index 1d91235a5114..0456813b0f2e 100644
--- a/ReactAndroid/src/main/res/devsupport/values-da/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-da/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Vent venligst...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspicer element</string>
+    <string name="catalyst_element_inspector">Inspicer element</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-de/strings.xml b/ReactAndroid/src/main/res/devsupport/values-de/strings.xml
index 993f9ef5de8f..3f3831ac5683 100644
--- a/ReactAndroid/src/main/res/devsupport/values-de/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-de/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Bitte warten Sie ...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Element untersuchen</string>
+    <string name="catalyst_element_inspector">Element untersuchen</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-el/strings.xml b/ReactAndroid/src/main/res/devsupport/values-el/strings.xml
index 4686909c1034..d441a14fb1eb 100644
--- a/ReactAndroid/src/main/res/devsupport/values-el/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-el/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Περιμένετε...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Εξέταση στοιχείου</string>
+    <string name="catalyst_element_inspector">Εξέταση στοιχείου</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-en-rGB/strings.xml b/ReactAndroid/src/main/res/devsupport/values-en-rGB/strings.xml
index ff57f514337c..de9b5103cbee 100644
--- a/ReactAndroid/src/main/res/devsupport/values-en-rGB/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-en-rGB/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Please wait...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspect Element</string>
+    <string name="catalyst_element_inspector">Show Inspector</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-es-rES/strings.xml b/ReactAndroid/src/main/res/devsupport/values-es-rES/strings.xml
index 79c6256d89ee..babdb30d2237 100644
--- a/ReactAndroid/src/main/res/devsupport/values-es-rES/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-es-rES/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Espera...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspeccionar elemento</string>
+    <string name="catalyst_element_inspector">Inspeccionar elemento</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-es/strings.xml b/ReactAndroid/src/main/res/devsupport/values-es/strings.xml
index f83b2b96e876..770fd6d0cf0c 100644
--- a/ReactAndroid/src/main/res/devsupport/values-es/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-es/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Espera...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspeccionar elemento</string>
+    <string name="catalyst_element_inspector">Inspeccionar elemento</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-fb-rLL/strings.xml b/ReactAndroid/src/main/res/devsupport/values-fb-rLL/strings.xml
index cf7e56e351fc..2b4f0a2ab6ce 100644
--- a/ReactAndroid/src/main/res/devsupport/values-fb-rLL/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-fb-rLL/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">(অনুগ্রহ করে অপেক্ষা করুন….)</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">(तत्व निरीक्षण गर्नुहोस्)</string>
+    <string name="catalyst_element_inspector">(तत्व निरीक्षण गर्नुहोस्)</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-fb/strings.xml b/ReactAndroid/src/main/res/devsupport/values-fb/strings.xml
index 4a3ea031ea4f..9af8d4bad19a 100644
--- a/ReactAndroid/src/main/res/devsupport/values-fb/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-fb/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">[Please wait...#71bad104a80f916d3bfb1ffa23a487af:1]</string>
     <string name="catalyst_jsload_message">[Fetching JS bundle#0de4127d4fc6e7d1e265e07433f26e25:1]</string>
     <string name="catalyst_jsload_error">[Unable to download JS bundle#51057ecd2555f91873cce4c452e1ea03:1]</string>
-    <string name="catalyst_inspect_element">[Inspect Element#c1b106c92869437ebd88f326d632b973:1]</string>
+    <string name="catalyst_element_inspector">[Show Inspector#c1b106c92869437ebd88f326d632b973:1]</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-fi/strings.xml b/ReactAndroid/src/main/res/devsupport/values-fi/strings.xml
index 2468d372e8c0..6499928f2f0c 100644
--- a/ReactAndroid/src/main/res/devsupport/values-fi/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-fi/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Odota…</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Tarkista elementti</string>
+    <string name="catalyst_element_inspector">Tarkista elementti</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-fr/strings.xml b/ReactAndroid/src/main/res/devsupport/values-fr/strings.xml
index f9fcf21a057c..d805db875c33 100644
--- a/ReactAndroid/src/main/res/devsupport/values-fr/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-fr/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Veuillez patienter...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspecter l’élément</string>
+    <string name="catalyst_element_inspector">Inspecter l’élément</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-hu/strings.xml b/ReactAndroid/src/main/res/devsupport/values-hu/strings.xml
index d44f8dc8f1d8..cfa294b08f7a 100644
--- a/ReactAndroid/src/main/res/devsupport/values-hu/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-hu/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Kérjük, várj...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Elem megtekintése</string>
+    <string name="catalyst_element_inspector">Elem megtekintése</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-in/strings.xml b/ReactAndroid/src/main/res/devsupport/values-in/strings.xml
index fe3b189a51a5..0b5a2dbb9462 100644
--- a/ReactAndroid/src/main/res/devsupport/values-in/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-in/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Harap tunggu...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Periksa Elemen</string>
+    <string name="catalyst_element_inspector">Periksa Elemen</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-it/strings.xml b/ReactAndroid/src/main/res/devsupport/values-it/strings.xml
index d9fe2dddf218..0ee3764be8fc 100644
--- a/ReactAndroid/src/main/res/devsupport/values-it/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-it/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Attendi...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Esamina elemento</string>
+    <string name="catalyst_element_inspector">Esamina elemento</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-ja/strings.xml b/ReactAndroid/src/main/res/devsupport/values-ja/strings.xml
index 3ae3b6917e3d..25e5291e82dc 100644
--- a/ReactAndroid/src/main/res/devsupport/values-ja/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-ja/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">しばらくお待ちください</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">要素を確認</string>
+    <string name="catalyst_element_inspector">要素を確認</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-ko/strings.xml b/ReactAndroid/src/main/res/devsupport/values-ko/strings.xml
index 21384a3a79ba..8f0d6633895f 100644
--- a/ReactAndroid/src/main/res/devsupport/values-ko/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-ko/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">기다려주세요...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">요소 검사</string>
+    <string name="catalyst_element_inspector">요소 검사</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-nb/strings.xml b/ReactAndroid/src/main/res/devsupport/values-nb/strings.xml
index 24f646c972c5..88450696a1db 100644
--- a/ReactAndroid/src/main/res/devsupport/values-nb/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-nb/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Vent litt ...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspiser element</string>
+    <string name="catalyst_element_inspector">Inspiser element</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-nl/strings.xml b/ReactAndroid/src/main/res/devsupport/values-nl/strings.xml
index 85d5d70f95aa..3fe195b32826 100644
--- a/ReactAndroid/src/main/res/devsupport/values-nl/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-nl/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Even geduld...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Element inspecteren</string>
+    <string name="catalyst_element_inspector">Element inspecteren</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-pl/strings.xml b/ReactAndroid/src/main/res/devsupport/values-pl/strings.xml
index b96d4f04fa03..88e9de7a19dc 100644
--- a/ReactAndroid/src/main/res/devsupport/values-pl/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-pl/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Zaczekaj...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Zbadaj element</string>
+    <string name="catalyst_element_inspector">Zbadaj element</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-pt-rPT/strings.xml b/ReactAndroid/src/main/res/devsupport/values-pt-rPT/strings.xml
index 98d7879bb9aa..1c5354bdc966 100644
--- a/ReactAndroid/src/main/res/devsupport/values-pt-rPT/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-pt-rPT/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Aguarda...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspect Element</string>
+    <string name="catalyst_element_inspector">Show Inspector</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-pt/strings.xml b/ReactAndroid/src/main/res/devsupport/values-pt/strings.xml
index ec3284c279f5..aca1d88b01ed 100644
--- a/ReactAndroid/src/main/res/devsupport/values-pt/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-pt/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Aguarde...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspect Element</string>
+    <string name="catalyst_element_inspector">Show Inspector</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-ro/strings.xml b/ReactAndroid/src/main/res/devsupport/values-ro/strings.xml
index 021a3fa34f2c..71b20b37ef66 100644
--- a/ReactAndroid/src/main/res/devsupport/values-ro/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-ro/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Please wait...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspect Element</string>
+    <string name="catalyst_element_inspector">Show Inspector</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-ru/strings.xml b/ReactAndroid/src/main/res/devsupport/values-ru/strings.xml
index 6d2ea955af2c..ee462ab36eec 100644
--- a/ReactAndroid/src/main/res/devsupport/values-ru/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-ru/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Подождите...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Проверить элемент</string>
+    <string name="catalyst_element_inspector">Проверить элемент</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-sv/strings.xml b/ReactAndroid/src/main/res/devsupport/values-sv/strings.xml
index da23a64ea98f..03440336834c 100644
--- a/ReactAndroid/src/main/res/devsupport/values-sv/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-sv/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Vänta ...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Inspektionselement</string>
+    <string name="catalyst_element_inspector">Inspektionselement</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-th/strings.xml b/ReactAndroid/src/main/res/devsupport/values-th/strings.xml
index 57ecdc141d3a..da3454ae4b91 100644
--- a/ReactAndroid/src/main/res/devsupport/values-th/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-th/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">โปรดรอ...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">ตรวจสอบอิลิเมนต์</string>
+    <string name="catalyst_element_inspector">ตรวจสอบอิลิเมนต์</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-tr/strings.xml b/ReactAndroid/src/main/res/devsupport/values-tr/strings.xml
index 62b5118494b0..afeccc0b7030 100644
--- a/ReactAndroid/src/main/res/devsupport/values-tr/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-tr/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Lütfen bekleyin...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Öğeyi Denetle</string>
+    <string name="catalyst_element_inspector">Öğeyi Denetle</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-vi/strings.xml b/ReactAndroid/src/main/res/devsupport/values-vi/strings.xml
index 2f35967da51a..0e37ac1bc54f 100644
--- a/ReactAndroid/src/main/res/devsupport/values-vi/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-vi/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">Vui lòng đợi...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">Kiểm tra phần tử</string>
+    <string name="catalyst_element_inspector">Kiểm tra phần tử</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-zh-rCN/strings.xml b/ReactAndroid/src/main/res/devsupport/values-zh-rCN/strings.xml
index 021308f2dc8c..ab8a485d5f61 100644
--- a/ReactAndroid/src/main/res/devsupport/values-zh-rCN/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-zh-rCN/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">请稍等...</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">检查元素</string>
+    <string name="catalyst_element_inspector">检查元素</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-zh-rHK/strings.xml b/ReactAndroid/src/main/res/devsupport/values-zh-rHK/strings.xml
index cd2c73c36715..68be2f34cd76 100644
--- a/ReactAndroid/src/main/res/devsupport/values-zh-rHK/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-zh-rHK/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">請稍候……</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">檢查項目</string>
+    <string name="catalyst_element_inspector">檢查項目</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values-zh-rTW/strings.xml b/ReactAndroid/src/main/res/devsupport/values-zh-rTW/strings.xml
index bad914ff30a9..e8e3a392e00d 100644
--- a/ReactAndroid/src/main/res/devsupport/values-zh-rTW/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values-zh-rTW/strings.xml
@@ -12,5 +12,5 @@
     <string name="catalyst_jsload_title">請稍候……</string>
     <string name="catalyst_jsload_message">Fetching JS bundle</string>
     <string name="catalyst_jsload_error">Unable to download JS bundle</string>
-    <string name="catalyst_inspect_element">檢查元素</string>
+    <string name="catalyst_element_inspector">檢查元素</string>
 </resources>
diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml
index 8969fe61ea5c..d56ca71d1cec 100644
--- a/ReactAndroid/src/main/res/devsupport/values/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml
@@ -3,6 +3,8 @@
   <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_off" project="catalyst" translatable="false">Stop Chrome Debugging</string>
+  <string name="catalyst_hot_module_replacement" project="catalyst" translatable="false">Enable Hot Module Replacement</string>
+  <string name="catalyst_hot_module_replacement_off" project="catalyst" translatable="false">Disable Hot Module Replacement</string>
   <string name="catalyst_live_reload" project="catalyst" translatable="false">Enable Live Reload</string>
   <string name="catalyst_live_reload_off" project="catalyst" translatable="false">Disable Live Reload</string>
   <string name="catalyst_perf_monitor" project="catalyst" translatable="false">Enable Perf Monitor</string>
@@ -14,7 +16,8 @@
   <string name="catalyst_jsload_error" project="catalyst" translatable="false">Unable to download JS bundle. Did you forget to start the development server or connect your device?</string>
   <string name="catalyst_remotedbg_message" project="catalyst" translatable="false">Connecting to remote debugger</string>
   <string name="catalyst_remotedbg_error" project="catalyst" translatable="false">Unable to connect with remote debugger</string>
-  <string name="catalyst_inspect_element" project="catalyst" translatable="false">Inspect Element</string>
+  <string name="catalyst_element_inspector" project="catalyst" translatable="false">Show Inspector</string>
+  <string name="catalyst_element_inspector_off" project="catalyst" translatable="false">Hide Inspector</string>
   <string name="catalyst_start_profile" project="catalyst" translatable="false">Start Profile</string>
   <string name="catalyst_stop_profile" project="catalyst" translatable="false">Stop Profile</string>
 </resources>
diff --git a/ReactAndroid/src/main/third-party/android/support-annotations/BUCK b/ReactAndroid/src/main/third-party/android/support-annotations/BUCK
new file mode 100644
index 000000000000..0043bf0ce7b2
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/android/support-annotations/BUCK
@@ -0,0 +1,11 @@
+prebuilt_jar(
+    name = 'android-support-annotations',
+    binary_jar = ':support-annotations-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'support-annotations-binary-aar',
+    url = 'mvn:com.android.support:support-annotations:jar:22.2.0',
+    sha1 = '66b42a1f3eb7676070b7ef7f14b603483aecbee1',
+)
diff --git a/ReactAndroid/src/main/third-party/android/support/v4/BUCK b/ReactAndroid/src/main/third-party/android/support/v4/BUCK
new file mode 100644
index 000000000000..3f34e9424b66
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/android/support/v4/BUCK
@@ -0,0 +1,23 @@
+android_prebuilt_aar(
+    name = 'lib-support-v4',
+    aar = ':lib-support-v4-binary-aar',
+    visibility = ['PUBLIC',],
+)
+
+remote_file(
+    name = 'lib-support-v4-binary-aar',
+    url = 'mvn:com.android.support:support-v4:aar:23.0.1',
+    sha1 = '9e8da0e4ecf9f63258c7fbd273889252cba2d0c3',
+)
+
+prebuilt_jar(
+    name = 'lib-support-annotations',
+    binary_jar = ':lib-support-annotations-binary-aar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'lib-support-annotations-binary-aar',
+    url = 'mvn:com.android.support:support-annotations:jar:22.2.0',
+    sha1 = '66b42a1f3eb7676070b7ef7f14b603483aecbee1',
+)
diff --git a/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/BUCK b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/BUCK
new file mode 100644
index 000000000000..9afb2e02fd6a
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/BUCK
@@ -0,0 +1,61 @@
+include_defs('//ReactAndroid/DEFS')
+
+# This is a bit messy and hopefully a temporary thing
+# The problem is that Gradle extracts appcompat resources into app namespace, com.facebook.react
+# While BUCK behaves properly and extracts them into android.support.v7.appcompat package.
+# We want to support both Gradle and BUCK builds so we hack a bit how BUCK extracts resources.
+# Besides that we still need JAVA classes from appcompat-v7.aar, that is why android_library
+# extracts classes.jar but the trick is that we can't take full appcompat.aar because resources
+# extracted from it by BUCK would conflict with resources we use under Gradelified package
+# All this mumbo jumbo will go away after t10182713
+
+android_library(
+  name = 'appcompat',
+  deps = [
+    ':res-for-appcompat',
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+  exported_deps = [
+    ':classes-for-react-native',
+  ],
+)
+
+# still used by appcompat library internally, so we need both during the build
+android_resource(
+  name = 'res-for-appcompat',
+  res = ':res-unpacker-cmd',
+  package = 'android.support.v7.appcompat',
+  visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'classes-for-react-native',
+    binary_jar = ':classes-unpacker-cmd',
+    visibility = ['//ReactAndroid/...',],
+)
+
+genrule(
+    name = 'classes-unpacker-cmd',
+    cmd = '$(exe :aar-unpacker) $(location :appcompat-binary-aar) "classes.jar" $OUT',
+    out = 'classes.jar',
+)
+
+genrule(
+    name = 'res-unpacker-cmd',
+    cmd = '$(exe :aar-unpacker) $(location :appcompat-binary-aar) "res/" $OUT',
+    out = 'res',
+    visibility = ['//ReactAndroid/...',],
+)
+
+python_binary(
+    name = 'aar-unpacker',
+    main = 'aar-unpacker.py',
+)
+
+remote_file(
+    name = 'appcompat-binary-aar',
+    url = 'mvn:com.android.support:appcompat-v7:aar:23.0.1',
+    sha1 = '7d659f671541394a8bc2b9f909950aa2a5ec87ff',
+)
diff --git a/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py
new file mode 100644
index 000000000000..ddb65efb2192
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py
@@ -0,0 +1,20 @@
+import contextlib
+import os
+import shutil
+import sys
+import tempfile
+import zipfile
+
+# Helper that unpacks the contents of an .aar file
+# into given destination.
+
+@contextlib.contextmanager
+def cleanup(path):
+    yield path
+    shutil.rmtree(path)
+
+if __name__ == '__main__':
+    with zipfile.ZipFile(sys.argv[1], 'r') as z:
+        with cleanup(tempfile.mkdtemp()) as temp_path:
+            z.extractall(temp_path, filter(lambda n: n.startswith(sys.argv[2]), z.namelist()))
+            shutil.move(os.path.join(temp_path, sys.argv[2]), sys.argv[3])
diff --git a/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK b/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK
new file mode 100644
index 000000000000..80624867285f
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/android/support/v7/recyclerview/BUCK
@@ -0,0 +1,13 @@
+android_prebuilt_aar(
+    name = 'recyclerview',
+    aar = ':recyclerview-binary-aar',
+    visibility = [
+        '//ReactAndroid/...',
+    ],
+)
+
+remote_file(
+    name = 'recyclerview-binary-aar',
+    url = 'mvn:com.android.support:recyclerview-v7:aar:23.0.1',
+    sha1 = '94d5f16be156521e135295e2ffa70d79ef8ad9d5',
+)
diff --git a/ReactAndroid/src/main/third-party/java/asm/BUCK b/ReactAndroid/src/main/third-party/java/asm/BUCK
new file mode 100644
index 000000000000..08b3194c4785
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/asm/BUCK
@@ -0,0 +1,71 @@
+android_library(
+  name = 'asm',
+  exported_deps = [
+      ':asm-core',
+      ':asm-commons',
+      ':asm-util',
+      ':asm-analysis',
+      ':asm-tree',
+  ],
+  visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+  name = 'asm-core',
+  binary_jar = ':download-asm',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-asm',
+  url = 'mvn:org.ow2.asm:asm:jar:5.0.1',
+  sha1 = '2fd56467a018aafe6ec6a73ccba520be4a7e1565',
+)
+
+prebuilt_jar(
+  name = 'asm-commons',
+  binary_jar = ':download-asm-commons',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-asm-commons',
+  url = 'mvn:org.ow2.asm:asm-commons:jar:5.0.1',
+  sha1 = '7b7147a390a93a14d2edfdcf3f7b0e87a0939c3e',
+)
+
+prebuilt_jar(
+  name = 'asm-tree',
+  binary_jar = ':download-asm-tree',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-asm-tree',
+  url = 'mvn:org.ow2.asm:asm-tree:jar:5.0.1',
+  sha1 = '1b1e6e9d869acd704056d0a4223071a511c619e6',
+)
+
+prebuilt_jar(
+  name = 'asm-util',
+  binary_jar = ':download-asm-util',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-asm-util',
+  url = 'mvn:org.ow2.asm:asm-util:jar:5.0.1',
+  sha1 = '7c8caddfbd0b2d7b844f8fcc75175b9cb9cf4724',
+)
+
+prebuilt_jar(
+  name = 'asm-analysis',
+  binary_jar = ':download-asm-analysis',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-asm-analysis',
+  url = 'mvn:org.ow2.asm:asm-analysis:jar:5.0.1',
+  sha1 = 'e286fbee48efacb4e7c175f7948d9d8b2ab52352',
+)
diff --git a/ReactAndroid/src/main/third-party/java/fest/BUCK b/ReactAndroid/src/main/third-party/java/fest/BUCK
new file mode 100644
index 000000000000..9b9e10c86331
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/fest/BUCK
@@ -0,0 +1,32 @@
+android_library(
+    name = 'fest',
+    exported_deps = [
+        ':fest-core',
+        ':fest-util',
+    ],
+    visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'fest-core',
+    binary_jar = ':fest-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'fest-binary-jar',
+    url = 'mvn:org.easytesting:fest-assert-core:jar:2.0M10',
+    sha1 = 'cb7c91cf614901928ae405f19d9bcdedf82781db',
+)
+
+prebuilt_jar(
+    name = 'fest-util',
+    binary_jar = ':fest-util-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'fest-util-binary-jar',
+    url = 'mvn:org.easytesting:fest-util:jar:1.2.5',
+    sha1 = 'c4a8d7305b23b8d043be12c979813b096df11f44',
+)
diff --git a/ReactAndroid/src/main/third-party/java/infer-annotations/BUCK b/ReactAndroid/src/main/third-party/java/infer-annotations/BUCK
new file mode 100644
index 000000000000..2eabc6be2f0f
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/infer-annotations/BUCK
@@ -0,0 +1,5 @@
+prebuilt_jar(
+    name = 'infer-annotations',
+    binary_jar = 'infer-annotations-1.5.jar',
+    visibility = ['//ReactAndroid/...'],
+)
diff --git a/ReactAndroid/libs/infer-annotations-1.5.jar b/ReactAndroid/src/main/third-party/java/infer-annotations/infer-annotations-1.5.jar
similarity index 100%
rename from ReactAndroid/libs/infer-annotations-1.5.jar
rename to ReactAndroid/src/main/third-party/java/infer-annotations/infer-annotations-1.5.jar
diff --git a/ReactAndroid/src/main/third-party/java/jackson/BUCK b/ReactAndroid/src/main/third-party/java/jackson/BUCK
new file mode 100644
index 000000000000..e6377adf4ed5
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/jackson/BUCK
@@ -0,0 +1,46 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'jackson',
+  exported_deps = [
+    ':databind',
+    ':annotations',
+  ],
+  visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'core',
+    binary_jar = ':jackson-core-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'jackson-core-binary-jar',
+    url = 'mvn:com.fasterxml.jackson.core:jackson-core:jar:2.2.3',
+    sha1 = '1a0113da2cab5f4c216b4e5e7c1dbfaa67087e14',
+)
+
+prebuilt_jar(
+    name = 'databind',
+    binary_jar = ':jackson-databind-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'jackson-databind-jar',
+    url = 'mvn:com.fasterxml.jackson.core:jackson-databind:jar:2.2.3',
+    sha1 = '03ae380888029daefb91d3ecdca3a37d8cb92bc9',
+)
+
+prebuilt_jar(
+    name = 'annotations',
+    binary_jar = ':jackson-annotations-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'jackson-annotations-jar',
+    url = 'mvn:com.fasterxml.jackson.core:jackson-annotations:jar:2.2.3',
+    sha1 = '0527fece4f23a457070a36c371a26d6c0208e1c3',
+)
diff --git a/ReactAndroid/src/main/third-party/java/javapoet/BUCK b/ReactAndroid/src/main/third-party/java/javapoet/BUCK
new file mode 100644
index 000000000000..d16df03bf9fa
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/javapoet/BUCK
@@ -0,0 +1,11 @@
+prebuilt_jar(
+    name = 'javapoet',
+    binary_jar = ':jsr305-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'jsr305-binary-jar',
+    url = 'mvn:com.squareup:javapoet:jar:1.2.0',
+    sha1 = 'ad3ba65c1788f4d814a4da056323e2b84412fb3c',
+)
diff --git a/ReactAndroid/src/main/third-party/java/jsr-305/BUCK b/ReactAndroid/src/main/third-party/java/jsr-305/BUCK
new file mode 100644
index 000000000000..0c02a2716a7a
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/jsr-305/BUCK
@@ -0,0 +1,11 @@
+prebuilt_jar(
+    name = 'jsr-305',
+    binary_jar = ':jsr305-binary-jar',
+    visibility = ['PUBLIC'],
+)
+
+remote_file(
+    name = 'jsr305-binary-jar',
+    url = 'mvn:com.google.code.findbugs:jsr305:jar:3.0.0',
+    sha1 = '5871fb60dc68d67da54a663c3fd636a10a532948',
+)
diff --git a/ReactAndroid/src/main/third-party/java/junit/BUCK b/ReactAndroid/src/main/third-party/java/junit/BUCK
new file mode 100644
index 000000000000..327e10078527
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/junit/BUCK
@@ -0,0 +1,34 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'junit',
+  exported_deps = [
+    ':junit-core',
+    ':hamcrest',
+  ],
+  visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+  name = 'junit-core',
+  binary_jar = ':download-junit',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-junit',
+  url = 'mvn:junit:junit:jar:4.12',
+  sha1 = '2973d150c0dc1fefe998f834810d68f278ea58ec',
+)
+
+prebuilt_jar(
+  name = 'hamcrest',
+  binary_jar = ':download-hamcrest',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-hamcrest',
+  url = 'mvn:org.hamcrest:hamcrest-all:jar:1.3',
+  sha1 = '63a21ebc981131004ad02e0434e799fd7f3a8d5a',
+)
diff --git a/ReactAndroid/src/main/third-party/java/mockito/BUCK b/ReactAndroid/src/main/third-party/java/mockito/BUCK
new file mode 100644
index 000000000000..35b50499a5a7
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/mockito/BUCK
@@ -0,0 +1,32 @@
+android_library(
+    name = 'mockito',
+    exported_deps = [
+        ':mockito-core',
+        ':objenesis',
+    ],
+    visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'mockito-core',
+    binary_jar = ':mockito-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'mockito-binary-jar',
+    url = 'mvn:org.mockito:mockito-core:jar:1.10.19',
+    sha1 = 'e8546f5bef4e061d8dd73895b4e8f40e3fe6effe',
+)
+
+prebuilt_jar(
+    name = 'objenesis',
+    binary_jar = ':objenesis-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'objenesis-binary-jar',
+    url = 'mvn:org.objenesis:objenesis:jar:2.1',
+    sha1 = '87c0ea803b69252868d09308b4618f766f135a96',
+)
diff --git a/ReactAndroid/src/main/third-party/java/okhttp/BUCK b/ReactAndroid/src/main/third-party/java/okhttp/BUCK
new file mode 100644
index 000000000000..f4da3fa0b0a1
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/okhttp/BUCK
@@ -0,0 +1,23 @@
+prebuilt_jar(
+    name = 'okhttp',
+    binary_jar = ':okhttp-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'okhttp-binary-jar',
+    url = 'mvn:com.squareup.okhttp:okhttp:jar:2.5.0',
+    sha1 = '4de2b4ed3445c37ec1720a7d214712e845a24636'
+)
+
+prebuilt_jar(
+    name = 'okhttp-ws',
+    binary_jar = ':okhttp-ws-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'okhttp-ws-binary-jar',
+    url = 'mvn:com.squareup.okhttp:okhttp-ws:jar:2.5.0',
+    sha1 = '0e9753b7dcae5deca92e871c5c759067070b07bc',
+)
diff --git a/ReactAndroid/src/main/third-party/java/okio/BUCK b/ReactAndroid/src/main/third-party/java/okio/BUCK
new file mode 100644
index 000000000000..61ad012c23cd
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/okio/BUCK
@@ -0,0 +1,11 @@
+prebuilt_jar(
+    name = 'okio',
+    binary_jar = ':okio-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'okio-binary-jar',
+    url = 'mvn:com.squareup.okio:okio:jar:1.6.0',
+    sha1 = '98476622f10715998eacf9240d6b479f12c66143',
+)
diff --git a/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK b/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK
new file mode 100644
index 000000000000..1f5d94f7670a
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/robolectric3/robolectric/BUCK
@@ -0,0 +1,153 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+    name = 'robolectric',
+    exported_deps = [
+        ':robolectric-core',
+        ':robolectric-resources',
+        ':robolectric-annotations',
+        ':robolectric-utils',
+        ':bouncycastle',
+        ':vtd-xml',
+        ':icu',
+        ':android-all-4.1.2_r1-robolectric-0',
+        ':json-20080701',
+        ':tagsoup-1.2',
+        ':shadows-core-3.0-16',
+        react_native_dep('third-party/java/asm:asm'),
+        react_native_dep('third-party/java/sqlite:sqlite'),
+    ],
+    visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+    name = 'robolectric-core',
+    binary_jar = ':robolectric-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-binary-jar',
+    url = 'mvn:org.robolectric:robolectric:jar:3.0',
+    sha1 = 'f888cea3bc1a24110e315eb9827ab593610ea62f',
+)
+
+prebuilt_jar(
+    name = 'robolectric-resources',
+    binary_jar = ':robolectric-resources-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-resources-binary-jar',
+    url = 'mvn:org.robolectric:robolectric-resources:jar:3.0',
+    sha1 = '1ab609054aab67cd13a434567467f4b4774f2465',
+)
+
+prebuilt_jar(
+    name = 'robolectric-annotations',
+    binary_jar = ':robolectric-annotations-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-annotations-binary-jar',
+    url = 'mvn:org.robolectric:robolectric-annotations:jar:3.0',
+    sha1 = '2a6cfc072d7680694c1ff893c5dc8fec33163110',
+)
+
+prebuilt_jar(
+    name = 'robolectric-utils',
+    binary_jar = ':robolectric-utils-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-utils-binary-jar',
+    url = 'mvn:org.robolectric:robolectric-utils:jar:3.0',
+    sha1 = '4bcecd8115fe7296088bb1636e6cbd7ae8927392',
+)
+
+prebuilt_jar(
+    name = 'bouncycastle',
+    binary_jar = ':bouncycastle-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'bouncycastle-binary-jar',
+    url = 'mvn:org.bouncycastle:bcprov-jdk16:jar:1.46',
+    sha1 = 'ce091790943599535cbb4de8ede84535b0c1260c',
+)
+
+prebuilt_jar(
+    name = 'vtd-xml',
+    binary_jar = ':vtd-xml-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'vtd-xml-binary-jar',
+    url = 'mvn:com.ximpleware:vtd-xml:jar:2.11',
+    sha1 = 'ee5bcf62c1acf76434ee9f1c67a840bafef72a6d',
+)
+
+prebuilt_jar(
+    name = 'icu',
+    binary_jar = ':icu-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'icu-binary-jar',
+    url = 'mvn:com.ibm.icu:icu4j:jar:53.1',
+    sha1 = '786d9055d4ca8c1aab4a7d4ac8283f973fd7e41f',
+)
+
+prebuilt_jar(
+    name = 'android-all-4.1.2_r1-robolectric-0', # name defines filename used by robolectric in runtime
+    binary_jar = ':robolectric-android-all-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-android-all-binary-jar',
+    url = 'mvn:org.robolectric:android-all:jar:4.1.2_r1-robolectric-0',
+    sha1 = 'aecc8ce5119a25fcea1cdf8285469c9d1261a352',
+)
+
+prebuilt_jar(
+    name = 'json-20080701', # name defines filename used by robolectric in runtime
+    binary_jar = ':json-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'json-jar',
+    url = 'mvn:org.json:json:jar:20080701',
+    sha1 = 'd652f102185530c93b66158b1859f35d45687258',
+)
+
+prebuilt_jar(
+    name = 'tagsoup-1.2', # name defines filename used by robolectric in runtime
+    binary_jar = ':tagsoup-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'tagsoup-jar',
+    url = 'mvn:org.ccil.cowan.tagsoup:tagsoup:jar:1.2',
+    sha1 = '639fd364750d7363c85797dc944b4a80f78fa684',
+)
+
+prebuilt_jar(
+    name = 'shadows-core-3.0-16', # name defines filename used by robolectric in runtime
+    binary_jar = ':robolectric-shadows-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'robolectric-shadows-binary-jar',
+    url = 'https://repo1.maven.org/maven2/org/robolectric/shadows-core/3.0/shadows-core-3.0-16.jar',
+    sha1 = '39d7a856bf91640b1a6d044333336a2b3f3c198f',
+)
diff --git a/ReactAndroid/src/main/third-party/java/sqlite/BUCK b/ReactAndroid/src/main/third-party/java/sqlite/BUCK
new file mode 100644
index 000000000000..441701450543
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/sqlite/BUCK
@@ -0,0 +1,19 @@
+android_library(
+  name = 'sqlite',
+  exported_deps = [
+      ':sqlite4java'
+  ],
+  visibility = ['//ReactAndroid/...',],
+)
+
+prebuilt_jar(
+  name = 'sqlite4java',
+  binary_jar = ':download-sqlite4java',
+  visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+  name = 'download-sqlite4java',
+  url = 'mvn:com.almworks.sqlite4java:sqlite4java:jar:0.282',
+  sha1 = '745a7e2f35fdbe6336922e0d492c979dbbfa74fb',
+)
diff --git a/ReactAndroid/src/main/third-party/java/stetho/BUCK b/ReactAndroid/src/main/third-party/java/stetho/BUCK
new file mode 100644
index 000000000000..598e76ba87b0
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/stetho/BUCK
@@ -0,0 +1,23 @@
+prebuilt_jar(
+    name = 'stetho',
+    binary_jar = ':stetho-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'stetho-binary-jar',
+    url = 'mvn:com.facebook.stetho:stetho:jar:1.2.0',
+    sha1 = '1d6cdb8ce8185b8b23213fad48a83987fb6d2d67',
+)
+
+prebuilt_jar(
+    name = 'stetho-okhttp',
+    binary_jar = ':stetho-okhttp-binary-jar',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'stetho-okhttp-binary-jar',
+    url = 'mvn:com.facebook.stetho:stetho-okhttp:jar:1.2.0',
+    sha1 = '6c5c9380e245c4ff5be2b2a49dcbb0200c39798e',
+)
diff --git a/ReactAndroid/src/main/third-party/java/testing-support-lib/BUCK b/ReactAndroid/src/main/third-party/java/testing-support-lib/BUCK
new file mode 100644
index 000000000000..f3b93922cc73
--- /dev/null
+++ b/ReactAndroid/src/main/third-party/java/testing-support-lib/BUCK
@@ -0,0 +1,12 @@
+android_prebuilt_aar(
+    name = 'testing-support-lib',
+    aar = ':testing-support-lib-download',
+    visibility = ['//ReactAndroid/...',],
+)
+
+remote_file(
+    name = 'testing-support-lib-download',
+    url = 'mvn:com.android.support.test:testing-support-lib:aar:0.1',
+    sha1 = '36e4f08b2a3389dd5a5093411df35091bdd68361',
+)
+
diff --git a/ReactAndroid/src/test/java/com/facebook/react/BUCK b/ReactAndroid/src/test/java/com/facebook/react/BUCK
new file mode 100644
index 000000000000..0396f42ae08a
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/BUCK
@@ -0,0 +1,34 @@
+include_defs('//ReactAndroid/DEFS')
+
+robolectric3_test(
+  name = 'react',
+  # Please change the contact to the oncall of your team
+  contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'],
+  srcs = glob(['*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/animation:animation'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/touch:touch'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_target('java/com/facebook/react/views/view:view'),
+    react_native_tests_target('java/com/facebook/react/bridge:testhelpers'),
+
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_dep('third-party/java/fest:fest'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'),
+  ],
+)
+
+project_config(
+  test_target = ':react',
+)
+
diff --git a/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java b/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java
new file mode 100644
index 000000000000..38e2dce4864c
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/CompositeReactPackageTest.java
@@ -0,0 +1,218 @@
+/**
+ * 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;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.ViewManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class CompositeReactPackageTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Mock ReactPackage packageNo1;
+  @Mock ReactPackage packageNo2;
+  @Mock ReactPackage packageNo3;
+
+  @Mock ReactApplicationContext reactContext;
+
+  @Before
+  public void initMocks() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void testThatCreateNativeModulesIsCalledOnAllPackages() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2, packageNo3);
+
+    // When
+    composite.createNativeModules(reactContext);
+
+    // Then
+    verify(packageNo1).createNativeModules(reactContext);
+    verify(packageNo2).createNativeModules(reactContext);
+    verify(packageNo3).createNativeModules(reactContext);
+  }
+
+  @Test
+  public void testThatCreateJSModulesIsCalledOnAllPackages() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2, packageNo3);
+
+    // When
+    composite.createJSModules();
+
+    // Then
+    verify(packageNo1).createJSModules();
+    verify(packageNo2).createJSModules();
+    verify(packageNo3).createJSModules();
+  }
+
+  @Test
+  public void testThatCreateViewManagersIsCalledOnAllPackages() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2, packageNo3);
+
+    // When
+    composite.createViewManagers(reactContext);
+
+    // Then
+    verify(packageNo1).createViewManagers(reactContext);
+    verify(packageNo2).createViewManagers(reactContext);
+    verify(packageNo3).createViewManagers(reactContext);
+  }
+
+  @Test
+  public void testThatCompositeReturnsASumOfNativeModules() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2);
+
+    NativeModule moduleNo1 = mock(NativeModule.class);
+    when(moduleNo1.getName()).thenReturn("ModuleNo1");
+
+    // module2 and module3 will share same name, composite should return only the latter one
+    final String sameModuleName = "SameModuleName";
+
+    NativeModule moduleNo2 = mock(NativeModule.class);
+    when(moduleNo2.getName()).thenReturn(sameModuleName);
+
+    NativeModule moduleNo3 = mock(NativeModule.class);
+    when(moduleNo3.getName()).thenReturn(sameModuleName);
+
+    NativeModule moduleNo4 = mock(NativeModule.class);
+    when(moduleNo4.getName()).thenReturn("ModuleNo4");
+
+    when(packageNo1.createNativeModules(reactContext)).thenReturn(
+        Arrays.asList(new NativeModule[]{moduleNo1, moduleNo2}));
+
+    when(packageNo2.createNativeModules(reactContext)).thenReturn(
+        Arrays.asList(new NativeModule[]{moduleNo3, moduleNo4}));
+
+    // When
+    List<NativeModule> compositeModules = composite.createNativeModules(reactContext);
+
+    // Then
+
+    // Wrapping lists into sets to be order-independent.
+    // Note that there should be no module2 returned.
+    Set<NativeModule> expected = new HashSet<>(
+        Arrays.asList(new NativeModule[]{moduleNo1, moduleNo3, moduleNo4}));
+    Set<NativeModule> actual = new HashSet<>(compositeModules);
+
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void testThatCompositeReturnsASumOfViewManagers() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2);
+
+    ViewManager managerNo1 = mock(ViewManager.class);
+    when(managerNo1.getName()).thenReturn("ManagerNo1");
+
+    // managerNo2 and managerNo3 will share same name, composite should return only the latter one
+    final String sameModuleName = "SameModuleName";
+
+    ViewManager managerNo2 = mock(ViewManager.class);
+    when(managerNo2.getName()).thenReturn(sameModuleName);
+
+    ViewManager managerNo3 = mock(ViewManager.class);
+    when(managerNo3.getName()).thenReturn(sameModuleName);
+
+    ViewManager managerNo4 = mock(ViewManager.class);
+    when(managerNo4.getName()).thenReturn("ManagerNo4");
+
+    when(packageNo1.createViewManagers(reactContext)).thenReturn(
+        Arrays.asList(new ViewManager[]{managerNo1, managerNo2}));
+
+    when(packageNo2.createViewManagers(reactContext)).thenReturn(
+        Arrays.asList(new ViewManager[]{managerNo3, managerNo4}));
+
+    // When
+    List<ViewManager> compositeModules = composite.createViewManagers(reactContext);
+
+    // Then
+
+    // Wrapping lists into sets to be order-independent.
+    // Note that there should be no managerNo2 returned.
+    Set<ViewManager> expected = new HashSet<>(
+        Arrays.asList(new ViewManager[]{managerNo1, managerNo3, managerNo4})
+    );
+    Set<ViewManager> actual = new HashSet<>(compositeModules);
+
+    assertEquals(expected, actual);
+  }
+
+  // public access level is required by Mockito
+  public static class JavaScriptModuleNo1 implements JavaScriptModule {};
+  public static class JavaScriptModuleNo2 implements JavaScriptModule {};
+  public static class JavaScriptModuleNo3 implements JavaScriptModule {};
+
+  @Test
+  public void testThatCompositeReturnsASumOfJSModules() {
+    // Given
+    CompositeReactPackage composite = new CompositeReactPackage(packageNo1, packageNo2);
+
+    Class<? extends JavaScriptModule> moduleNo1 = mock(JavaScriptModuleNo1.class).getClass();
+    Class<? extends JavaScriptModule> moduleNo2 = mock(JavaScriptModuleNo2.class).getClass();
+    Class<? extends JavaScriptModule> moduleNo3 = mock(JavaScriptModuleNo3.class).getClass();
+
+    List<Class<? extends JavaScriptModule>> l1 = new ArrayList<>();
+    l1.add(moduleNo1);
+    when(packageNo1.createJSModules()).thenReturn(l1);
+
+    List<Class<? extends JavaScriptModule>> l2 = new ArrayList<>();
+    l2.add(moduleNo2);
+    l2.add(moduleNo3);
+    when(packageNo2.createJSModules()).thenReturn(l2);
+
+    // When
+    List<Class<? extends JavaScriptModule>> compositeModules = composite.createJSModules();
+
+    // Then
+
+    // wrapping lists into sets to be order-independent
+    List<Class<? extends JavaScriptModule>> l3 = new ArrayList<>();
+    l3.add(moduleNo1);
+    l3.add(moduleNo2);
+    l3.add(moduleNo3);
+    Set<Class<? extends JavaScriptModule>> expected = new HashSet<>(l3);
+    Set<Class<? extends JavaScriptModule>> actual = new HashSet<>(compositeModules);
+
+    assertEquals(expected, actual);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java
new file mode 100644
index 000000000000..97e9d6e5392a
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java
@@ -0,0 +1,192 @@
+/**
+ * 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;
+
+import java.util.Date;
+
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@PrepareForTest(Arguments.class)
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class RootViewTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ReactContext mReactContext;
+  private CatalystInstance mCatalystInstanceMock;
+
+  @Before
+  public void setUp() {
+    PowerMockito.mockStatic(Arguments.class);
+    PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyArray();
+      }
+    });
+    PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyMap();
+      }
+    });
+
+    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
+    mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mReactContext.initializeWithInstance(mCatalystInstanceMock);
+    DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+
+    UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
+    when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
+        .thenReturn(uiManagerModuleMock);
+  }
+
+  @Test
+  public void testTouchEmitter() {
+    ReactInstanceManager instanceManager = mock(ReactInstanceManager.class);
+    when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext);
+
+    UIManagerModule uiManager = mock(UIManagerModule.class);
+    EventDispatcher eventDispatcher = mock(EventDispatcher.class);
+    RCTEventEmitter eventEmitterModuleMock = mock(RCTEventEmitter.class);
+    when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
+        .thenReturn(uiManager);
+    when(uiManager.getEventDispatcher()).thenReturn(eventDispatcher);
+
+    int rootViewId = 7;
+
+    ReactRootView rootView = new ReactRootView(mReactContext);
+    rootView.setId(rootViewId);
+    rootView.startReactApplication(instanceManager, "");
+    rootView.simulateAttachForTesting();
+
+    long ts = new Date().getTime();
+
+    // Test ACTION_DOWN event
+    rootView.onTouchEvent(
+        MotionEvent.obtain(100, ts, MotionEvent.ACTION_DOWN, 0, 0, 0));
+
+    ArgumentCaptor<Event> downEventCaptor = ArgumentCaptor.forClass(Event.class);
+    verify(eventDispatcher).dispatchEvent(downEventCaptor.capture());
+    verifyNoMoreInteractions(eventDispatcher);
+
+    downEventCaptor.getValue().dispatch(eventEmitterModuleMock);
+
+    ArgumentCaptor<JavaOnlyArray> downActionTouchesArgCaptor =
+        ArgumentCaptor.forClass(JavaOnlyArray.class);
+    verify(eventEmitterModuleMock).receiveTouches(
+        eq("topTouchStart"),
+        downActionTouchesArgCaptor.capture(),
+        any(JavaOnlyArray.class));
+    verifyNoMoreInteractions(eventEmitterModuleMock);
+
+    assertThat(downActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
+    assertThat(downActionTouchesArgCaptor.getValue().getMap(0)).isEqualTo(
+        JavaOnlyMap.of(
+            "pageX",
+            0.,
+            "pageY",
+            0.,
+            "locationX",
+            0.,
+            "locationY",
+            0.,
+            "target",
+            rootViewId,
+            "timeStamp",
+            (double) ts,
+            "identifier",
+            0.));
+
+    // Test ACTION_UP event
+    reset(eventEmitterModuleMock, eventDispatcher);
+
+    ArgumentCaptor<Event> upEventCaptor = ArgumentCaptor.forClass(Event.class);
+    ArgumentCaptor<JavaOnlyArray> upActionTouchesArgCaptor =
+        ArgumentCaptor.forClass(JavaOnlyArray.class);
+
+    rootView.onTouchEvent(
+        MotionEvent.obtain(50, ts, MotionEvent.ACTION_UP, 0, 0, 0));
+    verify(eventDispatcher).dispatchEvent(upEventCaptor.capture());
+    verifyNoMoreInteractions(eventDispatcher);
+
+    upEventCaptor.getValue().dispatch(eventEmitterModuleMock);
+    verify(eventEmitterModuleMock).receiveTouches(
+        eq("topTouchEnd"),
+        upActionTouchesArgCaptor.capture(),
+        any(WritableArray.class));
+    verifyNoMoreInteractions(eventEmitterModuleMock);
+
+    assertThat(upActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
+    assertThat(upActionTouchesArgCaptor.getValue().getMap(0)).isEqualTo(
+        JavaOnlyMap.of(
+            "pageX",
+            0.,
+            "pageY",
+            0.,
+            "locationX",
+            0.,
+            "locationY",
+            0.,
+            "target",
+            rootViewId,
+            "timeStamp",
+            (double) ts,
+            "identifier",
+            0.));
+
+    // Test other action
+    reset(eventDispatcher);
+    rootView.onTouchEvent(
+        MotionEvent.obtain(50, new Date().getTime(), MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0));
+    verifyNoMoreInteractions(eventDispatcher);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK b/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK
new file mode 100644
index 000000000000..83b2c81f8296
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BUCK
@@ -0,0 +1,50 @@
+include_defs('//ReactAndroid/DEFS')
+
+STANDARD_TEST_SRCS = [
+  '*Test.java',
+]
+
+android_library(
+  name = 'testhelpers',
+  srcs = glob(['*.java'], excludes = STANDARD_TEST_SRCS),
+  deps = [
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_tests_target('java/org/mockito/configuration:configuration'),
+],
+  visibility = [
+    'PUBLIC'
+  ],
+)
+
+robolectric3_test(
+  name = 'bridge',
+  # Please change the contact to the oncall of your team
+  contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'],
+  srcs = glob(STANDARD_TEST_SRCS),
+  deps = [
+    ':testhelpers',
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_dep('third-party/java/fest:fest'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/jackson:core'),
+    react_native_dep('third-party/java/jackson:jackson'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+    react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'),
+    react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+  ],
+  visibility = [
+    'PUBLIC'
+  ],
+)
+
+project_config(
+  test_target = ':bridge',
+)
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
new file mode 100644
index 000000000000..dec87ebe3c6f
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
@@ -0,0 +1,86 @@
+/**
+ * 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.bridge;
+
+import java.util.Map;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ReadableNativeArray;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import com.facebook.soloader.SoLoader;
+
+/**
+ * Tests for {@link BaseJavaModule}
+ */
+@PrepareForTest({ReadableNativeArray.class, SoLoader.class})
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+@RunWith(RobolectricTestRunner.class)
+public class BaseJavaModuleTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private Map<String, NativeModule.NativeMethod> mMethods;
+  private ReadableNativeArray mArguments;
+
+  @Before
+  public void setup() {
+    mMethods = new MethodsModule().getMethods();
+    PowerMockito.mockStatic(SoLoader.class);
+    mArguments = PowerMockito.mock(ReadableNativeArray.class);
+  }
+
+  @Test(expected = NativeArgumentsParseException.class)
+  public void testCallMethodWithoutEnoughArgs() throws Exception {
+    BaseJavaModule.NativeMethod regularMethod = mMethods.get("regularMethod");
+    Mockito.stub(mArguments.size()).toReturn(1);
+    regularMethod.invoke(null, null, mArguments);
+  }
+
+  @Test(expected = NativeArgumentsParseException.class)
+  public void testCallAsyncMethodWithoutEnoughArgs() throws Exception {
+    BaseJavaModule.NativeMethod asyncMethod = mMethods.get("asyncMethod");
+    Mockito.stub(mArguments.size()).toReturn(2);
+    asyncMethod.invoke(null, null, mArguments);
+  }
+
+  @Test()
+  public void testCallAsyncMethodWithEnoughArgs() throws Exception {
+    BaseJavaModule.NativeMethod asyncMethod = mMethods.get("asyncMethod");
+    Mockito.stub(mArguments.size()).toReturn(3);
+    asyncMethod.invoke(null, null, mArguments);
+  }
+
+  private static class MethodsModule extends BaseJavaModule {
+    @Override
+    public String getName() {
+      return "Methods";
+    }
+
+    @ReactMethod
+    public void regularMethod(String a, int b) {
+    }
+
+    @ReactMethod
+    public void asyncMethod(int a, Promise p) {
+    }
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaScriptModuleConfigTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaScriptModuleConfigTest.java
new file mode 100644
index 000000000000..6b2b9ea7bb9d
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/JavaScriptModuleConfigTest.java
@@ -0,0 +1,98 @@
+/**
+ * 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.bridge;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+public class JavaScriptModuleConfigTest {
+
+  private static interface SomeModule extends JavaScriptModule {
+    public void stringMethod(String arg);
+    public void intMethod(int arg);
+  }
+
+  private static interface OtherModule extends JavaScriptModule {
+    public void method(String arg1, int arg2);
+  }
+
+  @Test
+  public void testModuleWithMethods() throws Exception {
+    JavaScriptModulesConfig jsModulesConfig = new JavaScriptModulesConfig.Builder()
+        .add(SomeModule.class)
+        .build();
+
+    String json = getModuleDescriptions(jsModulesConfig);
+    JsonNode node = parse(json);
+    assertThat(node).hasSize(1);
+
+    JsonNode module = node.fields().next().getValue();
+    assertThat(module).isNotNull();
+
+    JsonNode methods = module.get("methods");
+    assertThat(methods)
+        .isNotNull()
+        .hasSize(2);
+
+    JsonNode intMethodNode = methods.get("intMethod");
+    assertThat(intMethodNode).isNotNull();
+    assertThat(intMethodNode.get("methodID").asInt()).isEqualTo(0);
+
+    JsonNode stringMethod = methods.get("stringMethod");
+    assertThat(stringMethod).isNotNull();
+    assertThat(stringMethod.get("methodID").asInt()).isEqualTo(1);
+  }
+
+  @Test
+  public void testMultipleModules() throws Exception {
+    JavaScriptModulesConfig jsModulesConfig = new JavaScriptModulesConfig.Builder()
+        .add(OtherModule.class)
+        .add(SomeModule.class)
+        .build();
+
+    String json = getModuleDescriptions(jsModulesConfig);
+    JsonNode node = parse(json);
+    assertThat(node).hasSize(2);
+
+    JsonNode someModuleNode = node.get("SomeModule");
+    assertThat(someModuleNode).isNotNull();
+    int someModuleID = someModuleNode.get("moduleID").asInt();
+
+    JsonNode otherModuleNode = node.get("OtherModule");
+    assertThat(otherModuleNode).isNotNull();
+    int otherModuleID = otherModuleNode.get("moduleID").asInt();
+    assertThat(otherModuleID)
+        .isNotEqualTo(someModuleID);
+  }
+
+  private static String getModuleDescriptions(JavaScriptModulesConfig jsModulesConfig)
+      throws IOException {
+    JsonFactory jsonFactory = new JsonFactory();
+    StringWriter writer = new StringWriter();
+    JsonGenerator jg = jsonFactory.createGenerator(writer);
+    jsModulesConfig.writeModuleDescriptions(jg);
+    jg.close();
+    return writer.getBuffer().toString();
+  }
+
+  private JsonNode parse(String json) throws Exception {
+    ObjectMapper mapper = new ObjectMapper();
+    return mapper.readTree(json);
+  }
+
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/NativeModuleRegistryTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/NativeModuleRegistryTest.java
new file mode 100644
index 000000000000..813120c21763
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/NativeModuleRegistryTest.java
@@ -0,0 +1,296 @@
+/**
+ * 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.bridge;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.facebook.react.common.MapBuilder;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.TextNode;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link NativeModuleRegistry}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class NativeModuleRegistryTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Test
+  public void testModuleWithMethods() throws Exception {
+    NativeModuleRegistry registry = new NativeModuleRegistry.Builder()
+        .add(new MethodsModule())
+        .build();
+
+    String json = getModuleDescriptions(registry);
+    JsonNode node = parse(json);
+
+    JsonNode module = node.fields().next().getValue();
+    assertThat(module).isNotNull();
+
+    JsonNode methods = module.get("methods");
+    assertThat(methods).isNotNull();
+
+    ArrayList<String> expected = new ArrayList<String>();
+    expected.add("doSomething");
+    expected.add("saveData");
+
+    assertMethodsContainExactly(methods, expected);
+  }
+
+  @Test
+  public void testAsyncMethod() throws Exception {
+    NativeModuleRegistry registry = new NativeModuleRegistry.Builder()
+        .add(new MethodsModule())
+        .build();
+
+    String json = getModuleDescriptions(registry);
+    JsonNode node = parse(json);
+
+    JsonNode asyncMethodData = node.get("TestModule").get("methods").get("saveData");
+    assertThat(asyncMethodData.get("type")).isEqualTo(new TextNode("remoteAsync"));
+
+    JsonNode regularMethodData = node.get("TestModule").get("methods").get("doSomething");
+    assertThat(regularMethodData.get("type")).isNotEqualTo(new TextNode("remoteAsync"));
+  }
+
+  @Test
+  public void testModuleWithConstants() throws Exception {
+    ConstantsModule constantsModule = new ConstantsModule();
+    NativeModuleRegistry registry = new NativeModuleRegistry.Builder()
+        .add(constantsModule)
+        .build();
+
+    String json = getModuleDescriptions(registry);
+    JsonNode node = parse(json);
+
+    JsonNode module = node.fields().next().getValue();
+    assertThat(module).isNotNull();
+
+    JsonNode methods = module.get("methods");
+    assertThat(methods).isNotNull();
+
+    ArrayList<String> expected = new ArrayList<String>();
+    expected.add("runDMC");
+
+    assertMethodsContainExactly(methods, expected);
+
+    JsonNode constants = module.get("constants");
+    assertThat(constants.get("testInt").asInt()).isEqualTo(3);
+    assertThat(constants.get("testDouble").asDouble()).isEqualTo(3.14);
+    assertThat(constants.get("testString").asText()).isEqualTo("red panda");
+
+    JsonNode stringMap = constants.get("testStringMap");
+    assertThat(stringMap.get("war_room").asText()).isEqualTo("17.1");
+    assertThat(stringMap.get("android_corex").asText()).isEqualTo("16.1");
+
+    JsonNode intMap = constants.get("testIntMap");
+    assertThat(intMap.get("42").asInt()).isEqualTo(1);
+    assertThat(intMap.get("84").asInt()).isEqualTo(2);
+
+    JsonNode stringList = constants.get("testStringList");
+    assertThat(stringList.get(0).asText()).isEqualTo("vulpes vulpes");
+    assertThat(stringList.get(4).asText()).isEqualTo("vulpes velox");
+
+    JsonNode intList = constants.get("testIntList");
+    assertThat(intList.get(0).asInt()).isEqualTo(3);
+    assertThat(intList.get(4).asInt()).isEqualTo(5);
+  }
+
+  @Test
+  public void testModuleWithOnlyConstants() throws Exception {
+    OnlyConstantsModule onlyConstantsModule = new OnlyConstantsModule();
+    NativeModuleRegistry registry = new NativeModuleRegistry.Builder()
+        .add(onlyConstantsModule)
+        .build();
+
+    String json = getModuleDescriptions(registry);
+    JsonNode node = parse(json);
+
+    JsonNode module = node.fields().next().getValue();
+    assertThat(module).isNotNull();
+
+    JsonNode constants = module.get("constants");
+    assertThat(constants.get("testInt").asInt()).isEqualTo(4);
+  }
+
+  @Test
+  public void testModuleWithNestedMapConstants() throws Exception {
+    NestedMapConstantsModule nestedMapConstantsModule = new NestedMapConstantsModule();
+    NativeModuleRegistry registry = new NativeModuleRegistry.Builder()
+        .add(nestedMapConstantsModule)
+        .build();
+
+    String json = getModuleDescriptions(registry);
+    JsonNode node = parse(json);
+
+    JsonNode module = node.fields().next().getValue();
+    assertThat(module).isNotNull();
+
+    JsonNode constants = module.get("constants");
+    assertThat(constants).isNotNull();
+
+    JsonNode nestedMapConstant = constants.get("nestedMap");
+    assertThat(nestedMapConstant).isNotNull();
+
+    JsonNode firstLevel = nestedMapConstant.get("weNeedToGoDeeper");
+    assertThat(firstLevel).isNotNull();
+
+    JsonNode secondLevel = firstLevel.get("evenDeeper");
+    assertThat(secondLevel).isNotNull();
+
+    assertThat(secondLevel.get("inception").asBoolean()).isTrue();
+  }
+
+  private JsonNode parse(String json) throws Exception {
+    ObjectMapper mapper = new ObjectMapper();
+    return mapper.readTree(json);
+  }
+
+  private void assertMethodsContainExactly(JsonNode methodsObject, List<String> methodNames) {
+    ArrayList<String> actual = new ArrayList();
+    Iterator<Map.Entry<String,JsonNode>> fields = methodsObject.fields();
+    while (fields.hasNext()) {
+      String name = fields.next().getKey();
+      actual.add(name);
+    }
+    assertThat(actual)
+        .hasSize(methodNames.size())
+        .containsAll(methodNames);
+  }
+
+  private static String getModuleDescriptions(NativeModuleRegistry registry)
+      throws IOException {
+    JsonFactory jsonFactory = new JsonFactory();
+    StringWriter writer = new StringWriter();
+    JsonGenerator jg = jsonFactory.createGenerator(writer);
+    registry.writeModuleDescriptions(jg);
+    jg.close();
+    return writer.getBuffer().toString();
+  }
+
+  private static class MethodsModule extends BaseJavaModule {
+
+    @Override
+    public String getName() {
+      return "TestModule";
+    }
+
+    public void notACatalystMethod() {
+    }
+
+    @ReactMethod
+    public void doSomething() {
+    }
+
+    @ReactMethod
+    public void saveData(Promise promise) {
+    }
+  }
+
+  private static class ConstantsModule extends BaseJavaModule {
+
+    @Override
+    public String getName() {
+      return "ConstantsModule";
+    }
+
+    @Override
+    public Map<String, Object> getConstants() {
+      HashMap<String, Object> constants = new HashMap<String, Object>();
+      constants.put("testInt", 3);
+      constants.put("testDouble", 3.14);
+      constants.put("testString", "red panda");
+      constants.put(
+          "testStringMap",
+          MapBuilder.of(
+              "war_room",
+              "17.1",
+              "android_corex",
+              "16.1"));
+      constants.put(
+          "testIntMap",
+          MapBuilder.of(
+              42,
+              1,
+              84,
+              2));
+      constants.put(
+          "testStringList",
+          Arrays.asList(
+              new String[]{
+                  "vulpes vulpes",
+                  "vulpes cana",
+                  "vulpes chama",
+                  "vulpes fulfa",
+                  "vulpes velox"}));
+      constants.put("testIntList", Arrays.asList(3, 1, 4, 1, 5));
+      return constants;
+    }
+
+    @ReactMethod
+    public void runDMC() {
+    }
+  }
+
+  private static class OnlyConstantsModule extends BaseJavaModule {
+
+    @Override
+    public String getName() {
+      return "OnlyConstantsModule";
+    }
+
+    @Override
+    public Map<String, Object> getConstants() {
+      return MapBuilder.<String, Object>of("testInt", 4);
+    }
+  }
+
+  private static class NestedMapConstantsModule extends BaseJavaModule {
+
+    @Override
+    public String getName() {
+      return "NestedMapConstantsModule";
+    }
+
+    @Override
+    public Map<String, Object> getConstants() {
+      return MapBuilder.<String, Object>of(
+          "nestedMap",
+          MapBuilder.of(
+              "weNeedToGoDeeper",
+              MapBuilder.of(
+                  "evenDeeper",
+                  MapBuilder.of("inception", true))));
+    }
+  }
+
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java
new file mode 100644
index 000000000000..1886a481b3dc
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/ReactTestHelper.java
@@ -0,0 +1,64 @@
+/**
+ * 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.bridge;
+
+import com.facebook.react.bridge.queue.ReactQueueConfiguration;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+import com.facebook.react.bridge.queue.MessageQueueThreadSpec;
+import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
+import com.facebook.react.uimanager.UIManagerModule;
+
+import org.robolectric.RuntimeEnvironment;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Utility for creating pre-configured instances of core react components for tests.
+ */
+public class ReactTestHelper {
+
+  /**
+   * @return a ReactApplicationContext that has a CatalystInstance mock returned by
+   * {@link #createMockCatalystInstance}
+   */
+  public static ReactApplicationContext createCatalystContextForTest() {
+    ReactApplicationContext context =
+        new ReactApplicationContext(RuntimeEnvironment.application);
+    context.initializeWithInstance(createMockCatalystInstance());
+    return context;
+  }
+
+  /**
+   * @return a CatalystInstance mock that has a default working ReactQueueConfiguration.
+   */
+  public static CatalystInstance createMockCatalystInstance() {
+    ReactQueueConfigurationSpec spec = ReactQueueConfigurationSpec.builder()
+        .setJSQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec())
+        .setNativeModulesQueueThreadSpec(MessageQueueThreadSpec.mainThreadSpec())
+        .build();
+    ReactQueueConfiguration ReactQueueConfiguration = ReactQueueConfigurationImpl.create(
+        spec,
+        new QueueThreadExceptionHandler() {
+          @Override
+          public void handleException(Exception e) {
+            throw new RuntimeException(e);
+          }
+        });
+
+    CatalystInstance reactInstance = mock(CatalystInstance.class);
+    when(reactInstance.getReactQueueConfiguration()).thenReturn(ReactQueueConfiguration);
+    when(reactInstance.getNativeModule(UIManagerModule.class))
+        .thenReturn(mock(UIManagerModule.class));
+
+    return reactInstance;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK
new file mode 100644
index 000000000000..5e9df2b0b99d
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK
@@ -0,0 +1,41 @@
+include_defs('//ReactAndroid/DEFS')
+
+robolectric3_test(
+  # Please change the contact to the oncall of your team
+  contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'],
+  name = 'modules',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/animation:animation'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
+    react_native_target('java/com/facebook/react/modules/common:common'),
+    react_native_target('java/com/facebook/react/modules/core:core'),
+    react_native_target('java/com/facebook/react/modules/debug:debug'),
+    react_native_target('java/com/facebook/react/modules/dialog:dialog'),
+    react_native_target('java/com/facebook/react/modules/network:network'),
+    react_native_target('java/com/facebook/react/modules/storage:storage'),
+    react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/touch:touch'),
+
+    react_native_tests_target('java/com/facebook/react/bridge:testhelpers'),
+    react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'),
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_dep('third-party/java/fest:fest'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+  ],
+  visibility = [
+    'PUBLIC'
+  ],
+)
+
+project_config(
+  test_target = ':modules',
+)
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java
new file mode 100644
index 000000000000..0486ae79f82f
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.java
@@ -0,0 +1,61 @@
+/**
+ * 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.modules.clipboard;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.text.ClipboardManager;
+
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.modules.clipboard.ClipboardModule;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+@SuppressLint({"ClipboardManager", "DeprecatedClass"})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ClipboardModuleTest {
+
+  private static final String TEST_CONTENT = "test";
+
+  private ClipboardModule mClipboardModule;
+  private ClipboardManager mClipboardManager;
+
+  @Before
+  public void setUp() {
+    mClipboardModule = new ClipboardModule(ReactTestHelper.createCatalystContextForTest());
+    mClipboardManager =
+        (ClipboardManager) RuntimeEnvironment.application.getSystemService(Context.CLIPBOARD_SERVICE);
+  }
+
+  @Test
+  public void testSetString() {
+    mClipboardModule.setString(TEST_CONTENT);
+    assertTrue(mClipboardManager.getText().equals(TEST_CONTENT));
+
+    mClipboardModule.setString(null);
+    assertFalse(mClipboardManager.hasText());
+
+    mClipboardModule.setString("");
+    assertFalse(mClipboardManager.hasText());
+
+    mClipboardModule.setString(" ");
+    assertTrue(mClipboardManager.hasText());
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java
new file mode 100644
index 000000000000..3925adb26f09
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/dialog/DialogModuleTest.java
@@ -0,0 +1,170 @@
+/**
+ * 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.modules.dialog;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.app.Activity;
+
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.JavaOnlyMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.util.ActivityController;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class DialogModuleTest {
+
+  private ActivityController<Activity> mActivityController;
+  private Activity mActivity;
+  private DialogModule mDialogModule;
+
+  final static class SimpleCallback implements Callback {
+    private Object[] mArgs;
+    private int mCalls;
+
+    @Override
+    public void invoke(Object... args) {
+      mCalls++;
+      mArgs = args;
+    }
+
+    public int getCalls() {
+      return mCalls;
+    }
+
+    public Object[] getArgs() {
+      return mArgs;
+    }
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    mActivityController = Robolectric.buildActivity(Activity.class);
+    mActivity = mActivityController
+        .create()
+        .start()
+        .resume()
+        .get();
+
+    final ReactApplicationContext context = PowerMockito.mock(ReactApplicationContext.class);
+    PowerMockito.when(context.hasActiveCatalystInstance()).thenReturn(true);
+    PowerMockito.when(context, "getCurrentActivity").thenReturn(mActivity);
+
+    mDialogModule = new DialogModule(context);
+    mDialogModule.onHostResume();
+  }
+
+  @After
+  public void tearDown() {
+    mActivityController.pause().stop().destroy();
+
+    mActivityController = null;
+    mDialogModule = null;
+  }
+
+  @Test
+  public void testAllOptions() {
+    final JavaOnlyMap options = new JavaOnlyMap();
+    options.putString("title", "Title");
+    options.putString("message", "Message");
+    options.putString("buttonPositive", "OK");
+    options.putString("buttonNegative", "Cancel");
+    options.putString("buttonNeutral", "Later");
+
+    mDialogModule.showAlert(options, null, null);
+
+    final AlertFragment fragment = getFragment();
+    assertNotNull("Fragment was not displayed", fragment);
+
+    final AlertDialog dialog = (AlertDialog) fragment.getDialog();
+    assertEquals("OK", dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText().toString());
+    assertEquals("Cancel", dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText().toString());
+    assertEquals("Later", dialog.getButton(DialogInterface.BUTTON_NEUTRAL).getText().toString());
+  }
+
+  @Test
+  public void testCallbackPositive() {
+    final JavaOnlyMap options = new JavaOnlyMap();
+    options.putString("buttonPositive", "OK");
+
+    final SimpleCallback actionCallback = new SimpleCallback();
+    mDialogModule.showAlert(options, null, actionCallback);
+
+    final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
+    dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+
+    assertEquals(1, actionCallback.getCalls());
+    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
+    assertEquals(DialogInterface.BUTTON_POSITIVE, actionCallback.getArgs()[1]);
+  }
+
+  @Test
+  public void testCallbackNegative() {
+    final JavaOnlyMap options = new JavaOnlyMap();
+    options.putString("buttonNegative", "Cancel");
+
+    final SimpleCallback actionCallback = new SimpleCallback();
+    mDialogModule.showAlert(options, null, actionCallback);
+
+    final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
+    dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick();
+
+    assertEquals(1, actionCallback.getCalls());
+    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
+    assertEquals(DialogInterface.BUTTON_NEGATIVE, actionCallback.getArgs()[1]);
+  }
+
+  @Test
+  public void testCallbackNeutral() {
+    final JavaOnlyMap options = new JavaOnlyMap();
+    options.putString("buttonNeutral", "Later");
+
+    final SimpleCallback actionCallback = new SimpleCallback();
+    mDialogModule.showAlert(options, null, actionCallback);
+
+    final AlertDialog dialog = (AlertDialog) getFragment().getDialog();
+    dialog.getButton(DialogInterface.BUTTON_NEUTRAL).performClick();
+
+    assertEquals(1, actionCallback.getCalls());
+    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.getArgs()[0]);
+    assertEquals(DialogInterface.BUTTON_NEUTRAL, actionCallback.getArgs()[1]);
+  }
+
+  @Test
+  public void testCallbackDismiss() {
+    final JavaOnlyMap options = new JavaOnlyMap();
+
+    final SimpleCallback actionCallback = new SimpleCallback();
+    mDialogModule.showAlert(options, null, actionCallback);
+
+    getFragment().getDialog().dismiss();
+
+    assertEquals(1, actionCallback.getCalls());
+    assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.getArgs()[0]);
+  }
+
+  private AlertFragment getFragment() {
+    return (AlertFragment) mActivity.getFragmentManager()
+        .findFragmentByTag(DialogModule.FRAGMENT_TAG);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
new file mode 100644
index 000000000000..ce38961233cb
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
@@ -0,0 +1,483 @@
+/**
+ * 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.modules.network;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.bridge.WritableArray;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
+
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Headers;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.MultipartBuilder;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import okio.Buffer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link NetworkingModule}.
+ */
+@PrepareForTest({
+    Arguments.class,
+    Call.class,
+    RequestBodyUtil.class,
+    MultipartBuilder.class,
+    NetworkingModule.class,
+    OkHttpClient.class})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class NetworkingModuleTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Test
+  public void testGetWithoutHeaders() throws Exception {
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        Call callMock = mock(Call.class);
+        return callMock;
+      }
+    });
+
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "GET",
+      "http://somedomain/foo",
+      0,
+      JavaOnlyArray.of(),
+      null,
+      true,
+      0);
+
+    ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
+    verify(httpClient).newCall(argumentCaptor.capture());
+    assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://somedomain/foo");
+    // We set the User-Agent header by default
+    assertThat(argumentCaptor.getValue().headers().size()).isEqualTo(1);
+    assertThat(argumentCaptor.getValue().method()).isEqualTo("GET");
+  }
+
+  @Test
+  public void testFailGetWithInvalidHeadersStruct() throws Exception {
+    RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class);
+    ReactApplicationContext context = mock(ReactApplicationContext.class);
+    when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter);
+
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient);
+
+    List<JavaOnlyArray> invalidHeaders = Arrays.asList(JavaOnlyArray.of("foo"));
+
+    mockEvents();
+
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "GET",
+      "http://somedoman/foo",
+      0,
+      JavaOnlyArray.from(invalidHeaders),
+      null,
+      true,
+      0);
+
+    verifyErrorEmit(emitter, 0);
+  }
+
+  @Test
+  public void testFailPostWithoutContentType() throws Exception {
+    RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class);
+    ReactApplicationContext context = mock(ReactApplicationContext.class);
+    when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter);
+
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient);
+
+    JavaOnlyMap body = new JavaOnlyMap();
+    body.putString("string", "This is request body");
+
+    mockEvents();
+
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "POST",
+      "http://somedomain/bar",
+      0,
+      JavaOnlyArray.of(),
+      body,
+      true,
+      0);
+
+    verifyErrorEmit(emitter, 0);
+  }
+
+  private static void verifyErrorEmit(RCTDeviceEventEmitter emitter, int requestId) {
+    ArgumentCaptor<WritableArray> captor = ArgumentCaptor.forClass(WritableArray.class);
+    verify(emitter).emit(eq("didCompleteNetworkResponse"), captor.capture());
+
+    WritableArray array = captor.getValue();
+    assertThat(array.getInt(0)).isEqualTo(requestId);
+    assertThat(array.getString(1)).isNotNull();
+  }
+
+  private static void mockEvents() {
+    PowerMockito.mockStatic(Arguments.class);
+    Mockito.when(Arguments.createArray()).thenAnswer(
+        new Answer<WritableArray>() {
+          @Override
+          public WritableArray answer(InvocationOnMock invocation) throws Throwable {
+            return new JavaOnlyArray();
+          }
+        });
+
+    Mockito.when(Arguments.createMap()).thenAnswer(
+        new Answer<WritableMap>() {
+          @Override
+          public WritableMap answer(InvocationOnMock invocation) throws Throwable {
+            return new JavaOnlyMap();
+          }
+        });
+  }
+
+  @Test
+  public void testSuccessfulPostRequest() throws Exception {
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            Call callMock = mock(Call.class);
+            return callMock;
+          }
+        });
+
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+
+    JavaOnlyMap body = new JavaOnlyMap();
+    body.putString("string", "This is request body");
+
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "POST",
+      "http://somedomain/bar",
+      0,
+      JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "text/plain")),
+      body,
+      true,
+      0);
+
+    ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
+    verify(httpClient).newCall(argumentCaptor.capture());
+    assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://somedomain/bar");
+    assertThat(argumentCaptor.getValue().headers().size()).isEqualTo(2);
+    assertThat(argumentCaptor.getValue().method()).isEqualTo("POST");
+    assertThat(argumentCaptor.getValue().body().contentType().type()).isEqualTo("text");
+    assertThat(argumentCaptor.getValue().body().contentType().subtype()).isEqualTo("plain");
+    Buffer contentBuffer = new Buffer();
+    argumentCaptor.getValue().body().writeTo(contentBuffer);
+    assertThat(contentBuffer.readUtf8()).isEqualTo("This is request body");
+  }
+
+  @Test
+  public void testHeaders() throws Exception {
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        Call callMock = mock(Call.class);
+        return callMock;
+      }
+    });
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+
+    List<JavaOnlyArray> headers = Arrays.asList(
+        JavaOnlyArray.of("Accept", "text/plain"),
+        JavaOnlyArray.of("User-Agent", "React test agent/1.0"));
+
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "GET",
+      "http://someurl/baz",
+      0,
+      JavaOnlyArray.from(headers),
+      null,
+      true,
+      0);
+    ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
+    verify(httpClient).newCall(argumentCaptor.capture());
+    Headers requestHeaders = argumentCaptor.getValue().headers();
+    assertThat(requestHeaders.size()).isEqualTo(2);
+    assertThat(requestHeaders.get("Accept")).isEqualTo("text/plain");
+    assertThat(requestHeaders.get("User-Agent")).isEqualTo("React test agent/1.0");
+  }
+
+  @Test
+  public void testMultipartPostRequestSimple() throws Exception {
+    PowerMockito.mockStatic(RequestBodyUtil.class);
+    when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class)))
+        .thenReturn(mock(InputStream.class));
+    when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class)))
+        .thenReturn(mock(RequestBody.class));
+
+    JavaOnlyMap body = new JavaOnlyMap();
+    JavaOnlyArray formData = new JavaOnlyArray();
+    JavaOnlyMap bodyPart = new JavaOnlyMap();
+    bodyPart.putString("string", "value");
+    bodyPart.putArray(
+        "headers",
+        JavaOnlyArray.from(
+            Arrays.asList(
+                JavaOnlyArray.of("content-disposition", "name"))));
+    formData.pushMap(bodyPart);
+    body.putArray("formData", formData);
+
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            Call callMock = mock(Call.class);
+            return callMock;
+          }
+        });
+
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "POST",
+      "http://someurl/uploadFoo",
+      0,
+      new JavaOnlyArray(),
+      body,
+      true,
+      0);
+
+    // verify url, method, headers
+    ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
+    verify(httpClient).newCall(argumentCaptor.capture());
+    assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://someurl/uploadFoo");
+    assertThat(argumentCaptor.getValue().method()).isEqualTo("POST");
+    assertThat(argumentCaptor.getValue().body().contentType().type()).
+        isEqualTo(MultipartBuilder.FORM.type());
+    assertThat(argumentCaptor.getValue().body().contentType().subtype()).
+        isEqualTo(MultipartBuilder.FORM.subtype());
+    Headers requestHeaders = argumentCaptor.getValue().headers();
+    assertThat(requestHeaders.size()).isEqualTo(1);
+  }
+
+  @Test
+  public void testMultipartPostRequestHeaders() throws Exception {
+    PowerMockito.mockStatic(RequestBodyUtil.class);
+    when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class)))
+        .thenReturn(mock(InputStream.class));
+    when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class)))
+        .thenReturn(mock(RequestBody.class));
+
+    List<JavaOnlyArray> headers = Arrays.asList(
+            JavaOnlyArray.of("Accept", "text/plain"),
+            JavaOnlyArray.of("User-Agent", "React test agent/1.0"),
+            JavaOnlyArray.of("content-type", "multipart/form-data"));
+
+    JavaOnlyMap body = new JavaOnlyMap();
+    JavaOnlyArray formData = new JavaOnlyArray();
+    JavaOnlyMap bodyPart = new JavaOnlyMap();
+    bodyPart.putString("string", "value");
+    bodyPart.putArray(
+        "headers",
+        JavaOnlyArray.from(
+            Arrays.asList(
+                JavaOnlyArray.of("content-disposition", "name"))));
+    formData.pushMap(bodyPart);
+    body.putArray("formData", formData);
+
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            Call callMock = mock(Call.class);
+            return callMock;
+          }
+        });
+
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "POST",
+      "http://someurl/uploadFoo",
+      0,
+      JavaOnlyArray.from(headers),
+      body,
+      true,
+      0);
+
+    // verify url, method, headers
+    ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
+    verify(httpClient).newCall(argumentCaptor.capture());
+    assertThat(argumentCaptor.getValue().urlString()).isEqualTo("http://someurl/uploadFoo");
+    assertThat(argumentCaptor.getValue().method()).isEqualTo("POST");
+    assertThat(argumentCaptor.getValue().body().contentType().type()).
+        isEqualTo(MultipartBuilder.FORM.type());
+    assertThat(argumentCaptor.getValue().body().contentType().subtype()).
+        isEqualTo(MultipartBuilder.FORM.subtype());
+    Headers requestHeaders = argumentCaptor.getValue().headers();
+    assertThat(requestHeaders.size()).isEqualTo(3);
+    assertThat(requestHeaders.get("Accept")).isEqualTo("text/plain");
+    assertThat(requestHeaders.get("User-Agent")).isEqualTo("React test agent/1.0");
+    assertThat(requestHeaders.get("content-type")).isEqualTo("multipart/form-data");
+  }
+
+  @Test
+  public void testMultipartPostRequestBody() throws Exception {
+    InputStream inputStream = mock(InputStream.class);
+    PowerMockito.mockStatic(RequestBodyUtil.class);
+    when(RequestBodyUtil.getFileInputStream(any(ReactContext.class), any(String.class)))
+        .thenReturn(inputStream);
+    when(RequestBodyUtil.create(any(MediaType.class), any(InputStream.class))).thenCallRealMethod();
+    when(inputStream.available()).thenReturn("imageUri".length());
+
+    final MultipartBuilder multipartBuilder = mock(MultipartBuilder.class);
+    PowerMockito.whenNew(MultipartBuilder.class).withNoArguments().thenReturn(multipartBuilder);
+    when(multipartBuilder.type(any(MediaType.class))).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            return multipartBuilder;
+          }
+        });
+    when(multipartBuilder.addPart(any(Headers.class), any(RequestBody.class))).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            return multipartBuilder;
+          }
+        });
+    when(multipartBuilder.build()).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            return mock(RequestBody.class);
+          }
+        });
+
+    List<JavaOnlyArray> headers = Arrays.asList(
+            JavaOnlyArray.of("content-type", "multipart/form-data"));
+
+    JavaOnlyMap body = new JavaOnlyMap();
+    JavaOnlyArray formData = new JavaOnlyArray();
+    body.putArray("formData", formData);
+
+    JavaOnlyMap bodyPart = new JavaOnlyMap();
+    bodyPart.putString("string", "locale");
+    bodyPart.putArray(
+        "headers",
+        JavaOnlyArray.from(
+            Arrays.asList(
+                          JavaOnlyArray.of("content-disposition", "user"))));
+    formData.pushMap(bodyPart);
+
+    JavaOnlyMap imageBodyPart = new JavaOnlyMap();
+    imageBodyPart.putString("uri", "imageUri");
+    imageBodyPart.putArray(
+        "headers",
+        JavaOnlyArray.from(
+            Arrays.asList(
+                JavaOnlyArray.of("content-type", "image/jpg"),
+                JavaOnlyArray.of("content-disposition", "filename=photo.jpg"))));
+    formData.pushMap(imageBodyPart);
+
+    OkHttpClient httpClient = mock(OkHttpClient.class);
+    when(httpClient.newCall(any(Request.class))).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            Call callMock = mock(Call.class);
+            return callMock;
+          }
+        });
+
+    NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
+    networkingModule.sendRequest(
+      mock(ExecutorToken.class),
+      "POST",
+      "http://someurl/uploadFoo",
+      0,
+      JavaOnlyArray.from(headers),
+      body,
+      true,
+      0);
+
+    // verify RequestBodyPart for image
+    PowerMockito.verifyStatic(times(1));
+    RequestBodyUtil.getFileInputStream(any(ReactContext.class), eq("imageUri"));
+    PowerMockito.verifyStatic(times(1));
+    RequestBodyUtil.create(MediaType.parse("image/jpg"), inputStream);
+
+    // verify body
+    verify(multipartBuilder).build();
+    verify(multipartBuilder).type(MultipartBuilder.FORM);
+    ArgumentCaptor<Headers> headersArgumentCaptor = ArgumentCaptor.forClass(Headers.class);
+    ArgumentCaptor<RequestBody> bodyArgumentCaptor = ArgumentCaptor.forClass(RequestBody.class);
+    verify(multipartBuilder, times(2)).
+        addPart(headersArgumentCaptor.capture(), bodyArgumentCaptor.capture());
+
+    List<Headers> bodyHeaders = headersArgumentCaptor.getAllValues();
+    assertThat(bodyHeaders.size()).isEqualTo(2);
+    List<RequestBody> bodyRequestBody = bodyArgumentCaptor.getAllValues();
+    assertThat(bodyRequestBody.size()).isEqualTo(2);
+
+    assertThat(bodyHeaders.get(0).get("content-disposition")).isEqualTo("user");
+    assertThat(bodyRequestBody.get(0).contentType()).isNull();
+    assertThat(bodyRequestBody.get(0).contentLength()).isEqualTo("locale".getBytes().length);
+    assertThat(bodyHeaders.get(1).get("content-disposition")).isEqualTo("filename=photo.jpg");
+    assertThat(bodyRequestBody.get(1).contentType()).isEqualTo(MediaType.parse("image/jpg"));
+    assertThat(bodyRequestBody.get(1).contentLength()).isEqualTo("imageUri".getBytes().length);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java
new file mode 100644
index 000000000000..291e1604819d
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/storage/AsyncStorageModuleTest.java
@@ -0,0 +1,344 @@
+/**
+ * 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.modules.storage;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.modules.storage.AsyncStorageModule;
+import com.facebook.react.modules.storage.ReactDatabaseSupplier;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Mockito.mock;
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link AsyncStorageModule}.
+ */
+@PrepareForTest({Arguments.class})
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*"})
+@RunWith(RobolectricTestRunner.class)
+public class AsyncStorageModuleTest {
+
+  private AsyncStorageModule mStorage;
+  private JavaOnlyArray mEmptyArray;
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Before
+  public void prepareModules() {
+    PowerMockito.mockStatic(Arguments.class);
+    Mockito.when(Arguments.createArray()).thenAnswer(
+        new Answer<Object>() {
+     @Override
+     public Object answer(InvocationOnMock invocation) throws Throwable {
+       return new JavaOnlyArray();
+     }
+   });
+
+    Mockito.when(Arguments.createMap()).thenAnswer(
+        new Answer<Object>() {
+       @Override
+       public Object answer(InvocationOnMock invocation) throws Throwable {
+         return new JavaOnlyMap();
+       }
+     });
+
+    // don't use Robolectric before initializing mocks
+    mStorage = new AsyncStorageModule(ReactTestHelper.createCatalystContextForTest());
+    mEmptyArray = new JavaOnlyArray();
+  }
+
+  @After
+  public void cleanUp() {
+    RuntimeEnvironment.application.deleteDatabase(ReactDatabaseSupplier.DATABASE_NAME);
+    ReactDatabaseSupplier.deleteInstance();
+  }
+
+  @Test
+  public void testMultiSetMultiGet() {
+    final String key1 = "foo1";
+    final String key2 = "foo2";
+    final String fakeKey = "fakeKey";
+    final String value1 = "bar1";
+    final String value2 = "bar2";
+    JavaOnlyArray keyValues = new JavaOnlyArray();
+    keyValues.pushArray(getArray(key1, value1));
+    keyValues.pushArray(getArray(key2, value2));
+
+    Callback setCallback = mock(Callback.class);
+    mStorage.multiSet(keyValues, setCallback);
+    Mockito.verify(setCallback, Mockito.times(1)).invoke();
+
+    JavaOnlyArray keys = new JavaOnlyArray();
+    keys.pushString(key1);
+    keys.pushString(key2);
+
+    Callback getCallback = mock(Callback.class);
+    mStorage.multiGet(keys, getCallback);
+    Mockito.verify(getCallback, Mockito.times(1)).invoke(null, keyValues);
+
+    keys.pushString(fakeKey);
+    JavaOnlyArray row3 = new JavaOnlyArray();
+    row3.pushString(fakeKey);
+    row3.pushString(null);
+    keyValues.pushArray(row3);
+
+    Callback getCallback2 = mock(Callback.class);
+    mStorage.multiGet(keys, getCallback2);
+    Mockito.verify(getCallback2, Mockito.times(1)).invoke(null, keyValues);
+  }
+
+  @Test
+  public void testMultiRemove() {
+    final String key1 = "foo1";
+    final String key2 = "foo2";
+    final String value1 = "bar1";
+    final String value2 = "bar2";
+
+    JavaOnlyArray keyValues = new JavaOnlyArray();
+    keyValues.pushArray(getArray(key1, value1));
+    keyValues.pushArray(getArray(key2, value2));
+    mStorage.multiSet(keyValues, mock(Callback.class));
+
+    JavaOnlyArray keys = new JavaOnlyArray();
+    keys.pushString(key1);
+    keys.pushString(key2);
+
+    Callback getCallback = mock(Callback.class);
+    mStorage.multiRemove(keys, getCallback);
+    Mockito.verify(getCallback, Mockito.times(1)).invoke();
+
+    Callback getAllCallback = mock(Callback.class);
+    mStorage.getAllKeys(getAllCallback);
+    Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, mEmptyArray);
+
+    mStorage.multiSet(keyValues, mock(Callback.class));
+
+    keys.pushString("fakeKey");
+    Callback getCallback2 = mock(Callback.class);
+    mStorage.multiRemove(keys, getCallback2);
+    Mockito.verify(getCallback2, Mockito.times(1)).invoke();
+
+    Callback getAllCallback2 = mock(Callback.class);
+    mStorage.getAllKeys(getAllCallback2);
+    Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray);
+  }
+
+  @Test
+  public void testMultiMerge() throws Exception {
+    final String mergeKey = "mergeTest";
+
+    JSONObject value = new JSONObject();
+    value.put("foo1", "bar1");
+    value.put("foo2", createJSONArray("val1", "val2", 3));
+    value.put("foo3", 1001);
+    value.put("foo4", createJSONObject("key1", "randomValueThatWillNeverBeUsed"));
+
+    mStorage.multiSet(JavaOnlyArray.of(getArray(mergeKey, value.toString())), mock(Callback.class));
+    {
+      Callback callback = mock(Callback.class);
+      mStorage.multiGet(getArray(mergeKey), callback);
+      Mockito.verify(callback, Mockito.times(1))
+          .invoke(null, JavaOnlyArray.of(getArray(mergeKey, value.toString())));
+    }
+
+    value.put("foo1", 1001);
+    value.put("foo2", createJSONObject("key1", "val1"));
+    value.put("foo3", "bar1");
+    value.put("foo4", createJSONArray("val1", "val2", 3));
+
+    JSONObject newValue = new JSONObject();
+    newValue.put("foo2", createJSONObject("key2", "val2"));
+
+    JSONObject newValue2 = new JSONObject();
+    newValue2.put("foo2", createJSONObject("key1", "val3"));
+
+    mStorage.multiMerge(
+        JavaOnlyArray.of(
+            JavaOnlyArray.of(mergeKey, value.toString()),
+            JavaOnlyArray.of(mergeKey, newValue.toString()),
+            JavaOnlyArray.of(mergeKey, newValue2.toString())),
+        mock(Callback.class));
+
+    value.put("foo2", createJSONObject("key1", "val3", "key2", "val2"));
+    Callback callback = mock(Callback.class);
+    mStorage.multiGet(getArray(mergeKey), callback);
+    Mockito.verify(callback, Mockito.times(1))
+        .invoke(null, JavaOnlyArray.of(getArray(mergeKey, value.toString())));
+  }
+
+  @Test
+  public void testGetAllKeys() {
+    final String[] keys = {"foo", "foo2"};
+    final String[] values = {"bar", "bar2"};
+    JavaOnlyArray keyValues = new JavaOnlyArray();
+    keyValues.pushArray(getArray(keys[0], values[0]));
+    keyValues.pushArray(getArray(keys[1], values[1]));
+    mStorage.multiSet(keyValues, mock(Callback.class));
+
+    JavaOnlyArray storedKeys = new JavaOnlyArray();
+    storedKeys.pushString(keys[0]);
+    storedKeys.pushString(keys[1]);
+
+    Callback getAllCallback = mock(Callback.class);
+    mStorage.getAllKeys(getAllCallback);
+    Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, storedKeys);
+
+    Callback getAllCallback2 = mock(Callback.class);
+    mStorage.multiRemove(getArray(keys[0]), mock(Callback.class));
+
+    mStorage.getAllKeys(getAllCallback2);
+    Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, getArray(keys[1]));
+
+    mStorage.multiRemove(getArray(keys[1]), mock(Callback.class));
+    Callback getAllCallback3 = mock(Callback.class);
+    mStorage.getAllKeys(getAllCallback3);
+    Mockito.verify(getAllCallback3, Mockito.times(1)).invoke(null, mEmptyArray);
+  }
+
+  @Test
+  public void testClear() {
+    JavaOnlyArray keyValues = new JavaOnlyArray();
+    keyValues.pushArray(getArray("foo", "foo2"));
+    keyValues.pushArray(getArray("bar", "bar2"));
+    mStorage.multiSet(keyValues, mock(Callback.class));
+
+    Callback clearCallback2 = mock(Callback.class);
+    mStorage.clear(clearCallback2);
+    Mockito.verify(clearCallback2, Mockito.times(1)).invoke();
+
+    Callback getAllCallback2 = mock(Callback.class);
+    mStorage.getAllKeys(getAllCallback2);
+    Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray);
+  }
+
+  @Test
+  public void testHugeMultiGetMultiGet() {
+    // Test with many keys, so that it's above the 999 limit per batch imposed by SQLite.
+    final int keyCount = 1001;
+    // don't set keys that divide by this magical number, so that we can check that multiGet works,
+    // and returns null for missing keys
+    final int magicalNumber = 343;
+
+    JavaOnlyArray keyValues = new JavaOnlyArray();
+    for (int i = 0; i < keyCount; i++) {
+      if (i % magicalNumber > 0) {
+        keyValues.pushArray(getArray("key" + i, "value" + i));
+      }
+    }
+    mStorage.multiSet(keyValues, mock(Callback.class));
+    JavaOnlyArray keys = new JavaOnlyArray();
+    for (int i = 0; i < keyCount; i++) {
+      keys.pushString("key" + i);
+    }
+    mStorage.multiGet(
+        keys, new Callback() {
+          @Override
+          public void invoke(Object... args) {
+            assertThat(args.length).isEqualTo(2);
+            JavaOnlyArray resultArray = (JavaOnlyArray) args[1];
+
+            assertThat(resultArray.size()).isEqualTo(keyCount);
+            boolean keyReceived[] = new boolean[keyCount];
+            for (int i = 0; i < keyCount; i++) {
+              String key = resultArray.getArray(i).getString(0).substring(3);
+              int idx = Integer.parseInt(key);
+              assertThat(keyReceived[idx]).isFalse();
+              keyReceived[idx] = true;
+
+              if (idx % magicalNumber > 0) {
+                String value = resultArray.getArray(i).getString(1).substring(5);
+                assertThat(key).isEqualTo(value);
+              } else {
+                assertThat(resultArray.getArray(i).isNull(1));
+              }
+            }
+          }
+        });
+
+    // Test removal in same test, since it's costly to set up the test again.
+    // Remove only odd keys
+    JavaOnlyArray keyRemoves = new JavaOnlyArray();
+    for (int i = 0; i < keyCount; i++) {
+      if (i % 2 > 0) {
+        keyRemoves.pushString("key" + i);
+      }
+    }
+    mStorage.multiRemove(keyRemoves, mock(Callback.class));
+    mStorage.getAllKeys(
+        new Callback() {
+          @Override
+          public void invoke(Object... args) {
+            JavaOnlyArray resultArray = (JavaOnlyArray) args[1];
+            assertThat(resultArray.size()).isEqualTo(499);
+            for (int i = 0; i < resultArray.size(); i++) {
+              String key = resultArray.getString(i).substring(3);
+              int idx = Integer.parseInt(key);
+              assertThat(idx % 2).isEqualTo(0);
+            }
+          }
+        });
+  }
+
+  private static JSONArray createJSONArray(Object... objects) {
+    return new JSONArray(Arrays.asList(objects));
+  }
+
+  private static JSONObject createJSONObject(Object... keysAndValues) {
+    if (keysAndValues.length % 2 != 0) {
+      throw new IllegalArgumentException("You must provide the same number of keys and values");
+    }
+    Map map = new HashMap();
+    for (int i = 0; i < keysAndValues.length; i += 2) {
+      map.put(keysAndValues[i], keysAndValues[i + 1]);
+    }
+    return new JSONObject(map);
+  }
+
+  private JavaOnlyArray getArray(String... values) {
+    JavaOnlyArray array = new JavaOnlyArray();
+    for (String value : values) {
+      array.pushString(value);
+    }
+    return array;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java
new file mode 100644
index 000000000000..1597d84e3641
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java
@@ -0,0 +1,183 @@
+/**
+ * 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.modules.timing;
+
+import android.view.Choreographer;
+
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.uimanager.ReactChoreographer;
+import com.facebook.react.common.SystemClock;
+import com.facebook.react.modules.core.JSTimersExecution;
+import com.facebook.react.modules.core.Timing;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests for {@link Timing}.
+ */
+@PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class})
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+@RunWith(RobolectricTestRunner.class)
+public class TimingModuleTest {
+
+  private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms
+
+  private Timing mTiming;
+  private ReactChoreographer mChoreographerMock;
+  private PostFrameCallbackHandler mPostFrameCallbackHandler;
+  private long mCurrentTimeNs;
+  private JSTimersExecution mJSTimersMock;
+  private ExecutorToken mExecutorTokenMock;
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Before
+  public void prepareModules() {
+    PowerMockito.mockStatic(Arguments.class);
+    when(Arguments.createArray()).thenAnswer(
+        new Answer<Object>() {
+          @Override
+          public Object answer(InvocationOnMock invocation) throws Throwable {
+            return new JavaOnlyArray();
+          }
+        });
+
+    PowerMockito.mockStatic(SystemClock.class);
+    when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
+    when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs);
+
+    mChoreographerMock = mock(ReactChoreographer.class);
+    PowerMockito.mockStatic(ReactChoreographer.class);
+    when(ReactChoreographer.getInstance()).thenReturn(mChoreographerMock);
+
+    CatalystInstance reactInstance = mock(CatalystInstance.class);
+    ReactApplicationContext reactContext = mock(ReactApplicationContext.class);
+    when(reactContext.getCatalystInstance()).thenReturn(reactInstance);
+
+    mCurrentTimeNs = 0;
+    mPostFrameCallbackHandler = new PostFrameCallbackHandler();
+
+    doAnswer(mPostFrameCallbackHandler)
+        .when(mChoreographerMock)
+        .postFrameCallback(
+            eq(ReactChoreographer.CallbackType.TIMERS_EVENTS),
+            any(Choreographer.FrameCallback.class));
+
+    mTiming = new Timing(reactContext);
+    mJSTimersMock = mock(JSTimersExecution.class);
+    mExecutorTokenMock = mock(ExecutorToken.class);
+    when(reactContext.getJSModule(mExecutorTokenMock, JSTimersExecution.class)).thenReturn(mJSTimersMock);
+    mTiming.initialize();
+  }
+
+  private void stepChoreographerFrame() {
+    Choreographer.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback();
+    mCurrentTimeNs += FRAME_TIME_NS;
+    if (callback != null) {
+      callback.doFrame(mCurrentTimeNs);
+    }
+  }
+
+  @Test
+  public void testSimpleTimer() {
+    mTiming.onHostResume();
+    mTiming.createTimer(mExecutorTokenMock, 1, 0, 0, false);
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1));
+    reset(mJSTimersMock);
+    stepChoreographerFrame();
+    verifyNoMoreInteractions(mJSTimersMock);
+  }
+
+  @Test
+  public void testSimpleRecurringTimer() {
+    mTiming.createTimer(mExecutorTokenMock, 100, 0, 0, true);
+    mTiming.onHostResume();
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
+
+    reset(mJSTimersMock);
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
+  }
+
+  @Test
+  public void testCancelRecurringTimer() {
+    mTiming.onHostResume();
+    mTiming.createTimer(mExecutorTokenMock, 105, 0, 0, true);
+
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105));
+
+    reset(mJSTimersMock);
+    mTiming.deleteTimer(mExecutorTokenMock, 105);
+    stepChoreographerFrame();
+    verifyNoMoreInteractions(mJSTimersMock);
+  }
+
+  @Test
+  public void testPausingAndResuming() {
+    mTiming.onHostResume();
+    mTiming.createTimer(mExecutorTokenMock, 41, 0, 0, true);
+
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
+
+    reset(mJSTimersMock);
+    mTiming.onHostPause();
+    stepChoreographerFrame();
+    verifyNoMoreInteractions(mJSTimersMock);
+
+    reset(mJSTimersMock);
+    mTiming.onHostResume();
+    stepChoreographerFrame();
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
+  }
+
+  private static class PostFrameCallbackHandler implements Answer<Void> {
+
+    private Choreographer.FrameCallback mFrameCallback;
+
+    @Override
+    public Void answer(InvocationOnMock invocation) throws Throwable {
+      Object[] args = invocation.getArguments();
+      mFrameCallback = (Choreographer.FrameCallback) args[1];
+      return null;
+    }
+
+    public Choreographer.FrameCallback getAndResetFrameCallback() {
+      Choreographer.FrameCallback callback = mFrameCallback;
+      mFrameCallback = null;
+      return callback;
+    }
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK
new file mode 100644
index 000000000000..9fe1b6977714
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/BUCK
@@ -0,0 +1,37 @@
+include_defs('//ReactAndroid/DEFS')
+
+robolectric3_test(
+  name = 'uimanager',
+  # Please change the contact to the oncall of your team
+  contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'],
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/animation:animation'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/touch:touch'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_target('java/com/facebook/react/views/view:view'),
+
+    react_native_tests_target('java/com/facebook/react/bridge:testhelpers'),
+    react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'),
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_dep('third-party/java/fest:fest'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+  ],
+  visibility = [
+    'PUBLIC'
+  ],
+)
+
+project_config(
+  test_target = ':uimanager',
+)
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java
new file mode 100644
index 000000000000..f1944ec12258
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java
@@ -0,0 +1,451 @@
+/**
+ * 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.uimanager;
+
+import android.util.DisplayMetrics;
+
+import com.facebook.csslayout.CSSAlign;
+import com.facebook.csslayout.CSSConstants;
+import com.facebook.csslayout.CSSFlexDirection;
+import com.facebook.csslayout.CSSJustify;
+import com.facebook.csslayout.CSSPositionType;
+import com.facebook.csslayout.Spacing;
+import com.facebook.react.bridge.JavaOnlyMap;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyFloat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@PrepareForTest({PixelUtil.class})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class LayoutPropertyApplicatorTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Before
+  public void setup() {
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
+    DisplayMetricsHolder.setScreenDisplayMetrics(new DisplayMetrics());
+  }
+
+  @After
+  public void teardown() {
+    DisplayMetricsHolder.setWindowDisplayMetrics(null);
+    DisplayMetricsHolder.setScreenDisplayMetrics(null);
+  }
+
+  public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
+    return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues));
+  }
+
+  @Test
+  public void testDimensions() {
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = spy(
+        buildStyles(
+            "width", 10.0,
+            "height", 10.0,
+            "left", 10.0,
+            "top", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setStyleWidth(anyFloat());
+    verify(map).getFloat(eq("width"), anyFloat());
+    verify(reactShadowNode).setStyleHeight(anyFloat());
+    verify(map).getFloat(eq("height"), anyFloat());
+    verify(reactShadowNode).setPositionLeft(anyFloat());
+    verify(map).getFloat(eq("left"), anyFloat());
+    verify(reactShadowNode).setPositionTop(anyFloat());
+    verify(map).getFloat(eq("top"), anyFloat());
+
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles());
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode, never()).setStyleWidth(anyFloat());
+    verify(map, never()).getFloat(eq("width"), anyFloat());
+    verify(reactShadowNode, never()).setStyleHeight(anyFloat());
+    verify(map, never()).getFloat(eq("height"), anyFloat());
+    verify(reactShadowNode, never()).setPositionLeft(anyFloat());
+    verify(map, never()).getFloat(eq("left"), anyFloat());
+    verify(reactShadowNode, never()).setPositionTop(anyFloat());
+    verify(map, never()).getFloat(eq("top"), anyFloat());
+  }
+
+  @Test
+  public void testFlex() {
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = spy(buildStyles("flex", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setFlex(anyFloat());
+    verify(map).getFloat("flex", 0.f);
+
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles());
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode, never()).setFlex(anyFloat());
+    verify(map, never()).getFloat("flex", 0.f);
+  }
+
+  @Test
+  public void testPosition() {
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = spy(buildStyles(
+            "position", "absolute",
+            "bottom", 10.0,
+            "right", 5.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPositionBottom(anyFloat());
+    verify(reactShadowNode).setPositionRight(anyFloat());
+    verify(reactShadowNode).setPositionType(any(CSSPositionType.class));
+    verify(map).getFloat("bottom", Float.NaN);
+    verify(map).getFloat("right", Float.NaN);
+
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles());
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode, never()).setPositionBottom(anyFloat());
+    verify(reactShadowNode, never()).setPositionRight(anyFloat());
+    verify(reactShadowNode, never()).setPositionType(any(CSSPositionType.class));
+    verify(map, never()).getFloat("bottom", Float.NaN);
+    verify(map, never()).getFloat("right", Float.NaN);
+  }
+
+  @Test
+  public void testMargin() {
+    // margin
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = spy(buildStyles("margin", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.ALL), anyFloat());
+    verify(map).getFloat("margin", CSSConstants.UNDEFINED);
+
+    // marginVertical
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginVertical", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.VERTICAL), anyFloat());
+    verify(map).getFloat("marginVertical", CSSConstants.UNDEFINED);
+
+    // marginHorizontal
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginHorizontal", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.HORIZONTAL), anyFloat());
+    verify(map).getFloat("marginHorizontal", CSSConstants.UNDEFINED);
+
+    // marginTop
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginTop", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.TOP), anyFloat());
+    verify(map).getFloat("marginTop", CSSConstants.UNDEFINED);
+
+    // marginBottom
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginBottom", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.BOTTOM), anyFloat());
+    verify(map).getFloat("marginBottom", CSSConstants.UNDEFINED);
+
+    // marginLeft
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginLeft", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.LEFT), anyFloat());
+    verify(map).getFloat("marginLeft", CSSConstants.UNDEFINED);
+
+    // marginRight
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("marginRight", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setMargin(eq(Spacing.RIGHT), anyFloat());
+    verify(map).getFloat("marginRight", CSSConstants.UNDEFINED);
+
+    // no margin
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles());
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode, never()).setMargin(anyInt(), anyFloat());
+    verify(map, never()).getFloat("margin", CSSConstants.UNDEFINED);
+  }
+
+  @Test
+  public void testPadding() {
+    // padding
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = spy(buildStyles("padding", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.ALL), anyFloat());
+    verify(map).getFloat("padding", CSSConstants.UNDEFINED);
+
+    // paddingVertical
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingVertical", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.VERTICAL), anyFloat());
+    verify(map).getFloat("paddingVertical", CSSConstants.UNDEFINED);
+
+    // paddingHorizontal
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingHorizontal", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.HORIZONTAL), anyFloat());
+    verify(map).getFloat("paddingHorizontal", CSSConstants.UNDEFINED);
+
+    // paddingTop
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingTop", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.TOP), anyFloat());
+    verify(map).getFloat("paddingTop", CSSConstants.UNDEFINED);
+
+    // paddingBottom
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingBottom", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.BOTTOM), anyFloat());
+    verify(map).getFloat("paddingBottom", CSSConstants.UNDEFINED);
+
+    // paddingLeft
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingLeft", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.LEFT), anyFloat());
+    verify(map).getFloat("paddingLeft", CSSConstants.UNDEFINED);
+
+    // paddingRight
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles("paddingRight", 10.0));
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setPadding(eq(Spacing.RIGHT), anyFloat());
+    verify(map).getFloat("paddingRight", CSSConstants.UNDEFINED);
+
+    // no padding
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = spy(buildStyles());
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode, never()).setPadding(anyInt(), anyFloat());
+    verify(map, never()).getFloat("padding", CSSConstants.UNDEFINED);
+  }
+
+  @Test
+  public void testEnumerations() {
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = buildStyles(
+        "flexDirection", "column",
+        "alignSelf", "stretch",
+        "alignItems", "center",
+        "justifyContent", "space_between",
+        "position", "relative");
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setFlexDirection(CSSFlexDirection.COLUMN);
+    verify(reactShadowNode).setAlignSelf(CSSAlign.STRETCH);
+    verify(reactShadowNode).setAlignItems(CSSAlign.CENTER);
+    verify(reactShadowNode).setJustifyContent(CSSJustify.SPACE_BETWEEN);
+    verify(reactShadowNode).setPositionType(CSSPositionType.RELATIVE);
+
+    reactShadowNode = spy(new LayoutShadowNode());
+    map = buildStyles();
+    reactShadowNode.updateProperties(map);
+
+    verify(reactShadowNode, never()).setFlexDirection(any(CSSFlexDirection.class));
+    verify(reactShadowNode, never()).setAlignSelf(any(CSSAlign.class));
+    verify(reactShadowNode, never()).setAlignItems(any(CSSAlign.class));
+    verify(reactShadowNode, never()).setJustifyContent(any(CSSJustify.class));
+    verify(reactShadowNode, never()).setPositionType(any(CSSPositionType.class));
+  }
+
+  @Test
+  public void testPropertiesResetToDefault() {
+    DisplayMetrics displayMetrics = new DisplayMetrics();
+    displayMetrics.density = 1.0f;
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+
+    LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
+    ReactStylesDiffMap map = buildStyles(
+        "width", 10.0,
+        "height", 10.0,
+        "left", 10.0,
+        "top", 10.0,
+        "flex", 1.0,
+        "padding", 10.0,
+        "marginLeft", 10.0,
+        "borderTopWidth", 10.0,
+        "flexDirection", "row",
+        "alignSelf", "stretch",
+        "alignItems", "center",
+        "justifyContent", "space_between",
+        "position", "absolute");
+
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setStyleWidth(10.f);
+    verify(reactShadowNode).setStyleHeight(10.f);
+    verify(reactShadowNode).setPositionLeft(10.f);
+    verify(reactShadowNode).setPositionTop(10.f);
+    verify(reactShadowNode).setFlex(1.0f);
+    verify(reactShadowNode).setPadding(Spacing.ALL, 10.f);
+    verify(reactShadowNode).setMargin(Spacing.LEFT, 10.f);
+    verify(reactShadowNode).setBorder(Spacing.TOP, 10.f);
+    verify(reactShadowNode).setFlexDirection(CSSFlexDirection.ROW);
+    verify(reactShadowNode).setAlignSelf(CSSAlign.STRETCH);
+    verify(reactShadowNode).setAlignItems(CSSAlign.CENTER);
+    verify(reactShadowNode).setJustifyContent(CSSJustify.SPACE_BETWEEN);
+    verify(reactShadowNode).setPositionType(CSSPositionType.ABSOLUTE);
+
+    map = buildStyles(
+        "width", null,
+        "height", null,
+        "left", null,
+        "top", null,
+        "flex", null,
+        "padding", null,
+        "marginLeft", null,
+        "borderTopWidth", null,
+        "flexDirection", null,
+        "alignSelf", null,
+        "alignItems", null,
+        "justifyContent", null,
+        "position", null);
+
+    reset(reactShadowNode);
+    reactShadowNode.updateProperties(map);
+    verify(reactShadowNode).setStyleWidth(CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setStyleHeight(CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setPositionLeft(CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setPositionTop(CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setFlex(0.f);
+    verify(reactShadowNode).setPadding(Spacing.ALL, CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setMargin(Spacing.LEFT, CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setBorder(Spacing.TOP, CSSConstants.UNDEFINED);
+    verify(reactShadowNode).setFlexDirection(CSSFlexDirection.COLUMN);
+    verify(reactShadowNode).setAlignSelf(CSSAlign.AUTO);
+    verify(reactShadowNode).setAlignItems(CSSAlign.STRETCH);
+    verify(reactShadowNode).setJustifyContent(CSSJustify.FLEX_START);
+    verify(reactShadowNode).setPositionType(CSSPositionType.RELATIVE);
+  }
+
+  @Test
+  public void testSettingDefaultStyleValues() {
+    mockStatic(PixelUtil.class);
+    when(PixelUtil.toPixelFromDIP(anyFloat())).thenAnswer(
+        new Answer() {
+          @Override
+          public Float answer(InvocationOnMock invocation) throws Throwable {
+            Object[] args = invocation.getArguments();
+            return (Float) args[0];
+          }
+        });
+
+    LayoutShadowNode[] nodes = new LayoutShadowNode[7];
+    for (int idx = 0; idx < nodes.length; idx++) {
+      nodes[idx] = new LayoutShadowNode();
+      nodes[idx].setDefaultPadding(Spacing.LEFT, 15);
+      nodes[idx].setDefaultPadding(Spacing.TOP, 25);
+      nodes[idx].setDefaultPadding(Spacing.RIGHT, 35);
+      nodes[idx].setDefaultPadding(Spacing.BOTTOM, 45);
+    }
+
+    ReactStylesDiffMap[] mapNodes = new ReactStylesDiffMap[7];
+    mapNodes[0] = buildStyles("paddingLeft", 10.0, "paddingHorizontal", 5.0);
+    mapNodes[1] = buildStyles("padding", 10.0, "paddingTop", 5.0);
+    mapNodes[2] = buildStyles("paddingLeft", 10.0, "paddingVertical", 5.0);
+    mapNodes[3] = buildStyles("paddingBottom", 10.0, "paddingHorizontal", 5.0);
+    mapNodes[4] = buildStyles("padding", null, "paddingTop", 5.0);
+    mapNodes[5] = buildStyles(
+        "paddingRight", 10.0,
+        "paddingHorizontal", null,
+        "paddingVertical", 7.0);
+    mapNodes[6] = buildStyles("margin", 5.0);
+
+    for (int idx = 0; idx < nodes.length; idx++) {
+      nodes[idx].updateProperties(mapNodes[idx]);
+    }
+
+    assertEquals(10.0, nodes[0].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(25.0, nodes[0].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(5.0, nodes[0].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(45.0, nodes[0].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(10.0, nodes[1].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(5.0, nodes[1].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(10.0, nodes[1].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(10.0, nodes[1].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(10.0, nodes[2].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(5.0, nodes[2].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(35.0, nodes[2].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(5.0, nodes[2].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(5.0, nodes[3].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(25.0, nodes[3].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(5.0, nodes[3].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(10.0, nodes[3].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(15.0, nodes[4].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(5.0, nodes[4].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(35.0, nodes[4].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(45.0, nodes[4].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(15.0, nodes[5].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(7.0, nodes[5].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(10.0, nodes[5].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(7.0, nodes[5].getPadding().get(Spacing.BOTTOM), .0001);
+
+    assertEquals(15.0, nodes[6].getPadding().get(Spacing.LEFT), .0001);
+    assertEquals(25.0, nodes[6].getPadding().get(Spacing.TOP), .0001);
+    assertEquals(35.0, nodes[6].getPadding().get(Spacing.RIGHT), .0001);
+    assertEquals(45.0, nodes[6].getPadding().get(Spacing.BOTTOM), .0001);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java
new file mode 100644
index 000000000000..8df49c6f6e69
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterSpecTest.java
@@ -0,0 +1,143 @@
+/**
+ * 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.uimanager;
+
+import java.util.Date;
+
+import android.view.View;
+
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Test that verifies that spec of methods annotated with @ReactProp is correct
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactPropAnnotationSetterSpecTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private abstract class BaseViewManager extends ViewManager<View, ReactShadowNode> {
+
+    @Override
+    public String getName() {
+      return "IgnoredName";
+    }
+
+    @Override
+    public ReactShadowNode createShadowNodeInstance() {
+      return null;
+    }
+
+    @Override
+    public Class<? extends ReactShadowNode> getShadowNodeClass() {
+      return ReactShadowNode.class;
+    }
+
+    @Override
+    protected View createViewInstance(ThemedReactContext reactContext) {
+      return null;
+    }
+
+    @Override
+    public void updateExtraData(View root, Object extraData) {
+    }
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testMethodWithWongNumberOfParams() {
+    new BaseViewManager() {
+      @ReactProp(name = "prop")
+      public void setterWithIncorrectNumberOfArgs(View v, boolean value, boolean otherValue) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testMethodWithTooFewParams() {
+    new BaseViewManager() {
+      @ReactProp(name = "prop")
+      public void setterWithTooFewParams(View v) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testUnsupportedPropValueType() {
+    new BaseViewManager() {
+      @ReactProp(name = "prop")
+      public void setterWithUnsupportedValueType(View v, Date value) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testSetterWIthNonViewParam() {
+    new BaseViewManager() {
+      @ReactProp(name = "prop")
+      public void setterWithNonViewParam(Object v, boolean value) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupInvalidNumberOfParams() {
+    new BaseViewManager() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWIthInvalidNumberOfParams(View v, int index, float value, float other) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupTooFewParams() {
+    new BaseViewManager() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWIthTooFewParams(View v, int index) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupNoIndexParam() {
+    new BaseViewManager() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithoutIndexParam(View v, float value, float sth) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupNoViewParam() {
+    new BaseViewManager() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithoutViewParam(Object v, int index, float value) {
+      }
+    }.getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupUnsupportedPropType() {
+    new BaseViewManager() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithUnsupportedPropType(View v, int index, long value) {
+      }
+    }.getNativeProps();
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java
new file mode 100644
index 000000000000..33e0b8892e8f
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropAnnotationSetterTest.java
@@ -0,0 +1,493 @@
+/**
+ * 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.uimanager;
+
+import android.view.View;
+
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+import org.junit.Test;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.fest.assertions.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+/**
+ * Test updating view through {@link ViewManager} with {@link ReactProp} and {@link ReactPropGroup}
+ * annotations.
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactPropAnnotationSetterTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  public interface ViewManagerUpdatesReceiver {
+    void onBooleanSetterCalled(boolean value);
+    void onIntSetterCalled(int value);
+    void onDoubleSetterCalled(double value);
+    void onFloatSetterCalled(float value);
+    void onStringSetterCalled(String value);
+    void onBoxedBooleanSetterCalled(Boolean value);
+    void onBoxedIntSetterCalled(Integer value);
+    void onArraySetterCalled(ReadableArray value);
+    void onMapSetterCalled(ReadableMap value);
+    void onFloatGroupPropSetterCalled(int index, float value);
+    void onIntGroupPropSetterCalled(int index, int value);
+    void onBoxedIntGroupPropSetterCalled(int index, Integer value);
+  }
+
+  private class ViewManagerUnderTest extends ViewManager<View, ReactShadowNode> {
+
+    final ViewManagerUpdatesReceiver mViewManagerUpdatesReceiver;
+
+    private ViewManagerUnderTest(ViewManagerUpdatesReceiver viewManagerUpdatesReceiver) {
+      mViewManagerUpdatesReceiver = viewManagerUpdatesReceiver;
+    }
+
+    @Override
+    public String getName() {
+      return "RedpandasLivestreamVideoView";
+    }
+
+    @Override
+    public ReactShadowNode createShadowNodeInstance() {
+      fail("This method should not be executed as a part of this test");
+      return null;
+    }
+
+    @Override
+    public Class<? extends ReactShadowNode> getShadowNodeClass() {
+      return ReactShadowNode.class;
+    }
+
+    @Override
+    protected View createViewInstance(ThemedReactContext reactContext) {
+      fail("This method should not be executed as a part of this test");
+      return null;
+    }
+
+    @Override
+    public void updateExtraData(View root, Object extraData) {
+      fail("This method should not be executed as a part of this test");
+    }
+
+    @ReactProp(name = "boolProp")
+    public void setBoolProp(View v, boolean value) {
+      mViewManagerUpdatesReceiver.onBooleanSetterCalled(value);
+    }
+
+    @ReactProp(name = "boolPropWithDefault", defaultBoolean = true)
+    public void setBoolPropWithDefault(View v, boolean value) {
+      mViewManagerUpdatesReceiver.onBooleanSetterCalled(value);
+    }
+
+    @ReactProp(name = "intProp")
+    public void setIntProp(View v, int value) {
+      mViewManagerUpdatesReceiver.onIntSetterCalled(value);
+    }
+
+    @ReactProp(name = "intPropWithDefault", defaultInt = 7168)
+    public void setIntPropWithDefault(View v, int value) {
+      mViewManagerUpdatesReceiver.onIntSetterCalled(value);
+    }
+
+    @ReactProp(name = "floatProp")
+    public void setFloatProp(View v, float value) {
+      mViewManagerUpdatesReceiver.onFloatSetterCalled(value);
+    }
+
+    @ReactProp(name = "floatPropWithDefault", defaultFloat = 14.0f)
+    public void setFloatPropWithDefault(View v, float value) {
+      mViewManagerUpdatesReceiver.onFloatSetterCalled(value);
+    }
+
+    @ReactProp(name = "doubleProp")
+    public void setDoubleProp(View v, double value) {
+      mViewManagerUpdatesReceiver.onDoubleSetterCalled(value);
+    }
+
+    @ReactProp(name = "doublePropWithDefault", defaultDouble = -88.0)
+    public void setDoublePropWithDefault(View v, double value) {
+      mViewManagerUpdatesReceiver.onDoubleSetterCalled(value);
+    }
+
+    @ReactProp(name = "stringProp")
+    public void setStringProp(View v, String value) {
+      mViewManagerUpdatesReceiver.onStringSetterCalled(value);
+    }
+
+    @ReactProp(name = "boxedBoolProp")
+    public void setBoxedBoolProp(View v, Boolean value) {
+      mViewManagerUpdatesReceiver.onBoxedBooleanSetterCalled(value);
+    }
+
+    @ReactProp(name = "boxedIntProp")
+    public void setBoxedIntProp(View v, Integer value) {
+      mViewManagerUpdatesReceiver.onBoxedIntSetterCalled(value);
+    }
+
+    @ReactProp(name = "arrayProp")
+    public void setArrayProp(View v, ReadableArray value) {
+      mViewManagerUpdatesReceiver.onArraySetterCalled(value);
+    }
+
+    @ReactProp(name = "mapProp")
+    public void setMapProp(View v, ReadableMap value) {
+      mViewManagerUpdatesReceiver.onMapSetterCalled(value);
+    }
+
+    @ReactPropGroup(names = {
+        "floatGroupPropFirst",
+        "floatGroupPropSecond",
+    })
+    public void setFloatGroupProp(View v, int index, float value) {
+      mViewManagerUpdatesReceiver.onFloatGroupPropSetterCalled(index, value);
+    }
+
+    @ReactPropGroup(names = {
+        "floatGroupPropWithDefaultFirst",
+        "floatGroupPropWithDefaultSecond",
+    }, defaultFloat = -100.0f)
+    public void setFloatGroupPropWithDefault(View v, int index, float value) {
+      mViewManagerUpdatesReceiver.onFloatGroupPropSetterCalled(index, value);
+    }
+
+    @ReactPropGroup(names = {
+        "intGroupPropFirst",
+        "intGroupPropSecond"
+    })
+    public void setIntGroupProp(View v, int index, int value) {
+      mViewManagerUpdatesReceiver.onIntGroupPropSetterCalled(index, value);
+    }
+
+    @ReactPropGroup(names = {
+        "intGroupPropWithDefaultFirst",
+        "intGroupPropWithDefaultSecond"
+    }, defaultInt = 555)
+    public void setIntGroupPropWithDefault(View v, int index, int value) {
+      mViewManagerUpdatesReceiver.onIntGroupPropSetterCalled(index, value);
+    }
+
+    @ReactPropGroup(names = {
+        "boxedIntGroupPropFirst",
+        "boxedIntGroupPropSecond",
+    })
+    public void setBoxedIntGroupProp(View v, int index, Integer value) {
+      mViewManagerUpdatesReceiver.onBoxedIntGroupPropSetterCalled(index, value);
+    }
+  }
+
+  public static ReactStylesDiffMap buildStyles(Object... keysAndValues) {
+    return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues));
+  }
+
+  private ViewManagerUnderTest mViewManager;
+  private ViewManagerUpdatesReceiver mUpdatesReceiverMock;
+
+  @Before
+  public void setup() {
+    mUpdatesReceiverMock = mock(ViewManagerUpdatesReceiver.class);
+    mViewManager = new ViewManagerUnderTest(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testBooleanSetter() {
+    mViewManager.updateProperties(null, buildStyles("boolProp", true));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(true);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boolProp", false));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(false);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boolProp", null));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(false);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boolPropWithDefault", false));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(false);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boolPropWithDefault", null));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(true);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testIntSetter() {
+    mViewManager.updateProperties(null, buildStyles("intProp", 13));
+    verify(mUpdatesReceiverMock).onIntSetterCalled(13);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intProp", null));
+    verify(mUpdatesReceiverMock).onIntSetterCalled(0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intPropWithDefault", -1));
+    verify(mUpdatesReceiverMock).onIntSetterCalled(-1);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intPropWithDefault", null));
+    verify(mUpdatesReceiverMock).onIntSetterCalled(7168);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testDoubleSetter() {
+    mViewManager.updateProperties(null, buildStyles("doubleProp", 13.0));
+    verify(mUpdatesReceiverMock).onDoubleSetterCalled(13.0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("doubleProp", null));
+    verify(mUpdatesReceiverMock).onDoubleSetterCalled(0.0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("doublePropWithDefault", -1.0));
+    verify(mUpdatesReceiverMock).onDoubleSetterCalled(-1.0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("doublePropWithDefault", null));
+    verify(mUpdatesReceiverMock).onDoubleSetterCalled(-88.0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testFloatSetter() {
+    mViewManager.updateProperties(null, buildStyles("floatProp", 13.0));
+    verify(mUpdatesReceiverMock).onFloatSetterCalled(13.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatProp", null));
+    verify(mUpdatesReceiverMock).onFloatSetterCalled(0.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatPropWithDefault", -1.0));
+    verify(mUpdatesReceiverMock).onFloatSetterCalled(-1.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatPropWithDefault", null));
+    verify(mUpdatesReceiverMock).onFloatSetterCalled(14.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testStringSetter() {
+    mViewManager.updateProperties(null, buildStyles("stringProp", "someRandomString"));
+    verify(mUpdatesReceiverMock).onStringSetterCalled("someRandomString");
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("stringProp", null));
+    verify(mUpdatesReceiverMock).onStringSetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testBoxedBooleanSetter() {
+    mViewManager.updateProperties(null, buildStyles("boxedBoolProp", true));
+    verify(mUpdatesReceiverMock).onBoxedBooleanSetterCalled(Boolean.TRUE);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boxedBoolProp", false));
+    verify(mUpdatesReceiverMock).onBoxedBooleanSetterCalled(Boolean.FALSE);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boxedBoolProp", null));
+    verify(mUpdatesReceiverMock).onBoxedBooleanSetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testBoxedIntSetter() {
+    mViewManager.updateProperties(null, buildStyles("boxedIntProp", 55));
+    verify(mUpdatesReceiverMock).onBoxedIntSetterCalled(Integer.valueOf(55));
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boxedIntProp", null));
+    verify(mUpdatesReceiverMock).onBoxedIntSetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testArraySetter() {
+    ReadableArray array = new JavaOnlyArray();
+    mViewManager.updateProperties(null, buildStyles("arrayProp", array));
+    verify(mUpdatesReceiverMock).onArraySetterCalled(array);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("arrayProp", null));
+    verify(mUpdatesReceiverMock).onArraySetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testMapSetter() {
+    ReadableMap map = new JavaOnlyMap();
+    mViewManager.updateProperties(null, buildStyles("mapProp", map));
+    verify(mUpdatesReceiverMock).onMapSetterCalled(map);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("mapProp", null));
+    verify(mUpdatesReceiverMock).onMapSetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testFloatGroupSetter() {
+    mViewManager.updateProperties(null, buildStyles("floatGroupPropFirst", 11.0));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(0, 11.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatGroupPropSecond", -111.0));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(1, -111.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatGroupPropSecond", null));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(1, 0.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("floatGroupPropWithDefaultFirst", null));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(0, -100.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testIntGroupSetter() {
+    mViewManager.updateProperties(null, buildStyles("intGroupPropFirst", -7));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(0, -7);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intGroupPropSecond", -77));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(1, -77);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intGroupPropSecond", null));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(1, 0);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intGroupPropWithDefaultFirst", 5));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(0, 5);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intGroupPropWithDefaultFirst", null));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(0, 555);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("intGroupPropWithDefaultSecond", null));
+    verify(mUpdatesReceiverMock).onIntGroupPropSetterCalled(1, 555);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testStringGroupSetter() {
+    mViewManager.updateProperties(null, buildStyles("boxedIntGroupPropFirst", -7));
+    verify(mUpdatesReceiverMock).onBoxedIntGroupPropSetterCalled(0, -7);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boxedIntGroupPropSecond", 12345));
+    verify(mUpdatesReceiverMock).onBoxedIntGroupPropSetterCalled(1, 12345);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mViewManager.updateProperties(null, buildStyles("boxedIntGroupPropSecond", null));
+    verify(mUpdatesReceiverMock).onBoxedIntGroupPropSetterCalled(1, null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateBoolPropWithMap() {
+    mViewManager.updateProperties(null, buildStyles("boolProp", new JavaOnlyMap()));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateStringPropWithDouble() {
+    mViewManager.updateProperties(null, buildStyles("stringProp", 14.5));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateDoublePropWithString() {
+    mViewManager.updateProperties(null, buildStyles("doubleProp", "hello"));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateIntPropWithDouble() {
+    mViewManager.updateProperties(null, buildStyles("intProp", -7.4));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateArrayPropWithBool() {
+    mViewManager.updateProperties(null, buildStyles("arrayProp", false));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateMapPropWithArray() {
+    mViewManager.updateProperties(null, buildStyles("mapProp", new JavaOnlyArray()));
+  }
+
+  @Test(expected = JSApplicationIllegalArgumentException.class)
+  public void testFailToUpdateBoxedIntPropWithBoxedDouble() {
+    mViewManager.updateProperties(null, buildStyles("boxedIntProp", Double.valueOf(1)));
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java
new file mode 100644
index 000000000000..bcc930fa63a8
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java
@@ -0,0 +1,192 @@
+/**
+ * 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.uimanager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.fail;
+
+/**
+ * Verifies that prop constants are generated properly based on {@code ReactProp} annotation.
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactPropConstantsTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private class ViewManagerUnderTest extends ViewManager<View, ReactShadowNode> {
+
+    @Override
+    public String getName() {
+      return "SomeView";
+    }
+
+    @Override
+    public ReactShadowNode createShadowNodeInstance() {
+      fail("This method should not be executed as a part of this test");
+      return null;
+    }
+
+    @Override
+    protected View createViewInstance(ThemedReactContext reactContext) {
+      fail("This method should not be executed as a part of this test");
+      return null;
+    }
+
+    @Override
+    public Class<? extends ReactShadowNode> getShadowNodeClass() {
+      return ReactShadowNode.class;
+    }
+
+    @Override
+    public void updateExtraData(View root, Object extraData) {
+      fail("This method should not be executed as a part of this test");
+    }
+
+    @ReactProp(name = "boolProp")
+    public void setBoolProp(View v, boolean value) {
+    }
+
+    @ReactProp(name = "intProp")
+    public void setIntProp(View v, int value) {
+    }
+
+    @ReactProp(name = "floatProp")
+    public void setFloatProp(View v, float value) {
+    }
+
+    @ReactProp(name = "doubleProp")
+    public void setDoubleProp(View v, double value) {
+    }
+
+    @ReactProp(name = "stringProp")
+    public void setStringProp(View v, String value) {
+    }
+
+    @ReactProp(name = "boxedBoolProp")
+    public void setBoxedBoolProp(View v, Boolean value) {
+    }
+
+    @ReactProp(name = "boxedIntProp")
+    public void setBoxedIntProp(View v, Integer value) {
+    }
+
+    @ReactProp(name = "arrayProp")
+    public void setArrayProp(View v, ReadableArray value) {
+    }
+
+    @ReactProp(name = "mapProp")
+    public void setMapProp(View v, ReadableMap value) {
+    }
+
+    @ReactPropGroup(names = {
+        "floatGroupPropFirst",
+        "floatGroupPropSecond",
+    })
+    public void setFloatGroupProp(View v, int index, float value) {
+    }
+
+    @ReactPropGroup(names = {
+        "intGroupPropFirst",
+        "intGroupPropSecond"
+    })
+    public void setIntGroupProp(View v, int index, int value) {
+    }
+
+    @ReactPropGroup(names = {
+        "boxedIntGroupPropFirst",
+        "boxedIntGroupPropSecond",
+    })
+    public void setBoxedIntGroupProp(View v, int index, Integer value) {
+    }
+
+    @ReactProp(name = "customIntProp", customType = "date")
+    public void customIntProp(View v, int value) {
+    }
+
+    @ReactPropGroup(names = {
+        "customBoxedIntGroupPropFirst",
+        "customBoxedIntGroupPropSecond"
+    }, customType = "color")
+    public void customIntGroupProp(View v, int index, Integer value) {
+    }
+  }
+
+  @Test
+  public void testNativePropsIncludeCorrectTypes() {
+    List<ViewManager> viewManagers = Arrays.<ViewManager>asList(new ViewManagerUnderTest());
+    ReactApplicationContext reactContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        reactContext,
+        viewManagers,
+        new UIImplementation(reactContext, viewManagers));
+    Map<String, String> constants =
+        (Map) valueAtPath(uiManagerModule.getConstants(), "SomeView", "NativeProps");
+    assertThat(constants).isEqualTo(
+        MapBuilder.<String, String>builder()
+            .put("boolProp", "boolean")
+            .put("intProp", "number")
+            .put("doubleProp", "number")
+            .put("floatProp", "number")
+            .put("stringProp", "String")
+            .put("boxedBoolProp", "boolean")
+            .put("boxedIntProp", "number")
+            .put("arrayProp", "Array")
+            .put("mapProp", "Map")
+            .put("floatGroupPropFirst", "number")
+            .put("floatGroupPropSecond", "number")
+            .put("intGroupPropFirst", "number")
+            .put("intGroupPropSecond", "number")
+            .put("boxedIntGroupPropFirst", "number")
+            .put("boxedIntGroupPropSecond", "number")
+            .put("customIntProp", "date")
+            .put("customBoxedIntGroupPropFirst", "color")
+            .put("customBoxedIntGroupPropSecond", "color")
+            .build());
+  }
+
+  private static Object valueAtPath(Map nestedMap, String... keyPath) {
+    assertThat(keyPath).isNotEmpty();
+    Object value = nestedMap;
+    for (String key : keyPath) {
+      assertThat(value).isInstanceOf(Map.class);
+      nestedMap = (Map) value;
+      assertThat(nestedMap).containsKey(key);
+      value = nestedMap.get(key);
+    }
+    return value;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java
new file mode 100644
index 000000000000..59710b7efcfc
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.java
@@ -0,0 +1,133 @@
+/**
+ * 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.uimanager;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+import org.junit.Test;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import static com.facebook.react.uimanager.ReactPropAnnotationSetterTest.ViewManagerUpdatesReceiver;
+import static com.facebook.react.uimanager.ReactPropAnnotationSetterTest.buildStyles;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+/**
+ * Test {@link ReactProp} annotation for {@link ReactShadowNode}. More comprahensive test of this
+ * annotation can be found in {@link ReactPropAnnotationSetterTest} where we test all possible types
+ * of properties to be updated.
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactPropForShadowNodeSetterTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private class ShadowViewUnderTest extends ReactShadowNode {
+
+    private ViewManagerUpdatesReceiver mViewManagerUpdatesReceiver;
+
+    private ShadowViewUnderTest(ViewManagerUpdatesReceiver viewManagerUpdatesReceiver) {
+      mViewManagerUpdatesReceiver = viewManagerUpdatesReceiver;
+    }
+
+    @ReactProp(name = "boolProp")
+    public void setBoolProp(boolean value) {
+      mViewManagerUpdatesReceiver.onBooleanSetterCalled(value);
+    }
+
+    @ReactProp(name = "stringProp")
+    public void setStringProp(@Nullable String value) {
+      mViewManagerUpdatesReceiver.onStringSetterCalled(value);
+    }
+
+    @ReactProp(name = "boxedIntProp")
+    public void setBoxedIntProp(@Nullable Integer value) {
+      mViewManagerUpdatesReceiver.onBoxedIntSetterCalled(value);
+    }
+
+    @ReactPropGroup(names = {
+        "floatGroupPropFirst",
+        "floatGroupPropSecond",
+    })
+    public void setFloatGroupProp(int index, float value) {
+      mViewManagerUpdatesReceiver.onFloatGroupPropSetterCalled(index, value);
+    }
+  }
+
+  private ViewManagerUpdatesReceiver mUpdatesReceiverMock;
+  private ShadowViewUnderTest mShadowView;
+
+  @Before
+  public void setup() {
+    mUpdatesReceiverMock = mock(ViewManagerUpdatesReceiver.class);
+    mShadowView = new ShadowViewUnderTest(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testBooleanSetter() {
+    mShadowView.updateProperties(buildStyles("boolProp", true));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(true);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mShadowView.updateProperties(buildStyles("boolProp", false));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(false);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mShadowView.updateProperties(buildStyles("boolProp", null));
+    verify(mUpdatesReceiverMock).onBooleanSetterCalled(false);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testStringSetter() {
+    mShadowView.updateProperties(buildStyles("stringProp", "someRandomString"));
+    verify(mUpdatesReceiverMock).onStringSetterCalled("someRandomString");
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mShadowView.updateProperties(buildStyles("stringProp", null));
+    verify(mUpdatesReceiverMock).onStringSetterCalled(null);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+
+  @Test
+  public void testFloatGroupSetter() {
+    mShadowView.updateProperties(buildStyles("floatGroupPropFirst", 11.0));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(0, 11.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mShadowView.updateProperties(buildStyles("floatGroupPropSecond", -111.0));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(1, -111.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+
+    mShadowView.updateProperties(buildStyles("floatGroupPropSecond", null));
+    verify(mUpdatesReceiverMock).onFloatGroupPropSetterCalled(1, 0.0f);
+    verifyNoMoreInteractions(mUpdatesReceiverMock);
+    reset(mUpdatesReceiverMock);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java
new file mode 100644
index 000000000000..1ddcdf047c23
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSpecTest.java
@@ -0,0 +1,123 @@
+/**
+ * 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.uimanager;
+
+import java.util.Map;
+
+import android.view.View;
+
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Test that verifies that spec of methods annotated with @ReactProp in {@link ReactShadowNode} is
+ * correct
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactPropForShadowNodeSpecTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private static class BaseViewManager extends ViewManager {
+
+    private final Class<? extends ReactShadowNode> mShadowNodeClass;
+
+    private BaseViewManager(Class<? extends ReactShadowNode> shadowNodeClass) {
+      mShadowNodeClass = shadowNodeClass;
+    }
+
+    @Override
+    public String getName() {
+      return "IgnoredName";
+    }
+
+    @Override
+    public ReactShadowNode createShadowNodeInstance() {
+      return null;
+    }
+
+    @Override
+    public Class getShadowNodeClass() {
+      return mShadowNodeClass;
+    }
+
+    @Override
+    protected View createViewInstance(ThemedReactContext reactContext) {
+      return null;
+    }
+
+    @Override
+    public void updateExtraData(View root, Object extraData) {
+    }
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testMethodWithWrongNumberOfParams() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactProp(name = "prop")
+      public void setterWithIncorrectNumberOfArgs(boolean value, int anotherValue) {
+      }
+    }.getClass()).getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testMethodWithTooFewParams() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactProp(name = "prop")
+      public void setterWithNoArgs() {
+      }
+    }.getClass()).getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testUnsupportedValueType() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactProp(name = "prop")
+      public void setterWithMap(Map value) {
+      }
+    }.getClass()).getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupInvalidNumberOfParams() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithTooManyParams(int index, float value, boolean bool) {
+      }
+    }.getClass()).getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupTooFewParams() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithTooManyParams(int index) {
+      }
+    }.getClass()).getNativeProps();
+  }
+
+  @Test(expected = RuntimeException.class)
+  public void testGroupNoIndexParam() {
+    new BaseViewManager(new ReactShadowNode() {
+      @ReactPropGroup(names = {"prop1", "prop2"})
+      public void setterWithTooManyParams(float value, boolean bool) {
+      }
+    }.getClass()).getNativeProps();
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java
new file mode 100644
index 000000000000..b97941d93f3e
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/SimpleViewPropertyTest.java
@@ -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.
+ */
+
+package com.facebook.react.uimanager;
+
+import java.util.Map;
+
+import android.view.View;
+
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.touch.JSResponderHandler;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.uimanager.annotations.ReactProp;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.RobolectricTestRunner;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.offset;
+
+/**
+ * Verify {@link View} view property being applied properly by {@link SimpleViewManager}
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class SimpleViewPropertyTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private static class ConcreteViewManager extends SimpleViewManager<View> {
+
+    @ReactProp(name = "foo")
+    public void setFoo(View view, boolean foo) {
+    }
+
+    @ReactProp(name = "bar")
+    public void setBar(View view, ReadableMap bar) {
+    }
+
+    @Override
+    protected View createViewInstance(ThemedReactContext reactContext) {
+      return new View(reactContext);
+    }
+
+    @Override
+    public String getName() {
+      return "View";
+    }
+  }
+
+  private ReactApplicationContext mContext;
+  private CatalystInstance mCatalystInstanceMock;
+  private ThemedReactContext mThemedContext;
+  private ConcreteViewManager mManager;
+
+  @Before
+  public void setup() {
+    mContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
+    mContext.initializeWithInstance(mCatalystInstanceMock);
+    mThemedContext = new ThemedReactContext(mContext, mContext);
+    mManager = new ConcreteViewManager();
+  }
+
+  public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
+    return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues));
+  }
+
+  @Test
+  public void testOpacity() {
+    View view = mManager.createView(mThemedContext, new JSResponderHandler());
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getAlpha()).isEqualTo(1.0f);
+
+    mManager.updateProperties(view, buildStyles("opacity", 0.31));
+    assertThat(view.getAlpha()).isEqualTo(0.31f, offset(1e-5f));
+
+    mManager.updateProperties(view, buildStyles("opacity", null));
+    assertThat(view.getAlpha()).isEqualTo(1.0f);
+  }
+
+  @Test
+  public void testGetNativeProps() {
+    Map<String, String> nativeProps = mManager.getNativeProps();
+    assertThat(nativeProps.get("foo")).isEqualTo("boolean");
+    assertThat(nativeProps.get("bar")).isEqualTo("Map");
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java
new file mode 100644
index 000000000000..0f8b7ec9195e
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java
@@ -0,0 +1,204 @@
+/**
+ * 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.uimanager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import android.util.DisplayMetrics;
+
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.common.MapBuilder;
+
+import org.fest.assertions.data.MapEntry;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class UIManagerModuleConstantsTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private static final String CUSTOM_BUBBLING_EVENT_TYPES = "customBubblingEventTypes";
+  private static final String CUSTOM_DIRECT_EVENT_TYPES = "customDirectEventTypes";
+
+  private static final Map TWIRL_BUBBLING_EVENT_MAP = MapBuilder.of(
+      "phasedRegistrationNames",
+      MapBuilder.of(
+          "bubbled",
+          "onTwirl",
+          "captured",
+          "onTwirlCaptured"));
+  private static final Map TWIRL_DIRECT_EVENT_MAP = MapBuilder.of("registrationName", "onTwirl");
+
+  private ReactApplicationContext mReactContext;
+  private UIImplementation mUIImplementation;
+
+  @Before
+  public void setUp() {
+    mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mUIImplementation = mock(UIImplementation.class);
+
+    DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
+  }
+
+  @Test
+  public void testNoCustomConstants() {
+    List<ViewManager> viewManagers = Arrays.asList(mock(ViewManager.class));
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat(constants)
+        .containsKey(CUSTOM_BUBBLING_EVENT_TYPES)
+        .containsKey(CUSTOM_DIRECT_EVENT_TYPES)
+        .containsKey("Dimensions");
+  }
+
+  @Test
+  public void testCustomBubblingEvents() {
+    ViewManager mockViewManager = mock(ViewManager.class);
+    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
+    when(mockViewManager.getExportedCustomBubblingEventTypeConstants())
+        .thenReturn(MapBuilder.of("onTwirl", TWIRL_BUBBLING_EVENT_MAP));
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat((Map) constants.get(CUSTOM_BUBBLING_EVENT_TYPES))
+        .contains(MapEntry.entry("onTwirl", TWIRL_BUBBLING_EVENT_MAP))
+        .containsKey("topChange");
+  }
+
+  @Test
+  public void testCustomDirectEvents() {
+    ViewManager mockViewManager = mock(ViewManager.class);
+    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
+    when(mockViewManager.getExportedCustomDirectEventTypeConstants())
+        .thenReturn(MapBuilder.of("onTwirl", TWIRL_DIRECT_EVENT_MAP));
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat((Map) constants.get(CUSTOM_DIRECT_EVENT_TYPES))
+        .contains(MapEntry.entry("onTwirl", TWIRL_DIRECT_EVENT_MAP))
+        .containsKey("topLoadingStart");
+  }
+
+  @Test
+  public void testCustomViewConstants() {
+    ViewManager mockViewManager = mock(ViewManager.class);
+    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
+    when(mockViewManager.getName()).thenReturn("RedPandaPhotoOfTheDayView");
+    when(mockViewManager.getExportedViewConstants())
+        .thenReturn(MapBuilder.of("PhotoSizeType", MapBuilder.of("Small", 1, "Large", 2)));
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat(constants).containsKey("RedPandaPhotoOfTheDayView");
+    assertThat((Map) constants.get("RedPandaPhotoOfTheDayView")).containsKey("Constants");
+    assertThat((Map) valueAtPath(constants, "RedPandaPhotoOfTheDayView", "Constants"))
+        .containsKey("PhotoSizeType");
+  }
+
+  @Test
+  public void testNativeProps() {
+    ViewManager mockViewManager = mock(ViewManager.class);
+    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
+    when(mockViewManager.getName()).thenReturn("SomeView");
+    when(mockViewManager.getNativeProps())
+        .thenReturn(MapBuilder.of("fooProp", "number"));
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat((String) valueAtPath(constants, "SomeView", "NativeProps", "fooProp"))
+        .isEqualTo("number");
+  }
+
+  @Test
+  public void testMergeConstants() {
+    ViewManager managerX = mock(ViewManager.class);
+    when(managerX.getExportedCustomDirectEventTypeConstants()).thenReturn(MapBuilder.of(
+        "onTwirl",
+        MapBuilder.of(
+            "registrationName",
+            "onTwirl",
+            "keyToOverride",
+            "valueX",
+            "mapToMerge",
+            MapBuilder.of("keyToOverride", "innerValueX", "anotherKey", "valueX"))));
+
+    ViewManager managerY = mock(ViewManager.class);
+    when(managerY.getExportedCustomDirectEventTypeConstants()).thenReturn(MapBuilder.of(
+        "onTwirl",
+        MapBuilder.of(
+            "extraKey",
+            "extraValue",
+            "keyToOverride",
+            "valueY",
+            "mapToMerge",
+            MapBuilder.of("keyToOverride", "innerValueY", "extraKey", "valueY"))));
+
+    List<ViewManager> viewManagers = Arrays.asList(managerX, managerY);
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        mUIImplementation);
+    Map<String, Object> constants = uiManagerModule.getConstants();
+    assertThat((Map) constants.get(CUSTOM_DIRECT_EVENT_TYPES)).containsKey("onTwirl");
+
+    Map twirlMap = (Map) valueAtPath(constants, CUSTOM_DIRECT_EVENT_TYPES, "onTwirl");
+    assertThat(twirlMap)
+        .contains(MapEntry.entry("registrationName", "onTwirl"))
+        .contains(MapEntry.entry("keyToOverride", "valueY"))
+        .contains(MapEntry.entry("extraKey", "extraValue"))
+        .containsKey("mapToMerge");
+
+    Map mapToMerge = (Map) valueAtPath(twirlMap, "mapToMerge");
+    assertThat(mapToMerge)
+        .contains(MapEntry.entry("keyToOverride", "innerValueY"))
+        .contains(MapEntry.entry("anotherKey", "valueX"))
+        .contains(MapEntry.entry("extraKey", "valueY"));
+  }
+
+  private static Object valueAtPath(Map nestedMap, String... keyPath) {
+    assertThat(keyPath).isNotEmpty();
+    Object value = nestedMap;
+    for (String key : keyPath) {
+      assertThat(value).isInstanceOf(Map.class);
+      nestedMap = (Map) value;
+      assertThat(nestedMap).containsKey(key);
+      value = nestedMap.get(key);
+    }
+    return value;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java
new file mode 100644
index 000000000000..3b9cd770647b
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java
@@ -0,0 +1,833 @@
+/**
+ * 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.uimanager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import android.graphics.Color;
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.facebook.react.ReactRootView;
+import com.facebook.react.animation.Animation;
+import com.facebook.react.animation.AnimationPropertyUpdater;
+import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.views.text.ReactRawTextManager;
+import com.facebook.react.views.text.ReactTextShadowNode;
+import com.facebook.react.views.text.ReactTextViewManager;
+import com.facebook.react.views.view.ReactViewGroup;
+import com.facebook.react.views.view.ReactViewManager;
+import com.facebook.react.bridge.ReactTestHelper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link UIManagerModule}.
+ */
+@PrepareForTest({Arguments.class, ReactChoreographer.class})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class UIManagerModuleTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ReactApplicationContext mReactContext;
+  private CatalystInstance mCatalystInstanceMock;
+  private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
+
+  @Before
+  public void setUp() {
+    PowerMockito.mockStatic(Arguments.class, ReactChoreographer.class);
+
+    ReactChoreographer choreographerMock = mock(ReactChoreographer.class);
+    PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyArray();
+      }
+    });
+    PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyMap();
+      }
+    });
+    PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(choreographerMock);
+
+    mPendingChoreographerCallbacks = new ArrayList<>();
+    doAnswer(new Answer() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        mPendingChoreographerCallbacks
+            .add((Choreographer.FrameCallback) invocation.getArguments()[1]);
+        return null;
+      }
+    }).when(choreographerMock).postFrameCallback(
+        any(ReactChoreographer.CallbackType.class),
+        any(Choreographer.FrameCallback.class));
+
+    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
+    mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mReactContext.initializeWithInstance(mCatalystInstanceMock);
+
+    DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
+
+    UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
+    when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
+        .thenReturn(uiManagerModuleMock);
+
+  }
+
+  @Test
+  public void testCreateSimpleHierarchy() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ViewGroup rootView = createSimpleTextHierarchy(uiManager, "Some text");
+
+    assertThat(rootView.getChildCount()).isEqualTo(1);
+
+    View firstChild = rootView.getChildAt(0);
+    assertThat(firstChild).isInstanceOf(TextView.class);
+    assertThat(((TextView) firstChild).getText().toString()).isEqualTo("Some text");
+  }
+
+  @Test
+  public void testUpdateSimpleHierarchy() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ViewGroup rootView = createSimpleTextHierarchy(uiManager, "Some text");
+    TextView textView = (TextView) rootView.getChildAt(0);
+
+    int rawTextTag = 3;
+    uiManager.updateView(
+        rawTextTag,
+        ReactRawTextManager.REACT_CLASS,
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "New text"));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(textView.getText().toString()).isEqualTo("New text");
+  }
+
+  @Test
+  public void testHierarchyWithView() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView =
+        new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+    int viewTag = rootTag + 1;
+    int subViewTag = viewTag + 1;
+
+    uiManager.createView(
+        viewTag,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        subViewTag,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+
+    uiManager.manageChildren(
+        viewTag,
+        null,
+        null,
+        JavaOnlyArray.of(subViewTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.manageChildren(
+        rootTag,
+        null,
+        null,
+        JavaOnlyArray.of(viewTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(rootView.getChildCount()).isEqualTo(1);
+
+    ViewGroup child = (ViewGroup) rootView.getChildAt(0);
+    assertThat(child.getChildCount()).isEqualTo(1);
+
+    ViewGroup grandchild = (ViewGroup) child.getChildAt(0);
+    assertThat(grandchild).isInstanceOf(ViewGroup.class);
+    assertThat(grandchild.getChildCount()).isEqualTo(0);
+  }
+
+  @Test
+  public void testMoveViews() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(1);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(2);
+    View expectedViewAt2 = hierarchy.nativeRootView.getChildAt(0);
+    View expectedViewAt3 = hierarchy.nativeRootView.getChildAt(3);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        JavaOnlyArray.of(1, 0, 2),
+        JavaOnlyArray.of(0, 2, 1),
+        null,
+        null,
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertChildrenAreExactly(
+        hierarchy.nativeRootView,
+        expectedViewAt0,
+        expectedViewAt1,
+        expectedViewAt2,
+        expectedViewAt3);
+  }
+
+  @Test
+  public void testDeleteViews() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(1);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(2);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        null,
+        null,
+        JavaOnlyArray.of(0, 3));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertChildrenAreExactly(
+        hierarchy.nativeRootView,
+        expectedViewAt0,
+        expectedViewAt1);
+  }
+
+  @Test
+  public void testMoveAndDeleteViews() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(0);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(3);
+    View expectedViewAt2 = hierarchy.nativeRootView.getChildAt(2);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        JavaOnlyArray.of(3),
+        JavaOnlyArray.of(1),
+        null,
+        null,
+        JavaOnlyArray.of(1));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertChildrenAreExactly(
+        hierarchy.nativeRootView,
+        expectedViewAt0,
+        expectedViewAt1,
+        expectedViewAt2);
+  }
+
+  @Test(expected = IllegalViewOperationException.class)
+  public void testMoveAndDeleteRemoveViewsDuplicateRemove() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        JavaOnlyArray.of(3),
+        JavaOnlyArray.of(1),
+        null,
+        null,
+        JavaOnlyArray.of(3));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+  }
+
+  @Test(expected = IllegalViewOperationException.class)
+  public void testDuplicateRemove() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        null,
+        null,
+        JavaOnlyArray.of(3, 3));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+  }
+
+  @Test
+  public void testMoveAndAddViews() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    int textViewTag = 1000;
+    uiManager.createView(
+        textViewTag,
+        ReactTextViewManager.REACT_CLASS,
+        hierarchy.rootView,
+        JavaOnlyMap.of("collapsable", false));
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(0);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(3);
+    View expectedViewAt3 = hierarchy.nativeRootView.getChildAt(1);
+    View expectedViewAt4 = hierarchy.nativeRootView.getChildAt(2);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        JavaOnlyArray.of(1, 2, 3),
+        JavaOnlyArray.of(3, 4, 1),
+        JavaOnlyArray.of(textViewTag),
+        JavaOnlyArray.of(2),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(5);
+    assertThat(hierarchy.nativeRootView.getChildAt(0)).isEqualTo(expectedViewAt0);
+    assertThat(hierarchy.nativeRootView.getChildAt(1)).isEqualTo(expectedViewAt1);
+    assertThat(hierarchy.nativeRootView.getChildAt(3)).isEqualTo(expectedViewAt3);
+    assertThat(hierarchy.nativeRootView.getChildAt(4)).isEqualTo(expectedViewAt4);
+  }
+
+  @Test
+  public void testMoveViewsWithChildren() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(0);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(2);
+    View expectedViewAt2 = hierarchy.nativeRootView.getChildAt(1);
+    View expectedViewAt3 = hierarchy.nativeRootView.getChildAt(3);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        JavaOnlyArray.of(1, 2),
+        JavaOnlyArray.of(2, 1),
+        null,
+        null,
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertChildrenAreExactly(
+        hierarchy.nativeRootView,
+        expectedViewAt0,
+        expectedViewAt1,
+        expectedViewAt2,
+        expectedViewAt3);
+    assertThat(((ViewGroup) hierarchy.nativeRootView.getChildAt(2)).getChildCount()).isEqualTo(2);
+  }
+
+  @Test
+  public void testDeleteViewsWithChildren() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View expectedViewAt0 = hierarchy.nativeRootView.getChildAt(0);
+    View expectedViewAt1 = hierarchy.nativeRootView.getChildAt(2);
+    View expectedViewAt2 = hierarchy.nativeRootView.getChildAt(3);
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        null,
+        null,
+        JavaOnlyArray.of(1));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertChildrenAreExactly(
+        hierarchy.nativeRootView,
+        expectedViewAt0,
+        expectedViewAt1,
+        expectedViewAt2);
+  }
+
+  @Test
+  public void testLayoutAppliedToNodes() throws Exception {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    int newViewTag = 10000;
+    uiManager.createView(
+        newViewTag,
+        ReactViewManager.REACT_CLASS,
+        hierarchy.rootView,
+        JavaOnlyMap
+            .of("left", 10.0, "top", 20.0, "width", 30.0, "height", 40.0, "collapsable", false));
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        JavaOnlyArray.of(newViewTag),
+        JavaOnlyArray.of(4),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    View newView = hierarchy.nativeRootView.getChildAt(4);
+    assertThat(newView.getLeft()).isEqualTo(10);
+    assertThat(newView.getTop()).isEqualTo(20);
+
+    assertThat(newView.getWidth()).isEqualTo(30);
+    assertThat(newView.getHeight()).isEqualTo(40);
+  }
+
+  /**
+   * This is to make sure we execute enqueued operations in the order given by JS.
+   */
+  @Test
+  public void testAddUpdateRemoveInSingleBatch() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    int newViewTag = 10000;
+    uiManager.createView(
+        newViewTag,
+        ReactViewManager.REACT_CLASS,
+        hierarchy.rootView,
+        JavaOnlyMap.of("collapsable", false));
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        JavaOnlyArray.of(newViewTag),
+        JavaOnlyArray.of(4),
+        null);
+
+    uiManager.updateView(
+        newViewTag,
+        ReactViewManager.REACT_CLASS,
+        JavaOnlyMap.of("backgroundColor", Color.RED));
+
+    uiManager.manageChildren(
+        hierarchy.rootView,
+        null,
+        null,
+        null,
+        null,
+        JavaOnlyArray.of(4));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(4);
+  }
+
+  @Test
+  public void testTagsAssignment() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    View view0 = hierarchy.nativeRootView.getChildAt(0);
+    assertThat(view0.getId()).isEqualTo(hierarchy.view0);
+
+    View viewWithChildren1 = hierarchy.nativeRootView.getChildAt(1);
+    assertThat(viewWithChildren1.getId()).isEqualTo(hierarchy.viewWithChildren1);
+
+    View childView0 = ((ViewGroup) viewWithChildren1).getChildAt(0);
+    assertThat(childView0.getId()).isEqualTo(hierarchy.childView0);
+
+    View childView1 = ((ViewGroup) viewWithChildren1).getChildAt(1);
+    assertThat(childView1.getId()).isEqualTo(hierarchy.childView1);
+
+    View view2 = hierarchy.nativeRootView.getChildAt(2);
+    assertThat(view2.getId()).isEqualTo(hierarchy.view2);
+
+    View view3 = hierarchy.nativeRootView.getChildAt(3);
+    assertThat(view3.getId()).isEqualTo(hierarchy.view3);
+  }
+
+  @Test
+  public void testLayoutPropertyUpdatingOnlyOnLayoutChange() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    // Update layout to some values, this way we can verify it hasn't been updated, because the
+    // update process would normally reset it back to some non-negative value
+    View view0 = hierarchy.nativeRootView.getChildAt(0);
+    view0.layout(1, 2, 3, 4);
+
+    // verify that X get updated when we update layout properties
+    uiManager.updateView(
+        hierarchy.view0,
+        ReactViewManager.REACT_CLASS,
+        JavaOnlyMap.of("left", 10.0, "top", 20.0, "width", 30.0, "height", 40.0));
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+    assertThat(view0.getLeft()).isGreaterThan(2);
+
+    // verify that the layout doesn't get updated when we update style property not affecting the
+    // position (e.g., background-color)
+    view0.layout(1, 2, 3, 4);
+    uiManager.updateView(
+        hierarchy.view0,
+        ReactViewManager.REACT_CLASS,
+        JavaOnlyMap.of("backgroundColor", Color.RED));
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+    assertThat(view0.getLeft()).isEqualTo(1);
+  }
+
+  private static class AnimationStub extends Animation {
+
+    public AnimationStub(int animationID, AnimationPropertyUpdater propertyUpdater) {
+      super(animationID, propertyUpdater);
+    }
+
+    @Override
+    public void run() {
+    }
+  }
+
+  @Test
+  public void testAddAndRemoveAnimation() {
+    UIManagerModule uiManagerModule = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManagerModule);
+
+    AnimationPropertyUpdater mockPropertyUpdater = mock(AnimationPropertyUpdater.class);
+    Animation mockAnimation = spy(new AnimationStub(1000, mockPropertyUpdater));
+    Callback callbackMock = mock(Callback.class);
+
+    int rootTag = hierarchy.rootView;
+    uiManagerModule.createView(
+        hierarchy.rootView,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+
+    uiManagerModule.registerAnimation(mockAnimation);
+    uiManagerModule.addAnimation(hierarchy.rootView, 1000, callbackMock);
+    uiManagerModule.removeAnimation(hierarchy.rootView, 1000);
+
+    uiManagerModule.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    verify(callbackMock, times(1)).invoke(false);
+    verify(mockAnimation).run();
+    verify(mockAnimation).cancel();
+  }
+
+  /**
+   * Makes sure replaceExistingNonRootView by replacing a view with a new view that has a background
+   * color set.
+   */
+  @Test
+  public void testReplaceExistingNonRootView() {
+    UIManagerModule uiManager = getUIManagerModule();
+    TestMoveDeleteHierarchy hierarchy = createMoveDeleteHierarchy(uiManager);
+
+    int newViewTag = 1234;
+    uiManager.createView(
+        newViewTag,
+        ReactViewManager.REACT_CLASS,
+        hierarchy.rootView,
+        JavaOnlyMap.of("backgroundColor", Color.RED));
+
+    uiManager.replaceExistingNonRootView(hierarchy.view2, newViewTag);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(4);
+    assertThat(hierarchy.nativeRootView.getChildAt(2)).isInstanceOf(ReactViewGroup.class);
+    ReactViewGroup view = (ReactViewGroup) hierarchy.nativeRootView.getChildAt(2);
+    assertThat(view.getBackgroundColor()).isEqualTo(Color.RED);
+  }
+
+  /**
+   * Verifies removeSubviewsFromContainerWithID works by adding subviews, removing them, and
+   * checking that the final number of children is correct.
+   */
+  @Test
+  public void testRemoveSubviewsFromContainerWithID() {
+    UIManagerModule uiManager = getUIManagerModule();
+    ReactRootView rootView =
+        new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+
+    final int containerTag = rootTag + 1;
+    final int containerSiblingTag = containerTag + 1;
+
+    uiManager.createView(
+        containerTag,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        containerSiblingTag,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    addChild(uiManager, rootTag, containerTag, 0);
+    addChild(uiManager, rootTag, containerSiblingTag, 1);
+
+    uiManager.createView(
+        containerTag + 2,
+        ReactTextViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        containerTag + 3,
+        ReactTextViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    addChild(uiManager, containerTag, containerTag + 2, 0);
+    addChild(uiManager, containerTag, containerTag + 3, 1);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(rootView.getChildCount()).isEqualTo(2);
+    assertThat(((ViewGroup)rootView.getChildAt(0)).getChildCount()).isEqualTo(2);
+
+    uiManager.removeSubviewsFromContainerWithID(containerTag);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    assertThat(rootView.getChildCount()).isEqualTo(2);
+    assertThat(((ViewGroup)rootView.getChildAt(0)).getChildCount()).isEqualTo(0);
+  }
+
+  /**
+   * Assuming no other views have been created, the root view will have tag 1, Text tag 2, and
+   * RawText tag 3.
+   */
+  private ViewGroup createSimpleTextHierarchy(UIManagerModule uiManager, String text) {
+    ReactRootView rootView =
+        new ReactRootView(RuntimeEnvironment.application.getApplicationContext());
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+    int textTag = rootTag + 1;
+    int rawTextTag = textTag + 1;
+
+    uiManager.createView(
+        textTag,
+        ReactTextViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        rawTextTag,
+        ReactRawTextManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, text, "collapsable", false));
+
+    uiManager.manageChildren(
+        textTag,
+        null,
+        null,
+        JavaOnlyArray.of(rawTextTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.manageChildren(
+        rootTag,
+        null,
+        null,
+        JavaOnlyArray.of(textTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    return rootView;
+  }
+
+  private TestMoveDeleteHierarchy createMoveDeleteHierarchy(UIManagerModule uiManager) {
+    ReactRootView rootView = new ReactRootView(mReactContext);
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+
+    TestMoveDeleteHierarchy hierarchy = new TestMoveDeleteHierarchy(rootView, rootTag);
+
+    uiManager.createView(
+        hierarchy.view0,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        hierarchy.viewWithChildren1,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        hierarchy.view2,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        hierarchy.view3,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        hierarchy.childView0,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+    uiManager.createView(
+        hierarchy.childView1,
+        ReactViewManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of("collapsable", false));
+
+    addChild(uiManager, rootTag, hierarchy.view0, 0);
+    addChild(uiManager, rootTag, hierarchy.viewWithChildren1, 1);
+    addChild(uiManager, rootTag, hierarchy.view2, 2);
+    addChild(uiManager, rootTag, hierarchy.view3, 3);
+
+    addChild(uiManager, hierarchy.viewWithChildren1, hierarchy.childView0, 0);
+    addChild(uiManager, hierarchy.viewWithChildren1, hierarchy.childView1, 1);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    return hierarchy;
+  }
+
+  private void addChild(UIManagerModule uiManager, int parentTag, int childTag, int index) {
+    uiManager.manageChildren(
+        parentTag,
+        null,
+        null,
+        JavaOnlyArray.of(childTag),
+        JavaOnlyArray.of(index),
+        null);
+  }
+
+  private void assertChildrenAreExactly(ViewGroup parent, View... views) {
+    assertThat(parent.getChildCount()).isEqualTo(views.length);
+    for (int i = 0; i < views.length; i++) {
+      assertThat(parent.getChildAt(i))
+          .describedAs("View at " + i)
+          .isEqualTo(views[i]);
+    }
+  }
+
+  /**
+   * Holder for the tags that represent that represent views in the following hierarchy:
+   *  - View rootView
+   *    - View view0
+   *    - View viewWithChildren1
+   *      - View childView0
+   *      - View childView1
+   *    - View view2
+   *    - View view3
+   *
+   * This hierarchy is used to test move/delete functionality in manageChildren.
+   */
+  private static class TestMoveDeleteHierarchy {
+
+    public ReactRootView nativeRootView;
+    public int rootView;
+    public int view0;
+    public int viewWithChildren1;
+    public int view2;
+    public int view3;
+    public int childView0;
+    public int childView1;
+
+    public TestMoveDeleteHierarchy(ReactRootView nativeRootView, int rootViewTag) {
+      this.nativeRootView = nativeRootView;
+      rootView = rootViewTag;
+      view0 = rootView + 1;
+      viewWithChildren1 = rootView + 2;
+      view2 = rootView + 3;
+      view3 = rootView + 4;
+      childView0 = rootView + 5;
+      childView1 = rootView + 6;
+    }
+  }
+
+  private void executePendingChoreographerCallbacks() {
+    ArrayList<Choreographer.FrameCallback> callbacks =
+        new ArrayList<>(mPendingChoreographerCallbacks);
+    mPendingChoreographerCallbacks.clear();
+    for (Choreographer.FrameCallback frameCallback : callbacks) {
+      frameCallback.doFrame(0);
+    }
+  }
+
+  private UIManagerModule getUIManagerModule() {
+    List<ViewManager> viewManagers = Arrays.<ViewManager>asList(
+        new ReactViewManager(),
+        new ReactTextViewManager(),
+        new ReactRawTextManager());
+    UIManagerModule uiManagerModule =  new UIManagerModule(
+        mReactContext,
+        viewManagers,
+        new UIImplementation(mReactContext, viewManagers));
+    uiManagerModule.onHostResume();
+    return uiManagerModule;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/BUCK b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK
new file mode 100644
index 000000000000..5834b9374d1b
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/BUCK
@@ -0,0 +1,39 @@
+include_defs('//ReactAndroid/DEFS')
+
+robolectric3_test(
+  name = 'views',
+  # Please change the contact to the oncall of your team
+  contacts = ['oncall+fbandroid_sheriff@xmail.facebook.com'],
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_target('java/com/facebook/csslayout:csslayout'),
+    react_native_target('java/com/facebook/react:react'),
+    react_native_target('java/com/facebook/react/bridge:bridge'),
+    react_native_target('java/com/facebook/react/common:common'),
+    react_native_target('java/com/facebook/react/touch:touch'),
+    react_native_target('java/com/facebook/react/uimanager:uimanager'),
+    react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
+    react_native_target('java/com/facebook/react/views/image:image'),
+    react_native_target('java/com/facebook/react/views/text:text'),
+    react_native_target('java/com/facebook/react/views/textinput:textinput'),
+    react_native_target('java/com/facebook/react/views/view:view'),
+
+    react_native_tests_target('java/com/facebook/react/bridge:testhelpers'),
+    react_native_dep('third-party/java/robolectric3/robolectric:robolectric'),
+    react_native_dep('third-party/java/fest:fest'),
+    react_native_dep('third-party/java/junit:junit'),
+    react_native_dep('third-party/java/okio:okio'),
+    react_native_dep('third-party/java/mockito:mockito'),
+    react_native_dep('third-party/java/okhttp:okhttp'),
+    react_native_dep('third-party/java/jsr-305:jsr-305'),
+
+    react_native_dep('libraries/fbcore/src/test/java/com/facebook/powermock:powermock'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
+    react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
+    react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
+  ],
+)
+
+project_config(
+  test_target = ':views',
+)
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java
new file mode 100644
index 000000000000..f74b4528e7ec
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java
@@ -0,0 +1,49 @@
+/**
+ * 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.views.image;
+
+import com.facebook.drawee.drawable.ScalingUtils;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ImageResizeModeTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Test
+  public void testImageResizeMode() {
+    assertThat(ImageResizeMode.toScaleType(null))
+        .isEqualTo(ScalingUtils.ScaleType.CENTER_CROP);
+
+    assertThat(ImageResizeMode.toScaleType("contain"))
+        .isEqualTo(ScalingUtils.ScaleType.CENTER_INSIDE);
+
+    assertThat(ImageResizeMode.toScaleType("cover"))
+        .isEqualTo(ScalingUtils.ScaleType.CENTER_CROP);
+
+    assertThat(ImageResizeMode.toScaleType("stretch"))
+        .isEqualTo(ScalingUtils.ScaleType.FIT_XY);
+
+    // No resizeMode set
+    assertThat(ImageResizeMode.defaultValue())
+        .isEqualTo(ScalingUtils.ScaleType.CENTER_CROP);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java
new file mode 100644
index 000000000000..186e4dca4fe9
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java
@@ -0,0 +1,134 @@
+/**
+ * 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.views.image;
+
+import android.graphics.Color;
+import android.util.DisplayMetrics;
+
+import com.facebook.drawee.backends.pipeline.Fresco;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.uimanager.ReactStylesDiffMap;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.ThemedReactContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Robolectric;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Verify that {@link ScalingUtils} properties are being applied correctly
+ * by {@link ReactImageManager}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactImagePropertyTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ReactApplicationContext mContext;
+  private CatalystInstance mCatalystInstanceMock;
+  private ThemedReactContext mThemeContext;
+
+  @Before
+  public void setup() {
+    mContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
+    mContext.initializeWithInstance(mCatalystInstanceMock);
+    mThemeContext = new ThemedReactContext(mContext, mContext);
+    Fresco.initialize(mContext);
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
+  }
+
+  @After
+  public void teardown() {
+    DisplayMetricsHolder.setWindowDisplayMetrics(null);
+  }
+
+  public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
+    return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues));
+  }
+
+  @Test(expected=JSApplicationIllegalArgumentException.class)
+  public void testImageInvalidResizeMode() {
+    ReactImageManager viewManager = new ReactImageManager();
+    ReactImageView view = viewManager.createViewInstance(mThemeContext);
+    viewManager.updateProperties(view, buildStyles("resizeMode", "pancakes"));
+  }
+
+  @Test
+  public void testBorderColor() {
+    ReactImageManager viewManager = new ReactImageManager();
+    ReactImageView view = viewManager.createViewInstance(mThemeContext);
+    viewManager.updateProperties(view, buildStyles("src", "http://mysite.com/mypic.jpg"));
+
+    viewManager.updateProperties(view, buildStyles("borderColor", Color.argb(0, 0, 255, 255)));
+    int borderColor = view.getHierarchy().getRoundingParams().getBorderColor();
+    assertEquals(0, Color.alpha(borderColor));
+    assertEquals(0, Color.red(borderColor));
+    assertEquals(255, Color.green(borderColor));
+    assertEquals(255, Color.blue(borderColor));
+
+    viewManager.updateProperties(view, buildStyles("borderColor", Color.argb(0, 255, 50, 128)));
+    borderColor = view.getHierarchy().getRoundingParams().getBorderColor();
+    assertEquals(0, Color.alpha(borderColor));
+    assertEquals(255, Color.red(borderColor));
+    assertEquals(50, Color.green(borderColor));
+    assertEquals(128, Color.blue(borderColor));
+
+    viewManager.updateProperties(view, buildStyles("borderColor", null));
+    borderColor = view.getHierarchy().getRoundingParams().getBorderColor();
+    assertEquals(0, Color.alpha(borderColor));
+    assertEquals(0, Color.red(borderColor));
+    assertEquals(0, Color.green(borderColor));
+    assertEquals(0, Color.blue(borderColor));
+  }
+
+  @Test
+  public void testRoundedCorners() {
+    ReactImageManager viewManager = new ReactImageManager();
+    ReactImageView view = viewManager.createViewInstance(mThemeContext);
+    viewManager.updateProperties(view, buildStyles("src", "http://mysite.com/mypic.jpg"));
+
+    // We can't easily verify if rounded corner was honored or not, this tests simply verifies
+    // we're not crashing..
+    viewManager.updateProperties(view, buildStyles("borderRadius", (double) 10));
+    viewManager.updateProperties(view, buildStyles("borderRadius", (double) 0));
+    viewManager.updateProperties(view, buildStyles("borderRadius", null));
+  }
+
+  @Test
+  public void testTintColor() {
+    ReactImageManager viewManager = new ReactImageManager();
+    ReactImageView view = viewManager.createViewInstance(mThemeContext);
+    assertNull(view.getColorFilter());
+    viewManager.updateProperties(view, buildStyles("tintColor", Color.argb(50, 0, 0, 255)));
+            // Can't actually assert the specific color so this is the next best thing.
+            // Does the color filter now exist?
+            assertNotNull(view.getColorFilter());
+    viewManager.updateProperties(view, buildStyles("tintColor", null));
+    assertNull(view.getColorFilter());
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
new file mode 100644
index 000000000000..68734c4a502a
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
@@ -0,0 +1,390 @@
+/**
+ * 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.views.text;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import android.annotation.TargetApi;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+import android.widget.TextView;
+
+import com.facebook.react.ReactRootView;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.ReactChoreographer;
+import com.facebook.react.uimanager.UIImplementation;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.react.uimanager.ViewProps;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link UIManagerModule} specifically for React Text/RawText.
+ */
+@PrepareForTest({Arguments.class, ReactChoreographer.class})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactTextTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
+
+  @Before
+  public void setUp() {
+    PowerMockito.mockStatic(Arguments.class, ReactChoreographer.class);
+
+    ReactChoreographer choreographerMock = mock(ReactChoreographer.class);
+    PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyMap();
+      }
+    });
+    PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(choreographerMock);
+
+    mPendingChoreographerCallbacks = new ArrayList<>();
+    doAnswer(new Answer() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        mPendingChoreographerCallbacks
+            .add((Choreographer.FrameCallback) invocation.getArguments()[1]);
+        return null;
+      }
+    }).when(choreographerMock).postFrameCallback(
+        any(ReactChoreographer.CallbackType.class),
+        any(Choreographer.FrameCallback.class));
+  }
+
+  @Test
+  public void testFontSizeApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_SIZE, 21.0),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    AbsoluteSizeSpan sizeSpan = getSingleSpan(
+        (TextView) rootView.getChildAt(0), AbsoluteSizeSpan.class);
+    assertThat(sizeSpan.getSize()).isEqualTo(21);
+  }
+
+  @Test
+  public void testBoldFontApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_WEIGHT, "bold"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView)rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isNotZero();
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isZero();
+  }
+
+  @Test
+  public void testNumericBoldFontApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_WEIGHT, "500"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isNotZero();
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isZero();
+  }
+
+  @Test
+  public void testItalicFontApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_STYLE, "italic"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isNotZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isZero();
+  }
+
+  @Test
+  public void testBoldItalicFontApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_WEIGHT, "bold", ViewProps.FONT_STYLE, "italic"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isNotZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isNotZero();
+  }
+
+  @Test
+  public void testNormalFontWeightApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_WEIGHT, "normal"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isZero();
+  }
+
+  @Test
+  public void testNumericNormalFontWeightApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_WEIGHT, "200"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isZero();
+  }
+
+  @Test
+  public void testNormalFontStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_STYLE, "normal"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isZero();
+  }
+
+  @Test
+  public void testFontFamilyStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_FAMILY, "sans-serif"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getFontFamily()).isEqualTo("sans-serif");
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isZero();
+  }
+
+  @Test
+  public void testFontFamilyBoldStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_FAMILY, "sans-serif", ViewProps.FONT_WEIGHT, "bold"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getFontFamily()).isEqualTo("sans-serif");
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isNotZero();
+  }
+
+  @Test
+  public void testFontFamilyItalicStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.FONT_FAMILY, "sans-serif", ViewProps.FONT_STYLE, "italic"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getFontFamily()).isEqualTo("sans-serif");
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isNotZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isZero();
+  }
+
+  @Test
+  public void testFontFamilyBoldItalicStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(
+            ViewProps.FONT_FAMILY, "sans-serif",
+            ViewProps.FONT_WEIGHT, "500",
+            ViewProps.FONT_STYLE, "italic"),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    CustomStyleSpan customStyleSpan =
+        getSingleSpan((TextView) rootView.getChildAt(0), CustomStyleSpan.class);
+    assertThat(customStyleSpan.getFontFamily()).isEqualTo("sans-serif");
+    assertThat(customStyleSpan.getStyle() & Typeface.ITALIC).isNotZero();
+    assertThat(customStyleSpan.getWeight() & Typeface.BOLD).isNotZero();
+  }
+
+  @Test
+  public void testBackgroundColorStyleApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.BACKGROUND_COLOR, Color.BLUE),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    Drawable backgroundDrawable = ((TextView) rootView.getChildAt(0)).getBackground();
+    assertThat(((ColorDrawable) backgroundDrawable).getColor()).isEqualTo(Color.BLUE);
+  }
+
+  // JELLY_BEAN is needed for TextView#getMaxLines(), which is OK, because in the actual code we
+  // only use TextView#setMaxLines() which exists since API Level 1.
+  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+  @Test
+  public void testMaxLinesApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = createText(
+        uiManager,
+        JavaOnlyMap.of(ViewProps.NUMBER_OF_LINES, 2),
+        JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "test text"));
+
+    TextView textView = (TextView) rootView.getChildAt(0);
+    assertThat(textView.getText().toString()).isEqualTo("test text");
+    assertThat(textView.getMaxLines()).isEqualTo(2);
+    assertThat(textView.getEllipsize()).isEqualTo(TextUtils.TruncateAt.END);
+  }
+
+  /**
+   * Make sure TextView has exactly one span and that span has given type.
+   */
+  private static <TSPAN> TSPAN getSingleSpan(TextView textView, Class<TSPAN> spanClass) {
+    Spanned text = (Spanned) textView.getText();
+    TSPAN[] spans = text.getSpans(0, text.length(), spanClass);
+    assertThat(spans).hasSize(1);
+    return spans[0];
+  }
+
+  private ReactRootView createText(
+      UIManagerModule uiManager,
+      JavaOnlyMap textProps,
+      JavaOnlyMap rawTextProps) {
+    ReactRootView rootView = new ReactRootView(RuntimeEnvironment.application);
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+    int textTag = rootTag + 1;
+    int rawTextTag = textTag + 1;
+
+    uiManager.createView(
+        textTag,
+        ReactTextViewManager.REACT_CLASS,
+        rootTag,
+        textProps);
+    uiManager.createView(
+        rawTextTag,
+        ReactRawTextManager.REACT_CLASS,
+        rootTag,
+        rawTextProps);
+
+    uiManager.manageChildren(
+        textTag,
+        null,
+        null,
+        JavaOnlyArray.of(rawTextTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.manageChildren(
+        rootTag,
+        null,
+        null,
+        JavaOnlyArray.of(textTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+    return rootView;
+  }
+
+  private void executePendingChoreographerCallbacks() {
+    ArrayList<Choreographer.FrameCallback> callbacks =
+        new ArrayList<>(mPendingChoreographerCallbacks);
+    mPendingChoreographerCallbacks.clear();
+    for (Choreographer.FrameCallback frameCallback : callbacks) {
+      frameCallback.doFrame(0);
+    }
+  }
+
+  public UIManagerModule getUIManagerModule() {
+    ReactApplicationContext reactContext = ReactTestHelper.createCatalystContextForTest();
+    DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
+    List<ViewManager> viewManagers = Arrays.asList(
+        new ViewManager[] {
+            new ReactTextViewManager(),
+            new ReactRawTextManager(),
+        });
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        reactContext,
+        viewManagers,
+        new UIImplementation(reactContext, viewManagers));
+    uiManagerModule.onHostResume();
+    return uiManagerModule;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
new file mode 100644
index 000000000000..5be0d0a79949
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
@@ -0,0 +1,314 @@
+/**
+ * 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.views.textinput;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.text.InputType;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.widget.EditText;
+
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.JSApplicationCausedNativeException;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.uimanager.ReactStylesDiffMap;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.views.text.DefaultStyleValuesUtil;
+import com.facebook.react.uimanager.ThemedReactContext;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+/**
+ * Verify {@link EditText} view property being applied properly by {@link ReactTextInputManager}
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ReactTextInputPropertyTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ReactApplicationContext mContext;
+  private CatalystInstance mCatalystInstanceMock;
+  private ThemedReactContext mThemedContext;
+  private ReactTextInputManager mManager;
+
+  @Before
+  public void setup() {
+    mContext = new ReactApplicationContext(RuntimeEnvironment.application);
+    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
+    mContext.initializeWithInstance(mCatalystInstanceMock);
+    mThemedContext = new ThemedReactContext(mContext, mContext);
+    mManager = new ReactTextInputManager();
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
+  }
+
+  public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
+    return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues));
+  }
+
+  @Test
+  public void testAutoCorrect() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero();
+
+    mManager.updateProperties(view, buildStyles("autoCorrect", true));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero();
+
+    mManager.updateProperties(view, buildStyles("autoCorrect", false));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("autoCorrect", null));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero();
+  }
+
+  @Test
+  public void testAutoCapitalize() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero();
+
+    mManager.updateProperties(
+        view,
+        buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero();
+
+    mManager.updateProperties(
+        view,
+        buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_WORDS));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_WORDS).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero();
+
+    mManager.updateProperties(
+        view,
+        buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotZero();
+
+    mManager.updateProperties(
+        view,
+        buildStyles("autoCapitalize", InputType.TYPE_CLASS_TEXT));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero();
+  }
+
+  @Test
+  public void testPlaceholder() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getHint()).isNull();
+
+    mManager.updateProperties(view, buildStyles("placeholder", "sometext"));
+    assertThat(view.getHint()).isEqualTo("sometext");
+
+    mManager.updateProperties(view, buildStyles("placeholder", null));
+    assertThat(view.getHint()).isNull();
+  }
+
+  @Test
+  public void testEditable() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.isEnabled()).isTrue();
+
+    mManager.updateProperties(view, buildStyles("editable", false));
+    assertThat(view.isEnabled()).isFalse();
+
+    mManager.updateProperties(view, buildStyles("editable", null));
+    assertThat(view.isEnabled()).isTrue();
+
+    mManager.updateProperties(view, buildStyles("editable", false));
+    assertThat(view.isEnabled()).isFalse();
+
+    mManager.updateProperties(view, buildStyles("editable", true));
+    assertThat(view.isEnabled()).isTrue();
+  }
+
+  @Test
+  public void testPlaceholderTextColor() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    final ColorStateList defaultPlaceholderColorStateList =
+        DefaultStyleValuesUtil.getDefaultTextColorHint(
+            view.getContext());
+
+    ColorStateList colors = view.getHintTextColors();
+    assertThat(colors).isEqualTo(defaultPlaceholderColorStateList);
+
+    mManager.updateProperties(view, buildStyles("placeholderTextColor", null));
+    colors = view.getHintTextColors();
+    assertThat(colors).isEqualTo(defaultPlaceholderColorStateList);
+
+    mManager.updateProperties(view, buildStyles("placeholderTextColor", Color.RED));
+    colors = view.getHintTextColors();
+    assertThat(colors.getDefaultColor()).isEqualTo(Color.RED);
+
+    mManager.updateProperties(view, buildStyles("placeholderTextColor", null));
+    colors = view.getHintTextColors();
+    assertThat(colors).isEqualTo(defaultPlaceholderColorStateList);
+  }
+
+  @Test
+  public void testMultiline() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero();
+
+    mManager.updateProperties(view, buildStyles("multiline", false));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero();
+
+    mManager.updateProperties(view, buildStyles("multiline", true));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("multiline", null));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero();
+  }
+
+  @Test
+  public void testNumLines() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getMinLines()).isEqualTo(1);
+
+    mManager.updateProperties(view, buildStyles("numberOfLines", 5));
+    assertThat(view.getMinLines()).isEqualTo(5);
+
+    mManager.updateProperties(view, buildStyles("numberOfLines", 4));
+    assertThat(view.getMinLines()).isEqualTo(4);
+  }
+
+  @Test
+  public void testKeyboardType() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+
+    mManager.updateProperties(view, buildStyles("keyboardType", "text"));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+
+    mManager.updateProperties(view, buildStyles("keyboardType", "numeric"));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("keyboardType", "email-address"));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("keyboardType", null));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+  }
+
+  @Test
+  public void testPasswordInput() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero();
+
+    mManager.updateProperties(view, buildStyles("password", false));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero();
+
+    mManager.updateProperties(view, buildStyles("password", true));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("password", null));
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero();
+  }
+
+  @Test
+  public void testIncrementalInputTypeUpdates() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+
+    mManager.updateProperties(view, buildStyles());
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero();
+
+    mManager.updateProperties(view, buildStyles("multiline", true));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero();
+
+    mManager.updateProperties(view, buildStyles("autoCorrect", false));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("keyboardType", "NUMERIC"));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero();
+
+    mManager.updateProperties(view, buildStyles("multiline", null));
+    assertThat(view.getInputType() & InputType.TYPE_CLASS_NUMBER).isNotZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero();
+    assertThat(view.getInputType() & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero();
+  }
+
+  @Test
+  public void testTextAlign() {
+    ReactEditText view = mManager.createViewInstance(mThemedContext);
+    int gravity = view.getGravity();
+    assertThat(view.getGravity() & Gravity.BOTTOM).isNotEqualTo(Gravity.BOTTOM);
+
+    mManager.updateProperties(view, buildStyles("textAlignVertical", "bottom"));
+    assertThat(view.getGravity() & Gravity.BOTTOM).isEqualTo(Gravity.BOTTOM);
+
+    mManager.updateProperties(
+        view,
+        buildStyles("textAlign", "right", "textAlignVertical", "top"));
+    assertThat(view.getGravity() & Gravity.BOTTOM).isNotEqualTo(Gravity.BOTTOM);
+    assertThat(view.getGravity() & (Gravity.RIGHT | Gravity.TOP))
+        .isEqualTo(Gravity.RIGHT | Gravity.TOP);
+
+    mManager.updateProperties(
+        view,
+        buildStyles("textAlignVertical", null));
+    assertThat(view.getGravity() & Gravity.RIGHT).isEqualTo(Gravity.RIGHT);
+    assertThat(view.getGravity() & Gravity.TOP).isNotEqualTo(Gravity.TOP);
+
+    mManager.updateProperties(view, buildStyles("textAlign", null));
+    assertThat(view.getGravity()).isEqualTo(gravity);
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java
new file mode 100644
index 000000000000..630c80a723a9
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java
@@ -0,0 +1,195 @@
+/**
+ * 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.views.textinput;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import android.util.DisplayMetrics;
+import android.view.Choreographer;
+import android.widget.EditText;
+
+import com.facebook.react.ReactRootView;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.JavaOnlyArray;
+import com.facebook.react.bridge.JavaOnlyMap;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactTestHelper;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.ReactChoreographer;
+import com.facebook.react.uimanager.UIImplementation;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.react.uimanager.ViewProps;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for TextInput.
+ */
+@PrepareForTest({Arguments.class, ReactChoreographer.class})
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class TextInputTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
+
+  @Before
+  public void setUp() {
+    PowerMockito.mockStatic(Arguments.class, ReactChoreographer.class);
+
+    ReactChoreographer choreographerMock = mock(ReactChoreographer.class);
+    PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        return new JavaOnlyMap();
+      }
+    });
+    PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(choreographerMock);
+
+    mPendingChoreographerCallbacks = new ArrayList<>();
+    doAnswer(new Answer() {
+      @Override
+      public Object answer(InvocationOnMock invocation) throws Throwable {
+        mPendingChoreographerCallbacks
+            .add((Choreographer.FrameCallback) invocation.getArguments()[1]);
+        return null;
+      }
+    }).when(choreographerMock).postFrameCallback(
+        any(ReactChoreographer.CallbackType.class),
+        any(Choreographer.FrameCallback.class));
+  }
+
+  @Test
+  public void testPropsApplied() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = new ReactRootView(RuntimeEnvironment.application);
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+    int textInputTag = rootTag + 1;
+    final String hintStr = "placeholder text";
+
+    uiManager.createView(
+        textInputTag,
+        ReactTextInputManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of(
+            ViewProps.FONT_SIZE, 13.37, ViewProps.HEIGHT, 20.0, "placeholder", hintStr));
+
+    uiManager.manageChildren(
+        rootTag,
+        null,
+        null,
+        JavaOnlyArray.of(textInputTag),
+        JavaOnlyArray.of(0),
+        null);
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    EditText editText = (EditText) rootView.getChildAt(0);
+    assertThat(editText.getHint()).isEqualTo(hintStr);
+    assertThat(editText.getTextSize()).isEqualTo((float) Math.ceil(13.37));
+    assertThat(editText.getHeight()).isEqualTo(20);
+  }
+
+  @Test
+  public void testPropsUpdate() {
+    UIManagerModule uiManager = getUIManagerModule();
+
+    ReactRootView rootView = new ReactRootView(RuntimeEnvironment.application);
+    int rootTag = uiManager.addMeasuredRootView(rootView);
+    int textInputTag = rootTag + 1;
+    final String hintStr = "placeholder text";
+
+    uiManager.createView(
+        textInputTag,
+        ReactTextInputManager.REACT_CLASS,
+        rootTag,
+        JavaOnlyMap.of(
+            ViewProps.FONT_SIZE, 13.37, ViewProps.HEIGHT, 20.0, "placeholder", hintStr));
+
+    uiManager.manageChildren(
+        rootTag,
+        null,
+        null,
+        JavaOnlyArray.of(textInputTag),
+        JavaOnlyArray.of(0),
+        null);
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    EditText editText = (EditText) rootView.getChildAt(0);
+    assertThat(editText.getHint()).isEqualTo(hintStr);
+    assertThat(editText.getTextSize()).isEqualTo((float) Math.ceil(13.37));
+    assertThat(editText.getHeight()).isEqualTo(20);
+
+    final String hintStr2 = "such hint";
+    uiManager.updateView(
+        textInputTag,
+        ReactTextInputManager.REACT_CLASS,
+        JavaOnlyMap.of(
+            ViewProps.FONT_SIZE, 26.74, ViewProps.HEIGHT, 40.0, "placeholder", hintStr2));
+
+    uiManager.onBatchComplete();
+    executePendingChoreographerCallbacks();
+
+    EditText updatedEditText = (EditText) rootView.getChildAt(0);
+    assertThat(updatedEditText.getHint()).isEqualTo(hintStr2);
+    assertThat(updatedEditText.getTextSize()).isEqualTo((float) Math.ceil(26.74f));
+    assertThat(updatedEditText.getHeight()).isEqualTo(40);
+  }
+
+  private void executePendingChoreographerCallbacks() {
+    ArrayList<Choreographer.FrameCallback> callbacks =
+        new ArrayList<>(mPendingChoreographerCallbacks);
+    mPendingChoreographerCallbacks.clear();
+    for (Choreographer.FrameCallback frameCallback : callbacks) {
+      frameCallback.doFrame(0);
+    }
+  }
+
+  public UIManagerModule getUIManagerModule() {
+    ReactApplicationContext reactContext = ReactTestHelper.createCatalystContextForTest();
+    List<ViewManager> viewManagers = Arrays.asList(
+        new ViewManager[] {
+            new ReactTextInputManager(),
+        });
+    DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
+    UIManagerModule uiManagerModule = new UIManagerModule(
+        reactContext,
+        viewManagers,
+        new UIImplementation(reactContext, viewManagers));
+    uiManagerModule.onHostResume();
+    return uiManagerModule;
+  }
+}
diff --git a/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java
new file mode 100644
index 000000000000..9b79b2594f0a
--- /dev/null
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/view/ColorUtilTest.java
@@ -0,0 +1,53 @@
+/**
+ * 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.views.view;
+
+import android.graphics.PixelFormat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.modules.junit4.rule.PowerMockRule;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * Based on Fresco's DrawableUtilsTest (https://github.com/facebook/fresco).
+ */
+@RunWith(RobolectricTestRunner.class)
+@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
+public class ColorUtilTest {
+
+  @Rule
+  public PowerMockRule rule = new PowerMockRule();
+
+  @Test
+  public void testMultiplyColorAlpha() {
+    assertEquals(0x00123456, ColorUtil.multiplyColorAlpha(0xC0123456, 0));
+    assertEquals(0x07123456, ColorUtil.multiplyColorAlpha(0xC0123456, 10));
+    assertEquals(0x96123456, ColorUtil.multiplyColorAlpha(0xC0123456, 200));
+    assertEquals(0xC0123456, ColorUtil.multiplyColorAlpha(0xC0123456, 255));
+  }
+
+  @Test
+  public void testGetOpacityFromColor() {
+    assertEquals(PixelFormat.TRANSPARENT, ColorUtil.getOpacityFromColor(0x00000000));
+    assertEquals(PixelFormat.TRANSPARENT, ColorUtil.getOpacityFromColor(0x00123456));
+    assertEquals(PixelFormat.TRANSPARENT, ColorUtil.getOpacityFromColor(0x00FFFFFF));
+    assertEquals(PixelFormat.TRANSLUCENT, ColorUtil.getOpacityFromColor(0xC0000000));
+    assertEquals(PixelFormat.TRANSLUCENT, ColorUtil.getOpacityFromColor(0xC0123456));
+    assertEquals(PixelFormat.TRANSLUCENT, ColorUtil.getOpacityFromColor(0xC0FFFFFF));
+    assertEquals(PixelFormat.OPAQUE, ColorUtil.getOpacityFromColor(0xFF000000));
+    assertEquals(PixelFormat.OPAQUE, ColorUtil.getOpacityFromColor(0xFF123456));
+    assertEquals(PixelFormat.OPAQUE, ColorUtil.getOpacityFromColor(0xFFFFFFFF));
+  }
+}
diff --git a/ReactAndroid/src/test/java/org/mockito/configuration/BUCK b/ReactAndroid/src/test/java/org/mockito/configuration/BUCK
new file mode 100644
index 000000000000..f4d1366df610
--- /dev/null
+++ b/ReactAndroid/src/test/java/org/mockito/configuration/BUCK
@@ -0,0 +1,16 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+  name = 'configuration',
+  srcs = glob(['**/*.java']),
+  deps = [
+    react_native_dep('third-party/java/mockito:mockito'),
+  ],
+  visibility = [
+    'PUBLIC',
+  ],
+)
+
+project_config(
+  src_target = ':configuration',
+)
diff --git a/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java b/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java
new file mode 100644
index 000000000000..20f4aa035f43
--- /dev/null
+++ b/ReactAndroid/src/test/java/org/mockito/configuration/MockitoConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * 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 org.mockito.configuration;
+
+/**
+ * Disables the Mockito cache to prevent Mockito & Robolectric bugs.
+ *
+ * Mockito loads this with reflection, so this class might appear unused.
+ */
+@SuppressWarnings("unused")
+public class MockitoConfiguration extends DefaultMockitoConfiguration {
+
+  /* (non-Javadoc)
+   * @see org.mockito.configuration.IMockitoConfiguration#enableClassCache()
+   */
+  public boolean enableClassCache() {
+    return false;
+  }
+}
diff --git a/Releases.md b/Releases.md
new file mode 100644
index 000000000000..649b5f177e60
--- /dev/null
+++ b/Releases.md
@@ -0,0 +1,130 @@
+The list of releases with notes can be found at:
+https://github.com/facebook/react-native/releases
+
+Release schedule:
+
+- **0.21 branch cut**, 0.21.0-rc - **week of Feb 15**
+- 0.21.0 - Feb 29
+- **0.22 branch cut**, 0.22.0-rc - **week of Feb 29**
+- 0.22.0 - Mar 14
+- **0.23 branch cut**, 0.23.0-rc - **week of Mar 14**
+- 0.23.0 - Mar 28
+- **0.24 branch cut**, 0.23.0-rc - **week of Mar 28**
+- 0.24.0 - Apr 11
+- ...
+
+## One time setup
+
+Set up Sinopia: https://github.com/facebook/react-native/tree/master/react-native-cli
+
+## Cut a release branch
+
+To cut a release branch and check that everything works, you'll need Mac OS with the [Android dev environment set up](https://github.com/facebook/react-native/blob/master/ReactAndroid/README.md).
+
+Run:
+
+    cd react-native
+    ./scripts/release.sh version_you_are_releasing # e.g. ./scripts/release.sh 0.22
+
+#### Check that everything works
+
+Make absolutely sure a basic iOS and Android workflow works on the release branch you've just created, see the instructions printed by `release.sh`.
+  
+#### Push to github
+
+  - Check git history, the last commit should be "[0.22-rc] Bump version numbers" (with the correct version)
+  - `git push origin 0.version_you_are_releasing-stable  # e.g. git push origin 0.22-stable`
+
+## Make sure we have release notes
+
+Post that we're ready to release so a voluteer can write release notes:
+https://github.com/facebook/react-native/releases
+
+To go through all the commits that went into a release, one way is to use the GitHub compare view: https://github.com/facebook/react-native/compare/0.18-stable...0.19-stable
+
+## Do an RC release (e.g. 0.22.0-rc)
+
+IMPORTANT: `npm publish` will automatically set the latest tag. **When doing an RC release**, run `npm publish --tag next` - this way people need to opt in to get the RC release.
+
+## IMPORTANT: Track bug reports from the community during the following two weeks and make sure they get fixed
+
+A good way to do this is to create a github issue and post about it so people can report bugs. Examples: https://github.com/facebook/react-native/issues/6087, https://github.com/facebook/react-native/issues/5201
+
+We should only be tracking bugs with small and non-risky fixes. Don't pick new features into the release as this greatly increases the risk of something breaking. The main point of the RC is to let people to use it for two weeks and fix the most serious bugs.
+
+-------------------
+
+## Do a release (e.g. 0.22.0, 0.22.1)
+
+Roughly two weeks after the branch cut (see the release schedule above) it's time to promote the RC to a real realease.
+
+Make sure you know which bug fixes should definitely be cheery-picked, example: https://github.com/facebook/react-native/issues/6087
+
+We should only cherry-pick small and non-risky bug fixes. Don't pick new features into the release as this greatly increases the risk of something breaking. The main point of the RC is to let people to use it for two weeks and fix the most serious bugs.
+
+Do the following:
+
+**NOTE: Most of these steps are similar to what the script `release.sh` does. The script is used to cut the release branch only, can be made more generic to help with this step too.**
+
+```
+cd react-native
+git checkout master
+git pull
+git checkout 0.version_you_are_releasing-stable   # e.g. git checkout 0.22-stable
+git pull origin 0.version_you_are_releasing-stable  # e.g. git pull origin 0.22-stable
+# Cherry-pick those commits, test everything again using Sinopia
+git cherry-pick commitHash1
+# Create the 'android' folder to be published to npm.
+./gradlew :ReactAndroid:installArchives
+# Check that it's there: `ls android`
+...
+npm set registry http://localhost:4873
+sinopia
+# change versions in package.json and React.podspec
+npm publish
+cd /tmp
+react-native init TestAapp
+cd TestApp
+react-native run-ios
+# Check that you can Reload JS and the Chrome debugger works
+# Kill packager
+open ios/TestApp.xcodeproj
+# Click run
+# Check that you can Reload JS and the Chrome debugger works
+cd android && ./gradlew dependencies
+# Double check the react-native dep has the correct version
+cd ..
+react-native run-android
+# Check that you can Reload JS and the Chrome debugger works
+```
+
+If everything worked:
+
+```
+npm set registry https://registry.npmjs.org
+npm publish
+```
+
+Tag the release in Git:
+
+```
+git tag v-version_you_are_releasing  # e.g. git tag v0.22.0, git tag v0.22.1
+git push --tags
+```
+
+To update the [website](https://facebook.github.io/react-native), move the `latest` tag and push to the `0.x-stable` branch. CircleCI will build and deploy the latest docs to the website.
+
+```
+git tag -d latest
+git push origin :latest
+git tag latest
+git push origin version_you_are_releasing-stable --tags  # e.g git push origin 0.22-stable --tags
+```
+
+Once you see the version in the top left corner of the website has been updated:
+Move the release notes to the tag you've just created. We want single release notes per version, for example if there is v0.22.0-rc and later we release v0.22.0, the release notes should live on v0.22.0:
+https://github.com/facebook/react-native/tags
+
+Uncheck the box "This is a pre-release" and publish the notes.
+
+Tweet about it! :) ([example tweet](https://twitter.com/grabbou/status/701510554758856704))
diff --git a/babel-preset/README.md b/babel-preset/README.md
new file mode 100644
index 000000000000..eaab8477ea6d
--- /dev/null
+++ b/babel-preset/README.md
@@ -0,0 +1,31 @@
+# babel-preset-react-native
+
+Babel presets for React Native applications. React Native itself uses this Babel preset by default when transforming your app's source code.
+
+If you wish to use a custom Babel configuration by writing a `.babelrc` file in your project's root directory, you must specify all the plugins necessary to transform your code. React Native does not apply its default Babel configuration in this case. So, to make your life easier, you can use this preset to get the default configuration and then specify more plugins that run before it.
+
+## Usage
+
+As mentioned above, you only need to use this preset if you are writing a custom `.babelrc` file.
+
+### Installation
+
+Install `babel-preset-react-native` in your app:
+```sh
+npm i babel-preset-react-native --save-dev
+```
+
+### Configuring Babel
+
+Then, create a file called `.babelrc` in your project's root directory. The existence of this `.babelrc` file will tell React Native to use your custom Babel configuration instead of its own. Then load this preset:
+```
+{
+  "presets": ["react-native"]
+}
+```
+
+You can further customize your Babel configuration by specifying plugins and other options. See [Babel's `.babelrc` documentation](https://babeljs.io/docs/usage/babelrc/) to learn more.
+
+## Help and Support
+
+If you get stuck configuring Babel, please ask a question on Stack Overflow or find a consultant for help. If you discover a bug, please open up an issue.
diff --git a/babel-preset/configs/hmr.js b/babel-preset/configs/hmr.js
new file mode 100644
index 000000000000..e88e2c67d5be
--- /dev/null
+++ b/babel-preset/configs/hmr.js
@@ -0,0 +1,35 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var path = require('path');
+var resolvePlugins = require('../lib/resolvePlugins');
+
+var hmrTransform = 'react-transform-hmr/lib/index.js';
+var transformPath = require.resolve(hmrTransform);
+
+module.exports = function(options, filename) {
+  var transform = filename
+      ? path.relative(path.dirname(filename), transformPath) // packager can't handle absolute paths
+      : hmrTransform;
+  return {
+    plugins: resolvePlugins([
+      [
+        'react-transform',
+        {
+          transforms: [{
+            transform: transform,
+            imports: ['React'],
+            locals: ['module'],
+          }]
+        },
+      ]
+    ])
+  };
+}
diff --git a/babel-preset/configs/internal.js b/babel-preset/configs/internal.js
new file mode 100644
index 000000000000..53830b0d6fd2
--- /dev/null
+++ b/babel-preset/configs/internal.js
@@ -0,0 +1,16 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var resolvePlugins = require('../lib/resolvePlugins');
+
+module.exports = function(options) {
+  // For future internal pipeline usage
+  return null;
+}
diff --git a/babel-preset/configs/main.js b/babel-preset/configs/main.js
new file mode 100644
index 000000000000..3322812bd07c
--- /dev/null
+++ b/babel-preset/configs/main.js
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var resolvePlugins = require('../lib/resolvePlugins');
+
+module.exports = {
+  comments: false,
+  compact: true,
+  plugins: resolvePlugins([
+    'syntax-async-functions',
+    'syntax-class-properties',
+    'syntax-trailing-function-commas',
+    'transform-class-properties',
+    'transform-es2015-arrow-functions',
+    'transform-es2015-block-scoping',
+    'transform-es2015-classes',
+    'transform-es2015-computed-properties',
+    'transform-es2015-constants',
+    'transform-es2015-destructuring',
+    ['transform-es2015-modules-commonjs', { strict: false, allowTopLevelThis: true }],
+    'transform-es2015-parameters',
+    'transform-es2015-shorthand-properties',
+    'transform-es2015-spread',
+    'transform-es2015-template-literals',
+    'transform-flow-strip-types',
+    'transform-object-assign',
+    'transform-object-rest-spread',
+    'transform-react-display-name',
+    'transform-react-jsx',
+    'transform-regenerator',
+    ['transform-es2015-for-of', { loose: true }],
+    require('../transforms/transform-symbol-member'),
+  ]),
+  retainLines: true,
+  sourceMaps: false,
+};
diff --git a/packager/react-packager/src/Cache/__mocks__/index.js b/babel-preset/index.js
similarity index 69%
rename from packager/react-packager/src/Cache/__mocks__/index.js
rename to babel-preset/index.js
index 6f7632f66c69..a1e2956ed563 100644
--- a/packager/react-packager/src/Cache/__mocks__/index.js
+++ b/babel-preset/index.js
@@ -8,13 +8,4 @@
  */
 'use strict';
 
-class Cache {
-  get(filepath, field, cb) {
-    return cb(filepath);
-  }
-
-  invalidate(filepath) { }
-  end() { }
-}
-
-module.exports = Cache;
+module.exports = require('./configs/main');
diff --git a/babel-preset/lib/resolvePlugins.js b/babel-preset/lib/resolvePlugins.js
new file mode 100644
index 000000000000..742ea0a78a15
--- /dev/null
+++ b/babel-preset/lib/resolvePlugins.js
@@ -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.
+ */
+'use strict';
+
+/**
+ * Manually resolve all default Babel plugins.
+ * `babel.transform` will attempt to resolve all base plugins relative to
+ * the file it's compiling. This makes sure that we're using the plugins
+ * installed in the react-native package.
+ */
+function resolvePlugins(plugins) {
+  return plugins.map(function(plugin) {
+    // Normalise plugin to an array.
+    if (!Array.isArray(plugin)) {
+      plugin = [plugin];
+    }
+    // Only resolve the plugin if it's a string reference.
+    if (typeof plugin[0] === 'string') {
+      plugin[0] = require('babel-plugin-' + plugin[0]);
+      plugin[0] = plugin[0].__esModule ? plugin[0].default : plugin[0];
+    }
+    return plugin;
+  });
+}
+
+module.exports = resolvePlugins;
diff --git a/babel-preset/package.json b/babel-preset/package.json
new file mode 100644
index 000000000000..00d24728afdd
--- /dev/null
+++ b/babel-preset/package.json
@@ -0,0 +1,45 @@
+{
+  "name": "babel-preset-react-native",
+  "version": "1.5.0",
+  "description": "Babel preset for React Native applications",
+  "main": "index.js",
+  "repository": "https://github.com/facebook/react-native/tree/master/babel-preset",
+  "keywords": [
+    "babel",
+    "preset",
+    "react-native"
+  ],
+  "license" : "BSD-3-Clause",
+  "bugs": {
+    "url": "https://github.com/facebook/react-native/issues"
+  },
+  "homepage": "https://github.com/facebook/react-native/tree/master/babel-preset/README.md",
+  "dependencies": {
+    "babel-plugin-react-transform": "2.0.0-beta1",
+    "babel-plugin-syntax-async-functions": "^6.3.13",
+    "babel-plugin-syntax-class-properties": "^6.3.13",
+    "babel-plugin-syntax-flow": "^6.3.13",
+    "babel-plugin-syntax-jsx": "^6.3.13",
+    "babel-plugin-syntax-trailing-function-commas": "^6.3.13",
+    "babel-plugin-transform-class-properties": "^6.4.0",
+    "babel-plugin-transform-es2015-arrow-functions": "^6.4.0",
+    "babel-plugin-transform-es2015-block-scoping": "^6.4.0",
+    "babel-plugin-transform-es2015-classes": "^6.4.0",
+    "babel-plugin-transform-es2015-computed-properties": "^6.4.0",
+    "babel-plugin-transform-es2015-constants": "^6.1.4",
+    "babel-plugin-transform-es2015-destructuring": "^6.4.0",
+    "babel-plugin-transform-es2015-for-of": "^6.3.13",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.4.0",
+    "babel-plugin-transform-es2015-parameters": "^6.4.2",
+    "babel-plugin-transform-es2015-shorthand-properties": "^6.3.13",
+    "babel-plugin-transform-es2015-spread": "^6.4.0",
+    "babel-plugin-transform-es2015-template-literals": "^6.3.13",
+    "babel-plugin-transform-flow-strip-types": "^6.4.0",
+    "babel-plugin-transform-object-assign": "^6.3.13",
+    "babel-plugin-transform-object-rest-spread": "^6.3.13",
+    "babel-plugin-transform-react-display-name": "^6.4.0",
+    "babel-plugin-transform-react-jsx": "^6.4.0",
+    "babel-plugin-transform-regenerator": "^6.3.26",
+    "react-transform-hmr": "^1.0.2"
+  }
+}
diff --git a/babel-preset/plugins.js b/babel-preset/plugins.js
new file mode 100644
index 000000000000..b24a31a3b081
--- /dev/null
+++ b/babel-preset/plugins.js
@@ -0,0 +1,35 @@
+/**
+ * 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.
+ */
+'use strict';
+
+module.exports = {
+  'babel-plugin-react-transform': require('babel-plugin-react-transform'),
+  'babel-plugin-syntax-async-functions': require('babel-plugin-syntax-async-functions'),
+  'babel-plugin-syntax-class-properties': require('babel-plugin-syntax-class-properties'),
+  'babel-plugin-syntax-trailing-function-commas': require('babel-plugin-syntax-trailing-function-commas'),
+  'babel-plugin-transform-class-properties': require('babel-plugin-transform-class-properties'),
+  'babel-plugin-transform-es2015-arrow-functions': require('babel-plugin-transform-es2015-arrow-functions'),
+  'babel-plugin-transform-es2015-block-scoping': require('babel-plugin-transform-es2015-block-scoping'),
+  'babel-plugin-transform-es2015-classes': require('babel-plugin-transform-es2015-classes'),
+  'babel-plugin-transform-es2015-computed-properties': require('babel-plugin-transform-es2015-computed-properties'),
+  'babel-plugin-transform-es2015-constants': require('babel-plugin-transform-es2015-constants'),
+  'babel-plugin-transform-es2015-destructuring': require('babel-plugin-transform-es2015-destructuring'),
+  'babel-plugin-transform-es2015-modules-commonjs': require('babel-plugin-transform-es2015-modules-commonjs'),
+  'babel-plugin-transform-es2015-parameters': require('babel-plugin-transform-es2015-parameters'),
+  'babel-plugin-transform-es2015-shorthand-properties': require('babel-plugin-transform-es2015-shorthand-properties'),
+  'babel-plugin-transform-es2015-spread': require('babel-plugin-transform-es2015-spread'),
+  'babel-plugin-transform-es2015-template-literals': require('babel-plugin-transform-es2015-template-literals'),
+  'babel-plugin-transform-flow-strip-types': require('babel-plugin-transform-flow-strip-types'),
+  'babel-plugin-transform-object-assign': require('babel-plugin-transform-object-assign'),
+  'babel-plugin-transform-object-rest-spread': require('babel-plugin-transform-object-rest-spread'),
+  'babel-plugin-transform-react-display-name': require('babel-plugin-transform-react-display-name'),
+  'babel-plugin-transform-react-jsx': require('babel-plugin-transform-react-jsx'),
+  'babel-plugin-transform-regenerator': require('babel-plugin-transform-regenerator'),
+  'babel-plugin-transform-es2015-for-of': require('babel-plugin-transform-es2015-for-of'),
+};
diff --git a/babel-preset/transforms/transform-symbol-member.js b/babel-preset/transforms/transform-symbol-member.js
new file mode 100644
index 000000000000..305eb3c2dbce
--- /dev/null
+++ b/babel-preset/transforms/transform-symbol-member.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+
+'use strict';
+
+/*eslint consistent-return: 0*/
+
+/**
+ * Transforms function properties of the `Symbol` into
+ * the presence check, and fallback string "@@<name>".
+ *
+ * Example:
+ *
+ *   Symbol.iterator;
+ *
+ * Transformed to:
+ *
+ *   typeof Symbol.iterator === 'function' ? Symbol.iterator : '@@iterator';
+ */
+module.exports = function symbolMember(babel) {
+  const t = babel.types;
+
+  return {
+    visitor: {
+      MemberExpression(path) {
+        let node = path.node;
+
+        if (!isAppropriateMember(node)) {
+          return;
+        }
+
+        path.replaceWith(
+          t.conditionalExpression(
+            t.binaryExpression(
+              '===',
+              t.unaryExpression(
+                'typeof',
+                t.identifier('Symbol'),
+                true
+              ),
+              t.stringLiteral('function')
+            ),
+            node,
+            t.stringLiteral(`@@${node.property.name}`)
+          )
+        );
+
+        // We should stop to avoid infinite recursion, since Babel
+        // traverses replaced path, and again would hit our transform.
+        path.stop();
+      },
+    },
+  };
+};
+
+function isAppropriateMember(node) {
+  return node.object.type === 'Identifier' &&
+    node.object.name === 'Symbol' &&
+    node.property.type === 'Identifier';
+}
diff --git a/bots/IssueCommands.txt b/bots/IssueCommands.txt
new file mode 100644
index 000000000000..3d2b2b103dc0
--- /dev/null
+++ b/bots/IssueCommands.txt
@@ -0,0 +1,29 @@
+React Native GitHub Issue Task Force: brentvatne, chirag04, christopherdro, corbt, dsibiski, grabbou, ide, jsierles, kmagiera, Kureev, lelandrichardson, satya164, skevy
+
+@facebook-github-bot answered
+comment Closing this issue as {author} says the question asked has been answered. Please help us by asking questions on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native). StackOverflow is amazing for Q&A: it has a reputation system, voting, the ability to mark a question as answered. Because of the reputation system it is likely the community will see and answer your question there. This also helps us use the GitHub bug tracker for bugs only.
+close
+
+@facebook-github-bot duplicate (#[0-9]+)
+comment {author} tells me this issue is a duplicate of {match0}. Let's discuss there, closing this one.
+close
+
+@facebook-github-bot expected
+comment The /expected comment above tells me this is expected behavior. Closing this as we'd like to use the GitHub issue tracker for bugs. If you'd like to change how this feature works please post a feature request on [Product Pains](https://productpains.com/product/react-native/) so that other people can vote on it.
+close
+
+@facebook-github-bot stack-overflow
+comment Hey {issue_author} and thanks for posting this! {author} tells me this issue looks like a question that would be best asked on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native). StackOverflow is amazing for Q&A: it has a reputation system, voting, the ability to mark a question as answered. Because of the reputation system it is likely the community will see and answer your question there. This also helps us use the GitHub bug tracker for bugs only. Will close this as this is really a question that should be asked on SO.
+add-label For Stack Overflow
+close
+
+@facebook-github-bot label (.*)
+add-label {match0}
+
+@facebook-github-bot no-reply
+comment Closing this issue as more information is needed to debug this and we haven't heard back from the author. Once there's more information we can reopen the issue.
+close
+
+@facebook-github-bot reopen
+comment Okay, reopening this issue.
+reopen
diff --git a/bots/IssueGuidelines.md b/bots/IssueGuidelines.md
new file mode 100644
index 000000000000..1453ebd0cccf
--- /dev/null
+++ b/bots/IssueGuidelines.md
@@ -0,0 +1,46 @@
+Use the following tips when responding to GitHub issues.
+
+### An issue is a duplicate of another issue
+Comment e.g. `@facebook-github-bot duplicate #123`. This will add a comment and close the issue.
+Example: [#5977](https://github.com/facebook/react-native/issues/5977)
+
+### An issue is a question
+StackOverflow is really good for Q&A. It has a reputation system and voting. Questions should absolutely be asked on StackOverflow rather than GitHub. However, to make this work we should hang out on StackOverflow every now and then and answer questions. A nice side effect is you'll get reputation for answering questions there rather than on GitHub.
+Comment `@facebook-github-bot stack-overflow` to close the issue.
+Examples: [#6015](https://github.com/facebook/react-native/issues/6015), [#6059](https://github.com/facebook/react-native/issues/6059), [#6062](https://github.com/facebook/react-native/issues/6062)
+
+### An issue is a question that's been answered
+Sometimes and issue has been resolved in the comments. Resolved issues should be closed.
+Comment `@facebook-github-bot answered` to close it.
+Example: [#6045](https://github.com/facebook/react-native/issues/6045)
+
+### An issue needs more information
+It is impossible to understand and reproduce the issue without more information, e.g. a short code sample, screenshot.
+Do the following:
+- Explain what additional info you need to understand the issue
+- Comment `@facebook-github-bot label Needs more information`
+Examples: [#6056](https://github.com/facebook/react-native/issues/6056), [#6008](https://github.com/facebook/react-native/issues/6008), [#5491](https://github.com/facebook/react-native/issues/5491)
+
+### An issue with label 'Needs more information' has been open for more than a week
+Comment mentioning the author asking if they plan to provide the additional information. If they don't come back close the issue using `@facebook-github-bot no-reply`.
+Example: [#6056](https://github.com/facebook/react-native/issues/6056)
+
+### An issue is a valid bug report
+Valid bug reports with good repro steps are some of the best issues! Thank the author for finding it, explain that React Native is a community project and ask them if they would be up for sending a fix.
+
+### An issue is a feature request and you're pretty sure React Native should have that feature
+Tell the author something like: "Pull requests are welcome. In case you're not up for sending a PR, you should post to [Product Pains](https://productpains.com/product/react-native/?tab=top). It has a voting system and if the feature gets upvoted enough it might get implemented."
+
+### An issue is a feature request for a feature we don't want
+This especially includes **new modules** Facebook doesn't use in production. Explain that those modules should be released to npm separately and that everyone will still be able to use the module super easily that way.
+
+### How to add a label
+Add any relevant labels, for example 'Android', 'iOS'.
+Comment e.g. `@facebook-github-bot label Android`
+
+### How to reopen a closed issue
+For example an issue was closed waiting for the author, the author replied and it turns out this is indeed a bug.
+Comment `@facebook-github-bot reopen`
+
+### What are all the available commands for the bot?
+When you mention the bot, it follows the commands defined in [IssueCommands.txt](https://github.com/facebook/react-native/blob/master/bots/IssueCommands.txt).
diff --git a/bots/NewIssueGreeting.md b/bots/NewIssueGreeting.md
index 71f3cad64664..c8c6aa23b2d4 100644
--- a/bots/NewIssueGreeting.md
+++ b/bots/NewIssueGreeting.md
@@ -1,7 +1,9 @@
 Hey {author}, thanks for reporting this issue!
 
-We've started using a new website called **[Product Pains](https://productpains.com/product/react-native/?tab=top)** to help the community **prioritize issues**.
+React Native, as you've probably heard, is getting really popular and truth is we're getting a bit overwhelmed by the activity surrounding it. There are just too many issues for us to manage properly.
 
-We have a limited number of people working on React Native; it's therefore important to know which issues should be fixed first. The reason Product Pains works better than GitHub is that people can **vote on issues**. If you want to help the community prioritize, go ahead and give [Product Pains](https://productpains.com/product/react-native/?tab=top) a try. You can link to this issue from Product Pains.
+- If you **don't know how to do something** or **something is not working as you expect but not sure it's a bug**, please ask on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native) with the tag `react-native` or for more real time interactions, ask on [Discord](https://discord.gg/0ZcbPKXt5bZjGY5n) in the #react-native channel.
 
-There are also other ways to get help. Chat with us on [Reactiflux](https://discord.gg/0ZcbPKXt5bWJVmUY) in #react-native or check out the [README](https://github.com/facebook/react-native#getting-help).
+- If this is a **feature request or a bug** that you would like to be fixed, please report it on [Product Pains](https://productpains.com/product/react-native/). It has a ranking feature that lets us focus on the most important issues the community is experiencing.
+
+- We welcome clear issues and PRs that are ready for in-depth discussion. Please provide **screenshots** where appropriate and always mention the **version** of React Native you're using. Thank you for your contributions!
diff --git a/bots/README.md b/bots/README.md
index 20311de2f500..8ee9530bef79 100644
--- a/bots/README.md
+++ b/bots/README.md
@@ -1 +1 @@
-These are the comments inserted by the Facebook github bot to new issues.
+Github bots and messages used by the Facebook Github bot.
diff --git a/bots/code-analysis-bot.js b/bots/code-analysis-bot.js
new file mode 100644
index 000000000000..8fabcfb1e429
--- /dev/null
+++ b/bots/code-analysis-bot.js
@@ -0,0 +1,242 @@
+/**
+ * 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.
+ */
+'use strict';
+
+if (!process.env.TRAVIS_REPO_SLUG) {
+  console.error('Missing TRAVIS_REPO_SLUG. Example: facebook/react-native');
+  process.exit(1);
+}
+if (!process.env.GITHUB_TOKEN) {
+  console.error('Missing GITHUB_TOKEN. Example: 5fd88b964fa214c4be2b144dc5af5d486a2f8c1e');
+  process.exit(1);
+}
+if (!process.env.TRAVIS_PULL_REQUEST) {
+  console.error('Missing TRAVIS_PULL_REQUEST. Example: 4687');
+  process.exit(1);
+}
+
+var GitHubApi = require('github');
+var path = require('path');
+
+var github = new GitHubApi({
+  version: '3.0.0',
+});
+
+github.authenticate({
+  type: 'oauth',
+  token: process.env.GITHUB_TOKEN,
+});
+
+function push(arr, key, value) {
+  if (!arr[key]) {
+    arr[key] = [];
+  }
+  arr[key].push(value);
+}
+
+/**
+ * There is unfortunately no standard format to report an error, so we have
+ * to write a specific converter for each tool we want to support.
+ *
+ * Those functions take a json object as input and fill the output with the
+ * following format:
+ *
+ * { [ path: string ]: Array< { message: string, line: number }> }
+ *
+ * This is an object where the keys are the path of the files and values
+ * is an array of objects of the shape message and line.
+ */
+var converters = {
+  raw: function(output, input) {
+    for (var key in input) {
+      input[key].forEach(function(message) {
+        push(output, key, message);
+      });
+    }
+  },
+
+  flow: function(output, input) {
+    if (!input || !input.errors) {
+      return;
+    }
+
+    input.errors.forEach(function(error) {
+      push(output, error.message[0].path, {
+        message: error.message.map(message => message.descr).join(' '),
+        line: error.message[0].line,
+      });
+    });
+  },
+
+  eslint: function(output, input) {
+    if (!input) {
+      return;
+    }
+
+    input.forEach(function(file) {
+      file.messages.forEach(function(message) {
+        push(output, file.filePath, {
+          message: message.ruleId + ': ' + message.message,
+          line: message.line,
+        });
+      });
+    });
+  }
+};
+
+function getShaFromPullRequest(user, repo, number, callback) {
+  github.pullRequests.get({user, repo, number}, (error, res) => {
+    if (error) {
+      console.log(error);
+      return;
+    }
+    callback(res.head.sha);
+  });
+}
+
+function getFilesFromCommit(user, repo, sha, callback) {
+  github.repos.getCommit({user, repo, sha}, (error, res) => {
+    if (error) {
+      console.log(error);
+      return;
+    }
+    callback(res.files);
+  });
+}
+
+
+/**
+ * Sadly we can't just give the line number to github, we have to give the
+ * line number relative to the patch file which is super annoying. This
+ * little function builds a map of line number in the file to line number
+ * in the patch file
+ */
+function getLineMapFromPatch(patchString) {
+  var diffLineIndex = 0;
+  var fileLineIndex = 0;
+  var lineMap = {};
+
+  patchString.split('\n').forEach((line) => {
+    if (line.match(/^@@/)) {
+      fileLineIndex = line.match(/\+([0-9]+)/)[1] - 1;
+      return;
+    }
+
+    diffLineIndex++;
+    if (line[0] !== '-') {
+      fileLineIndex++;
+      if (line[0] === '+') {
+        lineMap[fileLineIndex] = diffLineIndex;
+      }
+    }
+  });
+
+  return lineMap;
+}
+
+function sendComment(user, repo, number, sha, filename, lineMap, message) {
+  if (!lineMap[message.line]) {
+    // Do not send messages on lines that did not change
+    return;
+  }
+
+  var opts = {
+    user,
+    repo,
+    number,
+    sha,
+    path: filename,
+    commit_id: sha,
+    body: message.message,
+    position: lineMap[message.line],
+  };
+  github.pullRequests.createComment(opts, function(error, res) {
+    if (error) {
+      console.log(error);
+      return;
+    }
+  });
+  console.log('Sending comment', opts);
+}
+
+function main(messages, user, repo, number) {
+  // No message, we don't need to do anything :)
+  if (Object.keys(messages).length === 0) {
+    return;
+  }
+
+  getShaFromPullRequest(user, repo, number, (sha) => {
+    getFilesFromCommit(user, repo, sha, (files) => {
+      files
+        .filter((file) => messages[file.filename])
+        .forEach((file) => {
+          var lineMap = getLineMapFromPatch(file.patch);
+          messages[file.filename].forEach((message) => {
+            sendComment(user, repo, number, sha, file.filename, lineMap, message);
+          });
+        });
+    });
+  });
+}
+
+var content = '';
+process.stdin.resume();
+process.stdin.on('data', function(buf) { content += buf.toString(); });
+process.stdin.on('end', function() {
+  var messages = {};
+
+  // Since we send a few http requests to setup the process, we don't want
+  // to run this file one time per code analysis tool. Instead, we write all
+  // the results in the same stdin stream.
+  // The format of this stream is
+  //
+  //   name-of-the-converter
+  //   {"json":"payload"}
+  //   name-of-the-other-converter
+  //   {"other": ["json", "payload"]}
+  //
+  // In order to generate such stream, here is a sample bash command:
+  //
+  //   cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | node code-analysis-bot.js
+
+  var lines = content.trim().split('\n');
+  for (var i = 0; i < Math.ceil(lines.length / 2); ++i) {
+    var converter = converters[lines[i * 2]];
+    if (!converter) {
+      throw new Error('Unknown converter ' + lines[i * 2]);
+    }
+    var json;
+    try {
+      json = JSON.parse(lines[i * 2 + 1]);
+    } catch (e) {}
+
+    converter(messages, json);
+  }
+
+  // The paths are returned in absolute from code analysis tools but github works
+  // on paths relative from the root of the project. Doing the normalization here.
+  var pwd = path.resolve('.');
+  for (var absolutePath in messages) {
+    var relativePath = path.relative(pwd, absolutePath);
+    if (relativePath === absolutePath) {
+      continue;
+    }
+    messages[relativePath] = messages[absolutePath];
+    delete messages[absolutePath];
+  }
+
+  // TRAVIS_REPO_SLUG // 'facebook/react-native'
+  var user_repo = process.env.TRAVIS_REPO_SLUG.split('/');
+  var user = user_repo[0];
+  var repo = user_repo[1];
+  var number = process.env.TRAVIS_PULL_REQUEST;
+
+  // intentional lint warning to make sure that the bot is working :)
+  main(messages, user, repo, number)
+});
diff --git a/bots/question-bookmarklet.js b/bots/question-bookmarklet.js
new file mode 100644
index 000000000000..9fa4885de85c
--- /dev/null
+++ b/bots/question-bookmarklet.js
@@ -0,0 +1 @@
+javascript:(function(){$('#new_comment_field')[0].value='Hey and thanks for reporting this!\n\nThere\'s an awesome place to ask question like this one: [StackOverflow](http://stackoverflow.com/questions/tagged/react-native). It\'s the best system for Q&A. Many people from the community hang out there and will be able to see your question, you can vote on answers and mark question as answered etc. This lets us keep a list of bug reports and feature requests on github and especially [Product Pains](https://productpains.com/product/react-native/?tab=top) (again, with voting which is really nice).\n\nIf you think StackOverflow works for you please consider posting there instead and closing this issue.\n\nI\'m posting this here because github issues haven\'t been working very well for us and because StackOverflow is so much better. Thanks for reading! :)';$('button.btn-primary:contains("Comment")').click()})()
diff --git a/breaking-changes.md b/breaking-changes.md
index 5cb09da7966b..91e2ce2a2896 100644
--- a/breaking-changes.md
+++ b/breaking-changes.md
@@ -1,5 +1,15 @@
 # Breaking Changes
 
+**Breaking changes are now tracked as part of [Release Notes](https://github.com/facebook/react-native/releases)**.
+
+## 0.16
+
+- Touch events on Android now have coordinates consistent with iOS: [0c2ee5](https://github.com/facebook/react-native/commit/0c2ee5d480e696f8621252c936a8773e8de9f8b6)
+- YellowBox enabled by default: [8ab518](https://github.com/facebook/react-native/commit/8ab51828ff077ae0ad10c06f62f9f01d58b9bf85)
+- React Native now uses Babel 6 (props to tadeuzagallo for upgrading!). We've been using React Native with Babel 6 at Facebook for quite a while now. Nevertheless, please report any errors related to Babel, such as transforms for some JS features not working as expected and we'll fix them.
+- Decorators won't work until [T2645](https://phabricator.babeljs.io/T2645) lands in Babel.
+- Exporting default class that extends a base class won't work due to Babel's [T2694](https://phabricator.babeljs.io/T2694).
+
 ## 0.15
 
 - React Native now uses [React 0.14](http://facebook.github.io/react/blog/2015/10/07/react-v0.14.html); see the linked blog post for a changelog (at this time, the package split has not been made in RN (i.e., please continue to use `require('react-native')`) and refs to native components have not changed)
diff --git a/build.gradle b/build.gradle
index d87035a390a4..eed2379a8759 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,8 +6,8 @@ buildscript {
         mavenLocal()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.3.1'
-        classpath 'de.undercouch:gradle-download-task:1.2'
+        classpath 'com.android.tools.build:gradle:1.5.0'
+        classpath 'de.undercouch:gradle-download-task:2.0.0'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 000000000000..f8e17b4c1801
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,87 @@
+general:
+  branches:
+    ignore:
+      - gh-pages # list of branches to ignore
+machine:
+  node:
+    version: 5.6.0
+  environment:
+    PATH: "~/$CIRCLE_PROJECT_REPONAME/gradle-2.9/bin:$PATH"
+    TERM: "dumb"
+    ADB_INSTALL_TIMEOUT: 10
+    GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx512m -XX:+HeapDumpOnOutOfMemoryError"'
+
+dependencies:
+  pre:
+    # BUCK
+    - if [[ ! -e buck ]]; then git clone https://github.com/facebook/buck.git; fi
+    - cd buck && ant
+    - buck/bin/buck --version
+    - buck/bin/buck fetch ReactAndroid/src/test/java/com/facebook/react/modules
+    - buck/bin/buck fetch ReactAndroid/src/main/java/com/facebook/react
+    - buck/bin/buck fetch ReactAndroid/src/main/java/com/facebook/react/shell
+    - buck/bin/buck fetch ReactAndroid/src/test/...
+    - buck/bin/buck fetch ReactAndroid/src/androidTest/...
+    - source scripts/circle-ci-android-setup.sh && getAndroidSDK
+    - ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
+  cache_directories:
+    - "ReactAndroid/build/downloads"
+    - "buck"
+    - "buck-out/bin"
+    - "website/node_modules"
+  override:
+    - npm config set spin=false
+    - npm config set progress=false
+    - npm install
+    - cd website && npm install
+
+test:
+  pre:
+    # starting emulator in advance because it takes very long to boot
+    - $ANDROID_HOME/tools/emulator -avd testAVD -no-skin -no-audio -no-window:
+            background: true
+    - source scripts/circle-ci-android-setup.sh && waitForAVD
+
+  override:
+    # build app
+    - buck/bin/buck build ReactAndroid/src/main/java/com/facebook/react
+    - buck/bin/buck build ReactAndroid/src/main/java/com/facebook/react/shell
+
+    # unit tests
+    - buck/bin/buck test ReactAndroid/src/test/... --config build.threads=1
+
+    # instrumentation tests
+    # compile native libs with Gradle script
+    - ./gradlew :ReactAndroid:packageReactNdkLibsForBuck -PdisablePreDex -Pjobs=1:
+        timeout: 360
+    # build JS bundle for instrumentation tests
+    - node local-cli/cli.js bundle --platform android --dev true --entry-file ReactAndroid/src/androidTest/assets/TestBundle.js --bundle-output ReactAndroid/src/androidTest/assets/AndroidTestBundle.js
+    # build test APK
+    - buck/bin/buck install ReactAndroid/src/androidTest/buck-runner:instrumentation-tests --config build.threads=1
+    # run installed apk with tests
+    - ./scripts/run-android-instrumentation-tests.sh com.facebook.react.tests
+
+    # Deprecated: run tests with Gradle, we keep them for a while to compare performance
+    - ./gradlew :ReactAndroid:testDebugUnitTest -PdisablePreDex
+    - ./gradlew :ReactAndroid:connectedAndroidTest -PdisablePreDex --stacktrace --info:
+        timeout: 360
+
+    # testing docs generation is not broken
+    - cd website && node ./server/generate.js
+  post:
+    # copy test report for Circle CI to display
+    - mkdir -p $CIRCLE_TEST_REPORTS/junit/
+    - find . -type f -regex ".*/build/test-results/debug/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
+    - find . -type f -regex ".*/outputs/androidTest-results/connected/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
+    # TODO circle does not understand Buck's report, maybe need to transform xml slightly
+    #- find . -type f -regex ".*/buck-out/gen/ReactAndroid/src/test/.*/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
+
+deployment:
+  website:
+    branch: [/.*-stable/, /master/]
+    commands:
+      # generate docs website
+      - git config --global user.email "bestnader@fb.com"
+      - git config --global user.name "Website Deployment Script"
+      - echo "machine github.com login reactjs-bot password $GITHUB_TOKEN" > ~/.netrc
+      - cd website && GIT_USER=reactjs-bot npm run gh-pages
diff --git a/docs/Accessibility.md b/docs/Accessibility.md
index 10ac5884afc6..b968642c8908 100644
--- a/docs/Accessibility.md
+++ b/docs/Accessibility.md
@@ -81,7 +81,7 @@ Assign this property to a custom function which will be called when someone perf
 
 #### accessibilityComponentType (Android)
 
-In some cases, we also want to alert the end user of the type of selected component (ie, that it is a “button”). If we were using native buttons, this would work automatically. Since we are using javascript, we need to provide a bit more context for TalkBack. To do so, you must specify the ‘accessibilityComponentType’ property for any UI component. For instances, we support ‘button’, ‘radiobutton_checked’ and ‘radiobutton_unchecked’ and so on.
+In some cases, we also want to alert the end user of the type of selected component (i.e., that it is a “button”). If we were using native buttons, this would work automatically. Since we are using javascript, we need to provide a bit more context for TalkBack. To do so, you must specify the ‘accessibilityComponentType’ property for any UI component. For instances, we support ‘button’, ‘radiobutton_checked’ and ‘radiobutton_unchecked’ and so on.
 
 ```javascript
 <TouchableWithoutFeedback accessibilityComponentType=”button”
diff --git a/docs/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md
new file mode 100644
index 000000000000..5c31274640f7
--- /dev/null
+++ b/docs/AndroidBuildingFromSource.md
@@ -0,0 +1,126 @@
+---
+id: android-building-from-source
+title: Building React Native from source
+layout: docs
+category: Guides (Android)
+permalink: docs/android-building-from-source.html
+next: activityindicatorios
+---
+
+You will need to build React Native from source if you want to work on a new feature/bug fix, try out the latest features which are not released yet, or maintain your own fork with patches that cannot be merged to the core.
+
+## Prerequisites
+
+Assuming you have the Android SDK installed, run `android` to open the Android SDK Manager.
+
+Make sure you have the following installed:
+
+1. Android SDK version 23 (compileSdkVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle))
+2. SDK build tools version 23.0.1 (buildToolsVersion in [`build.gradle`](https://github.com/facebook/react-native/blob/master/ReactAndroid/build.gradle))
+3. Android Support Repository >= 17 (for Android Support Library)
+4. Android NDK (download & extraction instructions [here](http://developer.android.com/ndk/downloads/index.html))
+
+Point Gradle to your Android SDK: either have `$ANDROID_SDK` and `$ANDROID_NDK ` defined, or create a local.properties file in the root of your react-native checkout with the following contents:
+
+```
+sdk.dir=absolute_path_to_android_sdk
+ndk.dir=absolute_path_to_android_ndk
+```
+
+Example:
+
+```
+sdk.dir=/Users/your_unix_name/android-sdk-macosx
+ndk.dir=/Users/your_unix_name/android-ndk/android-ndk-r10e
+```
+
+
+## Building the source
+
+#### 1. Installing the fork
+
+First, you need to install `react-native` from your fork. For example, to install the master branch from the official repo, run the following:
+
+```sh
+npm install --save github:facebook/react-native#master
+```
+
+Alternatively, you can clone the repo to your `node_modules` directory and run `npm install` inside the cloned repo.
+
+#### 2. Adding gradle dependencies
+
+Add `gradle-download-task` as dependency in `android/build.gradle`:
+
+```gradle
+...
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.3.1'
+        classpath 'de.undercouch:gradle-download-task:2.0.0'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+...
+```
+
+#### 3. Adding the `:ReactAndroid` project
+
+Add the `:ReactAndroid` project in `android/settings.gradle`:
+
+```gradle
+...
+include ':ReactAndroid'
+
+project(':ReactAndroid').projectDir = new File(
+    rootProject.projectDir, '../node_modules/react-native/ReactAndroid')
+...
+```
+
+Modify your `android/app/build.gradle` to use the `:ReactAndroid` project instead of the pre-compiled library, e.g. - replace `compile 'com.facebook.react:react-native:0.16.+'` with `compile project(':ReactAndroid')`:
+
+```gradle
+...
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile 'com.android.support:appcompat-v7:23.0.1'
+
+    compile project(':ReactAndroid')
+
+    ...
+}
+...
+```
+
+#### 4. Making 3rd-party modules use your fork
+
+If you use 3rd-party React Native modules, you need to override their dependencies so that they don't bundle the pre-compiled library. Otherwise you'll get an error while compiling - `Error: more than one library with package name 'com.facebook.react'`.
+
+Modify your `android/app/build.gradle` and replace `compile project(':react-native-custom-module')` with:
+
+```gradle
+compile(project(':react-native-custom-module')) {
+    exclude group: 'com.facebook.react', module: 'react-native'
+}
+```
+
+## Building from Android Studio
+
+From the Welcome screen of Android Studio choose "Import project" and select the `android` folder of your app.
+
+You should be able to use the _Run_ button to run your app on a device. Android Studio won't start the packager automatically, you'll need to start it by running `npm start` on the command line.
+
+## Additional notes
+
+Building from source can take a long time, especially for the first build, as it needs to download ~200 MB of artifacts and compile the native code. Every time you update the `react-native` version from your repo, the build directory may get deleted, and all the files are re-downloaded. To avoid this, you might want to change your build directory path by editing the `~/.gradle/init.gradle ` file:
+
+```gradle
+gradle.projectsLoaded {
+    rootProject.allprojects {
+        buildDir = "/path/to/build/directory/${rootProject.name}/${project.name}"
+    }
+}
+```
+
+## Troubleshooting
+
+Gradle build fails in `ndk-build`. See the section about `local.properties` file above.
diff --git a/docs/AndroidUIPerformance.md b/docs/AndroidUIPerformance.md
index 927ba2131b68..3dc51532dc38 100644
--- a/docs/AndroidUIPerformance.md
+++ b/docs/AndroidUIPerformance.md
@@ -4,7 +4,7 @@ title: Profiling Android UI Performance
 layout: docs
 category: Guides (Android)
 permalink: docs/android-ui-performance.html
-next: activityindicatorios
+next: android-building-from-source
 ---
 
 We try our best to deliver buttery-smooth UI performance by default, but sometimes that just isn't possible. Remember, Android supports 10k+ different phones and is generalized to support software rendering: the framework architecture and need to generalize across many hardware targets unfortunately means you get less for free relative to iOS. But sometimes, there are things you can improve (and many times it's not native code's fault at all!).
@@ -12,7 +12,7 @@ We try our best to deliver buttery-smooth UI performance by default, but sometim
 The first step for debugging this jank is to answer the fundamental question of where your time is being spent during each 16ms frame. For that, we'll be using a standard Android profiling tool called systrace. But first...
 
 > Make sure that JS dev mode is OFF!
-> 
+>
 > You should see `__DEV__ === false, development-level warning are OFF, performance optimizations are ON` in your application logs (which you can view using `adb logcat`)
 
 ## Profiling with Systrace
@@ -41,9 +41,9 @@ Once the trace starts collecting, perform the animation or interaction you care
 
 ## Reading the trace
 
-After opening the trace in your browser (preferrably Chrome), you should see something like this:
+After opening the trace in your browser (preferably Chrome), you should see something like this:
 
-![Example](/react-native/img/SystraceExample.png)
+![Example](img/SystraceExample.png)
 
 **HINT**: Use the WASD keys to strafe and zoom
 
@@ -51,7 +51,7 @@ After opening the trace in your browser (preferrably Chrome), you should see som
 
 The first thing you should do is highlight the 16ms frame boundaries if you haven't already done that. Check this checkbox at the top right of the screen:
 
-![Enable VSync Highlighting](/react-native/img/SystraceHighlightVSync.png)
+![Enable VSync Highlighting](img/SystraceHighlightVSync.png)
 
 You should see zebra stripes as in the screenshot above. If you don't, try profiling on a different device: Samsung has been known to have issues displaying vsyncs while the Nexus series is generally pretty reliable.
 
@@ -65,43 +65,43 @@ On the left side, you'll see a set of threads which correspond to the timeline r
 
 This is where standard android measure/layout/draw happens. The thread name on the right will be your package name (in my case book.adsmanager) or UI Thread. The events that you see on this thread should look something like this and have to do with `Choreographer`, `traversals`, and `DispatchUI`:
 
-![UI Thread Example](/react-native/img/SystraceUIThreadExample.png)
+![UI Thread Example](img/SystraceUIThreadExample.png)
 
 ### JS Thread
 
 This is where JS is executed. The thread name will be either `mqt_js` or `<...>` depending on how cooperative the kernel on your device is being. To identify it if it doesn't have a name, look for things like `JSCall`, `Bridge.executeJSCall`, etc:
 
-![JS Thread Example](/react-native/img/SystraceJSThreadExample.png)
+![JS Thread Example](img/SystraceJSThreadExample.png)
 
 ### Native Modules Thread
 
 This is where native module calls (e.g. the `UIManager`) are executed. The thread name will be either `mqt_native_modules` or `<...>`. To identify it in the latter case, look for things like `NativeCall`, `callJavaModuleMethod`, and `onBatchComplete`:
 
-![Native Modules Thread Example](/react-native/img/SystraceNativeModulesThreadExample.png)
+![Native Modules Thread Example](img/SystraceNativeModulesThreadExample.png)
 
 ### Bonus: Render Thread
 
 If you're using Android L (5.0) and up, you will also have a render thread in your application. This thread generates the actual OpenGL commands used to draw your UI. The thread name will be either `RenderThread` or `<...>`. To identify it in the latter case, look for things like `DrawFrame` and `queueBuffer`:
 
-![Render Thread Example](/react-native/img/SystraceRenderThreadExample.png)
+![Render Thread Example](img/SystraceRenderThreadExample.png)
 
 ## Identifying a culprit
 
 A smooth animation should look something like the following:
 
-![Smooth Animation](/react-native/img/SystraceWellBehaved.png)
+![Smooth Animation](img/SystraceWellBehaved.png)
 
 Each change in color is a frame -- remember that in order to display a frame, all our UI work needs to be done by the end of that 16ms period. Notice that no thread is working close to the frame boundary. An application rendering like this is rendering at 60FPS.
 
 If you noticed chop, however, you might see something like this:
 
-![Choppy Animation from JS](/react-native/img/SystraceBadJS.png)
+![Choppy Animation from JS](img/SystraceBadJS.png)
 
 Notice that the JS thread is executing basically all the time, and across frame boundaries! This app is not rendering at 60FPS. In this case, **the problem lies in JS**.
 
 You might also see something like this:
 
-![Choppy Animation from UI](/react-native/img/SystraceBadUI.png)
+![Choppy Animation from UI](img/SystraceBadUI.png)
 
 In this case, the UI and render threads are the ones that have work crossing frame boundaries. The UI that we're trying to render on each frame is requiring too much work to be done. In this case, **the problem lies in the native views being rendered**.
 
@@ -111,7 +111,7 @@ At this point, you'll have some very helpful information to inform your next ste
 
 If you identified a JS problem, look for clues in the specific JS that you're executing. In the scenario above, we see `RCTEventEmitter` being called multiple times per frame. Here's a zoom-in of the JS thread from the trace above:
 
-![Too much JS](/react-native/img/SystraceBadJS2.png)
+![Too much JS](img/SystraceBadJS2.png)
 
 This doesn't seem right. Why is it being called so often? Are they actually different events? The answers to these questions will probably depend on your product code. And many times, you'll want to look into [shouldComponentUpdate](https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate).
 
@@ -128,7 +128,7 @@ If you identified a native UI problem, there are usually two scenarios:
 
 In the first scenario, you'll see a trace that has the UI thread and/or Render Thread looking like this:
 
-![Overloaded GPU](/react-native/img/SystraceBadUI.png)
+![Overloaded GPU](img/SystraceBadUI.png)
 
 Notice the long amount of time spent in `DrawFrame` that crosses frame boundaries. This is time spent waiting for the GPU to drain its command buffer from the previous frame.
 
@@ -143,7 +143,7 @@ If these don't help and you want to dig deeper into what the GPU is actually doi
 
 In the second scenario, you'll see something more like this:
 
-![Creating Views](/react-native/img/SystraceBadCreateUI.png)
+![Creating Views](img/SystraceBadCreateUI.png)
 
 Notice that first the JS thread thinks for a bit, then you see some work done on the native modules thread, followed by an expensive traversal on the UI thread.
 
diff --git a/docs/Animations.md b/docs/Animations.md
index a18f0f78ce69..1c570a952e41 100644
--- a/docs/Animations.md
+++ b/docs/Animations.md
@@ -168,9 +168,11 @@ Input | Output
 already implemented in the
 [`Easing`](https://github.com/facebook/react-native/blob/master/Libraries/Animation/Animated/Easing.js)
 class including quadratic, exponential, and bezier curves as well as functions
-like step and bounce.  `interpolation` also has configurable behavior for
-extrapolation, the default being `'extend'`, but `'clamp'` is also very useful
-to prevent the output value from exceeding `outputRange`.
+like step and bounce. `interpolation` also has configurable behavior for
+extrapolating the `outputRange`. You can set the extrapolation by setting the `extrapolate`,
+`extrapolateLeft` or `extrapolateRight` options. The default value is
+`extend` but you can use `clamp` to prevent the output value from exceeding
+`outputRange`.
 
 #### Tracking Dynamic Values
 
@@ -210,11 +212,14 @@ respectively (`gestureState` is the second arg passed to the `PanResponder` hand
 
 ```javascript
 onScroll={Animated.event(
-  [{nativeEvent: {contentOffset: {x: scrollX}}}]   // scrollX = e.nativeEvent.contentOffset.x
+  // scrollX = e.nativeEvent.contentOffset.x
+  [{nativeEvent: {contentOffset: {x: scrollX}}}]
 )}
 onPanResponderMove={Animated.event([
   null,                                          // ignore the native event
-  {dx: pan.x, dy: pan.y}                         // extract dx and dy from gestureState
+  // extract dx and dy from gestureState
+  // like 'pan.x = gestureState.dx, pan.y = gestureState.dy'
+  {dx: pan.x, dy: pan.y}
 ]);
 ```
 
@@ -246,7 +251,7 @@ concise, robust, and performant way.  Check out more example code in
 doesn't support what you need, and the following sections cover other animation
 systems.
 
-### LayoutAnimation (iOS only)
+### LayoutAnimation
 
 `LayoutAnimation` allows you to globally configure `create` and `update`
 animations that will be used for all views in the next render/layout cycle.
@@ -262,7 +267,7 @@ it provides much less control than `Animated` and other animation libraries, so
 you may need to use another approach if you can't get `LayoutAnimation` to do
 what you want.
 
-![](/react-native/img/LayoutAnimationExample.gif)
+![](img/LayoutAnimationExample.gif)
 
 ```javascript
 var App = React.createClass({
@@ -307,7 +312,7 @@ for more information.
 familiar with. It accepts a function as its only argument and calls that
 function before the next repaint. It is an essential building block for
 animations that underlies all of the JavaScript-based animation APIs.  In
-general, you shouldn't need to call this yourself - the animation API's will
+general, you shouldn't need to call this yourself - the animation APIs will
 manage frame updates for you.
 
 ### react-tween-state (Not recommended - use [Animated](#animated) instead)
@@ -339,7 +344,7 @@ your project, you will need to install it with `npm i react-tween-state
 --save` from your project directory.
 
 ```javascript
-var tweenState = require('react-tween-state');
+import tweenState from 'react-tween-state';
 
 var App = React.createClass({
   mixins: [tweenState.Mixin],
@@ -371,13 +376,13 @@ var App = React.createClass({
 ```
 [Run this example](https://rnplay.org/apps/4FUQ-A)
 
-![](/react-native/img/TweenState.gif)
+![](img/TweenState.gif)
 
 Here we animated the opacity, but as you might guess, we can animate any
 numeric value. Read more about react-tween-state in its
 [README](https://github.com/chenglou/react-tween-state).
 
-### Rebound (Not recommended - use [Animated](#animated) instead)
+### Rebound (Not recommended - use [Animated](docs/animation.html) instead)
 
 [Rebound.js](https://github.com/facebook/rebound-js) is a JavaScript port of
 [Rebound for Android](https://github.com/facebook/rebound). It is
@@ -390,14 +395,14 @@ value and end value.  Rebound [is used
 internally](https://github.com/facebook/react-native/search?utf8=%E2%9C%93&q=rebound)
 by React Native on `Navigator` and `WarningBox`.
 
-![](/react-native/img/ReboundImage.gif)
+![](img/ReboundImage.gif)
 
 Notice that Rebound animations can be interrupted - if you release in
 the middle of a press, it will animate back from the current state to
 the original value.
 
 ```javascript
-var rebound = require('rebound');
+import rebound from 'rebound';
 
 var App = React.createClass({
   // First we initialize the spring and add a listener, which calls
@@ -435,7 +440,7 @@ var App = React.createClass({
       transform: [{scaleX: this.state.scale}, {scaleY: this.state.scale}],
     };
 
-    var imageUri = "https://facebook.github.io/react-native/img/ReboundExample.png";
+    var imageUri = "img/ReboundExample.png";
 
     return (
       <View style={styles.container}>
@@ -456,13 +461,13 @@ oscillate around the end value. In the above example, we would add
 See the below gif for an example of where in your interface you might
 use this.
 
-![](/react-native/img/Rebound.gif) Screenshot from
+![](img/Rebound.gif) Screenshot from
 [react-native-scrollable-tab-view](https://github.com/brentvatne/react-native-scrollable-tab-view).
 You can run a similar example [here](https://rnplay.org/apps/qHU_5w).
 
 #### A sidenote about setNativeProps
 
-As mentioned [in the Direction Manipulation section](/react-native/docs/direct-manipulation.html),
+As mentioned [in the Direction Manipulation section](docs/direct-manipulation.html),
 `setNativeProps` allows us to modify properties of native-backed
 components (components that are actually backed by native views, unlike
 composite components) directly, without having to `setState` and
@@ -492,7 +497,7 @@ render: function() {
     <View style={styles.container}>
       <TouchableWithoutFeedback onPressIn={this._onPressIn} onPressOut={this._onPressOut}>
         <Image ref={component => this._photo = component}
-               source={{uri: "https://facebook.github.io/react-native/img/ReboundExample.png"}}
+               source={{uri: "img/ReboundExample.png"}}
                style={{width: 250, height: 200}} />
       </TouchableWithoutFeedback>
     </View>
@@ -511,23 +516,24 @@ frames per second), look into using `setNativeProps` or
 `shouldComponentUpdate` to optimize them. You may also want to defer any
 computationally intensive work until after animations are complete,
 using the
-[InteractionManager](/react-native/docs/interactionmanager.html). You
+[InteractionManager](docs/interactionmanager.html). You
 can monitor the frame rate by using the In-App Developer Menu "FPS
 Monitor" tool.
 
 ### Navigator Scene Transitions
 
 As mentioned in the [Navigator
-Comparison](https://facebook.github.io/react-native/docs/navigator-comparison.html#content),
+Comparison](docs/navigator-comparison.html#content),
 `Navigator` is implemented in JavaScript and `NavigatorIOS` is a wrapper
 around native functionality provided by `UINavigationController`, so
 these scene transitions apply only to `Navigator`. In order to re-create
 the various animations provided by `UINavigationController` and also
 make them customizable, React Native exposes a
-[NavigatorSceneConfigs](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js) API.
+[NavigatorSceneConfigs](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js) API which is then handed over to the [Navigator](https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/Navigator/Navigator.js) `configureScene` prop.
 
 ```javascript
-var SCREEN_WIDTH = require('Dimensions').get('window').width;
+import { Dimensions } from 'react-native';
+var SCREEN_WIDTH = Dimensions.get('window').width;
 var BaseConfig = Navigator.SceneConfigs.FloatFromRight;
 
 var CustomLeftToRightGesture = Object.assign({}, BaseConfig.gestures.pop, {
diff --git a/docs/Colors.md b/docs/Colors.md
new file mode 100644
index 000000000000..aba269085a2b
--- /dev/null
+++ b/docs/Colors.md
@@ -0,0 +1,173 @@
+---
+id: colors
+title: Colors
+layout: docs
+category: Polyfills
+permalink: docs/colors.html
+---
+
+The following formats are supported:
+
+ - `'#f0f'` (#rgb)
+ - `'#f0fc'` (#rgba)
+ - `'#ff00ff'` (#rrggbb)
+ - `'#ff00ff00'` (#rrggbbaa)
+ - `'rgb(255, 255, 255)'`
+ - `'rgba(255, 255, 255, 1.0)'`
+ - `'hsl(360, 100%, 100%)'`
+ - `'hsla(360, 100%, 100%, 1.0)'`
+ - `'transparent'`
+ - `'red'`
+ - `0xff00ff00` (0xrrggbbaa)
+
+
+For the named colors, React Native follows the [CSS3 specification](http://www.w3.org/TR/css3-color/#svg-color):
+
+- <color aliceblue /> aliceblue (#f0f8ff)
+- <color antiquewhite /> antiquewhite (#faebd7)
+- <color aqua /> aqua (#00ffff)
+- <color aquamarine /> aquamarine (#7fffd4)
+- <color azure /> azure (#f0ffff)
+- <color beige /> beige (#f5f5dc)
+- <color bisque /> bisque (#ffe4c4)
+- <color black /> black (#000000)
+- <color blanchedalmond /> blanchedalmond (#ffebcd)
+- <color blue /> blue (#0000ff)
+- <color blueviolet /> blueviolet (#8a2be2)
+- <color brown /> brown (#a52a2a)
+- <color burlywood /> burlywood (#deb887)
+- <color cadetblue /> cadetblue (#5f9ea0)
+- <color chartreuse /> chartreuse (#7fff00)
+- <color chocolate /> chocolate (#d2691e)
+- <color coral /> coral (#ff7f50)
+- <color cornflowerblue /> cornflowerblue (#6495ed)
+- <color cornsilk /> cornsilk (#fff8dc)
+- <color crimson /> crimson (#dc143c)
+- <color cyan /> cyan (#00ffff)
+- <color darkblue /> darkblue (#00008b)
+- <color darkcyan /> darkcyan (#008b8b)
+- <color darkgoldenrod /> darkgoldenrod (#b8860b)
+- <color darkgray /> darkgray (#a9a9a9)
+- <color darkgreen /> darkgreen (#006400)
+- <color darkgrey /> darkgrey (#a9a9a9)
+- <color darkkhaki /> darkkhaki (#bdb76b)
+- <color darkmagenta /> darkmagenta (#8b008b)
+- <color darkolivegreen /> darkolivegreen (#556b2f)
+- <color darkorange /> darkorange (#ff8c00)
+- <color darkorchid /> darkorchid (#9932cc)
+- <color darkred /> darkred (#8b0000)
+- <color darksalmon /> darksalmon (#e9967a)
+- <color darkseagreen /> darkseagreen (#8fbc8f)
+- <color darkslateblue /> darkslateblue (#483d8b)
+- <color darkslategray /> darkslategray (#2f4f4f)
+- <color darkslategrey /> darkslategrey (#2f4f4f)
+- <color darkturquoise /> darkturquoise (#00ced1)
+- <color darkviolet /> darkviolet (#9400d3)
+- <color deeppink /> deeppink (#ff1493)
+- <color deepskyblue /> deepskyblue (#00bfff)
+- <color dimgray /> dimgray (#696969)
+- <color dimgrey /> dimgrey (#696969)
+- <color dodgerblue /> dodgerblue (#1e90ff)
+- <color firebrick /> firebrick (#b22222)
+- <color floralwhite /> floralwhite (#fffaf0)
+- <color forestgreen /> forestgreen (#228b22)
+- <color fuchsia /> fuchsia (#ff00ff)
+- <color gainsboro /> gainsboro (#dcdcdc)
+- <color ghostwhite /> ghostwhite (#f8f8ff)
+- <color gold /> gold (#ffd700)
+- <color goldenrod /> goldenrod (#daa520)
+- <color gray /> gray (#808080)
+- <color green /> green (#008000)
+- <color greenyellow /> greenyellow (#adff2f)
+- <color grey /> grey (#808080)
+- <color honeydew /> honeydew (#f0fff0)
+- <color hotpink /> hotpink (#ff69b4)
+- <color indianred /> indianred (#cd5c5c)
+- <color indigo /> indigo (#4b0082)
+- <color ivory /> ivory (#fffff0)
+- <color khaki /> khaki (#f0e68c)
+- <color lavender /> lavender (#e6e6fa)
+- <color lavenderblush /> lavenderblush (#fff0f5)
+- <color lawngreen /> lawngreen (#7cfc00)
+- <color lemonchiffon /> lemonchiffon (#fffacd)
+- <color lightblue /> lightblue (#add8e6)
+- <color lightcoral /> lightcoral (#f08080)
+- <color lightcyan /> lightcyan (#e0ffff)
+- <color lightgoldenrodyellow /> lightgoldenrodyellow (#fafad2)
+- <color lightgray /> lightgray (#d3d3d3)
+- <color lightgreen /> lightgreen (#90ee90)
+- <color lightgrey /> lightgrey (#d3d3d3)
+- <color lightpink /> lightpink (#ffb6c1)
+- <color lightsalmon /> lightsalmon (#ffa07a)
+- <color lightseagreen /> lightseagreen (#20b2aa)
+- <color lightskyblue /> lightskyblue (#87cefa)
+- <color lightslategray /> lightslategray (#778899)
+- <color lightslategrey /> lightslategrey (#778899)
+- <color lightsteelblue /> lightsteelblue (#b0c4de)
+- <color lightyellow /> lightyellow (#ffffe0)
+- <color lime /> lime (#00ff00)
+- <color limegreen /> limegreen (#32cd32)
+- <color linen /> linen (#faf0e6)
+- <color magenta /> magenta (#ff00ff)
+- <color maroon /> maroon (#800000)
+- <color mediumaquamarine /> mediumaquamarine (#66cdaa)
+- <color mediumblue /> mediumblue (#0000cd)
+- <color mediumorchid /> mediumorchid (#ba55d3)
+- <color mediumpurple /> mediumpurple (#9370db)
+- <color mediumseagreen /> mediumseagreen (#3cb371)
+- <color mediumslateblue /> mediumslateblue (#7b68ee)
+- <color mediumspringgreen /> mediumspringgreen (#00fa9a)
+- <color mediumturquoise /> mediumturquoise (#48d1cc)
+- <color mediumvioletred /> mediumvioletred (#c71585)
+- <color midnightblue /> midnightblue (#191970)
+- <color mintcream /> mintcream (#f5fffa)
+- <color mistyrose /> mistyrose (#ffe4e1)
+- <color moccasin /> moccasin (#ffe4b5)
+- <color navajowhite /> navajowhite (#ffdead)
+- <color navy /> navy (#000080)
+- <color oldlace /> oldlace (#fdf5e6)
+- <color olive /> olive (#808000)
+- <color olivedrab /> olivedrab (#6b8e23)
+- <color orange /> orange (#ffa500)
+- <color orangered /> orangered (#ff4500)
+- <color orchid /> orchid (#da70d6)
+- <color palegoldenrod /> palegoldenrod (#eee8aa)
+- <color palegreen /> palegreen (#98fb98)
+- <color paleturquoise /> paleturquoise (#afeeee)
+- <color palevioletred /> palevioletred (#db7093)
+- <color papayawhip /> papayawhip (#ffefd5)
+- <color peachpuff /> peachpuff (#ffdab9)
+- <color peru /> peru (#cd853f)
+- <color pink /> pink (#ffc0cb)
+- <color plum /> plum (#dda0dd)
+- <color powderblue /> powderblue (#b0e0e6)
+- <color purple /> purple (#800080)
+- <color rebeccapurple /> rebeccapurple (#663399)
+- <color red /> red (#ff0000)
+- <color rosybrown /> rosybrown (#bc8f8f)
+- <color royalblue /> royalblue (#4169e1)
+- <color saddlebrown /> saddlebrown (#8b4513)
+- <color salmon /> salmon (#fa8072)
+- <color sandybrown /> sandybrown (#f4a460)
+- <color seagreen /> seagreen (#2e8b57)
+- <color seashell /> seashell (#fff5ee)
+- <color sienna /> sienna (#a0522d)
+- <color silver /> silver (#c0c0c0)
+- <color skyblue /> skyblue (#87ceeb)
+- <color slateblue /> slateblue (#6a5acd)
+- <color slategray /> slategray (#708090)
+- <color slategrey /> slategrey (#708090)
+- <color snow /> snow (#fffafa)
+- <color springgreen /> springgreen (#00ff7f)
+- <color steelblue /> steelblue (#4682b4)
+- <color tan /> tan (#d2b48c)
+- <color teal /> teal (#008080)
+- <color thistle /> thistle (#d8bfd8)
+- <color tomato /> tomato (#ff6347)
+- <color turquoise /> turquoise (#40e0d0)
+- <color violet /> violet (#ee82ee)
+- <color wheat /> wheat (#f5deb3)
+- <color white /> white (#ffffff)
+- <color whitesmoke /> whitesmoke (#f5f5f5)
+- <color yellow /> yellow (#ffff00)
+- <color yellowgreen /> yellowgreen (#9acd32)
diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md
new file mode 100644
index 000000000000..ebc08328dd27
--- /dev/null
+++ b/docs/CommunicationIOS.md
@@ -0,0 +1,217 @@
+---
+id: communication-ios
+title: Communication between native and React Native
+layout: docs
+category: Guides (iOS)
+permalink: docs/communication-ios.html
+next: native-modules-android
+---
+
+In [Integrating with Existing Apps guide](docs/embedded-app-ios.html) and [Native UI Components guide](docs/native-components-ios.html) we learn how to embed React Native in a native component and vice versa. When we mix native and React Native components, we'll eventually find a need to communicate between these two worlds. Some ways to achieve that have been already mentioned in other guides. This article summarizes available techniques.
+
+## Introduction
+
+React Native is inspired by React, so the basic idea of the information flow is similar. The flow in React is one-directional. We maintain a hierarchy of components, in which each component depends only on its parent and own internal state. We do this with properties: data is passed from a parent to its children in a top-down manner. If we have an ancestor component that rely on the state of its descendant, the recommended solution would be to pass down a callback that would be used by the descendant to update the ancestor.
+
+The same concept applies to React Native. As long as we are building our application purely within the framework, we can drive our app with properties and callbacks. But, when we mix React Native and native components, we need some special, cross-language mechanisms that would allow us to pass information between them.
+
+## Properties
+
+Properties are the simplest way of cross-component communication. So we need a way to pass properties both from native to React Native, and from React Native to native.
+
+### Passing properties from native to React Native
+
+In order to embed a React Native view in a native component, we use `RCTRootView`. `RCTRootView` is a `UIView` that holds a React Native app. It also provides an interface between native side and the hosted app.
+
+`RCTRootView` has an initializer that allows you to pass arbitrary properties down to the React Native app. The `initialProperties` parameter has to be an instance of `NSDictionary`. The dictionary is internally converted into a JSON object that the top-level JS component can reference.
+
+```
+NSArray *imageList = @[@"http://foo.com/bar1.png",
+                       @"http://foo.com/bar2.png"];
+
+NSDictionary *props = @{@"images" : imageList};
+
+RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
+                                                 moduleName:@"ImageBrowserApp"
+                                          initialProperties:props];
+```
+
+```
+'use strict';
+
+import React, {
+  View,
+  Image
+} from 'react-native';
+
+class ImageBrowserApp extends React.Component {
+  renderImage(imgURI) {
+    return (
+      <Image source={{uri: imgURI}} />
+    );
+  }
+  render() {
+    return (
+      <View>
+        {this.props.images.map(this.renderImage)}
+      </View>
+    );
+  }
+}
+
+React.AppRegistry.registerComponent('ImageBrowserApp', () => ImageBrowserApp);
+```
+
+`RCTRootView` also provides a read-write property `appProperties`. After `appProperties` is set, the React Native app is re-rendered with new properties. The update is only performed when the new updated properties differ from the previous ones.
+
+```
+NSArray *imageList = @[@"http://foo.com/bar3.png",
+                       @"http://foo.com/bar4.png"];
+
+rootView.appProperties = @{@"images" : imageList};
+```
+
+It is fine to update properties anytime. However, updates have to be performed on the main thread. You use the getter on any thread.
+
+There is no way to update only a few properties at a time. We suggest that you build it into your own wrapper instead.
+
+> ***Note:***
+> Currently, JS functions `componentWillReceiveProps` and `componentWillUpdateProps` of the top level RN component will not be called after a prop update. However, you can access the new props in `componentWillMount` function.
+
+### Passing properties from React Native to native
+The problem exposing properties of native components is covered in detail in [this article](docs/native-components-ios.html#properties). In short, export properties with `RCT_CUSTOM_VIEW_PROPERTY` macro in your custom native component, then just use them in React Native as if the component was an ordinary React Native component.
+
+### Limits of properties
+
+The main drawback of cross-language properties is that they do not support callbacks, which would allow us to handle bottom-up data bindings. Imagine you have a small RN view that you want to be removed from the native parent view as a result of a JS action. There is no way to do that with props, as the information would need to go bottom-up.
+
+Although we have a flavor of cross-language callbacks ([described here](docs/native-modules-ios.html#callbacks)), these callbacks are not always the thing we need. The main problem is that they are not intended to be passed as properties. Rather, this mechanism allows us to trigger a native action from JS, and handle the result of that action in JS.
+
+## Other ways of cross-language interaction (events and native modules)
+
+As stated in the previous chapter, using properties comes with some limitations. Sometimes properties are not enough to drive the logic of our app and we need a solution that gives more flexibility. This chapter covers other communication techniques available in React Native. They can be used for internal communication (between JS and native layers in RN) as well as for external communication (between RN and the 'pure native' part of your app).
+
+React Native enables you to perform cross-language function calls. You can execute custom native code from JS and vice versa. Unfortunately, depending on the side we are working on, we achieve the same goal in different ways. For native - we use events mechanism to schedule an execution of a handler function in JS, while for React Native we directly call methods exported by native modules.
+
+### Calling React Native functions from native (events)
+
+Events are described in detail in [this article](docs/native-components-ios.html#events). Note that using events gives us no guarantees about execution time, as the event is handled on a separate thread.
+
+Events are powerful, because they allow us to change React Native components without needing a reference to them. However, there are some pitfalls that you can fall into while using them:
+
+* As events can be sent from anywhere, they can introduce spaghetti-style dependencies into your project.
+* Events share namespace, which means that you may encounter some name collisions. Collisions will not be detected statically, what makes them hard to debug.
+* If you use several instances of the same React Native component and you want to distinguish them from the perspective of your event, you'll likely need to introduce some kind of identifiers and pass them along with events (you can use the native view's `reactTag` as an identifier).
+
+The common pattern we use when embedding native in React Native is to make the native component's RCTViewManager a delegate for the views, sending events back to JavaScript via the bridge. This keeps related event calls in one place.
+
+### Calling native functions from React Native (native modules)
+
+Native modules are Objective-C classes that are available in JS. Typically one instance of each module is created per JS bridge. They can export arbitrary functions and constants to React Native. They have been covered in detail in [this article](docs/native-modules-ios.html#content).
+
+The fact that native modules are singletons limits the mechanism in context of embedding. Let's say we have a React Native component embedded in a native view and we want to update the native, parent view. Using the native module mechanism, we would export a function that not only takes expected arguments, but also an identifier of the parent native view. The identifier would be used to retrieve a reference to the parent view to update. That said, we would need to keep a mapping from identifiers to native views in the module.
+
+Although this solution is complex, it is used in `RCTUIManager`, which is an internal React Native class that manages all React Native views.
+
+Native modules can also be used to expose existing native libraries to JS. [Geolocation library](https://github.com/facebook/react-native/tree/master/Libraries/Geolocation) is a living example of the idea.
+
+> ***Warning***:
+> All native modules share the same namespace. Watch out for name collisions when creating new ones.
+
+## Layout computation flow
+
+When integrating native and React Native, we also need a way to consolidate two different layout systems. This section covers common layouting problems and provides a brief description of mechanisms that are intended to address them.
+
+### Layout of a native component embedded in React Native
+
+This case is covered in [this article](docs/native-components-ios.html#styles). Basically, as all our native react views are subclasses of `UIView`, most style and size attributes will work like you would expect out of the box.
+
+### Layout of a React Native component embedded in native
+
+#### React Native content with fixed size
+The simplest scenario is when we have a React Native app with a fixed size, which is known to the native side. In particular, a full-screen React Native view falls into this case. If we want a smaller root view, we can explicitly set RCTRootView's frame.
+
+For instance, to make an RN app 200 (logical) pixels high, and the hosting view's width wide, we could do:
+
+```
+// SomeViewController.m
+
+- (void)viewDidLoad
+{
+  [...]
+  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
+                                                   moduleName:appName
+                                            initialProperties:props];
+  rootView.frame = CGMakeRect(0, 0, self.view.width, 200);
+  [self.view addSubview:rootView];
+}
+```
+
+When we have a fixed size root view, we need to respect its bounds on the JS side. In other words, we need to ensure that the React Native content can be contained within the fixed-size root view. The easiest way to ensure this is to use flexbox layout. If you use absolute positioning, and React components are visible outside the root view's bounds, you'll get overlap with native views, causing some features to behave unexpectedly. For instance, 'TouchableHighlight' will not highlight your touches outside the root view's bounds.
+
+It's totally fine to update root view's size dynamically by re-setting its frame property. React Native will take care of the content's layout.
+
+#### React Native content with flexible size
+
+In some cases we'd like to render content of initially unknown size. Let's say the size will be defined dynamically in JS. We have two solutions to this problem.
+
+
+1. You can wrap your React Native view in `ScrollView` component. This guarantees that your content will always be available and it won't overlap with native views.
+2. React Native allows you to determine, in JS, the size of the RN app and provide it to the owner of the hosting `RCTRootView`. The owner is then responsible for re-laying out the subviews and keeping the UI consistent. We achieve this with `RCTRootView`'s flexibility modes.
+
+
+`RCTRootView` supports 4 different size flexibility modes:
+
+```
+// RCTRootView.h
+
+typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) {
+  RCTRootViewSizeFlexibilityNone = 0,
+  RCTRootViewSizeFlexibilityWidth,
+  RCTRootViewSizeFlexibilityHeight,
+  RCTRootViewSizeFlexibilityWidthAndHeight,
+};
+```
+
+`RCTRootViewSizeFlexibilityNone` is the default value, which makes a root view's size fixed (but it still can be updated with `setFrame:`). The other three modes allow us to track React Native content's size updates. For instance, setting mode to `RCTRootViewSizeFlexibilityHeight` will cause React Native to measure the content's height and pass that information back to `RCTRootView`'s delegate. An arbitrary action can be performed within the delegate, including setting the root view's frame, so the content fits. The delegate is called only when the size of the content has changed.
+
+> ***Warning:***
+> Making a dimension flexible in both JS and native leads to undefined behavior. For example - don't make a top-level React component's width flexible (with `flexbox`) while you're using `RCTRootViewSizeFlexibilityWidth` on the hosting `RCTRootView`.
+
+Let's look at an example.
+
+```
+// FlexibleSizeExampleView.m
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+  [...]
+
+  _rootView = [[RCTRootView alloc] initWithBridge:bridge
+  moduleName:@"FlexibilityExampleApp"
+  initialProperties:@{}];
+
+  _rootView.delegate = self;
+  _rootView.sizeFlexibility = RCTRootViewSizeFlexibilityHeight;
+  _rootView.frame = CGRectMake(0, 0, self.frame.size.width, 0);
+}
+
+#pragma mark - RCTRootViewDelegate
+- (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView
+{
+  CGRect newFrame = rootView.frame;
+  newFrame.size = rootView.intrinsicSize;
+
+  rootView.frame = newFrame;
+}
+```
+
+In the example we have a `FlexibleSizeExampleView` view that holds a root view. We create the root view, initialize it and set the delegate. The delegate will handle size updates. Then, we set the root view's size flexibility to `RCTRootViewSizeFlexibilityHeight`, which means that `rootViewDidChangeIntrinsicSize:` method will be called every time the React Native content changes its height. Finally, we set the root view's width and position. Note that we set there height as well, but it has no effect as we made the height RN-dependent.
+
+You can checkout full source code of the example [here](https://phabricator.fb.com/diffusion/FBOBJC/browse/master/Libraries/FBReactKit/js/react-native-github/Examples/UIExplorer/UIExplorer/NativeExampleViews/FlexibleSizeExampleView.m).
+
+It's fine to change root view's size flexibility mode dynamically. Changing flexibility mode of a root view will schedule a layout recalculation and the delegate `rootViewDidChangeIntrinsicSize:` method will be called once the content size is known.
+
+> ***Note:*** React Native layout calculation is performed on a special thread, while native UI view updates are done on the main thread. This may cause temporary UI inconsistencies between native and React Native. This is a known problem and our team is working on synchronizing UI updates coming from different sources.
+
+> ***Note:*** React Native does not perform any layout calculations until the root view becomes a subview of some other views. If you want to hide React Native view until its dimensions are known, add the root view as a subview and make it initially hidden (use `UIView`'s `hidden` property). Then change its visibility in the delegate method.
diff --git a/docs/Debugging.md b/docs/Debugging.md
index 26a7ca44703f..6c70ff22ce1a 100644
--- a/docs/Debugging.md
+++ b/docs/Debugging.md
@@ -11,7 +11,7 @@ next: testing
 To access the in-app developer menu:
 
 1. On iOS shake the device or press `control + ⌘ + z` in the simulator.
-2. On Android shake the device or press hardware menu button (available on older devices and in most of the emulators, e.g. in [genymotion](https://www.genymotion.com) you can press `⌘ + m` to simulate hardware menu button click)
+2. On Android shake the device or press hardware menu button (available on older devices and in most of the emulators, e.g. in [genymotion](https://www.genymotion.com) you can press `⌘ + m` to simulate hardware menu button click). You can also install [Frappé](http://getfrappe.com), a tool for OS X, which allows you to emulate shaking of devices remotely. You can use `⌘ + Shift + R` as a shortcut to trigger a **shake** from Frappé.
 
 > Hint
 
@@ -20,9 +20,19 @@ To access the in-app developer menu:
 > 1. For iOS open your project in Xcode and select `Product` → `Scheme` → `Edit Scheme...` (or press `⌘ + <`). Next, select `Run` from the menu on the left and change the Build Configuration to `Release`.
 > 2. For Android, by default, developer menu will be disabled in release builds done by gradle (e.g with gradle `assembleRelease` task). Although this behavior can be customized by passing proper value to `ReactInstanceManager#setUseDeveloperSupport`.
 
+### Android logging
+Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your Android app's logs.
+
 ### Reload
 Selecting `Reload` (or pressing `⌘ + r` in the iOS simulator) will reload the JavaScript that powers your application. If you have added new resources (such as an image to `Images.xcassets` on iOS or to `res/drawable` folder on Android) or modified any native code (Objective-C/Swift code on iOS or Java/C++ code on Android), you will need to re-build the app for the changes to take effect.
 
+### YellowBox/RedBox
+Using `console.warn` will display an on-screen log on a yellow background. Click on this warning to show more information about it full screen and/or dismiss the warning.
+
+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.
+
 ### 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).
 
@@ -31,10 +41,7 @@ In Chrome, press `⌘ + option + i` or select `View` → `Developer` → `Develo
 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.
-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 8081 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.
-
-#### React Developer Tools (optional)
-Install the [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) extension for Google Chrome. This will allow you to navigate the component hierarchy via the `React` in the developer tools (see [facebook/react-devtools](https://github.com/facebook/react-devtools) for more information).
+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.
 
 ### Live Reload
 This option allows for your JS changes to trigger automatic reload on the connected device/emulator. To enable this option:
diff --git a/docs/DevelopmentSetupAndroid.md b/docs/DevelopmentSetupAndroid.md
index 2cc0a9159373..0d7265c9ddd5 100644
--- a/docs/DevelopmentSetupAndroid.md
+++ b/docs/DevelopmentSetupAndroid.md
@@ -7,9 +7,19 @@ permalink: docs/android-setup.html
 next: linux-windows-support
 ---
 
-This guide describes basic steps of the Android development environment setup that are required to run React Native android apps on an android emulator. We don't discuss developer tool configuration such as IDEs here.
+This guide describes basic steps of the Android development environment setup that are required to run React Native android apps on an android emulator.
 
-### Install the Android SDK (unless you have it)
+### Install Git
+
+  - **On Mac**, if you have installed [XCode](https://developer.apple.com/xcode/), Git is already installed, otherwise run the following:
+
+         brew install git
+
+  - **On Linux**, install Git [via your package manager](https://git-scm.com/download/linux).
+
+  - **On Windows**, download and install [Git for Windows](https://git-for-windows.github.io/). During the setup process, choose "Run Git from Windows Command Prompt", which will add Git to your `PATH` environment variable.
+
+### Install the Android SDK (unless you already have it)
 
 1. [Install the latest JDK](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
 2. Install the Android SDK:
@@ -25,11 +35,18 @@ __IMPORTANT__: Make sure the `ANDROID_HOME` environment variable points to your
         # If you installed the SDK via Homebrew, otherwise ~/Library/Android/sdk
         export ANDROID_HOME=/usr/local/opt/android-sdk
   - **On Linux**, add this to your `~/.bashrc`, `~/.bash_profile` or whatever your shell uses:
-        
+
         export ANDROID_HOME=<path_where_you_unpacked_android_sdk>
 
   - **On Windows**, go to `Control Panel` -> `System and Security` -> `System` -> `Change settings` -> `Advanced` -> `Environment variables` -> `New`
 
+__NOTE__: You need to restart the Command Prompt (Windows) / Terminal Emulator (Mac OS X, Linux) to apply the new Environment variables.
+
+
+### Use gradle daemon
+
+React Native Android use [gradle](https://docs.gradle.org) as a build system. We recommend to enable gradle daemon functionality which may result in up to 50% improvement in incremental build times for changes in java code. Learn [here](https://docs.gradle.org/2.9/userguide/gradle_daemon.html) how to enable it for your platform.
+
 ### Configure your SDK
 
 1. Open the Android SDK Manager (**on Mac** start a new shell and run `android`); in the window that appears make sure you check:
@@ -38,7 +55,7 @@ __IMPORTANT__: Make sure the `ANDROID_HOME` environment variable points to your
   * Android Support Repository
 2. Click "Install Packages"
 
-![SDK Manager window](/react-native/img/AndroidSDK1.png) ![SDK Manager window](/react-native/img/AndroidSDK2.png)
+![SDK Manager window](img/AndroidSDK1.png) ![SDK Manager window](img/AndroidSDK2.png)
 
 ### Install Genymotion
 
@@ -55,9 +72,31 @@ Genymotion is much easier to set up than stock Google emulators. However, it's o
   * Intel x86 Atom System Image (for Android 5.1.1 - API 22)
   * Intel x86 Emulator Accelerator (HAXM installer)
 2. Click "Install Packages".
-3. [Configure hardware acceleration (HAXM)](http://developer.android.com/tools/devices/emulator.html#vm-mac), otherwise the emulator is going to be slow.
+3. [Configure hardware acceleration (HAXM)](http://developer.android.com/tools/devices/emulator.html#vm-mac), otherwise the emulator is going to be slow (or may not run at all).
+  * On a mac this is typically requires opening: `/usr/local/opt/android-sdk/extras/intel/Hardware_Accelerated_Execution_Manager/IntelHAXM_<version>.dmg` and installing the package within.
 4. Create an Android Virtual Device (AVD):
   1. Run `android avd` and click on **Create...**
-  ![Create AVD dialog](/react-native/img/CreateAVD.png)
+  ![Create AVD dialog](img/CreateAVD.png)
   2. With the new AVD selected, click `Start...`
-5. To bring up the developer menu press F2
+5. To bring up the developer menu press F2 (or install [Frappé](http://getfrappe.com))
+
+### Windows Hyper-V Alternative
+
+The [Visual Studio Emulator for Android](https://www.visualstudio.com/en-us/features/msft-android-emulator-vs.aspx) is a free android emulator that is hardware accelerated via Hyper-V. It doesn't require you to install Visual Studio at all.
+
+To use it with react-native you just have to add a key and value to your registry:
+
+1. Open the Run Command (Windows+R)
+2. Enter `regedit.exe`
+3. In the Registry Editor navigate to `HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Android SDK Tools`
+4. Right Click on `Android SDK Tools` and choose `New > String Value`
+5. Set the name to `Path`
+6. Double Click the new `Path` Key and set the value to `C:\Program Files\Android\sdk`. The path value might be different on your machine.
+
+You will also need to run the command `adb reverse tcp:8081 tcp:8081` with this emulator.
+
+Then restart the emulator and when it runs you can just do `react-native run-android` as usual.
+
+### Editing your app's Java code in Android Studio
+
+You can use any editor to edit JavaScript. If you want to use Android Studio to work on native code, from the Welcome screen of Android Studio choose "Import project" and select the `android` folder of your app.
diff --git a/docs/DirectManipulation.md b/docs/DirectManipulation.md
index 77551e590ded..3c6fd46ed930 100644
--- a/docs/DirectManipulation.md
+++ b/docs/DirectManipulation.md
@@ -156,42 +156,11 @@ view using `{...this.props}`. The reason for this is that
 `TouchableOpacity` is actually a composite component, and so in addition
 to depending on `setNativeProps` on its child, it also requires that the
 child perform touch handling. To do this, it passes on [various
-props](https://facebook.github.io/react-native/docs/view.html#onmoveshouldsetresponder)
+props](docs/view.html#onmoveshouldsetresponder)
 that call back to the `TouchableOpacity` component.
 `TouchableHighlight`, in contrast, is backed by a native view and only
 requires that we implement `setNativeProps`.
 
-## Precomputing style
-
-We learned above that `setNativeProps` is a wrapper around
-`RCTUIManager.updateView`, which is also used internally by React to
-perform updates on re-render. One important difference is that
-`setNativeProps` does not call `precomputeStyle`, which is done
-internally by React, and so the `transform` property will not work if
-you try to update it manually with `setNativeProps`. To fix this,
-you can call `precomputeStyle` on your object first:
-
-```javascript
-var precomputeStyle = require('precomputeStyle');
-
-var App = React.createClass({
-  componentDidMount() {
-    var nativeProps = precomputeStyle({transform: [{rotate: '45deg'}]});
-    this._root.setNativeProps(nativeProps);
-  },
-
-  render() {
-    return (
-      <View ref={component => this._root = component}
-            style={styles.container}>
-        <Text>Precompute style!</Text>
-      </View>
-    )
-  },
-});
-```
-[Run this example](https://rnplay.org/apps/8_mIAA)
-
 ## setNativeProps to clear TextInput value
 
 Another very common use case of `setNativeProps` is to clear the value
diff --git a/docs/EmbeddedAppAndroid.md b/docs/EmbeddedAppAndroid.md
index 27a0bfddb1cb..13d573d1f38a 100644
--- a/docs/EmbeddedAppAndroid.md
+++ b/docs/EmbeddedAppAndroid.md
@@ -18,7 +18,7 @@ Since React makes no assumptions about the rest of your technology stack, it's e
 
 In your app's `build.gradle` file add the React Native dependency:
 
-    compile 'com.facebook.react:react-native:0.13.0'
+    compile 'com.facebook.react:react-native:0.20.+'
 
 You can find the latest version of the react-native library on [Maven Central](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.facebook.react%22%20AND%20a%3A%22react-native%22). Next, make sure you have the Internet permission in your `AndroidManifest.xml`:
 
@@ -77,7 +77,7 @@ protected void onResume() {
     super.onResume();
 
     if (mReactInstanceManager != null) {
-        mReactInstanceManager.onResume(this);
+        mReactInstanceManager.onResume(this, this);
     }
 }
 ```
@@ -128,11 +128,10 @@ Copy & paste the following code to `index.android.js` in your root folder — it
 ```js
 'use strict';
 
-var React = require('react-native');
-var {
+import React, {
   Text,
   View
-} = React;
+} from 'react-native';
 
 class MyAwesomeApp extends React.Component {
   render() {
@@ -166,7 +165,7 @@ To run your app, you need to first start the development server. To do this, sim
 
 Now build and run your Android app as normal (e.g. `./gradlew installDebug`). Once you reach your React-powered activity inside the app, it should load the JavaScript code from the development server and display:
 
-![Screenshot](/react-native/img/EmbeddedAppAndroid.png)
+![Screenshot](img/EmbeddedAppAndroid.png)
 
 ## Sharing a ReactInstance across multiple Activities / Fragments in your app
 
diff --git a/docs/EmbeddedAppIOS.md b/docs/EmbeddedAppIOS.md
index d913d0beb0d4..dbdb5e31d9c2 100644
--- a/docs/EmbeddedAppIOS.md
+++ b/docs/EmbeddedAppIOS.md
@@ -4,7 +4,7 @@ title: Integrating with Existing Apps
 layout: docs
 category: Guides (iOS)
 permalink: docs/embedded-app-ios.html
-next: native-modules-android
+next: communication-ios
 ---
 
 Since React makes no assumptions about the rest of your technology stack – it’s commonly noted as simply the `V` in `MVC` – it’s easily embeddable within an existing non-React Native app. In fact, it integrates with other best practice community tools like [CocoaPods](http://cocoapods.org/).
@@ -14,8 +14,11 @@ Since React makes no assumptions about the rest of your technology stack – it
 - [CocoaPods](http://cocoapods.org/) – `gem install cocoapods`
 - [Node.js](http://nodejs.org)
   - Install **nvm** with [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install node && nvm alias default node`, which installs the latest version of Node.js and sets up your terminal so you can run it by typing `node`.  With nvm you can install multiple versions of Node.js and easily switch between them.
-  - If you are using Node 5.0 or newer, we recommend installing npm 2, which is much faster than npm 3. After installing Node, run `npm install -g npm@2`
-- Install your copy of React Native under your `node_modules` directory where your JS resides.
+- Install the `react-native` package from npm by running the following command in the root directory of your project:
+  - `npm install react-native`
+
+At this point you should have the React Native package installed under a directory named `node_modules` as a sibling to your `.xcodeproj` file.
+
 
 ## Install React Native Using CocoaPods
 
@@ -26,7 +29,7 @@ When you are ready to work with CocoaPods, add the following lines to `Podfile`.
 ```ruby
 # Depending on how your project is organized, your node_modules directory may be
 # somewhere else; tell CocoaPods where you've installed react-native from npm
-pod 'React', :path => '../node_modules/react-native', :subspecs => [
+pod 'React', :path => './node_modules/react-native', :subspecs => [
   'Core',
   'RCTImage',
   'RCTNetwork',
@@ -63,11 +66,10 @@ Copy & paste following starter code for `index.ios.js` – it’s a barebones Re
 ```
 'use strict';
 
-var React = require('react-native');
-var {
+import React, {
   Text,
   View
-} = React;
+} from 'react-native';
 
 var styles = React.StyleSheet.create({
   container: {
@@ -95,7 +97,7 @@ React.AppRegistry.registerComponent('SimpleApp', () => SimpleApp);
 
 You should now add a container view for the React Native component. It can be any `UIView` in your app.
 
-![Container view example](/react-native/img/EmbeddedAppContainerViewExample.png)
+![Container view example](img/EmbeddedAppContainerViewExample.png)
 
 However, let's subclass `UIView` for the sake of clean code. Let's name it `ReactView`. Open up `Yourproject.xcworkspace` and create a new class `ReactView` (You can name it whatever you like :)).
 
@@ -126,7 +128,7 @@ Ready for the most interesting part? Now we shall create the `RCTRootView`, wher
 In `ReactView.m`, we need to first initiate `RCTRootView` with the URI of your `index.ios.bundle`. `index.ios.bundle` will be created by packager and served by React Native server, which will be discussed later on.
 
 ```
-NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
+NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
 // For production use, this `NSURL` could instead point to a pre-bundled file on disk:
 //
 //   NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
@@ -136,6 +138,7 @@ NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.b
 //   curl http://localhost:8081/index.ios.bundle -o main.jsbundle
 RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                     moduleName: @"SimpleApp"
+                                             initialProperties:nil
                                                  launchOptions:nil];
 ```
 
@@ -151,18 +154,40 @@ rootView.frame = self.bounds;
 In root directory, we need to start React Native development server.
 
 ```
-(JS_DIR=`pwd`/ReactComponent; cd Pods/React; npm run start -- --root $JS_DIR)
+(JS_DIR=`pwd`/ReactComponent; cd node_modules/react-native; npm run start -- --root $JS_DIR)
 ```
 
 This command will start up a React Native development server within our CocoaPods dependency to build our bundled script. The `--root` option indicates the root of your React Native apps – this will be our `ReactComponents` directory containing the single `index.ios.js` file. This running server will package up the `index.ios.bundle` file accessible via `http://localhost:8081/index.ios.bundle`.
 
+## Update App Transport Security
+
+On iOS 9 and above the app won't be a able to connect over http to localhost unless specifically told so. See this thread for alternatives and instructions: http://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http.
+
+It is recommended that you add an App Transport Security exception for `localhost` in your app's `Info.plist` file:
+
+```xml
+<key>NSAppTransportSecurity</key>
+<dict>
+    <key>NSExceptionDomains</key>
+    <dict>
+        <key>localhost</key>
+        <dict>
+            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+            <true/>
+        </dict>
+    </dict>
+</dict>
+```
+
+If you don't do this, you will see the error - `Could not connect to development server.` when connecting to your server over http.
+
 ## Compile And Run
 
 Now compile and run your app. You shall now see your React Native app running inside of the `ReactView`.
 
-![Example](/react-native/img/EmbeddedAppExample.png)
+![Example](img/EmbeddedAppExample.png)
 
-Live reload works from the simulator, too! You’ve got a simple React component totally encapsulated behind an Objective-C `UIView` subclass.
+Live reload and all of the debugging tools will work from the simulator (make sure that DEBUG=1 is set under Build Settings -> Preprocessor Macros).  You've got a simple React component totally encapsulated behind an Objective-C `UIView` subclass.
 
 ## Conclusion
 
diff --git a/docs/GestureResponderSystem.md b/docs/GestureResponderSystem.md
index fac70d0d28c4..b7c8dbc40e19 100644
--- a/docs/GestureResponderSystem.md
+++ b/docs/GestureResponderSystem.md
@@ -51,8 +51,8 @@ If the view is responding, the following handlers can be called:
      + `identifier` - The ID of the touch
      + `locationX` - The X position of the touch, relative to the element
      + `locationY` - The Y position of the touch, relative to the element
-     + `pageX` - The X position of the touch, relative to the screen
-     + `pageY` - The Y position of the touch, relative to the screen
+     + `pageX` - The X position of the touch, relative to the root element
+     + `pageY` - The Y position of the touch, relative to the root element
      + `target` - The node id of the element receiving the touch event
      + `timestamp` - A time identifier for the touch, useful for velocity calculation
      + `touches` - Array of all current touches on the screen
@@ -68,4 +68,4 @@ However, sometimes a parent will want to make sure that it becomes responder. Th
 
 ### PanResponder
 
-For higher-level gesture interpretation, check out [PanResponder](/react-native/docs/panresponder.html).
+For higher-level gesture interpretation, check out [PanResponder](docs/panresponder.html).
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index 714e06e7bd24..245ecc4c5a34 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -4,7 +4,7 @@ title: Getting Started
 layout: docs
 category: Quick Start
 permalink: docs/getting-started.html
-next: android-setup
+next: getting-started-linux
 ---
 
 ## Requirements
@@ -13,7 +13,6 @@ next: android-setup
 2. [Homebrew](http://brew.sh/) is the recommended way to install Watchman and Flow.
 3. Install [Node.js](https://nodejs.org/) 4.0 or newer.
   - Install **nvm** with [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install node && nvm alias default node`, which installs the latest version of Node.js and sets up your terminal so you can run it by typing `node`. With nvm you can install multiple versions of Node.js and easily switch between them.
-  - If you are using Node 5.0 or newer, we recommend installing npm 2, which is much faster than npm 3. After installing Node, run `npm install -g npm@2`.
   - New to [npm](https://docs.npmjs.com/)?
 4. `brew install watchman`. We recommend installing [watchman](https://facebook.github.io/watchman/docs/install.html), otherwise you might hit a node file watching bug.
 5. `brew install flow`, if you want to use [flow](http://www.flowtype.org).
@@ -26,15 +25,23 @@ We recommend periodically running `brew update && brew upgrade` to keep your pro
 
 ## Android Setup
 
-To write React Native apps for Android, you will need to install the Android SDK (and an Android emulator if you want to work on your app without having to use a physical device). See [Android setup guide](android-setup.html) for instructions on how to set up your Android environment.
+To write React Native apps for Android, you will need to install the Android SDK (and an Android emulator if you want to work on your app without having to use a physical device). See [Android setup guide](docs/android-setup.html) for instructions on how to set up your Android environment.
 
-_NOTE:_ There is experimental [Windows and Linux support](/react-native/docs/linux-windows-support.html) for Android development.
+_NOTE:_ There is experimental [Windows and Linux support](docs/linux-windows-support.html) for Android development.
 
 ## Quick start
 
+Install the React Native command line tools:
+
     $ npm install -g react-native-cli
+
+__NOTE__: If you see the error, `EACCES: permission denied`, please run the command: `sudo npm install -g react-native-cli`.
+
+Create a React Native project:
+
     $ react-native init AwesomeProject
 
+
 **To run the iOS app:**
 
 - `$ cd AwesomeProject`
@@ -42,6 +49,8 @@ _NOTE:_ There is experimental [Windows and Linux support](/react-native/docs/lin
 - Open `index.ios.js` in your text editor of choice and edit some lines.
 - Hit ⌘-R in your iOS simulator to reload the app and see your change!
 
+_Note: If you are using an iOS device, see the [Running on iOS Device page](docs/running-on-device-ios.html#content)._
+
 **To run the Android app:**
 
 - `$ cd AwesomeProject`
@@ -50,11 +59,11 @@ _NOTE:_ There is experimental [Windows and Linux support](/react-native/docs/lin
 - Press the menu button (F2 by default, or ⌘-M in Genymotion) and select *Reload JS* to see your change!
 - Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your app's logs
 
-_Note: If you are using a device, see the [Running on Device page](http://facebook.github.io/react-native/docs/running-on-device-android.html#content)._
+_Note: If you are using an Android device, see the [Running on Android Device page](docs/running-on-device-android.html#content)._
 
 Congratulations! You've successfully run and modified your first React Native app.
 
-_If you run into any issues getting started, see the [troubleshooting page](/react-native/docs/troubleshooting.html#content)._
+_If you run into any issues getting started, see the [troubleshooting page](docs/troubleshooting.html#content)._
 
 ## Adding Android to an existing React Native project
 
diff --git a/docs/GettingStartedOnLinux.md b/docs/GettingStartedOnLinux.md
new file mode 100644
index 000000000000..cbdef058f256
--- /dev/null
+++ b/docs/GettingStartedOnLinux.md
@@ -0,0 +1,116 @@
+---
+id: getting-started-linux
+title: Getting Started on Linux
+layout: docs
+category: Quick Start
+permalink: docs/getting-started-on-linux.html
+next: android-setup
+---
+
+This guide is essentially a beginner-friendly version of the [Getting Started](/react-native/docs/getting-started.html) page for React Native on Linux.
+
+### Prerequisites
+
+For the purposes of this guide, we assume that you're working on Ubuntu Linux 14.04 LTS.
+
+Before following this guide, you should have installed the Android SDK and run a successful Java-based "Hello World" app for Android.
+
+See [Android Setup](/react-native/docs/android-setup.html) for details.
+
+#### Installing NodeJS
+
+The first thing you need to do is to install NodeJS, a popular Javascript implementation.
+
+Fire up the Terminal and paste the following commands to install NodeJS from the [NodeSource](https://nodesource.com/) repository:
+
+```sh
+sudo apt-get install -y build-essential
+curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
+sudo apt-get install -y nodejs
+sudo ln -s /usr/bin/nodejs /usr/bin/node
+```
+__NOTE__: The above instructions are for Ubuntu. If you're on a different distro,  please follow the instructions on the [NodeJS website](https://nodejs.org/en/download/).
+
+#### Installing Watchman
+
+[watchman](https://facebook.github.io/watchman/docs/install.html) is a tool by Facebook for watching changes in the filesystem. You need to install it for better performance and avoid a node file-watching bug.
+
+Paste the following into your terminal to compile watchman from source and install it:
+
+```sh
+git clone https://github.com/facebook/watchman.git
+cd watchman
+git checkout v4.1.0  # the latest stable release
+./autogen.sh
+./configure
+make
+sudo make install
+```
+
+#### Installing Flow
+
+Flow is a static type checker for JavaScript. To install it, paste the following in the terminal:
+
+```sh
+sudo npm install -g flow-bin
+```
+
+## Setting up an Android Device
+
+Let's set up an Android device to run our starter project. 
+
+First thing is to plug in your device and check the manufacturer code by using `lsusb`, which should output something like this:
+
+```bash
+$ lsusb
+Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
+Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+Bus 001 Device 003: ID 22b8:2e76 Motorola PCS 
+Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
+Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
+Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+```
+These lines represent the USB devices currently connected to your machine.
+
+You want the line that represents your phone. If you're in doubt, try unplugging your phone and running the command again:
+
+```bash
+$ lsusb
+Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
+Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
+Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
+Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
+```
+You'll see that after removing the phone, the line which has the phone model ("Motorola PCS" in this case) disappeared from the list. This is the line that we care about.
+
+`Bus 001 Device 003: ID 22b8:2e76 Motorola PCS`
+
+From the above line, you want to grab the first four digits from the device ID:
+
+`22b8:2e76` 
+
+In this case, it's `22b8`. That's the identifier for Motorola.
+
+You'll need to input this into your udev rules in order to get up and running:
+
+```sh
+echo SUBSYSTEM=="usb", ATTR{idVendor}=="22b8", MODE="0666", GROUP="plugdev" | sudo tee /etc/udev/rules.d/51-android-usb.rules
+```
+
+Make sure that you replace `22b8` with the identifier you get in the above command.
+
+Now check that your device is properly connecting to ADB, the Android Debug Bridge, by using `adb devices`.
+
+```bash
+List of devices attached
+TA9300GLMK	device
+```
+
+For more information, please see the docs for [running an Android app on your device](/react-native/docs/running-on-device-android.html).
+
+## Next Steps
+
+Your Android device and your tools are all ready to go. You can now follow the instructions in the [Quick Start](http://facebook.github.io/react-native/docs/getting-started.html#quick-start) guide to install React Native and start your first project.
diff --git a/docs/Images.md b/docs/Images.md
index 4dad3998a445..7685626eb37b 100644
--- a/docs/Images.md
+++ b/docs/Images.md
@@ -60,6 +60,8 @@ var icon = this.props.active ? require('./my-icon-active.png') : require('./my-i
 <Image source={icon} />
 ```
 
+Note that image sources required this way include size (width, height) info for the Image. If you need to scale the image dynamically (i.e. via flex), you may need to manually set { width: undefined, height: undefined } on the style attribute.
+
 **Available React Native 0.14+**. If you've generated your project with 0.13 or earlier, read this. The new asset system relies on build hooks for [Xcode](https://github.com/facebook/react-native/pull/3523) and [Gradle](https://github.com/facebook/react-native/commit/9dc036d2b99e6233297c55a3490bfc308e34e75f) that are included in new projects generated with `react-native init`. If you generated your projects before that, you'll have to manually add them to your projects to use the new images asset system. See [Upgrading](/react-native/docs/upgrading.html) for instructions on how to do this.
 
 ## Images From Hybrid App's Resources
@@ -88,7 +90,7 @@ Many of the images you will display in your app will not be available at compile
 
 ## Local Filesystem Images
 
-See [CameraRoll](/react-native/docs/cameraroll.html) for an example of
+See [CameraRoll](docs/cameraroll.html) for an example of
 using local resources that are outside of `Images.xcassets`.
 
 ### Best Camera Roll Image
diff --git a/docs/JavaScriptEnvironment.md b/docs/JavaScriptEnvironment.md
index dcd866156c15..8bda774986da 100644
--- a/docs/JavaScriptEnvironment.md
+++ b/docs/JavaScriptEnvironment.md
@@ -22,7 +22,7 @@ Syntax transformers make writing code more enjoyable by allowing you to use new
 
 As of version 0.5.0, React Native ships with the [Babel JavaScript compiler](https://babeljs.io). Check [Babel documentation](http://babeljs.io/docs/advanced/transformers/) on its supported transformations for more details.
 
-Here's a full list of React Native's [enabled transformations](https://github.com/facebook/react-native/blob/master/packager/transformer.js#L21).
+Here's a full list of React Native's [enabled transformations](https://github.com/facebook/react-native/blob/master/babel-preset/configs/main.js#L16).
 
 ES5
 
@@ -36,6 +36,7 @@ ES6
 * [Classes](http://babeljs.io/docs/learn-es2015/#classes): `class C extends React.Component { render() { return <View />; } }`
 * [Constants](https://babeljs.io/docs/learn-es2015/#let-const): `const answer = 42;`
 * [Destructuring](http://babeljs.io/docs/learn-es2015/#destructuring): `var {isActive, style} = this.props;`
+* [for...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of): `for (var num of [1, 2, 3]) {}`
 * [Modules](http://babeljs.io/docs/learn-es2015/#modules): `import React, { Component } from 'react-native';`
 * [Computed Properties](http://babeljs.io/docs/learn-es2015/#enhanced-object-literals): `var key = 'abc'; var obj = {[key]: 10};`
 * Object Consise Method: `var obj = { method() { return 10; } };`
@@ -48,3 +49,36 @@ ES7
 * [Object Spread](https://github.com/sebmarkbage/ecmascript-rest-spread): `var extended = { ...obj, a: 10 };`
 * [Function Trailing Comma](https://github.com/jeffmo/es-trailing-function-commas): `function f(a, b, c,) { }`
 * [Async Functions](https://github.com/tc39/ecmascript-asyncawait): `async function doStuffAsync() { const foo = await doOtherStuffAsync(); }`;
+
+Specific
+
+* [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html): `<View style={{color: 'red'}} />`
+* [Flow](http://flowtype.org/): `function foo(x: ?number): string {}`
+
+
+## Polyfills
+
+Many standards functions are also available on all the supported JavaScript runtimes.
+
+Browser
+
+* [console.{log, warn, error, info, trace, table}](https://developer.chrome.com/devtools/docs/console-api)
+* [CommonJS require](https://nodejs.org/docs/latest/api/modules.html)
+* [XMLHttpRequest, fetch](/react-native/docs/network.html#content)
+* [{set, clear}{Timeout, Interval, Immediate}, {request, cancel}AnimationFrame](/react-native/docs/timers.html#content)
+* [navigator.geolocation](/react-native/docs/geolocation.html#content)
+ 
+ES6
+
+* [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+* String.prototype.{[startsWith](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith), [endsWith](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith), [repeat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeats), [includes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)}
+* [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from)
+* Array.prototype.{[find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find), [findIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)}
+
+ES7
+
+* Object.{[entries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries), [values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values)}
+
+Specific
+
+* `__DEV__`
diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md
index 6ce63307e609..fb205cb2be30 100644
--- a/docs/KnownIssues.md
+++ b/docs/KnownIssues.md
@@ -15,61 +15,50 @@ However, you can still use the Console feature of the devtools, and debugging Ja
 
 ### Missing Modules and Native Views
 
-This is an initial release of React Native Android and therefore not all of the views present on iOS are released on Android. We are very much interested in the communities' feedback on the next set of modules and views for Open Source. Not all native views between iOS and Android have a 100% equivalent representation, here it will be necessary to use a counterpart eg using ProgressBar on Android in place of ActivityIndicator on iOS.
-
-Our provisional plan for common views and modules includes:
+The work on React Native for Android started later than React Native for iOS. Not all of the views present on iOS have been released on Android yet.
 
 #### Views
 
-```
-Swipe Refresh
-Spinner
-ART
-Maps
-Modal
-Webview
-```
+- Maps - Please use Leland Richardson's [react-native-maps](https://github.com/lelandrichardson/react-native-maps) as it is more feature-complete than our internal implementation at fb.
+- Modal
+- Slider (also known as SeekBar)
 
 #### Modules
 
-```
-Geo Location
-Net Info
-Camera Roll
-App State
-Dialog
-Intent
-Media
-Pasteboard
-PushNotificationIOS
-Alert
-```
+- Media
+- PushNotificationIOS
 
 ### Some props are only supported on one platform
 
-There are properties that work on one platform only, either because they can inherently only be supported on that platform or because they haven't been implemented on the other platforms yet. All of these are annotated with `@platform` in JS docs and have a small badge next to them on the website. See e.g. [Image](https://facebook.github.io/react-native/docs/image.html).
+There are properties that work on one platform only, either because they can inherently only be supported on that platform or because they haven't been implemented on the other platforms yet. All of these are annotated with `@platform` in JS docs and have a small badge next to them on the website. See e.g. [Image](docs/image.html).
 
 ### Platform parity
 
 There are known cases where the APIs could be made more consistent across iOS and Android:
 
-- `<AndroidViewPager>` (to be open sourced soon) and `<ScrollView pagingEnabled={true}>` on iOS do a similar thing. We might want to unify them to `<ViewPager>`.
-- `alert()` needs Android support (once the Dialogs module is open sourced)
-- It might be possible to bring `LinkingIOS` and `IntentAndroid` (to be open sourced) closer together.
+- `<ViewPagerAndroid>` and `<ScrollView pagingEnabled={true}>` on iOS do a similar thing. We might want to unify them to `<ViewPager>`.
 - `ActivityIndicator` could render a native spinning indicator on both platforms (currently this is done using `ActivityIndicatorIOS` on iOS and `ProgressBarAndroid` on Android).
-- `ProgressBar` could render a horizontal progress bar on both platforms (currently only supported on iOS via `ProgressViewIOS`).
+- `ProgressBar` could render a horizontal progress bar on both platforms (on iOS this is `ProgressViewIOS`, on Android it's `ProgressBarAndroid`).
 
-### Publishing modules on Android
+### Using 3rd-party native modules
 
-There is currently no easy way of publishing custom native modules on Android. Smooth work flow for contributors is important and this will be looked at very closely after the initial Open Source release. Of course the aim will be to streamline and optimize the process between iOS and Android as much as possible.
+There are many awesome 3rd-party modules: [JS.coach](https://js.coach/react-native)
+
+Adding these to your apps should be made simpler. Here's [an example](https://github.com/apptailor/react-native-google-signin) how this is done currently.
 
 ### The `overflow` style property defaults to `hidden` and cannot be changed on Android
 
 This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks.
 
-### No support for shadows on Android
+Another issue with `overflow: 'hidden'` on Android: a view is not clipped by the parent's `borderRadius` even if the parent has `overflow: 'hidden'` enabled – the corners of the inner view will be visible outside of the rounded corners. This is only on Android; it works as expected on iOS. See a [demo of the bug](https://rnplay.org/apps/BlGjdQ) and the [corresponding issue](https://github.com/facebook/react-native/issues/3198).
+
+### View shadows
+
+The `shadow*` [view styles](docs/view.html#style) apply on iOS, and the `elevation` view prop is available on Android. Setting `elevation` on Android is equivalent to using the [native elevation API](https://developer.android.com/training/material/shadows-clipping.html#Elevation), and has the same limitations (most significantly, it only works on Android 5.0+). Setting `elevation` on Android also affects the z-order for overlapping views.
+
+### Android M permissions
 
-We don't support shadows on Android currently. These are notoriously hard to implement as they require drawing outside of a view's bounds and Android's invalidation logic has a hard time with that. A possible solution is to use [elevation](https://developer.android.com/training/material/shadows-clipping.html), but more experimentation will be required.
+The open source version of React Native doesn't yet support the [Android M permission model](http://developer.android.com/training/permissions/requesting.html).
 
 ### Layout-only nodes on Android
 
diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md
index db29a4bc2f4e..0ce0849531a4 100644
--- a/docs/LinkingLibraries.md
+++ b/docs/LinkingLibraries.md
@@ -24,24 +24,58 @@ error as soon as you try to use the library._
 
 ## Here the few steps to link your libraries that contain native code
 
-### Step 1
+### Automatic linking
+
+"[rnpm](http://github.com/rnpm/rnpm)" is a community project that allows linking of native dependencies automatically:
+
+#### Step 1
+
+Install `rnpm`:
+```bash
+$ npm install rnpm -g
+```
+
+**Note:** _`rnpm` requires `node` version 4.1 or higher_
+
+#### Step 2
+
+Install a library with native dependencies:
+```bash
+$ npm install <library-with-native-dependencies> --save
+```
+
+**Note:** _`--save` or `--save-dev` flag is very important for this step. `rnpm` will link
+your libs based on `dependencies` and `devDependencies` in your `package.json` file._
+
+#### Step 3
+
+Link your native dependencies:
+```bash
+$ rnpm link
+```
+
+Done! All libraries with a native dependencies should be successfully linked to your iOS/Android project.
+
+### Manual linking
+
+#### Step 1
 
 If the library has native code, there must be a `.xcodeproj` file inside it's
 folder.
 Drag this file to your project on Xcode (usually under the `Libraries` group
 on Xcode);
 
-![](/react-native/img/AddToLibraries.png)
+![](img/AddToLibraries.png)
 
-### Step 2
+#### Step 2
 
 Click on your main project file (the one that represents the `.xcodeproj`)
 select `Build Phases` and drag the static library from the `Products` folder
 inside the Library you are importing to `Link Binary With Libraries`
 
-![](/react-native/img/AddToBuildPhases.png)
+![](img/AddToBuildPhases.png)
 
-### Step 3
+#### Step 3
 
 Not every library will need this step, what you need to consider is:
 
@@ -54,7 +88,7 @@ This step is not necessary for libraries that we ship with React Native with the
 exception of `PushNotificationIOS` and `LinkingIOS`.
 
 In the case of the `PushNotificationIOS` for example, you have to call a method
-on the library from your `AppDelegate` every time a new push notifiation is
+on the library from your `AppDelegate` every time a new push notification is
 received.
 
 For that we need to know the library's headers. To achieve that you have to go
@@ -63,4 +97,4 @@ Paths`. There you should include the path to your library (if it has relevant
 files on subdirectories remember to make it `recursive`, like `React` on the
 example).
 
-![](/react-native/img/AddToSearchPaths.png)
+![](img/AddToSearchPaths.png)
diff --git a/docs/LinuxWindowsSupport.md b/docs/LinuxWindowsSupport.md
index caff0bc027b9..65bc9e851eb1 100644
--- a/docs/LinuxWindowsSupport.md
+++ b/docs/LinuxWindowsSupport.md
@@ -20,4 +20,8 @@ As of **version 0.14** Android development with React native is mostly possible
 On Windows the packager won't be started automatically when you run `react-native run-android`. You can start it manually using:
     
     cd MyAwesomeApp
-    node node_modules/react-native/packager/packager.js
+    react-native start
+
+If you hit a `ERROR  Watcher took too long to load` on Windows, try increasing the timeout in [this file](https://github.com/facebook/react-native/blob/master/packager/react-packager/src/DependencyResolver/FileWatcher/index.js#L16) (under your node_modules/react-native).
+
+
diff --git a/docs/NativeComponentsAndroid.md b/docs/NativeComponentsAndroid.md
index f70c32ee6188..3c32591c4579 100644
--- a/docs/NativeComponentsAndroid.md
+++ b/docs/NativeComponentsAndroid.md
@@ -57,7 +57,7 @@ Views are created in the `createViewInstance` method, the view should initialize
 
 ## 3. Expose view property setters using `@ReactProp` (or `@ReactPropGroup`) annotation
 
-Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with `@ReactProp` (or `@ReactPropGroup`). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be declared as a `void` method and should be `public`. Property type sent to JS is determined automatically based on the type of value argumen of the setter. The following type of values are currently supported: `boolean`, `int`, `float`, `double`, `String`, `Boolean`, `Integer`, `ReadableArray`, `ReadableMap`. 
+Properties that are to be reflected in JavaScript needs to be exposed as setter method annotated with `@ReactProp` (or `@ReactPropGroup`). Setter method should take view to be updated (of the current view type) as a first argument and property value as a second argument. Setter should be declared as a `void` method and should be `public`. Property type sent to JS is determined automatically based on the type of value argument of the setter. The following type of values are currently supported: `boolean`, `int`, `float`, `double`, `String`, `Boolean`, `Integer`, `ReadableArray`, `ReadableMap`.
 
 Annotation `@ReactProp` has one obligatory argument `name` of type `String`. Name assigned to the `@ReactProp` annotation linked to the setter method is used to reference the property on JS side.
 
@@ -72,12 +72,12 @@ Setter declaration requirements for methods annotated with `@ReactPropGroup` are
   public void setSrc(ReactImageView view, @Nullable String src) {
     view.setSource(src);
   }
-  
+
   @ReactProp(name = "borderRadius", defaultFloat = 0f)
   public void setBorderRadius(ReactImageView view, float borderRadius) {
     view.setBorderRadius(borderRadius);
   }
-  
+
   @ReactProp(name = ViewProps.RESIZE_MODE)
   public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
     view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
@@ -86,7 +86,7 @@ Setter declaration requirements for methods annotated with `@ReactPropGroup` are
 
 ## 4. Register the `ViewManager`
 
-The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](NativeModulesAndroid.md), via the applications package member function `createViewManagers.`
+The final Java step is to register the ViewManager to the application, this happens in a similar way to [Native Modules](docs/native-modules-android.html), via the applications package member function `createViewManagers.`
 
 ```java
   @Override
@@ -105,7 +105,7 @@ The very final step is to create the JavaScript module that defines the interfac
 ```js
 // ImageView.js
 
-var { requireNativeComponent, PropTypes } = require('react-native');
+import { requireNativeComponent, PropTypes } from 'react-native';
 
 var iface = {
   name: 'ImageView',
diff --git a/docs/NativeComponentsIOS.md b/docs/NativeComponentsIOS.md
index e506bc6c4203..e78d86714766 100644
--- a/docs/NativeComponentsIOS.md
+++ b/docs/NativeComponentsIOS.md
@@ -49,7 +49,7 @@ Then you just need a little bit of JavaScript to make this a usable React compon
 ```javascript
 // MapView.js
 
-var { requireNativeComponent } = require('react-native');
+import { requireNativeComponent } from 'react-native';
 
 // requireNativeComponent automatically resolves this to "RCTMapManager"
 module.exports = requireNativeComponent('RCTMap', null);
@@ -79,8 +79,7 @@ This isn't very well documented though - in order to know what properties are av
 
 ```javascript
 // MapView.js
-var React = require('react-native');
-var { requireNativeComponent } = React;
+import React, { requireNativeComponent } from 'react-native';
 
 class MapView extends React.Component {
   render() {
@@ -267,7 +266,7 @@ RCT_EXPORT_MODULE()
 }
 ```
 
-You can see we're setting the manager as the delegate for every view that it vends, then in the delegate method `-mapView:regionDidChangeAnimated:` the region is combined with the `reactTag` target to make an event that is dispatched to the corresponding React component instance in your application via `sendInputEventWithName:body:`.  The event name `@"topChange"` maps to the `onChange` callback prop in JavaScript (mappings are [here](https://github.com/facebook/react-native/blob/master/React/Modules/RCTUIManager.m#L1165)).  This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:
+You can see we're setting the manager as the delegate for every view that it vends, then in the delegate method `-mapView:regionDidChangeAnimated:` the region is combined with the `reactTag` target to make an event that is dispatched to the corresponding React component instance in your application via `sendInputEventWithName:body:`.  The event name `@"topChange"` maps to the `onChange` callback prop in JavaScript.  This callback is invoked with the raw event, which we typically process in the wrapper component to make a simpler API:
 
 ```javascript
 // MapView.js
@@ -302,7 +301,8 @@ Since all our native react views are subclasses of `UIView`, most style attribut
 ```javascript
 // DatePickerIOS.ios.js
 
-var RCTDatePickerIOSConsts = require('react-native').NativeModules.UIManager.RCTDatePicker.Constants;
+import { UIManager } from 'react-native';
+var RCTDatePickerIOSConsts = UIManager.RCTDatePicker.Constants;
 ...
   render: function() {
     return (
diff --git a/docs/NativeModulesAndroid.md b/docs/NativeModulesAndroid.md
index c7dcf2eb9750..5727c22606ad 100644
--- a/docs/NativeModulesAndroid.md
+++ b/docs/NativeModulesAndroid.md
@@ -86,6 +86,8 @@ ReadableMap -> Object
 ReadableArray -> Array
 ```
 
+Read more about [ReadableMap](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java) and [ReadableArray](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java)
+
 ### Register the Module
 
 The last step within Java is to register the Module; this happens in the `createNativeModules` of your apps package. If a module is not registered it will not be available from JavaScript.
@@ -106,50 +108,38 @@ class AnExampleReactPackage implements ReactPackage {
   }
 ```
 
-The package needs to be provided to the **ReactInstanceManager** when it is built. To accomplish this you will need to insert an `.addPackage(new YourPackageName())` call to the `mReactInstanceManager = ReactInstanceManager.builder()` call chain.
-
-Refer to the code below and add the `addPackage` statement to your application's `MainActivity.java` file. This file exists under the android folder in your react-native application directory. The path to this file is: `android/app/src/main/java/com/your-app-name/MainActivity.java`.
-
+The package needs to be provided in the `getPackages` method of the `MainActivity.java` file. This file exists under the android folder in your react-native application directory. The path to this file is: `android/app/src/main/java/com/your-app-name/MainActivity.java`.
 
 ```java
-mReactInstanceManager = ReactInstanceManager.builder()
-  .setApplication(getApplication())
-  .setBundleAssetName("AnExampleApp.android.bundle")
-  .setJSMainModuleName("Examples/AnExampleApp/AnExampleApp.android")
-  .addPackage(new AnExampleReactPackage())  // <-- Add this line with your package name.
-  .setUseDeveloperSupport(true)
-  .setInitialLifecycleState(LifecycleState.RESUMED)
-  .build();
+protected List<ReactPackage> getPackages() {
+    return Arrays.<ReactPackage>asList(
+            new MainReactPackage(),
+            new AnExampleReactPackage()); // <-- Add this line with your package name.
+}
 ```
 
 To make it simpler to access your new functionality from JavaScript, it is common to wrap the native module in a JavaScript module. This is not necessary but saves the consumers of your library the need to pull it off of `NativeModules` each time. This JavaScript file also becomes a good location for you to add any JavaScript side functionality.
 
-```java
-/**
- * @providesModule ToastAndroid
- */
-
+```js
 'use strict';
-
 /**
- * This exposes the native ToastAndroid module as a JS module. This has a function 'show'
- * which takes the following parameters:
+ * This exposes the native ToastAndroid module as a JS module. This has a
+ * function 'show' which takes the following parameters:
  *
  * 1. String message: A string with the text to toast
- * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or ToastAndroid.LONG
+ * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or
+ *    ToastAndroid.LONG
  */
-var { NativeModules } = require('react-native');
+import { NativeModules } from 'react-native';
 module.exports = NativeModules.ToastAndroid;
 ```
 
-Now, from your JavaScript file you can call the method like this:
+Now, from your other JavaScript file you can call the method like this:
 
 ```js
-var ToastAndroid = require('ToastAndroid')
-ToastAndroid.show('Awesome', ToastAndroid.SHORT);
+import ToastAndroid from './ToastAndroid';
 
-// Note: We require ToastAndroid without any relative filepath because
-// of the @providesModule directive. Using @providesModule is optional.
+ToastAndroid.show('Awesome', ToastAndroid.SHORT);
 ```
 
 ## Beyond Toasts
@@ -203,6 +193,62 @@ A native module is supposed to invoke its callback only once. It can, however, s
 
 It is very important to highlight that the callback is not invoked immediately after the native function completes - remember that bridge communication is asynchronous, and this too is tied to the run loop.
 
+### Promises
+
+Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's `async/await` syntax. When the last parameter of a bridged native method is a `Promise`, its corresponding JS method will return a JS Promise object.
+
+Refactoring the above code to use a promise instead of callbacks looks like this:
+
+```java
+public class UIManagerModule extends ReactContextBaseJavaModule {
+
+...
+
+  @ReactMethod
+  public void measureLayout(
+      int tag,
+      int ancestorTag,
+      Promise promise) {
+    try {
+      measureLayout(tag, ancestorTag, mMeasureBuffer);
+
+      WritableMap map = Arguments.createMap();
+
+      map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0]));
+      map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1]));
+      map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2]));
+      map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3]));
+
+      promise.resolve(map);
+    } catch (IllegalViewOperationException e) {
+      promise.reject(e);
+    }
+  }
+
+...
+```
+
+The JavaScript counterpart of this method returns a Promise. This means you can use the `await` keyword within an async function to call it and wait for its result:
+
+```js
+async function measureLayout() {
+  try {
+    var {
+      relativeX,
+      relativeY,
+      width,
+      height,
+    } = await UIManager.measureLayout(100, 100);
+
+    console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);
+  } catch (e) {
+    console.error(e);
+  }
+}
+
+measureLayout();
+```
+
 ### Threading
 
 Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there.
@@ -229,7 +275,7 @@ sendEvent(reactContext, "keyboardWillShow", params);
 JavaScript modules can then register to receive events by `addListenerOn` using the `Subscribable` mixin
 
 ```js
-var { DeviceEventEmitter } = require('react-native');
+import { DeviceEventEmitter } from 'react-native';
 ...
 
 var ScrollResponderMixin = {
@@ -259,4 +305,123 @@ componentWillMount: function() {
   });
 }
 ...
-```
\ No newline at end of file
+```
+
+### Getting activity result from `startActivityForResult`
+
+You'll need to listen to `onActivityResult` if you want to get results from an activity you started with `startActivityForResult`. To to do this, the module must implement `ActivityEventListener`. Then, you need to register a listener in the module's constructor,
+
+```java
+reactContext.addActivityEventListener(this);
+```
+
+Now you can listen to `onActivityResult` by implementing the following method:
+
+```java
+@Override
+public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
+  // Your logic here
+}
+```
+
+We will implement a simple image picker to demonstrate this. The image picker will expose the method `pickImage` to JavaScript, which will return the path of the image when called.
+
+```java
+public class ImagePickerModule extends ReactContextBaseJavaModule implements ActivityEventListener {
+
+  private static final int IMAGE_PICKER_REQUEST = 467081;
+  private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
+  private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
+  private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
+  private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";
+
+  private Promise mPickerPromise;
+
+  public ImagePickerModule(ReactApplicationContext reactContext) {
+    super(reactContext);
+
+    // Add the listener for `onActivityResult`
+    reactContext.addActivityEventListener(this);
+  }
+
+  @Override
+  public String getName() {
+    return "ImagePickerModule";
+  }
+
+  @ReactMethod
+  public void pickImage(final Promise promise) {
+    Activity currentActivity = getCurrentActivity();
+
+    if (currentActivity == null) {
+      promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
+      return;
+    }
+
+    // Store the promise to resolve/reject when picker returns data
+    mPickerPromise = promise;
+
+    try {
+      final Intent galleryIntent = new Intent(Intent.ACTION_PICK);
+
+      galleryIntent.setType("image/*");
+
+      final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");
+
+      currentActivity.startActivityForResult(chooserIntent, PICK_IMAGE);
+    } catch (Exception e) {
+      mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
+      mPickerPromise = null;
+    }
+  }
+
+  // You can get the result here
+  @Override
+  public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
+    if (requestCode == IMAGE_PICKER_REQUEST) {
+      if (mPickerPromise != null) {
+        if (resultCode == Activity.RESULT_CANCELED) {
+          mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled");
+        } else if (resultCode == Activity.RESULT_OK) {
+          Uri uri = intent.getData();
+
+          if (uri == null) {
+            mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found");
+          } else {
+            mPickerPromise.resolve(uri.toString());
+          }
+        }
+
+        mPickerPromise = null;
+      }
+    }
+  }
+}
+```
+
+### Listening to LifeCycle events
+
+Listening to the activity's LifeCycle events such as `onResume`, `onPause` etc. is very similar to how we implemented `ActivityEventListener`. The module must implement `LifecycleEventListener`. Then, you need to register a listener in the module's constructor,
+
+```java
+reactContext.addLifecycleEventListener(this);
+```
+
+Now you can listen to the activity's LifeCycle events by implementing the following methods:
+
+```java
+@Override
+public void onHostResume() {
+    // Actvity `onResume`
+}
+
+@Override
+public void onHostPause() {
+    // Actvity `onPause`
+}
+
+@Override
+public void onHostDestroy() {
+    // Actvity `onDestroy`
+}
+```
diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md
index f3b8510c4337..db20eda09ca8 100644
--- a/docs/NativeModulesIOS.md
+++ b/docs/NativeModulesIOS.md
@@ -50,7 +50,8 @@ RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
 Now, from your JavaScript file you can call the method like this:
 
 ```javascript
-var CalendarManager = require('react-native').NativeModules.CalendarManager;
+import { NativeModules } from 'react-native';
+var CalendarManager = NativeModules.CalendarManager;
 CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
 ```
 
@@ -132,7 +133,7 @@ and call it from JavaScript:
 ```javascript
 CalendarManager.addEvent('Birthday Party', {
   location: '4 Privet Drive, Surrey',
-  time: date.toTime(),
+  time: date.getTime(),
   description: '...'
 })
 ```
@@ -173,6 +174,43 @@ A native module is supposed to invoke its callback only once. It can, however, s
 
 If you want to pass error-like objects to JavaScript, use `RCTMakeError` from [`RCTUtils.h`](https://github.com/facebook/react-native/blob/master/React/Base/RCTUtils.h).  Right now this just passes an Error-shaped dictionary to JavaScript, but we would like to automatically generate real JavaScript `Error` objects in the future.
 
+## Promises
+
+Native modules can also fulfill a promise, which can simplify your code, especially when using ES2016's `async/await` syntax. When the last parameters of a bridged native method are an `RCTPromiseResolveBlock` and `RCTPromiseRejectBlock`, its corresponding JS method will return a JS Promise object.
+
+Refactoring the above code to use a promise instead of callbacks looks like this:
+
+```objective-c
+RCT_REMAP_METHOD(findEvents,
+                 resolver:(RCTPromiseResolveBlock)resolve
+                 rejecter:(RCTPromiseRejectBlock)reject)
+{
+  NSArray *events = ...
+  if (events) {
+    resolve(events);
+  } else {
+    NSError *error = ...
+    reject(@"no_events", @"There were no events", error);
+  }
+}
+```
+
+The JavaScript counterpart of this method returns a Promise. This means you can use the `await` keyword within an async function to call it and wait for its result:
+
+```js
+async function updateEvents() {
+  try {
+    var events = await CalendarManager.findEvents();
+
+    this.setState({ events });
+  } catch (e) {
+    console.error(e);
+  }
+}
+
+updateEvents();
+```
+
 ## Threading
 
 The native module should not have any assumptions about what thread it is being called on. React Native invokes native modules methods on a separate serial GCD queue, but this is an implementation detail and might change.  The `- (dispatch_queue_t)methodQueue` method allows the native module to specify which queue its methods should be run on.  For example, if it needs to use a main-thread-only iOS API, it should specify this via:
@@ -250,7 +288,7 @@ You must create a class extension of RCTConvert like so:
 @implementation RCTConvert (StatusBarAnimation)
   RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ @"statusBarAnimationNone" : @(UIStatusBarAnimationNone),
                                                @"statusBarAnimationFade" : @(UIStatusBarAnimationFade),
-                                               @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)},
+                                               @"statusBarAnimationSlide" : @(UIStatusBarAnimationSlide)}),
                       UIStatusBarAnimationNone, integerValue)
 @end
 ```
@@ -297,7 +335,7 @@ The native module can signal events to JavaScript without being invoked directly
 JavaScript code can subscribe to these events:
 
 ```javascript
-var { NativeAppEventEmitter } = require('react-native');
+import { NativeAppEventEmitter } from 'react-native';
 
 var subscription = NativeAppEventEmitter.addListener(
   'EventReminder',
@@ -338,7 +376,7 @@ Then create a private implementation file that will register the required inform
 
 @interface RCT_EXTERN_MODULE(CalendarManager, NSObject)
 
-RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(NSNumber *)date)
+RCT_EXTERN_METHOD(addEvent:(NSString *)name location:(NSString *)location date:(nonnull NSNumber *)date)
 
 @end
 ```
diff --git a/docs/NavigatorComparison.md b/docs/NavigatorComparison.md
index 326ef98d1265..80262d1a358d 100644
--- a/docs/NavigatorComparison.md
+++ b/docs/NavigatorComparison.md
@@ -7,8 +7,8 @@ permalink: docs/navigator-comparison.html
 next: known-issues
 ---
 
-The differences between [Navigator](/react-native/docs/navigator.html)
-and [NavigatorIOS](/react-native/docs/navigatorios.html) are a common
+The differences between [Navigator](docs/navigator.html)
+and [NavigatorIOS](docs/navigatorios.html) are a common
 source of confusion for newcomers.
 
 Both `Navigator` and `NavigatorIOS` are components that allow you to
@@ -41,7 +41,7 @@ between the two.
 - Development belongs to open-source community - not used by the React Native team on their apps.
   - A result of this is that there is currently a backlog of unresolved bugs, nobody who uses this has stepped up to take ownership for it yet.
 - Wraps UIKit, so it works exactly the same as it would on another native app. Lives in Objective-C and JavaScript.
-  - Consequently, you get the animations and behaviour that Apple has developed.
+  - Consequently, you get the animations and behavior that Apple has developed.
 - iOS only.
 - Includes a navigation bar by default; this navigation bar is not a React Native view component and the style can only be slightly modified.
 
diff --git a/docs/Network.md b/docs/Network.md
index d0366e646992..fa8566d2e301 100644
--- a/docs/Network.md
+++ b/docs/Network.md
@@ -9,45 +9,19 @@ next: timers
 
 One of React Native's goals is to be a playground where we can experiment with different architectures and crazy ideas. Since browsers are not flexible enough, we had no choice but to reimplement the entire stack. In the places that we did not intend to change anything, we tried to be as faithful as possible to the browser APIs. The networking stack is a great example.
 
-## XMLHttpRequest
-
-XMLHttpRequest API is implemented on-top of [iOS networking apis](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html). The notable difference from web is the security model: you can read from arbitrary websites on the internet since there is no concept of [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
-
-```javascript
-var request = new XMLHttpRequest();
-request.onreadystatechange = (e) => {
-  if (request.readyState !== 4) {
-    return;
-  }
-
-  if (request.status === 200) {
-    console.log('success', request.responseText);
-  } else {
-    console.warn('error');
-  }
-};
-
-request.open('GET', 'https://mywebsite.com/endpoint.php');
-request.send();
-```
-
-Please follow the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) for a complete description of the API.
-
-As a developer, you're probably not going to use XMLHttpRequest directly as its API is very tedious to work with. But the fact that it is implemented and compatible with the browser API gives you the ability to use third-party libraries such as [Parse]( https://parse.com/products/javascript) or [super-agent](https://github.com/visionmedia/superagent) directly from npm.
-
 ## Fetch
 
 [fetch](https://fetch.spec.whatwg.org/) is a better networking API being worked on by the standards committee and is already available in Chrome. It is available in React Native by default.
 
 #### Usage
 
-```javascript
+```js
 fetch('https://mywebsite.com/endpoint/')
 ```
 
 Include a request object as the optional second argument to customize the HTTP request:
 
-```javascript
+```js
 fetch('https://mywebsite.com/endpoint/', {
   method: 'POST',
   headers: {
@@ -62,61 +36,92 @@ fetch('https://mywebsite.com/endpoint/', {
 ```
 
 #### Async
+
 `fetch` returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that can be processed in two ways:
 
 1.  Using `then` and `catch` in synchronous code:
 
-```javascript
-fetch('https://mywebsite.com/endpoint.php')
-  .then((response) => response.text())
-  .then((responseText) => {
-    console.log(responseText);
-  })
-  .catch((error) => {
-    console.warn(error);
-  });
-```
-
+  ```js
+  fetch('https://mywebsite.com/endpoint.php')
+    .then((response) => response.text())
+    .then((responseText) => {
+      console.log(responseText);
+    })
+    .catch((error) => {
+      console.warn(error);
+    });
+  ```
 2.  Called within an asynchronous function using ES7 `async`/`await` syntax:
-
-```javascript
-async getUsersFromApi() {
-  try {
-    let response = await fetch('https://mywebsite.com/endpoint/');
-    return response.users;
-  } catch(error) {
-    throw error;
+  
+  ```js
+  class MyComponent extends React.Component {
+    ...
+    async getUsersFromApi() {
+      try {
+        let response = await fetch('https://mywebsite.com/endpoint/');
+        let responseJson = await response.json();
+        return responseJson.users;
+      } catch(error) {
+        // Handle error
+        console.error(error);
+      }
+    }
+    ...
   }
-}
-```
+  ```
 
 - Note: Errors thrown by rejected Promises need to be caught, or they will be swallowed silently
 
-
 ## WebSocket
 
 WebSocket is a protocol providing full-duplex communication channels over a single TCP connection.
 
-```javascript
+```js
 var ws = new WebSocket('ws://host.com/path');
 
-ws.on('open', function() {
+ws.onopen = () => {
   // connection opened
   ws.send('something');
-});
+};
 
-ws.on('message', function(e) {
+ws.onmessage = (e) => {
   // a message was received
   console.log(e.data);
-});
+};
 
-ws.on('error', function(e) {
+ws.onerror = (e) => {
   // an error occurred
   console.log(e.message);
-});
+};
 
-ws.on('close', function(e) {
+ws.onclose = (e) => {
   // connection closed
   console.log(e.code, e.reason);
-});
+};
 ```
+
+## XMLHttpRequest
+
+XMLHttpRequest API is implemented on-top of [iOS networking apis](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html). The notable difference from web is the security model: you can read from arbitrary websites on the internet since there is no concept of [CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
+
+```js
+var request = new XMLHttpRequest();
+request.onreadystatechange = (e) => {
+  if (request.readyState !== 4) {
+    return;
+  }
+
+  if (request.status === 200) {
+    console.log('success', request.responseText);
+  } else {
+    console.warn('error');
+  }
+};
+
+request.open('GET', 'https://mywebsite.com/endpoint.php');
+request.send();
+```
+
+Please follow the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) for a complete description of the API.
+
+As a developer, you're probably not going to use XMLHttpRequest directly as its API is very tedious to work with. But the fact that it is implemented and compatible with the browser API gives you the ability to use third-party libraries such as [Parse](https://parse.com/products/javascript), [frisbee](https://github.com/niftylettuce/frisbee), or [axios](https://github.com/mzabriskie/axios) directly from npm.
diff --git a/docs/Performance.md b/docs/Performance.md
index a69f6d17e80a..efded189650c 100644
--- a/docs/Performance.md
+++ b/docs/Performance.md
@@ -72,7 +72,7 @@ out of the box than `Navigator`. The reason for this is that the
 animations for the transitions are done entirely on the main thread, and
 so they are not interrupted by frame drops on the JavaScript thread.
 ([Read about why you should probably use Navigator
-anyways.](/react-native/docs/navigator-comparison.html))
+anyways.](docs/navigator-comparison.html))
 
 Similarly, you can happily scroll up and down through a ScrollView when
 the JavaScript thread is locked up because the ScrollView lives on the
@@ -205,7 +205,7 @@ should continue to render rows.
 "When true, offscreen child views (whose `overflow` value is `hidden`)
 are removed from their native backing superview when offscreen.  This
 can improve scrolling performance on long lists. The default value is
-false."
+`true`."(The default value is `false` before version 0.14-rc).
 
 This is an extremely important optimization to apply on large ListViews.
 On Android the `overflow` value is always `hidden` so you don't need to
diff --git a/docs/PlatformSpecificInformation.md b/docs/PlatformSpecificInformation.md
new file mode 100644
index 000000000000..03aa48bf1acc
--- /dev/null
+++ b/docs/PlatformSpecificInformation.md
@@ -0,0 +1,67 @@
+---
+id: platform-specific-code
+title: Platform Specific Code
+layout: docs
+category: Guides
+permalink: docs/platform-specific-code.html
+next: native-modules-ios
+---
+
+When building a cross-platform app, the need to write different code for different platforms may arise. This can always be achieved by organizing the various components in different folders:
+
+```sh
+/common/components/
+/android/components/
+/ios/components/
+```
+
+Another option may be naming the components differently depending on the platform they are going to be used in:
+
+```sh
+BigButtonIOS.js
+BigButtonAndroid.js
+```
+
+But React Native provides two alternatives to easily organize your code separating it by platform:
+
+## Platform specific extensions
+React Native will detect when a file has a `.ios.` or `.android.` extension and load the right file for each platform when requiring them from other components.
+
+For example, you can have these files in your project:
+
+```sh
+BigButton.ios.js
+BigButton.android.js
+```
+
+With this setup, you can just require the files from a different component without paying attention to the platform in which the app will run.
+
+```javascript
+import BigButton from './components/BigButton';
+```
+
+React Native will import the correct component for the running platform.
+
+## Platform module
+A module is provided by React Native to detect what is the platform in which the app is running. This piece of functionality can be useful when only small parts of a component are platform specific.
+
+```javascript
+var { Platform } = React;
+
+var styles = StyleSheet.create({
+  height: (Platform.OS === 'ios') ? 200 : 100,
+});
+```
+
+`Platform.OS` will be `ios` when running in iOS and `android` when running in an Android device or simulator.
+
+###Detecting Android version
+On Android, the Platform module can be also used to detect which is the version of the Android Platform in which the app is running
+
+```javascript
+var {Platform} = React;
+
+if(Platform.Version === 21){
+  console.log('Running on Lollipop!');
+}
+```
diff --git a/docs/RunningOnDeviceAndroid.md b/docs/RunningOnDeviceAndroid.md
index 08bc5f8da75b..845c91599b6f 100644
--- a/docs/RunningOnDeviceAndroid.md
+++ b/docs/RunningOnDeviceAndroid.md
@@ -20,7 +20,7 @@ Check that your device has been **successfully connected** by running `adb devic
 
 Seeing **device** in the right column means the device is connected. Android - go figure :) You must have **only one device connected**.
 
-Now you can use `react-native run-android` to install and lauch your app on the device.
+Now you can use `react-native run-android` to install and launch your app on the device.
 
 ## Accessing development server from device
 
@@ -28,7 +28,7 @@ You can also iterate quickly on device using the development server. Follow one
 
 > Hint
 >
-> Most modern android devices don't have a hardware menu button, which we use to trigger the developer menu. In that case you can shake the device to open the dev menu (to reload, debug, etc.)
+> Most modern android devices don't have a hardware menu button, which we use to trigger the developer menu. In that case you can shake the device to open the dev menu (to reload, debug, etc.). Alternatively, you can run the command `adb shell input keyevent 82` to open the dev menu (82 being the [Menu](http://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_MENU) key code).
 
 ### Using adb reverse
 
diff --git a/docs/RunningOnDeviceIOS.md b/docs/RunningOnDeviceIOS.md
index f4bc780235ae..619b048fcea2 100644
--- a/docs/RunningOnDeviceIOS.md
+++ b/docs/RunningOnDeviceIOS.md
@@ -23,31 +23,18 @@ You can iterate quickly on device using development server. To do that, your lap
 
 ## Using offline bundle
 
-You can also pack all the JavaScript code within the app itself. This way you can test it without development server running and submit the app to the AppStore.
+When you run your app on device, we pack all the JavaScript code and the images used into the app's resources. This way you can test it without development server running and submit the app to the AppStore.
 
 1. Open `AwesomeApp/ios/AwesomeApp/AppDelegate.m`
-2. Follow the instructions for "OPTION 2":
-  * Uncomment `jsCodeLocation = [[NSBundle mainBundle] ...`
-  * Run the `react-native bundle` command in terminal from the root directory of your app
-
-The bundle script supports a couple of flags:
-
-* `--dev` - sets the value of `__DEV__` variable to true. When `true` it turns on a bunch of useful development warnings. For production it is recommended to set `__DEV__=false`.
-* `--minify` - pipe the JS code through UglifyJS.
-
-Note that on 0.14 we'll change the API of `react-native bundle`. The major changes are: 
-
-* API is now `entry-file <path>` based instead of url based.
-* Need to specify which platform you're bundling for `--platform <ios|android>`.
-* Option `--out` has been renamed for `--bundle-output`.
-* Source maps are no longer automatically generated. Need to specify `--sourcemap-output <path>` 
+2. Uncomment `jsCodeLocation = [[NSBundle mainBundle] ...`
+3. The JS bundle will be built for dev or prod depending on your app's scheme (Debug = development build with warnings, Release = minified prod build with perf optimizations). To change the scheme navigate to `Product > Scheme > Edit Scheme...` in xcode and change `Build Configuration` between `Debug` and `Release`.
 
 ## Disabling in-app developer menu
 
-When building your app for production, your app's scheme should be set to `Release` as detailed in [the debugging documentation](/react-native/docs/debugging.html#debugging-react-native-apps) in order to disable the in-app developer menu.
+When building your app for production, your app's scheme should be set to `Release` as detailed in [the debugging documentation](docs/debugging.html#debugging-react-native-apps) in order to disable the in-app developer menu.
 
 ## Troubleshooting
 
 If `curl` command fails make sure the packager is running. Also try adding `--ipv4` flag to the end of it.
 
-If you started your project a while ago, `main.jsbundle` might not be included into Xcode project. To add it, right click on your project directory and click "Add Files to ..." - choose the `main.jsbundle` file that you generated.
+Note that since [v0.14](https://github.com/facebook/react-native/releases/tag/0.14.0) JS and images are automatically packaged into the iOS app using `Bundle React Native code and images` Xcode build phase.
diff --git a/docs/SignedAPKAndroid.md b/docs/SignedAPKAndroid.md
index 231c82ac89d0..e4ecc0d51935 100644
--- a/docs/SignedAPKAndroid.md
+++ b/docs/SignedAPKAndroid.md
@@ -35,13 +35,13 @@ MYAPP_RELEASE_KEY_PASSWORD=*****
 
 These are going to be global gradle variables, which we can later use in our gradle config to sign our app.
 
-_Note: Once you publish the app on the Play Store, you will need to republish your app under a different package name (loosing all downloads and ratings) if you want to change the signing key at any point. So backup your keystore and don't forget the passwords._
+_Note: Once you publish the app on the Play Store, you will need to republish your app under a different package name (losing all downloads and ratings) if you want to change the signing key at any point. So backup your keystore and don't forget the passwords._
 
 ### Adding signing config to your app's gradle config
 
 Edit the file `android/app/build.gradle` in your project folder and add the signing config,
 
-```
+```gradle
 ...
 android {
     ...
@@ -66,29 +66,15 @@ android {
 
 ### Generating the release APK
 
-#### If you have a `react.gradle` file in `android/app`
-
 Simply run the following in a terminal:
 
 ```sh
 $ cd android && ./gradlew assembleRelease
 ```
 
-If you need to change the way the JavaScript bundle and/or drawable resources are bundled (e.g. if you changed the default file/folder names or the general structure of the project), have a look at `android/app/build.gradle` to see how you can update it to reflect these changes.
-
-#### If you *don't* have a `react.gradle` file:
-
-You can [upgrade](/react-native/docs/upgrading.html) to the latest version of React Native to get this file. Alternatively, you can bundle the JavaScript package and drawable resources manually by doing the following in a terminal:
-
-```sh
-$ mkdir -p android/app/src/main/assets
-$ react-native bundle --platform android --dev false --entry-file index.android.js \
-  --bundle-output android/app/src/main/assets/index.android.bundle \
-  --assets-dest android/app/src/main/res/
-$ cd android && ./gradlew assembleRelease
-```
+Gradle's `assembleRelease` will bundle all the JavaScript needed to run your app into the APK. If you need to change the way the JavaScript bundle and/or drawable resources are bundled (e.g. if you changed the default file/folder names or the general structure of the project), have a look at `android/app/build.gradle` to see how you can update it to reflect these changes.
 
-In both cases the generated APK can be found under `android/app/build/outputs/apk/app-release.apk`, and is ready to be distributed.
+The generated APK can be found under `android/app/build/outputs/apk/app-release.apk`, and is ready to be distributed.
 
 ### Testing the release build of your app
 
@@ -108,18 +94,11 @@ Proguard is a tool that can slightly reduce the size of the APK. It does this by
 
 _**IMPORTANT**: Make sure to thoroughly test your app if you've enabled Proguard. Proguard often requires configuration specific to each native library you're using. See `app/proguard-rules.pro`._
 
-To enable Proguard, set `minifyEnabled` to `true`:
+To enable Proguard, edit `android/app/build.gradle`:
 
-```
-...
-android {
-    ...
-    buildTypes {
-        release {
-            ...
-            minifyEnabled true
-        }
-    }
-}
-...
+```gradle
+/**
+ * Run Proguard to shrink the Java bytecode in release builds.
+ */
+def enableProguardInReleaseBuilds = true
 ```
diff --git a/docs/Style.md b/docs/Style.md
index 9f7229c94e2e..fb6cb7a6dc07 100644
--- a/docs/Style.md
+++ b/docs/Style.md
@@ -33,7 +33,7 @@ var styles = StyleSheet.create({
 
 `StyleSheet.create` construct is optional but provides some key advantages. It ensures that the values are **immutable** and **opaque** by transforming them into plain numbers that reference an internal table. By putting it at the end of the file, you also ensure that they are only created once for the application and not on every render.
 
-All the attribute names and values are a subset of what works on the web. For layout, React Native implements [Flexbox](/react-native/docs/flexbox.html).
+All the attribute names and values are a subset of what works on the web. For layout, React Native implements [Flexbox](docs/flexbox.html).
 
 ## Using Styles
 
@@ -95,8 +95,8 @@ var List = React.createClass({
 
 You can checkout latest support of CSS Properties in following Links.
 
-- [View Properties](/react-native/docs/view.html#style)
-- [Image Properties](/react-native/docs/image.html#style)
-- [Text Properties](/react-native/docs/text.html#style)
-- [Flex Properties](/react-native/docs/flexbox.html#content)
-- [Transform Properties](/react-native/docs/transforms.html#content)
+- [View Properties](docs/view.html#style)
+- [Image Properties](docs/image.html#style)
+- [Text Properties](docs/text.html#style)
+- [Flex Properties](docs/flexbox.html#content)
+- [Transform Properties](docs/transforms.html#content)
diff --git a/docs/Testing.md b/docs/Testing.md
index 56ffd67892e4..4c881b2d654a 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -56,12 +56,12 @@ Note: you may have to install/upgrade/link Node.js and other parts of your envir
 
 ## Integration Tests (iOS only)
 
-React Native provides facilities to make it easier to test integrated components that require both native and JS components to communicate across the bridge.  The two main components are `RCTTestRunner` and `RCTTestModule`.  `RCTTestRunner` sets up the ReactNative environment and provides facilities to run the tests as `XCTestCase`s in Xcode (`runTest:module` is the simplest method).  `RCTTestModule` is exported to JS as `NativeModules.TestModule`.  The tests themselves are written in JS, and must call `TestModule.markTestCompleted()` when they are done, otherwise the test will timeout and fail.  Test failures are primarily indicated by throwing a JS exception.  It is also possible to test error conditions with `runTest:module:initialProps:expectErrorRegex:` or `runTest:module:initialProps:expectErrorBlock:` which will expect an error to be thrown and verify the error matches the provided criteria.  See [`IntegrationTestHarnessTest.js`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestHarnessTest.js), [`IntegrationTests.m`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTests.m), and [IntegrationTestsApp.js](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp.js) for example usage and integration points.
+React Native provides facilities to make it easier to test integrated components that require both native and JS components to communicate across the bridge.  The two main components are `RCTTestRunner` and `RCTTestModule`.  `RCTTestRunner` sets up the ReactNative environment and provides facilities to run the tests as `XCTestCase`s in Xcode (`runTest:module` is the simplest method).  `RCTTestModule` is exported to JS as `NativeModules.TestModule`.  The tests themselves are written in JS, and must call `TestModule.markTestCompleted()` when they are done, otherwise the test will timeout and fail.  Test failures are primarily indicated by throwing a JS exception.  It is also possible to test error conditions with `runTest:module:initialProps:expectErrorRegex:` or `runTest:module:initialProps:expectErrorBlock:` which will expect an error to be thrown and verify the error matches the provided criteria.  See [`IntegrationTestHarnessTest.js`](https://github.com/facebook/react-native/blob/master/IntegrationTests/IntegrationTestHarnessTest.js), [`UIExplorerIntegrationTests.m`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m), and [IntegrationTestsApp.js](https://github.com/facebook/react-native/blob/master/IntegrationTests/IntegrationTestsApp.js) for example usage and integration points.
 
 You can run integration tests locally with cmd+U in the IntegrationTest and UIExplorer apps in Xcode.
 
 ## Snapshot Tests (iOS only)
 
-A common type of integration test is the snapshot test.  These tests render a component, and verify snapshots of the screen against reference images using `TestModule.verifySnapshot()`, using the [`FBSnapshotTestCase`](https://github.com/facebook/ios-snapshot-test-case) library behind the scenes.  Reference images are recorded by setting `recordMode = YES` on the `RCTTestRunner`, then running the tests.  Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration.  It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies.  See [`SimpleSnapshotTest`](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js) for a basic example.
+A common type of integration test is the snapshot test.  These tests render a component, and verify snapshots of the screen against reference images using `TestModule.verifySnapshot()`, using the [`FBSnapshotTestCase`](https://github.com/facebook/ios-snapshot-test-case) library behind the scenes.  Reference images are recorded by setting `recordMode = YES` on the `RCTTestRunner`, then running the tests.  Snapshots will differ slightly between 32 and 64 bit, and various OS versions, so it's recommended that you enforce tests are run with the correct configuration.  It's also highly recommended that all network data be mocked out, along with other potentially troublesome dependencies.  See [`SimpleSnapshotTest`](https://github.com/facebook/react-native/blob/master/IntegrationTests/SimpleSnapshotTest.js) for a basic example.
 
-If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshotshot reference image.  To do this, simply change to `_runner.recordMode = YES;` in [UIExplorer/UIExplorerSnapshotTests.m](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m#L42), re-run the failing tests, then flip record back to `NO` and submit/update your PR and wait to see if the Travis build passes.
+If you make a change that affects a snapshot test in a PR, such as adding a new example case to one of the examples that is snapshotted, you'll need to re-record the snapshot reference image.  To do this, simply change to `_runner.recordMode = YES;` in [UIExplorer/UIExplorerSnapshotTests.m](https://github.com/facebook/react-native/blob/master/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerSnapshotTests.m#L42), re-run the failing tests, then flip record back to `NO` and submit/update your PR and wait to see if the Travis build passes.
diff --git a/docs/Timers.md b/docs/Timers.md
index 9f8e2ace0001..b90180d429e7 100644
--- a/docs/Timers.md
+++ b/docs/Timers.md
@@ -4,6 +4,7 @@ title: Timers
 layout: docs
 category: Polyfills
 permalink: docs/timers.html
+next: colors
 ---
 
 Timers are an important part of an application and React Native implements the [browser timers](https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Timers).
@@ -60,7 +61,7 @@ We found out that the primary cause of fatals in apps created with React Native
 This library does not ship with React Native - in order to use it on your project, you will need to install it with `npm i react-timer-mixin --save` from your project directory.
 
 ```javascript
-var TimerMixin = require('react-timer-mixin');
+import TimerMixin from 'react-timer-mixin';
 
 var Component = React.createClass({
   mixins: [TimerMixin],
diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md
index 6db929a2c278..875cfa2c8530 100644
--- a/docs/Troubleshooting.md
+++ b/docs/Troubleshooting.md
@@ -78,7 +78,7 @@ pod 'React', :path => '../node_modules/react-native', :subspecs => [
 ```
 Next, make sure you have run `pod install` and that a `Pods/` directory has been created in your project with React installed. CocoaPods will instruct you to use the generated `.xcworkspace` file henceforth to be able to use these installed dependencies.
 
-If you are adding React manually, make sure you have included all the relevant dependencies, like `RCTText.xcodeproj`, `RCTImage.xcodeproj` depending on the ones you are using. Next, the binaries built by these dependencies have to be linked to your app binary. Use the `Linked Frameworks and Binaries` section in the Xcode project settings. More detailed steps are here: [Linking Libraries](https://facebook.github.io/react-native/docs/linking-libraries-ios.html#content).
+If you are adding React manually, make sure you have included all the relevant dependencies, like `RCTText.xcodeproj`, `RCTImage.xcodeproj` depending on the ones you are using. Next, the binaries built by these dependencies have to be linked to your app binary. Use the `Linked Frameworks and Binaries` section in the Xcode project settings. More detailed steps are here: [Linking Libraries](docs/linking-libraries-ios.html#content).
 
 ##### Argument list too long: recursive header expansion failed
 
@@ -96,12 +96,12 @@ You need to run `adb reverse tcp:8081 tcp:8081` to forward requests from the dev
 
 ## Module that uses `WebSocket` (such as Firebase) throws an exception
 
-React Native implements a polyfill for WebSockets. These polyfills are initialized as part of the react-native module that you include in your application through `require('react-native')`. If you load another module that requires WebSockets, be sure to load/require it after react-native.
+React Native implements a polyfill for WebSockets. These polyfills are initialized as part of the react-native module that you include in your application through `import React from 'react-native'`. If you load another module that requires WebSockets, be sure to load/require it after react-native.
 
 So:
 ```
-var React = require('react-native');
-var Firebase = require('firebase');
+import React from 'react-native';
+import Firebase from 'firebase';
 ```
 
 Requiring firebase *before* react-native will result in a 'No transports available' redbox.
diff --git a/docs/Tutorial.md b/docs/Tutorial.md
index d1d71a6631cd..ec5a4d72bfc5 100644
--- a/docs/Tutorial.md
+++ b/docs/Tutorial.md
@@ -16,7 +16,7 @@ We assume you have experience writing applications with React. If not, you can l
 
 ## Setup
 
-React Native requires the basic setup explained at [React Native Getting Started](https://facebook.github.io/react-native/docs/getting-started.html#content).
+React Native requires the basic setup explained at [React Native Getting Started](docs/getting-started.html#content).
 
 After installing these dependencies there are two simple commands to get a React Native project all set up for development.
 
@@ -29,13 +29,21 @@ After installing these dependencies there are two simple commands to get a React
     This command fetches the React Native source code and dependencies and then creates a new Xcode project in `AwesomeProject/iOS/AwesomeProject.xcodeproj` and a gradle project in `AwesomeProject/android/app`.
 
 
-## Development
+## Overview
 
-For iOS, you can now open this new project (`AwesomeProject/ios/AwesomeProject.xcodeproj`) in Xcode and simply build and run it with `⌘+R`. Doing so will also start a Node server which enables live code reloading. With this you can see your changes by pressing `⌘+R` in the simulator rather than recompiling in Xcode.
+In this tutorial we'll be building a simple version of the Movies app that fetches 25 movies that are in theaters and displays them in a ListView.
 
-For Android, run `react-native run-android` from `AwesomeProject` to install the generated app on your emulator or device, and start the Node server which enables live code reloading. To see your changes you have to open the rage-shake-menu (either shake the device or press the menu button on devices, press F2 or Page Up for emulator, ⌘+M for Genymotion), and then press `Reload JS`.
+### Starting the app on iOS
 
-For this tutorial we'll be building a simple version of the Movies app that fetches 25 movies that are in theaters and displays them in a ListView.
+Open this new project (`AwesomeProject/ios/AwesomeProject.xcodeproj`) in Xcode and simply build and run it with `⌘+R`. Doing so will also start a Node server which enables live code reloading. With this you can see your changes by pressing `⌘+R` in the simulator rather than recompiling in Xcode.
+
+### Starting the app on Android
+
+In your terminal navigate into the `AwesomeProject` and run:
+
+    react-native run-android
+
+This will install the generated app on your emulator or device, as well as start the Node server which enables live code reloading. To see your changes you have to open the rage-shake-menu (either shake the device or press the menu button on devices, press F2 or Page Up for emulator, ⌘+M for Genymotion), and then press `Reload JS`.
 
 ### Hello World
 
@@ -43,7 +51,7 @@ For this tutorial we'll be building a simple version of the Movies app that fetc
 
 ### Mocking data
 
-Before we write the code to fetch actual Rotten Tomatoes data let's mock some data so we can get our hands dirty with React Native. At Facebook we typically declare constants at the top of JS files, just below the requires, but feel free to add the following constant wherever you like. In `index.ios.js` or `index.android.js` :
+Before we write the code to fetch actual Rotten Tomatoes data let's mock some data so we can get our hands dirty with React Native. At Facebook we typically declare constants at the top of JS files, just below the imports, but feel free to add the following constant wherever you like. In `index.ios.js` or `index.android.js` :
 
 ```javascript
 var MOCKED_MOVIES_DATA = [
@@ -54,22 +62,23 @@ var MOCKED_MOVIES_DATA = [
 
 ### Render a movie
 
-We're going to render the title, year, and thumbnail for the movie. Since thumbnail is an Image component in React Native, add Image to the list of React requires below.
+We're going to render the title, year, and thumbnail for the movie. Since thumbnail is an Image component in React Native, add Image to the list of React imports below.
 
 ```javascript
-var {
+import React, {
   AppRegistry,
+  Component,
   Image,
   StyleSheet,
   Text,
   View,
-} = React;
+} from 'react-native';
 ```
 
 Now change the render function so that we're rendering the data mentioned above rather than hello world.
 
 ```javascript
-  render: function() {
+  render() {
     var movie = MOCKED_MOVIES_DATA[0];
     return (
       <View style={styles.container}>
@@ -110,8 +119,8 @@ And lastly we need to apply this style to the Image component:
 Press `⌘+R` / `Reload JS` and the image should now render.
 
 <div class="tutorial-mock">
-  <img src="/react-native/img/TutorialMock.png" />
-  <img src="/react-native/img/TutorialMock2.png" />
+  <img src="img/TutorialMock.png" />
+  <img src="img/TutorialMock2.png" />
 </div>
 
 
@@ -129,7 +138,7 @@ Great, we've rendered our data. Now let's make it look better. I'd like to put t
 +---------------------------------+
 ```
 
-We'll need to add another container in order to vertically lay out components within horizontally layed out components.
+We'll need to add another container in order to vertically lay out components within horizontally laid out components.
 
 ```javascript
       return (
@@ -188,15 +197,15 @@ Styling the text is pretty straightforward:
 Go ahead and press `⌘+R` / `Reload JS` and you'll see the updated view.
 
 <div class="tutorial-mock">
-  <img src="/react-native/img/TutorialStyledMock.png" />
-  <img src="/react-native/img/TutorialStyledMock2.png" />
+  <img src="img/TutorialStyledMock.png" />
+  <img src="img/TutorialStyledMock2.png" />
 </div>
 
 ### Fetching real data
 
 Fetching data from Rotten Tomatoes's API isn't really relevant to learning React Native so feel free to breeze through this section.
 
-Add the following constants to the top of the file (typically below the requires) to create the REQUEST_URL used to request data with.
+Add the following constants to the top of the file (typically below the imports) to create the REQUEST_URL used to request data with.
 
 ```javascript
 /**
@@ -209,25 +218,26 @@ var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/maste
 Add some initial state to our application so that we can check `this.state.movies === null` to determine whether the movies data has been loaded or not. We can set this data when the response comes back with `this.setState({movies: moviesData})`. Add this code just above the render function inside our React class.
 
 ```javascript
-  getInitialState: function() {
-    return {
+  constructor(props) {
+    super(props); 
+    this.state = {
       movies: null,
     };
-  },
+  }
 ```
 
 We want to send off the request after the component has finished loading. `componentDidMount` is a function of React components that React will call exactly once, just after the component has been loaded.
 
 ```javascript
-  componentDidMount: function() {
+  componentDidMount() {
     this.fetchData();
-  },
+  }
 ```
 
 Now add `fetchData` function used above to our main component. This method will be responsible for handling data fetching. All you need to do is call `this.setState({movies: data})` after resolving the promise chain because the way React works is that `setState` actually triggers a re-render and then the render function will notice that `this.state.movies` is no longer `null`.  Note that we call `done()` at the end of the promise chain - always make sure to call `done()` or any errors thrown will get swallowed.
 
 ```javascript
-  fetchData: function() {
+  fetchData() {
     fetch(REQUEST_URL)
       .then((response) => response.json())
       .then((responseData) => {
@@ -236,22 +246,22 @@ Now add `fetchData` function used above to our main component. This method will
         });
       })
       .done();
-  },
+  }
 ```
 
 Now modify the render function to render a loading view if we don't have any movies data, and to render the first movie otherwise.
 
 ```javascript
-  render: function() {
+  render() {
     if (!this.state.movies) {
       return this.renderLoadingView();
     }
 
     var movie = this.state.movies[0];
     return this.renderMovie(movie);
-  },
+  }
 
-  renderLoadingView: function() {
+  renderLoadingView() {
     return (
       <View style={styles.container}>
         <Text>
@@ -259,9 +269,9 @@ Now modify the render function to render a loading view if we don't have any mov
         </Text>
       </View>
     );
-  },
+  }
 
-  renderMovie: function(movie) {
+  renderMovie(movie) {
     return (
       <View style={styles.container}>
         <Image
@@ -274,14 +284,14 @@ Now modify the render function to render a loading view if we don't have any mov
         </View>
       </View>
     );
-  },
+  }
 ```
 
 Now press `⌘+R` / `Reload JS` and you should see "Loading movies..." until the response comes back, then it will render the first movie it fetched from Rotten Tomatoes.
 
 <div class="tutorial-mock">
-  <img src="/react-native/img/TutorialSingleFetched.png" />
-  <img src="/react-native/img/TutorialSingleFetched2.png" />
+  <img src="img/TutorialSingleFetched.png" />
+  <img src="img/TutorialSingleFetched2.png" />
 </div>
 
 ## ListView
@@ -290,23 +300,24 @@ Let's now modify this application to render all of this data in a `ListView` com
 
 Why is a `ListView` better than just rendering all of these elements or putting them in a `ScrollView`? Despite React being fast, rendering a possibly infinite list of elements could be slow. `ListView` schedules rendering of views so that you only display the ones on screen and those already rendered but off screen are removed from the native view hierarchy.
 
-First things first: add the `ListView` require to the top of the file.
+First things first: add the `ListView` import to the top of the file.
 
 ```javascript
-var {
+import React, {
   AppRegistry,
+  Component,
   Image,
   ListView,
   StyleSheet,
   Text,
   View,
-} = React;
+} from 'react-native';
 ```
 
 Now modify the render function so that once we have our data it renders a ListView of movies instead of a single movie.
 
 ```javascript
-  render: function() {
+  render() {
     if (!this.state.loaded) {
       return this.renderLoadingView();
     }
@@ -318,28 +329,29 @@ Now modify the render function so that once we have our data it renders a ListVi
         style={styles.listView}
       />
     );
-  },
+  }
 ```
 
 The `dataSource` is an interface that `ListView` is using to determine which rows have changed over the course of updates.
 
-You'll notice we used `dataSource` from `this.state`. The next step is to add an empty `dataSource` to the object returned by `getInitialState`. Also, now that we're storing the data in `dataSource`, we should no longer use `this.state.movies` to avoid storing data twice. We can use boolean property of the state (`this.state.loaded`) to tell whether data fetching has finished.
+You'll notice we used `dataSource` from `this.state`. The next step is to add an empty `dataSource` to the object returned by `constructor`. Also, now that we're storing the data in `dataSource`, we should no longer use `this.state.movies` to avoid storing data twice. We can use boolean property of the state (`this.state.loaded`) to tell whether data fetching has finished.
 
 ```javascript
-  getInitialState: function() {
-    return {
+  constructor(props) {
+    super(props);
+    this.state = {
       dataSource: new ListView.DataSource({
         rowHasChanged: (row1, row2) => row1 !== row2,
       }),
       loaded: false,
     };
-  },
+  }
 ```
 
 And here is the modified `fetchData` method that updates the state accordingly:
 
 ```javascript
-  fetchData: function() {
+  fetchData() {
     fetch(REQUEST_URL)
       .then((response) => response.json())
       .then((responseData) => {
@@ -349,7 +361,7 @@ And here is the modified `fetchData` method that updates the state accordingly:
         });
       })
       .done();
-  },
+  }
 ```
 
 Finally, we add styles for the `ListView` component to the `styles` JS object:
@@ -363,8 +375,8 @@ Finally, we add styles for the `ListView` component to the `styles` JS object:
 And here's the final result:
 
 <div class="tutorial-mock">
-  <img src="/react-native/img/TutorialFinal.png" />
-  <img src="/react-native/img/TutorialFinal2.png" />
+  <img src="img/TutorialFinal.png" />
+  <img src="img/TutorialFinal2.png" />
 </div>
 
 There's still some work to be done to make it a fully functional app such as: adding navigation, search, infinite scroll loading, etc. Check the [Movies Example](https://github.com/facebook/react-native/tree/master/Examples/Movies) to see it all working.
@@ -377,17 +389,16 @@ There's still some work to be done to make it a fully functional app such as: ad
  * Sample React Native App
  * https://github.com/facebook/react-native
  */
-'use strict';
 
-var React = require('react-native');
-var {
+import React, {
   AppRegistry,
+  Component,
   Image,
   ListView,
   StyleSheet,
   Text,
   View,
-} = React;
+} from 'react-native';
 
 var API_KEY = '7waqfqbprs7pajbz28mqf6vz';
 var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json';
@@ -395,21 +406,22 @@ var PAGE_SIZE = 25;
 var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE;
 var REQUEST_URL = API_URL + PARAMS;
 
-var AwesomeProject = React.createClass({
-  getInitialState: function() {
-    return {
+class AwesomeProject extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
       dataSource: new ListView.DataSource({
         rowHasChanged: (row1, row2) => row1 !== row2,
       }),
       loaded: false,
     };
-  },
+  }
 
-  componentDidMount: function() {
+  componentDidMount() {
     this.fetchData();
-  },
+  }
 
-  fetchData: function() {
+  fetchData() {
     fetch(REQUEST_URL)
       .then((response) => response.json())
       .then((responseData) => {
@@ -419,9 +431,9 @@ var AwesomeProject = React.createClass({
         });
       })
       .done();
-  },
+  }
 
-  render: function() {
+  render() {
     if (!this.state.loaded) {
       return this.renderLoadingView();
     }
@@ -433,9 +445,9 @@ var AwesomeProject = React.createClass({
         style={styles.listView}
       />
     );
-  },
+  }
 
-  renderLoadingView: function() {
+  renderLoadingView() {
     return (
       <View style={styles.container}>
         <Text>
@@ -443,9 +455,9 @@ var AwesomeProject = React.createClass({
         </Text>
       </View>
     );
-  },
+  }
 
-  renderMovie: function(movie) {
+  renderMovie(movie) {
     return (
       <View style={styles.container}>
         <Image
@@ -458,8 +470,8 @@ var AwesomeProject = React.createClass({
         </View>
       </View>
     );
-  },
-});
+  }
+}
 
 var styles = StyleSheet.create({
   container: {
diff --git a/docs/Upgrading.md b/docs/Upgrading.md
index e427796f6bac..4034b4f731cf 100644
--- a/docs/Upgrading.md
+++ b/docs/Upgrading.md
@@ -4,7 +4,7 @@ title: Upgrading
 layout: docs
 category: Guides
 permalink: docs/upgrading.html
-next: native-modules-ios
+next: platform-specific-code
 ---
 
 Upgrading to new versions of React Native will give you access to more APIs, views, developer tools
@@ -15,15 +15,14 @@ version of React Native:
 
 ## 1. Upgrade the `react-native` dependency
 
-Note the latest version of the `react-native` npm package from here:
+Note the latest version of the `react-native` npm package from here (or use `npm info react-native` to check):
 
 * https://www.npmjs.com/package/react-native
 
-Open your `package.json` file and update the version of `react-native` under `dependencies` to the
-latest version. Now, in a terminal run:
+Now install that version of `react-native` in your project with `npm install --save`. For example, to upgrade to the version `0.18`, in a terminal run:
 
 ```sh
-$ npm install
+$ npm install --save react-native@0.18
 ```
 
 ## 2. Upgrade your project templates
@@ -51,7 +50,7 @@ Xcode project format is pretty complex and sometimes it's tricky to upgrade and
 
 ### From 0.13 to 0.14
 
-The major change in this version happened to the CLI ([see changelog](https://github.com/facebook/react-native/releases/tag/v0.14.0-rc)) and static images ([see docs](http://facebook.github.io/react-native/docs/images.html)). To use the new asset system in existing Xcode project, do the following:
+The major change in this version happened to the CLI ([see changelog](https://github.com/facebook/react-native/releases/tag/v0.14.0-rc)) and static images ([see docs](docs/images.html)). To use the new asset system in existing Xcode project, do the following:
 
 Add new "Run Script" step to your project's build phases:
 
diff --git a/docs/Videos.md b/docs/Videos.md
index 289f75a4c57f..aec0c770fe91 100644
--- a/docs/Videos.md
+++ b/docs/Videos.md
@@ -7,6 +7,35 @@ permalink: docs/videos.html
 next: style
 ---
 
+### React.js Conf 2016
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/2Zthnq-hIXA" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/Xnqy_zkBAew" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/RBg2_uQE4KM" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/0MlT74erp60" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/B8J8xn3pLpk" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/f1Sj48rJE3I" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/uBYPqb83C7k" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/09ddrCaLo10" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/d3VVfA9hWjc" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/impQkQOCbMw" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/wuLKELLuwVk" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/Zoerbz5Mu5U" frameborder="0" allowfullscreen></iframe>
+
+
+### React.js Conf 2015 
+
 <iframe width="650" height="315" src="//www.youtube.com/embed/KVZ-P-ZI6W4" frameborder="0" allowfullscreen></iframe>
 
 <iframe width="650" height="315" src="//www.youtube.com/embed/7rDsRXj9-cU" frameborder="0" allowfullscreen></iframe>
@@ -17,6 +46,10 @@ next: style
 
 <iframe width="650" height="315" src="//www.youtube.com/embed/hDviGU-57lU" frameborder="0" allowfullscreen></iframe>
 
+<iframe width="650" height="315" src="//www.youtube.com/embed/8N4f4h6SThc" frameborder="0" allowfullscreen></iframe>
+
+<iframe width="650" height="315" src="//www.youtube.com/embed/-XxSCi8TKuk" frameborder="0" allowfullscreen></iframe>
+
 ### [The Changelog #149](https://thechangelog.com/149/)
 With Christopher "vjeux" Chedeau and Spencer Ahrens
 
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64a8698..941144813d24 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e760dc93..0c42144e8035 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Mon Dec 14 14:06:01 GMT 2015
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-2.11-all.zip
diff --git a/gradlew b/gradlew
index 91a7e269e19d..9d82f7891513 100755
--- a/gradlew
+++ b/gradlew
@@ -42,11 +42,6 @@ case "`uname`" in
     ;;
 esac
 
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
-    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
 # Attempt to set APP_HOME
 # Resolve links: $0 may be a link
 PRG="$0"
@@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do
     fi
 done
 SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
+cd "`dirname \"$PRG\"`/" >/dev/null
 APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
+cd "$SAVED" >/dev/null
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
@@ -114,6 +109,7 @@ fi
 if $cygwin ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
     ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
diff --git a/jestSupport/env.js b/jestSupport/env.js
index 963d75e14f59..66cad00103b8 100644
--- a/jestSupport/env.js
+++ b/jestSupport/env.js
@@ -10,3 +10,12 @@
 
 require('../packager/react-packager/src/Resolver/polyfills/babelHelpers.js');
 global.__DEV__ = true;
+global.__fbBatchedBridgeConfig = {
+  remoteModuleConfig: [],
+  localModulesConfig: [],
+};
+
+global.Promise = require('promise');
+global.regeneratorRuntime = require.requireActual('regenerator/runtime');
+
+jest.setMock('ErrorUtils', require('ErrorUtils'));
diff --git a/jestSupport/preprocessor.js b/jestSupport/preprocessor.js
index b2006b7d24ce..7d1376b27dde 100644
--- a/jestSupport/preprocessor.js
+++ b/jestSupport/preprocessor.js
@@ -26,6 +26,6 @@ module.exports = {
   getCacheKey: createCacheKeyFunction([
     __filename,
     path.join(__dirname, '../packager/transformer.js'),
-    path.join(__dirname, '../node_modules/babel-core/package.json'),
+    require.resolve('babel-core/package.json'),
   ]),
 };
diff --git a/keystores/BUCK b/keystores/BUCK
new file mode 100644
index 000000000000..15da20e6b92c
--- /dev/null
+++ b/keystores/BUCK
@@ -0,0 +1,8 @@
+keystore(
+  name = 'debug',
+  store = 'debug.keystore',
+  properties = 'debug.keystore.properties',
+  visibility = [
+    'PUBLIC',
+  ],
+)
diff --git a/keystores/debug.keystore b/keystores/debug.keystore
new file mode 100644
index 000000000000..364e105ed39f
Binary files /dev/null and b/keystores/debug.keystore differ
diff --git a/keystores/debug.keystore.properties b/keystores/debug.keystore.properties
new file mode 100644
index 000000000000..121bfb49f0df
--- /dev/null
+++ b/keystores/debug.keystore.properties
@@ -0,0 +1,4 @@
+key.store=debug.keystore
+key.alias=androiddebugkey
+key.store.password=android
+key.alias.password=android
diff --git a/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js b/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js
index 1613b5b3656a..3f58299549cc 100644
--- a/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js
+++ b/local-cli/bundle/__tests__/getAssetDestPathAndroid-test.js
@@ -8,7 +8,9 @@
  */
 'use strict';
 
-jest.dontMock('../getAssetDestPathAndroid');
+jest
+  .dontMock('../getAssetDestPathAndroid')
+  .dontMock('../assetPathUtils');
 
 const getAssetDestPathAndroid = require('../getAssetDestPathAndroid');
 
diff --git a/local-cli/bundle/__tests__/saveBundleAndMap-test.js b/local-cli/bundle/__tests__/saveBundleAndMap-test.js
deleted file mode 100644
index 4bd516a05d46..000000000000
--- a/local-cli/bundle/__tests__/saveBundleAndMap-test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest.autoMockOff();
-
-jest.mock('fs');
-jest.mock('../sign');
-
-const saveBundleAndMap = require('../saveBundleAndMap');
-const fs = require('fs');
-const temp = require('temp');
-
-const code = 'const foo = "bar";';
-const map = JSON.stringify({
-  version: 3,
-  file: 'foo.js.map',
-  sources: ['foo.js'],
-  sourceRoot: '/',
-  names: ['bar'],
-  mappings: 'AAA0B,kBAAhBA,QAAOC,SACjBD,OAAOC,OAAO'
-});
-
-describe('saveBundleAndMap', () => {
-  beforeEach(() => {
-    fs.writeFileSync = jest.genMockFn();
-  });
-
-  it('should save bundle', () => {
-    const codeWithMap = {code: code};
-    const bundleOutput = temp.path({suffix: '.bundle'});
-
-    saveBundleAndMap(
-      codeWithMap,
-      'ios',
-      bundleOutput,
-      'utf8',
-    );
-
-    expect(fs.writeFileSync.mock.calls[0]).toEqual([bundleOutput, code, 'utf8']);
-  });
-
-  it('should save sourcemaps if required so', () => {
-    const codeWithMap = {code: code, map: map};
-    const bundleOutput = temp.path({suffix: '.bundle'});
-    const sourceMapOutput = temp.path({suffix: '.map'});
-    saveBundleAndMap(
-      codeWithMap,
-      'ios',
-      bundleOutput,
-      'utf8',
-      sourceMapOutput
-    );
-
-    expect(fs.writeFileSync.mock.calls[1]).toEqual([sourceMapOutput, map]);
-  });
-});
diff --git a/local-cli/bundle/assetPathUtils.js b/local-cli/bundle/assetPathUtils.js
new file mode 100644
index 000000000000..cf14b2c62cf0
--- /dev/null
+++ b/local-cli/bundle/assetPathUtils.js
@@ -0,0 +1,56 @@
+/**
+ * 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.
+ */
+'use strict';
+
+function getAndroidAssetSuffix(scale) {
+  switch (scale) {
+    case 0.75: return 'ldpi';
+    case 1: return 'mdpi';
+    case 1.5: return 'hdpi';
+    case 2: return 'xhdpi';
+    case 3: return 'xxhdpi';
+    case 4: return 'xxxhdpi';
+  }
+}
+
+function getAndroidDrawableFolderName(asset, scale) {
+  var suffix = getAndroidAssetSuffix(scale);
+  if (!suffix) {
+    throw new Error(
+      'Don\'t know which android drawable suffix to use for asset: ' +
+      JSON.stringify(asset)
+    );
+  }
+  const androidFolder = 'drawable-' + suffix;
+  return androidFolder;
+}
+
+function getAndroidResourceIdentifier(asset) {
+  var folderPath = getBasePath(asset);
+  return (folderPath + '/' + asset.name)
+    .toLowerCase()
+    .replace(/\//g, '_')           // Encode folder structure in file name
+    .replace(/([^a-z0-9_])/g, '')  // Remove illegal chars
+    .replace(/^assets_/, '');      // Remove "assets_" prefix
+}
+
+function getBasePath(asset) {
+  var basePath = asset.httpServerLocation;
+  if (basePath[0] === '/') {
+    basePath = basePath.substr(1);
+  }
+  return basePath;
+}
+
+module.exports = {
+  getAndroidAssetSuffix: getAndroidAssetSuffix,
+  getAndroidDrawableFolderName: getAndroidDrawableFolderName,
+  getAndroidResourceIdentifier: getAndroidResourceIdentifier,
+  getBasePath: getBasePath
+};
\ No newline at end of file
diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js
index f69eea5a8d78..5b75ca3fb687 100644
--- a/local-cli/bundle/buildBundle.js
+++ b/local-cli/bundle/buildBundle.js
@@ -9,12 +9,12 @@
 'use strict';
 
 const log = require('../util/log').out('bundle');
-const processBundle = require('./processBundle');
+const outputBundle = require('./output/bundle');
 const Promise = require('promise');
 const ReactPackager = require('../../packager/react-packager');
-const saveBundleAndMap = require('./saveBundleAndMap');
+const saveAssets = require('./saveAssets');
 
-function buildBundle(args, config) {
+function buildBundle(args, config, output = outputBundle) {
   return new Promise((resolve, reject) => {
 
     // This is used by a bazillion of npm modules we don't control so we don't
@@ -25,35 +25,51 @@ function buildBundle(args, config) {
       projectRoots: config.getProjectRoots(),
       assetRoots: config.getAssetRoots(),
       blacklistRE: config.getBlacklistRE(),
+      getTransformOptionsModulePath: config.getTransformOptionsModulePath,
       transformModulePath: args.transformer,
       verbose: args.verbose,
+      disableInternalTransforms: true,
     };
 
     const requestOpts = {
       entryFile: args['entry-file'],
+      sourceMapUrl: args['sourcemap-output'],
       dev: args.dev,
       minify: !args.dev,
       platform: args.platform,
     };
 
-    resolve(ReactPackager.createClientFor(options).then(client => {
-      log('Created ReactPackager');
-      return client.buildBundle(requestOpts)
-        .then(outputBundle => {
-          log('Closing client');
-          client.close();
-          return outputBundle;
-        })
-        .then(outputBundle => processBundle(outputBundle, args.dev))
-        .then(outputBundle => saveBundleAndMap(
-          outputBundle,
-          args.platform,
-          args['bundle-output'],
-          args['bundle-encoding'],
-          args['sourcemap-output'],
-          args['assets-dest']
-        ));
-    }));
+    const clientPromise = ReactPackager.createClientFor(options);
+
+    // Build and save the bundle
+    const bundlePromise = clientPromise
+      .then(client => {
+        log('Created ReactPackager');
+        return output.build(client, requestOpts);
+      })
+      .then(bundle => {
+        output.save(bundle, args, log);
+        return bundle;
+      });
+
+    // When we're done bundling, close the client
+    Promise.all([clientPromise, bundlePromise])
+      .then(([client]) => {
+        log('Closing client');
+        client.close();
+      });
+
+    // Save the assets of the bundle
+    const assets = bundlePromise
+      .then(bundle => bundle.getAssets())
+      .then(outputAssets => saveAssets(
+        outputAssets,
+        args.platform,
+        args['assets-dest']
+      ));
+
+    // When we're done saving bundle output and the assets, we're done.
+    resolve(assets);
   });
 }
 
diff --git a/local-cli/bundle/bundle.js b/local-cli/bundle/bundle.js
index 6826de71faa4..debbb62ecfc1 100644
--- a/local-cli/bundle/bundle.js
+++ b/local-cli/bundle/bundle.js
@@ -11,12 +11,24 @@
 const buildBundle = require('./buildBundle');
 const bundleCommandLineArgs = require('./bundleCommandLineArgs');
 const parseCommandLine = require('../util/parseCommandLine');
+const outputBundle = require('./output/bundle');
+const outputPrepack = require('./output/prepack');
 
 /**
  * Builds the bundle starting to look for dependencies at the given entry path.
  */
+function bundleWithOutput(argv, config, output) {
+  const args = parseCommandLine(bundleCommandLineArgs, argv);
+  if (!output) {
+    output = args.prepack ? outputPrepack : outputBundle;
+  }
+  return buildBundle(args, config, output);
+
+}
+
 function bundle(argv, config) {
-  return buildBundle(parseCommandLine(bundleCommandLineArgs, argv), config);
+  return bundleWithOutput(argv, config);
 }
 
 module.exports = bundle;
+module.exports.withOutput = bundleWithOutput;
diff --git a/local-cli/bundle/bundleCommandLineArgs.js b/local-cli/bundle/bundleCommandLineArgs.js
index 353c7ea5f28e..2597a7f13154 100644
--- a/local-cli/bundle/bundleCommandLineArgs.js
+++ b/local-cli/bundle/bundleCommandLineArgs.js
@@ -27,6 +27,14 @@ module.exports = [
     command: 'dev',
     description: 'If false, warnings are disabled and the bundle is minified',
     default: true,
+  }, {
+    command: 'prepack',
+    description: 'If true, the output bundle will use the Prepack format.',
+    default: false
+  }, {
+    command: 'bridge-config',
+    description: 'File name of a a JSON export of __fbBatchedBridgeConfig. Used by Prepack. Ex. ./bridgeconfig.json',
+    type: 'string'
   }, {
     command: 'bundle-output',
     description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle',
diff --git a/local-cli/bundle/getAssetDestPathAndroid.js b/local-cli/bundle/getAssetDestPathAndroid.js
index 3e039f54fd5e..f462ba5e81d1 100644
--- a/local-cli/bundle/getAssetDestPathAndroid.js
+++ b/local-cli/bundle/getAssetDestPathAndroid.js
@@ -9,34 +9,11 @@
 'use strict';
 
 const path = require('path');
-
-function getAndroidAssetSuffix(scale) {
-  switch (scale) {
-    case 0.75: return 'ldpi';
-    case 1: return 'mdpi';
-    case 1.5: return 'hdpi';
-    case 2: return 'xhdpi';
-    case 3: return 'xxhdpi';
-    case 4: return 'xxxhdpi';
-  }
-}
+const assetPathUtils = require('./assetPathUtils');
 
 function getAssetDestPathAndroid(asset, scale) {
-  var suffix = getAndroidAssetSuffix(scale);
-  if (!suffix) {
-    throw new Error(
-      'Don\'t know which android drawable suffix to use for asset: ' +
-      JSON.stringify(asset)
-    );
-  }
-  const androidFolder = 'drawable-' + suffix;
-  // TODO: reuse this logic from https://fburl.com/151101135
-  const fileName = (asset.httpServerLocation.substr(1) + '/' + asset.name)
-    .toLowerCase()
-    .replace(/\//g, '_')           // Encode folder structure in file name
-    .replace(/([^a-z0-9_])/g, '')  // Remove illegal chars
-    .replace(/^assets_/, '');      // Remove "assets_" prefix
-
+  const androidFolder = assetPathUtils.getAndroidDrawableFolderName(asset, scale);
+  const fileName =  assetPathUtils.getAndroidResourceIdentifier(asset);
   return path.join(androidFolder, fileName + '.' + asset.type);
 }
 
diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js
new file mode 100644
index 000000000000..6f40e5baccde
--- /dev/null
+++ b/local-cli/bundle/output/bundle.js
@@ -0,0 +1,59 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const Promise = require('promise');
+const sign = require('../sign');
+const writeFile = require('./writeFile');
+
+function buildBundle(packagerClient, requestOptions) {
+  return packagerClient.buildBundle(requestOptions);
+}
+
+function createCodeWithMap(bundle, dev) {
+  if (!dev) {
+    return bundle.getMinifiedSourceAndMap(dev);
+  } else {
+    return {
+      code: bundle.getSource({dev}),
+      map: JSON.stringify(bundle.getSourceMap({dev})),
+    };
+  }
+}
+
+function saveBundleAndMap(bundle, options, log) {
+  const {
+    'bundle-output': bundleOutput,
+    'bundle-encoding': encoding,
+    dev,
+    'sourcemap-output': sourcemapOutput,
+  } = options;
+
+  log('start');
+  const codeWithMap = createCodeWithMap(bundle, dev);
+  log('finish');
+
+  log('Writing bundle output to:', bundleOutput);
+  const writeBundle = writeFile(bundleOutput, sign(codeWithMap.code), encoding);
+  writeBundle.then(() => log('Done writing bundle output'));
+
+  if (sourcemapOutput) {
+    log('Writing sourcemap output to:', sourcemapOutput);
+    const writeMap = writeFile(sourcemapOutput, codeWithMap.map, null);
+    writeMap.then(() => log('Done writing sourcemap output'));
+    return Promise.all([writeBundle, writeMap]);
+  } else {
+    return writeBundle;
+  }
+}
+
+
+exports.build = buildBundle;
+exports.save = saveBundleAndMap;
+exports.formatName = 'bundle';
diff --git a/local-cli/bundle/output/prepack.js b/local-cli/bundle/output/prepack.js
new file mode 100644
index 000000000000..9165b8763571
--- /dev/null
+++ b/local-cli/bundle/output/prepack.js
@@ -0,0 +1,34 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const writeFile = require('./writeFile');
+
+function buildPrepackBundle(packagerClient, requestOptions) {
+  return packagerClient.buildPrepackBundle(requestOptions);
+}
+
+function savePrepackBundle(bundle, options, log) {
+  const {
+    'bundle-output': bundleOutput,
+    'bridge-config': bridgeConfig,
+  } = options;
+
+  const result = bundle.build({
+    batchedBridgeConfig: bridgeConfig
+  });
+
+  log('Writing prepack bundle output to:', bundleOutput);
+  const writePrepackBundle = writeFile(bundleOutput, result, 'ucs-2');
+  writePrepackBundle.then(() => log('Done writing prepack bundle output'));
+  return writePrepackBundle;
+}
+
+exports.build = buildPrepackBundle;
+exports.save = savePrepackBundle;
diff --git a/local-cli/bundle/output/unbundle/as-assets.js b/local-cli/bundle/output/unbundle/as-assets.js
new file mode 100644
index 000000000000..5f98521461ae
--- /dev/null
+++ b/local-cli/bundle/output/unbundle/as-assets.js
@@ -0,0 +1,106 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const mkdirp = require('mkdirp');
+const path = require('path');
+const Promise = require('promise');
+
+const writeFile = require('../writeFile');
+const writeSourceMap = require('./write-sourcemap');
+const MAGIC_UNBUNDLE_NUMBER = require('./magic-number');
+const MAGIC_UNBUNDLE_FILENAME = 'UNBUNDLE'; // must not start with a dot, as that won't go into the apk
+const MODULES_DIR = 'js-modules';
+
+/**
+ * Saves all JS modules of an app as single files
+ * The startup code (prelude, polyfills etc.) are written to the file
+ * designated by the `bundleOuput` option.
+ * All other modules go into a 'js-modules' folder that in the same parent
+ * directory as the startup file.
+ */
+function saveAsAssets(bundle, options, log) {
+  const {
+    'bundle-output': bundleOutput,
+    'bundle-encoding': encoding,
+    dev,
+    'sourcemap-output': sourcemapOutput,
+  } = options;
+
+  log('start');
+  const {startupCode, modules} = bundle.getUnbundle({minify: !dev});
+  log('finish');
+
+  log('Writing bundle output to:', bundleOutput);
+  const modulesDir = path.join(path.dirname(bundleOutput), MODULES_DIR);
+  const writeUnbundle =
+    createDir(modulesDir).then( // create the modules directory first
+      Promise.all([
+        writeModules(modulesDir, modules, encoding),
+        writeFile(bundleOutput, startupCode, encoding),
+        writeMagicFlagFile(modulesDir),
+      ])
+    );
+  writeUnbundle.then(() => log('Done writing unbundle output'));
+
+  return Promise.all([writeUnbundle, writeSourceMap(sourcemapOutput, '', log)]);
+}
+
+function createDir(dirName) {
+  return new Promise((resolve, reject) =>
+    mkdirp(dirName, error => error ? reject(error) : resolve()));
+}
+
+function createDirectoriesForModules(modulesDir, modules) {
+  const dirNames =
+    modules.map(name => {
+      // get all needed directory names
+      const dir = path.dirname(name);
+      return dir === '.' || dir === '' ? null : path.join(modulesDir, dir);
+    })
+    .filter(Boolean) // remove empty directories
+    .sort()
+    .filter((dir, i, dirs) => {
+      // remove parent directories and dedupe.
+      // After sorting, parent directories are located before child directories
+      const next = dirs[i + 1];
+      return !next || next !== dir && !next.startsWith(dir + path.sep);
+    });
+
+  return dirNames.reduce(
+    (promise, dirName) =>
+      promise.then(() => createDir(dirName)), Promise.resolve());
+}
+
+function writeModuleFile(module, modulesDir, encoding) {
+  const {name, code} = module;
+  return writeFile(path.join(modulesDir, name + '.js'), code, encoding);
+}
+
+function writeModuleFiles(modules, modulesDir, encoding) {
+  const writeFiles =
+    modules.map(module => writeModuleFile(module, modulesDir, encoding));
+  return Promise.all(writeFiles);
+}
+
+function writeModules(modulesDir, modules, encoding) {
+  return (
+    createDirectoriesForModules(modulesDir, modules.map(({name}) => name))
+      .then(() => writeModuleFiles(modules, modulesDir, encoding))
+  );
+}
+
+function writeMagicFlagFile(outputDir) {
+  /* global Buffer: true */
+  const buffer = Buffer(4);
+  buffer.writeUInt32LE(MAGIC_UNBUNDLE_NUMBER);
+  return writeFile(path.join(outputDir, MAGIC_UNBUNDLE_FILENAME), buffer);
+}
+
+module.exports = saveAsAssets;
diff --git a/local-cli/bundle/output/unbundle/as-indexed-file.js b/local-cli/bundle/output/unbundle/as-indexed-file.js
new file mode 100644
index 000000000000..f09b1f10d29a
--- /dev/null
+++ b/local-cli/bundle/output/unbundle/as-indexed-file.js
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const fs = require('fs');
+const Promise = require('promise');
+const writeSourceMap = require('./write-sourcemap');
+
+const MAGIC_UNBUNDLE_FILE_HEADER = require('./magic-number');
+const MAGIC_STARTUP_MODULE_ID = '';
+
+/**
+ * Saves all JS modules of an app as a single file, separated with null bytes.
+ * The file begins with an offset table that contains module ids and their
+ * lengths/offsets.
+ * The module id for the startup code (prelude, polyfills etc.) is the
+ * empty string.
+ */
+function saveAsIndexedFile(bundle, options, log) {
+  const {
+    'bundle-output': bundleOutput,
+    'bundle-encoding': encoding,
+    dev,
+    'sourcemap-output': sourcemapOutput,
+  } = options;
+
+  log('start');
+  const {startupCode, modules} = bundle.getUnbundle({minify: !dev});
+  log('finish');
+
+  log('Writing unbundle output to:', bundleOutput);
+  const writeUnbundle = writeBuffers(
+    fs.createWriteStream(bundleOutput),
+    buildTableAndContents(startupCode, modules, encoding)
+  );
+
+  writeUnbundle.then(() => log('Done writing unbundle output'));
+
+  return Promise.all([writeUnbundle, writeSourceMap(sourcemapOutput, '', log)]);
+}
+
+/* global Buffer: true */
+
+const fileHeader = Buffer(4);
+fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER);
+const nullByteBuffer = Buffer(1).fill(0);
+
+function writeBuffers(stream, buffers) {
+  buffers.forEach(buffer => stream.write(buffer));
+  return new Promise((resolve, reject) => {
+    stream.on('error', reject);
+    stream.on('finish', () => resolve());
+    stream.end();
+  });
+}
+
+const moduleToBuffer = ({name, code}, encoding) => ({
+  name,
+  buffer: Buffer.concat([
+    Buffer(code, encoding),
+    nullByteBuffer // create \0-terminated strings
+  ])
+});
+
+function uInt32Buffer(n) {
+  const buffer = Buffer(4);
+  buffer.writeUInt32LE(n, 0);
+  return buffer;
+}
+
+function buildModuleTable(buffers) {
+  // table format:
+  //  - table_length: uint_32 length of all table entries in bytes + the table length itself
+  //  - entries: entry...
+  //
+  // entry:
+  //  - module_id: NUL terminated utf8 string
+  //  - module_offset: uint_32 offset into the module string
+  //  - module_length: uint_32 length of the module string, including terminating NUL byte
+
+  const numBuffers = buffers.length;
+
+  const tableLengthBuffer = uInt32Buffer(0);
+  let tableLength = 4; // the table length itself, 4 == tableLengthBuffer.length
+  let currentOffset = 0;
+
+  const offsetTable = [tableLengthBuffer];
+  for (let i = 0; i < numBuffers; i++) {
+    const {name, buffer: {length}} = buffers[i];
+    const entry = Buffer.concat([
+      Buffer(i === 0 ? MAGIC_STARTUP_MODULE_ID : name, 'utf8'),
+      nullByteBuffer,
+      uInt32Buffer(currentOffset),
+      uInt32Buffer(length)
+    ]);
+    currentOffset += length;
+    tableLength += entry.length;
+    offsetTable.push(entry);
+  }
+
+  tableLengthBuffer.writeUInt32LE(tableLength, 0);
+  return Buffer.concat(offsetTable);
+}
+
+function buildModuleBuffers(startupCode, modules, encoding) {
+  return (
+    [moduleToBuffer({name: '', code: startupCode}, encoding)]
+      .concat(modules.map(module => moduleToBuffer(module, encoding)))
+  );
+}
+
+function buildTableAndContents(startupCode, modules, encoding) {
+  const buffers = buildModuleBuffers(startupCode, modules, encoding);
+  const table = buildModuleTable(buffers, encoding);
+  return [fileHeader, table].concat(buffers.map(({buffer}) => buffer));
+}
+
+module.exports = saveAsIndexedFile;
diff --git a/local-cli/bundle/output/unbundle/index.js b/local-cli/bundle/output/unbundle/index.js
new file mode 100644
index 000000000000..eb34cbbbf174
--- /dev/null
+++ b/local-cli/bundle/output/unbundle/index.js
@@ -0,0 +1,29 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const asIndexedFile = require('./as-indexed-file');
+const asAssets = require('./as-assets');
+
+function buildBundle(packagerClient, requestOptions) {
+  return packagerClient.buildBundle({...requestOptions, unbundle: true});
+}
+
+function saveUnbundle(bundle, options, log) {
+  // we fork here depending on the platform:
+  // while android is pretty good at loading individual assets, ios has a large
+  // overhead when reading hundreds pf assets from disk
+  return options.platform === 'android' ?
+    asAssets(bundle, options, log) :
+    asIndexedFile(bundle, options, log);
+}
+
+exports.build = buildBundle;
+exports.save = saveUnbundle;
+exports.formatName = 'bundle';
diff --git a/local-cli/bundle/output/unbundle/magic-number.js b/local-cli/bundle/output/unbundle/magic-number.js
new file mode 100644
index 000000000000..6e6a9d3759d6
--- /dev/null
+++ b/local-cli/bundle/output/unbundle/magic-number.js
@@ -0,0 +1,11 @@
+/**
+ * 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.
+ */
+'use strict';
+
+module.exports = 0xFB0BD1E5;
diff --git a/local-cli/bundle/output/unbundle/write-sourcemap.js b/local-cli/bundle/output/unbundle/write-sourcemap.js
new file mode 100644
index 000000000000..b194b99222e7
--- /dev/null
+++ b/local-cli/bundle/output/unbundle/write-sourcemap.js
@@ -0,0 +1,24 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const Promise = require('promise');
+const writeFile = require('../writeFile');
+
+function writeSourcemap(fileName, contents, log) {
+  if (!fileName) {
+    return Promise.resolve();
+  }
+  log('Writing sourcemap output to:', fileName);
+  const writeMap = writeFile(fileName, '', null);
+  writeMap.then(() => log('Done writing sourcemap output'));
+  return writeMap;
+}
+
+module.exports = writeSourcemap;
diff --git a/packager/react-packager/src/DependencyResolver/replacePatterns.js b/local-cli/bundle/output/writeFile.js
similarity index 51%
rename from packager/react-packager/src/DependencyResolver/replacePatterns.js
rename to local-cli/bundle/output/writeFile.js
index a4e563d2c67c..bb30d941fae5 100644
--- a/packager/react-packager/src/DependencyResolver/replacePatterns.js
+++ b/local-cli/bundle/output/writeFile.js
@@ -6,10 +6,20 @@
  * 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.
  */
-
 'use strict';
 
-exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
-exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
-exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
-exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
+const fs = require('fs');
+const Promise = require('promise');
+
+function writeFile(file, data, encoding) {
+  return new Promise((resolve, reject) => {
+    fs.writeFile(
+      file,
+      data,
+      encoding,
+      error => error ? reject(error) : resolve()
+    );
+  });
+}
+
+module.exports = writeFile;
diff --git a/local-cli/bundle/processBundle.js b/local-cli/bundle/processBundle.js
deleted file mode 100644
index 3a86a61f522e..000000000000
--- a/local-cli/bundle/processBundle.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const log = require('../util/log').out('bundle');
-
-function processBundle(input, dev) {
-  log('start');
-  let bundle;
-  if (!dev) {
-    bundle = input.getMinifiedSourceAndMap(dev);
-  } else {
-    bundle = {
-      code: input.getSource({ dev }),
-      map: JSON.stringify(input.getSourceMap({ dev })),
-    };
-  }
-  bundle.assets = input.getAssets();
-  log('finish');
-  return bundle;
-}
-
-module.exports = processBundle;
diff --git a/local-cli/bundle/saveBundleAndMap.js b/local-cli/bundle/saveAssets.js
similarity index 76%
rename from local-cli/bundle/saveBundleAndMap.js
rename to local-cli/bundle/saveAssets.js
index bb6bf2291f22..f95cd4c166ae 100644
--- a/local-cli/bundle/saveBundleAndMap.js
+++ b/local-cli/bundle/saveAssets.js
@@ -8,32 +8,18 @@
  */
 'use strict';
 
-const execFile = require('child_process').execFile;
 const fs = require('fs');
 const getAssetDestPathAndroid = require('./getAssetDestPathAndroid');
 const getAssetDestPathIOS = require('./getAssetDestPathIOS');
 const log = require('../util/log').out('bundle');
+const mkdirp = require('mkdirp');
 const path = require('path');
-const sign = require('./sign');
 
-function saveBundleAndMap(
-  codeWithMap,
+function saveAssets(
+  assets,
   platform,
-  bundleOutput,
-  encoding,
-  sourcemapOutput,
   assetsDest
 ) {
-  log('Writing bundle output to:', bundleOutput);
-  fs.writeFileSync(bundleOutput, sign(codeWithMap.code), encoding);
-  log('Done writing bundle output');
-
-  if (sourcemapOutput) {
-    log('Writing sourcemap output to:', sourcemapOutput);
-    fs.writeFileSync(sourcemapOutput, codeWithMap.map);
-    log('Done writing sourcemap output');
-  }
-
   if (!assetsDest) {
     console.warn('Assets destination folder is not set, skipping...');
     return Promise.resolve();
@@ -44,7 +30,7 @@ function saveBundleAndMap(
     : getAssetDestPathIOS;
 
   const filesToCopy = Object.create(null); // Map src -> dest
-  codeWithMap.assets
+  assets
     .filter(asset => !asset.deprecated)
     .forEach(asset =>
       asset.scales.forEach((scale, idx) => {
@@ -84,7 +70,7 @@ function copyAll(filesToCopy) {
 
 function copy(src, dest, callback) {
   const destDir = path.dirname(dest);
-  execFile('mkdir', ['-p', destDir], err => {
+  mkdirp(destDir, err => {
     if (err) {
       return callback(err);
     }
@@ -94,4 +80,4 @@ function copy(src, dest, callback) {
   });
 }
 
-module.exports = saveBundleAndMap;
+module.exports = saveAssets;
diff --git a/packager/react-packager/src/DependencyResolver/lib/replacePatterns.js b/local-cli/bundle/unbundle.js
similarity index 50%
rename from packager/react-packager/src/DependencyResolver/lib/replacePatterns.js
rename to local-cli/bundle/unbundle.js
index a4e563d2c67c..042093ced897 100644
--- a/packager/react-packager/src/DependencyResolver/lib/replacePatterns.js
+++ b/local-cli/bundle/unbundle.js
@@ -6,10 +6,16 @@
  * 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.
  */
-
 'use strict';
 
-exports.IMPORT_RE = /(\bimport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
-exports.EXPORT_RE = /(\bexport\s+(?:[^'"]+\s+from\s+)??)(['"])([^'"]+)(\2)/g;
-exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
-exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
+const bundleWithOutput = require('./bundle').withOutput;
+const outputUnbundle = require('./output/unbundle');
+
+/**
+ * Builds the bundle starting to look for dependencies at the given entry path.
+ */
+function unbundle(argv, config) {
+  return bundleWithOutput(argv, config, outputUnbundle);
+}
+
+module.exports = unbundle;
diff --git a/local-cli/cli.js b/local-cli/cli.js
index a7b6a2cf5ef6..ace3251d764c 100644
--- a/local-cli/cli.js
+++ b/local-cli/cli.js
@@ -18,16 +18,17 @@ var childProcess = require('child_process');
 var Config = require('./util/Config');
 var defaultConfig = require('./default.config');
 var dependencies = require('./dependencies/dependencies');
-var fs = require('fs');
 var generate = require('./generate/generate');
 var library = require('./library/library');
 var link = require('./library/link');
 var path = require('path');
 var Promise = require('promise');
 var runAndroid = require('./runAndroid/runAndroid');
+var runIOS = require('./runIOS/runIOS');
 var server = require('./server/server');
 var TerminalAdapter = require('yeoman-environment/lib/adapter.js');
 var yeoman = require('yeoman-environment');
+var unbundle = require('./bundle/unbundle');
 var upgrade = require('./upgrade/upgrade');
 
 var fs = require('fs');
@@ -40,10 +41,12 @@ gracefulFs.gracefulify(fs);
 var documentedCommands = {
   'start': [server, 'starts the webserver'],
   'bundle': [bundle, 'builds the javascript bundle for offline use'],
+  'unbundle': [unbundle, 'builds javascript as "unbundle" for offline use'],
   'new-library': [library, 'generates a native library bridge'],
   'link': [link, 'Adds a third-party library to your project. Example: react-native link awesome-camera'],
   'android': [generateWrapper, 'generates an Android project for your app'],
   'run-android': [runAndroid, 'builds your app and starts it on a connected Android emulator or device'],
+  'run-ios': [runIOS, 'builds your app and starts it on iOS simulator'],
   'upgrade': [upgrade, 'upgrade your app\'s template files to the latest version; run this after ' +
                        'updating the react-native version in your package.json and running npm install']
 };
diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js
index 54c1b004f8ab..8ada423cf262 100644
--- a/local-cli/dependencies/dependencies.js
+++ b/local-cli/dependencies/dependencies.js
@@ -60,8 +60,10 @@ function _dependencies(argv, config, resolve, reject) {
     projectRoots: config.getProjectRoots(),
     assetRoots: config.getAssetRoots(),
     blacklistRE: config.getBlacklistRE(args.platform),
+    getTransformOptionsModulePath: config.getTransformOptionsModulePath,
     transformModulePath: args.transformer,
     verbose: config.verbose,
+    disableInternalTransforms: true,
   };
 
   const relativePath = packageOpts.projectRoots.map(root =>
@@ -104,7 +106,9 @@ function _dependencies(argv, config, resolve, reject) {
             outStream.write(modulePath + '\n');
           }
         });
-        writeToFile && outStream.end();
+        return writeToFile
+          ? Promise.denodeify(outStream.end).bind(outStream)()
+          : Promise.resolve();
         // log('Wrote dependencies to output file');
       });
   }));
diff --git a/local-cli/generator-android/templates/package/MainActivity.java b/local-cli/generator-android/templates/package/MainActivity.java
index 1e2b912f9414..283dffc92e17 100644
--- a/local-cli/generator-android/templates/package/MainActivity.java
+++ b/local-cli/generator-android/templates/package/MainActivity.java
@@ -1,78 +1,40 @@
 package <%= package %>;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.KeyEvent;
-
-import com.facebook.react.LifecycleState;
-import com.facebook.react.ReactInstanceManager;
-import com.facebook.react.ReactRootView;
-import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.ReactActivity;
+import com.facebook.react.ReactPackage;
 import com.facebook.react.shell.MainReactPackage;
-import com.facebook.soloader.SoLoader;
-
-public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
-
-    private ReactInstanceManager mReactInstanceManager;
-    private ReactRootView mReactRootView;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mReactRootView = new ReactRootView(this);
-
-        mReactInstanceManager = ReactInstanceManager.builder()
-                .setApplication(getApplication())
-                .setBundleAssetName("index.android.bundle")
-                .setJSMainModuleName("index.android")
-                .addPackage(new MainReactPackage())
-                .setUseDeveloperSupport(BuildConfig.DEBUG)
-                .setInitialLifecycleState(LifecycleState.RESUMED)
-                .build();
 
-        mReactRootView.startReactApplication(mReactInstanceManager, "<%= name %>", null);
+import java.util.Arrays;
+import java.util.List;
 
-        setContentView(mReactRootView);
-    }
+public class MainActivity extends ReactActivity {
 
+    /**
+     * Returns the name of the main component registered from JavaScript.
+     * This is used to schedule rendering of the component.
+     */
     @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
-            mReactInstanceManager.showDevOptionsDialog();
-            return true;
-        }
-        return super.onKeyUp(keyCode, event);
+    protected String getMainComponentName() {
+        return "<%= name %>";
     }
 
+    /**
+     * Returns whether dev mode should be enabled.
+     * This enables e.g. the dev menu.
+     */
     @Override
-    public void onBackPressed() {
-      if (mReactInstanceManager != null) {
-        mReactInstanceManager.onBackPressed();
-      } else {
-        super.onBackPressed();
-      }
+    protected boolean getUseDeveloperSupport() {
+        return BuildConfig.DEBUG;
     }
 
+    /**
+     * A list of packages used by the app. If the app uses additional views
+     * or modules besides the default ones, add more packages here.
+     */
     @Override
-    public void invokeDefaultOnBackPressed() {
-      super.onBackPressed();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        if (mReactInstanceManager != null) {
-            mReactInstanceManager.onPause();
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        if (mReactInstanceManager != null) {
-            mReactInstanceManager.onResume(this, this);
-        }
+    protected List<ReactPackage> getPackages() {
+        return Arrays.<ReactPackage>asList(
+            new MainReactPackage()
+        );
     }
 }
diff --git a/local-cli/generator-android/templates/src/app/build.gradle b/local-cli/generator-android/templates/src/app/build.gradle
index 1298bb3f474c..428bb043dd75 100644
--- a/local-cli/generator-android/templates/src/app/build.gradle
+++ b/local-cli/generator-android/templates/src/app/build.gradle
@@ -1,7 +1,10 @@
 apply plugin: "com.android.application"
 
+import com.android.build.OutputFile
+
 /**
- * The react.gradle file registers two tasks: bundleDebugJsAndAssets and bundleReleaseJsAndAssets.
+ * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
+ * and bundleReleaseJsAndAssets).
  * These basically call `react-native bundle` with the correct arguments during the Android build
  * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
  * bundle directly from the development server. Below you can see all the possible configurations
@@ -21,6 +24,15 @@ apply plugin: "com.android.application"
  *   // whether to bundle JS and assets in release mode
  *   bundleInRelease: true,
  *
+ *   // whether to bundle JS and assets in another build variant (if configured).
+ *   // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
+ *   // The configuration property can be in the following formats
+ *   //         'bundleIn${productFlavor}${buildType}'
+ *   //         'bundleIn${buildType}'
+ *   // bundleInFreeDebug: true,
+ *   // bundleInPaidRelease: true,
+ *   // bundleInBeta: true,
+ *
  *   // the root of your project, i.e. where "package.json" lives
  *   root: "../../",
  *
@@ -49,6 +61,21 @@ apply plugin: "com.android.application"
 
 apply from: "react.gradle"
 
+/**
+ * Set this to true to create two separate APKs instead of one:
+ *   - An APK that only works on ARM devices
+ *   - An APK that only works on x86 devices
+ * The advantage is the size of the APK is reduced by about 4MB.
+ * Upload all the APKs to the Play Store and people will download
+ * the correct one based on the CPU architecture of their device.
+ */
+def enableSeparateBuildPerCPUArchitecture = false
+
+/**
+ * Run Proguard to shrink the Java bytecode in release builds.
+ */
+def enableProguardInReleaseBuilds = false
+
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.1"
@@ -63,16 +90,37 @@ android {
             abiFilters "armeabi-v7a", "x86"
         }
     }
+    splits {
+        abi {
+            reset()
+            enable enableSeparateBuildPerCPUArchitecture
+            universalApk false  // If true, also generate a universal APK
+            include "armeabi-v7a", "x86"
+        }
+    }
     buildTypes {
         release {
-            minifyEnabled false  // Set this to true to enable Proguard
+            minifyEnabled enableProguardInReleaseBuilds
             proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
         }
     }
+    // applicationVariants are e.g. debug, release
+    applicationVariants.all { variant ->
+        variant.outputs.each { output ->
+            // For each separate APK per architecture, set a unique version code as described here:
+            // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
+            def versionCodes = ["armeabi-v7a":1, "x86":2]
+            def abi = output.getFilter(OutputFile.ABI)
+            if (abi != null) {  // null for the universal-debug, universal-release variants
+                output.versionCodeOverride =
+                        versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
+            }
+        }
+    }
 }
 
 dependencies {
     compile fileTree(dir: "libs", include: ["*.jar"])
     compile "com.android.support:appcompat-v7:23.0.1"
-    compile "com.facebook.react:react-native:0.13.0"
+    compile "com.facebook.react:react-native:+"  // From node_modules
 }
diff --git a/local-cli/generator-android/templates/src/app/proguard-rules.pro b/local-cli/generator-android/templates/src/app/proguard-rules.pro
index ffa8c9f64b62..7d72e46927a9 100644
--- a/local-cli/generator-android/templates/src/app/proguard-rules.pro
+++ b/local-cli/generator-android/templates/src/app/proguard-rules.pro
@@ -40,9 +40,12 @@
 
 -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
 -keep class * extends com.facebook.react.bridge.NativeModule { *; }
+-keepclassmembers,includedescriptorclasses class * { native <methods>; }
 -keepclassmembers class *  { @com.facebook.react.uimanager.UIProp <fields>; }
--keepclassmembers class *  { @com.facebook.react.uimanager.ReactProp <methods>; }
--keepclassmembers class *  { @com.facebook.react.uimanager.ReactPropGroup <methods>; }
+-keepclassmembers class *  { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
+-keepclassmembers class *  { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
+
+-dontwarn com.facebook.react.**
 
 # okhttp
 
@@ -58,3 +61,7 @@
 -dontwarn java.nio.file.*
 -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
 -dontwarn okio.**
+
+# stetho
+
+-dontwarn com.facebook.stetho.**
diff --git a/local-cli/generator-android/templates/src/app/react.gradle b/local-cli/generator-android/templates/src/app/react.gradle
index 1e08b00f13b9..348fb127368b 100644
--- a/local-cli/generator-android/templates/src/app/react.gradle
+++ b/local-cli/generator-android/templates/src/app/react.gradle
@@ -11,77 +11,87 @@ def elvisFile(thing) {
 }
 
 def reactRoot = elvisFile(config.root) ?: file("../../")
-def jsBundleDirDebug = elvisFile(config.jsBundleDirDebug) ?:
-        file("$buildDir/intermediates/assets/debug")
-def jsBundleDirRelease = elvisFile(config.jsBundleDirRelease) ?:
-        file("$buildDir/intermediates/assets/release")
-def resourcesDirDebug = elvisFile(config.resourcesDirDebug) ?:
-        file("$buildDir/intermediates/res/merged/debug")
-def resourcesDirRelease = elvisFile(config.resourcesDirRelease) ?:
-        file("$buildDir/intermediates/res/merged/release")
 def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
 
-def jsBundleFileDebug = file("$jsBundleDirDebug/$bundleAssetName")
-def jsBundleFileRelease = file("$jsBundleDirRelease/$bundleAssetName")
-
-task bundleDebugJsAndAssets(type: Exec) {
-    // create dirs if they are not there (e.g. the "clean" task just ran)
-    doFirst {
-        jsBundleDirDebug.mkdirs()
-        resourcesDirDebug.mkdirs()
+void runBefore(String dependentTaskName, Task task) {
+    Task dependentTask = tasks.findByPath(dependentTaskName);
+    if (dependentTask != null) {
+        dependentTask.dependsOn task
     }
+}
 
-    // set up inputs and outputs so gradle can cache the result
-    inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
-    outputs.dir jsBundleDirDebug
-    outputs.dir resourcesDirDebug
-
-    // set up the call to the react-native cli
-    workingDir reactRoot
-    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
-        commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "true", "--entry-file",
-            entryFile, "--bundle-output", jsBundleFileDebug, "--assets-dest", resourcesDirDebug
-    } else {
-        commandLine "react-native", "bundle", "--platform", "android", "--dev", "true", "--entry-file",
-            entryFile, "--bundle-output", jsBundleFileDebug, "--assets-dest", resourcesDirDebug
-    }
+gradle.projectsEvaluated {
+    // Grab all build types and product flavors
+    def buildTypes = android.buildTypes.collect { type -> type.name }
+    def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
 
-    enabled config.bundleInDebug ?: false
-}
+    // When no product flavors defined, use empty
+    if (!productFlavors) productFlavors.add('')
 
-task bundleReleaseJsAndAssets(type: Exec) {
-    // create dirs if they are not there (e.g. the "clean" task just ran)
-    doFirst {
-        jsBundleDirRelease.mkdirs()
-        resourcesDirRelease.mkdirs()
-    }
+    productFlavors.each { productFlavorName ->
+        buildTypes.each { buildTypeName ->
+            // Create variant and target names
+            def targetName = "${productFlavorName.capitalize()}${buildTypeName.capitalize()}"
+            def targetPath = productFlavorName ?
+                    "${productFlavorName}/${buildTypeName}" :
+                    "${buildTypeName}"
 
-    // set up inputs and outputs so gradle can cache the result
-    inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
-    outputs.dir jsBundleDirRelease
-    outputs.dir resourcesDirRelease
-
-    // set up the call to the react-native cli
-    workingDir reactRoot
-    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
-        commandLine "cmd","/c", "react-native", "bundle", "--platform", "android", "--dev", "false", "--entry-file",
-            entryFile, "--bundle-output", jsBundleFileRelease, "--assets-dest", resourcesDirRelease
-    } else {
-        commandLine "react-native", "bundle", "--platform", "android", "--dev", "false", "--entry-file",
-            entryFile, "--bundle-output", jsBundleFileRelease, "--assets-dest", resourcesDirRelease
-    }
+            // React js bundle directories
+            def jsBundleDirConfigName = "jsBundleDir${targetName}"
+            def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
+                    file("$buildDir/intermediates/assets/${targetPath}")
 
-    enabled config.bundleInRelease ?: true
-}
+            def resourcesDirConfigName = "resourcesDir${targetName}"
+            def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
+                    file("$buildDir/intermediates/res/merged/${targetPath}")
+            def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
 
-gradle.projectsEvaluated {
-    // hook bundleDebugJsAndAssets into the android build process
-    bundleDebugJsAndAssets.dependsOn mergeDebugResources
-    bundleDebugJsAndAssets.dependsOn mergeDebugAssets
-    processDebugResources.dependsOn bundleDebugJsAndAssets
-
-    // hook bundleReleaseJsAndAssets into the android build process
-    bundleReleaseJsAndAssets.dependsOn mergeReleaseResources
-    bundleReleaseJsAndAssets.dependsOn mergeReleaseAssets
-    processReleaseResources.dependsOn bundleReleaseJsAndAssets
+            // Bundle task name for variant
+            def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
+
+            def currentBundleTask = tasks.create(
+                    name: bundleJsAndAssetsTaskName,
+                    type: Exec) {
+                group = "react"
+                description = "bundle JS and assets for ${targetName}."
+
+                // Create dirs if they are not there (e.g. the "clean" task just ran)
+                doFirst {
+                    jsBundleDir.mkdirs()
+                    resourcesDir.mkdirs()
+                }
+
+                // Set up inputs and outputs so gradle can cache the result
+                inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
+                outputs.dir jsBundleDir
+                outputs.dir resourcesDir
+
+                // Set up the call to the react-native cli
+                workingDir reactRoot
+
+                // Set up dev mode
+                def devEnabled = !targetName.toLowerCase().contains("release")
+                if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                    commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
+                            "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
+                } else {
+                    commandLine "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
+                            "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
+                }
+
+                enabled config."bundleIn${targetName}" ||
+                    config."bundleIn${buildTypeName.capitalize()}" ?:
+                            targetName.toLowerCase().contains("release")
+            }
+
+            // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
+            currentBundleTask.dependsOn("merge${targetName}Resources")
+            currentBundleTask.dependsOn("merge${targetName}Assets")
+
+            runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask)
+            runBefore("processX86${targetName}Resources", currentBundleTask)
+            runBefore("processUniversal${targetName}Resources", currentBundleTask)
+            runBefore("process${targetName}Resources", currentBundleTask)
+        }
+    }
 }
diff --git a/local-cli/generator-android/templates/src/build.gradle b/local-cli/generator-android/templates/src/build.gradle
index ccdfc4e3dca8..403a007564d9 100644
--- a/local-cli/generator-android/templates/src/build.gradle
+++ b/local-cli/generator-android/templates/src/build.gradle
@@ -16,5 +16,9 @@ allprojects {
     repositories {
         mavenLocal()
         jcenter()
+        maven {
+            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+            url "$projectDir/../../node_modules/react-native/android"
+        }
     }
 }
diff --git a/local-cli/generator-ios/index.js b/local-cli/generator-ios/index.js
index 1e1afb67b78e..28e3c442d787 100644
--- a/local-cli/generator-ios/index.js
+++ b/local-cli/generator-ios/index.js
@@ -49,6 +49,9 @@ module.exports = yeoman.generators.NamedBase.extend({
   end: function() {
     var projectPath = path.resolve(this.destinationRoot(), 'ios', this.name);
     this.log(chalk.white.bold('To run your app on iOS:'));
+    this.log(chalk.white('   cd ' + this.destinationRoot()));
+    this.log(chalk.white('   react-native run-ios'));
+    this.log(chalk.white('   - or -'));
     this.log(chalk.white('   Open ' + projectPath + '.xcodeproj in Xcode'));
     this.log(chalk.white('   Hit the Run button'));
   }
diff --git a/local-cli/generator-ios/templates/app/AppDelegate.m b/local-cli/generator-ios/templates/app/AppDelegate.m
index 6cf213bd4cbd..909092b9cc75 100644
--- a/local-cli/generator-ios/templates/app/AppDelegate.m
+++ b/local-cli/generator-ios/templates/app/AppDelegate.m
@@ -36,7 +36,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
   /**
    * OPTION 2
    * Load from pre-bundled file on disk. The static bundle is automatically
-   * generated by "Bundle React Native code and images" build step.
+   * generated by the "Bundle React Native code and images" build step when
+   * running the project on an actual device or running the project on the
+   * simulator in the "Release" build configuration.
    */
 
 //   jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
diff --git a/local-cli/generator-ios/templates/app/Info.plist b/local-cli/generator-ios/templates/app/Info.plist
index cddf0766c980..91963b267072 100644
--- a/local-cli/generator-ios/templates/app/Info.plist
+++ b/local-cli/generator-ios/templates/app/Info.plist
@@ -40,7 +40,7 @@
 	<string></string>
   <key>NSAppTransportSecurity</key>
   <dict>
-    <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/-->
+    <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
     <key>NSAllowsArbitraryLoads</key>
     <true/>
   </dict>
diff --git a/local-cli/generator-ios/templates/xcodeproj/project.pbxproj b/local-cli/generator-ios/templates/xcodeproj/project.pbxproj
index 33a28a480822..eab5cb0cbc56 100644
--- a/local-cli/generator-ios/templates/xcodeproj/project.pbxproj
+++ b/local-cli/generator-ios/templates/xcodeproj/project.pbxproj
@@ -525,7 +525,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "../node_modules/react-native/packager/react-native-xcode.sh";
+			shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh";
 			showEnvVarsInLog = 1;
 		};
 /* End PBXShellScriptBuildPhase section */
diff --git a/local-cli/generator/index.js b/local-cli/generator/index.js
index dadbd7a17c43..90f08912a1df 100644
--- a/local-cli/generator/index.js
+++ b/local-cli/generator/index.js
@@ -82,5 +82,13 @@ module.exports = yeoman.generators.NamedBase.extend({
         {name: this.name}
       );
     }
+  },
+
+  install: function() {
+    if (this.options.upgrade) {
+      return;
+    }
+
+    this.npmInstall('react', { '--save': true });
   }
 });
diff --git a/local-cli/generator/templates/index.android.js b/local-cli/generator/templates/index.android.js
index ab6226614f7a..ce565390a48f 100644
--- a/local-cli/generator/templates/index.android.js
+++ b/local-cli/generator/templates/index.android.js
@@ -3,17 +3,16 @@
  * https://github.com/facebook/react-native
  */
 'use strict';
-
-var React = require('react-native');
-var {
+import React, {
   AppRegistry,
+  Component,
   StyleSheet,
   Text,
-  View,
-} = React;
+  View
+} from 'react-native';
 
-var <%= name %> = React.createClass({
-  render: function() {
+class <%= name %> extends Component {
+  render() {
     return (
       <View style={styles.container}>
         <Text style={styles.welcome}>
@@ -28,9 +27,9 @@ var <%= name %> = React.createClass({
       </View>
     );
   }
-});
+}
 
-var styles = StyleSheet.create({
+const styles = StyleSheet.create({
   container: {
     flex: 1,
     justifyContent: 'center',
diff --git a/local-cli/generator/templates/index.ios.js b/local-cli/generator/templates/index.ios.js
index f37d0df804b3..de44bc2534c1 100644
--- a/local-cli/generator/templates/index.ios.js
+++ b/local-cli/generator/templates/index.ios.js
@@ -3,17 +3,16 @@
  * https://github.com/facebook/react-native
  */
 'use strict';
-
-var React = require('react-native');
-var {
+import React, {
   AppRegistry,
+  Component,
   StyleSheet,
   Text,
-  View,
-} = React;
+  View
+} from 'react-native';
 
-var <%= name %> = React.createClass({
-  render: function() {
+class <%= name %> extends Component {
+  render() {
     return (
       <View style={styles.container}>
         <Text style={styles.welcome}>
@@ -29,9 +28,9 @@ var <%= name %> = React.createClass({
       </View>
     );
   }
-});
+}
 
-var styles = StyleSheet.create({
+const styles = StyleSheet.create({
   container: {
     flex: 1,
     justifyContent: 'center',
diff --git a/local-cli/runAndroid/runAndroid.js b/local-cli/runAndroid/runAndroid.js
index fa2836998c15..8cd14884a83c 100644
--- a/local-cli/runAndroid/runAndroid.js
+++ b/local-cli/runAndroid/runAndroid.js
@@ -30,9 +30,15 @@ function _runAndroid(argv, config, resolve, reject) {
     command: 'install-debug',
     type: 'string',
     required: false,
+  }, {
+    command: 'root',
+    type: 'string',
+    description: 'Override the root directory for the android build (which contains the android directory)',
   }], argv);
 
-  if (!checkAndroid()) {
+  args.root = args.root || '';
+
+  if (!checkAndroid(args)) {
     console.log(chalk.red('Android project not found. Maybe run react-native android first?'));
     return;
   }
@@ -52,13 +58,13 @@ function _runAndroid(argv, config, resolve, reject) {
 }
 
 // Verifies this is an Android project
-function checkAndroid() {
-  return fs.existsSync('android/gradlew');
+function checkAndroid(args) {
+  return fs.existsSync(path.join(args.root, 'android/gradlew'));
 }
 
 // Builds the app and runs it on a connected emulator / device.
 function buildAndRun(args, reject) {
-  process.chdir('android');
+  process.chdir(path.join(args.root, 'android'));
   try {
     const cmd = process.platform.startsWith('win')
       ? 'gradlew.bat'
diff --git a/local-cli/runIOS/__tests__/findXcodeProject-test.js b/local-cli/runIOS/__tests__/findXcodeProject-test.js
new file mode 100644
index 000000000000..d0e3832be54e
--- /dev/null
+++ b/local-cli/runIOS/__tests__/findXcodeProject-test.js
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.dontMock('../findXcodeProject');
+
+const findXcodeProject = require('../findXcodeProject');
+
+describe('findXcodeProject', () => {
+  it('should find *.xcodeproj file', () => {
+    expect(findXcodeProject([
+      '.DS_Store',
+      'AwesomeApp',
+      'AwesomeApp.xcodeproj',
+      'AwesomeAppTests',
+      'PodFile',
+      'Podfile.lock',
+      'Pods'
+    ])).toEqual({
+      name: 'AwesomeApp.xcodeproj',
+      isWorkspace: false,
+    });
+  });
+
+  it('should prefer *.xcworkspace', () => {
+    expect(findXcodeProject([
+      '.DS_Store',
+      'AwesomeApp',
+      'AwesomeApp.xcodeproj',
+      'AwesomeApp.xcworkspace',
+      'AwesomeAppTests',
+      'PodFile',
+      'Podfile.lock',
+      'Pods'
+    ])).toEqual({
+      name: 'AwesomeApp.xcworkspace',
+      isWorkspace: true,
+    });
+  });
+
+  it('should return null if nothing found', () => {
+    expect(findXcodeProject([
+      '.DS_Store',
+      'AwesomeApp',
+      'AwesomeAppTests',
+      'PodFile',
+      'Podfile.lock',
+      'Pods'
+    ])).toEqual(null);
+  });
+});
diff --git a/local-cli/runIOS/__tests__/parseIOSSimulatorsList-test.js b/local-cli/runIOS/__tests__/parseIOSSimulatorsList-test.js
new file mode 100644
index 000000000000..92396fe4a82a
--- /dev/null
+++ b/local-cli/runIOS/__tests__/parseIOSSimulatorsList-test.js
@@ -0,0 +1,53 @@
+/**
+ * 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.
+ */
+
+'use strict';
+
+jest.dontMock('../parseIOSSimulatorsList');
+var parseIOSSimulatorsList = require('../parseIOSSimulatorsList');
+
+describe('parseIOSSimulatorsList', () => {
+  it('parses typical output', () => {
+    var simulators = parseIOSSimulatorsList([
+      '== Devices ==',
+      '-- iOS 8.1 --',
+      '    iPhone 4s (4FE43B33-EF13-49A5-B6A6-658D32F20988) (Shutdown)',
+      '-- iOS 8.4 --',
+      '    iPhone 4s (EAB622C7-8ADE-4FAE-A911-94C0CA4709BB) (Shutdown)',
+      '    iPhone 5 (AE1CD3D0-A85B-4A73-B320-9CA7BA4FAEB0) (Shutdown)',
+    ].join('\n'));
+
+    expect(simulators).toEqual([
+      {name: 'iPhone 4s', udid: '4FE43B33-EF13-49A5-B6A6-658D32F20988', version: '8.1'},
+      {name: 'iPhone 4s', udid: 'EAB622C7-8ADE-4FAE-A911-94C0CA4709BB', version: '8.4'},
+      {name: 'iPhone 5',  udid: 'AE1CD3D0-A85B-4A73-B320-9CA7BA4FAEB0', version: '8.4'},
+    ]);
+  });
+
+  it('ignores unavailable simulators', () => {
+    var simulators = parseIOSSimulatorsList([
+      '== Devices ==',
+      '-- iOS 8.1 --',
+      '    iPhone 4s (4FE43B33-EF13-49A5-B6A6-658D32F20988) (Shutdown)',
+      '-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-8-3 --',
+      '    iPhone 5s (EAB622C7-8ADE-4FAE-A911-94C0CA4709BB) (Shutdown)',
+    ].join('\n'));
+
+    expect(simulators).toEqual([{
+      name: 'iPhone 4s',
+      udid: '4FE43B33-EF13-49A5-B6A6-658D32F20988',
+      version: '8.1',
+    }]);
+
+  });
+
+  it('ignores garbage', () => {
+    expect(parseIOSSimulatorsList('Something went terribly wrong (-42)')).toEqual([]);
+  });
+});
diff --git a/local-cli/runIOS/findXcodeProject.js b/local-cli/runIOS/findXcodeProject.js
new file mode 100644
index 000000000000..0d6cd302c6d0
--- /dev/null
+++ b/local-cli/runIOS/findXcodeProject.js
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+'use strict';
+
+const path = require('path');
+
+type ProjectInfo = {
+  name: string;
+  isWorkspace: boolean;
+}
+
+function findXcodeProject(files: Array<string>): ?ProjectInfo {
+  const sortedFiles = files.sort();
+  for (let i = sortedFiles.length - 1; i >= 0; i--) {
+    const fileName = files[i];
+    const ext = path.extname(fileName);
+
+    if (ext === '.xcworkspace') {
+      return {
+        name: fileName,
+        isWorkspace: true,
+      };
+    }
+    if (ext === '.xcodeproj') {
+      return {
+        name: fileName,
+        isWorkspace: false,
+      };
+    }
+  }
+
+  return null;
+}
+
+module.exports = findXcodeProject;
diff --git a/local-cli/runIOS/parseIOSSimulatorsList.js b/local-cli/runIOS/parseIOSSimulatorsList.js
new file mode 100644
index 000000000000..66f0d0ffaf62
--- /dev/null
+++ b/local-cli/runIOS/parseIOSSimulatorsList.js
@@ -0,0 +1,49 @@
+/**
+ * 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.
+ *
+ * @flow
+ */
+'use strict';
+
+type IOSSimulatorInfo = {
+  name: string;
+  udid: string;
+  version: string;
+}
+
+/**
+ * Parses the output of `xcrun simctl list devices` command
+ */
+function parseIOSSimulatorsList(text: string): Array<IOSSimulatorInfo> {
+  const devices = [];
+  var currentOS = null;
+
+  text.split('\n').forEach((line) => {
+    var section = line.match(/^-- (.+) --$/);
+    if (section) {
+      var header = section[1].match(/^iOS (.+)$/);
+      if (header) {
+        currentOS = header[1];
+      } else {
+        currentOS = null;
+      }
+      return;
+    }
+
+    const device = line.match(/^[ ]*([^()]+) \(([^()]+)\)/);
+    if (device && currentOS) {
+      var name = device[1];
+      var udid = device[2];
+      devices.push({udid, name, version: currentOS});
+    }
+  });
+
+  return devices;
+}
+
+module.exports = parseIOSSimulatorsList;
diff --git a/local-cli/runIOS/runIOS.js b/local-cli/runIOS/runIOS.js
new file mode 100644
index 000000000000..00249820e0d1
--- /dev/null
+++ b/local-cli/runIOS/runIOS.js
@@ -0,0 +1,95 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const child_process = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const parseCommandLine = require('../util/parseCommandLine');
+const findXcodeProject = require('./findXcodeProject');
+const parseIOSSimulatorsList = require('./parseIOSSimulatorsList');
+const Promise = require('promise');
+
+/**
+ * Starts the app on iOS simulator
+ */
+function runIOS(argv, config) {
+  return new Promise((resolve, reject) => {
+    _runIOS(argv, config, resolve, reject);
+    resolve();
+  });
+}
+
+function _runIOS(argv, config, resolve, reject) {
+  const args = parseCommandLine([{
+    command: 'simulator',
+    description: 'Explicitly set simulator to use',
+    type: 'string',
+    required: false,
+    default: 'iPhone 6',
+  }], argv);
+
+  process.chdir('ios');
+  const xcodeProject = findXcodeProject(fs.readdirSync('.'));
+  if (!xcodeProject) {
+    throw new Error(`Could not find Xcode project files in ios folder`);
+  }
+
+  const inferredSchemeName = path.basename(xcodeProject.name, path.extname(xcodeProject.name));
+  console.log(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} ${xcodeProject.name}`);
+
+  const simulators = parseIOSSimulatorsList(
+    child_process.execFileSync('xcrun', ['simctl', 'list', 'devices'], {encoding: 'utf8'})
+  );
+  const selectedSimulator = matchingSimulator(simulators, args.simulator);
+  if (!selectedSimulator) {
+    throw new Error(`Cound't find ${args.simulator} simulator`);
+  }
+
+  const simulatorFullName = `${selectedSimulator.name} (${selectedSimulator.version})`;
+  console.log(`Launching ${simulatorFullName}...`);
+  try {
+    child_process.spawnSync('xcrun', ['instruments', '-w', simulatorFullName]);
+  } catch(e) {
+    // instruments always fail with 255 because it expects more arguments,
+    // but we want it to only launch the simulator
+  }
+
+  const xcodebuildArgs = [
+    xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name,
+    '-scheme', inferredSchemeName,
+    '-destination', `id=${selectedSimulator.udid}`,
+    '-derivedDataPath', 'build',
+  ];
+  console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`);
+  child_process.spawnSync('xcodebuild', xcodebuildArgs, {stdio: 'inherit'});
+
+  const appPath = `build/Build/Products/Debug-iphonesimulator/${inferredSchemeName}.app`;
+  console.log(`Installing ${appPath}`);
+  child_process.spawnSync('xcrun', ['simctl', 'install', 'booted', appPath], {stdio: 'inherit'});
+
+  const bundleID = child_process.execFileSync(
+    '/usr/libexec/PlistBuddy',
+    ['-c', 'Print:CFBundleIdentifier', path.join(appPath, 'Info.plist')],
+    {encoding: 'utf8'}
+  ).trim();
+
+  console.log(`Launching ${bundleID}`);
+  child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'});
+}
+
+function matchingSimulator(simulators, simulatorName) {
+  for (let i = simulators.length - 1; i >= 0; i--) {
+    if (simulators[i].name === simulatorName) {
+      return simulators[i];
+    }
+  }
+}
+
+module.exports = runIOS;
diff --git a/local-cli/server/checkNodeVersion.js b/local-cli/server/checkNodeVersion.js
index 5d73f9e463d9..406c08cf8257 100644
--- a/local-cli/server/checkNodeVersion.js
+++ b/local-cli/server/checkNodeVersion.js
@@ -36,5 +36,6 @@ module.exports = function() {
       marginRight: 1,
       paddingBottom: 1,
     }));
+    process.exit(1);
   }
 };
diff --git a/local-cli/server/middleware/getFlowTypeCheckMiddleware.js b/local-cli/server/middleware/getFlowTypeCheckMiddleware.js
deleted file mode 100644
index 3d90507dd4fe..000000000000
--- a/local-cli/server/middleware/getFlowTypeCheckMiddleware.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-var chalk = require('chalk');
-var exec = require('child_process').exec;
-var url = require('url');
-var Activity = require('../../../packager/react-packager').Activity;
-
-var hasWarned = {};
-
-function getFlowTypeCheckMiddleware(options) {
-  return function(req, res, next) {
-    var reqObj = url.parse(req.url);
-    var isFlowCheck =  (reqObj.path.match(/^\/flow\//));
-
-    if (!isFlowCheck) {
-      return next();
-    }
-    if (options.skipflow) {
-      _endSkipFlow(res);
-      return;
-    }
-    if (options.flowroot || options.projectRoots.length === 1) {
-      var flowroot = options.flowroot || options.projectRoots[0];
-    } else {
-      if (!hasWarned.noRoot) {
-        hasWarned.noRoot = true;
-        console.warn('flow: No suitable root');
-      }
-      _endFlowBad(res);
-      return;
-    }
-    exec('command -v flow >/dev/null 2>&1', function(error, stdout) {
-      if (error) {
-        if (!hasWarned.noFlow) {
-          hasWarned.noFlow = true;
-          console.warn(chalk.yellow('flow: Skipping because not installed.  Install with ' +
-            '`brew install flow`.'));
-        }
-        _endFlowBad(res);
-        return;
-      } else {
-        return doFlowTypecheck(res, flowroot, next);
-      }
-    });
-  };
-}
-
-function doFlowTypecheck(res, flowroot, next) {
-  var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20';
-  var eventId = Activity.startEvent('flow static typechecks');
-  exec(flowCmd, function(flowError, stdout, stderr) {
-    Activity.endEvent(eventId);
-    if (!flowError) {
-      _endFlowOk(res);
-      return;
-    } else {
-      try {
-        var flowResponse = JSON.parse(stdout);
-        var errors = [];
-        var errorNum = 1;
-        flowResponse.errors.forEach(function(err) {
-          // flow errors are paired across callsites, so we indent and prefix to
-          // group them
-          var indent = '';
-          err.message.forEach(function(msg) {
-            errors.push({
-              description: indent + 'E' + errorNum + ': ' + msg.descr,
-              filename: msg.path,
-              lineNumber: msg.line,
-              column: msg.start,
-            });
-            indent = '  ';
-          });
-          errorNum++;
-        });
-        var error = {
-          status: 200,
-          message: 'Flow found type errors.  If you think these are wrong, ' +
-            'make sure your flow bin and .flowconfig are up to date, or ' +
-            'disable with --skipflow.',
-          type: 'FlowError',
-          errors: errors,
-        };
-        res.writeHead(error.status, {
-          'Content-Type': 'application/json; charset=UTF-8',
-        });
-        res.end(JSON.stringify(error));
-      } catch (e) {
-        if (stderr.match(/Could not find a \.flowconfig/)) {
-          if (!hasWarned.noConfig) {
-            hasWarned.noConfig = true;
-            console.warn(chalk.yellow('flow: ' + stderr));
-          }
-          _endFlowBad(res);
-        } else if (flowError.code === 3) {
-          if (!hasWarned.timeout) {
-            hasWarned.timeout = true;
-            console.warn(chalk.yellow('flow: ' + stdout));
-          }
-          _endSkipFlow(res);
-        } else {
-          if (!hasWarned.brokenFlow) {
-            hasWarned.brokenFlow = true;
-            console.warn(chalk.yellow(
-              'Flow failed to provide parseable output:\n\n`' + stdout +
-              '`.\n' + 'stderr: `' + stderr + '`'
-            ));
-          }
-          _endFlowBad(res);
-        }
-        return;
-      }
-    }
-  });
-}
-
-function _endRes(res, message, code, silentError) {
-  res.writeHead(code, {
-    'Content-Type': 'application/json; charset=UTF-8',
-  });
-  res.end(JSON.stringify({
-    message: message,
-    errors: [],
-    silentError: silentError,
-  }));
-}
-
-function _endFlowOk(res) {
-  _endRes(res, 'No Flow Error', '200', true);
-}
-
-function _endFlowBad(res) {
-  // we want to show that flow failed
-  // status 200 is need for the fetch to not be rejected
-  _endRes(res, 'Flow failed to run! Please look at the console for more details.', '200', false);
-}
-
-function _endSkipFlow(res) {
-  _endRes(res, 'Flow was skipped, check the server options', '200', true);
-}
-
-module.exports = getFlowTypeCheckMiddleware;
diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js
index 47e5e73a2944..57c0be9d2627 100644
--- a/local-cli/server/runServer.js
+++ b/local-cli/server/runServer.js
@@ -8,12 +8,14 @@
  */
 'use strict';
 
+const attachHMRServer = require('./util/attachHMRServer');
 const connect = require('connect');
 const cpuProfilerMiddleware = require('./middleware/cpuProfilerMiddleware');
 const getDevToolsMiddleware = require('./middleware/getDevToolsMiddleware');
 const http = require('http');
 const isAbsolutePath = require('absolute-path');
 const loadRawBodyMiddleware = require('./middleware/loadRawBodyMiddleware');
+const messageSocket = require('./util/messageSocket.js');
 const openStackFrameInEditorMiddleware = require('./middleware/openStackFrameInEditorMiddleware');
 const path = require('path');
 const ReactPackager = require('../../packager/react-packager');
@@ -23,17 +25,18 @@ const webSocketProxy = require('./util/webSocketProxy.js');
 
 function runServer(args, config, readyCallback) {
   var wsProxy = null;
+  var ms = null;
+  const packagerServer = getPackagerServer(args, config);
   const app = connect()
     .use(loadRawBodyMiddleware)
     .use(connect.compress())
     .use(getDevToolsMiddleware(args, () => wsProxy && wsProxy.isChromeConnected()))
+    .use(getDevToolsMiddleware(args, () => ms && ms.isChromeConnected()))
     .use(openStackFrameInEditorMiddleware)
     .use(statusPageMiddleware)
     .use(systraceProfileMiddleware)
     .use(cpuProfilerMiddleware)
-    // Temporarily disable flow check until it's more stable
-    //.use(getFlowTypeCheckMiddleware(args))
-    .use(getAppMiddleware(args, config));
+    .use(packagerServer.processRequest.bind(packagerServer));
 
   args.projectRoots.forEach(root => app.use(connect.static(root)));
 
@@ -42,35 +45,47 @@ function runServer(args, config, readyCallback) {
 
   const serverInstance = http.createServer(app).listen(
     args.port,
-    '::',
+    args.host,
     function() {
+      attachHMRServer({
+        httpServer: serverInstance,
+        path: '/hot',
+        packagerServer,
+      });
+
       wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy');
+      ms = messageSocket.attachToServer(serverInstance, '/message');
       webSocketProxy.attachToServer(serverInstance, '/devtools');
       readyCallback();
     }
   );
+  // Disable any kind of automatic timeout behavior for incoming
+  // requests in case it takes the packager more than the default
+  // timeout of 120 seconds to respond to a request.
+  serverInstance.timeout = 0;
 }
 
-function getAppMiddleware(args, config) {
+function getPackagerServer(args, config) {
   let transformerPath = args.transformer;
   if (!isAbsolutePath(transformerPath)) {
     transformerPath = path.resolve(process.cwd(), transformerPath);
   }
 
-  return ReactPackager.middleware({
+  return ReactPackager.createServer({
     nonPersistent: args.nonPersistent,
     projectRoots: args.projectRoots,
     blacklistRE: config.getBlacklistRE(),
     cacheVersion: '3',
+    getTransformOptionsModulePath: config.getTransformOptionsModulePath,
     transformModulePath: transformerPath,
     assetRoots: args.assetRoots,
-    assetExts: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
-    resetCache: args.resetCache || args['reset-cache'],
-    polyfillModuleNames: [
-      require.resolve(
-        '../../Libraries/JavaScriptAppEngine/polyfills/document.js'
-      ),
+    assetExts: [
+      'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats
+      'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats
+      'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats
+      'html', // Document formats
     ],
+    resetCache: args.resetCache || args['reset-cache'],
     verbose: args.verbose,
   });
 }
diff --git a/local-cli/server/server.js b/local-cli/server/server.js
index 0648d937c2de..21d1d8e2cb06 100644
--- a/local-cli/server/server.js
+++ b/local-cli/server/server.js
@@ -30,6 +30,10 @@ function _server(argv, config, resolve, reject) {
     command: 'port',
     default: 8081,
     type: 'string',
+  }, {
+    command: 'host',
+    default: '',
+    type: 'string',
   }, {
     command: 'root',
     type: 'string',
@@ -79,7 +83,7 @@ function _server(argv, config, resolve, reject) {
   }
 
   args.assetRoots = args.assetRoots
-    ? argToArray(args.projectRoots).map(dir =>
+    ? argToArray(args.assetRoots).map(dir =>
       path.resolve(process.cwd(), dir)
     )
     : config.getAssetRoots();
diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js
new file mode 100644
index 000000000000..362fdadf57a5
--- /dev/null
+++ b/local-cli/server/util/attachHMRServer.js
@@ -0,0 +1,310 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const getInverseDependencies = require('node-haste').getInverseDependencies;
+const querystring = require('querystring');
+const url = require('url');
+
+/**
+ * Attaches a WebSocket based connection to the Packager to expose
+ * Hot Module Replacement updates to the simulator.
+ */
+function attachHMRServer({httpServer, path, packagerServer}) {
+  let client = null;
+
+  function disconnect() {
+    client = null;
+    packagerServer.setHMRFileChangeListener(null);
+  }
+
+  // For the give platform and entry file, returns a promise with:
+  //   - The full list of dependencies.
+  //   - The shallow dependencies each file on the dependency list has
+  //   - Inverse shallow dependencies map
+  function getDependencies(platform, bundleEntry) {
+    return packagerServer.getDependencies({
+      platform: platform,
+      dev: true,
+      entryFile: bundleEntry,
+    }).then(response => {
+      // for each dependency builds the object:
+      // `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
+      return Promise.all(Object.values(response.dependencies).map(dep => {
+        return dep.getName().then(depName => {
+          if (dep.isAsset() || dep.isAsset_DEPRECATED() || dep.isJSON()) {
+            return Promise.resolve({path: dep.path, deps: []});
+          }
+          return packagerServer.getShallowDependencies(dep.path)
+            .then(deps => {
+              return {
+                path: dep.path,
+                name: depName,
+                deps,
+              };
+            });
+        });
+      }))
+      .then(deps => {
+        // list with all the dependencies' filenames the bundle entry has
+        const dependenciesCache = response.dependencies.map(dep => dep.path);
+
+        // map from module name to path
+        const moduleToFilenameCache = Object.create(null);
+        deps.forEach(dep => moduleToFilenameCache[dep.name] = dep.path);
+
+        // map that indicates the shallow dependency each file included on the
+        // bundle has
+        const shallowDependencies = Object.create(null);
+        deps.forEach(dep => shallowDependencies[dep.path] = dep.deps);
+
+        // map from module name to the modules' dependencies the bundle entry
+        // has
+        const dependenciesModulesCache = Object.create(null);
+        return Promise.all(response.dependencies.map(dep => {
+          return dep.getName().then(depName => {
+            dependenciesModulesCache[depName] = dep;
+          });
+        })).then(() => {
+          return getInverseDependencies(response)
+            .then(inverseDependenciesCache => {
+              return {
+                dependenciesCache,
+                dependenciesModulesCache,
+                shallowDependencies,
+                inverseDependenciesCache,
+                resolutionResponse: response,
+              };
+            });
+        });
+      });
+    });
+  }
+
+  const WebSocketServer = require('ws').Server;
+  const wss = new WebSocketServer({
+    server: httpServer,
+    path: path,
+  });
+
+  console.log('[Hot Module Replacement] Server listening on', path);
+  wss.on('connection', ws => {
+    console.log('[Hot Module Replacement] Client connected');
+    const params = querystring.parse(url.parse(ws.upgradeReq.url).query);
+
+    getDependencies(params.platform, params.bundleEntry)
+      .then(({
+        dependenciesCache,
+        dependenciesModulesCache,
+        shallowDependencies,
+        inverseDependenciesCache,
+      }) => {
+        client = {
+          ws,
+          platform: params.platform,
+          bundleEntry: params.bundleEntry,
+          dependenciesCache,
+          dependenciesModulesCache,
+          shallowDependencies,
+          inverseDependenciesCache,
+        };
+
+        packagerServer.setHMRFileChangeListener((filename, stat) => {
+          if (!client) {
+            return;
+          }
+          console.log(
+            `[Hot Module Replacement] File change detected (${time()})`
+          );
+
+          client.ws.send(JSON.stringify({type: 'update-start'}));
+          stat.then(() => {
+            return packagerServer.getShallowDependencies(filename)
+              .then(deps => {
+                if (!client) {
+                  return [];
+                }
+
+                // if the file dependencies have change we need to invalidate the
+                // dependencies caches because the list of files we need to send
+                // to the client may have changed
+                const oldDependencies = client.shallowDependencies[filename];
+                if (arrayEquals(deps, oldDependencies)) {
+                  // Need to create a resolution response to pass to the bundler
+                  // to process requires after transform. By providing a
+                  // specific response we can compute a non recursive one which
+                  // is the least we need and improve performance.
+                  return packagerServer.getDependencies({
+                    platform: client.platform,
+                    dev: true,
+                    entryFile: filename,
+                    recursive: true,
+                  }).then(response => {
+                    const module = packagerServer.getModuleForPath(filename);
+
+                    return response.copy({dependencies: [module]});
+                  });
+                }
+
+                // if there're new dependencies compare the full list of
+                // dependencies we used to have with the one we now have
+                return getDependencies(client.platform, client.bundleEntry)
+                  .then(({
+                    dependenciesCache,
+                    dependenciesModulesCache,
+                    shallowDependencies,
+                    inverseDependenciesCache,
+                    resolutionResponse,
+                  }) => {
+                    if (!client) {
+                      return {};
+                    }
+
+                    // build list of modules for which we'll send HMR updates
+                    const modulesToUpdate = [packagerServer.getModuleForPath(filename)];
+                    Object.keys(dependenciesModulesCache).forEach(module => {
+                      if (!client.dependenciesModulesCache[module]) {
+                        modulesToUpdate.push(dependenciesModulesCache[module]);
+                      }
+                    });
+
+                    // Need to send modules to the client in an order it can
+                    // process them: if a new dependency graph was uncovered
+                    // because a new dependency was added, the file that was
+                    // changed, which is the root of the dependency tree that
+                    // will be sent, needs to be the last module that gets
+                    // processed. Reversing the new modules makes sense
+                    // because we get them through the resolver which returns
+                    // a BFS ordered list.
+                    modulesToUpdate.reverse();
+
+                    // invalidate caches
+                    client.dependenciesCache = dependenciesCache;
+                    client.dependenciesModulesCache = dependenciesModulesCache;
+                    client.shallowDependencies = shallowDependencies;
+
+                    return resolutionResponse.copy({
+                      dependencies: modulesToUpdate
+                    });
+                  });
+              })
+              .then((resolutionResponse) => {
+                if (!client) {
+                  return;
+                }
+
+                // make sure the file was modified is part of the bundle
+                if (!client.shallowDependencies[filename]) {
+                  return;
+                }
+
+                const httpServerAddress = httpServer.address();
+
+                // Sanitize the value from the HTTP server
+                let packagerHost = 'localhost';
+                if (httpServer.address().address &&
+                    httpServer.address().address !== '::' &&
+                    httpServer.address().address !== '') {
+                  packagerHost = httpServerAddress.address;
+                }
+
+                let packagerPort = httpServerAddress.port;
+
+                return packagerServer.buildBundleForHMR({
+                  entryFile: client.bundleEntry,
+                  platform: client.platform,
+                  resolutionResponse,
+                }, packagerHost, packagerPort);
+              })
+              .then(bundle => {
+                if (!client || !bundle || bundle.isEmpty()) {
+                  return;
+                }
+
+                return JSON.stringify({
+                  type: 'update',
+                  body: {
+                    modules: bundle.getModulesNamesAndCode(),
+                    inverseDependencies: inverseDependenciesCache,
+                    sourceURLs: bundle.getSourceURLs(),
+                    sourceMappingURLs: bundle.getSourceMappingURLs(),
+                  },
+                });
+              })
+              .catch(error => {
+                // send errors to the client instead of killing packager server
+                let body;
+                if (error.type === 'TransformError' ||
+                    error.type === 'NotFoundError' ||
+                    error.type === 'UnableToResolveError') {
+                  body = {
+                    type: error.type,
+                    description: error.description,
+                    filename: error.filename,
+                    lineNumber: error.lineNumber,
+                  };
+                } else {
+                  console.error(error.stack || error);
+                  body = {
+                    type: 'InternalError',
+                    description: 'react-packager has encountered an internal error, ' +
+                      'please check your terminal error output for more details',
+                  };
+                }
+
+                return JSON.stringify({type: 'error', body});
+              })
+              .then(update => {
+                if (!client || !update) {
+                  return;
+                }
+
+                console.log(
+                  '[Hot Module Replacement] Sending HMR update to client (' +
+                  time() + ')'
+                );
+                client.ws.send(update);
+              });
+            },
+            () => {
+              // do nothing, file was removed
+            },
+          ).finally(() => {
+            client.ws.send(JSON.stringify({type: 'update-done'}));
+          });
+        });
+
+        client.ws.on('error', e => {
+          console.error('[Hot Module Replacement] Unexpected error', e);
+          disconnect();
+        });
+
+        client.ws.on('close', () => disconnect());
+      })
+    .done();
+  });
+}
+
+function arrayEquals(arrayA, arrayB) {
+  arrayA = arrayA || [];
+  arrayB = arrayB || [];
+  return (
+    arrayA.length === arrayB.length &&
+    arrayA.every((element, index) => {
+      return element === arrayB[index];
+    })
+  );
+}
+
+function time() {
+  const date = new Date();
+  return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}:${date.getMilliseconds()}`;
+}
+
+module.exports = attachHMRServer;
diff --git a/local-cli/server/util/debugger.html b/local-cli/server/util/debugger.html
index a69424ea98dd..878574260cc4 100644
--- a/local-cli/server/util/debugger.html
+++ b/local-cli/server/util/debugger.html
@@ -17,93 +17,82 @@
 <script>
 (function() {
 
-var sessionID = window.localStorage.getItem('sessionID');
-window.localStorage.removeItem('sessionID');
-
-window.onbeforeunload = function() {
-  if (sessionID) {
-    return 'If you reload this page, it is going to break the debugging session. ' +
-      'You should press ⌘R in simulator to reload.';
-  }
-};
-
-window.addEventListener('load', function () {
-  if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
-    document.getElementById('devtools-banner').style.display = 'block';
-  }
-});
-
-// Alias native implementations needed by the debugger before platform-specific
-// implementations are loaded into the global namespace
-var debuggerSetTimeout = window.setTimeout;
-var DebuggerWebSocket = window.WebSocket;
-
 function setStatus(status) {
   document.getElementById('status').innerHTML = status;
 }
 
-// This worker will run the application javascript code,
-// making sure that it's run in an environment without a global
-// document, to make it consistent with the JSC executor environment.
-var worker = new Worker('debuggerWorker.js');
+var INITIAL_MESSAGE = 'Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.';
 
-var messageHandlers = {
-  // This method is a bit hacky. Catalyst asks for a new clean JS runtime.
-  // The easiest way to do this is to reload this page. That also means that
-  // web socket connection will be lost. To send reply back we need to remember
-  // message id.
-  // This message also needs to be handled outside of the worker, since the worker
-  // doesn't have access to local storage.
-  'prepareJSRuntime': function(message) {
-    window.onbeforeunload = undefined;
-    window.localStorage.setItem('sessionID', message.id);
-    window.location.reload();
-  },
-  'executeApplicationScript': function(message) {
-    worker.postMessage(message);
-  },
-  'executeJSCall': function(message) {
-    worker.postMessage(message);
+function connectToDebuggerProxy() {
+  var worker;
+  var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy?role=debugger&name=Chrome');
+
+  function createJSRuntime() {
+    // This worker will run the application javascript code,
+    // making sure that it's run in an environment without a global
+    // document, to make it consistent with the JSC executor environment.
+    worker = new Worker('debuggerWorker.js');
+    worker.onmessage = function(message) {
+      ws.send(JSON.stringify(message.data));
+    };
+    window.onbeforeunload = function() {
+      return 'If you reload this page, it is going to break the debugging session. ' +
+        'You should press ⌘R in simulator to reload.';
+    };
   }
-};
 
-function connectToDebuggerProxy() {
-  var ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy');
+  function shutdownJSRuntime() {
+    if (worker) {
+      worker.terminate();
+      worker = null;
+      window.onbeforeunload = null;
+    }
+  }
 
   ws.onopen = function() {
-    if (sessionID) {
-      setStatus('Debugger session #' + sessionID + ' active.');
-      ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
-    } else {
-      setStatus('Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.');
-    }
+    setStatus(INITIAL_MESSAGE);
   };
 
   ws.onmessage = function(message) {
+    if (!message.data) {
+      return;
+    }
     var object = JSON.parse(message.data);
+
+    if (object.$event === 'client-disconnected') {
+      shutdownJSRuntime();
+      setStatus('Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.');
+      return;
+    }
+
     if (!object.method) {
       return;
     }
 
-    var handler = messageHandlers[object.method];
-    if (handler) {
-      handler(object);
+    // Special message that asks for a new JS runtime
+    if (object.method === 'prepareJSRuntime') {
+      shutdownJSRuntime();
+      createJSRuntime();
+      ws.send(JSON.stringify({replyID: object.id}));
+      setStatus('Debugger session #' + object.id + ' active.');
+    } else if (object.method === '$disconnected') {
+      shutdownJSRuntime();
+      setStatus(INITIAL_MESSAGE);
     } else {
-      console.warn('Unknown method: ' + object.method);
+      // Otherwise, pass through to the worker.
+      worker.postMessage(object);
     }
   };
 
-  ws.onclose = function() {
+  ws.onclose = function(e) {
+    shutdownJSRuntime();
     setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?');
-
-    sessionID = null;
-    window.localStorage.removeItem('sessionID');
-    debuggerSetTimeout(connectToDebuggerProxy, 100);
+    if (e.reason) {
+      setStatus(e.reason);
+      console.warn(e.reason);
+    }
+    setTimeout(connectToDebuggerProxy, 500);
   };
-
-  worker.onmessage = function(message) {
-    ws.send(JSON.stringify(message.data));
-  }
 }
 
 connectToDebuggerProxy();
@@ -127,46 +116,12 @@
     border-radius: 4px;
     letter-spacing: 3px;
   }
-  #devtools-banner {
-    display: none;
-    background-color: #FDFDD5;
-    padding: 10px;
-  }
-  #devtools-banner h3 {
-    margin: 0;
-    font-weight: normal;
-  }
-  #devtools-banner a {
-    display: none;
-    padding: 10px 20px 10px 20px;
-    margin-bottom: 10px;
-    color: white;
-    text-decoration: none;
-    font-size: 11px;
-    text-shadow: 0 1px 1px rgba(0,0,0,0.1);
-    text-transform: uppercase;
-    font-weight: bold;
-    background-color: #4d7bd6;
-    border-radius: 2px;
-    border: 1px solid #2d53af;
-    display: inline-block;
-  }
   .content {
     padding: 10px;
   }
 </style>
 </head>
 <body>
-  <div id="devtools-banner">
-    <h3>Install React DevTools</h3>
-    <p>
-       React Developer Tools is an extension that allows you to inspect the
-       React component hierarchies in the Chrome Developer Tools.
-    </p>
-    <a href="https://fb.me/react-devtools" target="_blank">
-      Install
-    </a>
-  </div>
   <div class="content">
     <p>
       React Native JS code runs inside this Chrome tab.
diff --git a/local-cli/server/util/debuggerWorker.js b/local-cli/server/util/debuggerWorker.js
index 94f3b7d58623..fc72c5713c16 100644
--- a/local-cli/server/util/debuggerWorker.js
+++ b/local-cli/server/util/debuggerWorker.js
@@ -6,7 +6,7 @@
  * 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.
  */
-/* global self, importScripts, postMessage, onmessage: true */
+/* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */
 /* eslint no-unused-vars: 0 */
 'use strict';
 
@@ -17,16 +17,6 @@ var messageHandlers = {
     }
     importScripts(message.url);
     sendReply();
-  },
-  'executeJSCall': function(message, sendReply) {
-    var returnValue = [[], [], [], [], []];
-    try {
-      if (typeof require === 'function') {
-        returnValue = require(message.moduleName)[message.moduleMethod].apply(null, message.arguments);
-      }
-    } finally {
-      sendReply(JSON.stringify(returnValue));
-    }
   }
 };
 
@@ -39,8 +29,17 @@ onmessage = function(message) {
 
   var handler = messageHandlers[object.method];
   if (handler) {
+    // Special cased handlers
     handler(object, sendReply);
   } else {
-    console.warn('Unknown method: ' + object.method);
+    // Other methods get called on the bridge
+    var returnValue = [[], [], [], [], []];
+    try {
+      if (typeof __fbBatchedBridge === 'object') {
+        returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments);
+      }
+    } finally {
+      sendReply(JSON.stringify(returnValue));
+    }
   }
 };
diff --git a/local-cli/server/util/launchEditor.js b/local-cli/server/util/launchEditor.js
index a456eda59d26..ee8445294132 100644
--- a/local-cli/server/util/launchEditor.js
+++ b/local-cli/server/util/launchEditor.js
@@ -125,7 +125,13 @@ function launchEditor(fileName, lineNumber) {
     _childProcess.kill('SIGKILL');
   }
 
-  _childProcess = child_process.spawn(editor, args, {stdio: 'inherit'});
+  if (process.platform === 'win32') {
+    // On Windows, launch the editor in a shell because spawn can only
+    // launch .exe files.
+    _childProcess = child_process.spawn('cmd.exe', ['/C', editor].concat(args), {stdio: 'inherit'});
+  } else {
+    _childProcess = child_process.spawn(editor, args, {stdio: 'inherit'});
+  }
   _childProcess.on('exit', function(errorCode) {
     _childProcess = null;
 
diff --git a/local-cli/server/util/messageSocket.js b/local-cli/server/util/messageSocket.js
new file mode 100644
index 000000000000..3c56aa0544cd
--- /dev/null
+++ b/local-cli/server/util/messageSocket.js
@@ -0,0 +1,88 @@
+/**
+ * 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.
+ */
+'use strict';
+
+
+function attachToServer(server, path) {
+  var WebSocketServer = require('ws').Server;
+  var wss = new WebSocketServer({
+    server: server,
+    path: path
+  });
+  var interfaceSocket, shellSocket;
+
+  function send(dest, message) {
+    if (!dest) {
+      return;
+    }
+
+    try {
+      dest.send(message);
+    } catch(e) {
+      console.warn(e);
+      // Sometimes this call throws 'not opened'
+    }
+  }
+
+  wss.on('connection', function(ws) {
+    const {url} = ws.upgradeReq;
+
+    if (url.indexOf('role=interface') > -1) {
+      if (interfaceSocket) {
+        ws.close(1011, 'Another debugger is already connected');
+        return;
+      }
+      interfaceSocket = ws;
+      interfaceSocket.onerror =
+      interfaceSocket.onclose = () => {
+        interfaceSocket = null;
+        // if (shellSocket) {
+        //   shellSocket.close(1011, 'Interface was disconnected');
+        // }
+      };
+
+      interfaceSocket.onmessage = ({data}) => {
+        send(shellSocket, data)
+      };
+    } else if (url.indexOf('role=shell') > -1) {
+      if (shellSocket) {
+        shellSocket.onerror = shellSocket.onclose = shellSocket.onmessage = null;
+        shellSocket.close(1011, 'Another client connected');
+      }
+      shellSocket = ws;
+      shellSocket.onerror =
+      shellSocket.onclose = () => {
+        shellSocket = null;
+        send(interfaceSocket, JSON.stringify({method: '$disconnected'}));
+      };
+      shellSocket.onmessage = ({data}) => send(interfaceSocket, data);
+
+      // console.log('CLIENT ----');
+      // if (doIt) {
+      //   console.log('> sending: %s', str);
+      //   send(shellSocket, str);
+      //   console.log('< sending');
+      // }
+
+    } else {
+      ws.close(1011, 'Missing role param');
+    }
+  });
+
+  return {
+    server: wss,
+    isChromeConnected: function() {
+      return !!interfaceSocket;
+    }
+  };
+}
+
+module.exports = {
+  attachToServer: attachToServer
+};
diff --git a/local-cli/server/util/webSocketProxy.js b/local-cli/server/util/webSocketProxy.js
index 66eaefb15f16..74c01f8ef179 100644
--- a/local-cli/server/util/webSocketProxy.js
+++ b/local-cli/server/util/webSocketProxy.js
@@ -15,55 +15,59 @@ function attachToServer(server, path) {
     server: server,
     path: path
   });
-  var clients = [];
+  var debuggerSocket, clientSocket;
 
-  function sendSpecial(message) {
-    clients.forEach(function (cn) {
-      try {
-        cn.send(JSON.stringify(message));
-      } catch(e) {
-        // Sometimes this call throws 'not opened'
-      }
-    });
+  function send(dest, message) {
+    if (!dest) {
+      return;
+    }
+
+    try {
+      dest.send(message);
+    } catch(e) {
+      console.warn(e);
+      // Sometimes this call throws 'not opened'
+    }
   }
 
   wss.on('connection', function(ws) {
-    var id = Math.random().toString(15).slice(10, 20);
-    sendSpecial({$open: id});
-    clients.push(ws);
-
-    var allClientsExcept = function(ws) {
-      return clients.filter(function(cn) { return cn !== ws; });
-    };
+    const {url} = ws.upgradeReq;
 
-    ws.onerror = function() {
-      clients = allClientsExcept(ws);
-      sendSpecial({$error: id});
-    };
-
-    ws.onclose = function() {
-      clients = allClientsExcept(ws);
-      sendSpecial({$close: id});
-    };
-
-    ws.on('message', function(message) {
-      allClientsExcept(ws).forEach(function(cn) {
-        try {
-          cn.send(message);
-        } catch(e) {
-          // Sometimes this call throws 'not opened'
+    if (url.indexOf('role=debugger') > -1) {
+      if (debuggerSocket) {
+        ws.close(1011, 'Another debugger is already connected');
+        return;
+      }
+      debuggerSocket = ws;
+      debuggerSocket.onerror =
+      debuggerSocket.onclose = () => {
+        debuggerSocket = null;
+        if (clientSocket) {
+          clientSocket.close(1011, 'Debugger was disconnected');
         }
-      });
-    });
+      };
+      debuggerSocket.onmessage = ({data}) => send(clientSocket, data);
+    } else if (url.indexOf('role=client') > -1) {
+      if (clientSocket) {
+        clientSocket.onerror = clientSocket.onclose = clientSocket.onmessage = null;
+        clientSocket.close(1011, 'Another client connected');
+      }
+      clientSocket = ws;
+      clientSocket.onerror =
+      clientSocket.onclose = () => {
+        clientSocket = null;
+        send(debuggerSocket, JSON.stringify({method: '$disconnected'}));
+      };
+      clientSocket.onmessage = ({data}) => send(debuggerSocket, data);
+    } else {
+      ws.close(1011, 'Missing role param');
+    }
   });
 
   return {
     server: wss,
     isChromeConnected: function() {
-      return clients
-        .map(function(ws) { return ws.upgradeReq.headers['user-agent']; })
-        .filter(Boolean)
-        .some(function(userAgent) { return userAgent.includes('Chrome'); });
+      return !!debuggerSocket;
     }
   };
 }
diff --git a/local-cli/setup_env.sh b/local-cli/setup_env.sh
index 9bf18b5b4597..983cc8d6f084 100755
--- a/local-cli/setup_env.sh
+++ b/local-cli/setup_env.sh
@@ -5,4 +5,5 @@
 # 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.
 
-ulimit -n 4096
+# 2048 is the max for non root users on Mac
+ulimit -n 2048
diff --git a/local-cli/upgrade/upgrade.js b/local-cli/upgrade/upgrade.js
index daecd0ba5240..4ce704984532 100644
--- a/local-cli/upgrade/upgrade.js
+++ b/local-cli/upgrade/upgrade.js
@@ -9,16 +9,80 @@
 'use strict';
 
 const fs = require('fs');
+const chalk = require('chalk');
 const path = require('path');
 const Promise = require('promise');
 const yeoman = require('yeoman-environment');
+const semver = require('semver');
 
 module.exports = function upgrade(args, config) {
   args = args || process.argv;
-  let env = yeoman.createEnv();
-  let name = JSON.parse(fs.readFileSync('package.json', 'utf8')).name;
-  let generatorPath = path.join(__dirname, '..', 'generator');
+  const env = yeoman.createEnv();
+  const pak = JSON.parse(fs.readFileSync('package.json', 'utf8'));
+  const version = pak.dependencies['react-native'];
+
+  if (version) {
+    if (version === 'latest' || version === '*') {
+      console.warn(
+        chalk.yellow(
+          'Major releases are most likely to introduce breaking changes.\n' +
+          'Use a proper version number in your \'package.json\' file to avoid breakage.\n' +
+          'e.g. - ^0.18.0'
+        )
+      );
+    } else {
+      const installed = JSON.parse(fs.readFileSync('node_modules/react-native/package.json', 'utf8'));
+
+      if (semver.satisfies(installed.version, version)) {
+        const v = version.replace(/^(~|\^|=)/, '').replace(/x/i, '0');
+
+        if (semver.valid(v)) {
+          console.log(
+            'Upgrading project to react-native v' + installed.version + '\n' +
+            'Be sure to read the release notes and breaking changes:\n' +
+            chalk.blue(
+              'https://github.com/facebook/react-native/releases/tag/v' + semver.major(v) + '.' + semver.minor(v) + '.0'
+            )
+          );
+
+          // >= v0.21.0, we require react to be a peer depdendency
+          if (semver.gte(v, '0.21.0') && !pak.dependencies['react']) {
+            console.log(
+              chalk.yellow(
+                '\nYour \'package.json\' file doesn\'t seem to have \'react\' as a dependency.\n' +
+                '\'react\' was changed from a dependency to a peer dependency in react-native v0.21.0.\n' +
+                'Therefore, it\'s necessary to include \'react\' in your project\'s dependencies.\n' +
+                'Just run \'npm install --save react\', then re-run \'react-native upgrade\'.\n'
+              )
+            );
+            return Promise.resolve();
+          }
+        } else {
+          console.log(
+            chalk.yellow(
+              'A valid version number for \'react-native\' is not specified your \'package.json\' file.'
+            )
+          );
+        }
+      } else {
+        console.warn(
+          chalk.yellow(
+            'react-native version in \'package.json\' doesn\'t match the installed version in \'node_modules\'.\n' +
+            'Try running \'npm install\' to fix the issue.'
+          )
+        );
+      }
+    }
+  } else {
+    console.warn(
+      chalk.yellow(
+        'Your \'package.json\' file doesn\'t seem to have \'react-native\' as a dependency.'
+      )
+    );
+  }
+
+  const generatorPath = path.join(__dirname, '..', 'generator');
   env.register(generatorPath, 'react:app');
-  let generatorArgs = ['react:app', name].concat(args);
+  const generatorArgs = ['react:app', pak.name].concat(args);
   return new Promise((resolve) => env.run(generatorArgs, {upgrade: true}, resolve));
 };
diff --git a/local-cli/wrong-react-native.js b/local-cli/wrong-react-native.js
index bbc0a02919cb..44759e4b2bb7 100755
--- a/local-cli/wrong-react-native.js
+++ b/local-cli/wrong-react-native.js
@@ -15,3 +15,5 @@ console.error([
   'npm uninstall -g react-native',
   'npm install -g react-native-cli'
 ].join('\n'));
+
+process.exit(1);
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index ae8e80071546..402033f54913 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -1,3480 +1,3144 @@
-
 {
   "name": "react-native",
-  "version": "0.12.0",
+  "version": "0.0.0-master",
   "dependencies": {
     "absolute-path": {
       "version": "0.0.0",
-      "from": "absolute-path@0.0.0",
+      "from": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz",
       "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz"
     },
     "acorn": {
       "version": "1.2.2",
-      "from": "acorn@>=1.0.3 <2.0.0",
+      "from": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz"
     },
     "acorn-to-esprima": {
       "version": "1.0.5",
-      "from": "acorn-to-esprima@>=1.0.5 <2.0.0",
+      "from": "https://registry.npmjs.org/acorn-to-esprima/-/acorn-to-esprima-1.0.5.tgz",
       "resolved": "https://registry.npmjs.org/acorn-to-esprima/-/acorn-to-esprima-1.0.5.tgz"
     },
     "align-text": {
       "version": "0.1.3",
-      "from": "align-text@>=0.1.0 <0.2.0",
+      "from": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz",
       "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz"
     },
     "alter": {
       "version": "0.2.0",
-      "from": "alter@>=0.2.0 <0.3.0",
+      "from": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz",
       "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz"
     },
     "amdefine": {
       "version": "1.0.0",
-      "from": "amdefine@>=0.0.4",
+      "from": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
     },
     "ansi-green": {
       "version": "0.1.1",
-      "from": "ansi-green@>=0.1.1 <0.2.0",
+      "from": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz",
       "resolved": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz"
     },
     "ansi-regex": {
       "version": "2.0.0",
-      "from": "ansi-regex@>=2.0.0 <3.0.0",
+      "from": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
     },
     "ansi-styles": {
       "version": "2.1.0",
-      "from": "ansi-styles@>=2.1.0 <3.0.0",
+      "from": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
     },
     "ansi-wrap": {
       "version": "0.1.0",
-      "from": "ansi-wrap@0.1.0",
+      "from": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
       "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz"
     },
     "anymatch": {
       "version": "1.3.0",
-      "from": "anymatch@>=1.1.0 <2.0.0",
+      "from": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz",
       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz"
     },
     "arr-diff": {
       "version": "1.1.0",
-      "from": "arr-diff@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz",
       "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz"
     },
     "arr-flatten": {
       "version": "1.0.1",
-      "from": "arr-flatten@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz"
     },
     "array-differ": {
       "version": "1.0.0",
-      "from": "array-differ@>=1.0.0 <2.0.0",
+      "from": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz"
     },
     "array-slice": {
       "version": "0.2.3",
-      "from": "array-slice@>=0.2.3 <0.3.0",
+      "from": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
       "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz"
     },
     "array-uniq": {
       "version": "1.0.2",
-      "from": "array-uniq@>=1.0.2 <2.0.0",
+      "from": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
     },
     "array-unique": {
       "version": "0.2.1",
-      "from": "array-unique@>=0.2.1 <0.3.0",
+      "from": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz"
     },
     "arrify": {
       "version": "1.0.0",
-      "from": "arrify@>=1.0.0 <2.0.0",
+      "from": "https://registry.npmjs.org/arrify/-/arrify-1.0.0.tgz",
       "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.0.tgz"
     },
     "art": {
       "version": "0.10.0",
-      "from": "art@0.10.0",
+      "from": "https://registry.npmjs.org/art/-/art-0.10.0.tgz",
       "resolved": "https://registry.npmjs.org/art/-/art-0.10.0.tgz"
     },
     "ast-traverse": {
       "version": "0.1.1",
-      "from": "ast-traverse@>=0.1.1 <0.2.0",
+      "from": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz",
       "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz"
     },
-    "ast-types": {
-      "version": "0.8.5",
-      "from": "ast-types@0.8.5",
-      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.5.tgz"
-    },
     "async-each": {
       "version": "0.1.6",
-      "from": "async-each@>=0.1.5 <0.2.0",
+      "from": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz",
       "resolved": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz"
     },
     "babel": {
       "version": "5.8.29",
-      "from": "babel@>=5.4.7 <6.0.0",
+      "from": "https://registry.npmjs.org/babel/-/babel-5.8.29.tgz",
       "resolved": "https://registry.npmjs.org/babel/-/babel-5.8.29.tgz",
       "dependencies": {
-        "ast-types": {
-          "version": "0.8.12",
-          "from": "ast-types@0.8.12",
-          "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
-        },
         "babel-core": {
           "version": "5.8.33",
-          "from": "babel-core@>=5.6.21 <6.0.0",
+          "from": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.33.tgz",
           "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.33.tgz"
         },
+        "source-map": {
+          "version": "0.5.3",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
+        },
         "babylon": {
           "version": "5.8.29",
-          "from": "babylon@>=5.8.29 <6.0.0",
+          "from": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz",
           "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz"
         },
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.0.0 <2.0.0"
-        },
-        "debug": {
-          "version": "2.2.0",
-          "from": "debug@>=2.1.1 <3.0.0"
+        "ast-types": {
+          "version": "0.8.12",
+          "from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz",
+          "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
         },
         "globals": {
           "version": "6.4.1",
-          "from": "globals@>=6.4.0 <7.0.0",
+          "from": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz",
           "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz"
         },
         "js-tokens": {
           "version": "1.0.1",
-          "from": "js-tokens@1.0.1",
+          "from": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz",
           "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz"
         },
         "recast": {
           "version": "0.10.33",
-          "from": "recast@0.10.33",
+          "from": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz",
           "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz"
         },
         "regenerator": {
           "version": "0.8.40",
-          "from": "regenerator@0.8.40",
+          "from": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
           "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz"
-        },
-        "source-map": {
-          "version": "0.5.3",
-          "from": "source-map@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
-        }
-      }
-    },
-    "babel-code-frame": {
-      "version": "6.0.15",
-      "from": "babel-code-frame@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.0.15.tgz",
-      "dependencies": {
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.1.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
         }
       }
     },
     "babel-core": {
-      "version": "6.1.20",
-      "from": "babel-core@6.1.20",
-      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.1.20.tgz",
+      "version": "6.4.5",
+      "from": "https://registry.npmjs.org/babel-core/-/babel-core-6.4.5.tgz",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.4.5.tgz",
       "dependencies": {
         "babel-code-frame": {
-          "version": "6.1.18",
-          "from": "babel-code-frame@>=6.1.18 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.1.18.tgz"
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
         },
-        "babel-messages": {
-          "version": "6.1.18",
-          "from": "babel-messages@>=6.1.18 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.1.18.tgz"
-        },
-        "babel-template": {
-          "version": "6.1.18",
-          "from": "babel-template@>=6.1.18 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.1.18.tgz"
+        "babel-generator": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.4.5.tgz"
         },
-        "babel-traverse": {
-          "version": "6.1.20",
-          "from": "babel-traverse@>=6.1.20 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.1.20.tgz"
+        "babel-helpers": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.4.5.tgz"
         },
-        "babel-types": {
-          "version": "6.1.18",
-          "from": "babel-types@>=6.1.18 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.1.18.tgz"
+        "babel-messages": {
+          "version": "6.3.18",
+          "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
         },
-        "babylon": {
-          "version": "6.1.21",
-          "from": "babylon@>=6.1.20 <7.0.0",
-          "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.1.21.tgz"
+        "babel-template": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz"
         },
-        "esprima-fb": {
-          "version": "15001.1.0-dev-harmony-fb",
-          "from": "esprima-fb@>=15001.1.0-dev-harmony-fb <15001.2.0",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz"
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         },
-        "recast": {
-          "version": "0.10.24",
-          "from": "recast@0.10.24",
-          "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.24.tgz",
+        "babel-traverse": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
           "dependencies": {
-            "source-map": {
-              "version": "0.4.4",
-              "from": "source-map@>=0.4.1 <0.5.0",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+            "globals": {
+              "version": "8.18.0",
+              "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+              "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+            },
+            "invariant": {
+              "version": "2.2.0",
+              "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
             }
           }
         },
-        "regenerator": {
-          "version": "0.8.35",
-          "from": "regenerator@0.8.35",
-          "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.35.tgz"
-        },
         "source-map": {
           "version": "0.5.3",
-          "from": "source-map@>=0.5.0 <0.6.0",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
         }
       }
     },
     "babel-eslint": {
       "version": "4.1.4",
-      "from": "babel-eslint@*",
+      "from": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-4.1.4.tgz",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-4.1.4.tgz",
       "dependencies": {
+        "babel-core": {
+          "version": "5.8.33",
+          "from": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.33.tgz",
+          "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.33.tgz"
+        },
         "ast-types": {
           "version": "0.8.12",
-          "from": "ast-types@0.8.12",
+          "from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz",
           "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
         },
-        "babel-core": {
-          "version": "5.8.33",
-          "from": "babel-core@>=5.8.33 <6.0.0",
-          "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.33.tgz",
-          "dependencies": {
-            "chalk": {
-              "version": "1.1.1",
-              "from": "chalk@>=1.0.0 <2.0.0"
-            },
-            "debug": {
-              "version": "2.2.0",
-              "from": "debug@>=2.1.1 <3.0.0"
-            }
-          }
-        },
         "babylon": {
           "version": "5.8.29",
-          "from": "babylon@>=5.8.29 <6.0.0",
+          "from": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz",
           "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz"
         },
-        "esprima-fb": {
-          "version": "15001.1001.0-dev-harmony-fb",
-          "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
-        },
         "globals": {
           "version": "6.4.1",
-          "from": "globals@>=6.4.0 <7.0.0",
+          "from": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz",
           "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz"
         },
         "js-tokens": {
           "version": "1.0.1",
-          "from": "js-tokens@1.0.1",
+          "from": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz",
           "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz"
         },
         "recast": {
           "version": "0.10.33",
-          "from": "recast@0.10.33",
+          "from": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz",
           "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.33.tgz"
         },
         "regenerator": {
           "version": "0.8.40",
-          "from": "regenerator@0.8.40",
+          "from": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
           "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz"
         },
         "source-map": {
           "version": "0.5.3",
-          "from": "source-map@>=0.5.0 <0.6.0",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
         }
       }
     },
-    "babel-generator": {
-      "version": "6.1.20",
-      "from": "babel-generator@>=6.1.20 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.1.20.tgz",
-      "dependencies": {
-        "babel-code-frame": {
-          "version": "6.1.18",
-          "from": "babel-code-frame@>=6.1.18 <7.0.0"
-        },
-        "babel-messages": {
-          "version": "6.1.18",
-          "from": "babel-messages@>=6.1.18 <7.0.0"
-        },
-        "babel-traverse": {
-          "version": "6.1.20",
-          "from": "babel-traverse@>=6.1.18 <7.0.0"
-        },
-        "babel-types": {
-          "version": "6.1.18",
-          "from": "babel-types@>=6.1.18 <7.0.0"
-        },
-        "babylon": {
-          "version": "6.1.21",
-          "from": "babylon@>=6.1.20 <7.0.0"
-        },
-        "source-map": {
-          "version": "0.5.3",
-          "from": "source-map@>=0.5.0 <0.6.0"
-        }
-      }
-    },
-    "babel-helper-builder-react-jsx": {
-      "version": "6.0.15",
-      "from": "babel-helper-builder-react-jsx@>=6.0.14 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.0.15.tgz"
-    },
-    "babel-helper-call-delegate": {
-      "version": "6.0.15",
-      "from": "babel-helper-call-delegate@>=6.0.14 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.0.15.tgz"
-    },
-    "babel-helper-define-map": {
-      "version": "6.0.15",
-      "from": "babel-helper-define-map@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.0.15.tgz"
-    },
-    "babel-helper-function-name": {
-      "version": "6.0.15",
-      "from": "babel-helper-function-name@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.0.15.tgz"
-    },
-    "babel-helper-get-function-arity": {
-      "version": "6.0.15",
-      "from": "babel-helper-get-function-arity@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.0.15.tgz"
-    },
-    "babel-helper-hoist-variables": {
-      "version": "6.0.15",
-      "from": "babel-helper-hoist-variables@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.0.15.tgz"
-    },
-    "babel-helper-optimise-call-expression": {
-      "version": "6.0.15",
-      "from": "babel-helper-optimise-call-expression@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.0.15.tgz"
-    },
-    "babel-helper-replace-supers": {
-      "version": "6.0.18",
-      "from": "babel-helper-replace-supers@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.0.18.tgz"
-    },
-    "babel-helpers": {
-      "version": "6.1.20",
-      "from": "babel-helpers@>=6.1.20 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.1.20.tgz",
-      "dependencies": {
-        "babel-code-frame": {
-          "version": "6.1.18",
-          "from": "babel-code-frame@>=6.1.18 <7.0.0"
-        },
-        "babel-messages": {
-          "version": "6.1.18",
-          "from": "babel-messages@>=6.1.18 <7.0.0"
-        },
-        "babel-template": {
-          "version": "6.1.18",
-          "from": "babel-template@>=6.1.18 <7.0.0"
-        },
-        "babel-traverse": {
-          "version": "6.1.20",
-          "from": "babel-traverse@>=6.1.18 <7.0.0"
-        },
-        "babel-types": {
-          "version": "6.1.18",
-          "from": "babel-types@>=6.1.18 <7.0.0"
-        },
-        "babylon": {
-          "version": "6.1.21",
-          "from": "babylon@>=6.1.18 <7.0.0"
-        }
-      }
-    },
-    "babel-messages": {
-      "version": "6.0.15",
-      "from": "babel-messages@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.0.15.tgz"
-    },
     "babel-plugin-constant-folding": {
       "version": "1.0.1",
-      "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz"
     },
     "babel-plugin-dead-code-elimination": {
       "version": "1.0.2",
-      "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz"
     },
     "babel-plugin-eval": {
       "version": "1.0.1",
-      "from": "babel-plugin-eval@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz"
     },
-    "babel-plugin-external-helpers-2": {
-      "version": "6.1.4",
-      "from": "babel-plugin-external-helpers-2@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-external-helpers-2/-/babel-plugin-external-helpers-2-6.1.4.tgz"
+    "babel-plugin-external-helpers": {
+      "version": "6.4.0",
+      "from": "https://registry.npmjs.org/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.4.0.tgz",
+      "resolved": "https://registry.npmjs.org/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.4.0.tgz",
+      "dependencies": {
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+        }
+      }
     },
     "babel-plugin-inline-environment-variables": {
       "version": "1.0.1",
-      "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz"
     },
     "babel-plugin-jscript": {
       "version": "1.0.4",
-      "from": "babel-plugin-jscript@>=1.0.4 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz"
     },
     "babel-plugin-member-expression-literals": {
       "version": "1.0.1",
-      "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz"
     },
     "babel-plugin-property-literals": {
       "version": "1.0.1",
-      "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz"
     },
     "babel-plugin-proto-to-assign": {
       "version": "1.0.4",
-      "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz"
     },
     "babel-plugin-react-constant-elements": {
       "version": "1.0.3",
-      "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz"
     },
     "babel-plugin-react-display-name": {
       "version": "1.0.3",
-      "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz"
     },
     "babel-plugin-remove-console": {
       "version": "1.0.1",
-      "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz"
     },
     "babel-plugin-remove-debugger": {
       "version": "1.0.1",
-      "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz"
     },
     "babel-plugin-runtime": {
       "version": "1.0.7",
-      "from": "babel-plugin-runtime@>=1.0.7 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz"
     },
-    "babel-plugin-syntax-async-functions": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-async-functions@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.0.14.tgz"
-    },
-    "babel-plugin-syntax-class-properties": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-class-properties@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.0.14.tgz"
-    },
-    "babel-plugin-syntax-flow": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-flow@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.0.14.tgz"
-    },
-    "babel-plugin-syntax-jsx": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-jsx@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.0.14.tgz"
-    },
-    "babel-plugin-syntax-object-rest-spread": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-object-rest-spread@>=6.0.14 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.0.14.tgz"
-    },
-    "babel-plugin-syntax-trailing-function-commas": {
-      "version": "6.0.14",
-      "from": "babel-plugin-syntax-trailing-function-commas@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.0.14.tgz"
-    },
-    "babel-plugin-transform-class-properties": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-class-properties@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-arrow-functions": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-arrow-functions@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-block-scoping": {
-      "version": "6.0.18",
-      "from": "babel-plugin-transform-es2015-block-scoping@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.0.18.tgz"
-    },
-    "babel-plugin-transform-es2015-classes": {
-      "version": "6.1.2",
-      "from": "babel-plugin-transform-es2015-classes@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.1.2.tgz"
-    },
-    "babel-plugin-transform-es2015-computed-properties": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-computed-properties@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-constants": {
-      "version": "6.0.15",
-      "from": "babel-plugin-transform-es2015-constants@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-constants/-/babel-plugin-transform-es2015-constants-6.0.15.tgz"
-    },
-    "babel-plugin-transform-es2015-destructuring": {
-      "version": "6.0.18",
-      "from": "babel-plugin-transform-es2015-destructuring@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.0.18.tgz"
-    },
-    "babel-plugin-transform-es2015-for-of": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-for-of@>=6.0.14 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-modules-commonjs": {
-      "version": "6.1.3",
-      "from": "babel-plugin-transform-es2015-modules-commonjs@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.1.3.tgz"
-    },
-    "babel-plugin-transform-es2015-parameters": {
-      "version": "6.0.18",
-      "from": "babel-plugin-transform-es2015-parameters@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.0.18.tgz"
-    },
-    "babel-plugin-transform-es2015-shorthand-properties": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-shorthand-properties@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-spread": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-spread@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.0.14.tgz"
-    },
-    "babel-plugin-transform-es2015-template-literals": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-es2015-template-literals@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.0.14.tgz"
-    },
-    "babel-plugin-transform-flow-strip-types": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-flow-strip-types@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.0.14.tgz"
-    },
-    "babel-plugin-transform-object-assign": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-object-assign@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.0.14.tgz"
-    },
-    "babel-plugin-transform-object-rest-spread": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-object-rest-spread@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.0.14.tgz"
-    },
-    "babel-plugin-transform-react-display-name": {
-      "version": "6.0.14",
-      "from": "babel-plugin-transform-react-display-name@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.0.14.tgz"
-    },
-    "babel-plugin-transform-react-jsx": {
-      "version": "6.0.18",
-      "from": "babel-plugin-transform-react-jsx@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.0.18.tgz"
-    },
-    "babel-plugin-transform-regenerator": {
-      "version": "6.0.18",
-      "from": "babel-plugin-transform-regenerator@*",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.0.18.tgz"
-    },
-    "babel-plugin-transform-strict-mode": {
-      "version": "6.1.2",
-      "from": "babel-plugin-transform-strict-mode@>=6.0.15 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.1.2.tgz"
-    },
     "babel-plugin-undeclared-variables-check": {
       "version": "1.0.2",
-      "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz"
     },
     "babel-plugin-undefined-to-void": {
       "version": "1.1.6",
-      "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0",
+      "from": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz",
       "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz"
     },
     "babel-polyfill": {
-      "version": "6.0.16",
-      "from": "babel-polyfill@*",
-      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.0.16.tgz"
-    },
-    "babel-register": {
-      "version": "6.1.18",
-      "from": "babel-register@>=6.1.18 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.1.18.tgz"
-    },
-    "babel-runtime": {
-      "version": "5.8.29",
-      "from": "babel-runtime@>=5.0.0 <6.0.0",
-      "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz"
-    },
-    "babel-template": {
-      "version": "6.0.16",
-      "from": "babel-template@>=6.0.16 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.0.16.tgz"
-    },
-    "babel-traverse": {
-      "version": "6.1.2",
-      "from": "babel-traverse@>=6.1.2 <7.0.0",
-      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.1.2.tgz"
-    },
-    "babel-types": {
-      "version": "6.1.2",
-      "from": "babel-types@*",
-      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.1.2.tgz"
-    },
-    "babylon": {
-      "version": "6.1.2",
-      "from": "babylon@*",
-      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.1.2.tgz"
-    },
-    "balanced-match": {
-      "version": "0.2.1",
-      "from": "balanced-match@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
-    },
-    "beeper": {
-      "version": "1.1.0",
-      "from": "beeper@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz"
-    },
-    "binary-extensions": {
-      "version": "1.3.1",
-      "from": "binary-extensions@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.3.1.tgz"
-    },
-    "bluebird": {
-      "version": "2.10.2",
-      "from": "bluebird@>=2.9.33 <3.0.0",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz"
-    },
-    "brace-expansion": {
-      "version": "1.1.1",
-      "from": "brace-expansion@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
-    },
-    "braces": {
-      "version": "1.8.2",
-      "from": "braces@>=1.8.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.2.tgz"
-    },
-    "breakable": {
-      "version": "1.0.0",
-      "from": "breakable@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz"
-    },
-    "bser": {
-      "version": "1.0.2",
-      "from": "bser@1.0.2",
-      "resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz",
-      "dependencies": {
-        "node-int64": {
-          "version": "0.4.0",
-          "from": "node-int64@>=0.4.0 <0.5.0",
-          "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
-        }
-      }
-    },
-    "builtin-modules": {
-      "version": "1.1.0",
-      "from": "builtin-modules@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
-    },
-    "camelcase": {
-      "version": "1.2.1",
-      "from": "camelcase@>=1.2.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-    },
-    "camelcase-keys": {
-      "version": "1.0.0",
-      "from": "camelcase-keys@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz"
-    },
-    "center-align": {
-      "version": "0.1.2",
-      "from": "center-align@>=0.1.1 <0.2.0",
-      "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.2.tgz"
-    },
-    "chalk": {
-      "version": "1.1.1",
-      "from": "chalk@>=1.1.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
-    },
-    "chokidar": {
-      "version": "1.2.0",
-      "from": "chokidar@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.2.0.tgz"
-    },
-    "cliui": {
-      "version": "2.1.0",
-      "from": "cliui@>=2.1.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
-      "dependencies": {
-        "wordwrap": {
-          "version": "0.0.2",
-          "from": "wordwrap@0.0.2",
-          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
-        }
-      }
-    },
-    "clone": {
-      "version": "1.0.2",
-      "from": "clone@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
-    },
-    "clone-stats": {
-      "version": "0.0.1",
-      "from": "clone-stats@>=0.0.1 <0.0.2",
-      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-    },
-    "commander": {
-      "version": "2.9.0",
-      "from": "commander@>=2.5.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
-    },
-    "commoner": {
-      "version": "0.10.4",
-      "from": "commoner@>=0.10.3 <0.11.0",
+      "version": "6.3.14",
+      "from": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz",
       "dependencies": {
-        "graceful-fs": {
-          "version": "4.1.2",
-          "from": "graceful-fs@>=4.1.2 <5.0.0"
+        "babel-regenerator-runtime": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz"
+        },
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         }
       }
     },
-    "concat-map": {
-      "version": "0.0.1",
-      "from": "concat-map@0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-    },
-    "connect": {
-      "version": "2.8.3",
-      "from": "connect@2.8.3",
-      "resolved": "https://registry.npmjs.org/connect/-/connect-2.8.3.tgz",
+    "babel-preset-react-native": {
+      "version": "1.2.4",
+      "from": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-1.2.4.tgz",
+      "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-1.2.4.tgz",
       "dependencies": {
-        "buffer-crc32": {
-          "version": "0.2.1",
-          "from": "buffer-crc32@0.2.1",
-          "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz"
-        },
-        "bytes": {
-          "version": "0.2.0",
-          "from": "bytes@0.2.0",
-          "resolved": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz"
-        },
-        "cookie": {
-          "version": "0.1.0",
-          "from": "cookie@0.1.0",
-          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz"
+        "babel-plugin-react-transform": {
+          "version": "2.0.0-beta1",
+          "from": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.0-beta1.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-react-transform/-/babel-plugin-react-transform-2.0.0-beta1.tgz",
+          "dependencies": {
+            "array-find": {
+              "version": "1.0.0",
+              "from": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz"
+            }
+          }
         },
-        "cookie-signature": {
-          "version": "1.0.1",
-          "from": "cookie-signature@1.0.1",
-          "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz"
+        "babel-plugin-syntax-async-functions": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "debug": {
-          "version": "2.2.0",
-          "from": "debug@*"
+        "babel-plugin-syntax-class-properties": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "formidable": {
-          "version": "1.0.14",
-          "from": "formidable@1.0.14",
-          "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
+        "babel-plugin-syntax-flow": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "fresh": {
-          "version": "0.1.0",
-          "from": "fresh@0.1.0",
-          "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz"
+        "babel-plugin-syntax-jsx": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "methods": {
-          "version": "0.0.1",
-          "from": "methods@0.0.1",
-          "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz"
+        "babel-plugin-syntax-trailing-function-commas": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "pause": {
-          "version": "0.0.1",
-          "from": "pause@0.0.1",
-          "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz"
+        "babel-plugin-transform-class-properties": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.4.0.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "qs": {
-          "version": "0.6.5",
-          "from": "qs@0.6.5",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz"
+        "babel-plugin-transform-es2015-arrow-functions": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.4.0.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "send": {
-          "version": "0.1.2",
-          "from": "send@0.1.2",
-          "resolved": "https://registry.npmjs.org/send/-/send-0.1.2.tgz",
+        "babel-plugin-transform-es2015-block-scoping": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.4.0.tgz",
           "dependencies": {
-            "mime": {
-              "version": "1.2.11",
-              "from": "mime@>=1.2.9 <1.3.0",
-              "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
+            "babel-traverse": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "dependencies": {
+                "babel-code-frame": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                },
+                "babel-messages": {
+                  "version": "6.3.18",
+                  "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                },
+                "globals": {
+                  "version": "8.18.0",
+                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                },
+                "invariant": {
+                  "version": "2.2.0",
+                  "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                }
+              }
             },
-            "range-parser": {
-              "version": "0.0.4",
-              "from": "range-parser@0.0.4",
-              "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz"
+            "babel-template": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz"
+            },
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "uid2": {
-          "version": "0.0.2",
-          "from": "uid2@0.0.2",
-          "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.2.tgz"
-        }
-      }
-    },
-    "convert-source-map": {
-      "version": "1.1.1",
-      "from": "convert-source-map@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.1.tgz"
-    },
-    "core-js": {
-      "version": "1.2.6",
-      "from": "core-js@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
-    },
-    "core-util-is": {
-      "version": "1.0.1",
-      "from": "core-util-is@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-    },
-    "dateformat": {
-      "version": "1.0.11",
-      "from": "dateformat@>=1.0.11 <2.0.0",
-      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.11.tgz"
-    },
-    "debug": {
-      "version": "2.2.0",
-      "from": "debug@>=2.2.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
-    },
-    "decamelize": {
-      "version": "1.1.1",
-      "from": "decamelize@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.1.1.tgz"
-    },
-    "defined": {
-      "version": "1.0.0",
-      "from": "defined@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz"
-    },
-    "defs": {
-      "version": "1.1.1",
-      "from": "defs@>=1.1.0 <1.2.0",
-      "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz",
-      "dependencies": {
-        "yargs": {
-          "version": "3.27.0",
-          "from": "yargs@>=3.27.0 <3.28.0",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz"
-        }
-      }
-    },
-    "detect-indent": {
-      "version": "3.0.1",
-      "from": "detect-indent@>=3.0.1 <4.0.0",
-      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz"
-    },
-    "detective": {
-      "version": "4.3.1",
-      "from": "detective@>=4.3.1 <5.0.0",
-      "resolved": "https://registry.npmjs.org/detective/-/detective-4.3.1.tgz"
-    },
-    "duplexer2": {
-      "version": "0.0.2",
-      "from": "duplexer2@0.0.2",
-      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
-      "dependencies": {
-        "readable-stream": {
-          "version": "1.1.13",
-          "from": "readable-stream@>=1.1.9 <1.2.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
-        }
-      }
-    },
-    "error-ex": {
-      "version": "1.3.0",
-      "from": "error-ex@>=1.2.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz"
-    },
-    "escape-string-regexp": {
-      "version": "1.0.3",
-      "from": "escape-string-regexp@>=1.0.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
-    },
-    "eslint": {
-      "version": "1.3.1",
-      "from": "eslint@1.3.1",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-1.3.1.tgz",
-      "dependencies": {
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.0.0 <2.0.0"
-        },
-        "concat-stream": {
-          "version": "1.5.0",
-          "from": "concat-stream@>=1.4.6 <2.0.0",
-          "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+        "babel-plugin-transform-es2015-classes": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.4.5.tgz",
           "dependencies": {
-            "inherits": {
-              "version": "2.0.1",
-              "from": "inherits@>=2.0.1 <2.1.0",
-              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+            "babel-helper-optimise-call-expression": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.3.13.tgz"
+            },
+            "babel-helper-function-name": {
+              "version": "6.4.0",
+              "from": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.4.0.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.4.0.tgz",
+              "dependencies": {
+                "babel-helper-get-function-arity": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz"
+                }
+              }
             },
-            "readable-stream": {
-              "version": "2.0.2",
-              "from": "readable-stream@>=2.0.0 <2.1.0",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+            "babel-helper-replace-supers": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.3.13.tgz"
+            },
+            "babel-template": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz"
+            },
+            "babel-traverse": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
               "dependencies": {
-                "core-util-is": {
-                  "version": "1.0.1",
-                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                },
-                "isarray": {
-                  "version": "0.0.1",
-                  "from": "isarray@0.0.1",
-                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                },
-                "process-nextick-args": {
-                  "version": "1.0.2",
-                  "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.2.tgz"
+                "babel-code-frame": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
                 },
-                "string_decoder": {
-                  "version": "0.10.31",
-                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                "globals": {
+                  "version": "8.18.0",
+                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
                 },
-                "util-deprecate": {
-                  "version": "1.0.1",
-                  "from": "util-deprecate@>=1.0.1 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz"
+                "invariant": {
+                  "version": "2.2.0",
+                  "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                 }
               }
             },
-            "typedarray": {
-              "version": "0.0.6",
-              "from": "typedarray@>=0.0.5 <0.1.0",
-              "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
-            }
-          }
-        },
-        "debug": {
-          "version": "2.2.0",
-          "from": "debug@>=2.1.1 <3.0.0"
-        },
-        "doctrine": {
-          "version": "0.6.4",
-          "from": "doctrine@>=0.6.2 <0.7.0",
-          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz",
-          "dependencies": {
-            "esutils": {
-              "version": "1.1.6",
-              "from": "esutils@>=1.1.6 <2.0.0",
-              "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
+            "babel-helper-define-map": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.4.5.tgz"
             },
-            "isarray": {
-              "version": "0.0.1",
-              "from": "isarray@0.0.1",
-              "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+            "babel-messages": {
+              "version": "6.3.18",
+              "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+              "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+            },
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "escape-string-regexp": {
-          "version": "1.0.3",
-          "from": "escape-string-regexp@>=1.0.2 <2.0.0",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
-        },
-        "escope": {
-          "version": "3.2.0",
-          "from": "escope@>=3.2.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/escope/-/escope-3.2.0.tgz",
+        "babel-plugin-transform-es2015-computed-properties": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.4.0.tgz",
           "dependencies": {
-            "es6-map": {
-              "version": "0.1.1",
-              "from": "es6-map@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.1.tgz",
+            "babel-helper-define-map": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.4.5.tgz",
               "dependencies": {
-                "d": {
-                  "version": "0.1.1",
-                  "from": "d@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
-                },
-                "es5-ext": {
-                  "version": "0.10.7",
-                  "from": "es5-ext@>=0.10.4 <0.11.0",
-                  "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz",
-                  "dependencies": {
-                    "es6-symbol": {
-                      "version": "2.0.1",
-                      "from": "es6-symbol@>=2.0.1 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
-                    }
-                  }
-                },
-                "es6-iterator": {
-                  "version": "0.1.3",
-                  "from": "es6-iterator@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
+                "babel-helper-function-name": {
+                  "version": "6.4.0",
+                  "from": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.4.0.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.4.0.tgz",
                   "dependencies": {
-                    "es6-symbol": {
-                      "version": "2.0.1",
-                      "from": "es6-symbol@>=2.0.1 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
+                    "babel-traverse": {
+                      "version": "6.4.5",
+                      "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                      "dependencies": {
+                        "babel-code-frame": {
+                          "version": "6.3.13",
+                          "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                        },
+                        "babel-messages": {
+                          "version": "6.3.18",
+                          "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                        },
+                        "globals": {
+                          "version": "8.18.0",
+                          "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                          "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                        },
+                        "invariant": {
+                          "version": "2.2.0",
+                          "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                        }
+                      }
+                    },
+                    "babel-helper-get-function-arity": {
+                      "version": "6.3.13",
+                      "from": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz"
                     }
                   }
-                },
-                "es6-set": {
-                  "version": "0.1.1",
-                  "from": "es6-set@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.1.tgz"
-                },
-                "es6-symbol": {
-                  "version": "0.1.1",
-                  "from": "es6-symbol@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-0.1.1.tgz"
-                },
-                "event-emitter": {
-                  "version": "0.3.3",
-                  "from": "event-emitter@>=0.3.1 <0.4.0",
-                  "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.3.tgz"
                 }
               }
             },
-            "es6-weak-map": {
-              "version": "0.1.4",
-              "from": "es6-weak-map@>=0.1.2 <0.2.0",
-              "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz",
+            "babel-template": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
               "dependencies": {
-                "d": {
-                  "version": "0.1.1",
-                  "from": "d@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
-                },
-                "es5-ext": {
-                  "version": "0.10.7",
-                  "from": "es5-ext@>=0.10.6 <0.11.0",
-                  "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz"
-                },
-                "es6-iterator": {
-                  "version": "0.1.3",
-                  "from": "es6-iterator@>=0.1.3 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz"
-                },
-                "es6-symbol": {
-                  "version": "2.0.1",
-                  "from": "es6-symbol@>=2.0.1 <2.1.0",
-                  "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
+                "babel-traverse": {
+                  "version": "6.4.5",
+                  "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                  "dependencies": {
+                    "babel-code-frame": {
+                      "version": "6.3.13",
+                      "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                    },
+                    "babel-messages": {
+                      "version": "6.3.18",
+                      "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                    },
+                    "globals": {
+                      "version": "8.18.0",
+                      "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                      "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                    },
+                    "invariant": {
+                      "version": "2.2.0",
+                      "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                    }
+                  }
                 }
               }
             },
-            "esrecurse": {
-              "version": "3.1.1",
-              "from": "esrecurse@>=3.1.1 <4.0.0",
-              "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-3.1.1.tgz"
-            },
-            "estraverse": {
-              "version": "3.1.0",
-              "from": "estraverse@>=3.1.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-3.1.0.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "espree": {
-          "version": "2.2.4",
-          "from": "espree@>=2.2.4 <3.0.0",
-          "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.4.tgz"
-        },
-        "estraverse": {
-          "version": "4.1.0",
-          "from": "estraverse@>=4.1.0 <5.0.0",
-          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.0.tgz"
+        "babel-plugin-transform-es2015-constants": {
+          "version": "6.1.4",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-constants/-/babel-plugin-transform-es2015-constants-6.1.4.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-constants/-/babel-plugin-transform-es2015-constants-6.1.4.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "estraverse-fb": {
-          "version": "1.3.1",
-          "from": "estraverse-fb@>=1.3.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.1.tgz"
+        "babel-plugin-transform-es2015-destructuring": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.4.0.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "globals": {
-          "version": "8.7.0",
-          "from": "globals@>=8.5.0 <9.0.0",
-          "resolved": "https://registry.npmjs.org/globals/-/globals-8.7.0.tgz"
+        "babel-plugin-transform-es2015-for-of": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.3.13.tgz",
+          "dependencies": {
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            }
+          }
         },
-        "handlebars": {
-          "version": "3.0.3",
-          "from": "handlebars@>=3.0.3 <4.0.0",
-          "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.3.tgz",
+        "babel-plugin-transform-es2015-modules-commonjs": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.4.5.tgz",
           "dependencies": {
-            "source-map": {
-              "version": "0.1.43",
-              "from": "source-map@>=0.1.40 <0.2.0",
-              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            },
+            "babel-template": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
               "dependencies": {
-                "amdefine": {
-                  "version": "1.0.0",
-                  "from": "amdefine@>=0.0.4",
-                  "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+                "babel-traverse": {
+                  "version": "6.4.5",
+                  "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+                  "dependencies": {
+                    "babel-code-frame": {
+                      "version": "6.3.13",
+                      "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                    },
+                    "babel-messages": {
+                      "version": "6.3.18",
+                      "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                    },
+                    "globals": {
+                      "version": "8.18.0",
+                      "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                      "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                    },
+                    "invariant": {
+                      "version": "2.2.0",
+                      "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                    }
+                  }
                 }
               }
             },
-            "uglify-js": {
-              "version": "2.3.6",
-              "from": "uglify-js@>=2.3.0 <2.4.0",
-              "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz",
-              "dependencies": {
-                "async": {
-                  "version": "0.2.10",
-                  "from": "async@>=0.2.6 <0.3.0",
-                  "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
-                },
-                "optimist": {
-                  "version": "0.3.7",
-                  "from": "optimist@>=0.3.5 <0.4.0",
-                  "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
-                  "dependencies": {
-                    "wordwrap": {
-                      "version": "0.0.3",
-                      "from": "wordwrap@>=0.0.2 <0.1.0",
-                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
-                    }
-                  }
-                }
-              }
+            "babel-plugin-transform-strict-mode": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.3.13.tgz"
             }
           }
         },
-        "inquirer": {
-          "version": "0.9.0",
-          "from": "inquirer@>=0.9.0 <0.10.0",
-          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz",
+        "babel-plugin-transform-es2015-parameters": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.4.5.tgz",
           "dependencies": {
-            "ansi-regex": {
-              "version": "2.0.0",
-              "from": "ansi-regex@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
-            },
-            "cli-width": {
-              "version": "1.0.1",
-              "from": "cli-width@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz"
-            },
-            "figures": {
-              "version": "1.3.5",
-              "from": "figures@>=1.3.5 <2.0.0",
-              "resolved": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz"
-            },
-            "lodash": {
-              "version": "3.10.1",
-              "from": "lodash@>=3.3.1 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-            },
-            "readline2": {
-              "version": "0.1.1",
-              "from": "readline2@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+            "babel-traverse": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
               "dependencies": {
-                "mute-stream": {
-                  "version": "0.0.4",
-                  "from": "mute-stream@0.0.4",
-                  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz"
-                },
-                "strip-ansi": {
-                  "version": "2.0.1",
-                  "from": "strip-ansi@>=2.0.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
-                  "dependencies": {
-                    "ansi-regex": {
-                      "version": "1.1.1",
-                      "from": "ansi-regex@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz"
-                    }
-                  }
+                "babel-code-frame": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                },
+                "babel-messages": {
+                  "version": "6.3.18",
+                  "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                },
+                "globals": {
+                  "version": "8.18.0",
+                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                },
+                "invariant": {
+                  "version": "2.2.0",
+                  "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                 }
               }
             },
-            "run-async": {
-              "version": "0.1.0",
-              "from": "run-async@>=0.1.0 <0.2.0",
-              "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+            "babel-helper-call-delegate": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.3.13.tgz",
               "dependencies": {
-                "once": {
-                  "version": "1.3.2",
-                  "from": "once@>=1.3.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
+                "babel-helper-hoist-variables": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.3.13.tgz"
                 }
               }
             },
-            "rx-lite": {
-              "version": "2.5.2",
-              "from": "rx-lite@>=2.5.2 <3.0.0",
-              "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-2.5.2.tgz"
+            "babel-helper-get-function-arity": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.3.13.tgz"
             },
-            "strip-ansi": {
-              "version": "3.0.0",
-              "from": "strip-ansi@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+            "babel-template": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.3.13.tgz"
             },
-            "through": {
-              "version": "2.3.8",
-              "from": "through@>=2.3.6 <3.0.0",
-              "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "is-my-json-valid": {
-          "version": "2.12.2",
-          "from": "is-my-json-valid@>=2.10.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz",
+        "babel-plugin-transform-es2015-shorthand-properties": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.3.13.tgz",
           "dependencies": {
-            "generate-function": {
-              "version": "2.0.0",
-              "from": "generate-function@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
-            },
-            "generate-object-property": {
-              "version": "1.2.0",
-              "from": "generate-object-property@>=1.1.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
-              "dependencies": {
-                "is-property": {
-                  "version": "1.0.2",
-                  "from": "is-property@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
-                }
-              }
-            },
-            "jsonpointer": {
-              "version": "2.0.0",
-              "from": "jsonpointer@2.0.0",
-              "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
-            },
-            "xtend": {
-              "version": "4.0.0",
-              "from": "xtend@>=4.0.0 <5.0.0",
-              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "is-resolvable": {
-          "version": "1.0.0",
-          "from": "is-resolvable@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+        "babel-plugin-transform-es2015-spread": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.4.0.tgz",
           "dependencies": {
-            "tryit": {
-              "version": "1.0.1",
-              "from": "tryit@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.1.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "js-yaml": {
-          "version": "3.4.0",
-          "from": "js-yaml@>=3.0.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.0.tgz",
+        "babel-plugin-transform-es2015-template-literals": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.3.13.tgz",
           "dependencies": {
-            "argparse": {
-              "version": "1.0.2",
-              "from": "argparse@>=1.0.2 <1.1.0",
-              "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.2.tgz",
-              "dependencies": {
-                "lodash": {
-                  "version": "3.10.1",
-                  "from": "lodash@>=3.2.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-                },
-                "sprintf-js": {
-                  "version": "1.0.3",
-                  "from": "sprintf-js@>=1.0.2 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
-                }
-              }
-            },
-            "esprima": {
-              "version": "2.2.0",
-              "from": "esprima@>=2.2.0 <2.3.0",
-              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.2.0.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "lodash.clonedeep": {
-          "version": "3.0.2",
-          "from": "lodash.clonedeep@>=3.0.1 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz",
+        "babel-plugin-transform-flow-strip-types": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.4.0.tgz",
           "dependencies": {
-            "lodash._baseclone": {
-              "version": "3.3.0",
-              "from": "lodash._baseclone@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz",
-              "dependencies": {
-                "lodash._arraycopy": {
-                  "version": "3.0.0",
-                  "from": "lodash._arraycopy@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz"
-                },
-                "lodash._arrayeach": {
-                  "version": "3.0.0",
-                  "from": "lodash._arrayeach@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz"
-                },
-                "lodash._baseassign": {
-                  "version": "3.2.0",
-                  "from": "lodash._baseassign@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
-                  "dependencies": {
-                    "lodash._basecopy": {
-                      "version": "3.0.1",
-                      "from": "lodash._basecopy@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
-                    }
-                  }
-                },
-                "lodash._basefor": {
-                  "version": "3.0.2",
-                  "from": "lodash._basefor@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz"
-                },
-                "lodash.isarray": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarray@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-                },
-                "lodash.keys": {
-                  "version": "3.1.2",
-                  "from": "lodash.keys@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
-                  "dependencies": {
-                    "lodash._getnative": {
-                      "version": "3.9.1",
-                      "from": "lodash._getnative@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-                    },
-                    "lodash.isarguments": {
-                      "version": "3.0.4",
-                      "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-                    }
-                  }
-                }
-              }
-            },
-            "lodash._bindcallback": {
-              "version": "3.0.1",
-              "from": "lodash._bindcallback@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "lodash.merge": {
-          "version": "3.3.2",
-          "from": "lodash.merge@>=3.3.2 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz",
+        "babel-plugin-transform-object-assign": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.3.13.tgz",
           "dependencies": {
-            "lodash._arraycopy": {
-              "version": "3.0.0",
-              "from": "lodash._arraycopy@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz"
-            },
-            "lodash._arrayeach": {
-              "version": "3.0.0",
-              "from": "lodash._arrayeach@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz"
-            },
-            "lodash._createassigner": {
-              "version": "3.1.1",
-              "from": "lodash._createassigner@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
-              "dependencies": {
-                "lodash._bindcallback": {
-                  "version": "3.0.1",
-                  "from": "lodash._bindcallback@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz"
-                },
-                "lodash._isiterateecall": {
-                  "version": "3.0.9",
-                  "from": "lodash._isiterateecall@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
-                },
-                "lodash.restparam": {
-                  "version": "3.6.1",
-                  "from": "lodash.restparam@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
-                }
-              }
-            },
-            "lodash._getnative": {
-              "version": "3.9.1",
-              "from": "lodash._getnative@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-            },
-            "lodash.isarguments": {
-              "version": "3.0.4",
-              "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-            },
-            "lodash.isarray": {
-              "version": "3.0.4",
-              "from": "lodash.isarray@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-            },
-            "lodash.isplainobject": {
-              "version": "3.2.0",
-              "from": "lodash.isplainobject@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz",
-              "dependencies": {
-                "lodash._basefor": {
-                  "version": "3.0.2",
-                  "from": "lodash._basefor@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz"
-                }
-              }
-            },
-            "lodash.istypedarray": {
-              "version": "3.0.2",
-              "from": "lodash.istypedarray@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz"
-            },
-            "lodash.keys": {
-              "version": "3.1.2",
-              "from": "lodash.keys@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
-            },
-            "lodash.keysin": {
-              "version": "3.0.8",
-              "from": "lodash.keysin@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz"
-            },
-            "lodash.toplainobject": {
-              "version": "3.0.0",
-              "from": "lodash.toplainobject@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz",
-              "dependencies": {
-                "lodash._basecopy": {
-                  "version": "3.0.1",
-                  "from": "lodash._basecopy@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
-                }
-              }
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "lodash.omit": {
-          "version": "3.1.0",
-          "from": "lodash.omit@>=3.1.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz",
+        "babel-plugin-transform-object-rest-spread": {
+          "version": "6.3.13",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.3.13.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.3.13.tgz",
           "dependencies": {
-            "lodash._arraymap": {
-              "version": "3.0.0",
-              "from": "lodash._arraymap@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz"
-            },
-            "lodash._basedifference": {
-              "version": "3.0.3",
-              "from": "lodash._basedifference@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz",
-              "dependencies": {
-                "lodash._baseindexof": {
-                  "version": "3.1.0",
-                  "from": "lodash._baseindexof@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz"
-                },
-                "lodash._cacheindexof": {
-                  "version": "3.0.2",
-                  "from": "lodash._cacheindexof@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz"
-                },
-                "lodash._createcache": {
-                  "version": "3.1.2",
-                  "from": "lodash._createcache@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz",
-                  "dependencies": {
-                    "lodash._getnative": {
-                      "version": "3.9.1",
-                      "from": "lodash._getnative@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-                    }
-                  }
-                }
-              }
-            },
-            "lodash._baseflatten": {
-              "version": "3.1.4",
-              "from": "lodash._baseflatten@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz",
-              "dependencies": {
-                "lodash.isarguments": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-                },
-                "lodash.isarray": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarray@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-                }
-              }
-            },
-            "lodash._bindcallback": {
-              "version": "3.0.1",
-              "from": "lodash._bindcallback@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz"
-            },
-            "lodash._pickbyarray": {
-              "version": "3.0.2",
-              "from": "lodash._pickbyarray@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz"
-            },
-            "lodash._pickbycallback": {
-              "version": "3.0.0",
-              "from": "lodash._pickbycallback@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz",
-              "dependencies": {
-                "lodash._basefor": {
-                  "version": "3.0.2",
-                  "from": "lodash._basefor@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz"
-                }
-              }
-            },
-            "lodash.keysin": {
-              "version": "3.0.8",
-              "from": "lodash.keysin@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz",
-              "dependencies": {
-                "lodash.isarguments": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-                },
-                "lodash.isarray": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarray@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-                }
-              }
-            },
-            "lodash.restparam": {
-              "version": "3.6.1",
-              "from": "lodash.restparam@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
+            "babel-plugin-syntax-object-rest-spread": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.3.13.tgz"
+            },
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "minimatch": {
-          "version": "2.0.10",
-          "from": "minimatch@>=2.0.1 <3.0.0",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+        "babel-plugin-transform-react-display-name": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.4.0.tgz",
           "dependencies": {
-            "brace-expansion": {
-              "version": "1.1.0",
-              "from": "brace-expansion@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.0.tgz",
-              "dependencies": {
-                "balanced-match": {
-                  "version": "0.2.0",
-                  "from": "balanced-match@>=0.2.0 <0.3.0",
-                  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
-                },
-                "concat-map": {
-                  "version": "0.0.1",
-                  "from": "concat-map@0.0.1",
-                  "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-                }
-              }
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
-        "mkdirp": {
-          "version": "0.5.1",
-          "from": "mkdirp@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+        "babel-plugin-transform-react-jsx": {
+          "version": "6.4.0",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.4.0.tgz",
           "dependencies": {
-            "minimist": {
-              "version": "0.0.8",
-              "from": "minimist@0.0.8",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            },
+            "babel-helper-builder-react-jsx": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.3.13.tgz"
             }
           }
         },
-        "object-assign": {
-          "version": "2.1.1",
-          "from": "object-assign@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-        },
-        "optionator": {
-          "version": "0.5.0",
-          "from": "optionator@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+        "babel-plugin-transform-regenerator": {
+          "version": "6.4.4",
+          "from": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.4.4.tgz",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.4.4.tgz",
           "dependencies": {
-            "deep-is": {
-              "version": "0.1.3",
-              "from": "deep-is@>=0.1.2 <0.2.0",
-              "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
-            },
-            "fast-levenshtein": {
-              "version": "1.0.7",
-              "from": "fast-levenshtein@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
-            },
-            "levn": {
-              "version": "0.2.5",
-              "from": "levn@>=0.2.5 <0.3.0",
-              "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
-            },
-            "prelude-ls": {
-              "version": "1.1.2",
-              "from": "prelude-ls@>=1.1.1 <1.2.0",
-              "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
-            },
-            "type-check": {
-              "version": "0.3.1",
-              "from": "type-check@>=0.3.1 <0.4.0",
-              "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
-            },
-            "wordwrap": {
-              "version": "0.0.3",
-              "from": "wordwrap@>=0.0.2 <0.1.0",
-              "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            },
+            "babel-traverse": {
+              "version": "6.4.5",
+              "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+              "dependencies": {
+                "babel-code-frame": {
+                  "version": "6.3.13",
+                  "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+                },
+                "babel-messages": {
+                  "version": "6.3.18",
+                  "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+                },
+                "globals": {
+                  "version": "8.18.0",
+                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                },
+                "invariant": {
+                  "version": "2.2.0",
+                  "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                }
+              }
             }
           }
+        }
+      }
+    },
+    "babel-register": {
+      "version": "6.4.3",
+      "from": "https://registry.npmjs.org/babel-register/-/babel-register-6.4.3.tgz",
+      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.4.3.tgz",
+      "dependencies": {
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+        }
+      }
+    },
+    "babel-types": {
+      "version": "6.4.5",
+      "from": "https://registry.npmjs.org/babel-types/-/babel-types-6.4.5.tgz",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.4.5.tgz",
+      "dependencies": {
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         },
-        "path-is-absolute": {
-          "version": "1.0.0",
-          "from": "path-is-absolute@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
-        },
-        "path-is-inside": {
-          "version": "1.0.1",
-          "from": "path-is-inside@>=1.0.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz"
-        },
-        "strip-json-comments": {
-          "version": "1.0.4",
-          "from": "strip-json-comments@>=1.0.1 <1.1.0",
-          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
-        },
-        "text-table": {
-          "version": "0.2.0",
-          "from": "text-table@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
-        },
-        "user-home": {
-          "version": "1.1.1",
-          "from": "user-home@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz"
-        },
-        "xml-escape": {
-          "version": "1.0.0",
-          "from": "xml-escape@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz"
+        "babel-traverse": {
+          "version": "6.4.5",
+          "from": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.4.5.tgz",
+          "dependencies": {
+            "babel-code-frame": {
+              "version": "6.3.13",
+              "from": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz",
+              "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.3.13.tgz"
+            },
+            "babel-messages": {
+              "version": "6.3.18",
+              "from": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz",
+              "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.3.18.tgz"
+            },
+            "globals": {
+              "version": "8.18.0",
+              "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+              "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+            },
+            "invariant": {
+              "version": "2.2.0",
+              "from": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+            }
+          }
         }
       }
     },
-    "eslint-plugin-react": {
-      "version": "3.3.1",
-      "from": "eslint-plugin-react@3.3.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.3.1.tgz"
+    "babylon": {
+      "version": "6.4.5",
+      "from": "https://registry.npmjs.org/babylon/-/babylon-6.4.5.tgz",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.4.5.tgz",
+      "dependencies": {
+        "babel-runtime": {
+          "version": "5.8.35",
+          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+        }
+      }
     },
-    "esprima-fb": {
-      "version": "15001.1001.0-dev-harmony-fb",
-      "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0",
-      "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
+    "balanced-match": {
+      "version": "0.2.1",
+      "from": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
     },
-    "esutils": {
-      "version": "2.0.2",
-      "from": "esutils@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+    "base64-js": {
+      "version": "0.0.8",
+      "from": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz"
     },
-    "event-target-shim": {
-      "version": "1.0.5",
-      "from": "event-target-shim@*",
-      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.0.5.tgz"
+    "beeper": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz"
     },
-    "expand-brackets": {
-      "version": "0.1.4",
-      "from": "expand-brackets@>=0.1.1 <0.2.0",
-      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.4.tgz"
+    "binary-extensions": {
+      "version": "1.3.1",
+      "from": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.3.1.tgz"
     },
-    "expand-range": {
-      "version": "1.8.1",
-      "from": "expand-range@>=1.8.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz"
+    "bluebird": {
+      "version": "2.10.2",
+      "from": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz"
     },
-    "extglob": {
-      "version": "0.3.1",
-      "from": "extglob@>=0.3.0 <0.4.0",
-      "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.1.tgz"
+    "brace-expansion": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
     },
-    "fancy-log": {
-      "version": "1.1.0",
-      "from": "fancy-log@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.1.0.tgz",
-      "dependencies": {
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.1.1 <2.0.0"
-        }
-      }
+    "braces": {
+      "version": "1.8.2",
+      "from": "https://registry.npmjs.org/braces/-/braces-1.8.2.tgz",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.2.tgz"
     },
-    "fbjs-haste": {
-      "version": "0.3.2",
-      "from": "fbjs-haste@0.3.2",
-      "resolved": "https://registry.npmjs.org/fbjs-haste/-/fbjs-haste-0.3.2.tgz"
+    "breakable": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz"
     },
-    "fbjs-scripts": {
-      "version": "0.4.0",
-      "from": "fbjs-scripts@>=0.4.0 <0.5.0",
+    "bser": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz",
       "dependencies": {
-        "semver": {
-          "version": "5.0.3",
-          "from": "semver@>=5.0.1 <6.0.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
+        "node-int64": {
+          "version": "0.4.0",
+          "from": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
         }
       }
     },
-    "filename-regex": {
-      "version": "2.0.0",
-      "from": "filename-regex@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz"
+    "builtin-modules": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
     },
-    "fill-range": {
-      "version": "2.2.2",
-      "from": "fill-range@>=2.1.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.2.tgz"
+    "camelcase": {
+      "version": "1.2.1",
+      "from": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
     },
-    "find-up": {
+    "camelcase-keys": {
       "version": "1.0.0",
-      "from": "find-up@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
+      "from": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz"
+    },
+    "center-align": {
+      "version": "0.1.2",
+      "from": "https://registry.npmjs.org/center-align/-/center-align-0.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.2.tgz"
+    },
+    "chalk": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
+    },
+    "chokidar": {
+      "version": "1.2.0",
+      "from": "https://registry.npmjs.org/chokidar/-/chokidar-1.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.2.0.tgz"
+    },
+    "cliui": {
+      "version": "2.1.0",
+      "from": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
       "dependencies": {
-        "path-exists": {
-          "version": "2.0.0",
-          "from": "path-exists@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
+        "wordwrap": {
+          "version": "0.0.2",
+          "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
         }
       }
     },
-    "for-in": {
-      "version": "0.1.4",
-      "from": "for-in@>=0.1.4 <0.2.0",
-      "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.4.tgz"
+    "clone": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
     },
-    "for-own": {
-      "version": "0.1.3",
-      "from": "for-own@>=0.1.3 <0.2.0",
-      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.3.tgz"
+    "clone-stats": {
+      "version": "0.0.1",
+      "from": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
     },
-    "fs-readdir-recursive": {
-      "version": "0.1.2",
-      "from": "fs-readdir-recursive@>=0.1.0 <0.2.0",
-      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz"
+    "commander": {
+      "version": "2.9.0",
+      "from": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
     },
-    "fsevents": {
-      "version": "1.0.5",
-      "from": "fsevents@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.5.tgz",
+    "commoner": {
+      "version": "0.10.4",
+      "from": "commoner@0.10.4",
+      "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.4.tgz"
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "from": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+    },
+    "connect": {
+      "version": "2.8.3",
+      "from": "https://registry.npmjs.org/connect/-/connect-2.8.3.tgz",
+      "resolved": "https://registry.npmjs.org/connect/-/connect-2.8.3.tgz",
       "dependencies": {
-        "abbrev": {
-          "version": "1.0.7",
-          "from": "abbrev@1",
-          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
-        },
-        "ansi": {
-          "version": "0.3.0",
-          "from": "ansi@~0.3.0",
-          "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
+        "qs": {
+          "version": "0.6.5",
+          "from": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz"
         },
-        "ansi-regex": {
-          "version": "2.0.0",
-          "from": "ansi-regex@^2.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
+        "formidable": {
+          "version": "1.0.14",
+          "from": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz",
+          "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
         },
-        "ansi-styles": {
-          "version": "2.1.0",
-          "from": "ansi-styles@^2.1.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
+        "cookie-signature": {
+          "version": "1.0.1",
+          "from": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz"
         },
-        "are-we-there-yet": {
-          "version": "1.0.4",
-          "from": "are-we-there-yet@~1.0.0",
-          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.4.tgz"
+        "buffer-crc32": {
+          "version": "0.2.1",
+          "from": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz",
+          "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz"
         },
-        "asn1": {
-          "version": "0.1.11",
-          "from": "asn1@0.1.11",
-          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
+        "cookie": {
+          "version": "0.1.0",
+          "from": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz"
         },
-        "assert-plus": {
-          "version": "0.1.5",
-          "from": "assert-plus@^0.1.5",
-          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
+        "send": {
+          "version": "0.1.2",
+          "from": "https://registry.npmjs.org/send/-/send-0.1.2.tgz",
+          "resolved": "https://registry.npmjs.org/send/-/send-0.1.2.tgz",
+          "dependencies": {
+            "mime": {
+              "version": "1.2.11",
+              "from": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
+              "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
+            },
+            "range-parser": {
+              "version": "0.0.4",
+              "from": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz",
+              "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz"
+            }
+          }
         },
-        "async": {
-          "version": "1.5.0",
-          "from": "async@^1.4.0",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
+        "bytes": {
+          "version": "0.2.0",
+          "from": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz"
         },
-        "aws-sign2": {
-          "version": "0.6.0",
-          "from": "aws-sign2@~0.6.0",
-          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
+        "fresh": {
+          "version": "0.1.0",
+          "from": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz"
         },
-        "balanced-match": {
-          "version": "0.2.1",
-          "from": "balanced-match@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
+        "pause": {
+          "version": "0.0.1",
+          "from": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz"
         },
-        "bl": {
-          "version": "1.0.0",
-          "from": "bl@~1.0.0",
-          "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+        "uid2": {
+          "version": "0.0.2",
+          "from": "https://registry.npmjs.org/uid2/-/uid2-0.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.2.tgz"
+        },
+        "methods": {
+          "version": "0.0.1",
+          "from": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz"
+        }
+      }
+    },
+    "convert-source-map": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.1.tgz"
+    },
+    "core-js": {
+      "version": "1.2.6",
+      "from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
+    },
+    "core-util-is": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+    },
+    "dateformat": {
+      "version": "1.0.11",
+      "from": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.11.tgz",
+      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.11.tgz"
+    },
+    "debug": {
+      "version": "2.2.0",
+      "from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
+    },
+    "decamelize": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/decamelize/-/decamelize-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.1.1.tgz"
+    },
+    "defined": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz"
+    },
+    "defs": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz",
+      "dependencies": {
+        "yargs": {
+          "version": "3.27.0",
+          "from": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz"
+        }
+      }
+    },
+    "detect-indent": {
+      "version": "3.0.1",
+      "from": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz"
+    },
+    "detective": {
+      "version": "4.3.1",
+      "from": "https://registry.npmjs.org/detective/-/detective-4.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/detective/-/detective-4.3.1.tgz"
+    },
+    "duplexer2": {
+      "version": "0.0.2",
+      "from": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
+      "dependencies": {
+        "readable-stream": {
+          "version": "1.1.13",
+          "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
+        }
+      }
+    },
+    "error-ex": {
+      "version": "1.3.0",
+      "from": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz"
+    },
+    "escape-string-regexp": {
+      "version": "1.0.3",
+      "from": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
+    },
+    "eslint": {
+      "version": "1.3.1",
+      "from": "https://registry.npmjs.org/eslint/-/eslint-1.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-1.3.1.tgz",
+      "dependencies": {
+        "concat-stream": {
+          "version": "1.5.0",
+          "from": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+          "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
           "dependencies": {
+            "typedarray": {
+              "version": "0.0.6",
+              "from": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+              "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+            },
             "readable-stream": {
-              "version": "2.0.4",
-              "from": "readable-stream@~2.0.0",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
+              "version": "2.0.2",
+              "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
               "dependencies": {
-                "core-util-is": {
+                "process-nextick-args": {
+                  "version": "1.0.2",
+                  "from": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.2.tgz",
+                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.2.tgz"
+                },
+                "util-deprecate": {
                   "version": "1.0.1",
-                  "from": "core-util-is@~1.0.0",
-                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+                  "from": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz"
+                }
+              }
+            }
+          }
+        },
+        "doctrine": {
+          "version": "0.6.4",
+          "from": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.6.4.tgz",
+          "dependencies": {
+            "esutils": {
+              "version": "1.1.6",
+              "from": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz",
+              "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
+            }
+          }
+        },
+        "escope": {
+          "version": "3.2.0",
+          "from": "https://registry.npmjs.org/escope/-/escope-3.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/escope/-/escope-3.2.0.tgz",
+          "dependencies": {
+            "es6-map": {
+              "version": "0.1.1",
+              "from": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.1.tgz",
+              "dependencies": {
+                "d": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/d/-/d-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
                 },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@~2.0.1",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                "es5-ext": {
+                  "version": "0.10.7",
+                  "from": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz",
+                  "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz",
+                  "dependencies": {
+                    "es6-symbol": {
+                      "version": "2.0.1",
+                      "from": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
+                    }
+                  }
                 },
-                "isarray": {
-                  "version": "0.0.1",
-                  "from": "isarray@0.0.1",
-                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                "es6-iterator": {
+                  "version": "0.1.3",
+                  "from": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
+                  "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
+                  "dependencies": {
+                    "es6-symbol": {
+                      "version": "2.0.1",
+                      "from": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
+                    }
+                  }
                 },
-                "process-nextick-args": {
-                  "version": "1.0.3",
-                  "from": "process-nextick-args@~1.0.0",
-                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+                "es6-set": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.1.tgz"
                 },
-                "string_decoder": {
-                  "version": "0.10.31",
-                  "from": "string_decoder@~0.10.x",
-                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                "es6-symbol": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-0.1.1.tgz"
                 },
-                "util-deprecate": {
-                  "version": "1.0.2",
-                  "from": "util-deprecate@~1.0.1",
-                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                "event-emitter": {
+                  "version": "0.3.3",
+                  "from": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.3.tgz",
+                  "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.3.tgz"
+                }
+              }
+            },
+            "es6-weak-map": {
+              "version": "0.1.4",
+              "from": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz",
+              "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz",
+              "dependencies": {
+                "d": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/d/-/d-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
+                },
+                "es5-ext": {
+                  "version": "0.10.7",
+                  "from": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz",
+                  "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.7.tgz"
+                },
+                "es6-iterator": {
+                  "version": "0.1.3",
+                  "from": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
+                  "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz"
+                },
+                "es6-symbol": {
+                  "version": "2.0.1",
+                  "from": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz"
                 }
               }
+            },
+            "esrecurse": {
+              "version": "3.1.1",
+              "from": "https://registry.npmjs.org/esrecurse/-/esrecurse-3.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-3.1.1.tgz"
+            },
+            "estraverse": {
+              "version": "3.1.0",
+              "from": "https://registry.npmjs.org/estraverse/-/estraverse-3.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-3.1.0.tgz"
             }
           }
         },
-        "block-stream": {
-          "version": "0.0.8",
-          "from": "block-stream@*",
-          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
-        },
-        "boom": {
-          "version": "2.10.1",
-          "from": "boom@^2.8.x",
-          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
+        "espree": {
+          "version": "2.2.4",
+          "from": "https://registry.npmjs.org/espree/-/espree-2.2.4.tgz",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.4.tgz"
         },
-        "brace-expansion": {
-          "version": "1.1.1",
-          "from": "brace-expansion@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
+        "estraverse": {
+          "version": "4.1.0",
+          "from": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.0.tgz"
         },
-        "caseless": {
-          "version": "0.11.0",
-          "from": "caseless@~0.11.0",
-          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
+        "estraverse-fb": {
+          "version": "1.3.1",
+          "from": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.1.tgz",
+          "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.1.tgz"
         },
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@^1.1.1",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
+        "globals": {
+          "version": "8.7.0",
+          "from": "https://registry.npmjs.org/globals/-/globals-8.7.0.tgz",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-8.7.0.tgz"
         },
-        "combined-stream": {
-          "version": "1.0.5",
-          "from": "combined-stream@~1.0.5",
-          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
-        },
-        "commander": {
-          "version": "2.9.0",
-          "from": "commander@^2.8.1",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
-        },
-        "concat-map": {
-          "version": "0.0.1",
-          "from": "concat-map@0.0.1",
-          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-        },
-        "core-util-is": {
-          "version": "1.0.1",
-          "from": "core-util-is@~1.0.0",
-          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-        },
-        "cryptiles": {
-          "version": "2.0.5",
-          "from": "cryptiles@2.x.x",
-          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
-        },
-        "ctype": {
-          "version": "0.5.3",
-          "from": "ctype@0.5.3",
-          "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
+        "handlebars": {
+          "version": "3.0.3",
+          "from": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.3.tgz",
+          "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-3.0.3.tgz",
+          "dependencies": {
+            "source-map": {
+              "version": "0.1.43",
+              "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+              "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz"
+            },
+            "uglify-js": {
+              "version": "2.3.6",
+              "from": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz",
+              "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.3.6.tgz",
+              "dependencies": {
+                "async": {
+                  "version": "0.2.10",
+                  "from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+                  "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+                },
+                "optimist": {
+                  "version": "0.3.7",
+                  "from": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
+                  "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
+                  "dependencies": {
+                    "wordwrap": {
+                      "version": "0.0.3",
+                      "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+                    }
+                  }
+                }
+              }
+            }
+          }
         },
-        "debug": {
-          "version": "0.7.4",
-          "from": "debug@~0.7.2",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+        "inquirer": {
+          "version": "0.9.0",
+          "from": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz",
+          "dependencies": {
+            "cli-width": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz"
+            },
+            "figures": {
+              "version": "1.3.5",
+              "from": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz",
+              "resolved": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz"
+            },
+            "readline2": {
+              "version": "0.1.1",
+              "from": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+              "dependencies": {
+                "mute-stream": {
+                  "version": "0.0.4",
+                  "from": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz",
+                  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz"
+                },
+                "strip-ansi": {
+                  "version": "2.0.1",
+                  "from": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
+                  "dependencies": {
+                    "ansi-regex": {
+                      "version": "1.1.1",
+                      "from": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
+                      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz"
+                    }
+                  }
+                }
+              }
+            },
+            "run-async": {
+              "version": "0.1.0",
+              "from": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz"
+            },
+            "rx-lite": {
+              "version": "2.5.2",
+              "from": "https://registry.npmjs.org/rx-lite/-/rx-lite-2.5.2.tgz",
+              "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-2.5.2.tgz"
+            }
+          }
         },
-        "deep-extend": {
-          "version": "0.2.11",
-          "from": "deep-extend@~0.2.5",
-          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
+        "is-my-json-valid": {
+          "version": "2.12.2",
+          "from": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz",
+          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz",
+          "dependencies": {
+            "generate-function": {
+              "version": "2.0.0",
+              "from": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+            },
+            "generate-object-property": {
+              "version": "1.2.0",
+              "from": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+              "dependencies": {
+                "is-property": {
+                  "version": "1.0.2",
+                  "from": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+                  "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+                }
+              }
+            },
+            "jsonpointer": {
+              "version": "2.0.0",
+              "from": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
+            },
+            "xtend": {
+              "version": "4.0.0",
+              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+            }
+          }
         },
-        "delayed-stream": {
+        "is-resolvable": {
           "version": "1.0.0",
-          "from": "delayed-stream@~1.0.0",
-          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
-        },
-        "delegates": {
-          "version": "0.1.0",
-          "from": "delegates@^0.1.0",
-          "resolved": "https://registry.npmjs.org/delegates/-/delegates-0.1.0.tgz"
-        },
-        "escape-string-regexp": {
-          "version": "1.0.3",
-          "from": "escape-string-regexp@^1.0.2",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
-        },
-        "extend": {
-          "version": "3.0.0",
-          "from": "extend@~3.0.0",
-          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+          "from": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+          "dependencies": {
+            "tryit": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/tryit/-/tryit-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.1.tgz"
+            }
+          }
         },
-        "forever-agent": {
-          "version": "0.6.1",
-          "from": "forever-agent@~0.6.1",
-          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+        "js-yaml": {
+          "version": "3.4.0",
+          "from": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.0.tgz",
+          "dependencies": {
+            "argparse": {
+              "version": "1.0.2",
+              "from": "https://registry.npmjs.org/argparse/-/argparse-1.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.2.tgz",
+              "dependencies": {
+                "sprintf-js": {
+                  "version": "1.0.3",
+                  "from": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+                  "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+                }
+              }
+            },
+            "esprima": {
+              "version": "2.2.0",
+              "from": "https://registry.npmjs.org/esprima/-/esprima-2.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.2.0.tgz"
+            }
+          }
         },
-        "form-data": {
-          "version": "1.0.0-rc3",
-          "from": "form-data@~1.0.0-rc3",
-          "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz"
+        "lodash.clonedeep": {
+          "version": "3.0.2",
+          "from": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz",
+          "dependencies": {
+            "lodash._baseclone": {
+              "version": "3.3.0",
+              "from": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz",
+              "dependencies": {
+                "lodash._arraycopy": {
+                  "version": "3.0.0",
+                  "from": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz"
+                },
+                "lodash._arrayeach": {
+                  "version": "3.0.0",
+                  "from": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz"
+                }
+              }
+            }
+          }
         },
-        "fstream": {
-          "version": "1.0.8",
-          "from": "fstream@^1.0.2",
-          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz"
+        "lodash.merge": {
+          "version": "3.3.2",
+          "from": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz",
+          "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz",
+          "dependencies": {
+            "lodash._arraycopy": {
+              "version": "3.0.0",
+              "from": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz"
+            },
+            "lodash._arrayeach": {
+              "version": "3.0.0",
+              "from": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz"
+            },
+            "lodash.isplainobject": {
+              "version": "3.2.0",
+              "from": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz"
+            },
+            "lodash.istypedarray": {
+              "version": "3.0.2",
+              "from": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz"
+            },
+            "lodash.toplainobject": {
+              "version": "3.0.0",
+              "from": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz"
+            }
+          }
         },
-        "fstream-ignore": {
-          "version": "1.0.3",
-          "from": "fstream-ignore@~1.0.3",
-          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz",
+        "lodash.omit": {
+          "version": "3.1.0",
+          "from": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz",
           "dependencies": {
-            "minimatch": {
+            "lodash._arraymap": {
               "version": "3.0.0",
-              "from": "minimatch@>=3.0.0 <4.0.0"
+              "from": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz"
+            },
+            "lodash._basedifference": {
+              "version": "3.0.3",
+              "from": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz",
+              "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz",
+              "dependencies": {
+                "lodash._baseindexof": {
+                  "version": "3.1.0",
+                  "from": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz",
+                  "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz"
+                },
+                "lodash._cacheindexof": {
+                  "version": "3.0.2",
+                  "from": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz",
+                  "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz"
+                },
+                "lodash._createcache": {
+                  "version": "3.1.2",
+                  "from": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz",
+                  "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz"
+                }
+              }
             }
           }
         },
-        "gauge": {
-          "version": "1.2.2",
-          "from": "gauge@~1.2.0",
-          "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.2.tgz"
-        },
-        "generate-function": {
-          "version": "2.0.0",
-          "from": "generate-function@^2.0.0",
-          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
-        },
-        "generate-object-property": {
-          "version": "1.2.0",
-          "from": "generate-object-property@^1.1.0",
-          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
-        },
-        "graceful-fs": {
-          "version": "4.1.2",
-          "from": "graceful-fs@4.1",
-          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
-        },
-        "graceful-readlink": {
-          "version": "1.0.1",
-          "from": "graceful-readlink@>= 1.0.0",
-          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
-        },
-        "har-validator": {
-          "version": "2.0.2",
-          "from": "har-validator@~2.0.2",
-          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.2.tgz"
-        },
-        "has-ansi": {
-          "version": "2.0.0",
-          "from": "has-ansi@^2.0.0",
-          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
-        },
-        "has-unicode": {
-          "version": "1.0.1",
-          "from": "has-unicode@^1.0.0",
-          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-1.0.1.tgz"
-        },
-        "hawk": {
-          "version": "3.1.0",
-          "from": "hawk@~3.1.0",
-          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz"
-        },
-        "hoek": {
-          "version": "2.16.3",
-          "from": "hoek@2.x.x",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
-        },
-        "http-signature": {
-          "version": "0.11.0",
-          "from": "http-signature@~0.11.0",
-          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz"
+        "object-assign": {
+          "version": "2.1.1",
+          "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
         },
-        "inflight": {
-          "version": "1.0.4",
-          "from": "inflight@>=1.0.4 <2.0.0",
-          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+        "optionator": {
+          "version": "0.5.0",
+          "from": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+          "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
           "dependencies": {
-            "once": {
-              "version": "1.3.2",
-              "from": "once@>=1.3.0 <2.0.0"
+            "prelude-ls": {
+              "version": "1.1.2",
+              "from": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+              "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+            },
+            "deep-is": {
+              "version": "0.1.3",
+              "from": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+              "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+            },
+            "wordwrap": {
+              "version": "0.0.3",
+              "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+              "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+            },
+            "type-check": {
+              "version": "0.3.1",
+              "from": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz",
+              "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
+            },
+            "levn": {
+              "version": "0.2.5",
+              "from": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz",
+              "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
+            },
+            "fast-levenshtein": {
+              "version": "1.0.7",
+              "from": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz",
+              "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
             }
           }
         },
-        "inherits": {
-          "version": "2.0.1",
-          "from": "inherits@*",
-          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-        },
-        "ini": {
-          "version": "1.3.4",
-          "from": "ini@~1.3.0",
-          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
-        },
-        "is-my-json-valid": {
-          "version": "2.12.2",
-          "from": "is-my-json-valid@^2.12.2",
-          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz"
-        },
-        "is-property": {
-          "version": "1.0.2",
-          "from": "is-property@^1.0.0",
-          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
-        },
-        "isarray": {
-          "version": "0.0.1",
-          "from": "isarray@0.0.1",
-          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-        },
-        "isstream": {
-          "version": "0.1.2",
-          "from": "isstream@~0.1.2",
-          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
-        },
-        "json-stringify-safe": {
-          "version": "5.0.1",
-          "from": "json-stringify-safe@~5.0.1",
-          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
-        },
-        "jsonpointer": {
-          "version": "2.0.0",
-          "from": "jsonpointer@2.0.0",
-          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
-        },
-        "lodash._basetostring": {
-          "version": "3.0.1",
-          "from": "lodash._basetostring@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
-        },
-        "lodash._createpadding": {
-          "version": "3.6.1",
-          "from": "lodash._createpadding@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz"
-        },
-        "lodash.pad": {
-          "version": "3.1.1",
-          "from": "lodash.pad@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-3.1.1.tgz"
-        },
-        "lodash.padleft": {
-          "version": "3.1.1",
-          "from": "lodash.padleft@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.padleft/-/lodash.padleft-3.1.1.tgz"
-        },
-        "lodash.padright": {
-          "version": "3.1.1",
-          "from": "lodash.padright@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.padright/-/lodash.padright-3.1.1.tgz"
-        },
-        "lodash.repeat": {
-          "version": "3.0.1",
-          "from": "lodash.repeat@^3.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz"
-        },
-        "mime-db": {
-          "version": "1.19.0",
-          "from": "mime-db@~1.19.0",
-          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz"
-        },
-        "mime-types": {
-          "version": "2.1.7",
-          "from": "mime-types@~2.1.7",
-          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz"
+        "path-is-inside": {
+          "version": "1.0.1",
+          "from": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz"
         },
-        "minimist": {
-          "version": "0.0.8",
-          "from": "minimist@0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+        "strip-json-comments": {
+          "version": "1.0.4",
+          "from": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
         },
-        "mkdirp": {
-          "version": "0.5.1",
-          "from": "mkdirp@>=0.3.0 <0.4.0||>=0.4.0 <0.5.0||>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
+        "text-table": {
+          "version": "0.2.0",
+          "from": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
         },
-        "node-pre-gyp": {
-          "version": "0.6.15",
-          "from": "node-pre-gyp@latest",
-          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.15.tgz",
+        "xml-escape": {
+          "version": "1.0.0",
+          "from": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz"
+        }
+      }
+    },
+    "eslint-plugin-react": {
+      "version": "3.3.1",
+      "from": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.3.1.tgz"
+    },
+    "esprima-fb": {
+      "version": "15001.1001.0-dev-harmony-fb",
+      "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz",
+      "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
+    },
+    "esutils": {
+      "version": "2.0.2",
+      "from": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+    },
+    "event-target-shim": {
+      "version": "1.0.5",
+      "from": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.0.5.tgz",
+      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.0.5.tgz"
+    },
+    "expand-brackets": {
+      "version": "0.1.4",
+      "from": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.4.tgz",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.4.tgz"
+    },
+    "expand-range": {
+      "version": "1.8.1",
+      "from": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz",
+      "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz"
+    },
+    "extglob": {
+      "version": "0.3.1",
+      "from": "https://registry.npmjs.org/extglob/-/extglob-0.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.1.tgz"
+    },
+    "fancy-log": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.1.0.tgz"
+    },
+    "fast-path": {
+      "version": "1.1.0",
+      "from": "fast-path@*",
+      "resolved": "https://registry.npmjs.org/fast-path/-/fast-path-1.1.0.tgz"
+    },
+    "fbjs": {
+      "version": "0.7.2",
+      "from": "fbjs@0.7.2",
+      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.7.2.tgz",
+      "dependencies": {
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "from": "isomorphic-fetch@>=2.1.1 <3.0.0",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
           "dependencies": {
-            "nopt": {
-              "version": "3.0.4",
-              "from": "nopt@>=3.0.1 <3.1.0",
-              "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz"
+            "whatwg-fetch": {
+              "version": "0.11.0",
+              "from": "whatwg-fetch@>=0.10.0",
+              "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.11.0.tgz"
             }
           }
-        },
-        "node-uuid": {
-          "version": "1.4.3",
-          "from": "node-uuid@~1.4.3",
-          "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
-        },
-        "npmlog": {
-          "version": "1.2.1",
-          "from": "npmlog@~1.2.0",
-          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz"
-        },
-        "oauth-sign": {
-          "version": "0.8.0",
-          "from": "oauth-sign@~0.8.0",
-          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz"
-        },
-        "once": {
-          "version": "1.1.1",
-          "from": "once@~1.1.1",
-          "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz"
-        },
-        "path-is-absolute": {
-          "version": "1.0.0",
-          "from": "path-is-absolute@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
-        },
-        "pinkie": {
-          "version": "1.0.0",
-          "from": "pinkie@^1.0.0",
-          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-        },
-        "pinkie-promise": {
-          "version": "1.0.0",
-          "from": "pinkie-promise@^1.0.0",
-          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz"
-        },
-        "qs": {
-          "version": "5.2.0",
-          "from": "qs@~5.2.0",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
-        },
-        "rc": {
-          "version": "1.1.2",
-          "from": "rc@~1.1.0",
-          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.2.tgz",
-          "dependencies": {
-            "minimist": {
-              "version": "1.2.0",
-              "from": "minimist@^1.1.2",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-            }
-          }
-        },
-        "readable-stream": {
-          "version": "1.1.13",
-          "from": "readable-stream@^1.1.13",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
-        },
-        "request": {
-          "version": "2.65.0",
-          "from": "request@2.x",
-          "resolved": "https://registry.npmjs.org/request/-/request-2.65.0.tgz"
-        },
-        "rimraf": {
-          "version": "2.4.3",
-          "from": "rimraf@~2.4.0",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz",
-          "dependencies": {
-            "glob": {
-              "version": "5.0.15",
-              "from": "glob@>=5.0.14 <6.0.0",
-              "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz"
-            },
-            "minimatch": {
-              "version": "3.0.0",
-              "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz"
-            },
-            "once": {
-              "version": "1.3.2",
-              "from": "once@>=1.3.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz"
-            }
-          }
-        },
-        "semver": {
-          "version": "5.0.3",
-          "from": "semver@~5.0.1",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
-        },
-        "sntp": {
-          "version": "1.0.9",
-          "from": "sntp@1.x.x",
-          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
-        },
-        "string_decoder": {
-          "version": "0.10.31",
-          "from": "string_decoder@~0.10.x",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-        },
-        "stringstream": {
-          "version": "0.0.5",
-          "from": "stringstream@~0.0.4",
-          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
-        },
-        "strip-ansi": {
-          "version": "3.0.0",
-          "from": "strip-ansi@^3.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
-        },
-        "strip-json-comments": {
-          "version": "0.1.3",
-          "from": "strip-json-comments@0.1.x",
-          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
-        },
-        "supports-color": {
-          "version": "2.0.0",
-          "from": "supports-color@^2.0.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
-        },
-        "tar": {
-          "version": "2.2.1",
-          "from": "tar@~2.2.0",
-          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
-        },
-        "tar-pack": {
-          "version": "3.1.0",
-          "from": "tar-pack@~3.1.0",
-          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.1.0.tgz",
-          "dependencies": {
-            "readable-stream": {
-              "version": "1.0.33",
-              "from": "readable-stream@~1.0.2",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-              "dependencies": {
-                "core-util-is": {
-                  "version": "1.0.1",
-                  "from": "core-util-is@~1.0.0",
-                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@~2.0.1",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "isarray": {
-                  "version": "0.0.1",
-                  "from": "isarray@0.0.1",
-                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                },
-                "string_decoder": {
-                  "version": "0.10.31",
-                  "from": "string_decoder@~0.10.x",
-                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                }
-              }
-            },
-            "rimraf": {
-              "version": "2.2.8",
-              "from": "rimraf@~2.2.0",
-              "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
-            }
-          }
-        },
-        "tough-cookie": {
-          "version": "2.2.0",
-          "from": "tough-cookie@~2.2.0",
-          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.0.tgz"
-        },
-        "tunnel-agent": {
-          "version": "0.4.1",
-          "from": "tunnel-agent@~0.4.1",
-          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
-        },
-        "uid-number": {
-          "version": "0.0.3",
-          "from": "uid-number@0.0.3",
-          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.3.tgz"
-        },
-        "wrappy": {
-          "version": "1.0.1",
-          "from": "wrappy@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-        },
-        "xtend": {
-          "version": "4.0.1",
-          "from": "xtend@^4.0.0",
-          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
-        }
-      }
-    },
-    "get-stdin": {
-      "version": "4.0.1",
-      "from": "get-stdin@>=4.0.1 <5.0.0",
-      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-    },
-    "glob": {
-      "version": "5.0.15",
-      "from": "glob@>=5.0.15 <6.0.0",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz"
-    },
-    "glob-base": {
-      "version": "0.3.0",
-      "from": "glob-base@>=0.3.0 <0.4.0",
-      "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz"
-    },
-    "glob-parent": {
-      "version": "2.0.0",
-      "from": "glob-parent@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz"
-    },
-    "globals": {
-      "version": "8.11.0",
-      "from": "globals@>=8.3.0 <9.0.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-8.11.0.tgz"
-    },
-    "glogg": {
-      "version": "1.0.0",
-      "from": "glogg@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz"
-    },
-    "graceful-fs": {
-      "version": "4.1.2",
-      "from": "graceful-fs@>=4.1.2 <5.0.0",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
-    },
-    "graceful-readlink": {
-      "version": "1.0.1",
-      "from": "graceful-readlink@>=1.0.0",
-      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
-    },
-    "gulp-util": {
-      "version": "3.0.7",
-      "from": "gulp-util@>=3.0.4 <4.0.0",
-      "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.7.tgz",
-      "dependencies": {
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.0.0 <2.0.0"
-        },
-        "object-assign": {
-          "version": "3.0.0",
-          "from": "object-assign@>=3.0.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
         }
       }
     },
-    "gulplog": {
-      "version": "1.0.0",
-      "from": "gulplog@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz"
+    "fbjs-scripts": {
+      "version": "0.4.0",
+      "from": "fbjs-scripts@0.4.0",
+      "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.4.0.tgz"
     },
-    "has-ansi": {
+    "filename-regex": {
       "version": "2.0.0",
-      "from": "has-ansi@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
+      "from": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz"
     },
-    "has-gulplog": {
-      "version": "0.1.0",
-      "from": "has-gulplog@>=0.1.0 <0.2.0",
-      "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz"
+    "fill-range": {
+      "version": "2.2.2",
+      "from": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.2.tgz",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.2.tgz"
     },
-    "home-or-tmp": {
+    "find-up": {
       "version": "1.0.0",
-      "from": "home-or-tmp@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz"
-    },
-    "hosted-git-info": {
-      "version": "2.1.4",
-      "from": "hosted-git-info@>=2.1.4 <3.0.0",
-      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
-    },
-    "iconv-lite": {
-      "version": "0.4.13",
-      "from": "iconv-lite@>=0.4.5 <0.5.0",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
-    },
-    "image-size": {
-      "version": "0.3.5",
-      "from": "image-size@0.3.5",
-      "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz"
-    },
-    "immutable": {
-      "version": "3.7.5",
-      "from": "immutable@>=3.7.4 <4.0.0",
-      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.5.tgz"
-    },
-    "indent-string": {
-      "version": "2.1.0",
-      "from": "indent-string@>=2.1.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "from": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
       "dependencies": {
-        "repeating": {
+        "path-exists": {
           "version": "2.0.0",
-          "from": "repeating@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz"
+          "from": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
         }
       }
     },
-    "inflight": {
-      "version": "1.0.4",
-      "from": "inflight@>=1.0.4 <2.0.0",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz"
-    },
-    "inherits": {
-      "version": "2.0.1",
-      "from": "inherits@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-    },
-    "invariant": {
-      "version": "2.1.2",
-      "from": "invariant@>=2.1.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.1.2.tgz"
-    },
-    "invert-kv": {
-      "version": "1.0.0",
-      "from": "invert-kv@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz"
-    },
-    "is-arrayish": {
-      "version": "0.2.1",
-      "from": "is-arrayish@>=0.2.1 <0.3.0",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
-    },
-    "is-binary-path": {
-      "version": "1.0.1",
-      "from": "is-binary-path@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz"
-    },
-    "is-buffer": {
-      "version": "1.1.0",
-      "from": "is-buffer@>=1.0.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.0.tgz"
-    },
-    "is-builtin-module": {
-      "version": "1.0.0",
-      "from": "is-builtin-module@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz"
-    },
-    "is-dotfile": {
-      "version": "1.0.2",
-      "from": "is-dotfile@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz"
-    },
-    "is-equal-shallow": {
-      "version": "0.1.3",
-      "from": "is-equal-shallow@>=0.1.1 <0.2.0",
-      "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz"
-    },
-    "is-extendable": {
-      "version": "0.1.1",
-      "from": "is-extendable@>=0.1.1 <0.2.0",
-      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz"
-    },
-    "is-extglob": {
-      "version": "1.0.0",
-      "from": "is-extglob@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz"
-    },
-    "is-finite": {
-      "version": "1.0.1",
-      "from": "is-finite@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz"
-    },
-    "is-glob": {
-      "version": "2.0.1",
-      "from": "is-glob@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz"
-    },
-    "is-integer": {
-      "version": "1.0.6",
-      "from": "is-integer@>=1.0.4 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz"
-    },
-    "is-number": {
-      "version": "1.1.2",
-      "from": "is-number@>=1.1.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-1.1.2.tgz"
-    },
-    "is-primitive": {
-      "version": "2.0.0",
-      "from": "is-primitive@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz"
-    },
-    "is-utf8": {
-      "version": "0.2.0",
-      "from": "is-utf8@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-    },
-    "isarray": {
-      "version": "0.0.1",
-      "from": "isarray@0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-    },
-    "isobject": {
-      "version": "1.0.2",
-      "from": "isobject@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz"
-    },
-    "jest-cli": {
-      "version": "0.7.1",
-      "from": "jest-cli@0.7.1",
+    "flow-bin": {
+      "version": "0.21.0",
+      "from": "flow-bin@>=0.21.0 <0.22.0",
+      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.21.0.tgz",
       "dependencies": {
-        "cover": {
-          "version": "0.2.9",
-          "from": "cover@>=0.2.9 <0.3.0",
-          "resolved": "https://registry.npmjs.org/cover/-/cover-0.2.9.tgz",
+        "bin-wrapper": {
+          "version": "2.1.3",
+          "from": "bin-wrapper@>=2.0.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-2.1.3.tgz",
           "dependencies": {
-            "cli-table": {
-              "version": "0.0.2",
-              "from": "cli-table@>=0.0.0 <0.1.0",
-              "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.0.2.tgz",
+            "bin-check": {
+              "version": "1.1.0",
+              "from": "bin-check@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-1.1.0.tgz",
               "dependencies": {
-                "colors": {
-                  "version": "0.3.0",
-                  "from": "colors@0.3.0",
-                  "resolved": "https://registry.npmjs.org/colors/-/colors-0.3.0.tgz"
+                "executable": {
+                  "version": "1.1.0",
+                  "from": "executable@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/executable/-/executable-1.1.0.tgz"
+                },
+                "spawn-sync": {
+                  "version": "1.0.15",
+                  "from": "spawn-sync@>=1.0.6 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
+                  "dependencies": {
+                    "concat-stream": {
+                      "version": "1.5.1",
+                      "from": "concat-stream@>=1.4.6 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz",
+                      "dependencies": {
+                        "typedarray": {
+                          "version": "0.0.6",
+                          "from": "typedarray@>=0.0.5 <0.1.0",
+                          "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+                        }
+                      }
+                    },
+                    "os-shim": {
+                      "version": "0.1.3",
+                      "from": "os-shim@>=0.1.2 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz"
+                    }
+                  }
                 }
               }
             },
-            "underscore": {
-              "version": "1.2.4",
-              "from": "underscore@>=1.2.0 <1.3.0",
-              "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.2.4.tgz"
-            },
-            "underscore.string": {
-              "version": "2.0.0",
-              "from": "underscore.string@>=2.0.0 <2.1.0",
-              "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.0.0.tgz"
-            },
-            "which": {
-              "version": "1.0.9",
-              "from": "which@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz"
-            }
-          }
-        },
-        "diff": {
-          "version": "2.2.0",
-          "from": "diff@>=2.1.1 <3.0.0",
-          "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.0.tgz"
-        },
-        "graceful-fs": {
-          "version": "4.1.2",
-          "from": "graceful-fs@>=4.1.2 <5.0.0"
-        },
-        "istanbul": {
-          "version": "0.3.22",
-          "from": "istanbul@>=0.3.19 <0.4.0",
-          "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz",
-          "dependencies": {
-            "abbrev": {
-              "version": "1.0.7",
-              "from": "abbrev@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
-            },
-            "async": {
-              "version": "1.5.0",
-              "from": "async@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
-            },
-            "escodegen": {
-              "version": "1.7.0",
-              "from": "escodegen@>=1.7.0 <1.8.0",
-              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.0.tgz",
+            "bin-version-check": {
+              "version": "2.1.0",
+              "from": "bin-version-check@>=2.1.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-2.1.0.tgz",
               "dependencies": {
-                "esprima": {
-                  "version": "1.2.5",
-                  "from": "esprima@>=1.2.2 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
-                },
-                "estraverse": {
-                  "version": "1.9.3",
-                  "from": "estraverse@>=1.9.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
-                },
-                "esutils": {
-                  "version": "2.0.2",
-                  "from": "esutils@>=2.0.2 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
-                },
-                "optionator": {
-                  "version": "0.5.0",
-                  "from": "optionator@>=0.5.0 <0.6.0",
-                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+                "bin-version": {
+                  "version": "1.0.4",
+                  "from": "bin-version@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-1.0.4.tgz",
                   "dependencies": {
-                    "deep-is": {
-                      "version": "0.1.3",
-                      "from": "deep-is@>=0.1.2 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
-                    },
-                    "fast-levenshtein": {
-                      "version": "1.0.7",
-                      "from": "fast-levenshtein@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
-                    },
-                    "levn": {
-                      "version": "0.2.5",
-                      "from": "levn@>=0.2.5 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
-                    },
-                    "prelude-ls": {
-                      "version": "1.1.2",
-                      "from": "prelude-ls@>=1.1.1 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
-                    },
-                    "type-check": {
-                      "version": "0.3.1",
-                      "from": "type-check@>=0.3.1 <0.4.0",
-                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
-                    },
-                    "wordwrap": {
-                      "version": "0.0.3",
-                      "from": "wordwrap@>=0.0.2 <0.1.0",
-                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+                    "find-versions": {
+                      "version": "1.2.1",
+                      "from": "find-versions@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-1.2.1.tgz",
+                      "dependencies": {
+                        "semver-regex": {
+                          "version": "1.0.0",
+                          "from": "semver-regex@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz"
+                        }
+                      }
                     }
                   }
                 },
-                "source-map": {
-                  "version": "0.2.0",
-                  "from": "source-map@>=0.2.0 <0.3.0",
-                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+                "semver": {
+                  "version": "4.3.6",
+                  "from": "semver@>=4.0.3 <5.0.0",
+                  "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
+                },
+                "semver-truncate": {
+                  "version": "1.1.0",
+                  "from": "semver-truncate@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.0.tgz",
                   "dependencies": {
-                    "amdefine": {
-                      "version": "1.0.0",
-                      "from": "amdefine@>=0.0.4",
-                      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+                    "semver": {
+                      "version": "5.1.0",
+                      "from": "semver@>=5.0.3 <6.0.0",
+                      "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz"
                     }
                   }
                 }
               }
             },
-            "esprima": {
-              "version": "2.5.0",
-              "from": "esprima@>=2.5.0 <2.6.0",
-              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz"
-            },
-            "fileset": {
-              "version": "0.2.1",
-              "from": "fileset@>=0.2.0 <0.3.0",
-              "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz",
+            "download": {
+              "version": "3.3.0",
+              "from": "download@>=3.3.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/download/-/download-3.3.0.tgz",
               "dependencies": {
-                "glob": {
-                  "version": "5.0.15",
-                  "from": "glob@>=5.0.0 <6.0.0",
-                  "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+                "concat-stream": {
+                  "version": "1.5.1",
+                  "from": "concat-stream@>=1.4.6 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz",
                   "dependencies": {
-                    "inflight": {
-                      "version": "1.0.4",
-                      "from": "inflight@>=1.0.4 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+                    "typedarray": {
+                      "version": "0.0.6",
+                      "from": "typedarray@>=0.0.5 <0.1.0",
+                      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+                    }
+                  }
+                },
+                "decompress-tar": {
+                  "version": "2.0.2",
+                  "from": "decompress-tar@>=2.0.1 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-2.0.2.tgz",
+                  "dependencies": {
+                    "is-tar": {
+                      "version": "1.0.0",
+                      "from": "is-tar@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz"
+                    },
+                    "strip-dirs": {
+                      "version": "0.1.1",
+                      "from": "strip-dirs@>=0.1.1 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-0.1.1.tgz",
                       "dependencies": {
-                        "wrappy": {
-                          "version": "1.0.1",
-                          "from": "wrappy@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                        "chalk": {
+                          "version": "0.5.1",
+                          "from": "chalk@>=0.5.1 <0.6.0",
+                          "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
+                          "dependencies": {
+                            "ansi-styles": {
+                              "version": "1.1.0",
+                              "from": "ansi-styles@>=1.1.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz"
+                            },
+                            "has-ansi": {
+                              "version": "0.1.0",
+                              "from": "has-ansi@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
+                              "dependencies": {
+                                "ansi-regex": {
+                                  "version": "0.2.1",
+                                  "from": "ansi-regex@>=0.2.0 <0.3.0",
+                                  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                                }
+                              }
+                            },
+                            "strip-ansi": {
+                              "version": "0.3.0",
+                              "from": "strip-ansi@>=0.3.0 <0.4.0",
+                              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
+                              "dependencies": {
+                                "ansi-regex": {
+                                  "version": "0.2.1",
+                                  "from": "ansi-regex@>=0.2.0 <0.3.0",
+                                  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                                }
+                              }
+                            },
+                            "supports-color": {
+                              "version": "0.2.0",
+                              "from": "supports-color@>=0.2.0 <0.3.0",
+                              "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz"
+                            }
+                          }
+                        },
+                        "is-absolute": {
+                          "version": "0.1.7",
+                          "from": "is-absolute@>=0.1.4 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                          "dependencies": {
+                            "is-relative": {
+                              "version": "0.1.3",
+                              "from": "is-relative@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                            }
+                          }
                         }
                       }
                     },
-                    "inherits": {
-                      "version": "2.0.1",
-                      "from": "inherits@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                    },
-                    "path-is-absolute": {
-                      "version": "1.0.0",
-                      "from": "path-is-absolute@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+                    "tar-stream": {
+                      "version": "0.4.7",
+                      "from": "tar-stream@>=0.4.5 <0.5.0",
+                      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-0.4.7.tgz",
+                      "dependencies": {
+                        "bl": {
+                          "version": "0.9.5",
+                          "from": "bl@>=0.9.0 <0.10.0",
+                          "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "1.0.33",
+                              "from": "readable-stream@>=1.0.26 <1.1.0",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+                            }
+                          }
+                        },
+                        "end-of-stream": {
+                          "version": "1.1.0",
+                          "from": "end-of-stream@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+                        },
+                        "readable-stream": {
+                          "version": "1.1.13",
+                          "from": "readable-stream@>=1.0.27-1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
+                        }
+                      }
                     }
                   }
                 },
-                "minimatch": {
-                  "version": "2.0.10",
-                  "from": "minimatch@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+                "decompress-tarbz2": {
+                  "version": "2.0.2",
+                  "from": "decompress-tarbz2@>=2.0.1 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-2.0.2.tgz",
                   "dependencies": {
-                    "brace-expansion": {
-                      "version": "1.1.1",
-                      "from": "brace-expansion@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
+                    "is-bzip2": {
+                      "version": "1.0.0",
+                      "from": "is-bzip2@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz"
+                    },
+                    "seek-bzip": {
+                      "version": "1.0.5",
+                      "from": "seek-bzip@>=1.0.3 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
                       "dependencies": {
-                        "balanced-match": {
-                          "version": "0.2.1",
-                          "from": "balanced-match@>=0.2.0 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
-                        },
-                        "concat-map": {
-                          "version": "0.0.1",
-                          "from": "concat-map@0.0.1",
-                          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+                        "commander": {
+                          "version": "2.8.1",
+                          "from": "commander@>=2.8.1 <2.9.0",
+                          "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz"
+                        }
+                      }
+                    },
+                    "strip-dirs": {
+                      "version": "0.1.1",
+                      "from": "strip-dirs@>=0.1.1 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-0.1.1.tgz",
+                      "dependencies": {
+                        "chalk": {
+                          "version": "0.5.1",
+                          "from": "chalk@>=0.5.1 <0.6.0",
+                          "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
+                          "dependencies": {
+                            "ansi-styles": {
+                              "version": "1.1.0",
+                              "from": "ansi-styles@>=1.1.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz"
+                            },
+                            "has-ansi": {
+                              "version": "0.1.0",
+                              "from": "has-ansi@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
+                              "dependencies": {
+                                "ansi-regex": {
+                                  "version": "0.2.1",
+                                  "from": "ansi-regex@>=0.2.0 <0.3.0",
+                                  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                                }
+                              }
+                            },
+                            "strip-ansi": {
+                              "version": "0.3.0",
+                              "from": "strip-ansi@>=0.3.0 <0.4.0",
+                              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
+                              "dependencies": {
+                                "ansi-regex": {
+                                  "version": "0.2.1",
+                                  "from": "ansi-regex@>=0.2.0 <0.3.0",
+                                  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                                }
+                              }
+                            },
+                            "supports-color": {
+                              "version": "0.2.0",
+                              "from": "supports-color@>=0.2.0 <0.3.0",
+                              "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz"
+                            }
+                          }
+                        },
+                        "is-absolute": {
+                          "version": "0.1.7",
+                          "from": "is-absolute@>=0.1.4 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                          "dependencies": {
+                            "is-relative": {
+                              "version": "0.1.3",
+                              "from": "is-relative@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "tar-stream": {
+                      "version": "0.4.7",
+                      "from": "tar-stream@>=0.4.5 <0.5.0",
+                      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-0.4.7.tgz",
+                      "dependencies": {
+                        "bl": {
+                          "version": "0.9.5",
+                          "from": "bl@>=0.9.0 <0.10.0",
+                          "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "1.0.33",
+                              "from": "readable-stream@>=1.0.26 <1.1.0",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+                            }
+                          }
+                        },
+                        "end-of-stream": {
+                          "version": "1.1.0",
+                          "from": "end-of-stream@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+                        },
+                        "readable-stream": {
+                          "version": "1.1.13",
+                          "from": "readable-stream@>=1.0.27-1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
                         }
                       }
                     }
                   }
-                }
-              }
-            },
-            "handlebars": {
-              "version": "4.0.4",
-              "from": "handlebars@>=4.0.1 <5.0.0",
-              "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.4.tgz",
-              "dependencies": {
-                "source-map": {
-                  "version": "0.4.4",
-                  "from": "source-map@>=0.4.4 <0.5.0"
-                }
-              }
-            },
-            "js-yaml": {
-              "version": "3.4.3",
-              "from": "js-yaml@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.3.tgz",
-              "dependencies": {
-                "argparse": {
-                  "version": "1.0.3",
-                  "from": "argparse@>=1.0.2 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.3.tgz",
+                },
+                "decompress-targz": {
+                  "version": "2.1.0",
+                  "from": "decompress-targz@>=2.0.1 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-2.1.0.tgz",
                   "dependencies": {
-                    "lodash": {
-                      "version": "3.10.1",
-                      "from": "lodash@>=3.2.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
+                    "is-gzip": {
+                      "version": "1.0.0",
+                      "from": "is-gzip@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz"
                     },
-                    "sprintf-js": {
-                      "version": "1.0.3",
-                      "from": "sprintf-js@>=1.0.2 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+                    "strip-dirs": {
+                      "version": "1.1.1",
+                      "from": "strip-dirs@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                      "dependencies": {
+                        "get-stdin": {
+                          "version": "4.0.1",
+                          "from": "get-stdin@>=4.0.1 <5.0.0",
+                          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
+                        },
+                        "is-absolute": {
+                          "version": "0.1.7",
+                          "from": "is-absolute@>=0.1.4 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                          "dependencies": {
+                            "is-relative": {
+                              "version": "0.1.3",
+                              "from": "is-relative@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                            }
+                          }
+                        },
+                        "is-natural-number": {
+                          "version": "2.0.0",
+                          "from": "is-natural-number@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
+                        },
+                        "sum-up": {
+                          "version": "1.0.2",
+                          "from": "sum-up@>=1.0.1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
+                        }
+                      }
+                    },
+                    "tar-stream": {
+                      "version": "1.3.2",
+                      "from": "tar-stream@>=1.1.1 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.3.2.tgz",
+                      "dependencies": {
+                        "bl": {
+                          "version": "1.1.2",
+                          "from": "bl@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "2.0.5",
+                              "from": "readable-stream@>=2.0.5 <2.1.0",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz",
+                              "dependencies": {
+                                "process-nextick-args": {
+                                  "version": "1.0.6",
+                                  "from": "process-nextick-args@>=1.0.6 <1.1.0",
+                                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
+                                }
+                              }
+                            }
+                          }
+                        },
+                        "end-of-stream": {
+                          "version": "1.1.0",
+                          "from": "end-of-stream@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+                        }
+                      }
                     }
                   }
                 },
-                "esprima": {
-                  "version": "2.7.0",
-                  "from": "esprima@>=2.6.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.0.tgz"
-                }
-              }
-            },
-            "nopt": {
-              "version": "3.0.4",
-              "from": "nopt@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz"
-            },
-            "once": {
-              "version": "1.3.2",
-              "from": "once@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-              "dependencies": {
-                "wrappy": {
-                  "version": "1.0.1",
-                  "from": "wrappy@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                }
-              }
-            },
-            "supports-color": {
-              "version": "3.1.2",
-              "from": "supports-color@>=3.1.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
-              "dependencies": {
-                "has-flag": {
-                  "version": "1.0.0",
-                  "from": "has-flag@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
-                }
-              }
-            }
-          }
-        },
-        "jasmine-pit": {
-          "version": "2.0.2",
-          "from": "jasmine-pit@>=2.0.2 <3.0.0",
-          "resolved": "https://registry.npmjs.org/jasmine-pit/-/jasmine-pit-2.0.2.tgz"
-        },
-        "jsdom": {
-          "version": "7.0.2",
-          "from": "jsdom@7.0.2",
-          "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.0.2.tgz",
-          "dependencies": {
-            "abab": {
-              "version": "1.0.0",
-              "from": "abab@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.0.tgz"
-            },
-            "acorn": {
-              "version": "2.5.2",
-              "from": "acorn@>=2.4.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.5.2.tgz"
-            },
-            "acorn-globals": {
-              "version": "1.0.9",
-              "from": "acorn-globals@>=1.0.4 <2.0.0",
-              "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz"
-            },
-            "browser-request": {
-              "version": "0.3.3",
-              "from": "browser-request@>=0.3.1 <0.4.0",
-              "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz"
-            },
-            "cssom": {
-              "version": "0.3.0",
-              "from": "cssom@>=0.3.0 <0.4.0",
-              "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.0.tgz"
-            },
-            "cssstyle": {
-              "version": "0.2.30",
-              "from": "cssstyle@>=0.2.29 <0.3.0",
-              "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.30.tgz"
-            },
-            "escodegen": {
-              "version": "1.7.0",
-              "from": "escodegen@>=1.6.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.0.tgz",
-              "dependencies": {
-                "esprima": {
-                  "version": "1.2.5",
-                  "from": "esprima@>=1.2.2 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
-                },
-                "estraverse": {
-                  "version": "1.9.3",
-                  "from": "estraverse@>=1.9.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
-                },
-                "esutils": {
-                  "version": "2.0.2",
-                  "from": "esutils@>=2.0.2 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
-                },
-                "optionator": {
-                  "version": "0.5.0",
-                  "from": "optionator@>=0.5.0 <0.6.0",
-                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+                "decompress-unzip": {
+                  "version": "2.1.2",
+                  "from": "decompress-unzip@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-2.1.2.tgz",
                   "dependencies": {
-                    "deep-is": {
-                      "version": "0.1.3",
-                      "from": "deep-is@>=0.1.2 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
-                    },
-                    "fast-levenshtein": {
-                      "version": "1.0.7",
-                      "from": "fast-levenshtein@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
-                    },
-                    "levn": {
-                      "version": "0.2.5",
-                      "from": "levn@>=0.2.5 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
-                    },
-                    "prelude-ls": {
-                      "version": "1.1.2",
-                      "from": "prelude-ls@>=1.1.1 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+                    "is-zip": {
+                      "version": "1.0.0",
+                      "from": "is-zip@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz"
                     },
-                    "type-check": {
-                      "version": "0.3.1",
-                      "from": "type-check@>=0.3.1 <0.4.0",
-                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
+                    "strip-dirs": {
+                      "version": "1.1.1",
+                      "from": "strip-dirs@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                      "dependencies": {
+                        "get-stdin": {
+                          "version": "4.0.1",
+                          "from": "get-stdin@>=4.0.1 <5.0.0",
+                          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
+                        },
+                        "is-absolute": {
+                          "version": "0.1.7",
+                          "from": "is-absolute@>=0.1.5 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                          "dependencies": {
+                            "is-relative": {
+                              "version": "0.1.3",
+                              "from": "is-relative@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                            }
+                          }
+                        },
+                        "is-natural-number": {
+                          "version": "2.0.0",
+                          "from": "is-natural-number@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
+                        },
+                        "sum-up": {
+                          "version": "1.0.2",
+                          "from": "sum-up@>=1.0.1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
+                        }
+                      }
                     },
-                    "wordwrap": {
-                      "version": "0.0.3",
-                      "from": "wordwrap@>=0.0.2 <0.1.0",
-                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+                    "yauzl": {
+                      "version": "2.4.1",
+                      "from": "yauzl@>=2.2.1 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+                      "dependencies": {
+                        "fd-slicer": {
+                          "version": "1.0.1",
+                          "from": "fd-slicer@>=1.0.1 <1.1.0",
+                          "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+                          "dependencies": {
+                            "pend": {
+                              "version": "1.2.0",
+                              "from": "pend@>=1.2.0 <1.3.0",
+                              "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
+                            }
+                          }
+                        }
+                      }
                     }
                   }
                 },
-                "source-map": {
-                  "version": "0.2.0",
-                  "from": "source-map@>=0.2.0 <0.3.0",
-                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+                "each-async": {
+                  "version": "1.1.1",
+                  "from": "each-async@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz",
                   "dependencies": {
-                    "amdefine": {
-                      "version": "1.0.0",
-                      "from": "amdefine@>=0.0.4",
-                      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+                    "onetime": {
+                      "version": "1.1.0",
+                      "from": "onetime@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
+                    },
+                    "set-immediate-shim": {
+                      "version": "1.0.1",
+                      "from": "set-immediate-shim@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz"
                     }
                   }
-                }
-              }
-            },
-            "htmlparser2": {
-              "version": "3.8.3",
-              "from": "htmlparser2@>=3.7.3 <4.0.0",
-              "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
-              "dependencies": {
-                "domelementtype": {
-                  "version": "1.3.0",
-                  "from": "domelementtype@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
-                },
-                "domhandler": {
-                  "version": "2.3.0",
-                  "from": "domhandler@>=2.3.0 <2.4.0",
-                  "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
                 },
-                "domutils": {
-                  "version": "1.5.1",
-                  "from": "domutils@>=1.5.0 <1.6.0",
-                  "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+                "get-stdin": {
+                  "version": "3.0.2",
+                  "from": "get-stdin@>=3.0.0 <4.0.0",
+                  "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz"
+                },
+                "gulp-rename": {
+                  "version": "1.2.2",
+                  "from": "gulp-rename@>=1.2.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz"
+                },
+                "meow": {
+                  "version": "2.1.0",
+                  "from": "meow@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/meow/-/meow-2.1.0.tgz",
                   "dependencies": {
-                    "dom-serializer": {
-                      "version": "0.1.0",
-                      "from": "dom-serializer@>=0.0.0 <1.0.0",
-                      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+                    "indent-string": {
+                      "version": "1.2.2",
+                      "from": "indent-string@>=1.1.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz",
                       "dependencies": {
-                        "domelementtype": {
-                          "version": "1.1.3",
-                          "from": "domelementtype@>=1.1.1 <1.2.0",
-                          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
-                        },
-                        "entities": {
-                          "version": "1.1.1",
-                          "from": "entities@>=1.1.1 <1.2.0",
-                          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
+                        "get-stdin": {
+                          "version": "4.0.1",
+                          "from": "get-stdin@>=4.0.1 <5.0.0",
+                          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
                         }
                       }
+                    },
+                    "object-assign": {
+                      "version": "2.1.1",
+                      "from": "object-assign@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
                     }
                   }
                 },
-                "entities": {
-                  "version": "1.0.0",
-                  "from": "entities@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz"
-                },
-                "readable-stream": {
-                  "version": "1.1.13",
-                  "from": "readable-stream@>=1.1.0 <1.2.0",
-                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
+                "rc": {
+                  "version": "0.5.5",
+                  "from": "rc@>=0.5.1 <0.6.0",
+                  "resolved": "https://registry.npmjs.org/rc/-/rc-0.5.5.tgz",
                   "dependencies": {
-                    "core-util-is": {
-                      "version": "1.0.1",
-                      "from": "core-util-is@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+                    "minimist": {
+                      "version": "0.0.10",
+                      "from": "minimist@>=0.0.7 <0.1.0",
+                      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
                     },
-                    "inherits": {
-                      "version": "2.0.1",
-                      "from": "inherits@>=2.0.1 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                    "deep-extend": {
+                      "version": "0.2.11",
+                      "from": "deep-extend@>=0.2.5 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
                     },
-                    "isarray": {
-                      "version": "0.0.1",
-                      "from": "isarray@0.0.1",
-                      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                    "strip-json-comments": {
+                      "version": "0.1.3",
+                      "from": "strip-json-comments@>=0.1.0 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
                     },
-                    "string_decoder": {
-                      "version": "0.10.31",
-                      "from": "string_decoder@>=0.10.0 <0.11.0",
-                      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                    "ini": {
+                      "version": "1.3.4",
+                      "from": "ini@>=1.3.0 <1.4.0",
+                      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
                     }
                   }
-                }
-              }
-            },
-            "nwmatcher": {
-              "version": "1.3.6",
-              "from": "nwmatcher@>=1.3.6 <2.0.0",
-              "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.6.tgz"
-            },
-            "parse5": {
-              "version": "1.5.0",
-              "from": "parse5@>=1.4.2 <2.0.0",
-              "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.0.tgz"
-            },
-            "request": {
-              "version": "2.65.0",
-              "from": "request@>=2.55.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/request/-/request-2.65.0.tgz",
-              "dependencies": {
-                "aws-sign2": {
-                  "version": "0.6.0",
-                  "from": "aws-sign2@>=0.6.0 <0.7.0",
-                  "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
                 },
-                "bl": {
-                  "version": "1.0.0",
-                  "from": "bl@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+                "request": {
+                  "version": "2.69.0",
+                  "from": "request@>=2.34.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz",
                   "dependencies": {
-                    "readable-stream": {
-                      "version": "2.0.4",
-                      "from": "readable-stream@>=2.0.0 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
+                    "aws-sign2": {
+                      "version": "0.6.0",
+                      "from": "aws-sign2@>=0.6.0 <0.7.0",
+                      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
+                    },
+                    "aws4": {
+                      "version": "1.3.2",
+                      "from": "aws4@>=1.2.1 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz",
                       "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+                        "lru-cache": {
+                          "version": "4.0.0",
+                          "from": "lru-cache@>=4.0.0 <5.0.0",
+                          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.0.tgz",
+                          "dependencies": {
+                            "pseudomap": {
+                              "version": "1.0.2",
+                              "from": "pseudomap@>=1.0.1 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"
+                            },
+                            "yallist": {
+                              "version": "2.0.0",
+                              "from": "yallist@>=2.0.0 <3.0.0",
+                              "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz"
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "bl": {
+                      "version": "1.0.3",
+                      "from": "bl@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz",
+                      "dependencies": {
+                        "readable-stream": {
+                          "version": "2.0.5",
+                          "from": "readable-stream@>=2.0.5 <2.1.0",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz",
+                          "dependencies": {
+                            "process-nextick-args": {
+                              "version": "1.0.6",
+                              "from": "process-nextick-args@>=1.0.6 <1.1.0",
+                              "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "caseless": {
+                      "version": "0.11.0",
+                      "from": "caseless@>=0.11.0 <0.12.0",
+                      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
+                    },
+                    "combined-stream": {
+                      "version": "1.0.5",
+                      "from": "combined-stream@>=1.0.5 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+                      "dependencies": {
+                        "delayed-stream": {
+                          "version": "1.0.0",
+                          "from": "delayed-stream@>=1.0.0 <1.1.0",
+                          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+                        }
+                      }
+                    },
+                    "extend": {
+                      "version": "3.0.0",
+                      "from": "extend@>=3.0.0 <3.1.0",
+                      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+                    },
+                    "forever-agent": {
+                      "version": "0.6.1",
+                      "from": "forever-agent@>=0.6.1 <0.7.0",
+                      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+                    },
+                    "form-data": {
+                      "version": "1.0.0-rc3",
+                      "from": "form-data@>=1.0.0-rc3 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
+                      "dependencies": {
+                        "async": {
+                          "version": "1.5.2",
+                          "from": "async@>=1.4.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
+                        }
+                      }
+                    },
+                    "har-validator": {
+                      "version": "2.0.6",
+                      "from": "har-validator@>=2.0.6 <2.1.0",
+                      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+                      "dependencies": {
+                        "is-my-json-valid": {
+                          "version": "2.13.1",
+                          "from": "is-my-json-valid@>=2.12.4 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz",
+                          "dependencies": {
+                            "generate-function": {
+                              "version": "2.0.0",
+                              "from": "generate-function@>=2.0.0 <3.0.0",
+                              "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+                            },
+                            "generate-object-property": {
+                              "version": "1.2.0",
+                              "from": "generate-object-property@>=1.1.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+                              "dependencies": {
+                                "is-property": {
+                                  "version": "1.0.2",
+                                  "from": "is-property@>=1.0.0 <2.0.0",
+                                  "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+                                }
+                              }
+                            },
+                            "jsonpointer": {
+                              "version": "2.0.0",
+                              "from": "jsonpointer@2.0.0",
+                              "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
+                            }
+                          }
                         },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                        "pinkie-promise": {
+                          "version": "2.0.0",
+                          "from": "pinkie-promise@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
+                          "dependencies": {
+                            "pinkie": {
+                              "version": "2.0.4",
+                              "from": "pinkie@>=2.0.0 <3.0.0",
+                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "hawk": {
+                      "version": "3.1.3",
+                      "from": "hawk@>=3.1.0 <3.2.0",
+                      "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+                      "dependencies": {
+                        "hoek": {
+                          "version": "2.16.3",
+                          "from": "hoek@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
                         },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                        "boom": {
+                          "version": "2.10.1",
+                          "from": "boom@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
                         },
-                        "process-nextick-args": {
-                          "version": "1.0.3",
-                          "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+                        "cryptiles": {
+                          "version": "2.0.5",
+                          "from": "cryptiles@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
+                        },
+                        "sntp": {
+                          "version": "1.0.9",
+                          "from": "sntp@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+                        }
+                      }
+                    },
+                    "http-signature": {
+                      "version": "1.1.1",
+                      "from": "http-signature@>=1.1.0 <1.2.0",
+                      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+                      "dependencies": {
+                        "assert-plus": {
+                          "version": "0.2.0",
+                          "from": "assert-plus@>=0.2.0 <0.3.0",
+                          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
                         },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                        "jsprim": {
+                          "version": "1.2.2",
+                          "from": "jsprim@>=1.2.2 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz",
+                          "dependencies": {
+                            "extsprintf": {
+                              "version": "1.0.2",
+                              "from": "extsprintf@1.0.2",
+                              "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
+                            },
+                            "json-schema": {
+                              "version": "0.2.2",
+                              "from": "json-schema@0.2.2",
+                              "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
+                            },
+                            "verror": {
+                              "version": "1.3.6",
+                              "from": "verror@1.3.6",
+                              "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
+                            }
+                          }
                         },
-                        "util-deprecate": {
-                          "version": "1.0.2",
-                          "from": "util-deprecate@>=1.0.1 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                        "sshpk": {
+                          "version": "1.7.4",
+                          "from": "sshpk@>=1.7.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz",
+                          "dependencies": {
+                            "asn1": {
+                              "version": "0.2.3",
+                              "from": "asn1@>=0.2.3 <0.3.0",
+                              "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
+                            },
+                            "dashdash": {
+                              "version": "1.13.0",
+                              "from": "dashdash@>=1.10.1 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz",
+                              "dependencies": {
+                                "assert-plus": {
+                                  "version": "1.0.0",
+                                  "from": "assert-plus@>=1.0.0 <2.0.0",
+                                  "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
+                                }
+                              }
+                            },
+                            "jsbn": {
+                              "version": "0.1.0",
+                              "from": "jsbn@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
+                            },
+                            "tweetnacl": {
+                              "version": "0.14.1",
+                              "from": "tweetnacl@>=0.13.0 <1.0.0",
+                              "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.1.tgz"
+                            },
+                            "jodid25519": {
+                              "version": "1.0.2",
+                              "from": "jodid25519@>=1.0.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
+                            },
+                            "ecc-jsbn": {
+                              "version": "0.1.1",
+                              "from": "ecc-jsbn@>=0.0.1 <1.0.0",
+                              "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "is-typedarray": {
+                      "version": "1.0.0",
+                      "from": "is-typedarray@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
+                    },
+                    "isstream": {
+                      "version": "0.1.2",
+                      "from": "isstream@>=0.1.2 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
+                    },
+                    "json-stringify-safe": {
+                      "version": "5.0.1",
+                      "from": "json-stringify-safe@>=5.0.1 <5.1.0",
+                      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
+                    },
+                    "mime-types": {
+                      "version": "2.1.10",
+                      "from": "mime-types@>=2.1.7 <2.2.0",
+                      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
+                      "dependencies": {
+                        "mime-db": {
+                          "version": "1.22.0",
+                          "from": "mime-db@>=1.22.0 <1.23.0",
+                          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
                         }
                       }
+                    },
+                    "node-uuid": {
+                      "version": "1.4.7",
+                      "from": "node-uuid@>=1.4.7 <1.5.0",
+                      "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
+                    },
+                    "oauth-sign": {
+                      "version": "0.8.1",
+                      "from": "oauth-sign@>=0.8.0 <0.9.0",
+                      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz"
+                    },
+                    "qs": {
+                      "version": "6.0.2",
+                      "from": "qs@>=6.0.2 <6.1.0",
+                      "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.2.tgz"
+                    },
+                    "stringstream": {
+                      "version": "0.0.5",
+                      "from": "stringstream@>=0.0.4 <0.1.0",
+                      "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
+                    },
+                    "tough-cookie": {
+                      "version": "2.2.1",
+                      "from": "tough-cookie@>=2.2.0 <2.3.0",
+                      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.1.tgz"
+                    },
+                    "tunnel-agent": {
+                      "version": "0.4.2",
+                      "from": "tunnel-agent@>=0.4.1 <0.5.0",
+                      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz"
                     }
                   }
                 },
-                "caseless": {
-                  "version": "0.11.0",
-                  "from": "caseless@>=0.11.0 <0.12.0",
-                  "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
-                },
-                "combined-stream": {
-                  "version": "1.0.5",
-                  "from": "combined-stream@>=1.0.5 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
+                "stream-combiner": {
+                  "version": "0.2.2",
+                  "from": "stream-combiner@>=0.2.1 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
                   "dependencies": {
-                    "delayed-stream": {
-                      "version": "1.0.0",
-                      "from": "delayed-stream@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+                    "duplexer": {
+                      "version": "0.1.1",
+                      "from": "duplexer@>=0.1.1 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz"
                     }
                   }
                 },
-                "extend": {
-                  "version": "3.0.0",
-                  "from": "extend@>=3.0.0 <3.1.0",
-                  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+                "through2": {
+                  "version": "0.6.5",
+                  "from": "through2@>=0.6.1 <0.7.0",
+                  "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                  "dependencies": {
+                    "readable-stream": {
+                      "version": "1.0.33",
+                      "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+                    }
+                  }
                 },
-                "forever-agent": {
-                  "version": "0.6.1",
-                  "from": "forever-agent@>=0.6.1 <0.7.0",
-                  "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+                "url-regex": {
+                  "version": "2.1.3",
+                  "from": "url-regex@>=2.0.2 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-2.1.3.tgz",
+                  "dependencies": {
+                    "ip-regex": {
+                      "version": "1.0.3",
+                      "from": "ip-regex@>=1.0.1 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz"
+                    }
+                  }
                 },
-                "form-data": {
-                  "version": "1.0.0-rc3",
-                  "from": "form-data@>=1.0.0-rc3 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
+                "vinyl": {
+                  "version": "0.4.6",
+                  "from": "vinyl@>=0.4.3 <0.5.0",
+                  "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
                   "dependencies": {
-                    "async": {
-                      "version": "1.5.0",
-                      "from": "async@>=1.4.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
+                    "clone": {
+                      "version": "0.2.0",
+                      "from": "clone@>=0.2.0 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
                     }
                   }
                 },
-                "har-validator": {
-                  "version": "2.0.2",
-                  "from": "har-validator@>=2.0.2 <2.1.0",
-                  "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.2.tgz",
+                "vinyl-fs": {
+                  "version": "0.3.14",
+                  "from": "vinyl-fs@>=0.3.7 <0.4.0",
+                  "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz",
                   "dependencies": {
-                    "chalk": {
-                      "version": "1.1.1",
-                      "from": "chalk@>=1.1.1 <2.0.0"
+                    "defaults": {
+                      "version": "1.0.3",
+                      "from": "defaults@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz"
                     },
-                    "commander": {
-                      "version": "2.9.0",
-                      "from": "commander@>=2.8.1 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
+                    "glob-stream": {
+                      "version": "3.1.18",
+                      "from": "glob-stream@>=3.1.5 <4.0.0",
+                      "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz",
                       "dependencies": {
-                        "graceful-readlink": {
-                          "version": "1.0.1",
-                          "from": "graceful-readlink@>=1.0.0",
-                          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+                        "glob": {
+                          "version": "4.5.3",
+                          "from": "glob@>=4.3.1 <5.0.0",
+                          "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz"
+                        },
+                        "ordered-read-streams": {
+                          "version": "0.1.0",
+                          "from": "ordered-read-streams@>=0.1.0 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz"
+                        },
+                        "glob2base": {
+                          "version": "0.0.12",
+                          "from": "glob2base@>=0.0.12 <0.0.13",
+                          "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz",
+                          "dependencies": {
+                            "find-index": {
+                              "version": "0.1.1",
+                              "from": "find-index@>=0.1.1 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz"
+                            }
+                          }
+                        },
+                        "unique-stream": {
+                          "version": "1.0.0",
+                          "from": "unique-stream@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz"
                         }
                       }
                     },
-                    "is-my-json-valid": {
-                      "version": "2.12.2",
-                      "from": "is-my-json-valid@>=2.12.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz",
+                    "glob-watcher": {
+                      "version": "0.0.6",
+                      "from": "glob-watcher@>=0.0.6 <0.0.7",
+                      "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz",
                       "dependencies": {
-                        "generate-function": {
-                          "version": "2.0.0",
-                          "from": "generate-function@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
-                        },
-                        "generate-object-property": {
-                          "version": "1.2.0",
-                          "from": "generate-object-property@>=1.1.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+                        "gaze": {
+                          "version": "0.5.2",
+                          "from": "gaze@>=0.5.1 <0.6.0",
+                          "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
                           "dependencies": {
-                            "is-property": {
-                              "version": "1.0.2",
-                              "from": "is-property@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+                            "globule": {
+                              "version": "0.1.0",
+                              "from": "globule@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
+                              "dependencies": {
+                                "lodash": {
+                                  "version": "1.0.2",
+                                  "from": "lodash@>=1.0.1 <1.1.0",
+                                  "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz"
+                                },
+                                "glob": {
+                                  "version": "3.1.21",
+                                  "from": "glob@>=3.1.21 <3.2.0",
+                                  "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
+                                  "dependencies": {
+                                    "graceful-fs": {
+                                      "version": "1.2.3",
+                                      "from": "graceful-fs@>=1.2.0 <1.3.0",
+                                      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz"
+                                    },
+                                    "inherits": {
+                                      "version": "1.0.2",
+                                      "from": "inherits@>=1.0.0 <2.0.0",
+                                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz"
+                                    }
+                                  }
+                                },
+                                "minimatch": {
+                                  "version": "0.2.14",
+                                  "from": "minimatch@>=0.2.11 <0.3.0",
+                                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
+                                  "dependencies": {
+                                    "lru-cache": {
+                                      "version": "2.7.3",
+                                      "from": "lru-cache@>=2.0.0 <3.0.0",
+                                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
+                                    },
+                                    "sigmund": {
+                                      "version": "1.0.1",
+                                      "from": "sigmund@>=1.0.0 <1.1.0",
+                                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
+                                    }
+                                  }
+                                }
+                              }
                             }
                           }
-                        },
-                        "jsonpointer": {
-                          "version": "2.0.0",
-                          "from": "jsonpointer@2.0.0",
-                          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
-                        },
-                        "xtend": {
-                          "version": "4.0.1",
-                          "from": "xtend@>=4.0.0 <5.0.0",
-                          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
                         }
                       }
                     },
-                    "pinkie-promise": {
+                    "graceful-fs": {
+                      "version": "3.0.8",
+                      "from": "graceful-fs@>=3.0.0 <4.0.0",
+                      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
+                    },
+                    "strip-bom": {
                       "version": "1.0.0",
-                      "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
+                      "from": "strip-bom@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz",
                       "dependencies": {
-                        "pinkie": {
+                        "first-chunk-stream": {
                           "version": "1.0.0",
-                          "from": "pinkie@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
+                          "from": "first-chunk-stream@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz"
                         }
                       }
                     }
                   }
                 },
-                "hawk": {
-                  "version": "3.1.0",
-                  "from": "hawk@>=3.1.0 <3.2.0",
-                  "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz",
+                "ware": {
+                  "version": "1.3.0",
+                  "from": "ware@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz",
                   "dependencies": {
-                    "boom": {
-                      "version": "2.10.1",
-                      "from": "boom@>=2.8.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
-                    },
-                    "cryptiles": {
-                      "version": "2.0.5",
-                      "from": "cryptiles@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
-                    },
-                    "hoek": {
-                      "version": "2.16.3",
-                      "from": "hoek@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
-                    },
-                    "sntp": {
-                      "version": "1.0.9",
-                      "from": "sntp@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+                    "wrap-fn": {
+                      "version": "0.1.5",
+                      "from": "wrap-fn@>=0.1.0 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz",
+                      "dependencies": {
+                        "co": {
+                          "version": "3.1.0",
+                          "from": "co@3.1.0",
+                          "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz"
+                        }
+                      }
                     }
                   }
-                },
-                "http-signature": {
-                  "version": "0.11.0",
-                  "from": "http-signature@>=0.11.0 <0.12.0",
-                  "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz",
-                  "dependencies": {
-                    "asn1": {
-                      "version": "0.1.11",
-                      "from": "asn1@0.1.11",
-                      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
-                    },
-                    "assert-plus": {
-                      "version": "0.1.5",
-                      "from": "assert-plus@>=0.1.5 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
+                }
+              }
+            },
+            "download-status": {
+              "version": "2.2.1",
+              "from": "download-status@>=2.0.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/download-status/-/download-status-2.2.1.tgz",
+              "dependencies": {
+                "chalk": {
+                  "version": "0.5.1",
+                  "from": "chalk@>=0.5.1 <0.6.0",
+                  "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz",
+                  "dependencies": {
+                    "ansi-styles": {
+                      "version": "1.1.0",
+                      "from": "ansi-styles@>=1.1.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz"
                     },
-                    "ctype": {
-                      "version": "0.5.3",
-                      "from": "ctype@0.5.3",
-                      "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
+                    "has-ansi": {
+                      "version": "0.1.0",
+                      "from": "has-ansi@>=0.1.0 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz",
+                      "dependencies": {
+                        "ansi-regex": {
+                          "version": "0.2.1",
+                          "from": "ansi-regex@>=0.2.0 <0.3.0",
+                          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                        }
+                      }
+                    },
+                    "strip-ansi": {
+                      "version": "0.3.0",
+                      "from": "strip-ansi@>=0.3.0 <0.4.0",
+                      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz",
+                      "dependencies": {
+                        "ansi-regex": {
+                          "version": "0.2.1",
+                          "from": "ansi-regex@>=0.2.0 <0.3.0",
+                          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz"
+                        }
+                      }
+                    },
+                    "supports-color": {
+                      "version": "0.2.0",
+                      "from": "supports-color@>=0.2.0 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz"
                     }
                   }
                 },
-                "isstream": {
-                  "version": "0.1.2",
-                  "from": "isstream@>=0.1.2 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
-                },
-                "json-stringify-safe": {
-                  "version": "5.0.1",
-                  "from": "json-stringify-safe@>=5.0.1 <5.1.0",
-                  "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
-                },
-                "mime-types": {
-                  "version": "2.1.7",
-                  "from": "mime-types@>=2.1.7 <2.2.0",
-                  "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz",
+                "lpad-align": {
+                  "version": "1.1.0",
+                  "from": "lpad-align@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.0.tgz",
                   "dependencies": {
-                    "mime-db": {
-                      "version": "1.19.0",
-                      "from": "mime-db@>=1.19.0 <1.20.0",
-                      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz"
+                    "lpad": {
+                      "version": "2.0.1",
+                      "from": "lpad@>=2.0.1 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/lpad/-/lpad-2.0.1.tgz"
                     }
                   }
                 },
-                "node-uuid": {
-                  "version": "1.4.3",
-                  "from": "node-uuid@>=1.4.3 <1.5.0",
-                  "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
-                },
-                "oauth-sign": {
-                  "version": "0.8.0",
-                  "from": "oauth-sign@>=0.8.0 <0.9.0",
-                  "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz"
+                "object-assign": {
+                  "version": "2.1.1",
+                  "from": "object-assign@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                }
+              }
+            },
+            "globby": {
+              "version": "1.2.0",
+              "from": "globby@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/globby/-/globby-1.2.0.tgz",
+              "dependencies": {
+                "array-union": {
+                  "version": "1.0.1",
+                  "from": "array-union@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz"
                 },
-                "qs": {
-                  "version": "5.2.0",
-                  "from": "qs@>=5.2.0 <5.3.0",
-                  "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
+                "async": {
+                  "version": "0.9.2",
+                  "from": "async@>=0.9.0 <0.10.0",
+                  "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
                 },
-                "stringstream": {
-                  "version": "0.0.5",
-                  "from": "stringstream@>=0.0.4 <0.1.0",
-                  "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
+                "glob": {
+                  "version": "4.5.3",
+                  "from": "glob@>=4.4.0 <5.0.0",
+                  "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz"
                 },
-                "tunnel-agent": {
-                  "version": "0.4.1",
-                  "from": "tunnel-agent@>=0.4.1 <0.5.0",
-                  "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
+                "object-assign": {
+                  "version": "2.1.1",
+                  "from": "object-assign@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
                 }
               }
             },
-            "symbol-tree": {
-              "version": "3.1.3",
-              "from": "symbol-tree@>=3.1.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.3.tgz"
-            },
-            "tough-cookie": {
-              "version": "2.2.0",
-              "from": "tough-cookie@>=2.2.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.0.tgz"
-            },
-            "webidl-conversions": {
-              "version": "2.0.0",
-              "from": "webidl-conversions@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.0.tgz"
-            },
-            "whatwg-url-compat": {
-              "version": "0.6.5",
-              "from": "whatwg-url-compat@>=0.6.5 <0.7.0",
-              "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz",
+            "is-path-global": {
+              "version": "1.0.2",
+              "from": "is-path-global@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/is-path-global/-/is-path-global-1.0.2.tgz",
               "dependencies": {
-                "tr46": {
-                  "version": "0.0.2",
-                  "from": "tr46@>=0.0.1 <0.1.0",
-                  "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.2.tgz"
+                "is-path-inside": {
+                  "version": "1.0.0",
+                  "from": "is-path-inside@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz",
+                  "dependencies": {
+                    "path-is-inside": {
+                      "version": "1.0.1",
+                      "from": "path-is-inside@>=1.0.1 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz"
+                    }
+                  }
                 }
               }
             },
-            "xml-name-validator": {
-              "version": "2.0.1",
-              "from": "xml-name-validator@>=2.0.1 <3.0.0",
-              "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz"
-            }
-          }
-        },
-        "json-stable-stringify": {
-          "version": "1.0.0",
-          "from": "json-stable-stringify@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz",
-          "dependencies": {
-            "jsonify": {
-              "version": "0.0.0",
-              "from": "jsonify@>=0.0.0 <0.1.0",
-              "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
-            }
-          }
-        },
-        "lodash.template": {
-          "version": "3.6.2",
-          "from": "lodash.template@>=3.6.2 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz",
-          "dependencies": {
-            "lodash._basecopy": {
-              "version": "3.0.1",
-              "from": "lodash._basecopy@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
-            },
-            "lodash._basetostring": {
-              "version": "3.0.1",
-              "from": "lodash._basetostring@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
-            },
-            "lodash._basevalues": {
-              "version": "3.0.0",
-              "from": "lodash._basevalues@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz"
-            },
-            "lodash._isiterateecall": {
-              "version": "3.0.9",
-              "from": "lodash._isiterateecall@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
-            },
-            "lodash._reinterpolate": {
-              "version": "3.0.0",
-              "from": "lodash._reinterpolate@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz"
-            },
-            "lodash.escape": {
-              "version": "3.0.0",
-              "from": "lodash.escape@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.0.0.tgz"
-            },
-            "lodash.keys": {
-              "version": "3.1.2",
-              "from": "lodash.keys@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
+            "lnfs": {
+              "version": "1.1.0",
+              "from": "lnfs@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/lnfs/-/lnfs-1.1.0.tgz",
               "dependencies": {
-                "lodash._getnative": {
-                  "version": "3.9.1",
-                  "from": "lodash._getnative@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-                },
-                "lodash.isarguments": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-                },
-                "lodash.isarray": {
-                  "version": "3.0.4",
-                  "from": "lodash.isarray@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
+                "rimraf": {
+                  "version": "2.5.2",
+                  "from": "rimraf@>=2.2.8 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz",
+                  "dependencies": {
+                    "glob": {
+                      "version": "7.0.0",
+                      "from": "glob@>=7.0.0 <8.0.0",
+                      "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.0.tgz"
+                    }
+                  }
                 }
               }
             },
-            "lodash.restparam": {
-              "version": "3.6.1",
-              "from": "lodash.restparam@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
+            "npm-installed": {
+              "version": "1.0.0",
+              "from": "npm-installed@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/npm-installed/-/npm-installed-1.0.0.tgz",
+              "dependencies": {
+                "npm-which": {
+                  "version": "1.0.2",
+                  "from": "npm-which@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-1.0.2.tgz",
+                  "dependencies": {
+                    "npm-path": {
+                      "version": "1.1.0",
+                      "from": "npm-path@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-1.1.0.tgz"
+                    },
+                    "which": {
+                      "version": "1.2.4",
+                      "from": "which@>=1.0.5 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz",
+                      "dependencies": {
+                        "is-absolute": {
+                          "version": "0.1.7",
+                          "from": "is-absolute@>=0.1.7 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                          "dependencies": {
+                            "is-relative": {
+                              "version": "0.1.3",
+                              "from": "is-relative@>=0.1.0 <0.2.0",
+                              "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                            }
+                          }
+                        },
+                        "isexe": {
+                          "version": "1.1.2",
+                          "from": "isexe@>=1.1.1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz"
+                        }
+                      }
+                    }
+                  }
+                },
+                "rc": {
+                  "version": "0.5.5",
+                  "from": "rc@>=0.5.1 <0.6.0",
+                  "resolved": "https://registry.npmjs.org/rc/-/rc-0.5.5.tgz",
+                  "dependencies": {
+                    "minimist": {
+                      "version": "0.0.10",
+                      "from": "minimist@>=0.0.7 <0.1.0",
+                      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
+                    },
+                    "deep-extend": {
+                      "version": "0.2.11",
+                      "from": "deep-extend@>=0.2.5 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
+                    },
+                    "strip-json-comments": {
+                      "version": "0.1.3",
+                      "from": "strip-json-comments@>=0.1.0 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
+                    },
+                    "ini": {
+                      "version": "1.3.4",
+                      "from": "ini@>=1.3.0 <1.4.0",
+                      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
+                    }
+                  }
+                }
+              }
             },
-            "lodash.templatesettings": {
-              "version": "3.1.0",
-              "from": "lodash.templatesettings@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.0.tgz"
-            }
-          }
-        },
-        "mkdirp": {
-          "version": "0.5.1",
-          "from": "mkdirp@>=0.5.1 <0.6.0",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "dependencies": {
-            "minimist": {
-              "version": "0.0.8",
-              "from": "minimist@0.0.8",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-            }
-          }
-        },
-        "node-haste": {
-          "version": "1.2.8",
-          "from": "node-haste@>=1.2.8 <2.0.0",
-          "resolved": "https://registry.npmjs.org/node-haste/-/node-haste-1.2.8.tgz",
-          "dependencies": {
-            "esprima-fb": {
-              "version": "4001.1001.0-dev-harmony-fb",
-              "from": "esprima-fb@4001.1001.0-dev-harmony-fb",
-              "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-4001.1001.0-dev-harmony-fb.tgz"
+            "os-filter-obj": {
+              "version": "1.0.3",
+              "from": "os-filter-obj@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-1.0.3.tgz"
             }
           }
         },
-        "object-assign": {
-          "version": "4.0.1",
-          "from": "object-assign@>=4.0.1 <5.0.0",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-        },
-        "resolve": {
-          "version": "1.1.6",
-          "from": "resolve@>=1.1.6 <2.0.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz"
-        },
-        "through": {
-          "version": "2.3.8",
-          "from": "through@>=2.3.8 <3.0.0",
-          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
-        },
-        "which": {
-          "version": "1.2.0",
-          "from": "which@>=1.1.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/which/-/which-1.2.0.tgz",
+        "logalot": {
+          "version": "2.1.0",
+          "from": "logalot@>=2.0.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/logalot/-/logalot-2.1.0.tgz",
           "dependencies": {
-            "is-absolute": {
-              "version": "0.1.7",
-              "from": "is-absolute@>=0.1.7 <0.2.0",
-              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+            "figures": {
+              "version": "1.4.0",
+              "from": "figures@>=1.3.5 <2.0.0",
+              "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz"
+            },
+            "squeak": {
+              "version": "1.3.0",
+              "from": "squeak@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/squeak/-/squeak-1.3.0.tgz",
               "dependencies": {
-                "is-relative": {
-                  "version": "0.1.3",
-                  "from": "is-relative@>=0.1.0 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                "console-stream": {
+                  "version": "0.1.1",
+                  "from": "console-stream@>=0.1.1 <0.2.0",
+                  "resolved": "https://registry.npmjs.org/console-stream/-/console-stream-0.1.1.tgz"
+                },
+                "lpad-align": {
+                  "version": "1.1.0",
+                  "from": "lpad-align@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/lpad-align/-/lpad-align-1.1.0.tgz",
+                  "dependencies": {
+                    "lpad": {
+                      "version": "2.0.1",
+                      "from": "lpad@>=2.0.1 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/lpad/-/lpad-2.0.1.tgz"
+                    }
+                  }
                 }
               }
             }
@@ -3482,4964 +3146,4160 @@
         }
       }
     },
-    "joi": {
-      "version": "6.6.1",
-      "from": "joi@6.6.1",
-      "resolved": "https://registry.npmjs.org/joi/-/joi-6.6.1.tgz",
-      "dependencies": {
-        "hoek": {
-          "version": "2.14.0",
-          "from": "hoek@>=2.2.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.14.0.tgz"
-        },
-        "isemail": {
-          "version": "1.1.1",
-          "from": "isemail@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz"
-        },
-        "moment": {
-          "version": "2.10.6",
-          "from": "moment@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/moment/-/moment-2.10.6.tgz"
-        },
-        "topo": {
-          "version": "1.0.3",
-          "from": "topo@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/topo/-/topo-1.0.3.tgz"
-        }
-      }
-    },
-    "js-tokens": {
-      "version": "1.0.2",
-      "from": "js-tokens@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz"
+    "for-in": {
+      "version": "0.1.4",
+      "from": "https://registry.npmjs.org/for-in/-/for-in-0.1.4.tgz",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.4.tgz"
     },
-    "jsesc": {
-      "version": "0.5.0",
-      "from": "jsesc@>=0.5.0 <0.6.0",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
+    "for-own": {
+      "version": "0.1.3",
+      "from": "https://registry.npmjs.org/for-own/-/for-own-0.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.3.tgz"
     },
-    "json5": {
-      "version": "0.4.0",
-      "from": "json5@*",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz"
+    "fs-readdir-recursive": {
+      "version": "0.1.2",
+      "from": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz"
     },
-    "jstransform": {
-      "version": "11.0.3",
-      "from": "jstransform@11.0.3",
-      "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
+    "fsevents": {
+      "version": "1.0.5",
+      "from": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.5.tgz",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.5.tgz",
       "dependencies": {
-        "base62": {
-          "version": "1.1.0",
-          "from": "base62@>=1.1.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/base62/-/base62-1.1.0.tgz"
-        },
-        "commoner": {
-          "version": "0.10.3",
-          "from": "commoner@>=0.10.1 <0.11.0",
-          "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
+        "node-pre-gyp": {
+          "version": "0.6.15",
+          "from": "node-pre-gyp@latest",
+          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.15.tgz",
           "dependencies": {
-            "commander": {
-              "version": "2.5.1",
-              "from": "commander@>=2.5.0 <2.6.0",
-              "resolved": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz"
-            },
-            "glob": {
-              "version": "4.2.2",
-              "from": "glob@>=4.2.1 <4.3.0",
-              "resolved": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
-              "dependencies": {
-                "inflight": {
-                  "version": "1.0.4",
-                  "from": "inflight@>=1.0.4 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "minimatch": {
-                  "version": "1.0.0",
-                  "from": "minimatch@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
-                  "dependencies": {
-                    "lru-cache": {
-                      "version": "2.6.5",
-                      "from": "lru-cache@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
-                    },
-                    "sigmund": {
-                      "version": "1.0.1",
-                      "from": "sigmund@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
-                    }
-                  }
-                },
-                "once": {
-                  "version": "1.3.2",
-                  "from": "once@>=1.3.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                }
-              }
-            },
-            "graceful-fs": {
-              "version": "3.0.8",
-              "from": "graceful-fs@>=3.0.4 <3.1.0",
-              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
-            },
-            "iconv-lite": {
-              "version": "0.4.11",
-              "from": "iconv-lite@>=0.4.5 <0.5.0",
-              "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz"
-            },
-            "install": {
-              "version": "0.1.8",
-              "from": "install@>=0.1.7 <0.2.0",
-              "resolved": "https://registry.npmjs.org/install/-/install-0.1.8.tgz"
-            },
-            "mkdirp": {
-              "version": "0.5.1",
-              "from": "mkdirp@>=0.5.0 <0.6.0",
-              "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-              "dependencies": {
-                "minimist": {
-                  "version": "0.0.8",
-                  "from": "minimist@0.0.8",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-                }
-              }
-            },
-            "private": {
-              "version": "0.1.6",
-              "from": "private@>=0.1.6 <0.2.0",
-              "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
-            },
-            "q": {
-              "version": "1.1.2",
-              "from": "q@>=1.1.2 <1.2.0",
-              "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz"
-            },
-            "recast": {
-              "version": "0.10.32",
-              "from": "recast@>=0.10.0 <0.11.0",
-              "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.32.tgz",
-              "dependencies": {
-                "ast-types": {
-                  "version": "0.8.11",
-                  "from": "ast-types@0.8.11",
-                  "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.11.tgz"
-                },
-                "esprima-fb": {
-                  "version": "15001.1001.0-dev-harmony-fb",
-                  "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0",
-                  "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
-                }
-              }
+            "nopt": {
+              "version": "3.0.4",
+              "from": "nopt@>=3.0.1 <3.1.0",
+              "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz"
             }
           }
         },
-        "esprima-fb": {
-          "version": "15001.1.0-dev-harmony-fb",
-          "from": "esprima-fb@>=15001.1.0-dev-harmony-fb <15002.0.0",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz"
+        "abbrev": {
+          "version": "1.0.7",
+          "from": "abbrev@1",
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
         },
-        "object-assign": {
-          "version": "2.1.1",
-          "from": "object-assign@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+        "ansi": {
+          "version": "0.3.0",
+          "from": "ansi@~0.3.0",
+          "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz"
         },
-        "source-map": {
-          "version": "0.4.4",
-          "from": "source-map@>=0.4.2 <0.5.0"
-        }
-      }
-    },
-    "kind-of": {
-      "version": "2.0.1",
-      "from": "kind-of@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz"
-    },
-    "lazy-cache": {
-      "version": "0.2.4",
-      "from": "lazy-cache@>=0.2.4 <0.3.0",
-      "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.4.tgz"
-    },
-    "lcid": {
-      "version": "1.0.0",
-      "from": "lcid@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz"
-    },
-    "left-pad": {
-      "version": "0.0.3",
-      "from": "left-pad@0.0.3",
-      "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz"
-    },
-    "leven": {
-      "version": "1.0.2",
-      "from": "leven@>=1.0.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz"
-    },
-    "line-numbers": {
-      "version": "0.2.0",
-      "from": "line-numbers@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz"
-    },
-    "load-json-file": {
-      "version": "1.0.1",
-      "from": "load-json-file@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
-      "dependencies": {
-        "graceful-fs": {
-          "version": "4.1.2",
-          "from": "graceful-fs@>=4.1.2 <5.0.0"
-        }
-      }
-    },
-    "lodash": {
-      "version": "3.10.1",
-      "from": "lodash@>=3.10.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-    },
-    "lodash._baseassign": {
-      "version": "3.2.0",
-      "from": "lodash._baseassign@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz"
-    },
-    "lodash._basecopy": {
-      "version": "3.0.1",
-      "from": "lodash._basecopy@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
-    },
-    "lodash._baseflatten": {
-      "version": "3.1.4",
-      "from": "lodash._baseflatten@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz"
-    },
-    "lodash._basefor": {
-      "version": "3.0.2",
-      "from": "lodash._basefor@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz"
-    },
-    "lodash._basetostring": {
-      "version": "3.0.1",
-      "from": "lodash._basetostring@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
-    },
-    "lodash._basevalues": {
-      "version": "3.0.0",
-      "from": "lodash._basevalues@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz"
-    },
-    "lodash._bindcallback": {
-      "version": "3.0.1",
-      "from": "lodash._bindcallback@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz"
-    },
-    "lodash._createassigner": {
-      "version": "3.1.1",
-      "from": "lodash._createassigner@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz"
-    },
-    "lodash._getnative": {
-      "version": "3.9.1",
-      "from": "lodash._getnative@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-    },
-    "lodash._isiterateecall": {
-      "version": "3.0.9",
-      "from": "lodash._isiterateecall@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
-    },
-    "lodash._pickbyarray": {
-      "version": "3.0.2",
-      "from": "lodash._pickbyarray@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz"
-    },
-    "lodash._pickbycallback": {
-      "version": "3.0.0",
-      "from": "lodash._pickbycallback@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz"
-    },
-    "lodash._reescape": {
-      "version": "3.0.0",
-      "from": "lodash._reescape@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz"
-    },
-    "lodash._reevaluate": {
-      "version": "3.0.0",
-      "from": "lodash._reevaluate@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz"
-    },
-    "lodash._reinterpolate": {
-      "version": "3.0.0",
-      "from": "lodash._reinterpolate@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz"
-    },
-    "lodash.assign": {
-      "version": "3.2.0",
-      "from": "lodash.assign@>=3.2.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz"
-    },
-    "lodash.escape": {
-      "version": "3.0.0",
-      "from": "lodash.escape@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.0.0.tgz"
-    },
-    "lodash.flatten": {
-      "version": "3.0.2",
-      "from": "lodash.flatten@>=3.0.2 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-3.0.2.tgz"
-    },
-    "lodash.isarguments": {
-      "version": "3.0.4",
-      "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-    },
-    "lodash.isarray": {
-      "version": "3.0.4",
-      "from": "lodash.isarray@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-    },
-    "lodash.keys": {
-      "version": "3.1.2",
-      "from": "lodash.keys@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
-    },
-    "lodash.keysin": {
-      "version": "3.0.8",
-      "from": "lodash.keysin@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz"
-    },
-    "lodash.pick": {
-      "version": "3.1.0",
-      "from": "lodash.pick@>=3.1.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-3.1.0.tgz"
-    },
-    "lodash.restparam": {
-      "version": "3.6.1",
-      "from": "lodash.restparam@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
-    },
-    "lodash.template": {
-      "version": "3.6.2",
-      "from": "lodash.template@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz"
-    },
-    "lodash.templatesettings": {
-      "version": "3.1.0",
-      "from": "lodash.templatesettings@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.0.tgz"
-    },
-    "longest": {
-      "version": "1.0.1",
-      "from": "longest@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
-    },
-    "loose-envify": {
-      "version": "1.1.0",
-      "from": "loose-envify@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz"
-    },
-    "loud-rejection": {
-      "version": "1.0.0",
-      "from": "loud-rejection@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-    },
-    "lru-cache": {
-      "version": "2.7.0",
-      "from": "lru-cache@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz"
-    },
-    "map-obj": {
-      "version": "1.0.1",
-      "from": "map-obj@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-    },
-    "meow": {
-      "version": "3.5.0",
-      "from": "meow@*",
-      "resolved": "https://registry.npmjs.org/meow/-/meow-3.5.0.tgz"
-    },
-    "micromatch": {
-      "version": "2.3.0",
-      "from": "micromatch@>=2.1.5 <3.0.0",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.0.tgz"
-    },
-    "minimatch": {
-      "version": "2.0.10",
-      "from": "minimatch@>=2.0.3 <3.0.0",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz"
-    },
-    "minimist": {
-      "version": "1.2.0",
-      "from": "minimist@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-    },
-    "mkdirp": {
-      "version": "0.5.1",
-      "from": "mkdirp@>=0.5.0 <0.6.0",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-      "dependencies": {
-        "minimist": {
-          "version": "0.0.8",
-          "from": "minimist@0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-        }
-      }
-    },
-    "module-deps": {
-      "version": "3.9.1",
-      "from": "module-deps@3.9.1",
-      "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz",
-      "dependencies": {
-        "browser-resolve": {
-          "version": "1.9.1",
-          "from": "browser-resolve@>=1.7.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.9.1.tgz"
+        "ansi-regex": {
+          "version": "2.0.0",
+          "from": "ansi-regex@^2.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
         },
-        "concat-stream": {
-          "version": "1.4.10",
-          "from": "concat-stream@>=1.4.5 <1.5.0",
-          "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz",
-          "dependencies": {
-            "typedarray": {
-              "version": "0.0.6",
-              "from": "typedarray@>=0.0.5 <0.1.0",
-              "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
-            }
-          }
+        "ansi-styles": {
+          "version": "2.1.0",
+          "from": "ansi-styles@^2.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz"
         },
-        "defined": {
-          "version": "1.0.0",
-          "from": "defined@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz"
+        "are-we-there-yet": {
+          "version": "1.0.4",
+          "from": "are-we-there-yet@~1.0.0",
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.4.tgz"
         },
-        "detective": {
-          "version": "4.2.0",
-          "from": "detective@>=4.0.0 <5.0.0",
-          "resolved": "https://registry.npmjs.org/detective/-/detective-4.2.0.tgz",
-          "dependencies": {
-            "acorn": {
-              "version": "1.2.2",
-              "from": "acorn@>=1.0.3 <2.0.0",
-              "resolved": "https://registry.npmjs.org/acorn/-/acorn-1.2.2.tgz"
-            },
-            "escodegen": {
-              "version": "1.6.1",
-              "from": "escodegen@>=1.4.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.6.1.tgz",
-              "dependencies": {
-                "esprima": {
-                  "version": "1.2.5",
-                  "from": "esprima@>=1.2.2 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
-                },
-                "estraverse": {
-                  "version": "1.9.3",
-                  "from": "estraverse@>=1.9.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
-                },
-                "esutils": {
-                  "version": "1.1.6",
-                  "from": "esutils@>=1.1.6 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
-                },
-                "optionator": {
-                  "version": "0.5.0",
-                  "from": "optionator@>=0.5.0 <0.6.0",
-                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
-                  "dependencies": {
-                    "deep-is": {
-                      "version": "0.1.3",
-                      "from": "deep-is@>=0.1.2 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
-                    },
-                    "fast-levenshtein": {
-                      "version": "1.0.7",
-                      "from": "fast-levenshtein@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
-                    },
-                    "levn": {
-                      "version": "0.2.5",
-                      "from": "levn@>=0.2.5 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
-                    },
-                    "prelude-ls": {
-                      "version": "1.1.2",
-                      "from": "prelude-ls@>=1.1.1 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
-                    },
-                    "type-check": {
-                      "version": "0.3.1",
-                      "from": "type-check@>=0.3.1 <0.4.0",
-                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
-                    },
-                    "wordwrap": {
-                      "version": "0.0.3",
-                      "from": "wordwrap@>=0.0.2 <0.1.0",
-                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
-                    }
-                  }
-                },
-                "source-map": {
-                  "version": "0.1.43",
-                  "from": "source-map@>=0.1.40 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
-                  "dependencies": {
-                    "amdefine": {
-                      "version": "1.0.0",
-                      "from": "amdefine@>=0.0.4",
-                      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
-                    }
-                  }
-                }
-              }
-            }
-          }
+        "assert-plus": {
+          "version": "0.1.5",
+          "from": "assert-plus@^0.1.5",
+          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
         },
-        "duplexer2": {
-          "version": "0.0.2",
-          "from": "duplexer2@0.0.2",
-          "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz"
+        "asn1": {
+          "version": "0.1.11",
+          "from": "asn1@0.1.11",
+          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz"
         },
-        "inherits": {
-          "version": "2.0.1",
-          "from": "inherits@>=2.0.1 <3.0.0",
-          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+        "async": {
+          "version": "1.5.0",
+          "from": "async@^1.4.0",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
         },
-        "JSONStream": {
-          "version": "1.0.4",
-          "from": "JSONStream@>=1.0.3 <2.0.0",
-          "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.4.tgz",
-          "dependencies": {
-            "jsonparse": {
-              "version": "1.0.0",
-              "from": "jsonparse@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz"
-            },
-            "through": {
-              "version": "2.3.8",
-              "from": "through@>=2.2.7 <3.0.0",
-              "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
-            }
-          }
+        "aws-sign2": {
+          "version": "0.6.0",
+          "from": "aws-sign2@~0.6.0",
+          "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
         },
-        "parents": {
-          "version": "1.0.1",
-          "from": "parents@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
-          "dependencies": {
-            "path-platform": {
-              "version": "0.11.15",
-              "from": "path-platform@>=0.11.15 <0.12.0",
-              "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz"
-            }
-          }
+        "balanced-match": {
+          "version": "0.2.1",
+          "from": "balanced-match@>=0.2.0 <0.3.0",
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz"
         },
-        "readable-stream": {
-          "version": "1.1.13",
-          "from": "readable-stream@>=1.1.13 <2.0.0",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
-          "dependencies": {
-            "core-util-is": {
-              "version": "1.0.1",
-              "from": "core-util-is@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-            },
-            "isarray": {
-              "version": "0.0.1",
-              "from": "isarray@0.0.1",
-              "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-            },
-            "string_decoder": {
-              "version": "0.10.31",
-              "from": "string_decoder@>=0.10.0 <0.11.0",
-              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-            }
-          }
+        "block-stream": {
+          "version": "0.0.8",
+          "from": "block-stream@*",
+          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz"
         },
-        "resolve": {
-          "version": "1.1.6",
-          "from": "resolve@>=1.1.3 <2.0.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz"
+        "brace-expansion": {
+          "version": "1.1.1",
+          "from": "brace-expansion@>=1.0.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz"
         },
-        "stream-combiner2": {
-          "version": "1.0.2",
-          "from": "stream-combiner2@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
-          "dependencies": {
-            "through2": {
-              "version": "0.5.1",
-              "from": "through2@>=0.5.1 <0.6.0",
-              "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
-              "dependencies": {
-                "readable-stream": {
-                  "version": "1.0.33",
-                  "from": "readable-stream@>=1.0.17 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                  "dependencies": {
-                    "core-util-is": {
-                      "version": "1.0.1",
-                      "from": "core-util-is@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                    },
-                    "isarray": {
-                      "version": "0.0.1",
-                      "from": "isarray@0.0.1",
-                      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                    },
-                    "string_decoder": {
-                      "version": "0.10.31",
-                      "from": "string_decoder@>=0.10.0 <0.11.0",
-                      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                    }
-                  }
-                },
-                "xtend": {
-                  "version": "3.0.0",
-                  "from": "xtend@>=3.0.0 <3.1.0",
-                  "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz"
-                }
-              }
-            }
-          }
+        "boom": {
+          "version": "2.10.1",
+          "from": "boom@^2.8.x",
+          "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
         },
-        "subarg": {
-          "version": "1.0.0",
-          "from": "subarg@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
-          "dependencies": {
-            "minimist": {
-              "version": "1.2.0",
-              "from": "minimist@>=1.1.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-            }
-          }
+        "caseless": {
+          "version": "0.11.0",
+          "from": "caseless@~0.11.0",
+          "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
         },
-        "through2": {
+        "chalk": {
           "version": "1.1.1",
-          "from": "through2@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz"
+          "from": "chalk@^1.1.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
         },
-        "xtend": {
-          "version": "4.0.0",
-          "from": "xtend@>=4.0.0 <5.0.0",
-          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-        }
-      }
-    },
-    "ms": {
-      "version": "0.7.1",
-      "from": "ms@0.7.1",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
-    },
-    "multipipe": {
-      "version": "0.1.2",
-      "from": "multipipe@>=0.1.2 <0.2.0",
-      "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz"
-    },
-    "nan": {
-      "version": "2.1.0",
-      "from": "nan@>=2.0.2 <3.0.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.1.0.tgz"
-    },
-    "node-fetch": {
-      "version": "1.3.3",
-      "from": "node-fetch@1.3.3",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.3.3.tgz",
-      "dependencies": {
-        "encoding": {
-          "version": "0.1.11",
-          "from": "encoding@>=0.1.11 <0.2.0",
-          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.11.tgz",
-          "dependencies": {
-            "iconv-lite": {
-              "version": "0.4.13",
-              "from": "iconv-lite@>=0.4.4 <0.5.0",
-              "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
-            }
-          }
-        }
-      }
-    },
-    "normalize-package-data": {
-      "version": "2.3.5",
-      "from": "normalize-package-data@>=2.3.4 <3.0.0",
-      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz",
-      "dependencies": {
-        "semver": {
-          "version": "5.0.3",
-          "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
-        }
-      }
-    },
-    "normalize-path": {
-      "version": "2.0.0",
-      "from": "normalize-path@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.0.tgz"
-    },
-    "number-is-nan": {
-      "version": "1.0.0",
-      "from": "number-is-nan@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-    },
-    "object-assign": {
-      "version": "4.0.1",
-      "from": "object-assign@>=4.0.1 <5.0.0",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-    },
-    "object.omit": {
-      "version": "2.0.0",
-      "from": "object.omit@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz"
-    },
-    "once": {
-      "version": "1.3.2",
-      "from": "once@>=1.3.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz"
-    },
-    "opn": {
-      "version": "3.0.2",
-      "from": "opn@*",
-      "resolved": "https://registry.npmjs.org/opn/-/opn-3.0.2.tgz",
-      "dependencies": {
-        "object-assign": {
+        "combined-stream": {
+          "version": "1.0.5",
+          "from": "combined-stream@~1.0.5",
+          "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz"
+        },
+        "commander": {
+          "version": "2.9.0",
+          "from": "commander@^2.8.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
+        },
+        "concat-map": {
+          "version": "0.0.1",
+          "from": "concat-map@0.0.1",
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+        },
+        "core-util-is": {
+          "version": "1.0.1",
+          "from": "core-util-is@~1.0.0",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+        },
+        "ctype": {
+          "version": "0.5.3",
+          "from": "ctype@0.5.3",
+          "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
+        },
+        "cryptiles": {
+          "version": "2.0.5",
+          "from": "cryptiles@2.x.x",
+          "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
+        },
+        "debug": {
+          "version": "0.7.4",
+          "from": "debug@~0.7.2",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+        },
+        "deep-extend": {
+          "version": "0.2.11",
+          "from": "deep-extend@~0.2.5",
+          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
+        },
+        "delayed-stream": {
+          "version": "1.0.0",
+          "from": "delayed-stream@~1.0.0",
+          "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
+        },
+        "delegates": {
+          "version": "0.1.0",
+          "from": "delegates@^0.1.0",
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-0.1.0.tgz"
+        },
+        "escape-string-regexp": {
+          "version": "1.0.3",
+          "from": "escape-string-regexp@^1.0.2",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
+        },
+        "extend": {
           "version": "3.0.0",
-          "from": "object-assign@>=3.0.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-        }
-      }
-    },
-    "optimist": {
-      "version": "0.6.1",
-      "from": "optimist@0.6.1",
-      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
-      "dependencies": {
-        "minimist": {
-          "version": "0.0.10",
-          "from": "minimist@>=0.0.1 <0.1.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
+          "from": "extend@~3.0.0",
+          "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+        },
+        "forever-agent": {
+          "version": "0.6.1",
+          "from": "forever-agent@~0.6.1",
+          "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+        },
+        "form-data": {
+          "version": "1.0.0-rc3",
+          "from": "form-data@~1.0.0-rc3",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz"
+        },
+        "fstream": {
+          "version": "1.0.8",
+          "from": "fstream@^1.0.2",
+          "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz"
+        },
+        "gauge": {
+          "version": "1.2.2",
+          "from": "gauge@~1.2.0",
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.2.tgz"
+        },
+        "generate-function": {
+          "version": "2.0.0",
+          "from": "generate-function@^2.0.0",
+          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+        },
+        "generate-object-property": {
+          "version": "1.2.0",
+          "from": "generate-object-property@^1.1.0",
+          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
         },
-        "wordwrap": {
-          "version": "0.0.3",
-          "from": "wordwrap@>=0.0.2 <0.1.0",
-          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
-        }
-      }
-    },
-    "os-locale": {
-      "version": "1.4.0",
-      "from": "os-locale@>=1.4.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz"
-    },
-    "os-tmpdir": {
-      "version": "1.0.1",
-      "from": "os-tmpdir@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"
-    },
-    "output-file-sync": {
-      "version": "1.1.1",
-      "from": "output-file-sync@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.1.tgz"
-    },
-    "parse-glob": {
-      "version": "3.0.4",
-      "from": "parse-glob@>=3.0.1 <4.0.0",
-      "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz"
-    },
-    "parse-json": {
-      "version": "2.2.0",
-      "from": "parse-json@>=2.2.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
-    },
-    "path-exists": {
-      "version": "1.0.0",
-      "from": "path-exists@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz"
-    },
-    "path-is-absolute": {
-      "version": "1.0.0",
-      "from": "path-is-absolute@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
-    },
-    "path-type": {
-      "version": "1.0.0",
-      "from": "path-type@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-      "dependencies": {
         "graceful-fs": {
           "version": "4.1.2",
-          "from": "graceful-fs@>=4.1.2 <5.0.0"
-        }
-      }
-    },
-    "pify": {
-      "version": "2.3.0",
-      "from": "pify@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
-    },
-    "pinkie": {
-      "version": "1.0.0",
-      "from": "pinkie@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-    },
-    "pinkie-promise": {
-      "version": "1.0.0",
-      "from": "pinkie-promise@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz"
-    },
-    "portfinder": {
-      "version": "0.4.0",
-      "from": "portfinder@0.4.0",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
-      "dependencies": {
-        "async": {
-          "version": "0.9.0",
-          "from": "async@0.9.0",
-          "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
+          "from": "graceful-fs@4.1",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
         },
-        "mkdirp": {
-          "version": "0.5.1",
-          "from": "mkdirp@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "dependencies": {
-            "minimist": {
-              "version": "0.0.8",
-              "from": "minimist@0.0.8",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-            }
-          }
-        }
-      }
-    },
-    "preserve": {
-      "version": "0.2.0",
-      "from": "preserve@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz"
-    },
-    "private": {
-      "version": "0.1.6",
-      "from": "private@>=0.1.6 <0.2.0",
-      "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
-    },
-    "process-nextick-args": {
-      "version": "1.0.3",
-      "from": "process-nextick-args@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
-    },
-    "progress": {
-      "version": "1.1.8",
-      "from": "progress@>=1.1.8 <2.0.0",
-      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz"
-    },
-    "promise": {
-      "version": "7.0.4",
-      "from": "promise@>=7.0.3 <8.0.0",
-      "resolved": "https://registry.npmjs.org/promise/-/promise-7.0.4.tgz",
-      "dependencies": {
-        "asap": {
-          "version": "2.0.3",
-          "from": "asap@>=2.0.3 <2.1.0",
-          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.3.tgz"
-        }
-      }
-    },
-    "q": {
-      "version": "1.4.1",
-      "from": "q@>=1.1.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz"
-    },
-    "randomatic": {
-      "version": "1.1.0",
-      "from": "randomatic@>=1.1.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.0.tgz",
-      "dependencies": {
-        "kind-of": {
-          "version": "1.1.0",
-          "from": "kind-of@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz"
-        }
-      }
-    },
-    "react-haste": {
-      "version": "0.14.0",
-      "from": "react-haste@0.14.0",
-      "resolved": "https://registry.npmjs.org/react-haste/-/react-haste-0.14.0.tgz"
-    },
-    "react-timer-mixin": {
-      "version": "0.13.2",
-      "from": "react-timer-mixin@>=0.13.1 <0.14.0",
-      "resolved": "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.2.tgz"
-    },
-    "read-pkg": {
-      "version": "1.1.0",
-      "from": "read-pkg@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz"
-    },
-    "read-pkg-up": {
-      "version": "1.0.1",
-      "from": "read-pkg-up@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz"
-    },
-    "readable-stream": {
-      "version": "2.0.4",
-      "from": "readable-stream@>=2.0.2 <3.0.0",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
-    },
-    "readdirp": {
-      "version": "2.0.0",
-      "from": "readdirp@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz",
-      "dependencies": {
-        "graceful-fs": {
-          "version": "4.1.2",
-          "from": "graceful-fs@>=4.1.2 <5.0.0"
-        }
-      }
-    },
-    "rebound": {
-      "version": "0.0.13",
-      "from": "rebound@>=0.0.13 <0.0.14",
-      "resolved": "https://registry.npmjs.org/rebound/-/rebound-0.0.13.tgz"
-    },
-    "recast": {
-      "version": "0.10.37",
-      "from": "recast@>=0.10.0 <0.11.0",
-      "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.37.tgz",
-      "dependencies": {
-        "ast-types": {
-          "version": "0.8.12",
-          "from": "ast-types@0.8.12",
-          "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
+        "graceful-readlink": {
+          "version": "1.0.1",
+          "from": "graceful-readlink@>= 1.0.0",
+          "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
         },
-        "source-map": {
-          "version": "0.5.3",
-          "from": "source-map@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
-        }
-      }
-    },
-    "redent": {
-      "version": "1.0.0",
-      "from": "redent@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz"
-    },
-    "regenerate": {
-      "version": "1.2.1",
-      "from": "regenerate@>=1.2.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz"
-    },
-    "regenerator": {
-      "version": "0.8.36",
-      "from": "regenerator@0.8.36",
-      "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.36.tgz",
-      "dependencies": {
-        "commoner": {
-          "version": "0.10.3",
-          "from": "commoner@>=0.10.0 <0.11.0",
-          "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
-          "dependencies": {
-            "commander": {
-              "version": "2.5.1",
-              "from": "commander@>=2.5.0 <2.6.0",
-              "resolved": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz"
-            },
-            "glob": {
-              "version": "4.2.2",
-              "from": "glob@>=4.2.1 <4.3.0",
-              "resolved": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
-              "dependencies": {
-                "inflight": {
-                  "version": "1.0.4",
-                  "from": "inflight@>=1.0.4 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "minimatch": {
-                  "version": "1.0.0",
-                  "from": "minimatch@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
-                  "dependencies": {
-                    "lru-cache": {
-                      "version": "2.6.5",
-                      "from": "lru-cache@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
-                    },
-                    "sigmund": {
-                      "version": "1.0.1",
-                      "from": "sigmund@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
-                    }
-                  }
-                },
-                "once": {
-                  "version": "1.3.2",
-                  "from": "once@>=1.3.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                }
-              }
-            },
-            "graceful-fs": {
-              "version": "3.0.8",
-              "from": "graceful-fs@>=3.0.4 <3.1.0",
-              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
-            },
-            "iconv-lite": {
-              "version": "0.4.11",
-              "from": "iconv-lite@>=0.4.5 <0.5.0",
-              "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz"
-            },
-            "install": {
-              "version": "0.1.8",
-              "from": "install@>=0.1.7 <0.2.0",
-              "resolved": "https://registry.npmjs.org/install/-/install-0.1.8.tgz"
-            },
-            "mkdirp": {
-              "version": "0.5.1",
-              "from": "mkdirp@>=0.5.0 <0.6.0",
-              "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-              "dependencies": {
-                "minimist": {
-                  "version": "0.0.8",
-                  "from": "minimist@0.0.8",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-                }
-              }
-            },
-            "q": {
-              "version": "1.1.2",
-              "from": "q@>=1.1.2 <1.2.0",
-              "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz"
-            }
-          }
+        "har-validator": {
+          "version": "2.0.2",
+          "from": "har-validator@~2.0.2",
+          "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.2.tgz"
         },
-        "defs": {
-          "version": "1.1.0",
-          "from": "defs@>=1.1.0 <1.2.0",
-          "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.0.tgz",
-          "dependencies": {
-            "alter": {
-              "version": "0.2.0",
-              "from": "alter@>=0.2.0 <0.3.0",
-              "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz",
-              "dependencies": {
-                "stable": {
-                  "version": "0.1.5",
-                  "from": "stable@>=0.1.3 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz"
-                }
-              }
-            },
-            "ast-traverse": {
-              "version": "0.1.1",
-              "from": "ast-traverse@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz"
-            },
-            "breakable": {
-              "version": "1.0.0",
-              "from": "breakable@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz"
-            },
-            "esprima-fb": {
-              "version": "8001.1001.0-dev-harmony-fb",
-              "from": "esprima-fb@>=8001.1001.0-dev-harmony-fb <8001.1002.0",
-              "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-8001.1001.0-dev-harmony-fb.tgz"
-            },
-            "simple-fmt": {
-              "version": "0.1.0",
-              "from": "simple-fmt@>=0.1.0 <0.2.0",
-              "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz"
-            },
-            "simple-is": {
-              "version": "0.2.0",
-              "from": "simple-is@>=0.2.0 <0.3.0",
-              "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz"
-            },
-            "stringmap": {
-              "version": "0.2.2",
-              "from": "stringmap@>=0.2.2 <0.3.0",
-              "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz"
-            },
-            "stringset": {
-              "version": "0.2.1",
-              "from": "stringset@>=0.2.1 <0.3.0",
-              "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz"
-            },
-            "tryor": {
-              "version": "0.1.2",
-              "from": "tryor@>=0.1.2 <0.2.0",
-              "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz"
-            },
-            "yargs": {
-              "version": "1.3.3",
-              "from": "yargs@>=1.3.2 <1.4.0",
-              "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz"
-            }
-          }
+        "has-ansi": {
+          "version": "2.0.0",
+          "from": "has-ansi@^2.0.0",
+          "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
         },
-        "esprima-fb": {
-          "version": "15001.1.0-dev-harmony-fb",
-          "from": "esprima-fb@>=15001.1.0-dev-harmony-fb <15001.2.0",
-          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz"
+        "has-unicode": {
+          "version": "1.0.1",
+          "from": "has-unicode@^1.0.0",
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-1.0.1.tgz"
         },
-        "private": {
-          "version": "0.1.6",
-          "from": "private@>=0.1.5 <0.2.0",
-          "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
+        "hawk": {
+          "version": "3.1.0",
+          "from": "hawk@~3.1.0",
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz"
         },
-        "recast": {
-          "version": "0.10.25",
-          "from": "recast@0.10.25",
-          "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.25.tgz",
-          "dependencies": {
-            "ast-types": {
-              "version": "0.8.5",
-              "from": "ast-types@0.8.5",
-              "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.5.tgz"
-            },
-            "source-map": {
-              "version": "0.4.4",
-              "from": "source-map@>=0.4.1 <0.5.0"
-            }
-          }
+        "hoek": {
+          "version": "2.16.3",
+          "from": "hoek@2.x.x",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
         },
-        "through": {
-          "version": "2.3.8",
-          "from": "through@>=2.3.6 <2.4.0",
-          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
-        }
-      }
-    },
-    "regex-cache": {
-      "version": "0.4.2",
-      "from": "regex-cache@>=0.4.2 <0.5.0",
-      "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.2.tgz"
-    },
-    "regexpu": {
-      "version": "1.3.0",
-      "from": "regexpu@>=1.3.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz",
-      "dependencies": {
-        "esprima": {
-          "version": "2.7.0",
-          "from": "esprima@>=2.6.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.0.tgz"
-        }
-      }
-    },
-    "regjsgen": {
-      "version": "0.2.0",
-      "from": "regjsgen@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz"
-    },
-    "regjsparser": {
-      "version": "0.1.5",
-      "from": "regjsparser@>=0.1.4 <0.2.0",
-      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz"
-    },
-    "repeat-element": {
-      "version": "1.1.2",
-      "from": "repeat-element@>=1.1.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz"
-    },
-    "repeat-string": {
-      "version": "1.5.2",
-      "from": "repeat-string@>=1.5.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz"
-    },
-    "repeating": {
-      "version": "1.1.3",
-      "from": "repeating@>=1.1.3 <2.0.0",
-      "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz"
-    },
-    "replace-ext": {
-      "version": "0.0.1",
-      "from": "replace-ext@0.0.1",
-      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
-    },
-    "resolve": {
-      "version": "1.1.6",
-      "from": "resolve@>=1.1.6 <2.0.0",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz"
-    },
-    "right-align": {
-      "version": "0.1.3",
-      "from": "right-align@>=0.1.1 <0.2.0",
-      "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz"
-    },
-    "sane": {
-      "version": "1.2.0",
-      "from": "sane@>=1.2.0 <1.3.0",
-      "resolved": "https://registry.npmjs.org/sane/-/sane-1.2.0.tgz",
-      "dependencies": {
-        "exec-sh": {
-          "version": "0.2.0",
-          "from": "exec-sh@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
-          "dependencies": {
-            "merge": {
-              "version": "1.2.0",
-              "from": "merge@>=1.1.3 <2.0.0",
-              "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz"
-            }
-          }
+        "http-signature": {
+          "version": "0.11.0",
+          "from": "http-signature@~0.11.0",
+          "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz"
         },
-        "fb-watchman": {
-          "version": "1.6.0",
-          "from": "fb-watchman@>=1.5.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.6.0.tgz"
+        "inherits": {
+          "version": "2.0.1",
+          "from": "inherits@*",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
         },
-        "minimatch": {
-          "version": "0.2.14",
-          "from": "minimatch@>=0.2.14 <0.3.0",
-          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
-          "dependencies": {
-            "lru-cache": {
-              "version": "2.6.5",
-              "from": "lru-cache@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
-            },
-            "sigmund": {
-              "version": "1.0.1",
-              "from": "sigmund@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
+        "ini": {
+          "version": "1.3.4",
+          "from": "ini@~1.3.0",
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
+        },
+        "is-my-json-valid": {
+          "version": "2.12.2",
+          "from": "is-my-json-valid@^2.12.2",
+          "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz"
+        },
+        "is-property": {
+          "version": "1.0.2",
+          "from": "is-property@^1.0.0",
+          "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "from": "isarray@0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+        },
+        "isstream": {
+          "version": "0.1.2",
+          "from": "isstream@~0.1.2",
+          "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
+        },
+        "json-stringify-safe": {
+          "version": "5.0.1",
+          "from": "json-stringify-safe@~5.0.1",
+          "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
+        },
+        "jsonpointer": {
+          "version": "2.0.0",
+          "from": "jsonpointer@2.0.0",
+          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
+        },
+        "lodash._createpadding": {
+          "version": "3.6.1",
+          "from": "lodash._createpadding@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz"
+        },
+        "lodash._basetostring": {
+          "version": "3.0.1",
+          "from": "lodash._basetostring@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
+        },
+        "lodash.pad": {
+          "version": "3.1.1",
+          "from": "lodash.pad@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-3.1.1.tgz"
+        },
+        "lodash.padleft": {
+          "version": "3.1.1",
+          "from": "lodash.padleft@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash.padleft/-/lodash.padleft-3.1.1.tgz"
+        },
+        "lodash.padright": {
+          "version": "3.1.1",
+          "from": "lodash.padright@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash.padright/-/lodash.padright-3.1.1.tgz"
+        },
+        "lodash.repeat": {
+          "version": "3.0.1",
+          "from": "lodash.repeat@^3.0.0",
+          "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz"
+        },
+        "mime-db": {
+          "version": "1.19.0",
+          "from": "mime-db@~1.19.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz"
+        },
+        "mime-types": {
+          "version": "2.1.7",
+          "from": "mime-types@~2.1.7",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz"
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "from": "minimist@0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "from": "mkdirp@>=0.3.0 <0.4.0||>=0.4.0 <0.5.0||>=0.5.0 <0.6.0",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
+        },
+        "node-uuid": {
+          "version": "1.4.3",
+          "from": "node-uuid@~1.4.3",
+          "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz"
+        },
+        "npmlog": {
+          "version": "1.2.1",
+          "from": "npmlog@~1.2.0",
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz"
+        },
+        "oauth-sign": {
+          "version": "0.8.0",
+          "from": "oauth-sign@~0.8.0",
+          "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz"
+        },
+        "once": {
+          "version": "1.1.1",
+          "from": "once@~1.1.1",
+          "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz"
+        },
+        "path-is-absolute": {
+          "version": "1.0.0",
+          "from": "path-is-absolute@>=1.0.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+        },
+        "pinkie": {
+          "version": "1.0.0",
+          "from": "pinkie@^1.0.0",
+          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
+        },
+        "pinkie-promise": {
+          "version": "1.0.0",
+          "from": "pinkie-promise@^1.0.0",
+          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz"
+        },
+        "qs": {
+          "version": "5.2.0",
+          "from": "qs@~5.2.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
+        },
+        "readable-stream": {
+          "version": "1.1.13",
+          "from": "readable-stream@^1.1.13",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
+        },
+        "request": {
+          "version": "2.65.0",
+          "from": "request@2.x",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.65.0.tgz"
+        },
+        "semver": {
+          "version": "5.0.3",
+          "from": "semver@~5.0.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
+        },
+        "sntp": {
+          "version": "1.0.9",
+          "from": "sntp@1.x.x",
+          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "from": "string_decoder@~0.10.x",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+        },
+        "stringstream": {
+          "version": "0.0.5",
+          "from": "stringstream@~0.0.4",
+          "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
+        },
+        "strip-ansi": {
+          "version": "3.0.0",
+          "from": "strip-ansi@^3.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+        },
+        "strip-json-comments": {
+          "version": "0.1.3",
+          "from": "strip-json-comments@0.1.x",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "from": "supports-color@^2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
+        },
+        "tar": {
+          "version": "2.2.1",
+          "from": "tar@~2.2.0",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
+        },
+        "tough-cookie": {
+          "version": "2.2.0",
+          "from": "tough-cookie@~2.2.0",
+          "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.0.tgz"
+        },
+        "tunnel-agent": {
+          "version": "0.4.1",
+          "from": "tunnel-agent@~0.4.1",
+          "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
+        },
+        "uid-number": {
+          "version": "0.0.3",
+          "from": "uid-number@0.0.3",
+          "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.3.tgz"
+        },
+        "wrappy": {
+          "version": "1.0.1",
+          "from": "wrappy@>=1.0.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "from": "xtend@^4.0.0",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
+        },
+        "fstream-ignore": {
+          "version": "1.0.3",
+          "from": "fstream-ignore@~1.0.3",
+          "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz",
+          "dependencies": {
+            "minimatch": {
+              "version": "3.0.0",
+              "from": "minimatch@>=3.0.0 <4.0.0"
             }
           }
         },
-        "minimist": {
-          "version": "1.2.0",
-          "from": "minimist@>=1.1.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
+        "inflight": {
+          "version": "1.0.4",
+          "from": "inflight@>=1.0.4 <2.0.0",
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+          "dependencies": {
+            "once": {
+              "version": "1.3.2",
+              "from": "once@>=1.3.0 <2.0.0"
+            }
+          }
         },
-        "walker": {
-          "version": "1.0.7",
-          "from": "walker@>=1.0.5 <1.1.0",
-          "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
+        "rc": {
+          "version": "1.1.2",
+          "from": "rc@~1.1.0",
+          "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.2.tgz",
           "dependencies": {
-            "makeerror": {
-              "version": "1.0.11",
-              "from": "makeerror@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
-              "dependencies": {
-                "tmpl": {
-                  "version": "1.0.4",
-                  "from": "tmpl@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz"
-                }
-              }
+            "minimist": {
+              "version": "1.2.0",
+              "from": "minimist@^1.1.2",
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
             }
           }
         },
-        "watch": {
-          "version": "0.10.0",
-          "from": "watch@>=0.10.0 <0.11.0",
-          "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz"
-        }
-      }
-    },
-    "semver": {
-      "version": "5.0.3",
-      "from": "semver@>=5.0.1 <6.0.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
-    },
-    "shebang-regex": {
-      "version": "1.0.0",
-      "from": "shebang-regex@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
-    },
-    "sigmund": {
-      "version": "1.0.1",
-      "from": "sigmund@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
-    },
-    "simple-fmt": {
-      "version": "0.1.0",
-      "from": "simple-fmt@>=0.1.0 <0.2.0",
-      "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz"
+        "rimraf": {
+          "version": "2.4.3",
+          "from": "rimraf@~2.4.0",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz",
+          "dependencies": {
+            "glob": {
+              "version": "5.0.15",
+              "from": "glob@>=5.0.14 <6.0.0",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz"
+            },
+            "minimatch": {
+              "version": "3.0.0",
+              "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz"
+            },
+            "once": {
+              "version": "1.3.2",
+              "from": "once@>=1.3.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz"
+            }
+          }
+        },
+        "bl": {
+          "version": "1.0.0",
+          "from": "bl@~1.0.0",
+          "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+          "dependencies": {
+            "readable-stream": {
+              "version": "2.0.4",
+              "from": "readable-stream@~2.0.0",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
+              "dependencies": {
+                "core-util-is": {
+                  "version": "1.0.1",
+                  "from": "core-util-is@~1.0.0",
+                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+                },
+                "inherits": {
+                  "version": "2.0.1",
+                  "from": "inherits@~2.0.1",
+                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                },
+                "isarray": {
+                  "version": "0.0.1",
+                  "from": "isarray@0.0.1",
+                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                },
+                "process-nextick-args": {
+                  "version": "1.0.3",
+                  "from": "process-nextick-args@~1.0.0",
+                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+                },
+                "string_decoder": {
+                  "version": "0.10.31",
+                  "from": "string_decoder@~0.10.x",
+                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                },
+                "util-deprecate": {
+                  "version": "1.0.2",
+                  "from": "util-deprecate@~1.0.1",
+                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                }
+              }
+            }
+          }
+        },
+        "tar-pack": {
+          "version": "3.1.0",
+          "from": "tar-pack@~3.1.0",
+          "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.1.0.tgz",
+          "dependencies": {
+            "readable-stream": {
+              "version": "1.0.33",
+              "from": "readable-stream@~1.0.2",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+              "dependencies": {
+                "core-util-is": {
+                  "version": "1.0.1",
+                  "from": "core-util-is@~1.0.0",
+                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
+                },
+                "inherits": {
+                  "version": "2.0.1",
+                  "from": "inherits@~2.0.1",
+                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                },
+                "isarray": {
+                  "version": "0.0.1",
+                  "from": "isarray@0.0.1",
+                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                },
+                "string_decoder": {
+                  "version": "0.10.31",
+                  "from": "string_decoder@~0.10.x",
+                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                }
+              }
+            },
+            "rimraf": {
+              "version": "2.2.8",
+              "from": "rimraf@~2.2.0",
+              "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+            }
+          }
+        }
+      }
     },
-    "simple-is": {
-      "version": "0.2.0",
-      "from": "simple-is@>=0.2.0 <0.3.0",
-      "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz"
+    "get-stdin": {
+      "version": "4.0.1",
+      "from": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
     },
-    "slash": {
+    "glob": {
+      "version": "5.0.15",
+      "from": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz"
+    },
+    "glob-base": {
+      "version": "0.3.0",
+      "from": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz"
+    },
+    "glob-parent": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz"
+    },
+    "glogg": {
       "version": "1.0.0",
-      "from": "slash@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz"
+      "from": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz"
     },
-    "source-map": {
-      "version": "0.4.4",
-      "from": "source-map@>=0.4.4 <0.5.0",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+    "graceful-fs": {
+      "version": "4.1.2",
+      "from": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz"
     },
-    "source-map-support": {
-      "version": "0.2.10",
-      "from": "source-map-support@>=0.2.10 <0.3.0",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz",
+    "graceful-readlink": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+    },
+    "gulp-util": {
+      "version": "3.0.7",
+      "from": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.7.tgz",
+      "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.7.tgz",
       "dependencies": {
-        "source-map": {
-          "version": "0.1.32",
-          "from": "source-map@0.1.32",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz"
+        "object-assign": {
+          "version": "3.0.0",
+          "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
         }
       }
     },
-    "sparkles": {
+    "gulplog": {
       "version": "1.0.0",
-      "from": "sparkles@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz"
+      "from": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz"
     },
-    "spdx-correct": {
-      "version": "1.0.2",
-      "from": "spdx-correct@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz"
+    "has-ansi": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
     },
-    "spdx-exceptions": {
-      "version": "1.0.4",
-      "from": "spdx-exceptions@>=1.0.4 <2.0.0",
-      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz"
+    "has-gulplog": {
+      "version": "0.1.0",
+      "from": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz"
     },
-    "spdx-expression-parse": {
-      "version": "1.0.1",
-      "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.1.tgz"
+    "home-or-tmp": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz"
     },
-    "spdx-license-ids": {
-      "version": "1.1.0",
-      "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.1.0.tgz"
+    "hosted-git-info": {
+      "version": "2.1.4",
+      "from": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
     },
-    "stable": {
-      "version": "0.1.5",
-      "from": "stable@>=0.1.3 <0.2.0",
-      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz"
+    "iconv-lite": {
+      "version": "0.4.13",
+      "from": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz"
     },
-    "stacktrace-parser": {
-      "version": "0.1.3",
-      "from": "stacktrace-parser@0.1.3",
-      "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.3.tgz"
+    "image-size": {
+      "version": "0.3.5",
+      "from": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz",
+      "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz"
     },
-    "string_decoder": {
-      "version": "0.10.31",
-      "from": "string_decoder@>=0.10.0 <0.11.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+    "immutable": {
+      "version": "3.7.5",
+      "from": "https://registry.npmjs.org/immutable/-/immutable-3.7.5.tgz",
+      "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.5.tgz"
     },
-    "stringmap": {
-      "version": "0.2.2",
-      "from": "stringmap@>=0.2.2 <0.3.0",
-      "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz"
+    "indent-string": {
+      "version": "2.1.0",
+      "from": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "dependencies": {
+        "repeating": {
+          "version": "2.0.0",
+          "from": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz"
+        }
+      }
     },
-    "stringset": {
-      "version": "0.2.1",
-      "from": "stringset@>=0.2.1 <0.3.0",
-      "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz"
+    "inflight": {
+      "version": "1.0.4",
+      "from": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz"
     },
-    "strip-ansi": {
-      "version": "3.0.0",
-      "from": "strip-ansi@>=3.0.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+    "inherits": {
+      "version": "2.0.1",
+      "from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
     },
-    "strip-bom": {
-      "version": "2.0.0",
-      "from": "strip-bom@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz"
+    "invert-kv": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz"
     },
-    "strip-indent": {
+    "is-arrayish": {
+      "version": "0.2.1",
+      "from": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
+    },
+    "is-binary-path": {
       "version": "1.0.1",
-      "from": "strip-indent@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz"
+      "from": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz"
     },
-    "success-symbol": {
-      "version": "0.1.0",
-      "from": "success-symbol@>=0.1.0 <0.2.0",
-      "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz"
+    "is-buffer": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.0.tgz"
     },
-    "supports-color": {
-      "version": "2.0.0",
-      "from": "supports-color@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
+    "is-builtin-module": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz"
     },
-    "temp": {
-      "version": "0.8.3",
-      "from": "temp@0.8.3",
-      "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
-      "dependencies": {
-        "os-tmpdir": {
-          "version": "1.0.1",
-          "from": "os-tmpdir@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"
-        },
-        "rimraf": {
-          "version": "2.2.8",
-          "from": "rimraf@>=2.2.6 <2.3.0",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
-        }
-      }
-    },
-    "through": {
-      "version": "2.3.8",
-      "from": "through@>=2.3.6 <2.4.0",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+    "is-dotfile": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz"
     },
-    "through2": {
-      "version": "2.0.0",
-      "from": "through2@>=2.0.0 <3.0.0",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz"
+    "is-equal-shallow": {
+      "version": "0.1.3",
+      "from": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz"
     },
-    "to-fast-properties": {
-      "version": "1.0.1",
-      "from": "to-fast-properties@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz"
+    "is-extendable": {
+      "version": "0.1.1",
+      "from": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz"
     },
-    "trim-newlines": {
+    "is-extglob": {
       "version": "1.0.0",
-      "from": "trim-newlines@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
-    },
-    "trim-right": {
-      "version": "1.0.1",
-      "from": "trim-right@>=1.0.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz"
+      "from": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz"
     },
-    "try-resolve": {
+    "is-finite": {
       "version": "1.0.1",
-      "from": "try-resolve@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz"
-    },
-    "tryor": {
-      "version": "0.1.2",
-      "from": "tryor@>=0.1.2 <0.2.0",
-      "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz"
-    },
-    "uglify-js": {
-      "version": "2.4.24",
-      "from": "uglify-js@2.4.24",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz",
-      "dependencies": {
-        "async": {
-          "version": "0.2.10",
-          "from": "async@>=0.2.6 <0.3.0",
-          "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
-        },
-        "source-map": {
-          "version": "0.1.34",
-          "from": "source-map@0.1.34",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz",
-          "dependencies": {
-            "amdefine": {
-              "version": "1.0.0",
-              "from": "amdefine@>=0.0.4",
-              "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
-            }
-          }
-        },
-        "uglify-to-browserify": {
-          "version": "1.0.2",
-          "from": "uglify-to-browserify@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz"
-        },
-        "yargs": {
-          "version": "3.5.4",
-          "from": "yargs@>=3.5.4 <3.6.0",
-          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz",
-          "dependencies": {
-            "camelcase": {
-              "version": "1.2.1",
-              "from": "camelcase@>=1.0.2 <2.0.0",
-              "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-            },
-            "decamelize": {
-              "version": "1.0.0",
-              "from": "decamelize@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz"
-            },
-            "window-size": {
-              "version": "0.1.0",
-              "from": "window-size@0.1.0",
-              "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz"
-            },
-            "wordwrap": {
-              "version": "0.0.2",
-              "from": "wordwrap@0.0.2",
-              "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
-            }
-          }
-        }
-      }
-    },
-    "underscore": {
-      "version": "1.8.3",
-      "from": "underscore@1.8.3",
-      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
-    },
-    "user-home": {
-      "version": "1.1.1",
-      "from": "user-home@>=1.1.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz"
+      "from": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz"
     },
-    "util-deprecate": {
-      "version": "1.0.2",
-      "from": "util-deprecate@>=1.0.1 <1.1.0",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+    "is-glob": {
+      "version": "2.0.1",
+      "from": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz"
     },
-    "validate-npm-package-license": {
-      "version": "3.0.1",
-      "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz"
+    "is-integer": {
+      "version": "1.0.6",
+      "from": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz",
+      "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz"
     },
-    "vinyl": {
-      "version": "0.5.3",
-      "from": "vinyl@>=0.5.0 <0.6.0",
-      "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz"
+    "is-number": {
+      "version": "1.1.2",
+      "from": "https://registry.npmjs.org/is-number/-/is-number-1.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-1.1.2.tgz"
     },
-    "window-size": {
-      "version": "0.1.2",
-      "from": "window-size@>=0.1.2 <0.2.0",
-      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.2.tgz"
+    "is-primitive": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz"
     },
-    "wordwrap": {
-      "version": "1.0.0",
-      "from": "wordwrap@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
+    "is-utf8": {
+      "version": "0.2.0",
+      "from": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
     },
-    "worker-farm": {
-      "version": "1.3.1",
-      "from": "worker-farm@>=1.3.1 <2.0.0",
-      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.3.1.tgz",
-      "dependencies": {
-        "errno": {
-          "version": "0.1.4",
-          "from": "errno@>=0.1.1 <0.2.0-0",
-          "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
-          "dependencies": {
-            "prr": {
-              "version": "0.0.0",
-              "from": "prr@>=0.0.0 <0.1.0",
-              "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz"
-            }
-          }
-        },
-        "xtend": {
-          "version": "4.0.0",
-          "from": "xtend@>=4.0.0 <4.1.0-0",
-          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-        }
-      }
+    "isarray": {
+      "version": "0.0.1",
+      "from": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
     },
-    "wrappy": {
-      "version": "1.0.1",
-      "from": "wrappy@>=1.0.0 <2.0.0",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+    "isobject": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz"
     },
-    "ws": {
-      "version": "0.8.0",
-      "from": "ws@0.8.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-0.8.0.tgz",
+    "jest-cli": {
+      "version": "0.9.0-fb2",
+      "from": "jest-cli@next",
       "dependencies": {
-        "bufferutil": {
-          "version": "1.2.1",
-          "from": "bufferutil@>=1.2.0 <1.3.0",
-          "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.2.1.tgz",
+        "cover": {
+          "version": "0.2.9",
+          "from": "cover@>=0.2.9 <0.3.0",
+          "resolved": "https://registry.npmjs.org/cover/-/cover-0.2.9.tgz",
           "dependencies": {
-            "bindings": {
-              "version": "1.2.1",
-              "from": "bindings@>=1.2.0 <1.3.0",
-              "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
+            "cli-table": {
+              "version": "0.0.2",
+              "from": "cli-table@>=0.0.0 <0.1.0",
+              "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.0.2.tgz",
+              "dependencies": {
+                "colors": {
+                  "version": "0.3.0",
+                  "from": "colors@0.3.0",
+                  "resolved": "https://registry.npmjs.org/colors/-/colors-0.3.0.tgz"
+                }
+              }
             },
-            "nan": {
-              "version": "2.0.8",
-              "from": "nan@>=2.0.5 <3.0.0",
-              "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz"
+            "underscore": {
+              "version": "1.2.4",
+              "from": "underscore@>=1.2.0 <1.3.0",
+              "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.2.4.tgz"
+            },
+            "underscore.string": {
+              "version": "2.0.0",
+              "from": "underscore.string@>=2.0.0 <2.1.0",
+              "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.0.0.tgz"
+            },
+            "which": {
+              "version": "1.0.9",
+              "from": "which@>=1.0.0 <1.1.0",
+              "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz"
             }
           }
         },
-        "options": {
-          "version": "0.0.6",
-          "from": "options@>=0.0.5",
-          "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
+        "diff": {
+          "version": "2.2.1",
+          "from": "diff@>=2.1.1 <3.0.0",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.1.tgz"
         },
-        "ultron": {
-          "version": "1.0.2",
-          "from": "ultron@>=1.0.0 <1.1.0",
-          "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
+        "graceful-fs": {
+          "version": "4.1.3",
+          "from": "graceful-fs@>=4.1.3 <5.0.0",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz"
         },
-        "utf-8-validate": {
-          "version": "1.2.1",
-          "from": "utf-8-validate@>=1.2.0 <1.3.0",
-          "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.2.1.tgz",
+        "istanbul": {
+          "version": "0.4.2",
+          "from": "istanbul@>=0.4.2 <0.5.0",
+          "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.2.tgz",
           "dependencies": {
-            "bindings": {
-              "version": "1.2.1",
-              "from": "bindings@>=1.2.0 <1.3.0",
-              "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
+            "abbrev": {
+              "version": "1.0.7",
+              "from": "abbrev@>=1.0.0 <1.1.0",
+              "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
             },
-            "nan": {
-              "version": "2.0.8",
-              "from": "nan@>=2.0.5 <3.0.0",
-              "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz"
-            }
-          }
-        }
-      }
-    },
-    "xtend": {
-      "version": "4.0.1",
-      "from": "xtend@>=4.0.0 <5.0.0",
-      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
-    },
-    "y18n": {
-      "version": "3.2.0",
-      "from": "y18n@>=3.2.0 <4.0.0",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.0.tgz"
-    },
-    "yargs": {
-      "version": "3.24.0",
-      "from": "yargs@3.24.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.24.0.tgz",
-      "dependencies": {
-        "camelcase": {
-          "version": "1.2.1",
-          "from": "camelcase@>=1.2.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-        },
-        "cliui": {
-          "version": "2.1.0",
-          "from": "cliui@>=2.1.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
-          "dependencies": {
-            "center-align": {
-              "version": "0.1.1",
-              "from": "center-align@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.1.tgz",
-              "dependencies": {
-                "align-text": {
-                  "version": "0.1.3",
-                  "from": "align-text@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz",
-                  "dependencies": {
-                    "kind-of": {
-                      "version": "2.0.1",
-                      "from": "kind-of@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
-                      "dependencies": {
-                        "is-buffer": {
-                          "version": "1.0.2",
-                          "from": "is-buffer@>=1.0.2 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.0.2.tgz"
-                        }
-                      }
-                    },
-                    "longest": {
-                      "version": "1.0.1",
-                      "from": "longest@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
-                    },
-                    "repeat-string": {
-                      "version": "1.5.2",
-                      "from": "repeat-string@>=1.5.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz"
-                    }
-                  }
-                }
-              }
+            "async": {
+              "version": "1.5.2",
+              "from": "async@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
             },
-            "right-align": {
-              "version": "0.1.3",
-              "from": "right-align@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+            "escodegen": {
+              "version": "1.7.1",
+              "from": "escodegen@>=1.7.0 <1.8.0",
+              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz",
               "dependencies": {
-                "align-text": {
-                  "version": "0.1.3",
-                  "from": "align-text@>=0.1.1 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz",
+                "estraverse": {
+                  "version": "1.9.3",
+                  "from": "estraverse@>=1.9.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
+                },
+                "esprima": {
+                  "version": "1.2.5",
+                  "from": "esprima@>=1.2.2 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
+                },
+                "optionator": {
+                  "version": "0.5.0",
+                  "from": "optionator@>=0.5.0 <0.6.0",
+                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
                   "dependencies": {
-                    "kind-of": {
-                      "version": "2.0.1",
-                      "from": "kind-of@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
+                    "prelude-ls": {
+                      "version": "1.1.2",
+                      "from": "prelude-ls@>=1.1.1 <1.2.0",
+                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+                    },
+                    "deep-is": {
+                      "version": "0.1.3",
+                      "from": "deep-is@>=0.1.3 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+                    },
+                    "wordwrap": {
+                      "version": "0.0.3",
+                      "from": "wordwrap@>=0.0.2 <0.1.0",
+                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+                    },
+                    "type-check": {
+                      "version": "0.3.2",
+                      "from": "type-check@>=0.3.1 <0.4.0",
+                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
+                    },
+                    "levn": {
+                      "version": "0.2.5",
+                      "from": "levn@>=0.2.5 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
+                    },
+                    "fast-levenshtein": {
+                      "version": "1.0.7",
+                      "from": "fast-levenshtein@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
+                    }
+                  }
+                },
+                "source-map": {
+                  "version": "0.2.0",
+                  "from": "source-map@>=0.2.0 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz"
+                }
+              }
+            },
+            "esprima": {
+              "version": "2.7.2",
+              "from": "esprima@>=2.7.0 <2.8.0",
+              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
+            },
+            "fileset": {
+              "version": "0.2.1",
+              "from": "fileset@>=0.2.0 <0.3.0",
+              "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz"
+            },
+            "handlebars": {
+              "version": "4.0.5",
+              "from": "handlebars@>=4.0.1 <5.0.0",
+              "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.5.tgz",
+              "dependencies": {
+                "uglify-js": {
+                  "version": "2.6.1",
+                  "from": "uglify-js@>=2.6.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.1.tgz",
+                  "dependencies": {
+                    "async": {
+                      "version": "0.2.10",
+                      "from": "async@>=0.2.6 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+                    },
+                    "source-map": {
+                      "version": "0.5.3",
+                      "from": "source-map@>=0.5.1 <0.6.0",
+                      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
+                    },
+                    "uglify-to-browserify": {
+                      "version": "1.0.2",
+                      "from": "uglify-to-browserify@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz"
+                    },
+                    "yargs": {
+                      "version": "3.10.0",
+                      "from": "yargs@>=3.10.0 <3.11.0",
+                      "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
                       "dependencies": {
-                        "is-buffer": {
-                          "version": "1.0.2",
-                          "from": "is-buffer@>=1.0.2 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.0.2.tgz"
+                        "window-size": {
+                          "version": "0.1.0",
+                          "from": "window-size@0.1.0",
+                          "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz"
                         }
                       }
-                    },
-                    "longest": {
-                      "version": "1.0.1",
-                      "from": "longest@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
-                    },
-                    "repeat-string": {
-                      "version": "1.5.2",
-                      "from": "repeat-string@>=1.5.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz"
                     }
                   }
                 }
               }
             },
-            "wordwrap": {
-              "version": "0.0.2",
-              "from": "wordwrap@0.0.2",
-              "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
-            }
-          }
-        },
-        "decamelize": {
-          "version": "1.0.0",
-          "from": "decamelize@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz"
-        },
-        "os-locale": {
-          "version": "1.3.1",
-          "from": "os-locale@>=1.3.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.3.1.tgz",
-          "dependencies": {
-            "lcid": {
-              "version": "1.0.0",
-              "from": "lcid@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+            "js-yaml": {
+              "version": "3.5.3",
+              "from": "js-yaml@>=3.0.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.3.tgz",
+              "dependencies": {
+                "argparse": {
+                  "version": "1.0.6",
+                  "from": "argparse@>=1.0.2 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.6.tgz",
+                  "dependencies": {
+                    "sprintf-js": {
+                      "version": "1.0.3",
+                      "from": "sprintf-js@>=1.0.2 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+                    }
+                  }
+                }
+              }
+            },
+            "nopt": {
+              "version": "3.0.6",
+              "from": "nopt@>=3.0.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz"
+            },
+            "supports-color": {
+              "version": "3.1.2",
+              "from": "supports-color@>=3.1.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
               "dependencies": {
-                "invert-kv": {
+                "has-flag": {
                   "version": "1.0.0",
-                  "from": "invert-kv@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz"
+                  "from": "has-flag@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
                 }
               }
             }
           }
         },
-        "window-size": {
-          "version": "0.1.2",
-          "from": "window-size@>=0.1.2 <0.2.0",
-          "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.2.tgz"
-        },
-        "y18n": {
-          "version": "3.1.0",
-          "from": "y18n@>=3.1.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.1.0.tgz"
-        }
-      }
-    },
-    "yeoman-environment": {
-      "version": "1.2.7",
-      "from": "yeoman-environment@>=1.2.7 <2.0.0",
-      "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.2.7.tgz",
-      "dependencies": {
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.0.0 <2.0.0"
-        },
-        "debug": {
-          "version": "2.2.0",
-          "from": "debug@>=2.0.0 <3.0.0"
-        },
-        "diff": {
-          "version": "1.4.0",
-          "from": "diff@>=1.0.4 <2.0.0",
-          "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz"
-        },
-        "escape-string-regexp": {
-          "version": "1.0.3",
-          "from": "escape-string-regexp@>=1.0.2 <2.0.0",
-          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
-        },
-        "globby": {
-          "version": "2.1.0",
-          "from": "globby@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz",
+        "jsdom": {
+          "version": "7.2.2",
+          "from": "jsdom@>=7.2.0 <8.0.0",
+          "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
           "dependencies": {
-            "array-union": {
-              "version": "1.0.1",
-              "from": "array-union@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
+            "abab": {
+              "version": "1.0.3",
+              "from": "abab@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.3.tgz"
+            },
+            "acorn": {
+              "version": "2.7.0",
+              "from": "acorn@>=2.4.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz"
+            },
+            "acorn-globals": {
+              "version": "1.0.9",
+              "from": "acorn-globals@>=1.0.4 <2.0.0",
+              "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz"
+            },
+            "cssom": {
+              "version": "0.3.1",
+              "from": "cssom@>=0.3.0 <0.4.0",
+              "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.1.tgz"
+            },
+            "cssstyle": {
+              "version": "0.2.34",
+              "from": "cssstyle@>=0.2.29 <0.3.0",
+              "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.34.tgz"
+            },
+            "escodegen": {
+              "version": "1.8.0",
+              "from": "escodegen@>=1.6.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.0.tgz",
               "dependencies": {
-                "array-uniq": {
-                  "version": "1.0.2",
-                  "from": "array-uniq@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
+                "estraverse": {
+                  "version": "1.9.3",
+                  "from": "estraverse@>=1.9.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
+                },
+                "esprima": {
+                  "version": "2.7.2",
+                  "from": "esprima@>=2.7.1 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
+                },
+                "optionator": {
+                  "version": "0.8.1",
+                  "from": "optionator@>=0.8.1 <0.9.0",
+                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.1.tgz",
+                  "dependencies": {
+                    "prelude-ls": {
+                      "version": "1.1.2",
+                      "from": "prelude-ls@>=1.1.1 <1.2.0",
+                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+                    },
+                    "deep-is": {
+                      "version": "0.1.3",
+                      "from": "deep-is@>=0.1.3 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+                    },
+                    "type-check": {
+                      "version": "0.3.2",
+                      "from": "type-check@>=0.3.1 <0.4.0",
+                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
+                    },
+                    "levn": {
+                      "version": "0.3.0",
+                      "from": "levn@>=0.3.0 <0.4.0",
+                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz"
+                    },
+                    "fast-levenshtein": {
+                      "version": "1.1.3",
+                      "from": "fast-levenshtein@>=1.1.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.3.tgz"
+                    }
+                  }
+                },
+                "source-map": {
+                  "version": "0.2.0",
+                  "from": "source-map@>=0.2.0 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz"
                 }
               }
             },
-            "async": {
-              "version": "1.4.2",
-              "from": "async@>=1.4.2 <2.0.0",
-              "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz"
+            "nwmatcher": {
+              "version": "1.3.7",
+              "from": "nwmatcher@>=1.3.7 <2.0.0",
+              "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.7.tgz"
             },
-            "glob": {
-              "version": "5.0.14",
-              "from": "glob@>=5.0.3 <6.0.0",
-              "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.14.tgz",
+            "parse5": {
+              "version": "1.5.1",
+              "from": "parse5@>=1.5.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz"
+            },
+            "request": {
+              "version": "2.69.0",
+              "from": "request@>=2.55.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz",
               "dependencies": {
-                "inflight": {
-                  "version": "1.0.4",
-                  "from": "inflight@>=1.0.4 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
+                "aws-sign2": {
+                  "version": "0.6.0",
+                  "from": "aws-sign2@>=0.6.0 <0.7.0",
+                  "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz"
+                },
+                "aws4": {
+                  "version": "1.2.1",
+                  "from": "aws4@>=1.2.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.2.1.tgz",
                   "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                    "lru-cache": {
+                      "version": "2.7.3",
+                      "from": "lru-cache@>=2.6.5 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
                     }
                   }
                 },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "minimatch": {
-                  "version": "2.0.10",
-                  "from": "minimatch@>=2.0.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+                "bl": {
+                  "version": "1.0.3",
+                  "from": "bl@>=1.0.0 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz",
                   "dependencies": {
-                    "brace-expansion": {
-                      "version": "1.1.0",
-                      "from": "brace-expansion@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.0.tgz",
+                    "readable-stream": {
+                      "version": "2.0.5",
+                      "from": "readable-stream@>=2.0.5 <2.1.0",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz",
                       "dependencies": {
-                        "balanced-match": {
-                          "version": "0.2.0",
-                          "from": "balanced-match@>=0.2.0 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
-                        },
-                        "concat-map": {
-                          "version": "0.0.1",
-                          "from": "concat-map@0.0.1",
-                          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+                        "process-nextick-args": {
+                          "version": "1.0.6",
+                          "from": "process-nextick-args@>=1.0.6 <1.1.0",
+                          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz"
                         }
                       }
                     }
                   }
                 },
-                "once": {
-                  "version": "1.3.2",
-                  "from": "once@>=1.3.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
+                "caseless": {
+                  "version": "0.11.0",
+                  "from": "caseless@>=0.11.0 <0.12.0",
+                  "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
+                },
+                "combined-stream": {
+                  "version": "1.0.5",
+                  "from": "combined-stream@>=1.0.5 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
                   "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                    "delayed-stream": {
+                      "version": "1.0.0",
+                      "from": "delayed-stream@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
                     }
                   }
                 },
-                "path-is-absolute": {
-                  "version": "1.0.0",
-                  "from": "path-is-absolute@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
-                }
-              }
-            },
-            "object-assign": {
-              "version": "3.0.0",
-              "from": "object-assign@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-            }
-          }
-        },
-        "grouped-queue": {
-          "version": "0.3.0",
-          "from": "grouped-queue@>=0.3.0 <0.4.0",
-          "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.0.tgz",
-          "dependencies": {
-            "lodash": {
-              "version": "2.4.2",
-              "from": "lodash@>=2.4.1 <2.5.0",
-              "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz"
+                "extend": {
+                  "version": "3.0.0",
+                  "from": "extend@>=3.0.0 <3.1.0",
+                  "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
+                },
+                "forever-agent": {
+                  "version": "0.6.1",
+                  "from": "forever-agent@>=0.6.1 <0.7.0",
+                  "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
+                },
+                "form-data": {
+                  "version": "1.0.0-rc3",
+                  "from": "form-data@>=1.0.0-rc3 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
+                  "dependencies": {
+                    "async": {
+                      "version": "1.5.2",
+                      "from": "async@>=1.4.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
+                    }
+                  }
+                },
+                "har-validator": {
+                  "version": "2.0.6",
+                  "from": "har-validator@>=2.0.6 <2.1.0",
+                  "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+                  "dependencies": {
+                    "is-my-json-valid": {
+                      "version": "2.13.0",
+                      "from": "is-my-json-valid@>=2.12.4 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.0.tgz",
+                      "dependencies": {
+                        "generate-function": {
+                          "version": "2.0.0",
+                          "from": "generate-function@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
+                        },
+                        "generate-object-property": {
+                          "version": "1.2.0",
+                          "from": "generate-object-property@>=1.1.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+                          "dependencies": {
+                            "is-property": {
+                              "version": "1.0.2",
+                              "from": "is-property@>=1.0.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
+                            }
+                          }
+                        },
+                        "jsonpointer": {
+                          "version": "2.0.0",
+                          "from": "jsonpointer@2.0.0",
+                          "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz"
+                        }
+                      }
+                    },
+                    "pinkie-promise": {
+                      "version": "2.0.0",
+                      "from": "pinkie-promise@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
+                      "dependencies": {
+                        "pinkie": {
+                          "version": "2.0.4",
+                          "from": "pinkie@>=2.0.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
+                        }
+                      }
+                    }
+                  }
+                },
+                "hawk": {
+                  "version": "3.1.3",
+                  "from": "hawk@>=3.1.0 <3.2.0",
+                  "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+                  "dependencies": {
+                    "hoek": {
+                      "version": "2.16.3",
+                      "from": "hoek@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
+                    },
+                    "boom": {
+                      "version": "2.10.1",
+                      "from": "boom@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
+                    },
+                    "cryptiles": {
+                      "version": "2.0.5",
+                      "from": "cryptiles@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
+                    },
+                    "sntp": {
+                      "version": "1.0.9",
+                      "from": "sntp@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+                    }
+                  }
+                },
+                "http-signature": {
+                  "version": "1.1.1",
+                  "from": "http-signature@>=1.1.0 <1.2.0",
+                  "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+                  "dependencies": {
+                    "assert-plus": {
+                      "version": "0.2.0",
+                      "from": "assert-plus@>=0.2.0 <0.3.0",
+                      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
+                    },
+                    "jsprim": {
+                      "version": "1.2.2",
+                      "from": "jsprim@>=1.2.2 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz",
+                      "dependencies": {
+                        "extsprintf": {
+                          "version": "1.0.2",
+                          "from": "extsprintf@1.0.2",
+                          "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
+                        },
+                        "json-schema": {
+                          "version": "0.2.2",
+                          "from": "json-schema@0.2.2",
+                          "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
+                        },
+                        "verror": {
+                          "version": "1.3.6",
+                          "from": "verror@1.3.6",
+                          "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
+                        }
+                      }
+                    },
+                    "sshpk": {
+                      "version": "1.7.4",
+                      "from": "sshpk@>=1.7.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz",
+                      "dependencies": {
+                        "asn1": {
+                          "version": "0.2.3",
+                          "from": "asn1@>=0.2.3 <0.3.0",
+                          "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
+                        },
+                        "dashdash": {
+                          "version": "1.13.0",
+                          "from": "dashdash@>=1.10.1 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz",
+                          "dependencies": {
+                            "assert-plus": {
+                              "version": "1.0.0",
+                              "from": "assert-plus@>=1.0.0 <2.0.0",
+                              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz"
+                            }
+                          }
+                        },
+                        "jsbn": {
+                          "version": "0.1.0",
+                          "from": "jsbn@>=0.1.0 <0.2.0",
+                          "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
+                        },
+                        "tweetnacl": {
+                          "version": "0.13.3",
+                          "from": "tweetnacl@>=0.13.0 <1.0.0",
+                          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz"
+                        },
+                        "jodid25519": {
+                          "version": "1.0.2",
+                          "from": "jodid25519@>=1.0.0 <2.0.0",
+                          "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz"
+                        },
+                        "ecc-jsbn": {
+                          "version": "0.1.1",
+                          "from": "ecc-jsbn@>=0.0.1 <1.0.0",
+                          "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
+                        }
+                      }
+                    }
+                  }
+                },
+                "is-typedarray": {
+                  "version": "1.0.0",
+                  "from": "is-typedarray@>=1.0.0 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
+                },
+                "isstream": {
+                  "version": "0.1.2",
+                  "from": "isstream@>=0.1.2 <0.2.0",
+                  "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
+                },
+                "json-stringify-safe": {
+                  "version": "5.0.1",
+                  "from": "json-stringify-safe@>=5.0.1 <5.1.0",
+                  "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
+                },
+                "mime-types": {
+                  "version": "2.1.10",
+                  "from": "mime-types@>=2.1.7 <2.2.0",
+                  "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz",
+                  "dependencies": {
+                    "mime-db": {
+                      "version": "1.22.0",
+                      "from": "mime-db@>=1.22.0 <1.23.0",
+                      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz"
+                    }
+                  }
+                },
+                "node-uuid": {
+                  "version": "1.4.7",
+                  "from": "node-uuid@>=1.4.7 <1.5.0",
+                  "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
+                },
+                "oauth-sign": {
+                  "version": "0.8.1",
+                  "from": "oauth-sign@>=0.8.0 <0.9.0",
+                  "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz"
+                },
+                "qs": {
+                  "version": "6.0.2",
+                  "from": "qs@>=6.0.2 <6.1.0",
+                  "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.2.tgz"
+                },
+                "stringstream": {
+                  "version": "0.0.5",
+                  "from": "stringstream@>=0.0.4 <0.1.0",
+                  "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
+                },
+                "tunnel-agent": {
+                  "version": "0.4.2",
+                  "from": "tunnel-agent@>=0.4.1 <0.5.0",
+                  "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz"
+                }
+              }
             },
-            "setimmediate": {
-              "version": "1.0.2",
-              "from": "setimmediate@>=1.0.1 <1.1.0",
-              "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.2.tgz"
-            }
-          }
-        },
-        "inquirer": {
-          "version": "0.8.5",
-          "from": "inquirer@>=0.8.0 <0.9.0",
-          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
-          "dependencies": {
-            "ansi-regex": {
-              "version": "1.1.1",
-              "from": "ansi-regex@>=1.1.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz"
+            "sax": {
+              "version": "1.1.5",
+              "from": "sax@>=1.1.4 <2.0.0",
+              "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.5.tgz"
             },
-            "cli-width": {
-              "version": "1.0.1",
-              "from": "cli-width@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz"
+            "symbol-tree": {
+              "version": "3.1.4",
+              "from": "symbol-tree@>=3.1.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.4.tgz"
             },
-            "figures": {
-              "version": "1.3.5",
-              "from": "figures@>=1.3.5 <2.0.0",
-              "resolved": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz"
+            "tough-cookie": {
+              "version": "2.2.1",
+              "from": "tough-cookie@>=2.2.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.1.tgz"
             },
-            "readline2": {
-              "version": "0.1.1",
-              "from": "readline2@>=0.1.1 <0.2.0",
-              "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+            "webidl-conversions": {
+              "version": "2.0.1",
+              "from": "webidl-conversions@>=2.0.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz"
+            },
+            "whatwg-url-compat": {
+              "version": "0.6.5",
+              "from": "whatwg-url-compat@>=0.6.5 <0.7.0",
+              "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz",
               "dependencies": {
-                "mute-stream": {
-                  "version": "0.0.4",
-                  "from": "mute-stream@0.0.4",
-                  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz"
-                },
-                "strip-ansi": {
-                  "version": "2.0.1",
-                  "from": "strip-ansi@>=2.0.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz"
+                "tr46": {
+                  "version": "0.0.3",
+                  "from": "tr46@>=0.0.1 <0.1.0",
+                  "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
                 }
               }
             },
-            "rx": {
-              "version": "2.5.3",
-              "from": "rx@>=2.4.3 <3.0.0",
-              "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz"
-            },
-            "through": {
-              "version": "2.3.8",
-              "from": "through@>=2.3.6 <3.0.0",
-              "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+            "xml-name-validator": {
+              "version": "2.0.1",
+              "from": "xml-name-validator@>=2.0.1 <3.0.0",
+              "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz"
             }
           }
         },
-        "lodash": {
-          "version": "3.10.1",
-          "from": "lodash@>=3.1.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-        },
-        "log-symbols": {
-          "version": "1.0.2",
-          "from": "log-symbols@>=1.0.1 <2.0.0",
-          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz"
-        },
-        "mem-fs": {
-          "version": "1.1.0",
-          "from": "mem-fs@>=1.1.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.0.tgz",
+        "node-haste": {
+          "version": "2.2.0",
+          "from": "node-haste@>=2.2.0 <3.0.0",
           "dependencies": {
-            "through2": {
-              "version": "0.6.5",
-              "from": "through2@>=0.6.3 <0.7.0",
-              "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+            "sane": {
+              "version": "1.3.1",
+              "from": "sane@>=1.3.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/sane/-/sane-1.3.1.tgz",
               "dependencies": {
-                "readable-stream": {
-                  "version": "1.0.33",
-                  "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
-                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                "exec-sh": {
+                  "version": "0.2.0",
+                  "from": "exec-sh@>=0.2.0 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
                   "dependencies": {
-                    "core-util-is": {
-                      "version": "1.0.1",
-                      "from": "core-util-is@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                    },
-                    "inherits": {
-                      "version": "2.0.1",
-                      "from": "inherits@>=2.0.1 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                    },
-                    "isarray": {
-                      "version": "0.0.1",
-                      "from": "isarray@0.0.1",
-                      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                    "merge": {
+                      "version": "1.2.0",
+                      "from": "merge@>=1.1.3 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz"
+                    }
+                  }
+                },
+                "fb-watchman": {
+                  "version": "1.9.0",
+                  "from": "fb-watchman@>=1.8.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz"
+                },
+                "minimatch": {
+                  "version": "0.2.14",
+                  "from": "minimatch@>=0.2.14 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
+                  "dependencies": {
+                    "lru-cache": {
+                      "version": "2.7.3",
+                      "from": "lru-cache@>=2.0.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
                     },
-                    "string_decoder": {
-                      "version": "0.10.31",
-                      "from": "string_decoder@>=0.10.0 <0.11.0",
-                      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                    "sigmund": {
+                      "version": "1.0.1",
+                      "from": "sigmund@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
                     }
                   }
                 },
-                "xtend": {
-                  "version": "4.0.0",
-                  "from": "xtend@>=4.0.0 <4.1.0-0",
-                  "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                "walker": {
+                  "version": "1.0.7",
+                  "from": "walker@>=1.0.5 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
+                  "dependencies": {
+                    "makeerror": {
+                      "version": "1.0.11",
+                      "from": "makeerror@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
+                      "dependencies": {
+                        "tmpl": {
+                          "version": "1.0.4",
+                          "from": "tmpl@>=1.0.0 <1.1.0",
+                          "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz"
+                        }
+                      }
+                    }
+                  }
+                },
+                "watch": {
+                  "version": "0.10.0",
+                  "from": "watch@>=0.10.0 <0.11.0",
+                  "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz"
                 }
               }
             },
-            "vinyl": {
-              "version": "0.4.6",
-              "from": "vinyl@>=0.4.3 <0.5.0",
-              "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
-              "dependencies": {
-                "clone": {
-                  "version": "0.2.0",
-                  "from": "clone@>=0.2.0 <0.3.0",
-                  "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
-                },
-                "clone-stats": {
-                  "version": "0.0.1",
-                  "from": "clone-stats@>=0.0.1 <0.0.2",
-                  "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                }
-              }
-            },
-            "vinyl-file": {
-              "version": "1.2.1",
-              "from": "vinyl-file@>=1.1.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-1.2.1.tgz",
-              "dependencies": {
-                "graceful-fs": {
-                  "version": "4.1.2",
-                  "from": "graceful-fs@>=4.1.2 <5.0.0"
-                },
-                "strip-bom": {
-                  "version": "2.0.0",
-                  "from": "strip-bom@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-                  "dependencies": {
-                    "is-utf8": {
-                      "version": "0.2.0",
-                      "from": "is-utf8@>=0.2.0 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                    }
-                  }
-                },
-                "strip-bom-stream": {
-                  "version": "1.0.0",
-                  "from": "strip-bom-stream@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz",
-                  "dependencies": {
-                    "first-chunk-stream": {
-                      "version": "1.0.0",
-                      "from": "first-chunk-stream@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz"
-                    }
-                  }
-                },
-                "vinyl": {
-                  "version": "0.5.3",
-                  "from": "vinyl@>=0.5.0 <0.6.0",
-                  "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
-                  "dependencies": {
-                    "clone": {
-                      "version": "1.0.2",
-                      "from": "clone@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
-                    },
-                    "clone-stats": {
-                      "version": "0.0.1",
-                      "from": "clone-stats@>=0.0.1 <0.0.2",
-                      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                    },
-                    "replace-ext": {
-                      "version": "0.0.1",
-                      "from": "replace-ext@0.0.1",
-                      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
-                    }
-                  }
-                }
-              }
+            "throat": {
+              "version": "2.0.2",
+              "from": "throat@>=2.0.2 <3.0.0",
+              "resolved": "https://registry.npmjs.org/throat/-/throat-2.0.2.tgz"
             }
           }
         },
-        "text-table": {
-          "version": "0.2.0",
-          "from": "text-table@>=0.2.0 <0.3.0",
-          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
-        },
-        "untildify": {
-          "version": "2.1.0",
-          "from": "untildify@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz",
+        "which": {
+          "version": "1.2.4",
+          "from": "which@>=1.1.1 <2.0.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz",
           "dependencies": {
-            "os-homedir": {
-              "version": "1.0.1",
-              "from": "os-homedir@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
+            "is-absolute": {
+              "version": "0.1.7",
+              "from": "is-absolute@>=0.1.7 <0.2.0",
+              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+              "dependencies": {
+                "is-relative": {
+                  "version": "0.1.3",
+                  "from": "is-relative@>=0.1.0 <0.2.0",
+                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                }
+              }
+            },
+            "isexe": {
+              "version": "1.1.2",
+              "from": "isexe@>=1.1.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz"
             }
           }
         }
       }
     },
-    "yeoman-generator": {
-      "version": "0.20.3",
-      "from": "yeoman-generator@0.20.3",
-      "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-0.20.3.tgz",
+    "joi": {
+      "version": "6.6.1",
+      "from": "https://registry.npmjs.org/joi/-/joi-6.6.1.tgz",
+      "resolved": "https://registry.npmjs.org/joi/-/joi-6.6.1.tgz",
       "dependencies": {
-        "async": {
-          "version": "1.4.2",
-          "from": "async@>=1.4.2 <2.0.0",
-          "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz"
+        "hoek": {
+          "version": "2.14.0",
+          "from": "https://registry.npmjs.org/hoek/-/hoek-2.14.0.tgz",
+          "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.14.0.tgz"
         },
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@>=1.0.0 <2.0.0"
+        "topo": {
+          "version": "1.0.3",
+          "from": "https://registry.npmjs.org/topo/-/topo-1.0.3.tgz",
+          "resolved": "https://registry.npmjs.org/topo/-/topo-1.0.3.tgz"
         },
-        "class-extend": {
-          "version": "0.1.2",
-          "from": "class-extend@>=0.1.0 <0.2.0",
-          "resolved": "https://registry.npmjs.org/class-extend/-/class-extend-0.1.2.tgz",
-          "dependencies": {
-            "object-assign": {
-              "version": "2.1.1",
-              "from": "object-assign@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-            }
-          }
+        "isemail": {
+          "version": "1.1.1",
+          "from": "https://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.1.1.tgz"
         },
-        "cli-table": {
-          "version": "0.3.1",
-          "from": "cli-table@>=0.3.1 <0.4.0",
-          "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
-          "dependencies": {
-            "colors": {
-              "version": "1.0.3",
-              "from": "colors@1.0.3",
-              "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz"
-            }
-          }
+        "moment": {
+          "version": "2.10.6",
+          "from": "https://registry.npmjs.org/moment/-/moment-2.10.6.tgz",
+          "resolved": "https://registry.npmjs.org/moment/-/moment-2.10.6.tgz"
+        }
+      }
+    },
+    "js-tokens": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz"
+    },
+    "jsesc": {
+      "version": "0.5.0",
+      "from": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz"
+    },
+    "json-stable-stringify": {
+      "version": "1.0.1",
+      "from": "json-stable-stringify@>=1.0.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
+    },
+    "json5": {
+      "version": "0.4.0",
+      "from": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz"
+    },
+    "jsonify": {
+      "version": "0.0.0",
+      "from": "jsonify@>=0.0.0 <0.1.0",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
+    },
+    "jstransform": {
+      "version": "11.0.3",
+      "from": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
+      "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz",
+      "dependencies": {
+        "base62": {
+          "version": "1.1.0",
+          "from": "https://registry.npmjs.org/base62/-/base62-1.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/base62/-/base62-1.1.0.tgz"
         },
-        "cross-spawn": {
-          "version": "2.0.0",
-          "from": "cross-spawn@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-2.0.0.tgz",
+        "commoner": {
+          "version": "0.10.3",
+          "from": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
+          "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
           "dependencies": {
-            "cross-spawn-async": {
-              "version": "2.0.0",
-              "from": "cross-spawn-async@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.0.0.tgz",
+            "q": {
+              "version": "1.1.2",
+              "from": "https://registry.npmjs.org/q/-/q-1.1.2.tgz",
+              "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz"
+            },
+            "recast": {
+              "version": "0.10.32",
+              "from": "https://registry.npmjs.org/recast/-/recast-0.10.32.tgz",
+              "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.32.tgz",
               "dependencies": {
-                "lru-cache": {
-                  "version": "2.7.0",
-                  "from": "lru-cache@>=2.6.5 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz"
+                "esprima-fb": {
+                  "version": "15001.1001.0-dev-harmony-fb",
+                  "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz",
+                  "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz"
                 },
-                "which": {
-                  "version": "1.2.0",
-                  "from": "which@>=1.1.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/which/-/which-1.2.0.tgz",
-                  "dependencies": {
-                    "is-absolute": {
-                      "version": "0.1.7",
-                      "from": "is-absolute@>=0.1.7 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
-                      "dependencies": {
-                        "is-relative": {
-                          "version": "0.1.3",
-                          "from": "is-relative@>=0.1.0 <0.2.0",
-                          "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
-                        }
-                      }
-                    }
-                  }
+                "ast-types": {
+                  "version": "0.8.11",
+                  "from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.11.tgz",
+                  "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.11.tgz"
                 }
               }
             },
-            "spawn-sync": {
-              "version": "1.0.13",
-              "from": "spawn-sync@>=1.0.13 <2.0.0",
-              "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.13.tgz",
+            "commander": {
+              "version": "2.5.1",
+              "from": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz"
+            },
+            "graceful-fs": {
+              "version": "3.0.8",
+              "from": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz",
+              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
+            },
+            "glob": {
+              "version": "4.2.2",
+              "from": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
               "dependencies": {
-                "concat-stream": {
-                  "version": "1.5.0",
-                  "from": "concat-stream@>=1.4.7 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+                "minimatch": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
                   "dependencies": {
-                    "inherits": {
-                      "version": "2.0.1",
-                      "from": "inherits@>=2.0.1 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                    },
-                    "readable-stream": {
-                      "version": "2.0.2",
-                      "from": "readable-stream@>=2.0.0 <2.1.0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
-                      "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                        },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                        },
-                        "process-nextick-args": {
-                          "version": "1.0.3",
-                          "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
-                        },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                        },
-                        "util-deprecate": {
-                          "version": "1.0.2",
-                          "from": "util-deprecate@>=1.0.1 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
-                        }
-                      }
+                    "lru-cache": {
+                      "version": "2.6.5",
+                      "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz",
+                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
                     },
-                    "typedarray": {
-                      "version": "0.0.6",
-                      "from": "typedarray@>=0.0.5 <0.1.0",
-                      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+                    "sigmund": {
+                      "version": "1.0.1",
+                      "from": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
                     }
                   }
-                },
-                "os-shim": {
-                  "version": "0.1.3",
-                  "from": "os-shim@>=0.1.2 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz"
                 }
               }
+            },
+            "install": {
+              "version": "0.1.8",
+              "from": "https://registry.npmjs.org/install/-/install-0.1.8.tgz",
+              "resolved": "https://registry.npmjs.org/install/-/install-0.1.8.tgz"
+            },
+            "iconv-lite": {
+              "version": "0.4.11",
+              "from": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz",
+              "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz"
             }
           }
         },
-        "dargs": {
-          "version": "4.0.1",
-          "from": "dargs@>=4.0.0 <5.0.0",
-          "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.0.1.tgz",
-          "dependencies": {
-            "number-is-nan": {
-              "version": "1.0.0",
-              "from": "number-is-nan@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-            }
-          }
+        "esprima-fb": {
+          "version": "15001.1.0-dev-harmony-fb",
+          "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz"
         },
-        "dateformat": {
-          "version": "1.0.11",
-          "from": "dateformat@>=1.0.11 <2.0.0",
-          "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.11.tgz",
-          "dependencies": {
-            "get-stdin": {
-              "version": "5.0.0",
-              "from": "get-stdin@*",
-              "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.0.tgz"
-            },
-            "meow": {
-              "version": "3.4.2",
-              "from": "meow@*",
-              "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
-              "dependencies": {
-                "camelcase-keys": {
-                  "version": "1.0.0",
-                  "from": "camelcase-keys@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
-                  "dependencies": {
-                    "camelcase": {
-                      "version": "1.2.1",
-                      "from": "camelcase@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-                    },
-                    "map-obj": {
-                      "version": "1.0.1",
-                      "from": "map-obj@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-                    }
-                  }
-                },
-                "loud-rejection": {
-                  "version": "1.0.0",
-                  "from": "loud-rejection@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-                },
-                "minimist": {
-                  "version": "1.2.0",
-                  "from": "minimist@>=1.1.3 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                },
-                "normalize-package-data": {
-                  "version": "2.3.4",
-                  "from": "normalize-package-data@>=2.3.4 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+        "object-assign": {
+          "version": "2.1.1",
+          "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+        }
+      }
+    },
+    "kind-of": {
+      "version": "2.0.1",
+      "from": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz"
+    },
+    "lazy-cache": {
+      "version": "0.2.4",
+      "from": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.4.tgz",
+      "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.4.tgz"
+    },
+    "lcid": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz"
+    },
+    "left-pad": {
+      "version": "0.0.3",
+      "from": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz",
+      "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz"
+    },
+    "leven": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz"
+    },
+    "line-numbers": {
+      "version": "0.2.0",
+      "from": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz"
+    },
+    "load-json-file": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz"
+    },
+    "lodash": {
+      "version": "3.10.1",
+      "from": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
+    },
+    "lodash._baseassign": {
+      "version": "3.2.0",
+      "from": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz"
+    },
+    "lodash._basecopy": {
+      "version": "3.0.1",
+      "from": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
+    },
+    "lodash._baseflatten": {
+      "version": "3.1.4",
+      "from": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz"
+    },
+    "lodash._basefor": {
+      "version": "3.0.2",
+      "from": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz"
+    },
+    "lodash._basetostring": {
+      "version": "3.0.1",
+      "from": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
+    },
+    "lodash._basevalues": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz"
+    },
+    "lodash._bindcallback": {
+      "version": "3.0.1",
+      "from": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz"
+    },
+    "lodash._createassigner": {
+      "version": "3.1.1",
+      "from": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz"
+    },
+    "lodash._getnative": {
+      "version": "3.9.1",
+      "from": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
+    },
+    "lodash._isiterateecall": {
+      "version": "3.0.9",
+      "from": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
+    },
+    "lodash._pickbyarray": {
+      "version": "3.0.2",
+      "from": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz"
+    },
+    "lodash._pickbycallback": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz"
+    },
+    "lodash._reescape": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz"
+    },
+    "lodash._reevaluate": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz"
+    },
+    "lodash._reinterpolate": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz"
+    },
+    "lodash.assign": {
+      "version": "3.2.0",
+      "from": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz"
+    },
+    "lodash.escape": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.0.0.tgz"
+    },
+    "lodash.flatten": {
+      "version": "3.0.2",
+      "from": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-3.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-3.0.2.tgz"
+    },
+    "lodash.isarguments": {
+      "version": "3.0.4",
+      "from": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
+    },
+    "lodash.isarray": {
+      "version": "3.0.4",
+      "from": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
+    },
+    "lodash.keys": {
+      "version": "3.1.2",
+      "from": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
+    },
+    "lodash.keysin": {
+      "version": "3.0.8",
+      "from": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz"
+    },
+    "lodash.pick": {
+      "version": "3.1.0",
+      "from": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-3.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-3.1.0.tgz"
+    },
+    "lodash.restparam": {
+      "version": "3.6.1",
+      "from": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
+    },
+    "lodash.template": {
+      "version": "3.6.2",
+      "from": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz"
+    },
+    "lodash.templatesettings": {
+      "version": "3.1.0",
+      "from": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.0.tgz"
+    },
+    "longest": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
+    },
+    "loose-envify": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz"
+    },
+    "loud-rejection": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
+    },
+    "map-obj": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
+    },
+    "meow": {
+      "version": "3.5.0",
+      "from": "https://registry.npmjs.org/meow/-/meow-3.5.0.tgz",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-3.5.0.tgz"
+    },
+    "micromatch": {
+      "version": "2.3.0",
+      "from": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.0.tgz"
+    },
+    "minimatch": {
+      "version": "2.0.10",
+      "from": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz"
+    },
+    "minimist": {
+      "version": "1.2.0",
+      "from": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.8",
+          "from": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+        }
+      }
+    },
+    "module-deps": {
+      "version": "3.9.1",
+      "from": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz",
+      "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-3.9.1.tgz",
+      "dependencies": {
+        "JSONStream": {
+          "version": "1.0.4",
+          "from": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.4.tgz",
+          "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.4.tgz",
+          "dependencies": {
+            "jsonparse": {
+              "version": "1.0.0",
+              "from": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz"
+            }
+          }
+        },
+        "browser-resolve": {
+          "version": "1.9.1",
+          "from": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.9.1.tgz",
+          "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.9.1.tgz"
+        },
+        "concat-stream": {
+          "version": "1.4.10",
+          "from": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz",
+          "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.10.tgz",
+          "dependencies": {
+            "typedarray": {
+              "version": "0.0.6",
+              "from": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+              "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+            }
+          }
+        },
+        "detective": {
+          "version": "4.2.0",
+          "from": "https://registry.npmjs.org/detective/-/detective-4.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/detective/-/detective-4.2.0.tgz",
+          "dependencies": {
+            "escodegen": {
+              "version": "1.6.1",
+              "from": "https://registry.npmjs.org/escodegen/-/escodegen-1.6.1.tgz",
+              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.6.1.tgz",
+              "dependencies": {
+                "estraverse": {
+                  "version": "1.9.3",
+                  "from": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+                  "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
+                },
+                "esutils": {
+                  "version": "1.1.6",
+                  "from": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz",
+                  "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
+                },
+                "esprima": {
+                  "version": "1.2.5",
+                  "from": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
+                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
+                },
+                "optionator": {
+                  "version": "0.5.0",
+                  "from": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
                   "dependencies": {
-                    "hosted-git-info": {
-                      "version": "2.1.4",
-                      "from": "hosted-git-info@>=2.0.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
+                    "prelude-ls": {
+                      "version": "1.1.2",
+                      "from": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+                      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
                     },
-                    "is-builtin-module": {
-                      "version": "1.0.0",
-                      "from": "is-builtin-module@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-                      "dependencies": {
-                        "builtin-modules": {
-                          "version": "1.1.0",
-                          "from": "builtin-modules@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
-                        }
-                      }
+                    "deep-is": {
+                      "version": "0.1.3",
+                      "from": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+                      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
                     },
-                    "semver": {
-                      "version": "5.0.3",
-                      "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
+                    "wordwrap": {
+                      "version": "0.0.3",
+                      "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+                      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
                     },
-                    "validate-npm-package-license": {
-                      "version": "3.0.1",
-                      "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
-                      "dependencies": {
-                        "spdx-correct": {
-                          "version": "1.0.1",
-                          "from": "spdx-correct@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.1.tgz",
-                          "dependencies": {
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "spdx-expression-parse": {
-                          "version": "1.0.0",
-                          "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.0.tgz",
-                          "dependencies": {
-                            "spdx-exceptions": {
-                              "version": "1.0.3",
-                              "from": "spdx-exceptions@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.3.tgz"
-                            },
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                },
-                "object-assign": {
-                  "version": "4.0.1",
-                  "from": "object-assign@>=4.0.1 <5.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-                },
-                "read-pkg-up": {
-                  "version": "1.0.1",
-                  "from": "read-pkg-up@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-                  "dependencies": {
-                    "find-up": {
-                      "version": "1.0.0",
-                      "from": "find-up@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
-                      "dependencies": {
-                        "path-exists": {
-                          "version": "2.0.0",
-                          "from": "path-exists@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
-                        },
-                        "pinkie-promise": {
-                          "version": "1.0.0",
-                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                          "dependencies": {
-                            "pinkie": {
-                              "version": "1.0.0",
-                              "from": "pinkie@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                            }
-                          }
-                        }
-                      }
+                    "type-check": {
+                      "version": "0.3.1",
+                      "from": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz",
+                      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
                     },
-                    "read-pkg": {
-                      "version": "1.1.0",
-                      "from": "read-pkg@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-                      "dependencies": {
-                        "load-json-file": {
-                          "version": "1.0.1",
-                          "from": "load-json-file@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
-                          "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
-                            },
-                            "parse-json": {
-                              "version": "2.2.0",
-                              "from": "parse-json@>=2.2.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                              "dependencies": {
-                                "error-ex": {
-                                  "version": "1.2.0",
-                                  "from": "error-ex@>=1.2.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                                }
-                              }
-                            },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                              "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                }
-                              }
-                            },
-                            "strip-bom": {
-                              "version": "2.0.0",
-                              "from": "strip-bom@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-                              "dependencies": {
-                                "is-utf8": {
-                                  "version": "0.2.0",
-                                  "from": "is-utf8@>=0.2.0 <0.3.0",
-                                  "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        },
-                        "path-type": {
-                          "version": "1.0.0",
-                          "from": "path-type@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-                          "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
-                            },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                              "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                },
-                "redent": {
-                  "version": "1.0.0",
-                  "from": "redent@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-                  "dependencies": {
-                    "indent-string": {
-                      "version": "2.1.0",
-                      "from": "indent-string@>=2.1.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-                      "dependencies": {
-                        "repeating": {
-                          "version": "2.0.0",
-                          "from": "repeating@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
-                          "dependencies": {
-                            "is-finite": {
-                              "version": "1.0.1",
-                              "from": "is-finite@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
-                              "dependencies": {
-                                "number-is-nan": {
-                                  "version": "1.0.0",
-                                  "from": "number-is-nan@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        }
-                      }
+                    "levn": {
+                      "version": "0.2.5",
+                      "from": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz",
+                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
                     },
-                    "strip-indent": {
-                      "version": "1.0.1",
-                      "from": "strip-indent@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
-                      "dependencies": {
-                        "get-stdin": {
-                          "version": "4.0.1",
-                          "from": "get-stdin@>=4.0.1 <5.0.0",
-                          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-                        }
-                      }
+                    "fast-levenshtein": {
+                      "version": "1.0.7",
+                      "from": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz",
+                      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
                     }
                   }
                 },
-                "trim-newlines": {
-                  "version": "1.0.0",
-                  "from": "trim-newlines@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
+                "source-map": {
+                  "version": "0.1.43",
+                  "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz"
                 }
               }
             }
           }
         },
-        "debug": {
-          "version": "2.2.0",
-          "from": "debug@>=2.1.0 <3.0.0"
-        },
-        "detect-conflict": {
-          "version": "1.0.0",
-          "from": "detect-conflict@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.0.tgz"
+        "parents": {
+          "version": "1.0.1",
+          "from": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+          "dependencies": {
+            "path-platform": {
+              "version": "0.11.15",
+              "from": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+              "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz"
+            }
+          }
         },
-        "diff": {
-          "version": "2.1.3",
-          "from": "diff@>=2.0.2 <3.0.0",
-          "resolved": "https://registry.npmjs.org/diff/-/diff-2.1.3.tgz"
+        "readable-stream": {
+          "version": "1.1.13",
+          "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
         },
-        "download": {
-          "version": "4.2.1",
-          "from": "download@>=4.1.2 <5.0.0",
-          "resolved": "https://registry.npmjs.org/download/-/download-4.2.1.tgz",
+        "stream-combiner2": {
+          "version": "1.0.2",
+          "from": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
           "dependencies": {
-            "caw": {
-              "version": "1.1.0",
-              "from": "caw@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/caw/-/caw-1.1.0.tgz",
+            "through2": {
+              "version": "0.5.1",
+              "from": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
+              "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
               "dependencies": {
-                "get-proxy": {
-                  "version": "1.0.1",
-                  "from": "get-proxy@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.0.1.tgz",
-                  "dependencies": {
-                    "rc": {
-                      "version": "0.5.5",
-                      "from": "rc@>=0.5.5 <0.6.0",
-                      "resolved": "https://registry.npmjs.org/rc/-/rc-0.5.5.tgz",
-                      "dependencies": {
-                        "deep-extend": {
-                          "version": "0.2.11",
-                          "from": "deep-extend@>=0.2.5 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
-                        },
-                        "ini": {
-                          "version": "1.3.4",
-                          "from": "ini@>=1.3.0 <1.4.0",
-                          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
-                        },
-                        "minimist": {
-                          "version": "0.0.10",
-                          "from": "minimist@>=0.0.7 <0.1.0",
-                          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
-                        },
-                        "strip-json-comments": {
-                          "version": "0.1.3",
-                          "from": "strip-json-comments@>=0.1.0 <0.2.0",
-                          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
-                        }
-                      }
-                    }
-                  }
-                },
-                "is-obj": {
-                  "version": "1.0.0",
-                  "from": "is-obj@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.0.tgz"
+                "readable-stream": {
+                  "version": "1.0.33",
+                  "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
                 },
-                "object-assign": {
+                "xtend": {
                   "version": "3.0.0",
-                  "from": "object-assign@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-                },
-                "tunnel-agent": {
-                  "version": "0.4.1",
-                  "from": "tunnel-agent@>=0.4.0 <0.5.0",
-                  "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
+                  "from": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz"
                 }
               }
-            },
-            "concat-stream": {
-              "version": "1.5.0",
-              "from": "concat-stream@>=1.4.7 <2.0.0",
-              "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+            }
+          }
+        },
+        "subarg": {
+          "version": "1.0.0",
+          "from": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz"
+        },
+        "through2": {
+          "version": "1.1.1",
+          "from": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz"
+        },
+        "xtend": {
+          "version": "4.0.0",
+          "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+        }
+      }
+    },
+    "ms": {
+      "version": "0.7.1",
+      "from": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
+    },
+    "multipipe": {
+      "version": "0.1.2",
+      "from": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz"
+    },
+    "nan": {
+      "version": "2.1.0",
+      "from": "https://registry.npmjs.org/nan/-/nan-2.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.1.0.tgz"
+    },
+    "node-fetch": {
+      "version": "1.3.3",
+      "from": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.3.3.tgz",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.3.3.tgz",
+      "dependencies": {
+        "encoding": {
+          "version": "0.1.11",
+          "from": "https://registry.npmjs.org/encoding/-/encoding-0.1.11.tgz",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.11.tgz"
+        }
+      }
+    },
+    "node-haste": {
+      "version": "2.4.0",
+      "from": "node-haste@2.4.0",
+      "resolved": "https://registry.npmjs.org/node-haste/-/node-haste-2.4.0.tgz",
+      "dependencies": {
+        "graceful-fs": {
+          "version": "4.1.3",
+          "from": "graceful-fs@>=4.1.3 <5.0.0",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz"
+        },
+        "sane": {
+          "version": "1.3.3",
+          "from": "sane@>=1.3.1 <2.0.0",
+          "resolved": "https://registry.npmjs.org/sane/-/sane-1.3.3.tgz",
+          "dependencies": {
+            "exec-sh": {
+              "version": "0.2.0",
+              "from": "exec-sh@>=0.2.0 <0.3.0",
+              "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
               "dependencies": {
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.1 <2.1.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "readable-stream": {
-                  "version": "2.0.4",
-                  "from": "readable-stream@>=2.0.0 <2.1.0"
-                },
-                "typedarray": {
-                  "version": "0.0.6",
-                  "from": "typedarray@>=0.0.5 <0.1.0",
-                  "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
+                "merge": {
+                  "version": "1.2.0",
+                  "from": "merge@>=1.1.3 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz"
                 }
               }
             },
-            "each-async": {
-              "version": "1.1.1",
-              "from": "each-async@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz",
+            "fb-watchman": {
+              "version": "1.9.0",
+              "from": "fb-watchman@>=1.8.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.0.tgz"
+            },
+            "minimatch": {
+              "version": "0.2.14",
+              "from": "minimatch@>=0.2.14 <0.3.0",
+              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
               "dependencies": {
-                "onetime": {
-                  "version": "1.0.0",
-                  "from": "onetime@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.0.0.tgz"
+                "lru-cache": {
+                  "version": "2.7.3",
+                  "from": "lru-cache@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
                 },
-                "set-immediate-shim": {
+                "sigmund": {
                   "version": "1.0.1",
-                  "from": "set-immediate-shim@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz"
+                  "from": "sigmund@>=1.0.0 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
                 }
               }
             },
-            "filenamify": {
-              "version": "1.2.0",
-              "from": "filenamify@>=1.0.1 <2.0.0",
-              "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.0.tgz",
+            "walker": {
+              "version": "1.0.7",
+              "from": "walker@>=1.0.5 <1.1.0",
+              "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
               "dependencies": {
-                "filename-reserved-regex": {
-                  "version": "1.0.0",
-                  "from": "filename-reserved-regex@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz"
-                },
-                "strip-outer": {
-                  "version": "1.0.0",
-                  "from": "strip-outer@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.0.tgz",
+                "makeerror": {
+                  "version": "1.0.11",
+                  "from": "makeerror@>=1.0.0 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
                   "dependencies": {
-                    "escape-string-regexp": {
-                      "version": "1.0.3",
-                      "from": "escape-string-regexp@>=1.0.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
-                    }
-                  }
-                },
-                "trim-repeated": {
-                  "version": "1.0.0",
-                  "from": "trim-repeated@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
-                  "dependencies": {
-                    "escape-string-regexp": {
-                      "version": "1.0.3",
-                      "from": "escape-string-regexp@>=1.0.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz"
+                    "tmpl": {
+                      "version": "1.0.4",
+                      "from": "tmpl@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz"
                     }
                   }
                 }
               }
             },
-            "got": {
-              "version": "4.2.0",
-              "from": "got@>=4.2.0 <5.0.0",
-              "resolved": "https://registry.npmjs.org/got/-/got-4.2.0.tgz",
+            "watch": {
+              "version": "0.10.0",
+              "from": "watch@>=0.10.0 <0.11.0",
+              "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz"
+            }
+          }
+        },
+        "throat": {
+          "version": "2.0.2",
+          "from": "throat@>=2.0.2 <3.0.0",
+          "resolved": "https://registry.npmjs.org/throat/-/throat-2.0.2.tgz"
+        }
+      }
+    },
+    "normalize-package-data": {
+      "version": "2.3.5",
+      "from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz"
+    },
+    "normalize-path": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.0.tgz"
+    },
+    "number-is-nan": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
+    },
+    "object-assign": {
+      "version": "4.0.1",
+      "from": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
+    },
+    "object.omit": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz"
+    },
+    "once": {
+      "version": "1.3.2",
+      "from": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz"
+    },
+    "opn": {
+      "version": "3.0.2",
+      "from": "https://registry.npmjs.org/opn/-/opn-3.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/opn/-/opn-3.0.2.tgz",
+      "dependencies": {
+        "object-assign": {
+          "version": "3.0.0",
+          "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+        }
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "from": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "dependencies": {
+        "wordwrap": {
+          "version": "0.0.3",
+          "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+        },
+        "minimist": {
+          "version": "0.0.10",
+          "from": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
+        }
+      }
+    },
+    "os-locale": {
+      "version": "1.4.0",
+      "from": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz"
+    },
+    "os-tmpdir": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz"
+    },
+    "output-file-sync": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.1.tgz"
+    },
+    "parse-glob": {
+      "version": "3.0.4",
+      "from": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz"
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "from": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz"
+    },
+    "path-exists": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz"
+    },
+    "path-is-absolute": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+    },
+    "path-type": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz"
+    },
+    "pify": {
+      "version": "2.3.0",
+      "from": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
+    },
+    "pinkie": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
+    },
+    "pinkie-promise": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz"
+    },
+    "portfinder": {
+      "version": "0.4.0",
+      "from": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-0.4.0.tgz",
+      "dependencies": {
+        "async": {
+          "version": "0.9.0",
+          "from": "https://registry.npmjs.org/async/-/async-0.9.0.tgz",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
+        }
+      }
+    },
+    "preserve": {
+      "version": "0.2.0",
+      "from": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz"
+    },
+    "private": {
+      "version": "0.1.6",
+      "from": "https://registry.npmjs.org/private/-/private-0.1.6.tgz",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz"
+    },
+    "process-nextick-args": {
+      "version": "1.0.3",
+      "from": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+    },
+    "progress": {
+      "version": "1.1.8",
+      "from": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz"
+    },
+    "promise": {
+      "version": "7.1.1",
+      "from": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz",
+      "dependencies": {
+        "asap": {
+          "version": "2.0.3",
+          "from": "https://registry.npmjs.org/asap/-/asap-2.0.3.tgz",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.3.tgz"
+        }
+      }
+    },
+    "q": {
+      "version": "1.4.1",
+      "from": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
+      "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz"
+    },
+    "randomatic": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.0.tgz",
+      "dependencies": {
+        "kind-of": {
+          "version": "1.1.0",
+          "from": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz"
+        }
+      }
+    },
+    "react": {
+      "version": "0.14.5",
+      "from": "https://registry.npmjs.org/react/-/react-0.14.5.tgz",
+      "resolved": "https://registry.npmjs.org/react/-/react-0.14.5.tgz",
+      "dependencies": {
+        "envify": {
+          "version": "3.4.0",
+          "from": "https://registry.npmjs.org/envify/-/envify-3.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.0.tgz",
+          "dependencies": {
+            "jstransform": {
+              "version": "10.1.0",
+              "from": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz",
               "dependencies": {
-                "create-error-class": {
-                  "version": "2.0.1",
-                  "from": "create-error-class@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-2.0.1.tgz",
-                  "dependencies": {
-                    "capture-stack-trace": {
-                      "version": "1.0.0",
-                      "from": "capture-stack-trace@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz"
-                    },
-                    "inherits": {
-                      "version": "2.0.1",
-                      "from": "inherits@>=2.0.1 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                    }
-                  }
-                },
-                "duplexify": {
-                  "version": "3.4.2",
-                  "from": "duplexify@>=3.2.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
-                  "dependencies": {
-                    "end-of-stream": {
-                      "version": "1.0.0",
-                      "from": "end-of-stream@1.0.0",
-                      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
-                      "dependencies": {
-                        "once": {
-                          "version": "1.3.2",
-                          "from": "once@>=1.3.0 <1.4.0",
-                          "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                          "dependencies": {
-                            "wrappy": {
-                              "version": "1.0.1",
-                              "from": "wrappy@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "readable-stream": {
-                      "version": "2.0.4",
-                      "from": "readable-stream@>=2.0.0 <3.0.0"
-                    }
-                  }
-                },
-                "is-plain-obj": {
-                  "version": "1.0.0",
-                  "from": "is-plain-obj@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.0.0.tgz"
-                },
-                "is-redirect": {
-                  "version": "1.0.0",
-                  "from": "is-redirect@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz"
-                },
-                "is-stream": {
-                  "version": "1.0.1",
-                  "from": "is-stream@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz"
-                },
-                "lowercase-keys": {
-                  "version": "1.0.0",
-                  "from": "lowercase-keys@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz"
-                },
-                "node-status-codes": {
-                  "version": "1.0.0",
-                  "from": "node-status-codes@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz"
-                },
-                "object-assign": {
-                  "version": "3.0.0",
-                  "from": "object-assign@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-                },
-                "parse-json": {
-                  "version": "2.2.0",
-                  "from": "parse-json@>=2.1.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                  "dependencies": {
-                    "error-ex": {
-                      "version": "1.2.0",
-                      "from": "error-ex@>=1.2.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                    }
-                  }
-                },
-                "pinkie-promise": {
-                  "version": "1.0.0",
-                  "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                  "dependencies": {
-                    "pinkie": {
-                      "version": "1.0.0",
-                      "from": "pinkie@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                    }
-                  }
-                },
-                "prepend-http": {
-                  "version": "1.0.3",
-                  "from": "prepend-http@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz"
-                },
-                "read-all-stream": {
-                  "version": "3.0.1",
-                  "from": "read-all-stream@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz"
+                "base62": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz"
                 },
-                "timed-out": {
-                  "version": "2.0.0",
-                  "from": "timed-out@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz"
+                "esprima-fb": {
+                  "version": "13001.1001.0-dev-harmony-fb",
+                  "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz",
+                  "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz"
                 },
-                "unzip-response": {
-                  "version": "1.0.0",
-                  "from": "unzip-response@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.0.tgz"
+                "source-map": {
+                  "version": "0.1.31",
+                  "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz",
+                  "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz"
                 }
               }
-            },
-            "gulp-decompress": {
-              "version": "1.1.0",
-              "from": "gulp-decompress@>=1.0.2 <2.0.0",
-              "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.1.0.tgz",
-              "dependencies": {
-                "archive-type": {
-                  "version": "3.0.1",
-                  "from": "archive-type@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.0.1.tgz",
-                  "dependencies": {
-                    "file-type": {
-                      "version": "2.12.0",
-                      "from": "file-type@>=2.0.1 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz",
-                      "dependencies": {
-                        "meow": {
-                          "version": "3.4.2",
-                          "from": "meow@>=3.3.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
-                          "dependencies": {
-                            "camelcase-keys": {
-                              "version": "1.0.0",
-                              "from": "camelcase-keys@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
-                              "dependencies": {
-                                "camelcase": {
-                                  "version": "1.2.1",
-                                  "from": "camelcase@>=1.0.1 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-                                },
-                                "map-obj": {
-                                  "version": "1.0.1",
-                                  "from": "map-obj@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-                                }
-                              }
-                            },
-                            "loud-rejection": {
-                              "version": "1.0.0",
-                              "from": "loud-rejection@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-                            },
-                            "minimist": {
-                              "version": "1.2.0",
-                              "from": "minimist@>=1.1.3 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                            },
-                            "normalize-package-data": {
-                              "version": "2.3.4",
-                              "from": "normalize-package-data@>=2.3.4 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
-                              "dependencies": {
-                                "hosted-git-info": {
-                                  "version": "2.1.4",
-                                  "from": "hosted-git-info@>=2.0.2 <3.0.0",
-                                  "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
-                                },
-                                "is-builtin-module": {
-                                  "version": "1.0.0",
-                                  "from": "is-builtin-module@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-                                  "dependencies": {
-                                    "builtin-modules": {
-                                      "version": "1.1.0",
-                                      "from": "builtin-modules@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
-                                    }
-                                  }
-                                },
-                                "semver": {
-                                  "version": "5.0.3",
-                                  "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
-                                },
-                                "validate-npm-package-license": {
-                                  "version": "3.0.1",
-                                  "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-                                  "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
-                                  "dependencies": {
-                                    "spdx-correct": {
-                                      "version": "1.0.1",
-                                      "from": "spdx-correct@>=1.0.0 <1.1.0",
-                                      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.1.tgz",
-                                      "dependencies": {
-                                        "spdx-license-ids": {
-                                          "version": "1.0.2",
-                                          "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                                        }
-                                      }
-                                    },
-                                    "spdx-expression-parse": {
-                                      "version": "1.0.0",
-                                      "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-                                      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.0.tgz",
-                                      "dependencies": {
-                                        "spdx-exceptions": {
-                                          "version": "1.0.3",
-                                          "from": "spdx-exceptions@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.3.tgz"
-                                        },
-                                        "spdx-license-ids": {
-                                          "version": "1.0.2",
-                                          "from": "spdx-license-ids@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                                        }
-                                      }
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "object-assign": {
-                              "version": "4.0.1",
-                              "from": "object-assign@>=4.0.1 <5.0.0",
-                              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-                            },
-                            "read-pkg-up": {
-                              "version": "1.0.1",
-                              "from": "read-pkg-up@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-                              "dependencies": {
-                                "find-up": {
-                                  "version": "1.0.0",
-                                  "from": "find-up@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
-                                  "dependencies": {
-                                    "path-exists": {
-                                      "version": "2.0.0",
-                                      "from": "path-exists@>=2.0.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
-                                    },
-                                    "pinkie-promise": {
-                                      "version": "1.0.0",
-                                      "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                      "dependencies": {
-                                        "pinkie": {
-                                          "version": "1.0.0",
-                                          "from": "pinkie@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                        }
-                                      }
-                                    }
-                                  }
-                                },
-                                "read-pkg": {
-                                  "version": "1.1.0",
-                                  "from": "read-pkg@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-                                  "dependencies": {
-                                    "load-json-file": {
-                                      "version": "1.0.1",
-                                      "from": "load-json-file@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
-                                      "dependencies": {
-                                        "graceful-fs": {
-                                          "version": "4.1.2",
-                                          "from": "graceful-fs@>=4.1.2 <5.0.0"
-                                        },
-                                        "parse-json": {
-                                          "version": "2.2.0",
-                                          "from": "parse-json@>=2.2.0 <3.0.0",
-                                          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                                          "dependencies": {
-                                            "error-ex": {
-                                              "version": "1.2.0",
-                                              "from": "error-ex@>=1.2.0 <2.0.0",
-                                              "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                                            }
-                                          }
-                                        },
-                                        "pify": {
-                                          "version": "2.2.0",
-                                          "from": "pify@>=2.0.0 <3.0.0",
-                                          "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                                        },
-                                        "pinkie-promise": {
-                                          "version": "1.0.0",
-                                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                          "dependencies": {
-                                            "pinkie": {
-                                              "version": "1.0.0",
-                                              "from": "pinkie@>=1.0.0 <2.0.0",
-                                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                            }
-                                          }
-                                        },
-                                        "strip-bom": {
-                                          "version": "2.0.0",
-                                          "from": "strip-bom@>=2.0.0 <3.0.0",
-                                          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-                                          "dependencies": {
-                                            "is-utf8": {
-                                              "version": "0.2.0",
-                                              "from": "is-utf8@>=0.2.0 <0.3.0",
-                                              "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                                            }
-                                          }
-                                        }
-                                      }
-                                    },
-                                    "path-type": {
-                                      "version": "1.0.0",
-                                      "from": "path-type@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-                                      "dependencies": {
-                                        "graceful-fs": {
-                                          "version": "4.1.2",
-                                          "from": "graceful-fs@>=4.1.2 <5.0.0"
-                                        },
-                                        "pify": {
-                                          "version": "2.2.0",
-                                          "from": "pify@>=2.0.0 <3.0.0",
-                                          "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                                        },
-                                        "pinkie-promise": {
-                                          "version": "1.0.0",
-                                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                          "dependencies": {
-                                            "pinkie": {
-                                              "version": "1.0.0",
-                                              "from": "pinkie@>=1.0.0 <2.0.0",
-                                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                            }
-                                          }
-                                        }
-                                      }
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "redent": {
-                              "version": "1.0.0",
-                              "from": "redent@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-                              "dependencies": {
-                                "indent-string": {
-                                  "version": "2.1.0",
-                                  "from": "indent-string@>=2.1.0 <3.0.0",
-                                  "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-                                  "dependencies": {
-                                    "repeating": {
-                                      "version": "2.0.0",
-                                      "from": "repeating@>=2.0.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
-                                      "dependencies": {
-                                        "is-finite": {
-                                          "version": "1.0.1",
-                                          "from": "is-finite@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
-                                          "dependencies": {
-                                            "number-is-nan": {
-                                              "version": "1.0.0",
-                                              "from": "number-is-nan@>=1.0.0 <2.0.0",
-                                              "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-                                            }
-                                          }
-                                        }
-                                      }
-                                    }
-                                  }
-                                },
-                                "strip-indent": {
-                                  "version": "1.0.1",
-                                  "from": "strip-indent@>=1.0.1 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
-                                  "dependencies": {
-                                    "get-stdin": {
-                                      "version": "4.0.1",
-                                      "from": "get-stdin@>=4.0.1 <5.0.0",
-                                      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "trim-newlines": {
-                              "version": "1.0.0",
-                              "from": "trim-newlines@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                },
-                "decompress": {
-                  "version": "2.3.0",
-                  "from": "decompress@>=2.1.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/decompress/-/decompress-2.3.0.tgz",
+            }
+          }
+        },
+        "fbjs": {
+          "version": "0.6.0",
+          "from": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.0.tgz",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.0.tgz",
+          "dependencies": {
+            "whatwg-fetch": {
+              "version": "0.9.0",
+              "from": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz",
+              "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz"
+            }
+          }
+        }
+      }
+    },
+    "react-timer-mixin": {
+      "version": "0.13.2",
+      "from": "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.2.tgz",
+      "resolved": "https://registry.npmjs.org/react-timer-mixin/-/react-timer-mixin-0.13.2.tgz"
+    },
+    "react-transform-hmr": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.2.tgz",
+      "dependencies": {
+        "global": {
+          "version": "4.3.0",
+          "from": "https://registry.npmjs.org/global/-/global-4.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/global/-/global-4.3.0.tgz",
+          "dependencies": {
+            "min-document": {
+              "version": "2.18.0",
+              "from": "https://registry.npmjs.org/min-document/-/min-document-2.18.0.tgz",
+              "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.18.0.tgz",
+              "dependencies": {
+                "dom-walk": {
+                  "version": "0.1.1",
+                  "from": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz"
+                }
+              }
+            },
+            "process": {
+              "version": "0.5.2",
+              "from": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
+              "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz"
+            }
+          }
+        },
+        "react-proxy": {
+          "version": "1.1.2",
+          "from": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.2.tgz",
+          "resolved": "https://registry.npmjs.org/react-proxy/-/react-proxy-1.1.2.tgz",
+          "dependencies": {
+            "react-deep-force-update": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/react-deep-force-update/-/react-deep-force-update-1.0.1.tgz"
+            }
+          }
+        }
+      }
+    },
+    "read-pkg": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz"
+    },
+    "read-pkg-up": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz"
+    },
+    "readable-stream": {
+      "version": "2.0.4",
+      "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+    },
+    "readdirp": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz"
+    },
+    "rebound": {
+      "version": "0.0.13",
+      "from": "https://registry.npmjs.org/rebound/-/rebound-0.0.13.tgz",
+      "resolved": "https://registry.npmjs.org/rebound/-/rebound-0.0.13.tgz"
+    },
+    "recast": {
+      "version": "0.10.37",
+      "from": "https://registry.npmjs.org/recast/-/recast-0.10.37.tgz",
+      "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.37.tgz",
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.3",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
+        },
+        "ast-types": {
+          "version": "0.8.12",
+          "from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz",
+          "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.12.tgz"
+        }
+      }
+    },
+    "redent": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz"
+    },
+    "regenerate": {
+      "version": "1.2.1",
+      "from": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz"
+    },
+    "regenerator": {
+      "version": "0.8.36",
+      "from": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.36.tgz",
+      "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.36.tgz",
+      "dependencies": {
+        "commoner": {
+          "version": "0.10.3",
+          "from": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
+          "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz",
+          "dependencies": {
+            "q": {
+              "version": "1.1.2",
+              "from": "https://registry.npmjs.org/q/-/q-1.1.2.tgz",
+              "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz"
+            },
+            "commander": {
+              "version": "2.5.1",
+              "from": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz"
+            },
+            "graceful-fs": {
+              "version": "3.0.8",
+              "from": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz",
+              "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
+            },
+            "glob": {
+              "version": "4.2.2",
+              "from": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz",
+              "dependencies": {
+                "minimatch": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz",
                   "dependencies": {
-                    "buffer-to-vinyl": {
-                      "version": "1.0.1",
-                      "from": "buffer-to-vinyl@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.0.1.tgz",
-                      "dependencies": {
-                        "file-type": {
-                          "version": "2.12.0",
-                          "from": "file-type@>=2.2.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz"
-                        },
-                        "readable-stream": {
-                          "version": "2.0.4",
-                          "from": "readable-stream@>=2.0.2 <3.0.0"
-                        },
-                        "uuid": {
-                          "version": "2.0.1",
-                          "from": "uuid@>=2.0.1 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz"
-                        },
-                        "vinyl": {
-                          "version": "0.5.3",
-                          "from": "vinyl@>=0.5.1 <0.6.0",
-                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz"
-                        }
-                      }
+                    "lru-cache": {
+                      "version": "2.6.5",
+                      "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz",
+                      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
                     },
-                    "decompress-tar": {
-                      "version": "3.1.0",
-                      "from": "decompress-tar@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz",
-                      "dependencies": {
-                        "is-tar": {
-                          "version": "1.0.0",
-                          "from": "is-tar@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz"
-                        },
-                        "object-assign": {
-                          "version": "2.1.1",
-                          "from": "object-assign@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-                        },
-                        "strip-dirs": {
-                          "version": "1.1.1",
-                          "from": "strip-dirs@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
-                          "dependencies": {
-                            "is-absolute": {
-                              "version": "0.1.7",
-                              "from": "is-absolute@>=0.1.5 <0.2.0",
-                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
-                              "dependencies": {
-                                "is-relative": {
-                                  "version": "0.1.3",
-                                  "from": "is-relative@>=0.1.0 <0.2.0",
-                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
-                                }
-                              }
-                            },
-                            "is-natural-number": {
-                              "version": "2.0.0",
-                              "from": "is-natural-number@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
-                            },
-                            "minimist": {
-                              "version": "1.2.0",
-                              "from": "minimist@>=1.1.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                            },
-                            "sum-up": {
-                              "version": "1.0.2",
-                              "from": "sum-up@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "tar-stream": {
-                          "version": "1.2.1",
-                          "from": "tar-stream@>=1.1.1 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
-                          "dependencies": {
-                            "bl": {
-                              "version": "1.0.0",
-                              "from": "bl@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
-                            },
-                            "end-of-stream": {
-                              "version": "1.1.0",
-                              "from": "end-of-stream@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
-                              "dependencies": {
-                                "once": {
-                                  "version": "1.3.2",
-                                  "from": "once@>=1.3.0 <1.4.0",
-                                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                                  "dependencies": {
-                                    "wrappy": {
-                                      "version": "1.0.1",
-                                      "from": "wrappy@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "readable-stream": {
-                              "version": "2.0.4",
-                              "from": "readable-stream@>=2.0.0 <3.0.0"
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <5.0.0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "through2": {
-                          "version": "0.6.5",
-                          "from": "through2@>=0.6.1 <0.7.0",
-                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-                          "dependencies": {
-                            "readable-stream": {
-                              "version": "1.0.33",
-                              "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
-                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                              "dependencies": {
-                                "core-util-is": {
-                                  "version": "1.0.1",
-                                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                                },
-                                "inherits": {
-                                  "version": "2.0.1",
-                                  "from": "inherits@>=2.0.1 <2.1.0",
-                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                                },
-                                "isarray": {
-                                  "version": "0.0.1",
-                                  "from": "isarray@0.0.1",
-                                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                                },
-                                "string_decoder": {
-                                  "version": "0.10.31",
-                                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                                }
-                              }
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <4.1.0-0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "vinyl": {
-                          "version": "0.4.6",
-                          "from": "vinyl@>=0.4.3 <0.5.0",
-                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
-                          "dependencies": {
-                            "clone": {
-                              "version": "0.2.0",
-                              "from": "clone@>=0.2.0 <0.3.0",
-                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
-                            },
-                            "clone-stats": {
-                              "version": "0.0.1",
-                              "from": "clone-stats@>=0.0.1 <0.0.2",
-                              "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "decompress-tarbz2": {
-                      "version": "3.1.0",
-                      "from": "decompress-tarbz2@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz",
-                      "dependencies": {
-                        "is-bzip2": {
-                          "version": "1.0.0",
-                          "from": "is-bzip2@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz"
-                        },
-                        "object-assign": {
-                          "version": "2.1.1",
-                          "from": "object-assign@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-                        },
-                        "seek-bzip": {
-                          "version": "1.0.5",
-                          "from": "seek-bzip@>=1.0.3 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
-                          "dependencies": {
-                            "commander": {
-                              "version": "2.8.1",
-                              "from": "commander@>=2.8.1 <2.9.0",
-                              "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
-                              "dependencies": {
-                                "graceful-readlink": {
-                                  "version": "1.0.1",
-                                  "from": "graceful-readlink@>=1.0.0",
-                                  "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
-                                }
-                              }
-                            }
-                          }
-                        },
-                        "strip-dirs": {
-                          "version": "1.1.1",
-                          "from": "strip-dirs@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
-                          "dependencies": {
-                            "is-absolute": {
-                              "version": "0.1.7",
-                              "from": "is-absolute@>=0.1.5 <0.2.0",
-                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
-                              "dependencies": {
-                                "is-relative": {
-                                  "version": "0.1.3",
-                                  "from": "is-relative@>=0.1.0 <0.2.0",
-                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
-                                }
-                              }
-                            },
-                            "is-natural-number": {
-                              "version": "2.0.0",
-                              "from": "is-natural-number@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
-                            },
-                            "minimist": {
-                              "version": "1.2.0",
-                              "from": "minimist@>=1.1.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                            },
-                            "sum-up": {
-                              "version": "1.0.2",
-                              "from": "sum-up@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "tar-stream": {
-                          "version": "1.2.1",
-                          "from": "tar-stream@>=1.1.1 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
-                          "dependencies": {
-                            "bl": {
-                              "version": "1.0.0",
-                              "from": "bl@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
-                            },
-                            "end-of-stream": {
-                              "version": "1.1.0",
-                              "from": "end-of-stream@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
-                              "dependencies": {
-                                "once": {
-                                  "version": "1.3.2",
-                                  "from": "once@>=1.3.0 <1.4.0",
-                                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                                  "dependencies": {
-                                    "wrappy": {
-                                      "version": "1.0.1",
-                                      "from": "wrappy@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "readable-stream": {
-                              "version": "2.0.4",
-                              "from": "readable-stream@>=2.0.0 <3.0.0"
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <5.0.0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "through2": {
-                          "version": "0.6.5",
-                          "from": "through2@>=0.6.1 <0.7.0",
-                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-                          "dependencies": {
-                            "readable-stream": {
-                              "version": "1.0.33",
-                              "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
-                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                              "dependencies": {
-                                "core-util-is": {
-                                  "version": "1.0.1",
-                                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                                },
-                                "inherits": {
-                                  "version": "2.0.1",
-                                  "from": "inherits@>=2.0.1 <2.1.0",
-                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                                },
-                                "isarray": {
-                                  "version": "0.0.1",
-                                  "from": "isarray@0.0.1",
-                                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                                },
-                                "string_decoder": {
-                                  "version": "0.10.31",
-                                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                                }
-                              }
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <4.1.0-0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "vinyl": {
-                          "version": "0.4.6",
-                          "from": "vinyl@>=0.4.3 <0.5.0",
-                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
-                          "dependencies": {
-                            "clone": {
-                              "version": "0.2.0",
-                              "from": "clone@>=0.2.0 <0.3.0",
-                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
-                            },
-                            "clone-stats": {
-                              "version": "0.0.1",
-                              "from": "clone-stats@>=0.0.1 <0.0.2",
-                              "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "decompress-targz": {
-                      "version": "3.1.0",
-                      "from": "decompress-targz@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz",
-                      "dependencies": {
-                        "is-gzip": {
-                          "version": "1.0.0",
-                          "from": "is-gzip@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz"
-                        },
-                        "object-assign": {
-                          "version": "2.1.1",
-                          "from": "object-assign@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-                        },
-                        "strip-dirs": {
-                          "version": "1.1.1",
-                          "from": "strip-dirs@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
-                          "dependencies": {
-                            "is-absolute": {
-                              "version": "0.1.7",
-                              "from": "is-absolute@>=0.1.5 <0.2.0",
-                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
-                              "dependencies": {
-                                "is-relative": {
-                                  "version": "0.1.3",
-                                  "from": "is-relative@>=0.1.0 <0.2.0",
-                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
-                                }
-                              }
-                            },
-                            "is-natural-number": {
-                              "version": "2.0.0",
-                              "from": "is-natural-number@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
-                            },
-                            "minimist": {
-                              "version": "1.2.0",
-                              "from": "minimist@>=1.1.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                            },
-                            "sum-up": {
-                              "version": "1.0.2",
-                              "from": "sum-up@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "tar-stream": {
-                          "version": "1.2.1",
-                          "from": "tar-stream@>=1.1.1 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
-                          "dependencies": {
-                            "bl": {
-                              "version": "1.0.0",
-                              "from": "bl@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
-                            },
-                            "end-of-stream": {
-                              "version": "1.1.0",
-                              "from": "end-of-stream@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
-                              "dependencies": {
-                                "once": {
-                                  "version": "1.3.2",
-                                  "from": "once@>=1.3.0 <1.4.0",
-                                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                                  "dependencies": {
-                                    "wrappy": {
-                                      "version": "1.0.1",
-                                      "from": "wrappy@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "readable-stream": {
-                              "version": "2.0.4",
-                              "from": "readable-stream@>=2.0.0 <3.0.0"
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <5.0.0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "through2": {
-                          "version": "0.6.5",
-                          "from": "through2@>=0.6.1 <0.7.0",
-                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-                          "dependencies": {
-                            "readable-stream": {
-                              "version": "1.0.33",
-                              "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
-                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                              "dependencies": {
-                                "core-util-is": {
-                                  "version": "1.0.1",
-                                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                                },
-                                "inherits": {
-                                  "version": "2.0.1",
-                                  "from": "inherits@>=2.0.1 <2.1.0",
-                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                                },
-                                "isarray": {
-                                  "version": "0.0.1",
-                                  "from": "isarray@0.0.1",
-                                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                                },
-                                "string_decoder": {
-                                  "version": "0.10.31",
-                                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                                }
-                              }
-                            },
-                            "xtend": {
-                              "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <4.1.0-0",
-                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                            }
-                          }
-                        },
-                        "vinyl": {
-                          "version": "0.4.6",
-                          "from": "vinyl@>=0.4.3 <0.5.0",
-                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
-                          "dependencies": {
-                            "clone": {
-                              "version": "0.2.0",
-                              "from": "clone@>=0.2.0 <0.3.0",
-                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
-                            },
-                            "clone-stats": {
-                              "version": "0.0.1",
-                              "from": "clone-stats@>=0.0.1 <0.0.2",
-                              "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "decompress-unzip": {
-                      "version": "3.3.0",
-                      "from": "decompress-unzip@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.3.0.tgz",
-                      "dependencies": {
-                        "is-zip": {
-                          "version": "1.0.0",
-                          "from": "is-zip@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz"
-                        },
-                        "stat-mode": {
-                          "version": "0.2.1",
-                          "from": "stat-mode@>=0.2.0 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.1.tgz"
-                        },
-                        "strip-dirs": {
-                          "version": "1.1.1",
-                          "from": "strip-dirs@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
-                          "dependencies": {
-                            "is-absolute": {
-                              "version": "0.1.7",
-                              "from": "is-absolute@>=0.1.5 <0.2.0",
-                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
-                              "dependencies": {
-                                "is-relative": {
-                                  "version": "0.1.3",
-                                  "from": "is-relative@>=0.1.0 <0.2.0",
-                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
-                                }
-                              }
-                            },
-                            "is-natural-number": {
-                              "version": "2.0.0",
-                              "from": "is-natural-number@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
-                            },
-                            "minimist": {
-                              "version": "1.2.0",
-                              "from": "minimist@>=1.1.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                            },
-                            "sum-up": {
-                              "version": "1.0.2",
-                              "from": "sum-up@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "vinyl": {
-                          "version": "0.5.3",
-                          "from": "vinyl@>=0.5.0 <0.6.0"
-                        },
-                        "yauzl": {
-                          "version": "2.3.1",
-                          "from": "yauzl@>=2.2.1 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.3.1.tgz",
-                          "dependencies": {
-                            "fd-slicer": {
-                              "version": "1.0.1",
-                              "from": "fd-slicer@>=1.0.1 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz"
-                            },
-                            "pend": {
-                              "version": "1.2.0",
-                              "from": "pend@>=1.2.0 <1.3.0",
-                              "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "get-stdin": {
-                      "version": "4.0.1",
-                      "from": "get-stdin@>=4.0.1 <5.0.0",
-                      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-                    },
-                    "meow": {
-                      "version": "3.4.2",
-                      "from": "meow@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+                    "sigmund": {
+                      "version": "1.0.1",
+                      "from": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
+                    }
+                  }
+                }
+              }
+            },
+            "install": {
+              "version": "0.1.8",
+              "from": "https://registry.npmjs.org/install/-/install-0.1.8.tgz",
+              "resolved": "https://registry.npmjs.org/install/-/install-0.1.8.tgz"
+            },
+            "iconv-lite": {
+              "version": "0.4.11",
+              "from": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz",
+              "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.11.tgz"
+            }
+          }
+        },
+        "defs": {
+          "version": "1.1.0",
+          "from": "https://registry.npmjs.org/defs/-/defs-1.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.0.tgz",
+          "dependencies": {
+            "esprima-fb": {
+              "version": "8001.1001.0-dev-harmony-fb",
+              "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-8001.1001.0-dev-harmony-fb.tgz",
+              "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-8001.1001.0-dev-harmony-fb.tgz"
+            },
+            "yargs": {
+              "version": "1.3.3",
+              "from": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz",
+              "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.3.3.tgz"
+            }
+          }
+        },
+        "esprima-fb": {
+          "version": "15001.1.0-dev-harmony-fb",
+          "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz",
+          "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz"
+        },
+        "recast": {
+          "version": "0.10.25",
+          "from": "https://registry.npmjs.org/recast/-/recast-0.10.25.tgz",
+          "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.25.tgz",
+          "dependencies": {
+            "ast-types": {
+              "version": "0.8.5",
+              "from": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.5.tgz",
+              "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.5.tgz"
+            }
+          }
+        }
+      }
+    },
+    "regex-cache": {
+      "version": "0.4.2",
+      "from": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.2.tgz",
+      "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.2.tgz"
+    },
+    "regexpu": {
+      "version": "1.3.0",
+      "from": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz",
+      "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz",
+      "dependencies": {
+        "esprima": {
+          "version": "2.7.0",
+          "from": "https://registry.npmjs.org/esprima/-/esprima-2.7.0.tgz",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.0.tgz"
+        }
+      }
+    },
+    "regjsgen": {
+      "version": "0.2.0",
+      "from": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz"
+    },
+    "regjsparser": {
+      "version": "0.1.5",
+      "from": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz"
+    },
+    "repeat-element": {
+      "version": "1.1.2",
+      "from": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz"
+    },
+    "repeat-string": {
+      "version": "1.5.2",
+      "from": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz"
+    },
+    "repeating": {
+      "version": "1.1.3",
+      "from": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz"
+    },
+    "replace-ext": {
+      "version": "0.0.1",
+      "from": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
+    },
+    "resolve": {
+      "version": "1.1.6",
+      "from": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz"
+    },
+    "right-align": {
+      "version": "0.1.3",
+      "from": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz"
+    },
+    "sane": {
+      "version": "1.2.0",
+      "from": "https://registry.npmjs.org/sane/-/sane-1.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/sane/-/sane-1.2.0.tgz",
+      "dependencies": {
+        "exec-sh": {
+          "version": "0.2.0",
+          "from": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.0.tgz",
+          "dependencies": {
+            "merge": {
+              "version": "1.2.0",
+              "from": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz"
+            }
+          }
+        },
+        "fb-watchman": {
+          "version": "1.6.0",
+          "from": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.6.0.tgz",
+          "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.6.0.tgz"
+        },
+        "minimatch": {
+          "version": "0.2.14",
+          "from": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
+          "dependencies": {
+            "lru-cache": {
+              "version": "2.6.5",
+              "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz",
+              "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz"
+            },
+            "sigmund": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
+            }
+          }
+        },
+        "walker": {
+          "version": "1.0.7",
+          "from": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
+          "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
+          "dependencies": {
+            "makeerror": {
+              "version": "1.0.11",
+              "from": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
+              "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
+              "dependencies": {
+                "tmpl": {
+                  "version": "1.0.4",
+                  "from": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
+                  "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz"
+                }
+              }
+            }
+          }
+        },
+        "watch": {
+          "version": "0.10.0",
+          "from": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz",
+          "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz"
+        }
+      }
+    },
+    "semver": {
+      "version": "5.0.3",
+      "from": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz"
+    },
+    "simple-fmt": {
+      "version": "0.1.0",
+      "from": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz"
+    },
+    "simple-is": {
+      "version": "0.2.0",
+      "from": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz"
+    },
+    "slash": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz"
+    },
+    "source-map": {
+      "version": "0.4.4",
+      "from": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+    },
+    "source-map-support": {
+      "version": "0.2.10",
+      "from": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz",
+      "dependencies": {
+        "source-map": {
+          "version": "0.1.32",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz"
+        }
+      }
+    },
+    "sparkles": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz"
+    },
+    "spdx-correct": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz"
+    },
+    "spdx-exceptions": {
+      "version": "1.0.4",
+      "from": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz"
+    },
+    "spdx-expression-parse": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.1.tgz"
+    },
+    "spdx-license-ids": {
+      "version": "1.1.0",
+      "from": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.1.0.tgz"
+    },
+    "stable": {
+      "version": "0.1.5",
+      "from": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz",
+      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz"
+    },
+    "stacktrace-parser": {
+      "version": "0.1.3",
+      "from": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.3.tgz",
+      "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.3.tgz"
+    },
+    "string_decoder": {
+      "version": "0.10.31",
+      "from": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+    },
+    "stringmap": {
+      "version": "0.2.2",
+      "from": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz",
+      "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz"
+    },
+    "stringset": {
+      "version": "0.2.1",
+      "from": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz",
+      "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz"
+    },
+    "strip-ansi": {
+      "version": "3.0.0",
+      "from": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+    },
+    "strip-bom": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz"
+    },
+    "strip-indent": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz"
+    },
+    "success-symbol": {
+      "version": "0.1.0",
+      "from": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz",
+      "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz"
+    },
+    "supports-color": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
+    },
+    "temp": {
+      "version": "0.8.3",
+      "from": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
+      "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
+      "dependencies": {
+        "rimraf": {
+          "version": "2.2.8",
+          "from": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
+        }
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "from": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+    },
+    "through2": {
+      "version": "2.0.0",
+      "from": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz"
+    },
+    "to-fast-properties": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz"
+    },
+    "trim-newlines": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
+    },
+    "trim-right": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz"
+    },
+    "try-resolve": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz"
+    },
+    "tryor": {
+      "version": "0.1.2",
+      "from": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz"
+    },
+    "ua-parser-js": {
+      "version": "0.7.10",
+      "from": "ua-parser-js@>=0.7.9 <0.8.0",
+      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.10.tgz"
+    },
+    "uglify-js": {
+      "version": "2.4.24",
+      "from": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz",
+      "dependencies": {
+        "async": {
+          "version": "0.2.10",
+          "from": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+        },
+        "source-map": {
+          "version": "0.1.34",
+          "from": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz"
+        },
+        "uglify-to-browserify": {
+          "version": "1.0.2",
+          "from": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz"
+        },
+        "yargs": {
+          "version": "3.5.4",
+          "from": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz",
+          "dependencies": {
+            "decamelize": {
+              "version": "1.0.0",
+              "from": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz"
+            },
+            "window-size": {
+              "version": "0.1.0",
+              "from": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz"
+            },
+            "wordwrap": {
+              "version": "0.0.2",
+              "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
+            }
+          }
+        }
+      }
+    },
+    "underscore": {
+      "version": "1.8.3",
+      "from": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
+    },
+    "user-home": {
+      "version": "1.1.1",
+      "from": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
+      "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz"
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "from": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.1",
+      "from": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz"
+    },
+    "vinyl": {
+      "version": "0.5.3",
+      "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
+      "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz"
+    },
+    "window-size": {
+      "version": "0.1.2",
+      "from": "https://registry.npmjs.org/window-size/-/window-size-0.1.2.tgz",
+      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.2.tgz"
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
+    },
+    "worker-farm": {
+      "version": "1.3.1",
+      "from": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.3.1.tgz",
+      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.3.1.tgz",
+      "dependencies": {
+        "errno": {
+          "version": "0.1.4",
+          "from": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
+          "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
+          "dependencies": {
+            "prr": {
+              "version": "0.0.0",
+              "from": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz"
+            }
+          }
+        },
+        "xtend": {
+          "version": "4.0.0",
+          "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.1",
+      "from": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+    },
+    "ws": {
+      "version": "0.8.0",
+      "from": "https://registry.npmjs.org/ws/-/ws-0.8.0.tgz",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-0.8.0.tgz",
+      "dependencies": {
+        "options": {
+          "version": "0.0.6",
+          "from": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+          "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
+        },
+        "ultron": {
+          "version": "1.0.2",
+          "from": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
+        },
+        "bufferutil": {
+          "version": "1.2.1",
+          "from": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.2.1.tgz",
+          "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.2.1.tgz",
+          "dependencies": {
+            "bindings": {
+              "version": "1.2.1",
+              "from": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+              "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
+            },
+            "nan": {
+              "version": "2.0.8",
+              "from": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz",
+              "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz"
+            }
+          }
+        },
+        "utf-8-validate": {
+          "version": "1.2.1",
+          "from": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.2.1.tgz",
+          "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.2.1.tgz",
+          "dependencies": {
+            "bindings": {
+              "version": "1.2.1",
+              "from": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
+              "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
+            },
+            "nan": {
+              "version": "2.0.8",
+              "from": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz",
+              "resolved": "https://registry.npmjs.org/nan/-/nan-2.0.8.tgz"
+            }
+          }
+        }
+      }
+    },
+    "xtend": {
+      "version": "4.0.1",
+      "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
+    },
+    "y18n": {
+      "version": "3.2.0",
+      "from": "https://registry.npmjs.org/y18n/-/y18n-3.2.0.tgz",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.0.tgz"
+    },
+    "yargs": {
+      "version": "3.24.0",
+      "from": "https://registry.npmjs.org/yargs/-/yargs-3.24.0.tgz",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.24.0.tgz",
+      "dependencies": {
+        "decamelize": {
+          "version": "1.0.0",
+          "from": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz"
+        },
+        "os-locale": {
+          "version": "1.3.1",
+          "from": "https://registry.npmjs.org/os-locale/-/os-locale-1.3.1.tgz",
+          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.3.1.tgz"
+        },
+        "y18n": {
+          "version": "3.1.0",
+          "from": "https://registry.npmjs.org/y18n/-/y18n-3.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.1.0.tgz"
+        }
+      }
+    },
+    "yeoman-environment": {
+      "version": "1.2.7",
+      "from": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.2.7.tgz",
+      "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.2.7.tgz",
+      "dependencies": {
+        "diff": {
+          "version": "1.4.0",
+          "from": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz"
+        },
+        "globby": {
+          "version": "2.1.0",
+          "from": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz",
+          "dependencies": {
+            "array-union": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz"
+            },
+            "async": {
+              "version": "1.4.2",
+              "from": "https://registry.npmjs.org/async/-/async-1.4.2.tgz",
+              "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz"
+            },
+            "glob": {
+              "version": "5.0.14",
+              "from": "https://registry.npmjs.org/glob/-/glob-5.0.14.tgz",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.14.tgz"
+            },
+            "object-assign": {
+              "version": "3.0.0",
+              "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+            }
+          }
+        },
+        "grouped-queue": {
+          "version": "0.3.0",
+          "from": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.0.tgz",
+          "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.0.tgz",
+          "dependencies": {
+            "lodash": {
+              "version": "2.4.2",
+              "from": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz",
+              "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz"
+            },
+            "setimmediate": {
+              "version": "1.0.2",
+              "from": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.2.tgz"
+            }
+          }
+        },
+        "inquirer": {
+          "version": "0.8.5",
+          "from": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
+          "dependencies": {
+            "ansi-regex": {
+              "version": "1.1.1",
+              "from": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz"
+            },
+            "cli-width": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz"
+            },
+            "figures": {
+              "version": "1.3.5",
+              "from": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz",
+              "resolved": "https://registry.npmjs.org/figures/-/figures-1.3.5.tgz"
+            },
+            "readline2": {
+              "version": "0.1.1",
+              "from": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
+              "dependencies": {
+                "mute-stream": {
+                  "version": "0.0.4",
+                  "from": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz",
+                  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz"
+                },
+                "strip-ansi": {
+                  "version": "2.0.1",
+                  "from": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz"
+                }
+              }
+            },
+            "rx": {
+              "version": "2.5.3",
+              "from": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz",
+              "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz"
+            }
+          }
+        },
+        "log-symbols": {
+          "version": "1.0.2",
+          "from": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz"
+        },
+        "mem-fs": {
+          "version": "1.1.0",
+          "from": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.0.tgz",
+          "dependencies": {
+            "through2": {
+              "version": "0.6.5",
+              "from": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+              "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+              "dependencies": {
+                "readable-stream": {
+                  "version": "1.0.33",
+                  "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+                },
+                "xtend": {
+                  "version": "4.0.0",
+                  "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                }
+              }
+            },
+            "vinyl": {
+              "version": "0.4.6",
+              "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+              "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+              "dependencies": {
+                "clone": {
+                  "version": "0.2.0",
+                  "from": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
+                }
+              }
+            },
+            "vinyl-file": {
+              "version": "1.2.1",
+              "from": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-1.2.1.tgz",
+              "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-1.2.1.tgz",
+              "dependencies": {
+                "strip-bom-stream": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz",
+                  "dependencies": {
+                    "first-chunk-stream": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz"
+                    }
+                  }
+                },
+                "vinyl": {
+                  "version": "0.5.3",
+                  "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
+                  "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz"
+                }
+              }
+            }
+          }
+        },
+        "text-table": {
+          "version": "0.2.0",
+          "from": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+          "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
+        },
+        "untildify": {
+          "version": "2.1.0",
+          "from": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz",
+          "dependencies": {
+            "os-homedir": {
+              "version": "1.0.1",
+              "from": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
+            }
+          }
+        }
+      }
+    },
+    "yeoman-generator": {
+      "version": "0.20.3",
+      "from": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-0.20.3.tgz",
+      "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-0.20.3.tgz",
+      "dependencies": {
+        "async": {
+          "version": "1.4.2",
+          "from": "https://registry.npmjs.org/async/-/async-1.4.2.tgz",
+          "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz"
+        },
+        "class-extend": {
+          "version": "0.1.2",
+          "from": "https://registry.npmjs.org/class-extend/-/class-extend-0.1.2.tgz",
+          "resolved": "https://registry.npmjs.org/class-extend/-/class-extend-0.1.2.tgz",
+          "dependencies": {
+            "object-assign": {
+              "version": "2.1.1",
+              "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+            }
+          }
+        },
+        "cli-table": {
+          "version": "0.3.1",
+          "from": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
+          "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz",
+          "dependencies": {
+            "colors": {
+              "version": "1.0.3",
+              "from": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
+              "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz"
+            }
+          }
+        },
+        "cross-spawn": {
+          "version": "2.0.0",
+          "from": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-2.0.0.tgz",
+          "dependencies": {
+            "cross-spawn-async": {
+              "version": "2.0.0",
+              "from": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.0.0.tgz",
+              "dependencies": {
+                "lru-cache": {
+                  "version": "2.7.0",
+                  "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz",
+                  "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz"
+                },
+                "which": {
+                  "version": "1.2.0",
+                  "from": "https://registry.npmjs.org/which/-/which-1.2.0.tgz",
+                  "resolved": "https://registry.npmjs.org/which/-/which-1.2.0.tgz",
+                  "dependencies": {
+                    "is-absolute": {
+                      "version": "0.1.7",
+                      "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
                       "dependencies": {
-                        "camelcase-keys": {
-                          "version": "1.0.0",
-                          "from": "camelcase-keys@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
-                          "dependencies": {
-                            "camelcase": {
-                              "version": "1.2.1",
-                              "from": "camelcase@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-                            },
-                            "map-obj": {
-                              "version": "1.0.1",
-                              "from": "map-obj@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-                            }
-                          }
-                        },
-                        "loud-rejection": {
-                          "version": "1.0.0",
-                          "from": "loud-rejection@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-                        },
-                        "minimist": {
-                          "version": "1.2.0",
-                          "from": "minimist@>=1.1.3 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                        },
-                        "normalize-package-data": {
-                          "version": "2.3.4",
-                          "from": "normalize-package-data@>=2.3.4 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
-                          "dependencies": {
-                            "hosted-git-info": {
-                              "version": "2.1.4",
-                              "from": "hosted-git-info@>=2.0.2 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
-                            },
-                            "is-builtin-module": {
-                              "version": "1.0.0",
-                              "from": "is-builtin-module@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-                              "dependencies": {
-                                "builtin-modules": {
-                                  "version": "1.1.0",
-                                  "from": "builtin-modules@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
-                                }
-                              }
-                            },
-                            "semver": {
-                              "version": "5.0.3",
-                              "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
-                            },
-                            "validate-npm-package-license": {
-                              "version": "3.0.1",
-                              "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-                              "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
-                              "dependencies": {
-                                "spdx-correct": {
-                                  "version": "1.0.1",
-                                  "from": "spdx-correct@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.1.tgz",
-                                  "dependencies": {
-                                    "spdx-license-ids": {
-                                      "version": "1.0.2",
-                                      "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                                    }
-                                  }
-                                },
-                                "spdx-expression-parse": {
-                                  "version": "1.0.0",
-                                  "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.0.tgz",
-                                  "dependencies": {
-                                    "spdx-exceptions": {
-                                      "version": "1.0.3",
-                                      "from": "spdx-exceptions@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.3.tgz"
-                                    },
-                                    "spdx-license-ids": {
-                                      "version": "1.0.2",
-                                      "from": "spdx-license-ids@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        },
-                        "object-assign": {
-                          "version": "4.0.1",
-                          "from": "object-assign@>=4.0.1 <5.0.0"
-                        },
-                        "read-pkg-up": {
-                          "version": "1.0.1",
-                          "from": "read-pkg-up@>=1.0.1 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-                          "dependencies": {
-                            "find-up": {
-                              "version": "1.0.0",
-                              "from": "find-up@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
-                              "dependencies": {
-                                "path-exists": {
-                                  "version": "2.0.0",
-                                  "from": "path-exists@>=2.0.0 <3.0.0",
-                                  "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
-                                },
-                                "pinkie-promise": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                  "dependencies": {
-                                    "pinkie": {
-                                      "version": "1.0.0",
-                                      "from": "pinkie@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "read-pkg": {
-                              "version": "1.1.0",
-                              "from": "read-pkg@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-                              "dependencies": {
-                                "load-json-file": {
-                                  "version": "1.0.1",
-                                  "from": "load-json-file@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
-                                  "dependencies": {
-                                    "graceful-fs": {
-                                      "version": "4.1.2",
-                                      "from": "graceful-fs@>=4.1.2 <5.0.0"
-                                    },
-                                    "parse-json": {
-                                      "version": "2.2.0",
-                                      "from": "parse-json@>=2.2.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                                      "dependencies": {
-                                        "error-ex": {
-                                          "version": "1.2.0",
-                                          "from": "error-ex@>=1.2.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                                        }
-                                      }
-                                    },
-                                    "pify": {
-                                      "version": "2.2.0",
-                                      "from": "pify@>=2.0.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                                    },
-                                    "pinkie-promise": {
-                                      "version": "1.0.0",
-                                      "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                      "dependencies": {
-                                        "pinkie": {
-                                          "version": "1.0.0",
-                                          "from": "pinkie@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                        }
-                                      }
-                                    },
-                                    "strip-bom": {
-                                      "version": "2.0.0",
-                                      "from": "strip-bom@>=2.0.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-                                      "dependencies": {
-                                        "is-utf8": {
-                                          "version": "0.2.0",
-                                          "from": "is-utf8@>=0.2.0 <0.3.0",
-                                          "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                                        }
-                                      }
-                                    }
-                                  }
-                                },
-                                "path-type": {
-                                  "version": "1.0.0",
-                                  "from": "path-type@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-                                  "dependencies": {
-                                    "graceful-fs": {
-                                      "version": "4.1.2",
-                                      "from": "graceful-fs@>=4.1.2 <5.0.0"
-                                    },
-                                    "pify": {
-                                      "version": "2.2.0",
-                                      "from": "pify@>=2.0.0 <3.0.0",
-                                      "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                                    },
-                                    "pinkie-promise": {
-                                      "version": "1.0.0",
-                                      "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                                      "dependencies": {
-                                        "pinkie": {
-                                          "version": "1.0.0",
-                                          "from": "pinkie@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                        }
-                                      }
-                                    }
-                                  }
-                                }
-                              }
-                            }
-                          }
-                        },
-                        "redent": {
-                          "version": "1.0.0",
-                          "from": "redent@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-                          "dependencies": {
-                            "indent-string": {
-                              "version": "2.1.0",
-                              "from": "indent-string@>=2.1.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-                              "dependencies": {
-                                "repeating": {
-                                  "version": "2.0.0",
-                                  "from": "repeating@>=2.0.0 <3.0.0",
-                                  "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
-                                  "dependencies": {
-                                    "is-finite": {
-                                      "version": "1.0.1",
-                                      "from": "is-finite@>=1.0.0 <2.0.0",
-                                      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
-                                      "dependencies": {
-                                        "number-is-nan": {
-                                          "version": "1.0.0",
-                                          "from": "number-is-nan@>=1.0.0 <2.0.0",
-                                          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-                                        }
-                                      }
-                                    }
-                                  }
-                                }
-                              }
-                            },
-                            "strip-indent": {
-                              "version": "1.0.1",
-                              "from": "strip-indent@>=1.0.1 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz"
-                            }
-                          }
-                        },
-                        "trim-newlines": {
-                          "version": "1.0.0",
-                          "from": "trim-newlines@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
+                        "is-relative": {
+                          "version": "0.1.3",
+                          "from": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                          "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
                         }
                       }
+                    }
+                  }
+                }
+              }
+            },
+            "spawn-sync": {
+              "version": "1.0.13",
+              "from": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.13.tgz",
+              "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.13.tgz",
+              "dependencies": {
+                "concat-stream": {
+                  "version": "1.5.0",
+                  "from": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+                  "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+                  "dependencies": {
+                    "typedarray": {
+                      "version": "0.0.6",
+                      "from": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+                      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
                     },
-                    "vinyl-assign": {
-                      "version": "1.2.1",
-                      "from": "vinyl-assign@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz",
-                      "dependencies": {
-                        "object-assign": {
-                          "version": "4.0.1",
-                          "from": "object-assign@>=4.0.1 <5.0.0"
-                        },
-                        "readable-stream": {
-                          "version": "2.0.4",
-                          "from": "readable-stream@>=2.0.0 <3.0.0"
-                        }
-                      }
+                    "readable-stream": {
+                      "version": "2.0.2",
+                      "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz"
                     }
                   }
                 },
-                "gulp-util": {
-                  "version": "3.0.6",
-                  "from": "gulp-util@>=3.0.1 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.6.tgz",
+                "os-shim": {
+                  "version": "0.1.3",
+                  "from": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
+                  "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz"
+                }
+              }
+            }
+          }
+        },
+        "dargs": {
+          "version": "4.0.1",
+          "from": "https://registry.npmjs.org/dargs/-/dargs-4.0.1.tgz",
+          "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.0.1.tgz"
+        },
+        "detect-conflict": {
+          "version": "1.0.0",
+          "from": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.0.tgz"
+        },
+        "diff": {
+          "version": "2.1.3",
+          "from": "https://registry.npmjs.org/diff/-/diff-2.1.3.tgz",
+          "resolved": "https://registry.npmjs.org/diff/-/diff-2.1.3.tgz"
+        },
+        "download": {
+          "version": "4.2.1",
+          "from": "https://registry.npmjs.org/download/-/download-4.2.1.tgz",
+          "resolved": "https://registry.npmjs.org/download/-/download-4.2.1.tgz",
+          "dependencies": {
+            "caw": {
+              "version": "1.1.0",
+              "from": "https://registry.npmjs.org/caw/-/caw-1.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/caw/-/caw-1.1.0.tgz",
+              "dependencies": {
+                "get-proxy": {
+                  "version": "1.0.1",
+                  "from": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-1.0.1.tgz",
                   "dependencies": {
-                    "array-differ": {
-                      "version": "1.0.0",
-                      "from": "array-differ@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz"
-                    },
-                    "array-uniq": {
-                      "version": "1.0.2",
-                      "from": "array-uniq@>=1.0.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
-                    },
-                    "beeper": {
-                      "version": "1.1.0",
-                      "from": "beeper@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.0.tgz"
-                    },
-                    "lodash._reescape": {
-                      "version": "3.0.0",
-                      "from": "lodash._reescape@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz"
-                    },
-                    "lodash._reevaluate": {
-                      "version": "3.0.0",
-                      "from": "lodash._reevaluate@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz"
-                    },
-                    "lodash._reinterpolate": {
-                      "version": "3.0.0",
-                      "from": "lodash._reinterpolate@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz"
-                    },
-                    "lodash.template": {
-                      "version": "3.6.2",
-                      "from": "lodash.template@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz",
+                    "rc": {
+                      "version": "0.5.5",
+                      "from": "https://registry.npmjs.org/rc/-/rc-0.5.5.tgz",
+                      "resolved": "https://registry.npmjs.org/rc/-/rc-0.5.5.tgz",
                       "dependencies": {
-                        "lodash._basecopy": {
-                          "version": "3.0.1",
-                          "from": "lodash._basecopy@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
-                        },
-                        "lodash._basetostring": {
-                          "version": "3.0.1",
-                          "from": "lodash._basetostring@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz"
-                        },
-                        "lodash._basevalues": {
-                          "version": "3.0.0",
-                          "from": "lodash._basevalues@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz"
-                        },
-                        "lodash._isiterateecall": {
-                          "version": "3.0.9",
-                          "from": "lodash._isiterateecall@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
-                        },
-                        "lodash.escape": {
-                          "version": "3.0.0",
-                          "from": "lodash.escape@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.0.0.tgz"
+                        "minimist": {
+                          "version": "0.0.10",
+                          "from": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+                          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
                         },
-                        "lodash.keys": {
-                          "version": "3.1.2",
-                          "from": "lodash.keys@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
-                          "dependencies": {
-                            "lodash._getnative": {
-                              "version": "3.9.1",
-                              "from": "lodash._getnative@>=3.0.0 <4.0.0",
-                              "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
-                            },
-                            "lodash.isarguments": {
-                              "version": "3.0.4",
-                              "from": "lodash.isarguments@>=3.0.0 <4.0.0",
-                              "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz"
-                            },
-                            "lodash.isarray": {
-                              "version": "3.0.4",
-                              "from": "lodash.isarray@>=3.0.0 <4.0.0",
-                              "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
-                            }
-                          }
+                        "deep-extend": {
+                          "version": "0.2.11",
+                          "from": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz",
+                          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz"
                         },
-                        "lodash.restparam": {
-                          "version": "3.6.1",
-                          "from": "lodash.restparam@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz"
+                        "strip-json-comments": {
+                          "version": "0.1.3",
+                          "from": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz",
+                          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz"
                         },
-                        "lodash.templatesettings": {
-                          "version": "3.1.0",
-                          "from": "lodash.templatesettings@>=3.0.0 <4.0.0",
-                          "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.0.tgz"
-                        }
-                      }
-                    },
-                    "minimist": {
-                      "version": "1.2.0",
-                      "from": "minimist@>=1.1.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                    },
-                    "multipipe": {
-                      "version": "0.1.2",
-                      "from": "multipipe@>=0.1.2 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz",
-                      "dependencies": {
-                        "duplexer2": {
-                          "version": "0.0.2",
-                          "from": "duplexer2@0.0.2",
-                          "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
-                          "dependencies": {
-                            "readable-stream": {
-                              "version": "1.1.13",
-                              "from": "readable-stream@>=1.1.9 <1.2.0",
-                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
-                              "dependencies": {
-                                "core-util-is": {
-                                  "version": "1.0.1",
-                                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                                },
-                                "inherits": {
-                                  "version": "2.0.1",
-                                  "from": "inherits@>=2.0.1 <2.1.0",
-                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                                },
-                                "isarray": {
-                                  "version": "0.0.1",
-                                  "from": "isarray@0.0.1",
-                                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                                },
-                                "string_decoder": {
-                                  "version": "0.10.31",
-                                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                                }
-                              }
-                            }
-                          }
+                        "ini": {
+                          "version": "1.3.4",
+                          "from": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
+                          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz"
                         }
                       }
-                    },
-                    "object-assign": {
-                      "version": "3.0.0",
-                      "from": "object-assign@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-                    },
-                    "replace-ext": {
-                      "version": "0.0.1",
-                      "from": "replace-ext@0.0.1",
-                      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
-                    },
-                    "vinyl": {
-                      "version": "0.5.3",
-                      "from": "vinyl@>=0.5.0 <0.6.0"
                     }
                   }
+                },
+                "is-obj": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.0.tgz"
+                },
+                "object-assign": {
+                  "version": "3.0.0",
+                  "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+                },
+                "tunnel-agent": {
+                  "version": "0.4.1",
+                  "from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz",
+                  "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz"
                 }
               }
             },
-            "gulp-rename": {
-              "version": "1.2.2",
-              "from": "gulp-rename@>=1.2.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz"
-            },
-            "is-url": {
-              "version": "1.2.1",
-              "from": "is-url@>=1.2.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.1.tgz"
-            },
-            "object-assign": {
-              "version": "4.0.1",
-              "from": "object-assign@>=4.0.1 <5.0.0",
-              "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-            },
-            "read-all-stream": {
-              "version": "3.0.1",
-              "from": "read-all-stream@>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
+            "concat-stream": {
+              "version": "1.5.0",
+              "from": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
+              "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz",
               "dependencies": {
-                "pinkie-promise": {
-                  "version": "1.0.0",
-                  "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                  "dependencies": {
-                    "pinkie": {
-                      "version": "1.0.0",
-                      "from": "pinkie@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                    }
-                  }
+                "typedarray": {
+                  "version": "0.0.6",
+                  "from": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+                  "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
                 },
                 "readable-stream": {
                   "version": "2.0.4",
-                  "from": "readable-stream@>=2.0.0 <3.0.0"
+                  "from": "readable-stream@2.0.4",
+                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
                 }
               }
             },
-            "readable-stream": {
-              "version": "2.0.2",
-              "from": "readable-stream@>=2.0.2 <3.0.0",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+            "each-async": {
+              "version": "1.1.1",
+              "from": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz",
+              "resolved": "https://registry.npmjs.org/each-async/-/each-async-1.1.1.tgz",
               "dependencies": {
-                "core-util-is": {
-                  "version": "1.0.1",
-                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.1 <2.1.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "isarray": {
-                  "version": "0.0.1",
-                  "from": "isarray@0.0.1",
-                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                "onetime": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/onetime/-/onetime-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.0.0.tgz"
                 },
-                "process-nextick-args": {
-                  "version": "1.0.3",
-                  "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+                "set-immediate-shim": {
+                  "version": "1.0.1",
+                  "from": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz"
+                }
+              }
+            },
+            "filenamify": {
+              "version": "1.2.0",
+              "from": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.0.tgz",
+              "dependencies": {
+                "filename-reserved-regex": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz"
                 },
-                "string_decoder": {
-                  "version": "0.10.31",
-                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                "strip-outer": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.0.tgz"
                 },
-                "util-deprecate": {
-                  "version": "1.0.2",
-                  "from": "util-deprecate@>=1.0.1 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                "trim-repeated": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz"
                 }
               }
             },
-            "stream-combiner2": {
-              "version": "1.0.2",
-              "from": "stream-combiner2@>=1.0.2 <2.0.0",
-              "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
+            "got": {
+              "version": "4.2.0",
+              "from": "https://registry.npmjs.org/got/-/got-4.2.0.tgz",
+              "resolved": "https://registry.npmjs.org/got/-/got-4.2.0.tgz",
               "dependencies": {
-                "duplexer2": {
-                  "version": "0.0.2",
-                  "from": "duplexer2@>=0.0.2 <0.1.0",
-                  "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
+                "create-error-class": {
+                  "version": "2.0.1",
+                  "from": "https://registry.npmjs.org/create-error-class/-/create-error-class-2.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-2.0.1.tgz",
                   "dependencies": {
-                    "readable-stream": {
-                      "version": "1.1.13",
-                      "from": "readable-stream@>=1.1.9 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
-                      "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                        },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                        },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                        },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                        }
-                      }
+                    "capture-stack-trace": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz"
                     }
                   }
                 },
-                "through2": {
-                  "version": "0.5.1",
-                  "from": "through2@>=0.5.1 <0.6.0",
-                  "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
+                "duplexify": {
+                  "version": "3.4.2",
+                  "from": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                  "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
                   "dependencies": {
-                    "readable-stream": {
-                      "version": "1.0.33",
-                      "from": "readable-stream@>=1.0.17 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                      "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                        },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                        },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                        },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                        }
-                      }
+                    "end-of-stream": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz"
                     },
-                    "xtend": {
-                      "version": "3.0.0",
-                      "from": "xtend@>=3.0.0 <3.1.0",
-                      "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz"
+                    "readable-stream": {
+                      "version": "2.0.4",
+                      "from": "readable-stream@2.0.4",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
                     }
                   }
-                }
-              }
-            },
-            "vinyl": {
-              "version": "0.5.3",
-              "from": "vinyl@>=0.5.3 <0.6.0",
-              "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
-              "dependencies": {
-                "clone": {
-                  "version": "1.0.2",
-                  "from": "clone@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
                 },
-                "clone-stats": {
-                  "version": "0.0.1",
-                  "from": "clone-stats@>=0.0.1 <0.0.2",
-                  "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
+                "is-plain-obj": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.0.0.tgz"
+                },
+                "is-redirect": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz"
+                },
+                "is-stream": {
+                  "version": "1.0.1",
+                  "from": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz"
+                },
+                "lowercase-keys": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz"
+                },
+                "node-status-codes": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/node-status-codes/-/node-status-codes-1.0.0.tgz"
+                },
+                "object-assign": {
+                  "version": "3.0.0",
+                  "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+                },
+                "prepend-http": {
+                  "version": "1.0.3",
+                  "from": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz",
+                  "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz"
                 },
-                "replace-ext": {
-                  "version": "0.0.1",
-                  "from": "replace-ext@0.0.1",
-                  "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
+                "timed-out": {
+                  "version": "2.0.0",
+                  "from": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz"
+                },
+                "unzip-response": {
+                  "version": "1.0.0",
+                  "from": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.0.tgz"
                 }
               }
             },
-            "vinyl-fs": {
-              "version": "1.0.0",
-              "from": "vinyl-fs@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-1.0.0.tgz",
+            "gulp-decompress": {
+              "version": "1.1.0",
+              "from": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/gulp-decompress/-/gulp-decompress-1.1.0.tgz",
               "dependencies": {
-                "duplexify": {
-                  "version": "3.4.2",
-                  "from": "duplexify@>=3.2.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                "archive-type": {
+                  "version": "3.0.1",
+                  "from": "https://registry.npmjs.org/archive-type/-/archive-type-3.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-3.0.1.tgz",
                   "dependencies": {
-                    "end-of-stream": {
-                      "version": "1.0.0",
-                      "from": "end-of-stream@1.0.0",
-                      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                    "file-type": {
+                      "version": "2.12.0",
+                      "from": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz",
+                      "resolved": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz",
                       "dependencies": {
-                        "once": {
-                          "version": "1.3.2",
-                          "from": "once@>=1.3.0 <1.4.0",
-                          "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
+                        "meow": {
+                          "version": "3.4.2",
+                          "from": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+                          "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
                           "dependencies": {
-                            "wrappy": {
-                              "version": "1.0.1",
-                              "from": "wrappy@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                            "normalize-package-data": {
+                              "version": "2.3.4",
+                              "from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+                              "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz"
                             }
                           }
                         }
                       }
-                    },
-                    "readable-stream": {
-                      "version": "2.0.4",
-                      "from": "readable-stream@>=2.0.0 <3.0.0"
                     }
                   }
                 },
-                "glob-stream": {
-                  "version": "4.1.1",
-                  "from": "glob-stream@>=4.0.1 <5.0.0",
-                  "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-4.1.1.tgz",
+                "decompress": {
+                  "version": "2.3.0",
+                  "from": "https://registry.npmjs.org/decompress/-/decompress-2.3.0.tgz",
+                  "resolved": "https://registry.npmjs.org/decompress/-/decompress-2.3.0.tgz",
                   "dependencies": {
-                    "glob": {
-                      "version": "4.5.3",
-                      "from": "glob@>=4.3.1 <5.0.0",
-                      "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz",
+                    "buffer-to-vinyl": {
+                      "version": "1.0.1",
+                      "from": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/buffer-to-vinyl/-/buffer-to-vinyl-1.0.1.tgz",
                       "dependencies": {
-                        "inflight": {
-                          "version": "1.0.4",
-                          "from": "inflight@>=1.0.4 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
-                          "dependencies": {
-                            "wrappy": {
-                              "version": "1.0.1",
-                              "from": "wrappy@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                            }
-                          }
+                        "file-type": {
+                          "version": "2.12.0",
+                          "from": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz",
+                          "resolved": "https://registry.npmjs.org/file-type/-/file-type-2.12.0.tgz"
                         },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                        "readable-stream": {
+                          "version": "2.0.4",
+                          "from": "readable-stream@2.0.4",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
                         },
-                        "once": {
-                          "version": "1.3.2",
-                          "from": "once@>=1.3.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                          "dependencies": {
-                            "wrappy": {
-                              "version": "1.0.1",
-                              "from": "wrappy@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "glob2base": {
-                      "version": "0.0.12",
-                      "from": "glob2base@>=0.0.12 <0.0.13",
-                      "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz",
-                      "dependencies": {
-                        "find-index": {
-                          "version": "0.1.1",
-                          "from": "find-index@>=0.1.1 <0.2.0",
-                          "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz"
+                        "uuid": {
+                          "version": "2.0.1",
+                          "from": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
+                          "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz"
                         }
                       }
                     },
-                    "minimatch": {
-                      "version": "2.0.10",
-                      "from": "minimatch@>=2.0.1 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
+                    "decompress-tar": {
+                      "version": "3.1.0",
+                      "from": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz",
+                      "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-3.1.0.tgz",
                       "dependencies": {
-                        "brace-expansion": {
+                        "is-tar": {
+                          "version": "1.0.0",
+                          "from": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/is-tar/-/is-tar-1.0.0.tgz"
+                        },
+                        "object-assign": {
+                          "version": "2.1.1",
+                          "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                        },
+                        "strip-dirs": {
                           "version": "1.1.1",
-                          "from": "brace-expansion@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
+                          "from": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
                           "dependencies": {
-                            "balanced-match": {
-                              "version": "0.2.0",
-                              "from": "balanced-match@>=0.2.0 <0.3.0",
-                              "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
+                            "is-absolute": {
+                              "version": "0.1.7",
+                              "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "dependencies": {
+                                "is-relative": {
+                                  "version": "0.1.3",
+                                  "from": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                                }
+                              }
+                            },
+                            "is-natural-number": {
+                              "version": "2.0.0",
+                              "from": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
                             },
-                            "concat-map": {
-                              "version": "0.0.1",
-                              "from": "concat-map@0.0.1",
-                              "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+                            "sum-up": {
+                              "version": "1.0.2",
+                              "from": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz",
+                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
                             }
                           }
-                        }
-                      }
-                    },
-                    "ordered-read-streams": {
-                      "version": "0.1.0",
-                      "from": "ordered-read-streams@>=0.1.0 <0.2.0",
-                      "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz"
-                    },
-                    "unique-stream": {
-                      "version": "2.2.0",
-                      "from": "unique-stream@>=2.0.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.0.tgz",
-                      "dependencies": {
-                        "through2-filter": {
-                          "version": "2.0.0",
-                          "from": "through2-filter@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz",
+                        },
+                        "tar-stream": {
+                          "version": "1.2.1",
+                          "from": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
+                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
                           "dependencies": {
-                            "through2": {
-                              "version": "2.0.0",
-                              "from": "through2@>=2.0.0 <2.1.0",
-                              "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz",
-                              "dependencies": {
-                                "readable-stream": {
-                                  "version": "2.0.4",
-                                  "from": "readable-stream@>=2.0.0 <2.1.0"
-                                }
-                              }
+                            "bl": {
+                              "version": "1.0.0",
+                              "from": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
+                            },
+                            "end-of-stream": {
+                              "version": "1.1.0",
+                              "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
+                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+                            },
+                            "readable-stream": {
+                              "version": "2.0.4",
+                              "from": "readable-stream@2.0.4",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+                            },
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                            }
+                          }
+                        },
+                        "through2": {
+                          "version": "0.6.5",
+                          "from": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "1.0.33",
+                              "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
                             },
                             "xtend": {
                               "version": "4.0.0",
-                              "from": "xtend@>=4.0.0 <4.1.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
                               "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
                             }
                           }
+                        },
+                        "vinyl": {
+                          "version": "0.4.6",
+                          "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "dependencies": {
+                            "clone": {
+                              "version": "0.2.0",
+                              "from": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
+                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
+                            }
+                          }
                         }
                       }
-                    }
-                  }
-                },
-                "glob-watcher": {
-                  "version": "0.0.8",
-                  "from": "glob-watcher@>=0.0.8 <0.0.9",
-                  "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.8.tgz",
-                  "dependencies": {
-                    "gaze": {
-                      "version": "0.5.2",
-                      "from": "gaze@>=0.5.1 <0.6.0",
-                      "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
+                    },
+                    "decompress-tarbz2": {
+                      "version": "3.1.0",
+                      "from": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz",
+                      "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz",
                       "dependencies": {
-                        "globule": {
-                          "version": "0.1.0",
-                          "from": "globule@>=0.1.0 <0.2.0",
-                          "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
+                        "is-bzip2": {
+                          "version": "1.0.0",
+                          "from": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/is-bzip2/-/is-bzip2-1.0.0.tgz"
+                        },
+                        "object-assign": {
+                          "version": "2.1.1",
+                          "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                        },
+                        "seek-bzip": {
+                          "version": "1.0.5",
+                          "from": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
+                          "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
                           "dependencies": {
-                            "glob": {
-                              "version": "3.1.21",
-                              "from": "glob@>=3.1.21 <3.2.0",
-                              "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
+                            "commander": {
+                              "version": "2.8.1",
+                              "from": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
+                              "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz"
+                            }
+                          }
+                        },
+                        "strip-dirs": {
+                          "version": "1.1.1",
+                          "from": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                          "dependencies": {
+                            "is-absolute": {
+                              "version": "0.1.7",
+                              "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
                               "dependencies": {
-                                "graceful-fs": {
-                                  "version": "1.2.3",
-                                  "from": "graceful-fs@>=1.2.0 <1.3.0",
-                                  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz"
-                                },
-                                "inherits": {
-                                  "version": "1.0.2",
-                                  "from": "inherits@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz"
+                                "is-relative": {
+                                  "version": "0.1.3",
+                                  "from": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
                                 }
                               }
                             },
-                            "lodash": {
+                            "is-natural-number": {
+                              "version": "2.0.0",
+                              "from": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
+                            },
+                            "sum-up": {
                               "version": "1.0.2",
-                              "from": "lodash@>=1.0.1 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz"
+                              "from": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz",
+                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
+                            }
+                          }
+                        },
+                        "tar-stream": {
+                          "version": "1.2.1",
+                          "from": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
+                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
+                          "dependencies": {
+                            "bl": {
+                              "version": "1.0.0",
+                              "from": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
                             },
-                            "minimatch": {
-                              "version": "0.2.14",
-                              "from": "minimatch@>=0.2.11 <0.3.0",
-                              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
-                              "dependencies": {
-                                "lru-cache": {
-                                  "version": "2.7.0",
-                                  "from": "lru-cache@>=2.0.0 <3.0.0",
-                                  "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz"
-                                },
-                                "sigmund": {
-                                  "version": "1.0.1",
-                                  "from": "sigmund@>=1.0.0 <1.1.0",
-                                  "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
-                                }
-                              }
+                            "end-of-stream": {
+                              "version": "1.1.0",
+                              "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
+                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
+                            },
+                            "readable-stream": {
+                              "version": "2.0.4",
+                              "from": "readable-stream@2.0.4",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+                            },
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
                             }
                           }
-                        }
-                      }
-                    }
-                  }
-                },
-                "graceful-fs": {
-                  "version": "3.0.8",
-                  "from": "graceful-fs@>=3.0.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
-                },
-                "merge-stream": {
-                  "version": "0.1.8",
-                  "from": "merge-stream@>=0.1.7 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-0.1.8.tgz"
-                },
-                "object-assign": {
-                  "version": "2.1.1",
-                  "from": "object-assign@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
-                },
-                "strip-bom": {
-                  "version": "1.0.0",
-                  "from": "strip-bom@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz",
-                  "dependencies": {
-                    "first-chunk-stream": {
-                      "version": "1.0.0",
-                      "from": "first-chunk-stream@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz"
-                    },
-                    "is-utf8": {
-                      "version": "0.2.0",
-                      "from": "is-utf8@>=0.2.0 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                    }
-                  }
-                },
-                "through2": {
-                  "version": "0.6.5",
-                  "from": "through2@>=0.6.1 <0.7.0",
-                  "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
-                  "dependencies": {
-                    "readable-stream": {
-                      "version": "1.0.33",
-                      "from": "readable-stream@>=1.0.33-1 <1.1.0-0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
-                      "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                        },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
                         },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                        "through2": {
+                          "version": "0.6.5",
+                          "from": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "1.0.33",
+                              "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
+                            },
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                            }
+                          }
                         },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                        "vinyl": {
+                          "version": "0.4.6",
+                          "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "dependencies": {
+                            "clone": {
+                              "version": "0.2.0",
+                              "from": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
+                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
+                            }
+                          }
                         }
                       }
                     },
-                    "xtend": {
-                      "version": "4.0.0",
-                      "from": "xtend@>=4.0.0 <4.1.0-0",
-                      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-                    }
-                  }
-                },
-                "vinyl": {
-                  "version": "0.4.6",
-                  "from": "vinyl@>=0.4.0 <0.5.0",
-                  "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
-                  "dependencies": {
-                    "clone": {
-                      "version": "0.2.0",
-                      "from": "clone@>=0.2.0 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
-                    },
-                    "clone-stats": {
-                      "version": "0.0.1",
-                      "from": "clone-stats@>=0.0.1 <0.0.2",
-                      "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                    }
-                  }
-                }
-              }
-            },
-            "ware": {
-              "version": "1.3.0",
-              "from": "ware@>=1.2.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz",
-              "dependencies": {
-                "wrap-fn": {
-                  "version": "0.1.4",
-                  "from": "wrap-fn@>=0.1.0 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.4.tgz",
-                  "dependencies": {
-                    "co": {
+                    "decompress-targz": {
                       "version": "3.1.0",
-                      "from": "co@3.1.0",
-                      "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        },
-        "findup-sync": {
-          "version": "0.2.1",
-          "from": "findup-sync@>=0.2.1 <0.3.0",
-          "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.2.1.tgz",
-          "dependencies": {
-            "glob": {
-              "version": "4.3.5",
-              "from": "glob@>=4.3.0 <4.4.0",
-              "resolved": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz",
-              "dependencies": {
-                "inflight": {
-                  "version": "1.0.4",
-                  "from": "inflight@>=1.0.4 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "minimatch": {
-                  "version": "2.0.10",
-                  "from": "minimatch@>=2.0.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
-                  "dependencies": {
-                    "brace-expansion": {
-                      "version": "1.1.1",
-                      "from": "brace-expansion@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
-                      "dependencies": {
-                        "balanced-match": {
-                          "version": "0.2.0",
-                          "from": "balanced-match@>=0.2.0 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
-                        },
-                        "concat-map": {
-                          "version": "0.0.1",
-                          "from": "concat-map@0.0.1",
-                          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-                        }
-                      }
-                    }
-                  }
-                },
-                "once": {
-                  "version": "1.3.2",
-                  "from": "once@>=1.3.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-                  "dependencies": {
-                    "wrappy": {
-                      "version": "1.0.1",
-                      "from": "wrappy@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                    }
-                  }
-                }
-              }
-            }
-          }
-        },
-        "github-username": {
-          "version": "2.0.0",
-          "from": "github-username@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/github-username/-/github-username-2.0.0.tgz",
-          "dependencies": {
-            "gh-got": {
-              "version": "1.1.0",
-              "from": "gh-got@>=1.0.3 <2.0.0",
-              "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-1.1.0.tgz",
-              "dependencies": {
-                "got": {
-                  "version": "3.3.1",
-                  "from": "got@>=3.2.0 <4.0.0",
-                  "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
-                  "dependencies": {
-                    "duplexify": {
-                      "version": "3.4.2",
-                      "from": "duplexify@>=3.2.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                      "from": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz",
+                      "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-3.1.0.tgz",
                       "dependencies": {
-                        "end-of-stream": {
+                        "is-gzip": {
                           "version": "1.0.0",
-                          "from": "end-of-stream@1.0.0",
-                          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                          "from": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/is-gzip/-/is-gzip-1.0.0.tgz"
+                        },
+                        "object-assign": {
+                          "version": "2.1.1",
+                          "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                        },
+                        "strip-dirs": {
+                          "version": "1.1.1",
+                          "from": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
                           "dependencies": {
-                            "once": {
-                              "version": "1.3.2",
-                              "from": "once@>=1.3.0 <1.4.0",
-                              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
+                            "is-absolute": {
+                              "version": "0.1.7",
+                              "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
                               "dependencies": {
-                                "wrappy": {
-                                  "version": "1.0.1",
-                                  "from": "wrappy@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                                "is-relative": {
+                                  "version": "0.1.3",
+                                  "from": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
                                 }
                               }
+                            },
+                            "is-natural-number": {
+                              "version": "2.0.0",
+                              "from": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
+                            },
+                            "sum-up": {
+                              "version": "1.0.2",
+                              "from": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz",
+                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
                             }
                           }
                         },
-                        "readable-stream": {
-                          "version": "2.0.2",
-                          "from": "readable-stream@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+                        "tar-stream": {
+                          "version": "1.2.1",
+                          "from": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
+                          "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.2.1.tgz",
                           "dependencies": {
-                            "core-util-is": {
-                              "version": "1.0.1",
-                              "from": "core-util-is@>=1.0.0 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                            },
-                            "inherits": {
-                              "version": "2.0.1",
-                              "from": "inherits@>=2.0.1 <2.1.0",
-                              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+                            "bl": {
+                              "version": "1.0.0",
+                              "from": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz"
                             },
-                            "isarray": {
-                              "version": "0.0.1",
-                              "from": "isarray@0.0.1",
-                              "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+                            "end-of-stream": {
+                              "version": "1.1.0",
+                              "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz",
+                              "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz"
                             },
-                            "process-nextick-args": {
-                              "version": "1.0.3",
-                              "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
+                            "readable-stream": {
+                              "version": "2.0.4",
+                              "from": "readable-stream@2.0.4",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
                             },
-                            "string_decoder": {
-                              "version": "0.10.31",
-                              "from": "string_decoder@>=0.10.0 <0.11.0",
-                              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                            }
+                          }
+                        },
+                        "through2": {
+                          "version": "0.6.5",
+                          "from": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                          "dependencies": {
+                            "readable-stream": {
+                              "version": "1.0.33",
+                              "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
                             },
-                            "util-deprecate": {
-                              "version": "1.0.2",
-                              "from": "util-deprecate@>=1.0.1 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
+                            }
+                          }
+                        },
+                        "vinyl": {
+                          "version": "0.4.6",
+                          "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                          "dependencies": {
+                            "clone": {
+                              "version": "0.2.0",
+                              "from": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
+                              "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
                             }
                           }
                         }
-                      }
-                    },
-                    "infinity-agent": {
-                      "version": "2.0.3",
-                      "from": "infinity-agent@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz"
-                    },
-                    "is-redirect": {
-                      "version": "1.0.0",
-                      "from": "is-redirect@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz"
-                    },
-                    "is-stream": {
-                      "version": "1.0.1",
-                      "from": "is-stream@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz"
-                    },
-                    "lowercase-keys": {
-                      "version": "1.0.0",
-                      "from": "lowercase-keys@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz"
-                    },
-                    "nested-error-stacks": {
-                      "version": "1.0.1",
-                      "from": "nested-error-stacks@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.1.tgz",
-                      "dependencies": {
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                        }
-                      }
-                    },
-                    "object-assign": {
-                      "version": "3.0.0",
-                      "from": "object-assign@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
-                    },
-                    "prepend-http": {
-                      "version": "1.0.3",
-                      "from": "prepend-http@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz"
+                      }
                     },
-                    "read-all-stream": {
-                      "version": "3.0.1",
-                      "from": "read-all-stream@>=3.0.0 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
+                    "decompress-unzip": {
+                      "version": "3.3.0",
+                      "from": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.3.0.tgz",
+                      "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-3.3.0.tgz",
                       "dependencies": {
-                        "pinkie-promise": {
+                        "is-zip": {
                           "version": "1.0.0",
-                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
+                          "from": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/is-zip/-/is-zip-1.0.0.tgz"
+                        },
+                        "stat-mode": {
+                          "version": "0.2.1",
+                          "from": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.1.tgz",
+                          "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.1.tgz"
+                        },
+                        "strip-dirs": {
+                          "version": "1.1.1",
+                          "from": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz",
                           "dependencies": {
-                            "pinkie": {
-                              "version": "1.0.0",
-                              "from": "pinkie@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
+                            "is-absolute": {
+                              "version": "0.1.7",
+                              "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+                              "dependencies": {
+                                "is-relative": {
+                                  "version": "0.1.3",
+                                  "from": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                                  "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz"
+                                }
+                              }
+                            },
+                            "is-natural-number": {
+                              "version": "2.0.0",
+                              "from": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-2.0.0.tgz"
+                            },
+                            "sum-up": {
+                              "version": "1.0.2",
+                              "from": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz",
+                              "resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.2.tgz"
                             }
                           }
                         },
-                        "readable-stream": {
-                          "version": "2.0.2",
-                          "from": "readable-stream@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+                        "yauzl": {
+                          "version": "2.3.1",
+                          "from": "https://registry.npmjs.org/yauzl/-/yauzl-2.3.1.tgz",
+                          "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.3.1.tgz",
                           "dependencies": {
-                            "core-util-is": {
+                            "fd-slicer": {
                               "version": "1.0.1",
-                              "from": "core-util-is@>=1.0.0 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                            },
-                            "inherits": {
-                              "version": "2.0.1",
-                              "from": "inherits@>=2.0.1 <2.1.0",
-                              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                            },
-                            "isarray": {
-                              "version": "0.0.1",
-                              "from": "isarray@0.0.1",
-                              "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                            },
-                            "process-nextick-args": {
-                              "version": "1.0.3",
-                              "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
-                            },
-                            "string_decoder": {
-                              "version": "0.10.31",
-                              "from": "string_decoder@>=0.10.0 <0.11.0",
-                              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+                              "from": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+                              "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz"
                             },
-                            "util-deprecate": {
-                              "version": "1.0.2",
-                              "from": "util-deprecate@>=1.0.1 <1.1.0",
-                              "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
+                            "pend": {
+                              "version": "1.2.0",
+                              "from": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+                              "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz"
                             }
                           }
                         }
                       }
                     },
-                    "timed-out": {
-                      "version": "2.0.0",
-                      "from": "timed-out@>=2.0.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz"
+                    "meow": {
+                      "version": "3.4.2",
+                      "from": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+                      "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+                      "dependencies": {
+                        "normalize-package-data": {
+                          "version": "2.3.4",
+                          "from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+                          "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz"
+                        }
+                      }
+                    },
+                    "vinyl-assign": {
+                      "version": "1.2.1",
+                      "from": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz",
+                      "resolved": "https://registry.npmjs.org/vinyl-assign/-/vinyl-assign-1.2.1.tgz",
+                      "dependencies": {
+                        "readable-stream": {
+                          "version": "2.0.4",
+                          "from": "readable-stream@2.0.4",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+                        }
+                      }
                     }
                   }
                 },
-                "object-assign": {
-                  "version": "2.1.1",
-                  "from": "object-assign@>=2.0.0 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                "gulp-util": {
+                  "version": "3.0.6",
+                  "from": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.6.tgz",
+                  "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.6.tgz",
+                  "dependencies": {
+                    "object-assign": {
+                      "version": "3.0.0",
+                      "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+                    }
+                  }
                 }
               }
             },
-            "meow": {
-              "version": "3.4.2",
-              "from": "meow@>=3.1.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+            "gulp-rename": {
+              "version": "1.2.2",
+              "from": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz",
+              "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.2.2.tgz"
+            },
+            "is-url": {
+              "version": "1.2.1",
+              "from": "https://registry.npmjs.org/is-url/-/is-url-1.2.1.tgz",
+              "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.1.tgz"
+            },
+            "read-all-stream": {
+              "version": "3.0.1",
+              "from": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
+              "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
               "dependencies": {
-                "camelcase-keys": {
-                  "version": "1.0.0",
-                  "from": "camelcase-keys@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
+                "readable-stream": {
+                  "version": "2.0.4",
+                  "from": "readable-stream@2.0.4",
+                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+                }
+              }
+            },
+            "readable-stream": {
+              "version": "2.0.2",
+              "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz"
+            },
+            "stream-combiner2": {
+              "version": "1.0.2",
+              "from": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
+              "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.0.2.tgz",
+              "dependencies": {
+                "through2": {
+                  "version": "0.5.1",
+                  "from": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
+                  "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz",
                   "dependencies": {
-                    "camelcase": {
-                      "version": "1.2.1",
-                      "from": "camelcase@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
+                    "readable-stream": {
+                      "version": "1.0.33",
+                      "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
                     },
-                    "map-obj": {
-                      "version": "1.0.1",
-                      "from": "map-obj@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
+                    "xtend": {
+                      "version": "3.0.0",
+                      "from": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz"
+                    }
+                  }
+                }
+              }
+            },
+            "vinyl-fs": {
+              "version": "1.0.0",
+              "from": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-1.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-1.0.0.tgz",
+              "dependencies": {
+                "duplexify": {
+                  "version": "3.4.2",
+                  "from": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                  "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                  "dependencies": {
+                    "end-of-stream": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz"
+                    },
+                    "readable-stream": {
+                      "version": "2.0.4",
+                      "from": "readable-stream@2.0.4",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
                     }
                   }
                 },
-                "loud-rejection": {
-                  "version": "1.0.0",
-                  "from": "loud-rejection@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-                },
-                "minimist": {
-                  "version": "1.2.0",
-                  "from": "minimist@>=1.1.3 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                },
-                "normalize-package-data": {
-                  "version": "2.3.4",
-                  "from": "normalize-package-data@>=2.3.4 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+                "glob-stream": {
+                  "version": "4.1.1",
+                  "from": "https://registry.npmjs.org/glob-stream/-/glob-stream-4.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-4.1.1.tgz",
                   "dependencies": {
-                    "hosted-git-info": {
-                      "version": "2.1.4",
-                      "from": "hosted-git-info@>=2.0.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
+                    "glob": {
+                      "version": "4.5.3",
+                      "from": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz",
+                      "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz"
                     },
-                    "is-builtin-module": {
-                      "version": "1.0.0",
-                      "from": "is-builtin-module@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+                    "ordered-read-streams": {
+                      "version": "0.1.0",
+                      "from": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz",
+                      "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz"
+                    },
+                    "glob2base": {
+                      "version": "0.0.12",
+                      "from": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz",
+                      "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz",
                       "dependencies": {
-                        "builtin-modules": {
-                          "version": "1.1.0",
-                          "from": "builtin-modules@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
+                        "find-index": {
+                          "version": "0.1.1",
+                          "from": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz",
+                          "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz"
                         }
                       }
                     },
-                    "semver": {
-                      "version": "5.0.3",
-                      "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
-                    },
-                    "validate-npm-package-license": {
-                      "version": "3.0.1",
-                      "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
+                    "unique-stream": {
+                      "version": "2.2.0",
+                      "from": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.0.tgz",
+                      "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.0.tgz",
                       "dependencies": {
-                        "spdx-correct": {
-                          "version": "1.0.1",
-                          "from": "spdx-correct@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.1.tgz",
-                          "dependencies": {
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "spdx-expression-parse": {
-                          "version": "1.0.0",
-                          "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.0.tgz",
+                        "through2-filter": {
+                          "version": "2.0.0",
+                          "from": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz",
                           "dependencies": {
-                            "spdx-exceptions": {
-                              "version": "1.0.3",
-                              "from": "spdx-exceptions@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.3.tgz"
+                            "through2": {
+                              "version": "2.0.0",
+                              "from": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz",
+                              "dependencies": {
+                                "readable-stream": {
+                                  "version": "2.0.4",
+                                  "from": "readable-stream@2.0.4",
+                                  "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.4.tgz"
+                                }
+                              }
                             },
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
+                            "xtend": {
+                              "version": "4.0.0",
+                              "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
                             }
                           }
                         }
@@ -8447,120 +7307,57 @@
                     }
                   }
                 },
-                "object-assign": {
-                  "version": "4.0.1",
-                  "from": "object-assign@>=4.0.1 <5.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-                },
-                "read-pkg-up": {
-                  "version": "1.0.1",
-                  "from": "read-pkg-up@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+                "glob-watcher": {
+                  "version": "0.0.8",
+                  "from": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.8.tgz",
+                  "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.8.tgz",
                   "dependencies": {
-                    "find-up": {
-                      "version": "1.0.0",
-                      "from": "find-up@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
-                      "dependencies": {
-                        "path-exists": {
-                          "version": "2.0.0",
-                          "from": "path-exists@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
-                        },
-                        "pinkie-promise": {
-                          "version": "1.0.0",
-                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                          "dependencies": {
-                            "pinkie": {
-                              "version": "1.0.0",
-                              "from": "pinkie@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "read-pkg": {
-                      "version": "1.1.0",
-                      "from": "read-pkg@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+                    "gaze": {
+                      "version": "0.5.2",
+                      "from": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
+                      "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz",
                       "dependencies": {
-                        "load-json-file": {
-                          "version": "1.0.1",
-                          "from": "load-json-file@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
+                        "globule": {
+                          "version": "0.1.0",
+                          "from": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
+                          "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz",
                           "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
-                            },
-                            "parse-json": {
-                              "version": "2.2.0",
-                              "from": "parse-json@>=2.2.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                              "dependencies": {
-                                "error-ex": {
-                                  "version": "1.2.0",
-                                  "from": "error-ex@>=1.2.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                                }
-                              }
-                            },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                              "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                }
-                              }
+                            "lodash": {
+                              "version": "1.0.2",
+                              "from": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz",
+                              "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz"
                             },
-                            "strip-bom": {
-                              "version": "2.0.0",
-                              "from": "strip-bom@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+                            "glob": {
+                              "version": "3.1.21",
+                              "from": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
+                              "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz",
                               "dependencies": {
-                                "is-utf8": {
-                                  "version": "0.2.0",
-                                  "from": "is-utf8@>=0.2.0 <0.3.0",
-                                  "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
+                                "graceful-fs": {
+                                  "version": "1.2.3",
+                                  "from": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz",
+                                  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz"
+                                },
+                                "inherits": {
+                                  "version": "1.0.2",
+                                  "from": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz",
+                                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz"
                                 }
                               }
-                            }
-                          }
-                        },
-                        "path-type": {
-                          "version": "1.0.0",
-                          "from": "path-type@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-                          "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
                             },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
+                            "minimatch": {
+                              "version": "0.2.14",
+                              "from": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
+                              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz",
                               "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
+                                "lru-cache": {
+                                  "version": "2.7.0",
+                                  "from": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz",
+                                  "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz"
+                                },
+                                "sigmund": {
+                                  "version": "1.0.1",
+                                  "from": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+                                  "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
                                 }
                               }
                             }
@@ -8570,115 +7367,199 @@
                     }
                   }
                 },
-                "redent": {
+                "graceful-fs": {
+                  "version": "3.0.8",
+                  "from": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz",
+                  "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz"
+                },
+                "merge-stream": {
+                  "version": "0.1.8",
+                  "from": "https://registry.npmjs.org/merge-stream/-/merge-stream-0.1.8.tgz",
+                  "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-0.1.8.tgz"
+                },
+                "object-assign": {
+                  "version": "2.1.1",
+                  "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
+                },
+                "strip-bom": {
                   "version": "1.0.0",
-                  "from": "redent@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+                  "from": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz",
+                  "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz",
                   "dependencies": {
-                    "indent-string": {
-                      "version": "2.1.0",
-                      "from": "indent-string@>=2.1.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-                      "dependencies": {
-                        "repeating": {
-                          "version": "2.0.0",
-                          "from": "repeating@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
-                          "dependencies": {
-                            "is-finite": {
-                              "version": "1.0.1",
-                              "from": "is-finite@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz",
-                              "dependencies": {
-                                "number-is-nan": {
-                                  "version": "1.0.0",
-                                  "from": "number-is-nan@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        }
-                      }
+                    "first-chunk-stream": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz"
+                    }
+                  }
+                },
+                "through2": {
+                  "version": "0.6.5",
+                  "from": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                  "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+                  "dependencies": {
+                    "readable-stream": {
+                      "version": "1.0.33",
+                      "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz"
                     },
-                    "strip-indent": {
-                      "version": "1.0.1",
-                      "from": "strip-indent@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
-                      "dependencies": {
-                        "get-stdin": {
-                          "version": "4.0.1",
-                          "from": "get-stdin@>=4.0.1 <5.0.0",
-                          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-                        }
-                      }
+                    "xtend": {
+                      "version": "4.0.0",
+                      "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
                     }
                   }
                 },
-                "trim-newlines": {
-                  "version": "1.0.0",
-                  "from": "trim-newlines@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
+                "vinyl": {
+                  "version": "0.4.6",
+                  "from": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                  "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz",
+                  "dependencies": {
+                    "clone": {
+                      "version": "0.2.0",
+                      "from": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz",
+                      "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz"
+                    }
+                  }
+                }
+              }
+            },
+            "ware": {
+              "version": "1.3.0",
+              "from": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz",
+              "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz",
+              "dependencies": {
+                "wrap-fn": {
+                  "version": "0.1.4",
+                  "from": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.4.tgz",
+                  "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.4.tgz",
+                  "dependencies": {
+                    "co": {
+                      "version": "3.1.0",
+                      "from": "https://registry.npmjs.org/co/-/co-3.1.0.tgz",
+                      "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz"
+                    }
+                  }
                 }
               }
             }
           }
         },
-        "glob": {
-          "version": "5.0.15",
-          "from": "glob@>=5.0.3 <6.0.0",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+        "findup-sync": {
+          "version": "0.2.1",
+          "from": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.2.1.tgz",
+          "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.2.1.tgz",
           "dependencies": {
-            "inflight": {
-              "version": "1.0.4",
-              "from": "inflight@>=1.0.4 <2.0.0",
-              "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz",
-              "dependencies": {
-                "wrappy": {
-                  "version": "1.0.1",
-                  "from": "wrappy@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                }
-              }
-            },
-            "inherits": {
-              "version": "2.0.1",
-              "from": "inherits@>=2.0.0 <3.0.0",
-              "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-            },
-            "minimatch": {
-              "version": "3.0.0",
-              "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
-              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz",
+            "glob": {
+              "version": "4.3.5",
+              "from": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-4.3.5.tgz"
+            }
+          }
+        },
+        "github-username": {
+          "version": "2.0.0",
+          "from": "https://registry.npmjs.org/github-username/-/github-username-2.0.0.tgz",
+          "resolved": "https://registry.npmjs.org/github-username/-/github-username-2.0.0.tgz",
+          "dependencies": {
+            "gh-got": {
+              "version": "1.1.0",
+              "from": "https://registry.npmjs.org/gh-got/-/gh-got-1.1.0.tgz",
+              "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-1.1.0.tgz",
               "dependencies": {
-                "brace-expansion": {
-                  "version": "1.1.1",
-                  "from": "brace-expansion@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
+                "got": {
+                  "version": "3.3.1",
+                  "from": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
+                  "resolved": "https://registry.npmjs.org/got/-/got-3.3.1.tgz",
                   "dependencies": {
-                    "balanced-match": {
-                      "version": "0.2.0",
-                      "from": "balanced-match@>=0.2.0 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
+                    "duplexify": {
+                      "version": "3.4.2",
+                      "from": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.4.2.tgz",
+                      "dependencies": {
+                        "end-of-stream": {
+                          "version": "1.0.0",
+                          "from": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz",
+                          "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz"
+                        },
+                        "readable-stream": {
+                          "version": "2.0.2",
+                          "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz"
+                        }
+                      }
+                    },
+                    "infinity-agent": {
+                      "version": "2.0.3",
+                      "from": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz",
+                      "resolved": "https://registry.npmjs.org/infinity-agent/-/infinity-agent-2.0.3.tgz"
                     },
-                    "concat-map": {
-                      "version": "0.0.1",
-                      "from": "concat-map@0.0.1",
-                      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+                    "is-redirect": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz"
+                    },
+                    "is-stream": {
+                      "version": "1.0.1",
+                      "from": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.0.1.tgz"
+                    },
+                    "lowercase-keys": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz"
+                    },
+                    "nested-error-stacks": {
+                      "version": "1.0.1",
+                      "from": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-1.0.1.tgz"
+                    },
+                    "object-assign": {
+                      "version": "3.0.0",
+                      "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
+                    },
+                    "prepend-http": {
+                      "version": "1.0.3",
+                      "from": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz",
+                      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.3.tgz"
+                    },
+                    "read-all-stream": {
+                      "version": "3.0.1",
+                      "from": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
+                      "resolved": "https://registry.npmjs.org/read-all-stream/-/read-all-stream-3.0.1.tgz",
+                      "dependencies": {
+                        "readable-stream": {
+                          "version": "2.0.2",
+                          "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
+                          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz"
+                        }
+                      }
+                    },
+                    "timed-out": {
+                      "version": "2.0.0",
+                      "from": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-2.0.0.tgz"
                     }
                   }
+                },
+                "object-assign": {
+                  "version": "2.1.1",
+                  "from": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
+                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
                 }
               }
             },
-            "once": {
-              "version": "1.3.2",
-              "from": "once@>=1.3.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
+            "meow": {
+              "version": "3.4.2",
+              "from": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
+              "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
               "dependencies": {
-                "wrappy": {
-                  "version": "1.0.1",
-                  "from": "wrappy@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
+                "normalize-package-data": {
+                  "version": "2.3.4",
+                  "from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+                  "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz"
                 }
               }
             }
@@ -8686,93 +7567,81 @@
         },
         "gruntfile-editor": {
           "version": "1.0.0",
-          "from": "gruntfile-editor@>=1.0.0 <2.0.0",
+          "from": "https://registry.npmjs.org/gruntfile-editor/-/gruntfile-editor-1.0.0.tgz",
           "resolved": "https://registry.npmjs.org/gruntfile-editor/-/gruntfile-editor-1.0.0.tgz",
           "dependencies": {
             "ast-query": {
               "version": "1.0.1",
-              "from": "ast-query@>=1.0.1 <2.0.0",
+              "from": "https://registry.npmjs.org/ast-query/-/ast-query-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/ast-query/-/ast-query-1.0.1.tgz",
               "dependencies": {
                 "escodegen": {
                   "version": "1.7.0",
-                  "from": "escodegen@>=1.6.0 <2.0.0",
+                  "from": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.0.tgz",
                   "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.0.tgz",
                   "dependencies": {
-                    "esprima": {
-                      "version": "1.2.5",
-                      "from": "esprima@>=1.2.2 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
-                    },
                     "estraverse": {
                       "version": "1.9.3",
-                      "from": "estraverse@>=1.9.1 <2.0.0",
+                      "from": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
                       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz"
                     },
-                    "esutils": {
-                      "version": "2.0.2",
-                      "from": "esutils@>=2.0.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+                    "esprima": {
+                      "version": "1.2.5",
+                      "from": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
+                      "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
                     },
                     "optionator": {
                       "version": "0.5.0",
-                      "from": "optionator@>=0.5.0 <0.6.0",
+                      "from": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
                       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
                       "dependencies": {
+                        "prelude-ls": {
+                          "version": "1.1.2",
+                          "from": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+                          "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+                        },
                         "deep-is": {
                           "version": "0.1.3",
-                          "from": "deep-is@>=0.1.2 <0.2.0",
+                          "from": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
                           "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
                         },
-                        "fast-levenshtein": {
-                          "version": "1.0.7",
-                          "from": "fast-levenshtein@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
-                        },
-                        "levn": {
-                          "version": "0.2.5",
-                          "from": "levn@>=0.2.5 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
-                        },
-                        "prelude-ls": {
-                          "version": "1.1.2",
-                          "from": "prelude-ls@>=1.1.1 <1.2.0",
-                          "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+                        "wordwrap": {
+                          "version": "0.0.3",
+                          "from": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+                          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
                         },
                         "type-check": {
                           "version": "0.3.1",
-                          "from": "type-check@>=0.3.1 <0.4.0",
+                          "from": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz",
                           "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz"
                         },
-                        "wordwrap": {
-                          "version": "0.0.3",
-                          "from": "wordwrap@>=0.0.2 <0.1.0",
-                          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+                        "levn": {
+                          "version": "0.2.5",
+                          "from": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz",
+                          "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
+                        },
+                        "fast-levenshtein": {
+                          "version": "1.0.7",
+                          "from": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz",
+                          "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz"
                         }
                       }
                     },
                     "source-map": {
                       "version": "0.2.0",
-                      "from": "source-map@>=0.2.0 <0.3.0",
-                      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
-                      "dependencies": {
-                        "amdefine": {
-                          "version": "1.0.0",
-                          "from": "amdefine@>=0.0.4",
-                          "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
-                        }
-                      }
+                      "from": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+                      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz"
                     }
                   }
                 },
                 "esprima": {
                   "version": "2.6.0",
-                  "from": "esprima@>=2.0.0 <3.0.0",
+                  "from": "https://registry.npmjs.org/esprima/-/esprima-2.6.0.tgz",
                   "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.6.0.tgz"
                 },
                 "traverse": {
                   "version": "0.6.6",
-                  "from": "traverse@>=0.6.6 <0.7.0",
+                  "from": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz",
                   "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz"
                 }
               }
@@ -8781,116 +7650,94 @@
         },
         "html-wiring": {
           "version": "1.2.0",
-          "from": "html-wiring@>=1.0.0 <2.0.0",
+          "from": "https://registry.npmjs.org/html-wiring/-/html-wiring-1.2.0.tgz",
           "resolved": "https://registry.npmjs.org/html-wiring/-/html-wiring-1.2.0.tgz",
           "dependencies": {
             "cheerio": {
               "version": "0.19.0",
-              "from": "cheerio@>=0.19.0 <0.20.0",
+              "from": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz",
               "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz",
               "dependencies": {
                 "css-select": {
                   "version": "1.0.0",
-                  "from": "css-select@>=1.0.0 <1.1.0",
+                  "from": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz",
                   "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz",
                   "dependencies": {
-                    "boolbase": {
-                      "version": "1.0.0",
-                      "from": "boolbase@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz"
-                    },
                     "css-what": {
                       "version": "1.0.0",
-                      "from": "css-what@>=1.0.0 <1.1.0",
+                      "from": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz",
                       "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz"
                     },
                     "domutils": {
                       "version": "1.4.3",
-                      "from": "domutils@>=1.4.0 <1.5.0",
+                      "from": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz",
                       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz",
                       "dependencies": {
                         "domelementtype": {
                           "version": "1.3.0",
-                          "from": "domelementtype@>=1.0.0 <2.0.0",
+                          "from": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
                           "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
                         }
                       }
                     },
+                    "boolbase": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz"
+                    },
                     "nth-check": {
                       "version": "1.0.1",
-                      "from": "nth-check@>=1.0.0 <1.1.0",
+                      "from": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
                       "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz"
                     }
                   }
                 },
-                "dom-serializer": {
-                  "version": "0.1.0",
-                  "from": "dom-serializer@>=0.1.0 <0.2.0",
-                  "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
-                  "dependencies": {
-                    "domelementtype": {
-                      "version": "1.1.3",
-                      "from": "domelementtype@>=1.1.1 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
-                    }
-                  }
-                },
                 "entities": {
                   "version": "1.1.1",
-                  "from": "entities@>=1.1.1 <1.2.0",
+                  "from": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
                   "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
                 },
                 "htmlparser2": {
                   "version": "3.8.3",
-                  "from": "htmlparser2@>=3.8.1 <3.9.0",
+                  "from": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
                   "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
                   "dependencies": {
-                    "domelementtype": {
-                      "version": "1.3.0",
-                      "from": "domelementtype@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
-                    },
                     "domhandler": {
                       "version": "2.3.0",
-                      "from": "domhandler@>=2.3.0 <2.4.0",
+                      "from": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
                       "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
                     },
                     "domutils": {
                       "version": "1.5.1",
-                      "from": "domutils@>=1.5.0 <1.6.0",
+                      "from": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
                       "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
                     },
-                    "entities": {
-                      "version": "1.0.0",
-                      "from": "entities@>=1.0.0 <1.1.0",
-                      "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz"
+                    "domelementtype": {
+                      "version": "1.3.0",
+                      "from": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+                      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
                     },
                     "readable-stream": {
                       "version": "1.1.13",
-                      "from": "readable-stream@>=1.1.0 <1.2.0",
-                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
-                      "dependencies": {
-                        "core-util-is": {
-                          "version": "1.0.1",
-                          "from": "core-util-is@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                        },
-                        "inherits": {
-                          "version": "2.0.1",
-                          "from": "inherits@>=2.0.1 <2.1.0",
-                          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                        },
-                        "isarray": {
-                          "version": "0.0.1",
-                          "from": "isarray@0.0.1",
-                          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                        },
-                        "string_decoder": {
-                          "version": "0.10.31",
-                          "from": "string_decoder@>=0.10.0 <0.11.0",
-                          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                        }
-                      }
+                      "from": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz",
+                      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
+                    },
+                    "entities": {
+                      "version": "1.0.0",
+                      "from": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+                      "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz"
+                    }
+                  }
+                },
+                "dom-serializer": {
+                  "version": "0.1.0",
+                  "from": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+                  "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+                  "dependencies": {
+                    "domelementtype": {
+                      "version": "1.1.3",
+                      "from": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+                      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
                     }
                   }
                 }
@@ -8898,200 +7745,113 @@
             },
             "detect-newline": {
               "version": "1.0.3",
-              "from": "detect-newline@>=1.0.3 <2.0.0",
-              "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-1.0.3.tgz",
-              "dependencies": {
-                "get-stdin": {
-                  "version": "4.0.1",
-                  "from": "get-stdin@>=4.0.1 <5.0.0",
-                  "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-                },
-                "minimist": {
-                  "version": "1.2.0",
-                  "from": "minimist@>=1.1.3 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                }
-              }
+              "from": "https://registry.npmjs.org/detect-newline/-/detect-newline-1.0.3.tgz",
+              "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-1.0.3.tgz"
             }
           }
         },
         "inquirer": {
           "version": "0.8.5",
-          "from": "inquirer@>=0.8.0 <0.9.0",
+          "from": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
           "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz",
           "dependencies": {
             "ansi-regex": {
               "version": "1.1.1",
-              "from": "ansi-regex@>=1.1.1 <2.0.0",
+              "from": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz",
               "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz"
             },
             "cli-width": {
               "version": "1.0.1",
-              "from": "cli-width@>=1.0.1 <2.0.0",
+              "from": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz"
             },
             "figures": {
               "version": "1.4.0",
-              "from": "figures@>=1.3.5 <2.0.0",
+              "from": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz",
               "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz"
             },
             "readline2": {
               "version": "0.1.1",
-              "from": "readline2@>=0.1.1 <0.2.0",
+              "from": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
               "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz",
               "dependencies": {
                 "mute-stream": {
                   "version": "0.0.4",
-                  "from": "mute-stream@0.0.4",
+                  "from": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz",
                   "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz"
                 },
                 "strip-ansi": {
                   "version": "2.0.1",
-                  "from": "strip-ansi@>=2.0.1 <3.0.0",
+                  "from": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz",
                   "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz"
                 }
               }
             },
             "rx": {
               "version": "2.5.3",
-              "from": "rx@>=2.4.3 <3.0.0",
+              "from": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz",
               "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz"
-            },
-            "through": {
-              "version": "2.3.8",
-              "from": "through@>=2.3.6 <3.0.0",
-              "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
             }
           }
         },
         "istextorbinary": {
           "version": "1.0.2",
-          "from": "istextorbinary@>=1.0.2 <2.0.0",
+          "from": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-1.0.2.tgz",
           "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-1.0.2.tgz",
           "dependencies": {
-            "binaryextensions": {
-              "version": "1.0.0",
-              "from": "binaryextensions@>=1.0.0 <1.1.0",
-              "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.0.tgz"
-            },
             "textextensions": {
               "version": "1.0.1",
-              "from": "textextensions@>=1.0.0 <1.1.0",
+              "from": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-1.0.1.tgz"
+            },
+            "binaryextensions": {
+              "version": "1.0.0",
+              "from": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.0.tgz",
+              "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.0.tgz"
             }
           }
         },
-        "lodash": {
-          "version": "3.10.1",
-          "from": "lodash@>=3.5.0 <4.0.0",
-          "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
-        },
         "mem-fs-editor": {
           "version": "2.0.4",
-          "from": "mem-fs-editor@>=2.0.0 <3.0.0",
+          "from": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-2.0.4.tgz",
           "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-2.0.4.tgz",
           "dependencies": {
             "commondir": {
               "version": "1.0.1",
-              "from": "commondir@>=1.0.1 <2.0.0",
+              "from": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz"
             },
             "ejs": {
               "version": "2.3.4",
-              "from": "ejs@>=2.3.1 <3.0.0",
+              "from": "https://registry.npmjs.org/ejs/-/ejs-2.3.4.tgz",
               "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.3.4.tgz"
             },
             "globby": {
               "version": "2.1.0",
-              "from": "globby@>=2.0.0 <3.0.0",
+              "from": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz",
               "resolved": "https://registry.npmjs.org/globby/-/globby-2.1.0.tgz",
               "dependencies": {
                 "array-union": {
                   "version": "1.0.1",
-                  "from": "array-union@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
-                  "dependencies": {
-                    "array-uniq": {
-                      "version": "1.0.2",
-                      "from": "array-uniq@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
-                    }
-                  }
+                  "from": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz"
                 },
                 "object-assign": {
                   "version": "3.0.0",
-                  "from": "object-assign@>=3.0.0 <4.0.0",
+                  "from": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
                   "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz"
                 }
               }
             },
             "multimatch": {
               "version": "2.0.0",
-              "from": "multimatch@>=2.0.0 <3.0.0",
+              "from": "https://registry.npmjs.org/multimatch/-/multimatch-2.0.0.tgz",
               "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.0.0.tgz",
               "dependencies": {
-                "array-differ": {
-                  "version": "1.0.0",
-                  "from": "array-differ@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz"
-                },
                 "array-union": {
                   "version": "1.0.1",
-                  "from": "array-union@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
-                  "dependencies": {
-                    "array-uniq": {
-                      "version": "1.0.2",
-                      "from": "array-uniq@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
-                    }
-                  }
-                },
-                "minimatch": {
-                  "version": "2.0.10",
-                  "from": "minimatch@>=2.0.1 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz",
-                  "dependencies": {
-                    "brace-expansion": {
-                      "version": "1.1.1",
-                      "from": "brace-expansion@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz",
-                      "dependencies": {
-                        "balanced-match": {
-                          "version": "0.2.0",
-                          "from": "balanced-match@>=0.2.0 <0.3.0",
-                          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz"
-                        },
-                        "concat-map": {
-                          "version": "0.0.1",
-                          "from": "concat-map@0.0.1",
-                          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
-                        }
-                      }
-                    }
-                  }
-                }
-              }
-            },
-            "vinyl": {
-              "version": "0.5.3",
-              "from": "vinyl@>=0.5.0 <0.6.0",
-              "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz",
-              "dependencies": {
-                "clone": {
-                  "version": "1.0.2",
-                  "from": "clone@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz"
-                },
-                "clone-stats": {
-                  "version": "0.0.1",
-                  "from": "clone-stats@>=0.0.1 <0.0.2",
-                  "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz"
-                },
-                "replace-ext": {
-                  "version": "0.0.1",
-                  "from": "replace-ext@0.0.1",
-                  "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz"
+                  "from": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz",
+                  "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz"
                 }
               }
             }
@@ -9099,476 +7859,129 @@
         },
         "mime": {
           "version": "1.3.4",
-          "from": "mime@>=1.2.9 <2.0.0",
+          "from": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
           "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
         },
-        "mkdirp": {
-          "version": "0.5.1",
-          "from": "mkdirp@>=0.5.0 <0.6.0",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "dependencies": {
-            "minimist": {
-              "version": "0.0.8",
-              "from": "minimist@0.0.8",
-              "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
-            }
-          }
-        },
         "nopt": {
           "version": "3.0.4",
-          "from": "nopt@>=3.0.0 <4.0.0",
+          "from": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz",
           "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz",
           "dependencies": {
             "abbrev": {
               "version": "1.0.7",
-              "from": "abbrev@>=1.0.0 <2.0.0",
+              "from": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz",
               "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
             }
           }
         },
-        "path-exists": {
-          "version": "1.0.0",
-          "from": "path-exists@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz"
-        },
-        "path-is-absolute": {
-          "version": "1.0.0",
-          "from": "path-is-absolute@>=1.0.0 <2.0.0",
-          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
-        },
         "pretty-bytes": {
           "version": "2.0.1",
-          "from": "pretty-bytes@>=2.0.1 <3.0.0",
+          "from": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-2.0.1.tgz",
           "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-2.0.1.tgz",
           "dependencies": {
-            "get-stdin": {
-              "version": "4.0.1",
-              "from": "get-stdin@>=4.0.1 <5.0.0",
-              "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz"
-            },
             "meow": {
               "version": "3.4.2",
-              "from": "meow@>=3.1.0 <4.0.0",
+              "from": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
               "resolved": "https://registry.npmjs.org/meow/-/meow-3.4.2.tgz",
               "dependencies": {
-                "camelcase-keys": {
-                  "version": "1.0.0",
-                  "from": "camelcase-keys@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz",
-                  "dependencies": {
-                    "camelcase": {
-                      "version": "1.2.1",
-                      "from": "camelcase@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz"
-                    },
-                    "map-obj": {
-                      "version": "1.0.1",
-                      "from": "map-obj@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz"
-                    }
-                  }
-                },
-                "loud-rejection": {
-                  "version": "1.0.0",
-                  "from": "loud-rejection@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.0.0.tgz"
-                },
-                "minimist": {
-                  "version": "1.2.0",
-                  "from": "minimist@>=1.1.3 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
-                },
                 "normalize-package-data": {
                   "version": "2.3.4",
-                  "from": "normalize-package-data@>=2.3.4 <3.0.0",
-                  "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
-                  "dependencies": {
-                    "hosted-git-info": {
-                      "version": "2.1.4",
-                      "from": "hosted-git-info@>=2.0.2 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz"
-                    },
-                    "is-builtin-module": {
-                      "version": "1.0.0",
-                      "from": "is-builtin-module@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
-                      "dependencies": {
-                        "builtin-modules": {
-                          "version": "1.1.0",
-                          "from": "builtin-modules@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz"
-                        }
-                      }
-                    },
-                    "semver": {
-                      "version": "5.0.3",
-                      "from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0"
-                    },
-                    "validate-npm-package-license": {
-                      "version": "3.0.1",
-                      "from": "validate-npm-package-license@>=3.0.1 <4.0.0",
-                      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz",
-                      "dependencies": {
-                        "spdx-correct": {
-                          "version": "1.0.1",
-                          "from": "spdx-correct@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.1.tgz",
-                          "dependencies": {
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.2 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                            }
-                          }
-                        },
-                        "spdx-expression-parse": {
-                          "version": "1.0.0",
-                          "from": "spdx-expression-parse@>=1.0.0 <1.1.0",
-                          "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.0.tgz",
-                          "dependencies": {
-                            "spdx-exceptions": {
-                              "version": "1.0.3",
-                              "from": "spdx-exceptions@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.3.tgz"
-                            },
-                            "spdx-license-ids": {
-                              "version": "1.0.2",
-                              "from": "spdx-license-ids@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.0.2.tgz"
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                },
-                "object-assign": {
-                  "version": "4.0.1",
-                  "from": "object-assign@>=4.0.1 <5.0.0",
-                  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
-                },
-                "read-pkg-up": {
-                  "version": "1.0.1",
-                  "from": "read-pkg-up@>=1.0.1 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-                  "dependencies": {
-                    "find-up": {
-                      "version": "1.0.0",
-                      "from": "find-up@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.0.0.tgz",
-                      "dependencies": {
-                        "path-exists": {
-                          "version": "2.0.0",
-                          "from": "path-exists@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.0.0.tgz"
-                        },
-                        "pinkie-promise": {
-                          "version": "1.0.0",
-                          "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                          "dependencies": {
-                            "pinkie": {
-                              "version": "1.0.0",
-                              "from": "pinkie@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "read-pkg": {
-                      "version": "1.1.0",
-                      "from": "read-pkg@>=1.0.0 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-                      "dependencies": {
-                        "load-json-file": {
-                          "version": "1.0.1",
-                          "from": "load-json-file@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.0.1.tgz",
-                          "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
-                            },
-                            "parse-json": {
-                              "version": "2.2.0",
-                              "from": "parse-json@>=2.2.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                              "dependencies": {
-                                "error-ex": {
-                                  "version": "1.2.0",
-                                  "from": "error-ex@>=1.2.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.2.0.tgz"
-                                }
-                              }
-                            },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                              "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                }
-                              }
-                            },
-                            "strip-bom": {
-                              "version": "2.0.0",
-                              "from": "strip-bom@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
-                              "dependencies": {
-                                "is-utf8": {
-                                  "version": "0.2.0",
-                                  "from": "is-utf8@>=0.2.0 <0.3.0",
-                                  "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        },
-                        "path-type": {
-                          "version": "1.0.0",
-                          "from": "path-type@>=1.0.0 <2.0.0",
-                          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.0.0.tgz",
-                          "dependencies": {
-                            "graceful-fs": {
-                              "version": "4.1.2",
-                              "from": "graceful-fs@>=4.1.2 <5.0.0"
-                            },
-                            "pify": {
-                              "version": "2.2.0",
-                              "from": "pify@>=2.0.0 <3.0.0",
-                              "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz"
-                            },
-                            "pinkie-promise": {
-                              "version": "1.0.0",
-                              "from": "pinkie-promise@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz",
-                              "dependencies": {
-                                "pinkie": {
-                                  "version": "1.0.0",
-                                  "from": "pinkie@>=1.0.0 <2.0.0",
-                                  "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz"
-                                }
-                              }
-                            }
-                          }
-                        }
-                      }
-                    }
-                  }
-                },
-                "redent": {
-                  "version": "1.0.0",
-                  "from": "redent@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-                  "dependencies": {
-                    "indent-string": {
-                      "version": "2.1.0",
-                      "from": "indent-string@>=2.1.0 <3.0.0",
-                      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-                      "dependencies": {
-                        "repeating": {
-                          "version": "2.0.0",
-                          "from": "repeating@>=2.0.0 <3.0.0",
-                          "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.0.tgz",
-                          "dependencies": {
-                            "is-finite": {
-                              "version": "1.0.1",
-                              "from": "is-finite@>=1.0.0 <2.0.0",
-                              "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz"
-                            }
-                          }
-                        }
-                      }
-                    },
-                    "strip-indent": {
-                      "version": "1.0.1",
-                      "from": "strip-indent@>=1.0.1 <2.0.0",
-                      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz"
-                    }
-                  }
-                },
-                "trim-newlines": {
-                  "version": "1.0.0",
-                  "from": "trim-newlines@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
+                  "from": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz",
+                  "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz"
                 }
               }
-            },
-            "number-is-nan": {
-              "version": "1.0.0",
-              "from": "number-is-nan@>=1.0.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz"
             }
           }
         },
         "read-chunk": {
           "version": "1.0.1",
-          "from": "read-chunk@>=1.0.1 <2.0.0",
+          "from": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz",
           "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz"
         },
         "rimraf": {
           "version": "2.4.3",
-          "from": "rimraf@>=2.2.0 <3.0.0",
+          "from": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz",
           "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz"
         },
         "run-async": {
           "version": "0.1.0",
-          "from": "run-async@>=0.1.0 <0.2.0",
-          "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
-          "dependencies": {
-            "once": {
-              "version": "1.3.2",
-              "from": "once@>=1.3.0 <2.0.0",
-              "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz",
-              "dependencies": {
-                "wrappy": {
-                  "version": "1.0.1",
-                  "from": "wrappy@>=1.0.0 <2.0.0",
-                  "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz"
-                }
-              }
-            }
-          }
+          "from": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+          "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz"
         },
         "shelljs": {
           "version": "0.5.3",
-          "from": "shelljs@>=0.5.0 <0.6.0",
+          "from": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz",
           "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz"
         },
         "sinon": {
           "version": "1.17.1",
-          "from": "sinon@>=1.9.1 <2.0.0",
+          "from": "https://registry.npmjs.org/sinon/-/sinon-1.17.1.tgz",
           "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.1.tgz",
           "dependencies": {
             "formatio": {
               "version": "1.1.1",
-              "from": "formatio@1.1.1",
+              "from": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
               "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz"
             },
+            "util": {
+              "version": "0.10.3",
+              "from": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+              "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz"
+            },
             "lolex": {
               "version": "1.3.2",
-              "from": "lolex@1.3.2",
+              "from": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz",
               "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz"
             },
             "samsam": {
               "version": "1.1.2",
-              "from": "samsam@1.1.2",
+              "from": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz",
               "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz"
-            },
-            "util": {
-              "version": "0.10.3",
-              "from": "util@>=0.10.3 <1.0.0",
-              "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
-              "dependencies": {
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@2.0.1",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                }
-              }
             }
           }
         },
         "text-table": {
           "version": "0.2.0",
-          "from": "text-table@>=0.2.0 <0.3.0",
+          "from": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
           "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
         },
-        "through2": {
-          "version": "2.0.0",
-          "from": "through2@>=2.0.0 <3.0.0",
-          "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.0.tgz",
-          "dependencies": {
-            "readable-stream": {
-              "version": "2.0.2",
-              "from": "readable-stream@>=2.0.2 <3.0.0",
-              "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz",
-              "dependencies": {
-                "core-util-is": {
-                  "version": "1.0.1",
-                  "from": "core-util-is@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
-                },
-                "inherits": {
-                  "version": "2.0.1",
-                  "from": "inherits@>=2.0.1 <2.1.0",
-                  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
-                },
-                "isarray": {
-                  "version": "0.0.1",
-                  "from": "isarray@0.0.1",
-                  "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
-                },
-                "process-nextick-args": {
-                  "version": "1.0.3",
-                  "from": "process-nextick-args@>=1.0.0 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz"
-                },
-                "string_decoder": {
-                  "version": "0.10.31",
-                  "from": "string_decoder@>=0.10.0 <0.11.0",
-                  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
-                },
-                "util-deprecate": {
-                  "version": "1.0.2",
-                  "from": "util-deprecate@>=1.0.1 <1.1.0",
-                  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
-                }
-              }
-            },
-            "xtend": {
-              "version": "4.0.0",
-              "from": "xtend@>=4.0.0 <4.1.0",
-              "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
-            }
-          }
-        },
         "underscore.string": {
           "version": "3.2.2",
-          "from": "underscore.string@>=3.0.3 <4.0.0",
+          "from": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.2.tgz",
           "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.2.2.tgz"
         },
         "user-home": {
           "version": "2.0.0",
-          "from": "user-home@>=2.0.0 <3.0.0",
+          "from": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
           "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz",
           "dependencies": {
             "os-homedir": {
               "version": "1.0.1",
-              "from": "os-homedir@>=1.0.0 <2.0.0",
+              "from": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
             }
           }
         },
         "xdg-basedir": {
           "version": "2.0.0",
-          "from": "xdg-basedir@>=2.0.0 <3.0.0",
+          "from": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz",
           "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz",
           "dependencies": {
             "os-homedir": {
               "version": "1.0.1",
-              "from": "os-homedir@>=1.0.0 <2.0.0",
+              "from": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz",
               "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
             }
           }
         },
         "yeoman-assert": {
           "version": "2.1.0",
-          "from": "yeoman-assert@>=2.0.0 <3.0.0",
+          "from": "https://registry.npmjs.org/yeoman-assert/-/yeoman-assert-2.1.0.tgz",
           "resolved": "https://registry.npmjs.org/yeoman-assert/-/yeoman-assert-2.1.0.tgz"
         },
         "yeoman-welcome": {
           "version": "1.0.1",
-          "from": "yeoman-welcome@>=1.0.0 <2.0.0",
+          "from": "https://registry.npmjs.org/yeoman-welcome/-/yeoman-welcome-1.0.1.tgz",
           "resolved": "https://registry.npmjs.org/yeoman-welcome/-/yeoman-welcome-1.0.1.tgz"
         }
       }
diff --git a/package.json b/package.json
index b3f5727f70c1..34a0d874f0d9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-native",
-  "version": "0.12.0",
+  "version": "0.0.0-master",
   "description": "A framework for building native apps using React",
   "license": "BSD-3-Clause",
   "repository": {
@@ -17,15 +17,83 @@
       "^image![a-zA-Z0-9$_-]+$": "GlobalImageStub",
       "^[./a-zA-Z0-9$_-]+\\.png$": "RelativeImageStub"
     },
+    "testRunner": "<rootDir>/node_modules/jest-cli/src/testRunners/jasmine/jasmine2.js",
     "testPathIgnorePatterns": [
       "/node_modules/"
     ],
+    "haste": {
+      "defaultPlatform": "ios",
+      "providesModuleNodeModules": [
+        "fbjs",
+        "react",
+        "react-native",
+        "parse",
+        "react-transform-hmr"
+      ],
+      "platforms": [
+        "ios",
+        "android"
+      ]
+    },
+    "modulePathIgnorePatterns": [
+      "/node_modules/(?!react|fbjs|react-native|parse|react-transform-hmr|core-js|promise)/",
+      "node_modules/react/node_modules/fbjs/",
+      "node_modules/react/lib/React.js",
+      "node_modules/react/lib/ReactDOM.js",
+      "node_modules/fbjs/lib/Map.js",
+      "node_modules/fbjs/lib/Promise.js",
+      "node_modules/fbjs/lib/fetch.js",
+      "node_modules/fbjs/lib/ErrorUtils.js",
+      "node_modules/fbjs/lib/URI.js",
+      "node_modules/fbjs/lib/Deferred.js",
+      "node_modules/fbjs/lib/PromiseMap.js",
+      "node_modules/fbjs/lib/UserAgent.js",
+      "node_modules/fbjs/lib/areEqual.js",
+      "node_modules/fbjs/lib/base62.js",
+      "node_modules/fbjs/lib/crc32.js",
+      "node_modules/fbjs/lib/everyObject.js",
+      "node_modules/fbjs/lib/fetchWithRetries.js",
+      "node_modules/fbjs/lib/filterObject.js",
+      "node_modules/fbjs/lib/flattenArray.js",
+      "node_modules/fbjs/lib/forEachObject.js",
+      "node_modules/fbjs/lib/isEmpty.js",
+      "node_modules/fbjs/lib/nullthrows.js",
+      "node_modules/fbjs/lib/removeFromArray.js",
+      "node_modules/fbjs/lib/resolveImmediate.js",
+      "node_modules/fbjs/lib/someObject.js",
+      "node_modules/fbjs/lib/sprintf.js",
+      "node_modules/fbjs/lib/xhrSimpleDataSerializer.js",
+      "downstream/core/CSSCore.js",
+      "downstream/core/TouchEventUtils.js",
+      "downstream/core/camelize.js",
+      "downstream/core/createArrayFromMixed.js",
+      "downstream/core/createNodesFromMarkup.js",
+      "downstream/core/dom/containsNode.js",
+      "downstream/core/dom/focusNode.js",
+      "downstream/core/dom/getActiveElement.js",
+      "downstream/core/dom/getUnboundedScrollPosition.js",
+      "downstream/core/dom/isNode.js",
+      "downstream/core/dom/isTextNode.js",
+      "downstream/core/emptyFunction.js",
+      "downstream/core/emptyObject.js",
+      "downstream/core/getMarkupWrap.js",
+      "downstream/core/hyphenate.js",
+      "downstream/core/hyphenateStyleName.js",
+      "downstream/core/invariant.js",
+      "downstream/core/nativeRequestAnimationFrame.js",
+      "downstream/core/toArray.js",
+      "node_modules/jest-cli",
+      "node_modules/react/dist",
+      "/node_modules/fbjs/.*/__mocks__/"
+    ],
     "testFileExtensions": [
       "js"
     ],
     "unmockedModulePathPatterns": [
       "promise",
-      "source-map"
+      "source-map",
+      "fast-path",
+      "fbjs"
     ]
   },
   "main": "Libraries/react-native/react-native.js",
@@ -33,6 +101,7 @@
     "React",
     "React.podspec",
     "ReactAndroid",
+    "android",
     "Libraries",
     "packager",
     "cli.js",
@@ -42,74 +111,64 @@
     "PATENTS",
     "README.md",
     "jestSupport",
-    ".flowconfig"
+    ".flowconfig",
+    "npm-shrinkwrap.json"
   ],
   "scripts": {
     "test": "NODE_ENV=test jest",
+    "flow": "flow",
     "lint": "eslint Examples/ Libraries/",
     "start": "/usr/bin/env bash -c './packager/packager.sh \"$@\" || true' --"
   },
   "bin": {
     "react-native": "local-cli/wrong-react-native.js"
   },
+  "peerDependencies": {
+    "react": "^0.14.5"
+  },
   "dependencies": {
     "absolute-path": "^0.0.0",
     "art": "^0.10.0",
-    "babel-core": "^6.1.20",
-    "babel-plugin-external-helpers-2": "^6.1.4",
-    "babel-plugin-syntax-async-functions": "^6.0.14",
-    "babel-plugin-syntax-class-properties": "^6.0.14",
-    "babel-plugin-syntax-flow": "^6.0.14",
-    "babel-plugin-syntax-jsx": "^6.0.14",
-    "babel-plugin-syntax-trailing-function-commas": "^6.0.14",
-    "babel-plugin-transform-class-properties": "^6.0.14",
-    "babel-plugin-transform-es2015-arrow-functions": "^6.0.14",
-    "babel-plugin-transform-es2015-block-scoping": "^6.0.18",
-    "babel-plugin-transform-es2015-classes": "^6.1.2",
-    "babel-plugin-transform-es2015-computed-properties": "^6.0.14",
-    "babel-plugin-transform-es2015-constants": "^6.0.15",
-    "babel-plugin-transform-es2015-destructuring": "^6.0.18",
-    "babel-plugin-transform-es2015-modules-commonjs": "^6.1.3",
-    "babel-plugin-transform-es2015-parameters": "^6.0.18",
-    "babel-plugin-transform-es2015-shorthand-properties": "^6.0.14",
-    "babel-plugin-transform-es2015-spread": "^6.0.14",
-    "babel-plugin-transform-es2015-template-literals": "^6.0.14",
-    "babel-plugin-transform-flow-strip-types": "^6.0.14",
-    "babel-plugin-transform-object-assign": "^6.0.14",
-    "babel-plugin-transform-object-rest-spread": "^6.0.14",
-    "babel-plugin-transform-react-display-name": "^6.0.14",
-    "babel-plugin-transform-react-jsx": "^6.0.18",
-    "babel-plugin-transform-regenerator": "^6.0.18",
-    "babel-polyfill": "^6.0.16",
-    "babel-types": "^6.1.2",
-    "babylon": "^6.1.2",
+    "babel-core": "~6.4.5",
+    "babel-plugin-external-helpers": "~6.4.0",
+    "babel-polyfill": "~6.3.14",
+    "babel-preset-react-native": "^1.2.4",
+    "babel-register": "~6.4.3",
+    "babel-types": "~6.4.5",
+    "babylon": "~6.4.5",
+    "base64-js": "^0.0.8",
     "bser": "^1.0.2",
     "chalk": "^1.1.1",
     "connect": "^2.8.3",
     "debug": "^2.2.0",
     "event-target-shim": "^1.0.5",
-    "fbjs-haste": "^0.3.2",
+    "fast-path": "^1.1.0",
+    "fbjs": "^0.7.2",
     "fbjs-scripts": "^0.4.0",
     "graceful-fs": "^4.1.2",
     "image-size": "^0.3.5",
     "immutable": "^3.7.5",
     "joi": "^6.6.1",
+    "json-stable-stringify": "^1.0.1",
     "json5": "^0.4.0",
     "jstransform": "^11.0.3",
+    "mkdirp": "^0.5.1",
     "module-deps": "^3.9.1",
     "node-fetch": "^1.3.3",
+    "node-haste": "~2.4.0",
     "opn": "^3.0.2",
     "optimist": "^0.6.1",
     "progress": "^1.1.8",
-    "promise": "^7.0.4",
-    "react-haste": "^0.14.2",
+    "promise": "^7.1.1",
     "react-timer-mixin": "^0.13.2",
+    "react-transform-hmr": "^1.0.2",
     "rebound": "^0.0.13",
     "regenerator": "^0.8.36",
     "sane": "^1.2.0",
-    "semver": "^5.0.1",
+    "semver": "^5.0.3",
     "source-map": "^0.4.4",
     "stacktrace-parser": "^0.1.3",
+    "temp": "0.8.3",
     "uglify-js": "^2.4.24",
     "underscore": "^1.8.3",
     "wordwrap": "^1.0.0",
@@ -120,11 +179,11 @@
     "yeoman-generator": "^0.20.3"
   },
   "devDependencies": {
-    "jest-cli": "0.7.1",
-    "babel-eslint": "4.1.4",
-    "eslint": "1.3.1",
-    "eslint-plugin-react": "3.3.1",
-    "portfinder": "0.4.0",
-    "temp": "0.8.3"
+    "babel-eslint": "^5.0.0",
+    "eslint": "^2.2.0",
+    "eslint-plugin-react": "^4.1.0",
+    "flow-bin": "^0.21.0",
+    "jest-cli": "0.9.0-fb2",
+    "portfinder": "0.4.0"
   }
 }
diff --git a/packager/README.md b/packager/README.md
index 67bab1d9bb21..8010cc383650 100644
--- a/packager/README.md
+++ b/packager/README.md
@@ -12,7 +12,7 @@ to wait more than a few seconds after starting the packager.
 
 The main deviation from the node module system is the support for our
 proprietary module format known as `@providesModule`. However, we
-discourage people to use this module format because going forward, we
+discourage people from using this module format because going forward we
 want to completely separate our infrastructure from React Native and
 provide an experience most JavaScript developers are familiar with,
 namely the node module format. We want to even go further, and let you
@@ -112,6 +112,9 @@ middleware. Takes the following options:
   should be used as a persistent deamon to watch files and update
   itself
 * `assetRoots` array: Where should the packager look for assets
+* `getTransformOptionsModulePath` string: Path to module that exports a function
+  that acts as a middleware for generating options to pass to the transformer
+  based on the bundle and module being transformed.
 
 ### ReactPackager.buildPackageFromUrl(options, url)
 
@@ -144,5 +147,5 @@ is informed by React Native needs.
 ### Why didn't you use webpack?
 
 We love webpack, however, when we tried on our codebase it was slower
-than our developers would like it to be. You find can more discussion about
-the subject [here](https://github.com/facebook/react-native/issues/5)
+than our developers would like it to be. You can find more discussion about
+the subject [here](https://github.com/facebook/react-native/issues/5).
diff --git a/packager/babelRegisterOnly.js b/packager/babelRegisterOnly.js
index bf32861055ef..f862bb757e23 100644
--- a/packager/babelRegisterOnly.js
+++ b/packager/babelRegisterOnly.js
@@ -16,7 +16,7 @@ var path = require('path');
 var _only = [];
 
 function readBabelRC() {
-  var rcpath = path.join(__dirname, 'react-packager', '.babelrc');
+  var rcpath = path.join(__dirname, 'react-packager', 'rn-babelrc.json');
   var source = fs.readFileSync(rcpath).toString();
   return JSON.parse(source);
 }
@@ -25,5 +25,5 @@ module.exports = function(onlyList) {
   _only = _only.concat(onlyList);
   var config = readBabelRC();
   config.only = _only;
-  require('babel-core/register')(config);
+  require('babel-register')(config);
 };
diff --git a/packager/blacklist.js b/packager/blacklist.js
index bb64f75256f1..b6476dc45063 100644
--- a/packager/blacklist.js
+++ b/packager/blacklist.js
@@ -10,67 +10,23 @@
 
 var path = require('path');
 
-// Don't forget to everything listed here to `testConfig.json`
+// Don't forget to everything listed here to `package.json`
 // modulePathIgnorePatterns.
 var sharedBlacklist = [
-  'node_modules/react-haste/renderers/shared/event/eventPlugins/ResponderEventPlugin.js',
-  'node_modules/react-haste/React.js',
-  'node_modules/react-haste/renderers/dom/ReactDOM.js',
+  /node_modules[/\\]react[/\\]dist[/\\].*/,
+  'node_modules/react/lib/React.js',
+  'node_modules/react/lib/ReactDOM.js',
 
-  // For each of these fbjs files (especially the non-forks/stubs), we should
-  // consider deleting the conflicting copy and just using the fbjs version.
-  'node_modules/fbjs-haste/__forks__/Map.js',
-  'node_modules/fbjs-haste/__forks__/Promise.js',
-  'node_modules/fbjs-haste/__forks__/fetch.js',
-  'node_modules/fbjs-haste/core/Deferred.js',
-  'node_modules/fbjs-haste/core/PromiseMap.js',
-  'node_modules/fbjs-haste/core/areEqual.js',
-  'node_modules/fbjs-haste/core/flattenArray.js',
-  'node_modules/fbjs-haste/core/isEmpty.js',
-  'node_modules/fbjs-haste/core/removeFromArray.js',
-  'node_modules/fbjs-haste/core/resolveImmediate.js',
-  'node_modules/fbjs-haste/core/sprintf.js',
-  'node_modules/fbjs-haste/crypto/crc32.js',
-  'node_modules/fbjs-haste/fetch/fetchWithRetries.js',
-  'node_modules/fbjs-haste/functional/everyObject.js',
-  'node_modules/fbjs-haste/functional/filterObject.js',
-  'node_modules/fbjs-haste/functional/forEachObject.js',
-  'node_modules/fbjs-haste/functional/someObject.js',
-  'node_modules/fbjs-haste/request/xhrSimpleDataSerializer.js',
-  'node_modules/fbjs-haste/stubs/ErrorUtils.js',
-  'node_modules/fbjs-haste/stubs/URI.js',
-  'node_modules/fbjs-haste/useragent/UserAgent.js',
-  'node_modules/fbjs-haste/utils/nullthrows.js',
+  // fbjs forks:
+  'node_modules/fbjs/lib/Map.js',
+  'node_modules/fbjs/lib/isEmpty.js',
 
-  // Those conflicts with the ones in fbjs-haste/. We need to blacklist the
-  // internal version otherwise they won't work in open source.
-  'downstream/core/CSSCore.js',
-  'downstream/core/TouchEventUtils.js',
-  'downstream/core/camelize.js',
-  'downstream/core/createArrayFromMixed.js',
-  'downstream/core/createNodesFromMarkup.js',
-  'downstream/core/dom/containsNode.js',
-  'downstream/core/dom/focusNode.js',
-  'downstream/core/dom/getActiveElement.js',
-  'downstream/core/dom/getUnboundedScrollPosition.js',
-  'downstream/core/dom/isNode.js',
-  'downstream/core/dom/isTextNode.js',
-  'downstream/core/emptyFunction.js',
-  'downstream/core/emptyObject.js',
-  'downstream/core/getMarkupWrap.js',
-  'downstream/core/hyphenate.js',
-  'downstream/core/hyphenateStyleName.js',
   'downstream/core/invariant.js',
-  'downstream/core/nativeRequestAnimationFrame.js',
-  'downstream/core/toArray.js',
-  'downstream/functional/mapObject.js',
-  'downstream/key-mirror/keyMirror.js',
-  'downstream/key-mirror/keyOf.js',
-];
 
-// Raw unescaped patterns in case you need to use wildcards
-var sharedBlacklistWildcards = [
-  'website\/node_modules\/.*',
+  /website\/node_modules\/.*/,
+
+  // TODO(jkassens, #9876132): Remove this rule when it's no longer needed.
+  'Libraries/Relay/relay/tools/relayUnstableBatchedUpdates.js',
 ];
 
 var platformBlacklists = {
@@ -88,10 +44,16 @@ var platformBlacklists = {
   ],
 };
 
-function escapeRegExp(str) {
-  var escaped = str.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
-  // convert the '/' into an escaped local file separator
-  return escaped.replace(/\//g,'\\' + path.sep);
+function escapeRegExp(pattern) {
+  if (Object.prototype.toString.call(pattern) === '[object RegExp]') {
+    return pattern.source.replace(/\//g, path.sep);
+  } else if (typeof pattern === 'string') {
+    var escaped = pattern.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
+    // convert the '/' into an escaped local file separator
+    return escaped.replace(/\//g,'\\' + path.sep);
+  } else {
+    throw new Error('Unexpected packager blacklist pattern: ' + pattern);
+  }
 }
 
 function blacklist(platform, additionalBlacklist) {
@@ -99,7 +61,6 @@ function blacklist(platform, additionalBlacklist) {
     (additionalBlacklist || []).concat(sharedBlacklist)
       .concat(platformBlacklists[platform] || [])
       .map(escapeRegExp)
-      .concat(sharedBlacklistWildcards)
       .join('|') +
     ')$'
   );
diff --git a/packager/launchPackager.command b/packager/launchPackager.command
index 0d08a298a759..56683667df92 100755
--- a/packager/launchPackager.command
+++ b/packager/launchPackager.command
@@ -12,7 +12,7 @@ echo -en "\033]0;React Packager\a"
 clear
 
 THIS_DIR=$(dirname "$0")
-pushd $THIS_DIR
+pushd "$THIS_DIR"
 source packager.sh
 popd
 
diff --git a/packager/package.json b/packager/package.json
index c5d4832e65ec..cf2306f0ff05 100644
--- a/packager/package.json
+++ b/packager/package.json
@@ -1,5 +1,5 @@
 {
-  "version": "0.1.2",
+  "version": "0.1.4",
   "name": "react-native-packager",
   "description": "Build native apps with React!",
   "repository": {
diff --git a/packager/react-native-xcode.sh b/packager/react-native-xcode.sh
index dfa413b80ad8..9e6ffbe438c9 100755
--- a/packager/react-native-xcode.sh
+++ b/packager/react-native-xcode.sh
@@ -8,31 +8,34 @@
 
 # Bundle React Native app's code and image assets.
 # This script is supposed to be invoked as part of Xcode build process
-# and relies on envoronment variables (including PWD) set by Xcode
+# and relies on environment variables (including PWD) set by Xcode
 
 case "$CONFIGURATION" in
   Debug)
+    # Speed up build times by skipping the creation of the offline package for debug
+    # builds on the simulator since the packager is supposed to be running anyways.
+    if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then		
+      echo "Skipping bundling for Simulator platform"		
+      exit 0;		
+    fi
+    
     DEV=true
     ;;
-  Release)
-    DEV=false
-    ;;
   "")
     echo "$0 must be invoked by Xcode"
     exit 1
     ;;
   *)
-    echo "Unsupported value of \$CONFIGURATION=$CONFIGURATION"
-    exit 1
+    DEV=false
     ;;
 esac
 
+# Path to react-native folder inside node_modules
+REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+
 # Xcode project file for React Native apps is located in ios/ subfolder
 cd ..
 
-set -x
-DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
-
 # Define NVM_DIR and source the nvm.sh setup script
 [ -z "$NVM_DIR" ] && export NVM_DIR="$HOME/.nvm"
 
@@ -42,7 +45,30 @@ elif [[ -x "$(command -v brew)" && -s "$(brew --prefix nvm)/nvm.sh" ]]; then
   . "$(brew --prefix nvm)/nvm.sh"
 fi
 
-react-native bundle \
+# Set up the nodenv node version manager if present
+if [[ -x "$HOME/.nodenv/bin/nodenv" ]]; then
+  eval "$($HOME/.nodenv/bin/nodenv init -)"
+fi
+
+[ -z "$NODE_BINARY" ] && export NODE_BINARY="node"
+
+nodejs_not_found()
+{
+  echo "error: Can't find '$NODE_BINARY' binary to build React Native bundle" >&2
+  echo "If you have non-standard nodejs installation, select your project in Xcode," >&2
+  echo "find 'Build Phases' - 'Bundle React Native code and images'" >&2
+  echo "and change NODE_BINARY to absolute path to your node executable" >&2
+  echo "(you can find it by invoking 'which node' in the terminal)" >&2
+  exit 2
+}
+
+type $NODE_BINARY >/dev/null 2>&1 || nodejs_not_found
+
+# Print commands before executing them (useful for troubleshooting)
+set -x
+DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH
+
+$NODE_BINARY "$REACT_NATIVE_DIR/local-cli/cli.js" bundle \
   --entry-file index.ios.js \
   --platform ios \
   --dev $DEV \
diff --git a/packager/react-packager/.babelrc b/packager/react-packager/.babelrc
deleted file mode 100644
index c72259dac08c..000000000000
--- a/packager/react-packager/.babelrc
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "retainLines": true,
-  "compact": true,
-  "comments": false,
-  "plugins": [
-    "syntax-async-functions",
-    "syntax-class-properties",
-    "syntax-trailing-function-commas",
-    "transform-class-properties",
-    "transform-es2015-arrow-functions",
-    "transform-es2015-block-scoping",
-    "transform-es2015-classes",
-    "transform-es2015-computed-properties",
-    "transform-es2015-constants",
-    "transform-es2015-destructuring",
-    ["transform-es2015-modules-commonjs", {"strict": false, "allowTopLevelThis": true}],
-    "transform-es2015-parameters",
-    "transform-es2015-shorthand-properties",
-    "transform-es2015-spread",
-    "transform-es2015-template-literals",
-    "transform-flow-strip-types",
-    "transform-object-assign",
-    "transform-object-rest-spread",
-    "transform-react-display-name",
-    "transform-react-jsx",
-    "transform-regenerator"
-  ],
-  "sourceMaps": false
-}
diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js
index e0a29684d9bb..a392bdf5dd96 100644
--- a/packager/react-packager/index.js
+++ b/packager/react-packager/index.js
@@ -10,12 +10,14 @@
 
 require('../babelRegisterOnly')([/react-packager\/src/]);
 
+require('fast-path').replace();
 useGracefulFs();
 
 var debug = require('debug');
 var omit = require('underscore').omit;
 var Activity = require('./src/Activity');
 
+exports.createServer = createServer;
 exports.middleware = function(options) {
   var server = createServer(options);
   return server.processRequest.bind(server);
@@ -35,6 +37,15 @@ exports.buildBundle = function(options, bundleOptions) {
     });
 };
 
+exports.buildPrepackBundle = function(options, bundleOptions) {
+  var server = createNonPersistentServer(options);
+  return server.buildPrepackBundle(bundleOptions)
+    .then(function(p) {
+      server.end();
+      return p;
+    });
+};
+
 exports.buildPackageFromUrl =
 exports.buildBundleFromUrl = function(options, reqUrl) {
   var server = createNonPersistentServer(options);
diff --git a/packager/react-packager/rn-babelrc.json b/packager/react-packager/rn-babelrc.json
new file mode 100644
index 000000000000..bbc20ea981db
--- /dev/null
+++ b/packager/react-packager/rn-babelrc.json
@@ -0,0 +1,4 @@
+{
+  "presets": [ "react-native" ],
+  "plugins": []
+}
diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
index 7a544a270e03..15bfc0ce05fb 100644
--- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
+++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
@@ -1,8 +1,8 @@
 'use strict';
 
 jest
-  .dontMock('../../DependencyResolver/lib/getPlatformExtension')
-  .dontMock('../../DependencyResolver/lib/getAssetDataFromName')
+  .dontMock('node-haste/lib/lib/getPlatformExtension')
+  .dontMock('node-haste/node_modules/throat')
   .dontMock('../');
 
 jest
@@ -16,6 +16,11 @@ var crypto = require('crypto');
 var fs = require('fs');
 
 describe('AssetServer', () => {
+  beforeEach(() => {
+    const NodeHaste = require('node-haste');
+    NodeHaste.getAssetDataFromName = require.requireActual('node-haste/lib/lib/getAssetDataFromName');
+  });
+
   describe('assetServer.get', () => {
     pit('should work for the simple case', () => {
       const server = new AssetServer({
@@ -185,7 +190,7 @@ describe('AssetServer', () => {
     });
   });
 
-  describe('assetSerer.getAssetData', () => {
+  describe('assetServer.getAssetData', () => {
     pit('should get assetData', () => {
       const hash = {
         update: jest.genMockFn(),
diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js
index bb411ce6ff4f..71356acf38f6 100644
--- a/packager/react-packager/src/AssetServer/index.js
+++ b/packager/react-packager/src/AssetServer/index.js
@@ -13,7 +13,7 @@ const Promise = require('promise');
 const crypto = require('crypto');
 const declareOpts = require('../lib/declareOpts');
 const fs = require('fs');
-const getAssetDataFromName = require('../DependencyResolver/lib/getAssetDataFromName');
+const getAssetDataFromName = require('node-haste').getAssetDataFromName;
 const path = require('path');
 
 const stat = Promise.denodeify(fs.stat);
diff --git a/packager/react-packager/src/Bundler/Bundle.js b/packager/react-packager/src/Bundler/Bundle.js
index b6635be9eacf..9700af088631 100644
--- a/packager/react-packager/src/Bundler/Bundle.js
+++ b/packager/react-packager/src/Bundler/Bundle.js
@@ -10,105 +10,82 @@
 
 const _ = require('underscore');
 const base64VLQ = require('./base64-vlq');
+const BundleBase = require('./BundleBase');
 const UglifyJS = require('uglify-js');
 const ModuleTransport = require('../lib/ModuleTransport');
 const Activity = require('../Activity');
+const crypto = require('crypto');
 
-const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
+const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL=';
 
-class Bundle {
+const minifyCode = code =>
+  UglifyJS.minify(code, {fromString: true, ascii_only: true}).code;
+const getCode = x => x.code;
+const getMinifiedCode = x => minifyCode(x.code);
+const getNameAndCode = ({name, code}) => ({name, code});
+const getNameAndMinifiedCode =
+  ({name, code}) => ({name, code: minifyCode(code)});
+
+class Bundle extends BundleBase {
   constructor(sourceMapUrl) {
-    this._finalized = false;
-    this._modules = [];
-    this._assets = [];
+    super();
     this._sourceMap = false;
     this._sourceMapUrl = sourceMapUrl;
     this._shouldCombineSourceMaps = false;
+    this._numPrependedModules = 0;
+    this._numRequireCalls = 0;
   }
 
-  setMainModuleId(moduleId) {
-    this._mainModuleId = moduleId;
-  }
-
-  addModule(module) {
-    if (!(module instanceof ModuleTransport)) {
-      throw new Error('Expeceted a ModuleTransport object');
-    }
-
-    // If we get a map from the transformer we'll switch to a mode
-    // were we're combining the source maps as opposed to
-    if (!this._shouldCombineSourceMaps && module.map != null) {
-      this._shouldCombineSourceMaps = true;
-    }
+  addModule(resolver, response, module, transformed) {
+    return resolver.wrapModule(
+      response,
+      module,
+      transformed.code
+    ).then(({code, name}) => {
+      const moduleTransport = new ModuleTransport({
+        code,
+        name,
+        map: transformed.map,
+        sourceCode: transformed.sourceCode,
+        sourcePath: transformed.sourcePath,
+        virtual: transformed.virtual,
+      });
 
-    this._modules.push(module);
-  }
+      // If we get a map from the transformer we'll switch to a mode
+      // were we're combining the source maps as opposed to
+      if (!this._shouldCombineSourceMaps && moduleTransport.map != null) {
+        this._shouldCombineSourceMaps = true;
+      }
 
-  getModules() {
-    return this._modules;
+      super.addModule(moduleTransport);
+    });
   }
 
-  addAsset(asset) {
-    this._assets.push(asset);
+  setNumPrependedModules(n) {
+    this._numPrependedModules = n;
   }
 
   finalize(options) {
     options = options || {};
     if (options.runMainModule) {
       options.runBeforeMainModule.forEach(this._addRequireCall, this);
-      this._addRequireCall(this._mainModuleId);
+      this._addRequireCall(super.getMainModuleId());
     }
 
-    Object.freeze(this._modules);
-    Object.seal(this._modules);
-    Object.freeze(this._assets);
-    Object.seal(this._assets);
-    this._finalized = true;
+    super.finalize();
   }
 
   _addRequireCall(moduleId) {
     const code = ';require("' + moduleId + '");';
     const name = 'require-' + moduleId;
-    this.addModule(new ModuleTransport({
+    super.addModule(new ModuleTransport({
       name,
       code,
       virtual: true,
       sourceCode: code,
       sourcePath: name + '.js',
     }));
-  }
-
-  _assertFinalized() {
-    if (!this._finalized) {
-      throw new Error('Bundle needs to be finalized before getting any source');
-    }
-  }
-
-  _getSource(dev) {
-    if (this._source) {
-      return this._source;
-    }
-
-    this._source = _.pluck(this._modules, 'code').join('\n');
-
-    if (dev) {
-      return this._source;
-    }
-
-    const wpoActivity = Activity.startEvent('Whole Program Optimisations');
-    const result = require('babel-core').transform(this._source, {
-      retainLines: true,
-      compact: true,
-      plugins: require('../transforms/whole-program-optimisations'),
-      inputSourceMap: this.getSourceMap(),
-    });
-
-    this._source = result.code;
-    this._sourceMap = result.map;
-
-    Activity.endEvent(wpoActivity);
-
-    return this._source;
+    this._numRequireCalls += 1;
   }
 
   _getInlineSourceMap(dev) {
@@ -122,7 +99,7 @@ class Bundle {
   }
 
   getSource(options) {
-    this._assertFinalized();
+    super.assertFinalized();
 
     options = options || {};
 
@@ -130,7 +107,7 @@ class Bundle {
       return this.getMinifiedSourceAndMap(options.dev).code;
     }
 
-    let source = this._getSource(options.dev);
+    let source = super.getSource();
 
     if (options.inlineSourceMap) {
       source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev);
@@ -141,20 +118,56 @@ class Bundle {
     return source;
   }
 
+  getUnbundle({minify}) {
+    const allModules = super.getModules().slice();
+    const prependedModules = this._numPrependedModules;
+    const requireCalls = this._numRequireCalls;
+
+    const modules =
+      allModules
+        .splice(prependedModules, allModules.length - requireCalls - prependedModules);
+    const startupCode =
+      allModules
+        .map(minify ? getMinifiedCode : getCode)
+        .join('\n');
+
+    return {
+      startupCode,
+      modules:
+        modules.map(minify ? getNameAndMinifiedCode : getNameAndCode)
+    };
+  }
+
   getMinifiedSourceAndMap(dev) {
-    this._assertFinalized();
+    super.assertFinalized();
 
     if (this._minifiedSourceAndMap) {
       return this._minifiedSourceAndMap;
     }
 
-    const source = this._getSource(dev);
+    let source = this.getSource();
+    let map = this.getSourceMap();
+
+    if (!dev) {
+      const wpoActivity = Activity.startEvent('Whole Program Optimisations');
+      const wpoResult = require('babel-core').transform(source, {
+        retainLines: true,
+        compact: true,
+        plugins: require('../transforms/whole-program-optimisations'),
+        inputSourceMap: map,
+      });
+      Activity.endEvent(wpoActivity);
+
+      source = wpoResult.code;
+      map = wpoResult.map;
+    }
+
     try {
       const minifyActivity = Activity.startEvent('minify');
       this._minifiedSourceAndMap = UglifyJS.minify(source, {
         fromString: true,
-        outSourceMap: 'bundle.js',
-        inSourceMap: this.getSourceMap(),
+        outSourceMap: this._sourceMapUrl,
+        inSourceMap: map,
         output: {ascii_only: true},
       });
       Activity.endEvent(minifyActivity);
@@ -193,12 +206,12 @@ class Bundle {
   _getCombinedSourceMaps(options) {
     const result = {
       version: 3,
-      file: 'bundle.js',
+      file: this._getSourceMapFile(),
       sections: [],
     };
 
     let line = 0;
-    this._modules.forEach(function(module) {
+    super.getModules().forEach(function(module) {
       let map = module.map;
       if (module.virtual) {
         map = generateSourceMapForVirtualModule(module);
@@ -219,7 +232,7 @@ class Bundle {
   }
 
   getSourceMap(options) {
-    this._assertFinalized();
+    super.assertFinalized();
 
     options = options || {};
 
@@ -233,23 +246,30 @@ class Bundle {
 
     const mappings = this._getMappings();
     const map = {
-      file: 'bundle.js',
-      sources: _.pluck(this._modules, 'sourcePath'),
+      file: this._getSourceMapFile(),
+      sources: _.pluck(super.getModules(), 'sourcePath'),
       version: 3,
       names: [],
       mappings: mappings,
       sourcesContent: options.excludeSource
-    ? [] : _.pluck(this._modules, 'sourceCode')
+    ? [] : _.pluck(super.getModules(), 'sourceCode')
     };
     return map;
   }
 
-  getAssets() {
-    return this._assets;
+  getEtag() {
+    var eTag = crypto.createHash('md5').update(this.getSource()).digest('hex');
+    return eTag;
+  }
+
+  _getSourceMapFile() {
+    return this._sourceMapUrl
+      ? this._sourceMapUrl.replace('.map', '.bundle')
+      : 'bundle.js';
   }
 
   _getMappings() {
-    const modules = this._modules;
+    const modules = super.getModules();
 
     // The first line mapping in our package is basically the base64vlq code for
     // zeros (A).
@@ -296,7 +316,7 @@ class Bundle {
   }
 
   getJSModulePaths() {
-    return this._modules.filter(function(module) {
+    return super.getModules().filter(function(module) {
       // Filter out non-js files. Like images etc.
       return !module.virtual;
     }).map(function(module) {
@@ -306,7 +326,7 @@ class Bundle {
 
   getDebugInfo() {
     return [
-      '<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',
+      '<div><h3>Main Module:</h3> ' + super.getMainModuleId() + '</div>',
       '<style>',
       'pre.collapsed {',
       '  height: 10px;',
@@ -318,7 +338,7 @@ class Bundle {
       '}',
       '</style>',
       '<h3> Module paths and transformed code: </h3>',
-      this._modules.map(function(m) {
+      super.getModules().map(function(m) {
         return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
                '<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
                _.escape(m.code) + '</pre></code></div>';
@@ -327,30 +347,24 @@ class Bundle {
   }
 
   toJSON() {
-    if (!this._finalized) {
-      throw new Error('Cannot serialize bundle unless finalized');
-    }
+    this.assertFinalized('Cannot serialize bundle unless finalized');
 
     return {
-      modules: this._modules,
-      assets: this._assets,
+      ...super.toJSON(),
       sourceMapUrl: this._sourceMapUrl,
-      mainModuleId: this._mainModuleId,
+      numPrependedModules: this._numPrependedModules,
+      numRequireCalls: this._numRequireCalls,
     };
   }
 
   static fromJSON(json) {
     const bundle = new Bundle(json.sourceMapUrl);
-    bundle._mainModuleId = json.mainModuleId;
-    bundle._assets = json.assets;
-    bundle._modules = json.modules;
+
     bundle._sourceMapUrl = json.sourceMapUrl;
+    bundle._numPrependedModules = json.numPrependedModules;
+    bundle._numRequireCalls = json.numRequireCalls;
 
-    Object.freeze(bundle._modules);
-    Object.seal(bundle._modules);
-    Object.freeze(bundle._assets);
-    Object.seal(bundle._assets);
-    bundle._finalized = true;
+    BundleBase.fromJSON(bundle, json);
 
     return bundle;
   }
diff --git a/packager/react-packager/src/Bundler/BundleBase.js b/packager/react-packager/src/Bundler/BundleBase.js
new file mode 100644
index 000000000000..d3794a433c2f
--- /dev/null
+++ b/packager/react-packager/src/Bundler/BundleBase.js
@@ -0,0 +1,100 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const _ = require('underscore');
+const ModuleTransport = require('../lib/ModuleTransport');
+
+class BundleBase {
+  constructor() {
+    this._finalized = false;
+    this._modules = [];
+    this._assets = [];
+    this._mainModuleId = undefined;
+  }
+
+  isEmpty() {
+    return this._modules.length === 0 && this._assets.length === 0;
+  }
+
+  getMainModuleId() {
+    return this._mainModuleId;
+  }
+
+  setMainModuleId(moduleId) {
+    this._mainModuleId = moduleId;
+  }
+
+  addModule(module) {
+    if (!module instanceof ModuleTransport) {
+      throw new Error('Expeceted a ModuleTransport object');
+    }
+
+    this._modules.push(module);
+  }
+
+  getModules() {
+    return this._modules;
+  }
+
+  getAssets() {
+    return this._assets;
+  }
+
+  addAsset(asset) {
+    this._assets.push(asset);
+  }
+
+  finalize(options) {
+    Object.freeze(this._modules);
+    Object.seal(this._modules);
+    Object.freeze(this._assets);
+    Object.seal(this._assets);
+    this._finalized = true;
+  }
+
+  getSource(options) {
+    this.assertFinalized();
+
+    if (this._source) {
+      return this._source;
+    }
+
+    this._source = _.pluck(this._modules, 'code').join('\n');
+    return this._source;
+  }
+
+  assertFinalized(message) {
+    if (!this._finalized) {
+      throw new Error(message || 'Bundle needs to be finalized before getting any source');
+    }
+  }
+
+  toJSON() {
+    return {
+      modules: this._modules,
+      assets: this._assets,
+      mainModuleId: this.getMainModuleId(),
+    };
+  }
+
+  static fromJSON(bundle, json) {
+    bundle._assets = json.assets;
+    bundle._modules = json.modules;
+    bundle.setMainModuleId(json.mainModuleId);
+
+    Object.freeze(bundle._modules);
+    Object.seal(bundle._modules);
+    Object.freeze(bundle._assets);
+    Object.seal(bundle._assets);
+    bundle._finalized = true;
+  }
+}
+
+module.exports = BundleBase;
diff --git a/packager/react-packager/src/Bundler/HMRBundle.js b/packager/react-packager/src/Bundler/HMRBundle.js
new file mode 100644
index 000000000000..72850778846c
--- /dev/null
+++ b/packager/react-packager/src/Bundler/HMRBundle.js
@@ -0,0 +1,62 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const _ = require('underscore');
+const BundleBase = require('./BundleBase');
+const ModuleTransport = require('../lib/ModuleTransport');
+
+class HMRBundle extends BundleBase {
+  constructor({sourceURLFn, sourceMappingURLFn}) {
+    super();
+    this._sourceURLFn = sourceURLFn
+    this._sourceMappingURLFn = sourceMappingURLFn;
+    this._sourceURLs = [];
+    this._sourceMappingURLs = [];
+  }
+
+  addModule(resolver, response, module, transformed) {
+    return resolver.resolveRequires(response,
+      module,
+      transformed.code,
+    ).then(({name, code}) => {
+      const moduleTransport = new ModuleTransport({
+        code,
+        name,
+        map: transformed.map,
+        sourceCode: transformed.sourceCode,
+        sourcePath: transformed.sourcePath,
+        virtual: transformed.virtual,
+      });
+
+      super.addModule(moduleTransport);
+      this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath));
+      this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath));
+    });
+  }
+
+  getModulesNamesAndCode() {
+    return this._modules.map(module => {
+      return {
+        name: JSON.stringify(module.name),
+        code: module.code,
+      };
+    });
+  }
+
+  getSourceURLs() {
+    return this._sourceURLs;
+  }
+
+  getSourceMappingURLs() {
+    return this._sourceMappingURLs;
+  }
+}
+
+module.exports = HMRBundle;
diff --git a/packager/react-packager/src/Bundler/PrepackBundle.js b/packager/react-packager/src/Bundler/PrepackBundle.js
new file mode 100644
index 000000000000..123d3297a5c0
--- /dev/null
+++ b/packager/react-packager/src/Bundler/PrepackBundle.js
@@ -0,0 +1,149 @@
+/**
+ * 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.
+ */
+'use strict';
+
+const fs = require('fs');
+
+class PrepackBundle {
+  constructor(sourceMapUrl) {
+    this._finalized = false;
+    this._moduleIds = Object.create(null);
+    this._modules = Object.create(null);
+    this._eagerModules = [];
+    this._mainModule = null;
+    this._assets = [];
+    this._sourceMapUrl = sourceMapUrl;
+  }
+
+  addModule(id, module, deps, isPolyfill) {
+    this._modules[module.sourcePath] = { module, deps };
+    this._moduleIds[id] = module.sourcePath;
+    if (isPolyfill) {
+      this._eagerModules.push(id);
+    }
+  }
+
+  addAsset(asset) {
+    this._assets.push(asset);
+  }
+
+  // Synchronously load a file path.
+  _loadFilename(path) {
+    const module = this._modules[path];
+    if (!module) {
+      throw new Error('Could not find file "' + path + '" in preloaded files.');
+    }
+    return module.module.code;
+  }
+
+  // Synchronously resolve a relative require from a parent module.
+  _resolveFilename(parentPath, relativePath) {
+    if (!parentPath) {
+      const resolvedPath = this._moduleIds[relativePath];
+      if (!resolvedPath) {
+        throw new Error('Could not resolve "' + relativePath + '".');
+      }
+      return resolvedPath;
+    }
+    const deps = this._modules[parentPath].deps;
+    const resolvedPath = deps[relativePath];
+    if (!resolvedPath) {
+      throw new Error(
+        'Could not resolve "' + relativePath + '" from "' + parentPath + '".'
+      );
+    }
+    return resolvedPath;
+  }
+
+  build(options) {
+    var prepack = require('prepack');
+
+    var batchedBridgeConfig = (options && options.batchedBridgeConfig) || null;
+    if (typeof batchedBridgeConfig === 'string') {
+      batchedBridgeConfig = JSON.parse(
+        fs.readFileSync(batchedBridgeConfig, 'utf-8')
+      );
+    }
+
+    var options = {
+      batchedBridgeConfig: batchedBridgeConfig,
+      environment: 'react-native',
+      resolveFilename: this._resolveFilename.bind(this),
+      loadFilename: this._loadFilename.bind(this),
+      eagerModules: this._eagerModules
+    };
+
+    return prepack.compileModule(this._mainModule, options);
+  }
+
+  finalize(options) {
+    options = options || {};
+    if (options.runMainModule) {
+      options.runBeforeMainModule.forEach(this._addRequireCall, this);
+      this._mainModule = options.mainModuleId;
+    }
+
+    Object.freeze(this._moduleIds);
+    Object.freeze(this._modules);
+    Object.freeze(this._assets);
+    Object.freeze(this._eagerModules);
+    this._finalized = true;
+  }
+
+  _addRequireCall(moduleId) {
+    this._eagerModules.push(moduleId);
+  }
+
+  _assertFinalized() {
+    if (!this._finalized) {
+      throw new Error('Bundle needs to be finalized before getting any source');
+    }
+  }
+
+  getAssets() {
+    return this._assets;
+  }
+
+  toJSON() {
+    if (!this._finalized) {
+      throw new Error('Cannot serialize bundle unless finalized');
+    }
+
+    return {
+      modules: this._modules,
+      moduleIds: this._moduleIds,
+      assets: this._assets,
+      sourceMapUrl: this._sourceMapUrl,
+      mainModule: this._mainModule,
+      eagerModules: this._eagerModules,
+    };
+  }
+
+  static fromJSON(json) {
+    const bundle = new PrepackBundle(json.sourceMapUrl);
+    bundle._assets = json.assets;
+    bundle._moduleIds = json.moduleIds;
+    bundle._modules = json.modules;
+    bundle._sourceMapUrl = json.sourceMapUrl;
+
+    bundle._eagerModules = json.eagerModules;
+    bundle._mainModule = json.mainModule;
+
+    Object.freeze(bundle._moduleIds);
+    Object.freeze(bundle._modules);
+    Object.freeze(bundle._assets);
+    Object.freeze(bundle._eagerModules);
+
+    bundle._finalized = true;
+
+    return bundle;
+  }
+}
+
+module.exports = PrepackBundle;
diff --git a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
index 46b57741a125..23fe5762dda7 100644
--- a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
+++ b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
@@ -10,92 +10,107 @@
 
 jest.autoMockOff();
 
-var SourceMapGenerator = require('source-map').SourceMapGenerator;
+const Bundle = require('../Bundle');
+const ModuleTransport = require('../../lib/ModuleTransport');
+const Promise = require('Promise');
+const SourceMapGenerator = require('source-map').SourceMapGenerator;
+const UglifyJS = require('uglify-js');
+const crypto = require('crypto');
 
-var Bundle = require('../Bundle');
-var ModuleTransport = require('../../lib/ModuleTransport');
-var UglifyJS = require('uglify-js');
-
-describe('Bundle', function() {
+describe('Bundle', () => {
   var bundle;
 
-  beforeEach(function() {
+  beforeEach(() => {
     bundle = new Bundle('test_url');
-    bundle.getSourceMap = jest.genMockFn().mockImpl(function() {
+    bundle.getSourceMap = jest.genMockFn().mockImpl(() => {
       return 'test-source-map';
     });
   });
 
-  describe('source bundle', function() {
-    it('should create a bundle and get the source', function() {
-      bundle.addModule(new ModuleTransport({
-        code: 'transformed foo;',
-        sourceCode: 'source foo',
-        sourcePath: 'foo path',
-      }));
-      bundle.addModule(new ModuleTransport({
-        code: 'transformed bar;',
-        sourceCode: 'source bar',
-        sourcePath: 'bar path',
-      }));
-
-      bundle.finalize({});
-      expect(bundle.getSource({dev: true})).toBe([
-        'transformed foo;',
-        'transformed bar;',
-        '\/\/@ sourceMappingURL=test_url'
-      ].join('\n'));
+  describe('source bundle', () => {
+    pit('should create a bundle and get the source', () => {
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle,
+          code: 'transformed foo;',
+          sourceCode: 'source foo',
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle,
+          code: 'transformed bar;',
+          sourceCode: 'source bar',
+          sourcePath: 'bar path',
+        });
+      }).then(() => {
+        bundle.finalize({});
+        expect(bundle.getSource({dev: true})).toBe([
+          'transformed foo;',
+          'transformed bar;',
+          '\/\/# sourceMappingURL=test_url'
+        ].join('\n'));
+      });
     });
 
-    it('should be ok to leave out the source map url', function() {
-      var p = new Bundle();
-      p.addModule(new ModuleTransport({
-        code: 'transformed foo;',
-        sourceCode: 'source foo',
-        sourcePath: 'foo path',
-      }));
-      p.addModule(new ModuleTransport({
-        code: 'transformed bar;',
-        sourceCode: 'source bar',
-        sourcePath: 'bar path',
-      }));
-
-      p.finalize({});
-      expect(p.getSource({dev: true})).toBe([
-        'transformed foo;',
-        'transformed bar;',
-      ].join('\n'));
+    pit('should be ok to leave out the source map url', () => {
+      const otherBundle = new Bundle();
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'transformed foo;',
+          sourceCode: 'source foo',
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'transformed bar;',
+          sourceCode: 'source bar',
+          sourcePath: 'bar path',
+        });
+      }).then(() => {
+        otherBundle.finalize({});
+        expect(otherBundle.getSource({dev: true})).toBe([
+          'transformed foo;',
+          'transformed bar;',
+        ].join('\n'));
+      });
     });
 
-    it('should create a bundle and add run module code', function() {
-      bundle.addModule(new ModuleTransport({
-        code: 'transformed foo;',
-        sourceCode: 'source foo',
-        sourcePath: 'foo path'
-      }));
-
-      bundle.addModule(new ModuleTransport({
-        code: 'transformed bar;',
-        sourceCode: 'source bar',
-        sourcePath: 'bar path'
-      }));
-
-      bundle.setMainModuleId('foo');
-      bundle.finalize({
-        runBeforeMainModule: ['bar'],
-        runMainModule: true,
+    pit('should create a bundle and add run module code', () => {
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle,
+          code: 'transformed foo;',
+          sourceCode: 'source foo',
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle,
+          code: 'transformed bar;',
+          sourceCode: 'source bar',
+          sourcePath: 'bar path',
+        });
+      }).then(() => {
+        bundle.setMainModuleId('foo');
+        bundle.finalize({
+          runBeforeMainModule: ['bar'],
+          runMainModule: true,
+        });
+        expect(bundle.getSource({dev: true})).toBe([
+          'transformed foo;',
+          'transformed bar;',
+          ';require("bar");',
+          ';require("foo");',
+          '\/\/# sourceMappingURL=test_url',
+        ].join('\n'));
       });
-      expect(bundle.getSource({dev: true})).toBe([
-        'transformed foo;',
-        'transformed bar;',
-        ';require("bar");',
-        ';require("foo");',
-        '\/\/@ sourceMappingURL=test_url',
-      ].join('\n'));
     });
 
-    it('should get minified source', function() {
-      var minified = {
+    pit('should get minified source', () => {
+      const minified = {
         code: 'minified',
         map: 'map',
       };
@@ -104,141 +119,156 @@ describe('Bundle', function() {
         return minified;
       };
 
-      bundle.addModule(new ModuleTransport({
-        code: 'transformed foo;',
-        sourceCode: 'source foo',
-        sourcePath: 'foo path'
-      }));
-      bundle.finalize();
-      expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified);
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle,
+          code: 'transformed foo;',
+          sourceCode: 'source foo',
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        bundle.finalize();
+        expect(bundle.getMinifiedSourceAndMap({dev: true})).toBe(minified);
+      });
     });
   });
 
-  describe('sourcemap bundle', function() {
-    it('should create sourcemap', function() {
-      var p = new Bundle('test_url');
-      p.addModule(new ModuleTransport({
-        code: [
-          'transformed foo',
-          'transformed foo',
-          'transformed foo',
-        ].join('\n'),
-        sourceCode: [
-          'source foo',
-          'source foo',
-          'source foo',
-        ].join('\n'),
-        sourcePath: 'foo path',
-      }));
-      p.addModule(new ModuleTransport({
-        code: [
-          'transformed bar',
-          'transformed bar',
-          'transformed bar',
-        ].join('\n'),
-        sourceCode: [
-          'source bar',
-          'source bar',
-          'source bar',
-        ].join('\n'),
-        sourcePath: 'bar path',
-      }));
+  describe('sourcemap bundle', () => {
+    pit('should create sourcemap', () => {
+      const otherBundle = new Bundle('test_url');
 
-      p.setMainModuleId('foo');
-      p.finalize({
-        runBeforeMainModule: [],
-        runMainModule: true,
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: [
+            'transformed foo',
+            'transformed foo',
+            'transformed foo',
+          ].join('\n'),
+          sourceCode: [
+            'source foo',
+            'source foo',
+            'source foo',
+          ].join('\n'),
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: [
+            'transformed bar',
+            'transformed bar',
+            'transformed bar',
+          ].join('\n'),
+          sourceCode: [
+            'source bar',
+            'source bar',
+            'source bar',
+          ].join('\n'),
+          sourcePath: 'bar path',
+        });
+      }).then(() => {
+        otherBundle.setMainModuleId('foo');
+        otherBundle.finalize({
+          runBeforeMainModule: [],
+          runMainModule: true,
+        });
+        const sourceMap = otherBundle.getSourceMap({dev: true});
+        expect(sourceMap).toEqual(genSourceMap(otherBundle.getModules()));
       });
-      var s = p.getSourceMap({dev: true});
-      expect(s).toEqual(genSourceMap(p.getModules()));
     });
 
-    it('should combine sourcemaps', function() {
-      var p = new Bundle('test_url');
-
-      p.addModule(new ModuleTransport({
-        code: 'transformed foo;\n',
-        map: {name: 'sourcemap foo'},
-        sourceCode: 'source foo',
-        sourcePath: 'foo path'
-      }));
-
-      p.addModule(new ModuleTransport({
-        code: 'transformed foo;\n',
-        map: {name: 'sourcemap bar'},
-        sourceCode: 'source foo',
-        sourcePath: 'foo path'
-      }));
-
-      p.addModule(new ModuleTransport({
-        code: 'image module;\nimage module;',
-        virtual: true,
-        sourceCode: 'image module;\nimage module;',
-        sourcePath: 'image.png',
-      }));
+    pit('should combine sourcemaps', () => {
+      const otherBundle = new Bundle('test_url');
 
-      p.setMainModuleId('foo');
-      p.finalize({
-        runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
-        runMainModule: true,
-      });
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'transformed foo;\n',
+          sourceCode: 'source foo',
+          map: {name: 'sourcemap foo'},
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'transformed bar;\n',
+          sourceCode: 'source bar',
+          map: {name: 'sourcemap bar'},
+          sourcePath: 'bar path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'image module;\nimage module;',
+          virtual: true,
+          sourceCode: 'image module;\nimage module;',
+          sourcePath: 'image.png',
+        });
+      }).then(() => {
+        otherBundle.setMainModuleId('foo');
+        otherBundle.finalize({
+          runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
+          runMainModule: true,
+        });
 
-      var s = p.getSourceMap({dev: true});
-      expect(s).toEqual({
-        file: 'bundle.js',
-        version: 3,
-        sections: [
-          { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } },
-          { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } },
-          {
-            offset: {
-              column: 0,
-              line: 4
+        const sourceMap = otherBundle.getSourceMap({dev: true});
+        expect(sourceMap).toEqual({
+          file: 'test_url',
+          version: 3,
+          sections: [
+            { offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } },
+            { offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } },
+            {
+              offset: {
+                column: 0,
+                line: 4
+              },
+              map: {
+                file: 'image.png',
+                mappings: 'AAAA;AACA;',
+                names: [],
+                sources: [ 'image.png' ],
+                sourcesContent: ['image module;\nimage module;'],
+                version: 3,
+              }
             },
-            map: {
-              file: 'image.png',
-              mappings: 'AAAA;AACA;',
-              names: [],
-              sources: [ 'image.png' ],
-              sourcesContent: ['image module;\nimage module;'],
-              version: 3,
-            }
-          },
-          {
-            offset: {
-              column: 0,
-              line: 6
+            {
+              offset: {
+                column: 0,
+                line: 6
+              },
+              map: {
+                file: 'require-InitializeJavaScriptAppEngine.js',
+                mappings: 'AAAA;',
+                names: [],
+                sources: [ 'require-InitializeJavaScriptAppEngine.js' ],
+                sourcesContent: [';require("InitializeJavaScriptAppEngine");'],
+                version: 3,
+              }
             },
-            map: {
-              file: 'require-InitializeJavaScriptAppEngine.js',
-              mappings: 'AAAA;',
-              names: [],
-              sources: [ 'require-InitializeJavaScriptAppEngine.js' ],
-              sourcesContent: [';require("InitializeJavaScriptAppEngine");'],
-              version: 3,
-            }
-          },
-          {
-            offset: {
-              column: 0,
-              line: 7
+            {
+              offset: {
+                column: 0,
+                line: 7
+              },
+              map: {
+                file: 'require-foo.js',
+                mappings: 'AAAA;',
+                names: [],
+                sources: [ 'require-foo.js' ],
+                sourcesContent: [';require("foo");'],
+                version: 3,
+              }
             },
-            map: {
-              file: 'require-foo.js',
-              mappings: 'AAAA;',
-              names: [],
-              sources: [ 'require-foo.js' ],
-              sourcesContent: [';require("foo");'],
-              version: 3,
-            }
-          },
-        ],
+          ],
+        });
       });
     });
   });
 
-  describe('getAssets()', function() {
-    it('should save and return asset objects', function() {
+  describe('getAssets()', () => {
+    it('should save and return asset objects', () => {
       var p = new Bundle('test_url');
       var asset1 = {};
       var asset2 = {};
@@ -249,29 +279,61 @@ describe('Bundle', function() {
     });
   });
 
-  describe('getJSModulePaths()', function() {
-    it('should return module paths', function() {
-      var p = new Bundle('test_url');
-      p.addModule(new ModuleTransport({
-        code: 'transformed foo;\n',
-        sourceCode: 'source foo',
-        sourcePath: 'foo path'
-      }));
-      p.addModule(new ModuleTransport({
-        code: 'image module;\nimage module;',
-        virtual: true,
-        sourceCode: 'image module;\nimage module;',
-        sourcePath: 'image.png',
-      }));
+  describe('getJSModulePaths()', () => {
+    pit('should return module paths', () => {
+      var otherBundle = new Bundle('test_url');
+      return Promise.resolve().then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'transformed foo;\n',
+          sourceCode: 'source foo',
+          sourcePath: 'foo path',
+        });
+      }).then(() => {
+        return addModule({
+          bundle: otherBundle,
+          code: 'image module;\nimage module;',
+          virtual: true,
+          sourceCode: 'image module;\nimage module;',
+          sourcePath: 'image.png',
+        });
+      }).then(() => {
+        expect(otherBundle.getJSModulePaths()).toEqual(['foo path']);
+      });
+    });
+  });
 
-      expect(p.getJSModulePaths()).toEqual(['foo path']);
+  describe('getEtag()', function() {
+    it('should return an etag', function() {
+      var bundle = new Bundle('test_url');
+      bundle.finalize({});
+      var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex');
+      expect(bundle.getEtag()).toEqual(eTag);
+    });
+  });
+
+  describe('main module id:', function() {
+    it('can save a main module ID', function() {
+      const id = 'arbitrary module ID';
+      bundle.setMainModuleId(id);
+      expect(bundle.getMainModuleId()).toEqual(id);
+    });
+
+    it('can serialize and deserialize the module ID', function() {
+      const id = 'arbitrary module ID';
+      bundle.setMainModuleId(id);
+      bundle.finalize({});
+
+      const deserialized = Bundle.fromJSON(bundle.toJSON());
+
+      expect(deserialized.getMainModuleId()).toEqual(id);
     });
   });
 });
 
 
 function genSourceMap(modules) {
-  var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
+  var sourceMapGen = new SourceMapGenerator({file: 'test_url', version: 3});
   var bundleLineNo = 0;
   for (var i = 0; i < modules.length; i++) {
     var module = modules[i];
@@ -302,3 +364,20 @@ function genSourceMap(modules) {
   }
   return sourceMapGen.toJSON();
 }
+
+function resolverFor(code) {
+  return {
+    wrapModule: (response, module, sourceCode) => Promise.resolve(
+      {name: 'name', code}
+    ),
+  };
+}
+
+function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) {
+  return bundle.addModule(
+    resolverFor(code),
+    null,
+    null,
+    {sourceCode, sourcePath, map, virtual}
+  );
+}
diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
index ac8494bec9c3..1c72d0a426b5 100644
--- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
+++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
@@ -10,6 +10,7 @@
 
 jest
   .setMock('worker-farm', () => () => undefined)
+  .dontMock('node-haste/node_modules/throat')
   .dontMock('underscore')
   .dontMock('../../lib/ModuleTransport')
   .setMock('uglify-js')
@@ -46,6 +47,7 @@ describe('Bundler', function() {
   }
 
   var getDependencies;
+  var getModuleSystemDependencies;
   var wrapModule;
   var bundler;
   var assetServer;
@@ -53,10 +55,12 @@ describe('Bundler', function() {
 
   beforeEach(function() {
     getDependencies = jest.genMockFn();
+    getModuleSystemDependencies = jest.genMockFn();
     wrapModule = jest.genMockFn();
     Resolver.mockImpl(function() {
       return {
         getDependencies: getDependencies,
+        getModuleSystemDependencies: getModuleSystemDependencies,
         wrapModule: wrapModule,
       };
     });
@@ -112,6 +116,10 @@ describe('Bundler', function() {
       });
     });
 
+    getModuleSystemDependencies.mockImpl(function() {
+      return [];
+    });
+
     JSTransformer.prototype.loadFileAndTransform
       .mockImpl(function(path) {
         return Promise.resolve({
@@ -123,7 +131,10 @@ describe('Bundler', function() {
       });
 
     wrapModule.mockImpl(function(response, module, code) {
-      return Promise.resolve('lol ' + code + ' lol');
+      return module.getName().then(name => ({
+        name,
+        code: 'lol ' + code + ' lol'
+      }));
     });
 
     sizeOf.mockImpl(function(path, cb) {
@@ -151,43 +162,29 @@ describe('Bundler', function() {
       runBeforeMainModule: [],
       runModule: true,
       sourceMapUrl: 'source_map_url',
-    }).then(function(p) {
-        expect(p.addModule.mock.calls[0][0]).toEqual({
-          code: 'lol transformed /root/foo.js lol',
-          map: 'sourcemap /root/foo.js',
-          sourceCode: 'source /root/foo.js',
-          sourcePath: '/root/foo.js',
-        });
+    }).then(bundle => {
+        const ithAddedModule = (i) => bundle.addModule.mock.calls[i][2].path;
 
-        expect(p.addModule.mock.calls[1][0]).toEqual({
-          code: 'lol transformed /root/bar.js lol',
-          map: 'sourcemap /root/bar.js',
-          sourceCode: 'source /root/bar.js',
-          sourcePath: '/root/bar.js'
-        });
+        expect(ithAddedModule(0)).toEqual('/root/foo.js');
+        expect(ithAddedModule(1)).toEqual('/root/bar.js');
+        expect(ithAddedModule(2)).toEqual('/root/img/img.png');
+        expect(ithAddedModule(3)).toEqual('/root/img/new_image.png');
+        expect(ithAddedModule(4)).toEqual('/root/file.json');
 
-        var imgModule_DEPRECATED = {
+        expect(bundle.finalize.mock.calls[0]).toEqual([
+          {runMainModule: true, runBeforeMainModule: []}
+        ]);
+
+        expect(bundle.addAsset.mock.calls).toContain([{
           __packager_asset: true,
           path: '/root/img/img.png',
           uri: 'img',
           width: 25,
           height: 50,
           deprecated: true,
-        };
-
-        expect(p.addModule.mock.calls[2][0]).toEqual({
-          code: 'lol module.exports = ' +
-            JSON.stringify(imgModule_DEPRECATED) +
-            '; lol',
-          sourceCode: 'module.exports = ' +
-            JSON.stringify(imgModule_DEPRECATED) +
-            ';',
-          sourcePath: '/root/img/img.png',
-          virtual: true,
-          map: undefined,
-        });
+        }]);
 
-        var imgModule = {
+        expect(bundle.addAsset.mock.calls).toContain([{
           __packager_asset: true,
           fileSystemLocation: '/root/img',
           httpServerLocation: '/assets/img',
@@ -202,39 +199,7 @@ describe('Bundler', function() {
           hash: 'i am a hash',
           name: 'img',
           type: 'png',
-        };
-
-        expect(p.addModule.mock.calls[3][0]).toEqual({
-          code: 'lol module.exports = require("AssetRegistry").registerAsset(' +
-            JSON.stringify(imgModule) +
-            '); lol',
-          sourceCode: 'module.exports = require("AssetRegistry").registerAsset(' +
-            JSON.stringify(imgModule) +
-            ');',
-          sourcePath: '/root/img/new_image.png',
-          virtual: true,
-          map: undefined,
-        });
-
-        expect(p.addModule.mock.calls[4][0]).toEqual({
-          code: 'lol module.exports = {"json":true}; lol',
-          sourceCode: 'module.exports = {"json":true};',
-          sourcePath: '/root/file.json',
-          map: undefined,
-          virtual: true,
-        });
-
-        expect(p.finalize.mock.calls[0]).toEqual([
-          {runMainModule: true, runBeforeMainModule: []}
-        ]);
-
-        expect(p.addAsset.mock.calls).toContain([
-          imgModule_DEPRECATED
-        ]);
-
-        expect(p.addAsset.mock.calls).toContain([
-          imgModule
-        ]);
+        }]);
 
         // TODO(amasad) This fails with 0 != 5 in OSS
         //expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length);
@@ -242,11 +207,12 @@ describe('Bundler', function() {
   });
 
   pit('gets the list of dependencies from the resolver', function() {
-    return bundler.getDependencies('/root/foo.js', true)
-      .then(
-        () => expect(getDependencies)
-                .toBeCalledWith('/root/foo.js', { dev: true })
-      );
+    return bundler.getDependencies('/root/foo.js', true).then(() =>
+      expect(getDependencies).toBeCalledWith(
+        '/root/foo.js',
+        { dev: true, recursive: true },
+      )
+    );
   });
 
   describe('getOrderedDependencyPaths', () => {
diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js
index 82a0282be22b..9da681d6586c 100644
--- a/packager/react-packager/src/Bundler/index.js
+++ b/packager/react-packager/src/Bundler/index.js
@@ -13,19 +13,23 @@ const fs = require('fs');
 const path = require('path');
 const Promise = require('promise');
 const ProgressBar = require('progress');
-const BundlesLayout = require('../BundlesLayout');
-const Cache = require('../Cache');
+const Cache = require('node-haste').Cache;
 const Transformer = require('../JSTransformer');
 const Resolver = require('../Resolver');
 const Bundle = require('./Bundle');
+const HMRBundle = require('./HMRBundle');
+const PrepackBundle = require('./PrepackBundle');
 const Activity = require('../Activity');
 const ModuleTransport = require('../lib/ModuleTransport');
 const declareOpts = require('../lib/declareOpts');
 const imageSize = require('image-size');
+const version = require('../../../../package.json').version;
 
 const sizeOf = Promise.denodeify(imageSize);
 const readFile = Promise.denodeify(fs.readFile);
 
+const noop = () => {};
+
 const validateOpts = declareOpts({
   projectRoots: {
     type: 'array',
@@ -78,6 +82,10 @@ const validateOpts = declareOpts({
     type: 'number',
     required: false,
   },
+  disableInternalTransforms: {
+    type: 'boolean',
+    default: false,
+  },
 });
 
 class Bundler {
@@ -87,11 +95,32 @@ class Bundler {
 
     opts.projectRoots.forEach(verifyRootExists);
 
+    let mtime;
+    try {
+      ({mtime} = fs.statSync(opts.transformModulePath));
+      mtime = String(mtime.getTime());
+    } catch (error) {
+      mtime = '';
+    }
+
+    const cacheKeyParts =  [
+      'react-packager-cache',
+      version,
+      opts.cacheVersion,
+      opts.projectRoots.join(',').split(path.sep).join('-'),
+      mtime,
+    ];
+
+    if (opts.transformModulePath) {
+      const transformer = require(opts.transformModulePath);
+      if (typeof transformer.cacheKey !== 'undefined') {
+        cacheKeyParts.push(transformer.cacheKey);
+      }
+    }
+
     this._cache = new Cache({
       resetCache: opts.resetCache,
-      cacheVersion: opts.cacheVersion,
-      projectRoots: opts.projectRoots,
-      transformModulePath: opts.transformModulePath,
+      cacheKey: cacheKeyParts.join('$'),
     });
 
     this._resolver = new Resolver({
@@ -105,22 +134,22 @@ class Bundler {
       cache: this._cache,
     });
 
-    this._bundlesLayout = new BundlesLayout({
-      dependencyResolver: this._resolver,
-      resetCache: opts.resetCache,
-      cacheVersion: opts.cacheVersion,
-      projectRoots: opts.projectRoots,
-    });
-
     this._transformer = new Transformer({
       projectRoots: opts.projectRoots,
       blacklistRE: opts.blacklistRE,
       cache: this._cache,
       transformModulePath: opts.transformModulePath,
+      disableInternalTransforms: opts.disableInternalTransforms,
     });
 
     this._projectRoots = opts.projectRoots;
     this._assetServer = opts.assetServer;
+
+    if (opts.getTransformOptionsModulePath) {
+      this._transformOptionsModule = require(
+        opts.getTransformOptionsModulePath
+      );
+    }
   }
 
   kill() {
@@ -128,71 +157,243 @@ class Bundler {
     return this._cache.end();
   }
 
-  getLayout(main, isDev) {
-    return this._bundlesLayout.generateLayout(main, isDev);
+  bundle(options) {
+    const {dev, unbundle, platform} = options;
+    const moduleSystemDeps =
+      this._resolver.getModuleSystemDependencies({dev, unbundle, platform});
+    return this._bundle({
+      bundle: new Bundle(options.sourceMapUrl),
+      moduleSystemDeps,
+      ...options,
+    });
+  }
+
+  _sourceHMRURL(platform, host, port, path) {
+    return this._hmrURL(
+      `http://${host}:${port}`,
+      platform,
+      'bundle',
+      path,
+    );
+  }
+
+  _sourceMappingHMRURL(platform, path) {
+    // Chrome expects `sourceURL` when eval'ing code
+    return this._hmrURL(
+      '\/\/# sourceURL=',
+      platform,
+      'map',
+      path,
+    );
+  }
+
+  _hmrURL(prefix, platform, extensionOverride, path) {
+    const matchingRoot = this._projectRoots.find(root => path.startsWith(root));
+
+    if (!matchingRoot) {
+      throw new Error('No matching project root for ', path);
+    }
+
+    const extensionStart = path.lastIndexOf('.');
+    let resource = path.substring(
+      matchingRoot.length,
+      extensionStart !== -1 ? extensionStart : undefined,
+    );
+
+    const extension = extensionStart !== -1
+      ? path.substring(extensionStart + 1)
+      : null;
+
+    return (
+      prefix + resource +
+      '.' + extensionOverride + '?' +
+      'platform=' + platform + '&runModule=false&entryModuleOnly=true&hot=true'
+    );
+  }
+
+  hmrBundle(options, host, port) {
+    return this._bundle({
+      bundle: new HMRBundle({
+        sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port),
+        sourceMappingURLFn: this._sourceMappingHMRURL.bind(
+          this,
+          options.platform,
+        ),
+      }),
+      hot: true,
+      ...options,
+    });
   }
 
-  bundle({
+  _bundle({
+    bundle,
     entryFile,
     runModule: runMainModule,
     runBeforeMainModule,
-    sourceMapUrl,
     dev: isDev,
     platform,
+    moduleSystemDeps = [],
+    hot,
+    entryModuleOnly,
+    resolutionResponse
   }) {
-    // Const cannot have the same name as the method (babel/babel#2834)
-    const bbundle = new Bundle(sourceMapUrl);
-    const findEventId = Activity.startEvent('find dependencies');
-    let transformEventId;
+    const onResolutionResponse = response => {
+      bundle.setMainModuleId(response.mainModuleId);
+      if (bundle.setNumPrependedModules) {
+        bundle.setNumPrependedModules(
+          response.numPrependedDependencies + moduleSystemDeps.length
+        );
+      }
+      if (entryModuleOnly) {
+        response.dependencies = response.dependencies.filter(module =>
+          module.path.endsWith(entryFile)
+        );
+      } else {
+        response.dependencies = moduleSystemDeps.concat(response.dependencies);
+      }
+    };
+    const finalizeBundle = ({bundle, transformedModules, response}) =>
+      Promise.all(
+        transformedModules.map(({module, transformed}) =>
+          bundle.addModule(this._resolver, response, module, transformed)
+        )
+      ).then(() => {
+        bundle.finalize({runBeforeMainModule, runMainModule});
+        return bundle;
+      });
 
-    return this.getDependencies(entryFile, isDev, platform).then((response) => {
-      Activity.endEvent(findEventId);
-      transformEventId = Activity.startEvent('transform');
-
-      let bar;
-      if (process.stdout.isTTY) {
-        bar = new ProgressBar('transforming [:bar] :percent :current/:total', {
-          complete: '=',
-          incomplete: ' ',
-          width: 40,
-          total: response.dependencies.length,
+    return this._buildBundle({
+      entryFile,
+      isDev,
+      platform,
+      bundle,
+      hot,
+      resolutionResponse,
+      onResolutionResponse,
+      finalizeBundle,
+    });
+  }
+
+  prepackBundle({
+    entryFile,
+    runModule: runMainModule,
+    runBeforeMainModule,
+    sourceMapUrl,
+    dev: isDev,
+    platform,
+  }) {
+    const onModuleTransformed = ({module, transformed, response, bundle}) => {
+      const deps = Object.create(null);
+      const pairs = response.getResolvedDependencyPairs(module);
+      if (pairs) {
+        pairs.forEach(pair => {
+          deps[pair[0]] = pair[1].path;
         });
       }
 
-      bbundle.setMainModuleId(response.mainModuleId);
-      return Promise.all(
-        response.dependencies.map(
-          module => this._transformModule(
-            bbundle,
-            response,
+      return module.getName().then(name => {
+        bundle.addModule(name, transformed, deps, module.isPolyfill());
+      });
+    };
+    const finalizeBundle = ({bundle, response}) => {
+      const {mainModuleId} = response;
+      bundle.finalize({runBeforeMainModule, runMainModule, mainModuleId});
+      return bundle;
+    };
+
+    return this._buildBundle({
+      entryFile,
+      isDev,
+      platform,
+      onModuleTransformed,
+      finalizeBundle,
+      bundle: new PrepackBundle(sourceMapUrl),
+    });
+  }
+
+  _buildBundle({
+    entryFile,
+    isDev,
+    platform,
+    bundle,
+    hot,
+    resolutionResponse,
+    onResolutionResponse = noop,
+    onModuleTransformed = noop,
+    finalizeBundle = noop,
+  }) {
+    const findEventId = Activity.startEvent('find dependencies');
+    if (!resolutionResponse) {
+      resolutionResponse = this.getDependencies(entryFile, isDev, platform);
+    }
+
+    return Promise.resolve(resolutionResponse).then(response => {
+      Activity.endEvent(findEventId);
+      onResolutionResponse(response);
+
+      const transformEventId = Activity.startEvent('transform');
+      const bar = process.stdout.isTTY
+          ? new ProgressBar('transforming [:bar] :percent :current/:total', {
+              complete: '=',
+              incomplete: ' ',
+              width: 40,
+              total: response.dependencies.length,
+            })
+          : {tick() {}};
+      const transformPromises =
+        response.dependencies.map(module =>
+          this._transformModule({
+            mainModuleName: response.mainModuleId,
+            bundle,
             module,
-            platform
-          ).then(transformed => {
-            if (bar) {
-              bar.tick();
-            }
-            return transformed;
+            platform,
+            dev: isDev,
+            hot
+          }).then(transformed => {
+            bar.tick();
+            onModuleTransformed({module, transformed, response, bundle});
+            return {module, transformed};
           })
-        )
-      );
-    }).then((transformedModules) => {
-      Activity.endEvent(transformEventId);
-
-      transformedModules.forEach(function(moduleTransport) {
-        bbundle.addModule(moduleTransport);
+        );
+      return Promise.all(transformPromises).then(transformedModules => {
+        Activity.endEvent(transformEventId);
+        return Promise
+          .resolve(finalizeBundle({bundle, transformedModules, response}))
+          .then(() => bundle);
       });
-
-      bbundle.finalize({runBeforeMainModule, runMainModule});
-      return bbundle;
     });
   }
 
   invalidateFile(filePath) {
+    if (this._transformOptionsModule) {
+      this._transformOptionsModule.onFileChange &&
+        this._transformOptionsModule.onFileChange();
+    }
+
     this._transformer.invalidateFile(filePath);
   }
 
-  getDependencies(main, isDev, platform) {
-    return this._resolver.getDependencies(main, { dev: isDev, platform });
+  getShallowDependencies(entryFile) {
+    return this._resolver.getShallowDependencies(entryFile);
+  }
+
+  stat(filePath) {
+    return this._resolver.stat(filePath);
+  }
+
+  getModuleForPath(entryFile) {
+    return this._resolver.getModuleForPath(entryFile);
+  }
+
+  getDependencies(main, isDev, platform, recursive = true) {
+    return this._resolver.getDependencies(
+      main,
+      {
+        dev: isDev,
+        platform,
+        recursive,
+      },
+    );
   }
 
   getOrderedDependencyPaths({ entryFile, dev, platform }) {
@@ -227,44 +428,43 @@ class Bundler {
     );
   }
 
-  _transformModule(bundle, response, module, platform = null) {
-    let transform;
-
+  _transformModule({
+    bundle,
+    module,
+    mainModuleName,
+    platform = null,
+    dev = true,
+    hot = false,
+  }) {
     if (module.isAsset_DEPRECATED()) {
-      transform = this.generateAssetModule_DEPRECATED(bundle, module);
+      return this._generateAssetModule_DEPRECATED(bundle, module);
     } else if (module.isAsset()) {
-      transform = this.generateAssetModule(bundle, module, platform);
+      return this._generateAssetModule(bundle, module, platform);
     } else if (module.isJSON()) {
-      transform = generateJSONModule(module);
+      return generateJSONModule(module);
     } else {
-      transform = this._transformer.loadFileAndTransform(
-        path.resolve(module.path)
-      );
+      return this._getTransformOptions(
+        {
+          bundleEntry: mainModuleName,
+          platform: platform,
+          dev: dev,
+          modulePath: module.path,
+        },
+        {hot},
+      ).then(options => {
+        return this._transformer.loadFileAndTransform(
+          path.resolve(module.path),
+          options,
+        );
+      });
     }
-
-    const resolver = this._resolver;
-    return transform.then(
-      transformed => resolver.wrapModule(
-        response,
-        module,
-        transformed.code
-      ).then(
-        code => new ModuleTransport({
-          code: code,
-          map: transformed.map,
-          sourceCode: transformed.sourceCode,
-          sourcePath: transformed.sourcePath,
-          virtual: transformed.virtual,
-        })
-      )
-    );
   }
 
   getGraphDebugInfo() {
     return this._resolver.getDebugInfo();
   }
 
-  generateAssetModule_DEPRECATED(bundle, module) {
+  _generateAssetModule_DEPRECATED(bundle, module) {
     return Promise.all([
       sizeOf(module.path),
       module.getName(),
@@ -291,21 +491,33 @@ class Bundler {
     });
   }
 
-  generateAssetModule(bundle, module, platform = null) {
+  _generateAssetObjAndCode(module, platform = null) {
     const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
+    var assetUrlPath = path.join('/assets', path.dirname(relPath));
+
+    // On Windows, change backslashes to slashes to get proper URL path from file path.
+    if (path.sep === '\\') {
+      assetUrlPath = assetUrlPath.replace(/\\/g, '/');
+    }
+
+    // Test extension against all types supported by image-size module.
+    // If it's not one of these, we won't treat it as an image.
+    let isImage = [
+      'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'
+    ].indexOf(path.extname(module.path).slice(1)) !== -1;
 
     return Promise.all([
-      sizeOf(module.path),
+      isImage ? sizeOf(module.path) : null,
       this._assetServer.getAssetData(relPath, platform),
     ]).then(function(res) {
       const dimensions = res[0];
       const assetData = res[1];
-      const img = {
+      const asset = {
         __packager_asset: true,
         fileSystemLocation: path.dirname(module.path),
-        httpServerLocation: path.join('/assets', path.dirname(relPath)),
-        width: dimensions.width / module.resolution,
-        height: dimensions.height / module.resolution,
+        httpServerLocation: assetUrlPath,
+        width: dimensions ? dimensions.width / module.resolution : undefined,
+        height: dimensions ? dimensions.height / module.resolution : undefined,
         scales: assetData.scales,
         files: assetData.files,
         hash: assetData.hash,
@@ -313,11 +525,17 @@ class Bundler {
         type: assetData.type,
       };
 
-      bundle.addAsset(img);
-
       const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
-      const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
+      const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset));
+
+      return {asset, code};
+    });
+  }
+
 
+  _generateAssetModule(bundle, module, platform = null) {
+    return this._generateAssetObjAndCode(module, platform).then(({asset, code}) => {
+      bundle.addAsset(asset);
       return new ModuleTransport({
         code: code,
         sourceCode: code,
@@ -326,6 +544,23 @@ class Bundler {
       });
     });
   }
+
+  _getTransformOptions(config, options) {
+    const transformerOptions = this._transformOptionsModule
+      ? this._transformOptionsModule.get(Object.assign(
+          {
+            bundler: this,
+            platform: options.platform,
+            dev: options.dev,
+          },
+          config,
+        ))
+      : Promise.resolve(null);
+
+    return transformerOptions.then(overrides => {
+      return {...options, ...overrides};
+    });
+  }
 }
 
 function generateJSONModule(module) {
diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js
deleted file mode 100644
index bc4fac8afd7e..000000000000
--- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js
+++ /dev/null
@@ -1,320 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest.dontMock('../index')
-    .mock('fs');
-
-var Promise = require('promise');
-var BundlesLayout = require('../index');
-var Resolver = require('../../Resolver');
-var loadCacheSync = require('../../lib/loadCacheSync');
-
-describe('BundlesLayout', () => {
-  function newBundlesLayout(options) {
-    return new BundlesLayout(Object.assign({
-      projectRoots: ['/root'],
-      dependencyResolver: new Resolver(),
-    }, options));
-  }
-
-  describe('layout', () => {
-    function isPolyfill() {
-      return false;
-    }
-
-    describe('getLayout', () => {
-      function dep(path) {
-        return {
-          path: path,
-          isPolyfill: isPolyfill,
-        };
-      }
-
-      pit('should bundle sync dependencies', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js'), dep('/root/a.js')],
-                asyncDependencies: [],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        return newBundlesLayout({resetCache: true})
-          .getLayout('/root/index.js')
-          .then(bundles =>
-            expect(bundles).toEqual({
-              id: 'bundle.0',
-              modules: ['/root/index.js', '/root/a.js'],
-              children: [],
-            })
-          );
-      });
-
-      pit('should separate async dependencies into different bundle', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js')],
-                asyncDependencies: [['/root/a.js']],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        return newBundlesLayout({resetCache: true})
-          .getLayout('/root/index.js')
-          .then(bundles =>
-            expect(bundles).toEqual({
-              id: 'bundle.0',
-              modules: ['/root/index.js'],
-              children: [{
-                id:'bundle.0.1',
-                modules: ['/root/a.js'],
-                children: [],
-              }],
-            })
-          );
-      });
-
-      pit('separate async dependencies of async dependencies', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js')],
-                asyncDependencies: [['/root/a.js']],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js')],
-                asyncDependencies: [['/root/b.js']],
-              });
-            case '/root/b.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/b.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        return newBundlesLayout({resetCache: true})
-          .getLayout('/root/index.js')
-          .then(bundles =>
-            expect(bundles).toEqual({
-              id: 'bundle.0',
-              modules: ['/root/index.js'],
-              children: [{
-                id: 'bundle.0.1',
-                modules: ['/root/a.js'],
-                  children: [{
-                    id: 'bundle.0.1.2',
-                    modules: ['/root/b.js'],
-                    children: [],
-                  }],
-              }],
-            })
-          );
-      });
-
-      pit('separate bundle sync dependencies of async ones on same bundle', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js')],
-                asyncDependencies: [['/root/a.js']],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js'), dep('/root/b.js')],
-                asyncDependencies: [],
-              });
-            case '/root/b.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/b.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        return newBundlesLayout({resetCache: true})
-          .getLayout('/root/index.js')
-          .then(bundles =>
-            expect(bundles).toEqual({
-              id: 'bundle.0',
-              modules: ['/root/index.js'],
-              children: [{
-                id: 'bundle.0.1',
-                modules: ['/root/a.js', '/root/b.js'],
-                children: [],
-              }],
-            })
-          );
-      });
-
-      pit('separate cache in which bundle is each dependency', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js'), dep('/root/a.js')],
-                asyncDependencies: [],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js')],
-                asyncDependencies: [['/root/b.js']],
-              });
-            case '/root/b.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/b.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        return newBundlesLayout({resetCache: true})
-          .getLayout('/root/index.js')
-          .then(bundles =>
-            expect(bundles).toEqual({
-              id: 'bundle.0',
-              modules: ['/root/index.js', '/root/a.js'],
-              children: [{
-                id: 'bundle.0.1',
-                modules: ['/root/b.js'],
-                children: [],
-              }],
-            })
-          );
-      });
-
-      pit('separate cache in which bundle is each dependency', () => {
-        Resolver.prototype.getDependencies.mockImpl((path) => {
-          switch (path) {
-            case '/root/index.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/index.js'), dep('/root/a.js')],
-                asyncDependencies: [['/root/b.js'], ['/root/c.js']],
-              });
-            case '/root/a.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/a.js')],
-                asyncDependencies: [],
-              });
-            case '/root/b.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/b.js')],
-                asyncDependencies: [['/root/d.js']],
-              });
-            case '/root/c.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/c.js')],
-                asyncDependencies: [],
-              });
-            case '/root/d.js':
-              return Promise.resolve({
-                dependencies: [dep('/root/d.js')],
-                asyncDependencies: [],
-              });
-            default:
-              throw 'Undefined path: ' + path;
-          }
-        });
-
-        var layout = newBundlesLayout({resetCache: true});
-        return layout.getLayout('/root/index.js').then(() => {
-          expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
-          expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0');
-          expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1');
-          expect(layout.getBundleIDForModule('/root/c.js')).toBe('bundle.0.2');
-          expect(layout.getBundleIDForModule('/root/d.js')).toBe('bundle.0.1.3');
-        });
-      });
-    });
-  });
-
-  describe('cache', () => {
-    beforeEach(() => {
-      loadCacheSync.mockReturnValue({
-        '/root/index.js': {
-          id: 'bundle.0',
-          modules: ['/root/index.js'],
-          children: [{
-            id: 'bundle.0.1',
-            modules: ['/root/a.js'],
-            children: [],
-          }],
-        },
-        '/root/b.js': {
-          id: 'bundle.2',
-          modules: ['/root/b.js'],
-          children: [],
-        },
-      });
-    });
-
-    pit('should load layouts', () => {
-      const layout = newBundlesLayout({ resetCache: false });
-
-      return Promise
-        .all([
-          layout.getLayout('/root/index.js'),
-          layout.getLayout('/root/b.js'),
-        ])
-        .then(([layoutIndex, layoutB]) => {
-          expect(layoutIndex).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/a.js'],
-              children: [],
-            }],
-          });
-
-          expect(layoutB).toEqual({
-            id: 'bundle.2',
-            modules: ['/root/b.js'],
-            children: [],
-          });
-        });
-    });
-
-    it('should load moduleToBundle map', () => {
-      const layout = newBundlesLayout({ resetCache: false });
-
-      expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
-      expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0.1');
-      expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.2');
-    });
-  });
-});
diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js
deleted file mode 100644
index 1cade30c4b45..000000000000
--- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js
+++ /dev/null
@@ -1,598 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest
-  .autoMockOff()
-  .mock('../../Cache')
-  .mock('../../Activity');
-
-const Promise = require('promise');
-const path = require('path');
-
-jest.mock('fs');
-
-var BundlesLayout = require('../index');
-var Cache = require('../../Cache');
-var Resolver = require('../../Resolver');
-var fs = require('fs');
-
-describe('BundlesLayout', () => {
-  var fileWatcher;
-
-  const polyfills = [
-    'polyfills/prelude_dev.js',
-    'polyfills/prelude.js',
-    'polyfills/require.js',
-    'polyfills/polyfills.js',
-    'polyfills/console.js',
-    'polyfills/error-guard.js',
-    'polyfills/String.prototype.es6.js',
-    'polyfills/Array.prototype.es6.js',
-    'polyfills/Array.es6.js',
-    'polyfills/babelHelpers.js',
-  ];
-  const baseFs = getBaseFs();
-
-  beforeEach(() => {
-    fileWatcher = {
-      on: () => this,
-      isWatchman: () => Promise.resolve(false)
-    };
-  });
-
-  describe('generate', () => {
-    function newBundlesLayout() {
-      const resolver = new Resolver({
-        projectRoots: ['/root', '/' + __dirname.split('/')[1]],
-        fileWatcher: fileWatcher,
-        cache: new Cache(),
-        assetExts: ['js', 'png'],
-        assetRoots: ['/root'],
-      });
-
-      return new BundlesLayout({
-        dependencyResolver: resolver,
-        resetCache: true,
-        projectRoots: ['/root', '/' + __dirname.split('/')[1]],
-      });
-    }
-
-    function stripPolyfills(bundle) {
-      return Promise
-        .all(bundle.children.map(childModule => stripPolyfills(childModule)))
-        .then(children => {
-          const modules = bundle.modules
-            .filter(moduleName => { // filter polyfills
-              for (let p of polyfills) {
-                if (moduleName.indexOf(p) !== -1) {
-                  return false;
-                }
-              }
-              return true;
-            });
-
-          return {
-            id: bundle.id,
-            modules: modules,
-            children: children,
-          };
-        });
-    }
-
-    function setMockFilesystem(mockFs) {
-      fs.__setMockFilesystem(Object.assign(mockFs, baseFs));
-    }
-
-    pit('should bundle single-module app', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [],
-          })
-        )
-      );
-    });
-
-    pit('should bundle dependant modules', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            require("a");`,
-          'a.js': `
-            /**
-             * @providesModule a
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js', '/root/a.js'],
-            children: [],
-          })
-        )
-      );
-    });
-
-    pit('should split bundles for async dependencies', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/a.js'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should split into multiple bundles separate async dependencies', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");
-            System.import("b");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */`,
-          'b.js': `
-            /**
-             * @providesModule b
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [
-              {
-                id: 'bundle.0.1',
-                modules: ['/root/a.js'],
-                children: [],
-              }, {
-                id: 'bundle.0.2',
-                modules: ['/root/b.js'],
-                children: [],
-              },
-            ],
-          })
-        )
-      );
-    });
-
-    pit('should fully traverse sync dependencies', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            require("a");
-            System.import("b");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */`,
-          'b.js': `
-            /**
-             * @providesModule b
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js', '/root/a.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/b.js'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should include sync dependencies async dependencies might have', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */,
-            require("b");`,
-          'b.js': `
-            /**
-             * @providesModule b
-             */
-            require("c");`,
-          'c.js': `
-            /**
-             * @providesModule c
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/a.js', '/root/b.js', '/root/c.js'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should allow duplicated dependencies across bundles', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");
-            System.import("b");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */,
-            require("c");`,
-          'b.js': `
-            /**
-             * @providesModule b
-             */
-            require("c");`,
-          'c.js': `
-            /**
-             * @providesModule c
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [
-              {
-                id: 'bundle.0.1',
-                modules: ['/root/a.js', '/root/c.js'],
-                children: [],
-              },
-              {
-                id: 'bundle.0.2',
-                modules: ['/root/b.js', '/root/c.js'],
-                children: [],
-              },
-            ],
-          })
-        )
-      );
-    });
-
-    pit('should put in separate bundles async dependencies of async dependencies', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");`,
-          'a.js': `
-            /**,
-             * @providesModule a
-             */,
-            System.import("b");`,
-          'b.js': `
-            /**
-             * @providesModule b
-             */
-            require("c");`,
-          'c.js': `
-            /**
-             * @providesModule c
-             */`,
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [
-              {
-                id: 'bundle.0.1',
-                modules: ['/root/a.js'],
-                children: [{
-                  id: 'bundle.0.1.2',
-                  modules: ['/root/b.js', '/root/c.js'],
-                  children: [],
-                }],
-              },
-            ],
-          })
-        )
-      );
-    });
-
-    pit('should put image dependencies into separate bundles', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");`,
-          'a.js':`
-            /**,
-             * @providesModule a
-             */,
-            require("./img.png");`,
-          'img.png': '',
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/a.js', '/root/img.png'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should put image dependencies across bundles', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");
-            System.import("b");`,
-          'a.js':`
-            /**,
-             * @providesModule a
-             */,
-            require("./img.png");`,
-          'b.js':`
-            /**,
-             * @providesModule b
-             */,
-            require("./img.png");`,
-          'img.png': '',
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [
-              {
-                id: 'bundle.0.1',
-                modules: ['/root/a.js', '/root/img.png'],
-                children: [],
-              },
-              {
-                id: 'bundle.0.2',
-                modules: ['/root/b.js', '/root/img.png'],
-                children: [],
-              },
-            ],
-          })
-        )
-      );
-    });
-
-    pit('could async require asset', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("./img.png");`,
-          'img.png': '',
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/img.png'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should include deprecated assets into separate bundles', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("a");`,
-          'a.js':`
-            /**,
-             * @providesModule a
-             */,
-            require("image!img");`,
-          'img.png': '',
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/a.js', '/root/img.png'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('could async require deprecated asset', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("image!img");`,
-          'img.png': '',
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/img.png'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-
-    pit('should put packages into bundles', () => {
-      setMockFilesystem({
-        'root': {
-          'index.js': `
-            /**
-             * @providesModule index
-             */
-            System.import("aPackage");`,
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: './main.js',
-              browser: {
-                './main.js': './client.js',
-              },
-            }),
-            'main.js': 'some other code',
-            'client.js': 'some code',
-          },
-        }
-      });
-
-      return newBundlesLayout().getLayout('/root/index.js').then(bundles =>
-        stripPolyfills(bundles).then(resolvedBundles =>
-          expect(resolvedBundles).toEqual({
-            id: 'bundle.0',
-            modules: ['/root/index.js'],
-            children: [{
-              id: 'bundle.0.1',
-              modules: ['/root/aPackage/client.js'],
-              children: [],
-            }],
-          })
-        )
-      );
-    });
-  });
-
-  function getBaseFs() {
-    const p = path.join(__dirname, '../../../Resolver/polyfills').substring(1);
-    const root = {};
-    let currentPath = root;
-
-    p.split('/').forEach(part => {
-      const child = {};
-      currentPath[part] = child;
-      currentPath = child;
-    });
-
-    polyfills.forEach(polyfill =>
-      currentPath[polyfill.split('/')[1]] = ''
-    );
-
-    return root;
-  }
-});
diff --git a/packager/react-packager/src/BundlesLayout/index.js b/packager/react-packager/src/BundlesLayout/index.js
deleted file mode 100644
index d946cefe1cea..000000000000
--- a/packager/react-packager/src/BundlesLayout/index.js
+++ /dev/null
@@ -1,217 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const Activity = require('../Activity');
-
-const _ = require('underscore');
-const declareOpts = require('../lib/declareOpts');
-const fs = require('fs');
-const getCacheFilePath = require('../lib/getCacheFilePath');
-const loadCacheSync = require('../lib/loadCacheSync');
-const version = require('../../../../package.json').version;
-const path = require('path');
-
-const validateOpts = declareOpts({
-  dependencyResolver: {
-    type: 'object',
-    required: true,
-  },
-  resetCache: {
-    type: 'boolean',
-    default: false,
-  },
-  cacheVersion: {
-    type: 'string',
-    default: '1.0',
-  },
-  projectRoots: {
-    type: 'array',
-    required: true,
-  },
-});
-
-const BUNDLE_PREFIX = 'bundle';
-
-/**
- * Class that takes care of separating the graph of dependencies into
- * separate bundles
- */
-class BundlesLayout {
-  constructor(options) {
-    const opts = validateOpts(options);
-    this._resolver = opts.dependencyResolver;
-
-    // Cache in which bundle is each module.
-    this._moduleToBundle = Object.create(null);
-
-    // Cache the bundles layouts for each entry point. This entries
-    // are not evicted unless the user explicitly specifies so as
-    // computing them is pretty expensive
-    this._layouts = Object.create(null);
-
-    // TODO: watch for file creations and removals to update this caches
-
-    this._cacheFilePath = this._getCacheFilePath(opts);
-    if (!opts.resetCache) {
-      this._loadCacheSync(this._cacheFilePath);
-    } else {
-      this._persistCacheEventually();
-    }
-  }
-
-  getLayout(entryPath, isDev) {
-    if (this._layouts[entryPath]) {
-      return this._layouts[entryPath];
-    }
-    var currentBundleID =  0;
-    const rootBundle = {
-      id: BUNDLE_PREFIX + '.' + currentBundleID++,
-      modules: [],
-      children: [],
-    };
-    var pending = [{paths: [entryPath], bundle: rootBundle}];
-
-    this._layouts[entryPath] = promiseWhile(
-      () => pending.length > 0,
-      () => rootBundle,
-      () => {
-        const {paths, bundle} = pending.shift();
-
-        // pending sync dependencies we still need to explore for the current
-        // pending dependency
-        const pendingSyncDeps = paths;
-
-        // accum variable for sync dependencies of the current pending
-        // dependency we're processing
-        const syncDependencies = Object.create(null);
-
-        return promiseWhile(
-          () => pendingSyncDeps.length > 0,
-          () => {
-            const dependencies = Object.keys(syncDependencies);
-            if (dependencies.length > 0) {
-              bundle.modules = dependencies;
-            }
-
-            // persist changes to layouts
-            this._persistCacheEventually();
-          },
-          index => {
-            const pendingSyncDep = pendingSyncDeps.shift();
-            return this._resolver
-              .getDependencies(pendingSyncDep, {dev: isDev})
-              .then(deps => {
-                deps.dependencies.forEach(dep => {
-                  if (dep.path !== pendingSyncDep && !dep.isPolyfill()) {
-                    pendingSyncDeps.push(dep.path);
-                  }
-                  syncDependencies[dep.path] = true;
-                  this._moduleToBundle[dep.path] = bundle.id;
-                });
-                deps.asyncDependencies.forEach(asyncDeps => {
-                  const childBundle = {
-                    id: bundle.id + '.' + currentBundleID++,
-                    modules: [],
-                    children: [],
-                  };
-
-                  bundle.children.push(childBundle);
-                  pending.push({paths: asyncDeps, bundle: childBundle});
-                });
-              });
-          },
-        );
-      },
-    );
-
-    return this._layouts[entryPath];
-  }
-
-  getBundleIDForModule(path) {
-    return this._moduleToBundle[path];
-  }
-
-  _loadCacheSync(cachePath) {
-    const loadCacheId = Activity.startEvent('Loading bundles layout');
-    const cacheOnDisk = loadCacheSync(cachePath);
-
-    // TODO: create single-module bundles for unexistent modules
-    // TODO: remove modules that no longer exist
-    Object.keys(cacheOnDisk).forEach(entryPath => {
-      this._layouts[entryPath] = Promise.resolve(cacheOnDisk[entryPath]);
-      this._fillModuleToBundleMap(cacheOnDisk[entryPath]);
-    });
-
-    Activity.endEvent(loadCacheId);
-  }
-
-  _fillModuleToBundleMap(bundle) {
-    bundle.modules.forEach(module => this._moduleToBundle[module] = bundle.id);
-    bundle.children.forEach(child => this._fillModuleToBundleMap(child));
-  }
-
-  _persistCacheEventually() {
-    _.debounce(
-      this._persistCache.bind(this),
-      2000,
-    );
-  }
-
-  _persistCache() {
-    if (this._persisting !== null) {
-      return this._persisting;
-    }
-
-    this._persisting = Promise
-      .all(_.values(this._layouts))
-      .then(bundlesLayout => {
-        var json = Object.create(null);
-        Object.keys(this._layouts).forEach((p, i) =>
-          json[p] = bundlesLayout[i]
-        );
-
-        return Promise.denodeify(fs.writeFile)(
-          this._cacheFilepath,
-          JSON.stringify(json),
-        );
-      })
-      .then(() => this._persisting = null);
-
-    return this._persisting;
-  }
-
-  _getCacheFilePath(options) {
-    return getCacheFilePath(
-      'react-packager-bundles-cache-',
-      version,
-      options.projectRoots.join(',').split(path.sep).join('-'),
-      options.cacheVersion || '0',
-    );
-  }
-}
-
-// Runs the body Promise meanwhile the condition callback is satisfied.
-// Once it's not satisfied anymore, it returns what the results callback
-// indicates
-function promiseWhile(condition, result, body) {
-  return _promiseWhile(condition, result, body, 0);
-}
-
-function _promiseWhile(condition, result, body, index) {
-  if (!condition()) {
-    return Promise.resolve(result());
-  }
-
-  return body(index).then(() =>
-    _promiseWhile(condition, result, body, index + 1)
-  );
-}
-
-module.exports = BundlesLayout;
diff --git a/packager/react-packager/src/Cache/__tests__/Cache-test.js b/packager/react-packager/src/Cache/__tests__/Cache-test.js
deleted file mode 100644
index ee06dd221a8c..000000000000
--- a/packager/react-packager/src/Cache/__tests__/Cache-test.js
+++ /dev/null
@@ -1,293 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest
-  .dontMock('underscore')
-  .dontMock('absolute-path')
-  .dontMock('../')
-  .dontMock('../../lib/loadCacheSync')
-  .dontMock('../../lib/getCacheFilePath');
-
-jest
-  .mock('fs')
-  .setMock('os', {
-    tmpDir() { return 'tmpDir'; }
-  });
-
-var Promise = require('promise');
-var fs = require('fs');
-var _ = require('underscore');
-
-var Cache = require('../');
-
-describe('JSTransformer Cache', () => {
-  describe('getting/setting', () => {
-    pit('calls loader callback for uncached file', () => {
-      fs.stat.mockImpl((file, callback) => {
-        callback(null, {
-          mtime: {
-            getTime: () => {}
-          }
-        });
-      });
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() => Promise.resolve());
-
-      return cache
-        .get('/rootDir/someFile', 'field', loaderCb)
-        .then($ =>
-          expect(loaderCb).toBeCalledWith('/rootDir/someFile')
-        );
-    });
-
-    pit('supports storing multiple fields', () => {
-      fs.stat.mockImpl((file, callback) => {
-        callback(null, {
-          mtime: {
-            getTime: () => {}
-          }
-        });
-      });
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var index = 0;
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve(index++)
-      );
-
-      return cache
-        .get('/rootDir/someFile', 'field1', loaderCb)
-        .then(value => {
-          expect(value).toBe(0);
-          return cache
-            .get('/rootDir/someFile', 'field2', loaderCb)
-            .then(value2 => expect(value2).toBe(1));
-        });
-    });
-
-    pit('gets the value from the loader callback', () => {
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => {}
-          }
-        })
-      );
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('lol')
-      );
-
-      return cache
-        .get('/rootDir/someFile', 'field', loaderCb)
-        .then(value => expect(value).toBe('lol'));
-    });
-
-    pit('caches the value after the first call', () => {
-      fs.stat.mockImpl((file, callback) => {
-        callback(null, {
-          mtime: {
-            getTime: () => {}
-          }
-        });
-      });
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('lol')
-      );
-
-      return cache
-        .get('/rootDir/someFile', 'field', loaderCb)
-        .then(() => {
-          var shouldNotBeCalled = jest.genMockFn();
-          return cache.get('/rootDir/someFile', 'field', shouldNotBeCalled)
-            .then(value => {
-              expect(shouldNotBeCalled).not.toBeCalled();
-              expect(value).toBe('lol');
-            });
-        });
-    });
-
-    pit('clears old field when getting new field and mtime changed', () => {
-      var mtime = 0;
-      fs.stat.mockImpl((file, callback) => {
-        callback(null, {
-          mtime: {
-            getTime: () => mtime++
-          }
-        });
-      });
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('lol' + mtime)
-      );
-
-      return cache
-        .get('/rootDir/someFile', 'field1', loaderCb)
-        .then(value => cache
-          .get('/rootDir/someFile', 'field2', loaderCb)
-          .then(value2 => cache
-            .get('/rootDir/someFile', 'field1', loaderCb)
-            .then(value3 => expect(value3).toBe('lol2'))
-          )
-        );
-    });
-  });
-
-  describe('loading cache from disk', () => {
-    var fileStats;
-
-    beforeEach(() => {
-      fileStats = {
-        '/rootDir/someFile': {
-          mtime: {
-            getTime: () => 22
-          }
-        },
-        '/rootDir/foo': {
-          mtime: {
-            getTime: () => 11
-          }
-        }
-      };
-
-      fs.existsSync.mockImpl(() => true);
-
-      fs.statSync.mockImpl(filePath => fileStats[filePath]);
-
-      fs.readFileSync.mockImpl(() => JSON.stringify({
-        '/rootDir/someFile': {
-          metadata: {mtime: 22},
-          data: {field: 'oh hai'},
-        },
-        '/rootDir/foo': {
-          metadata: {mtime: 11},
-          data: {field: 'lol wat'},
-        }
-      }));
-    });
-
-    pit('should load cache from disk', () => {
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn();
-
-      return cache
-        .get('/rootDir/someFile', 'field', loaderCb)
-        .then(value => {
-          expect(loaderCb).not.toBeCalled();
-          expect(value).toBe('oh hai');
-
-          return cache
-            .get('/rootDir/foo', 'field', loaderCb)
-            .then(val => {
-              expect(loaderCb).not.toBeCalled();
-              expect(val).toBe('lol wat');
-            });
-        });
-    });
-
-    pit('should not load outdated cache', () => {
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => {}
-          }
-        })
-      );
-
-      fileStats['/rootDir/foo'].mtime.getTime = () => 123;
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('new value')
-      );
-
-      return cache
-        .get('/rootDir/someFile', 'field', loaderCb)
-        .then(value => {
-          expect(loaderCb).not.toBeCalled();
-          expect(value).toBe('oh hai');
-
-          return cache
-            .get('/rootDir/foo', 'field', loaderCb)
-            .then(val => {
-              expect(loaderCb).toBeCalled();
-              expect(val).toBe('new value');
-            });
-        });
-    });
-  });
-
-  describe('writing cache to disk', () => {
-    it('should write cache to disk', () => {
-      var index = 0;
-      var mtimes = [10, 20, 30];
-      var debounceIndex = 0;
-      _.debounce = callback => {
-        return () => {
-          if (++debounceIndex === 3) {
-            callback();
-          }
-        };
-      };
-
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => mtimes[index++]
-          }
-        })
-      );
-
-      var cache = new Cache({
-        projectRoots: ['/rootDir'],
-        transformModulePath: 'x.js',
-      });
-
-      cache.get('/rootDir/bar', 'field', () =>
-        Promise.resolve('bar value')
-      );
-      cache.get('/rootDir/foo', 'field', () =>
-        Promise.resolve('foo value')
-      );
-      cache.get('/rootDir/baz', 'field', () =>
-        Promise.resolve('baz value')
-      );
-
-      jest.runAllTicks();
-      expect(fs.writeFile).toBeCalled();
-    });
-  });
-});
diff --git a/packager/react-packager/src/Cache/index.js b/packager/react-packager/src/Cache/index.js
deleted file mode 100644
index cbd9e2150fa0..000000000000
--- a/packager/react-packager/src/Cache/index.js
+++ /dev/null
@@ -1,211 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const Promise = require('promise');
-const _ = require('underscore');
-const declareOpts = require('../lib/declareOpts');
-const fs = require('fs');
-const getCacheFilePath = require('../lib/getCacheFilePath');
-const isAbsolutePath = require('absolute-path');
-const loadCacheSync = require('../lib/loadCacheSync');
-const path = require('path');
-const version = require('../../../../package.json').version;
-
-const validateOpts = declareOpts({
-  resetCache: {
-    type: 'boolean',
-    default: false,
-  },
-  cacheVersion: {
-    type: 'string',
-    default: '1.0',
-  },
-  projectRoots: {
-    type: 'array',
-    required: true,
-  },
-  transformModulePath: {
-    type:'string',
-    required: true,
-  },
-});
-
-// TODO: move to Packager directory
-class Cache {
-  constructor(options) {
-    var opts = validateOpts(options);
-
-    this._cacheFilePath = this._getCacheFilePath(opts);
-
-    var data;
-    if (!opts.resetCache) {
-      data = this._loadCacheSync(this._cacheFilePath);
-    } else {
-      data = Object.create(null);
-    }
-    this._data = data;
-
-    this._persistEventually = _.debounce(
-      this._persistCache.bind(this),
-      2000,
-    );
-  }
-
-  get(filepath, field, loaderCb) {
-    if (!isAbsolutePath(filepath)) {
-      throw new Error('Use absolute paths');
-    }
-
-    var recordP = this._has(filepath, field)
-      ? this._data[filepath].data[field]
-      : this._set(filepath, field, loaderCb(filepath));
-
-    return recordP.then(record => record);
-  }
-
-  invalidate(filepath) {
-    if (this._has(filepath)) {
-      delete this._data[filepath];
-    }
-  }
-
-  end() {
-    return this._persistCache();
-  }
-
-  _has(filepath, field) {
-    return Object.prototype.hasOwnProperty.call(this._data, filepath) &&
-      (!field || Object.prototype.hasOwnProperty.call(this._data[filepath].data, field));
-  }
-
-  _set(filepath, field, loaderPromise) {
-    let record = this._data[filepath];
-    if (!record) {
-      record = Object.create(null);
-      this._data[filepath] = record;
-      this._data[filepath].data = Object.create(null);
-      this._data[filepath].metadata = Object.create(null);
-    }
-
-    record.data[field] = loaderPromise
-      .then(data => Promise.all([
-        data,
-        Promise.denodeify(fs.stat)(filepath),
-      ]))
-      .then(([data, stat]) => {
-        this._persistEventually();
-
-        // Evict all existing field data from the cache if we're putting new
-        // more up to date data
-        var mtime = stat.mtime.getTime();
-        if (record.metadata.mtime !== mtime) {
-          record.data = Object.create(null);
-        }
-        record.metadata.mtime = mtime;
-
-        return data;
-      });
-
-    return record.data[field];
-  }
-
-  _persistCache() {
-    if (this._persisting != null) {
-      return this._persisting;
-    }
-
-    var data = this._data;
-    var cacheFilepath = this._cacheFilePath;
-
-    var allPromises = _.values(data)
-      .map(record => {
-        var fieldNames = Object.keys(record.data);
-        var fieldValues = _.values(record.data);
-
-        return Promise
-          .all(fieldValues)
-          .then(ref => {
-            var ret = Object.create(null);
-            ret.metadata = record.metadata;
-            ret.data = Object.create(null);
-            fieldNames.forEach((field, index) =>
-              ret.data[field] = ref[index]
-            );
-
-            return ret;
-          });
-      }
-    );
-
-    this._persisting = Promise.all(allPromises)
-      .then(values => {
-        var json = Object.create(null);
-        Object.keys(data).forEach((key, i) => {
-          json[key] = Object.create(null);
-          json[key].metadata = data[key].metadata;
-          json[key].data = values[i].data;
-        });
-        return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json));
-      })
-      .then(() => {
-        this._persisting = null;
-        return true;
-      });
-
-    return this._persisting;
-  }
-
-  _loadCacheSync(cachePath) {
-    var ret = Object.create(null);
-    var cacheOnDisk = loadCacheSync(cachePath);
-
-    // Filter outdated cache and convert to promises.
-    Object.keys(cacheOnDisk).forEach(key => {
-      if (!fs.existsSync(key)) {
-        return;
-      }
-      var record = cacheOnDisk[key];
-      var stat = fs.statSync(key);
-      if (stat.mtime.getTime() === record.metadata.mtime) {
-        ret[key] = Object.create(null);
-        ret[key].metadata = Object.create(null);
-        ret[key].data = Object.create(null);
-        ret[key].metadata.mtime = record.metadata.mtime;
-
-        Object.keys(record.data).forEach(field => {
-          ret[key].data[field] = Promise.resolve(record.data[field]);
-        });
-      }
-    });
-
-    return ret;
-  }
-
-  _getCacheFilePath(options) {
-    let mtime;
-    try {
-      ({mtime} = fs.statSync(options.transformModulePath));
-      mtime = String(mtime.getTime());
-    } catch (error) {
-      mtime = '';
-    }
-
-    return getCacheFilePath(
-      'react-packager-cache-',
-      version,
-      options.projectRoots.join(',').split(path.sep).join('-'),
-      options.cacheVersion || '0',
-      options.transformModulePath,
-      mtime
-    );
-  }
-}
-
-module.exports = Cache;
diff --git a/packager/react-packager/src/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js
deleted file mode 100644
index 1e5ff7d44b4f..000000000000
--- a/packager/react-packager/src/DependencyResolver/AssetModule.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-const Module = require('./Module');
-const Promise = require('promise');
-const getAssetDataFromName = require('./lib/getAssetDataFromName');
-
-class AssetModule extends Module {
-  constructor(...args) {
-    super(...args);
-    const { resolution, name, type } = getAssetDataFromName(this.path);
-    this.resolution = resolution;
-    this._name = name;
-    this._type = type;
-  }
-
-  isHaste() {
-    return Promise.resolve(false);
-  }
-
-  getDependencies() {
-    return Promise.resolve([]);
-  }
-
-  getAsyncDependencies() {
-    return Promise.resolve([]);
-  }
-
-  _read() {
-    return Promise.resolve({});
-  }
-
-  getName() {
-    return super.getName().then(
-      id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`)
-    );
-  }
-
-  hash() {
-    return `AssetModule : ${this.path}`;
-  }
-
-  isJSON() {
-    return false;
-  }
-
-  isAsset() {
-    return true;
-  }
-}
-
-module.exports = AssetModule;
diff --git a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js
deleted file mode 100644
index a77d0aef5e93..000000000000
--- a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js
+++ /dev/null
@@ -1,49 +0,0 @@
-'use strict';
-
-const Module = require('./Module');
-const Promise = require('promise');
-const getAssetDataFromName = require('./lib/getAssetDataFromName');
-
-class AssetModule_DEPRECATED extends Module {
-  constructor(...args) {
-    super(...args);
-    const {resolution, name} = getAssetDataFromName(this.path);
-    this.resolution = resolution;
-    this.name = name;
-  }
-
-  isHaste() {
-    return Promise.resolve(false);
-  }
-
-  getName() {
-    return Promise.resolve(`image!${this.name}`);
-  }
-
-  getDependencies() {
-    return Promise.resolve([]);
-  }
-
-  getAsyncDependencies() {
-    return Promise.resolve([]);
-  }
-
-  hash() {
-    return `AssetModule_DEPRECATED : ${this.path}`;
-  }
-
-  isJSON() {
-    return false;
-  }
-
-  isAsset_DEPRECATED() {
-    return true;
-  }
-
-  resolution() {
-    return getAssetDataFromName(this.path).resolution;
-  }
-
-}
-
-module.exports = AssetModule_DEPRECATED;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js
deleted file mode 100644
index aedfa4ce0aa0..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/DeprecatedAssetMap.js
+++ /dev/null
@@ -1,115 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
-const Fastfs = require('../fastfs');
-const debug = require('debug')('ReactNativePackager:DependencyGraph');
-const path = require('path');
-const Promise = require('promise');
-
-class DeprecatedAssetMap {
-  constructor({
-    fsCrawl,
-    roots,
-    assetExts,
-    fileWatcher,
-    ignoreFilePath,
-    helpers,
-    activity,
-  }) {
-    if (roots == null || roots.length === 0) {
-      this._disabled = true;
-      return;
-    }
-
-    this._helpers = helpers;
-    this._map = Object.create(null);
-    this._assetExts = assetExts;
-    this._fastfs = new Fastfs(
-      'Assets',
-      roots,
-      fileWatcher,
-      { ignore: ignoreFilePath, crawling: fsCrawl, activity }
-    );
-    this._activity = activity;
-
-    this._fastfs.on('change', this._processFileChange.bind(this));
-  }
-
-  build() {
-    if (this._disabled) {
-      return Promise.resolve();
-    }
-
-    return this._fastfs.build().then(
-      () => {
-        const activity = this._activity;
-        let processAsset_DEPRECATEDActivity;
-        if (activity) {
-          processAsset_DEPRECATEDActivity = activity.startEvent(
-            'Building (deprecated) Asset Map',
-          );
-        }
-
-        this._fastfs.findFilesByExts(this._assetExts).forEach(
-          file => this._processAsset(file)
-        );
-
-        if (activity) {
-          activity.endEvent(processAsset_DEPRECATEDActivity);
-        }
-      }
-    );
-  }
-
-  resolve(fromModule, toModuleName) {
-    if (this._disabled) {
-      return null;
-    }
-
-    const assetMatch = toModuleName.match(/^image!(.+)/);
-    if (assetMatch && assetMatch[1]) {
-      if (!this._map[assetMatch[1]]) {
-        debug('WARINING: Cannot find asset:', assetMatch[1]);
-        return null;
-      }
-      return this._map[assetMatch[1]];
-    }
-  }
-
-  _processAsset(file) {
-    const ext = this._helpers.extname(file);
-    if (this._assetExts.indexOf(ext) !== -1) {
-      const name = assetName(file, ext);
-      if (this._map[name] != null) {
-        debug('Conflcting assets', name);
-      }
-
-      this._map[name] = new AssetModule_DEPRECATED(file);
-    }
-  }
-
-  _processFileChange(type, filePath, root, fstat) {
-    const name = assetName(filePath);
-    if (type === 'change' || type === 'delete') {
-      delete this._map[name];
-    }
-
-    if (type === 'change' || type === 'add') {
-      this._processAsset(path.join(root, filePath));
-    }
-  }
-}
-
-function assetName(file, ext) {
-  return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, '');
-}
-
-module.exports = DeprecatedAssetMap;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js
deleted file mode 100644
index e1626fef8067..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js
+++ /dev/null
@@ -1,133 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const path = require('path');
-const getPlatformExtension = require('../lib/getPlatformExtension');
-const Promise = require('promise');
-
-const GENERIC_PLATFORM = 'generic';
-
-class HasteMap {
-  constructor({ extensions, fastfs, moduleCache, helpers }) {
-    this._extensions = extensions;
-    this._fastfs = fastfs;
-    this._moduleCache = moduleCache;
-    this._helpers = helpers;
-  }
-
-  build() {
-    this._map = Object.create(null);
-
-    let promises = this._fastfs.findFilesByExts(this._extensions, {
-      ignore: (file) => this._helpers.isNodeModulesDir(file),
-    }).map(file => this._processHasteModule(file));
-
-    promises = promises.concat(
-      this._fastfs.findFilesByName('package.json', {
-        ignore: (file) => this._helpers.isNodeModulesDir(file),
-      }).map(file => this._processHastePackage(file))
-    );
-
-    return Promise.all(promises);
-  }
-
-  processFileChange(type, absPath) {
-    return Promise.resolve().then(() => {
-      /*eslint no-labels: 0 */
-      if (type === 'delete' || type === 'change') {
-        loop: for (const name in this._map) {
-          const modulesMap = this._map[name];
-          for (const platform in modulesMap) {
-            const module = modulesMap[platform];
-            if (module.path === absPath) {
-              delete modulesMap[platform];
-              break loop;
-            }
-          }
-        }
-
-        if (type === 'delete') {
-          return null;
-        }
-      }
-
-      if (this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) {
-        if (path.basename(absPath) === 'package.json') {
-          return this._processHastePackage(absPath);
-        } else {
-          return this._processHasteModule(absPath);
-        }
-      }
-    });
-  }
-
-  getModule(name, platform = null) {
-    const modulesMap = this._map[name];
-    if (modulesMap == null) {
-      return null;
-    }
-
-    // If no platform is given we choose the generic platform module list.
-    // If a platform is given and no modules exist we fallback
-    // to the generic platform module list.
-    if (platform == null) {
-      return modulesMap[GENERIC_PLATFORM];
-    } else {
-      let module = modulesMap[platform];
-      if (module == null) {
-        module = modulesMap[GENERIC_PLATFORM];
-      }
-      return module;
-    }
-  }
-
-  _processHasteModule(file) {
-    const module = this._moduleCache.getModule(file);
-    return module.isHaste().then(
-      isHaste => isHaste && module.getName()
-        .then(name => this._updateHasteMap(name, module))
-    );
-  }
-
-  _processHastePackage(file) {
-    file = path.resolve(file);
-    const p = this._moduleCache.getPackage(file, this._fastfs);
-    return p.isHaste()
-      .then(isHaste => isHaste && p.getName()
-            .then(name => this._updateHasteMap(name, p)))
-      .catch(e => {
-        if (e instanceof SyntaxError) {
-          // Malformed package.json.
-          return;
-        }
-        throw e;
-      });
-  }
-
-  _updateHasteMap(name, mod) {
-    if (this._map[name] == null) {
-      this._map[name] = Object.create(null);
-    }
-
-    const moduleMap = this._map[name];
-    const modulePlatform = getPlatformExtension(mod.path) || GENERIC_PLATFORM;
-
-    if (moduleMap[modulePlatform]) {
-      throw new Error(
-        `Naming collision detected: ${mod.path} ` +
-        `collides with ${moduleMap[modulePlatform].path}`
-      );
-    }
-
-    moduleMap[modulePlatform] = mod;
-  }
-}
-
-module.exports = HasteMap;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js
deleted file mode 100644
index fda5600777a1..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/Helpers.js
+++ /dev/null
@@ -1,49 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const path = require('path');
-
-class Helpers {
-  constructor({ providesModuleNodeModules, assetExts }) {
-    this._providesModuleNodeModules = providesModuleNodeModules;
-    this._assetExts = assetExts;
-  }
-
-  isNodeModulesDir(file) {
-    let parts = path.normalize(file).split(path.sep);
-    const indexOfNodeModules = parts.lastIndexOf('node_modules');
-
-    if (indexOfNodeModules === -1) {
-      return false;
-    }
-
-    parts = parts.slice(indexOfNodeModules + 1);
-
-    const dirs = this._providesModuleNodeModules;
-
-    for (let i = 0; i < dirs.length; i++) {
-      if (parts.indexOf(dirs[i]) > -1) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
-  isAssetFile(file) {
-    return this._assetExts.indexOf(this.extname(file)) !== -1;
-  }
-
-  extname(name) {
-    return path.extname(name).replace(/^\./, '');
-  }
-}
-
-module.exports = Helpers;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js
deleted file mode 100644
index 2125b97f8602..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js
+++ /dev/null
@@ -1,413 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const debug = require('debug')('ReactNativePackager:DependencyGraph');
-const util = require('util');
-const path = require('path');
-const isAbsolutePath = require('absolute-path');
-const getAssetDataFromName = require('../lib/getAssetDataFromName');
-const Promise = require('promise');
-
-class ResolutionRequest {
-  constructor({
-    platform,
-    entryPath,
-    hasteMap,
-    deprecatedAssetMap,
-    helpers,
-    moduleCache,
-    fastfs,
-  }) {
-    this._platform = platform;
-    this._entryPath = entryPath;
-    this._hasteMap = hasteMap;
-    this._deprecatedAssetMap = deprecatedAssetMap;
-    this._helpers = helpers;
-    this._moduleCache = moduleCache;
-    this._fastfs = fastfs;
-    this._resetResolutionCache();
-  }
-
-  _tryResolve(action, secondaryAction) {
-    return action().catch((error) => {
-      if (error.type !== 'UnableToResolveError') {
-        throw error;
-      }
-      return secondaryAction();
-    });
-  }
-
-  resolveDependency(fromModule, toModuleName) {
-    const resHash = resolutionHash(fromModule.path, toModuleName);
-
-    if (this._immediateResolutionCache[resHash]) {
-      return Promise.resolve(this._immediateResolutionCache[resHash]);
-    }
-
-    const asset_DEPRECATED = this._deprecatedAssetMap.resolve(
-      fromModule,
-      toModuleName
-    );
-    if (asset_DEPRECATED) {
-      return Promise.resolve(asset_DEPRECATED);
-    }
-
-    const cacheResult = (result) => {
-      this._immediateResolutionCache[resHash] = result;
-      return result;
-    };
-
-    const forgive = (error) => {
-      if (error.type !== 'UnableToResolveError' ||
-        this._platform === 'ios') {
-        throw error;
-      }
-
-      debug(
-        'Unable to resolve module %s from %s',
-        toModuleName,
-        fromModule.path
-      );
-      return null;
-    };
-
-    if (!this._helpers.isNodeModulesDir(fromModule.path)
-        && toModuleName[0] !== '.' &&
-        toModuleName[0] !== '/') {
-      return this._tryResolve(
-        () => this._resolveHasteDependency(fromModule, toModuleName),
-        () => this._resolveNodeDependency(fromModule, toModuleName)
-      ).then(
-        cacheResult,
-        forgive,
-      );
-    }
-
-    return this._resolveNodeDependency(fromModule, toModuleName)
-      .then(
-        cacheResult,
-        forgive,
-      );
-  }
-
-  getOrderedDependencies(response, mocksPattern) {
-    return this._getAllMocks(mocksPattern).then(mocks => {
-      response.setMocks(mocks);
-
-      const entry = this._moduleCache.getModule(this._entryPath);
-      const visited = Object.create(null);
-      visited[entry.hash()] = true;
-
-      const collect = (mod) => {
-        response.pushDependency(mod);
-        return mod.getDependencies().then(
-          depNames => Promise.all(
-            depNames.map(name => this.resolveDependency(mod, name))
-          ).then((dependencies) => [depNames, dependencies])
-        ).then(([depNames, dependencies]) => {
-          if (mocks) {
-            return mod.getName().then(name => {
-              if (mocks[name]) {
-                const mockModule =
-                  this._moduleCache.getModule(mocks[name]);
-                depNames.push(name);
-                dependencies.push(mockModule);
-              }
-              return [depNames, dependencies];
-            });
-          }
-          return Promise.resolve([depNames, dependencies]);
-        }).then(([depNames, dependencies]) => {
-          let p = Promise.resolve();
-          const filteredPairs = [];
-
-          dependencies.forEach((modDep, i) => {
-            if (modDep == null) {
-              debug(
-                'WARNING: Cannot find required module `%s` from module `%s`',
-                depNames[i],
-                mod.path
-              );
-              return false;
-            }
-            return filteredPairs.push([depNames[i], modDep]);
-          });
-
-          response.setResolvedDependencyPairs(mod, filteredPairs);
-
-          filteredPairs.forEach(([depName, modDep]) => {
-            p = p.then(() => {
-              if (!visited[modDep.hash()]) {
-                visited[modDep.hash()] = true;
-                return collect(modDep);
-              }
-              return null;
-            });
-          });
-
-          return p;
-        });
-      };
-
-      return collect(entry);
-    });
-  }
-
-  getAsyncDependencies(response) {
-    return Promise.resolve().then(() => {
-      const mod = this._moduleCache.getModule(this._entryPath);
-      return mod.getAsyncDependencies().then(bundles =>
-        Promise
-          .all(bundles.map(bundle =>
-            Promise.all(bundle.map(
-              dep => this.resolveDependency(mod, dep)
-            ))
-          ))
-          .then(bs => bs.map(bundle => bundle.map(dep => dep.path)))
-      );
-    }).then(asyncDependencies => asyncDependencies.forEach(
-      (dependency) => response.pushAsyncDependency(dependency)
-    ));
-  }
-
-  _getAllMocks(pattern) {
-    // Take all mocks in all the roots into account. This is necessary
-    // because currently mocks are global: any module can be mocked by
-    // any mock in the system.
-    let mocks = null;
-    if (pattern) {
-      mocks = Object.create(null);
-      this._fastfs.matchFilesByPattern(pattern).forEach(file =>
-        mocks[path.basename(file, path.extname(file))] = file
-      );
-    }
-    return Promise.resolve(mocks);
-  }
-
-  _resolveHasteDependency(fromModule, toModuleName) {
-    toModuleName = normalizePath(toModuleName);
-
-    let p = fromModule.getPackage();
-    if (p) {
-      p = p.redirectRequire(toModuleName);
-    } else {
-      p = Promise.resolve(toModuleName);
-    }
-
-    return p.then((realModuleName) => {
-      let dep = this._hasteMap.getModule(realModuleName, this._platform);
-      if (dep && dep.type === 'Module') {
-        return dep;
-      }
-
-      let packageName = realModuleName;
-      while (packageName && packageName !== '.') {
-        dep = this._hasteMap.getModule(packageName, this._platform);
-        if (dep && dep.type === 'Package') {
-          break;
-        }
-        packageName = path.dirname(packageName);
-      }
-
-      if (dep && dep.type === 'Package') {
-        const potentialModulePath = path.join(
-          dep.root,
-          path.relative(packageName, realModuleName)
-        );
-        return this._tryResolve(
-          () => this._loadAsFile(
-            potentialModulePath,
-            fromModule,
-            toModuleName,
-          ),
-          () => this._loadAsDir(potentialModulePath, fromModule, toModuleName),
-        );
-      }
-
-      throw new UnableToResolveError(
-        fromModule,
-        toModuleName,
-        'Unable to resolve dependency',
-      );
-    });
-  }
-
-  _redirectRequire(fromModule, modulePath) {
-    return Promise.resolve(fromModule.getPackage()).then(p => {
-      if (p) {
-        return p.redirectRequire(modulePath);
-      }
-      return modulePath;
-    });
-  }
-
-  _resolveNodeDependency(fromModule, toModuleName) {
-    if (toModuleName[0] === '.' || toModuleName[1] === '/') {
-      const potentialModulePath = isAbsolutePath(toModuleName) ?
-              toModuleName :
-              path.join(path.dirname(fromModule.path), toModuleName);
-      return this._redirectRequire(fromModule, potentialModulePath).then(
-        realModuleName => this._tryResolve(
-          () => this._loadAsFile(realModuleName, fromModule, toModuleName),
-          () => this._loadAsDir(realModuleName, fromModule, toModuleName)
-        )
-      );
-    } else {
-      return this._redirectRequire(fromModule, toModuleName).then(
-        realModuleName => {
-          const searchQueue = [];
-          for (let currDir = path.dirname(fromModule.path);
-               currDir !== path.parse(fromModule.path).root;
-               currDir = path.dirname(currDir)) {
-            searchQueue.push(
-              path.join(currDir, 'node_modules', realModuleName)
-            );
-          }
-
-          let p = Promise.reject(new UnableToResolveError(
-            fromModule,
-            toModuleName,
-            'Node module not found',
-          ));
-          searchQueue.forEach(potentialModulePath => {
-            p = this._tryResolve(
-              () => this._tryResolve(
-                () => p,
-                () => this._loadAsFile(potentialModulePath, fromModule, toModuleName),
-              ),
-              () => this._loadAsDir(potentialModulePath, fromModule, toModuleName)
-            );
-          });
-
-          return p;
-        });
-    }
-  }
-
-  _loadAsFile(potentialModulePath, fromModule, toModule) {
-    return Promise.resolve().then(() => {
-      if (this._helpers.isAssetFile(potentialModulePath)) {
-        const dirname = path.dirname(potentialModulePath);
-        if (!this._fastfs.dirExists(dirname)) {
-          throw new UnableToResolveError(
-            fromModule,
-            toModule,
-            `Directory ${dirname} doesn't exist`,
-          );
-        }
-
-        const {name, type} = getAssetDataFromName(potentialModulePath);
-
-        let pattern = '^' + name + '(@[\\d\\.]+x)?';
-        if (this._platform != null) {
-          pattern += '(\\.' + this._platform + ')?';
-        }
-        pattern += '\\.' + type;
-
-        // We arbitrarly grab the first one, because scale selection
-        // will happen somewhere
-        const [assetFile] = this._fastfs.matches(
-          dirname,
-          new RegExp(pattern)
-        );
-
-        if (assetFile) {
-          return this._moduleCache.getAssetModule(assetFile);
-        }
-      }
-
-      let file;
-      if (this._fastfs.fileExists(potentialModulePath)) {
-        file = potentialModulePath;
-      } else if (this._platform != null &&
-                 this._fastfs.fileExists(potentialModulePath + '.' + this._platform + '.js')) {
-        file = potentialModulePath + '.' + this._platform + '.js';
-      } else if (this._fastfs.fileExists(potentialModulePath + '.js')) {
-        file = potentialModulePath + '.js';
-      } else if (this._fastfs.fileExists(potentialModulePath + '.json')) {
-        file = potentialModulePath + '.json';
-      } else {
-        throw new UnableToResolveError(
-          fromModule,
-          toModule,
-          `File ${potentialModulePath} doesnt exist`,
-        );
-      }
-
-      return this._moduleCache.getModule(file);
-    });
-  }
-
-  _loadAsDir(potentialDirPath, fromModule, toModule) {
-    return Promise.resolve().then(() => {
-      if (!this._fastfs.dirExists(potentialDirPath)) {
-        throw new UnableToResolveError(
-          fromModule,
-          toModule,
-          `Invalid directory ${potentialDirPath}`,
-        );
-      }
-
-      const packageJsonPath = path.join(potentialDirPath, 'package.json');
-      if (this._fastfs.fileExists(packageJsonPath)) {
-        return this._moduleCache.getPackage(packageJsonPath)
-          .getMain().then(
-            (main) => this._tryResolve(
-              () => this._loadAsFile(main, fromModule, toModule),
-              () => this._loadAsDir(main, fromModule, toModule)
-            )
-          );
-      }
-
-      return this._loadAsFile(
-        path.join(potentialDirPath, 'index'),
-        fromModule,
-        toModule,
-      );
-    });
-  }
-
-  _resetResolutionCache() {
-    this._immediateResolutionCache = Object.create(null);
-  }
-
-}
-
-
-function resolutionHash(modulePath, depName) {
-  return `${path.resolve(modulePath)}:${depName}`;
-}
-
-
-function UnableToResolveError(fromModule, toModule, message) {
-  Error.call(this);
-  Error.captureStackTrace(this, this.constructor);
-  this.message = util.format(
-    'Unable to resolve module %s from %s: %s',
-    toModule,
-    fromModule.path,
-    message,
-  );
-  this.type = this.name = 'UnableToResolveError';
-}
-
-util.inherits(UnableToResolveError, Error);
-
-function normalizePath(modulePath) {
-  if (path.sep === '/') {
-    modulePath = path.normalize(modulePath);
-  } else if (path.posix) {
-    modulePath = path.posix.normalize(modulePath);
-  }
-
-  return modulePath.replace(/\/$/, '');
-}
-
-module.exports = ResolutionRequest;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js
deleted file mode 100644
index aa454a9f5fdd..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js
+++ /dev/null
@@ -1,78 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-class ResolutionResponse {
-  constructor() {
-    this.dependencies = [];
-    this.asyncDependencies = [];
-    this.mainModuleId = null;
-    this.mocks = null;
-    this._mappings = Object.create(null);
-    this._finalized = false;
-  }
-
-  _assertNotFinalized() {
-    if (this._finalized) {
-      throw new Error('Attempted to mutate finalized response.');
-    }
-  }
-
-  _assertFinalized() {
-    if (!this._finalized) {
-      throw new Error('Attempted to access unfinalized response.');
-    }
-  }
-
-  finalize() {
-    return this._mainModule.getName().then(id => {
-      this.mainModuleId = id;
-      this._finalized = true;
-      return this;
-    });
-  }
-
-  pushDependency(module) {
-    this._assertNotFinalized();
-    if (this.dependencies.length === 0) {
-      this._mainModule = module;
-    }
-
-    this.dependencies.push(module);
-  }
-
-  prependDependency(module) {
-    this._assertNotFinalized();
-    this.dependencies.unshift(module);
-  }
-
-  pushAsyncDependency(dependency) {
-    this._assertNotFinalized();
-    this.asyncDependencies.push(dependency);
-  }
-
-  setResolvedDependencyPairs(module, pairs) {
-    this._assertNotFinalized();
-    const hash = module.hash();
-    if (this._mappings[hash] == null) {
-      this._mappings[hash] = pairs;
-    }
-  }
-
-  setMocks(mocks) {
-    this.mocks = mocks;
-  }
-
-  getResolvedDependencyPairs(module) {
-    this._assertFinalized();
-    return this._mappings[module.hash()];
-  }
-}
-
-module.exports = ResolutionResponse;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
deleted file mode 100644
index b8caec76a19b..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
+++ /dev/null
@@ -1,3859 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest.autoMockOff();
-
-const Promise = require('promise');
-
-jest
-  .mock('fs');
-
-var DependencyGraph = require('../index');
-var fs = require('fs');
-
-describe('DependencyGraph', function() {
-  let defaults;
-
-  function getOrderedDependenciesAsJSON(dgraph, entry, platform) {
-    return dgraph.getDependencies(entry, platform)
-      .then(response => response.finalize())
-      .then(({ dependencies }) => Promise.all(dependencies.map(dep => Promise.all([
-        dep.getName(),
-        dep.getDependencies(),
-      ]).then(([name, moduleDependencies]) => ({
-        path: dep.path,
-        isJSON: dep.isJSON(),
-        isAsset: dep.isAsset(),
-        isAsset_DEPRECATED: dep.isAsset_DEPRECATED(),
-        isPolyfill: dep.isPolyfill(),
-        resolution: dep.resolution,
-        id: name,
-        dependencies: moduleDependencies,
-      })))
-    ));
-  }
-
-  beforeEach(function() {
-    const fileWatcher = {
-      on: function() {
-        return this;
-      },
-      isWatchman: () => Promise.resolve(false),
-    };
-
-    const Cache = jest.genMockFn();
-    Cache.prototype.get = jest.genMockFn().mockImplementation(
-      (filepath, field, cb) => cb(filepath)
-    );
-    Cache.prototype.invalidate = jest.genMockFn();
-    Cache.prototype.end = jest.genMockFn();
-
-    defaults = {
-      assetExts: ['png', 'jpg'],
-      cache: new Cache(),
-      fileWatcher,
-      providesModuleNodeModules: [
-        'haste-fbjs',
-        'react-haste',
-        'react-native',
-        // Parse requires AsyncStorage. They will
-        // change that to require('react-native') which
-        // should work after this release and we can
-        // remove it from here.
-        'parse',
-      ],
-      platforms: ['ios', 'android'],
-    };
-  });
-
-  describe('get sync dependencies', function() {
-    pit('should get dependencies', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("a")',
-          ].join('\n'),
-          'a.js': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'a',
-              path: '/root/a.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should get dependencies with the correct extensions', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("a")',
-          ].join('\n'),
-          'a.js': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-          ].join('\n'),
-          'a.js.orig': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'a',
-              path: '/root/a.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should get json dependencies', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'package.json': JSON.stringify({
-            name: 'package',
-          }),
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./a.json")',
-            'require("./b")',
-          ].join('\n'),
-          'a.json': JSON.stringify({}),
-          'b.json': JSON.stringify({}),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['./a.json', './b'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'package/a.json',
-              isJSON: true,
-              path: '/root/a.json',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'package/b.json',
-              isJSON: true,
-              path: '/root/b.json',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should get package json as a dep', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'package.json': JSON.stringify({
-            name: 'package',
-          }),
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./package.json")',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['./package.json'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'package/package.json',
-              isJSON: true,
-              path: '/root/package.json',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should get dependencies with deprecated assets', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("image!a")',
-          ].join('\n'),
-          'imgs': {
-            'a.png': '',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        assetRoots_DEPRECATED: ['/root/imgs'],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['image!a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'image!a',
-              path: '/root/imgs/a.png',
-              dependencies: [],
-              isAsset_DEPRECATED: true,
-              resolution: 1,
-              isAsset: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-          ]);
-      });
-    });
-
-    pit('should get dependencies with relative assets', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./imgs/a.png")',
-          ].join('\n'),
-          'imgs': {
-            'a.png': '',
-          },
-          'package.json': JSON.stringify({
-            name: 'rootPackage',
-          }),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['./imgs/a.png'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'rootPackage/imgs/a.png',
-              path: '/root/imgs/a.png',
-              dependencies: [],
-              isAsset: true,
-              resolution: 1,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-          ]);
-      });
-    });
-
-    pit('should get dependencies with assets and resolution', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./imgs/a.png");',
-            'require("./imgs/b.png");',
-            'require("./imgs/c.png");',
-          ].join('\n'),
-          'imgs': {
-            'a@1.5x.png': '',
-            'b@.7x.png': '',
-            'c.png': '',
-            'c@2x.png': '',
-          },
-          'package.json': JSON.stringify({
-            name: 'rootPackage',
-          }),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: [
-                './imgs/a.png',
-                './imgs/b.png',
-                './imgs/c.png',
-              ],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'rootPackage/imgs/a.png',
-              path: '/root/imgs/a@1.5x.png',
-              resolution: 1.5,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-            {
-              id: 'rootPackage/imgs/b.png',
-              path: '/root/imgs/b@.7x.png',
-              resolution: 0.7,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-            {
-              id: 'rootPackage/imgs/c.png',
-              path: '/root/imgs/c.png',
-              resolution: 1,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-          ]);
-      });
-    });
-
-    pit('should respect platform extension in assets', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./imgs/a.png");',
-            'require("./imgs/b.png");',
-            'require("./imgs/c.png");',
-          ].join('\n'),
-          'imgs': {
-            'a@1.5x.ios.png': '',
-            'b@.7x.ios.png': '',
-            'c.ios.png': '',
-            'c@2x.ios.png': '',
-          },
-          'package.json': JSON.stringify({
-            name: 'rootPackage',
-          }),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', 'ios').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: [
-                './imgs/a.png',
-                './imgs/b.png',
-                './imgs/c.png',
-              ],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'rootPackage/imgs/a.png',
-              path: '/root/imgs/a@1.5x.ios.png',
-              resolution: 1.5,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-            {
-              id: 'rootPackage/imgs/b.png',
-              path: '/root/imgs/b@.7x.ios.png',
-              resolution: 0.7,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-            {
-              id: 'rootPackage/imgs/c.png',
-              path: '/root/imgs/c.ios.png',
-              resolution: 1,
-              dependencies: [],
-              isAsset: true,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-          ]);
-      });
-    });
-
-    pit('Deprecated and relative assets can live together', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./imgs/a.png")',
-            'require("image!a")',
-          ].join('\n'),
-          'imgs': {
-            'a.png': '',
-          },
-          'package.json': JSON.stringify({
-            name: 'rootPackage',
-          }),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        assetRoots_DEPRECATED: ['/root/imgs'],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['./imgs/a.png', 'image!a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'rootPackage/imgs/a.png',
-              path: '/root/imgs/a.png',
-              dependencies: [],
-              isAsset: true,
-              resolution: 1,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-            {
-              id: 'image!a',
-              path: '/root/imgs/a.png',
-              dependencies: [],
-              isAsset_DEPRECATED: true,
-              resolution: 1,
-              isAsset: false,
-              isJSON: false,
-              isPolyfill: false,
-            },
-          ]);
-      });
-    });
-
-    pit('should get recursive dependencies', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("a")',
-          ].join('\n'),
-          'a.js': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-            'require("index")',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'a',
-              path: '/root/a.js',
-              dependencies: ['index'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/main.js',
-              path: '/root/aPackage/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage/")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage/'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/main.js',
-              path: '/root/aPackage/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with packages with a dot in the name', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("sha.js")',
-            'require("x.y.z")',
-          ].join('\n'),
-          'sha.js': {
-            'package.json': JSON.stringify({
-              name: 'sha.js',
-              main: 'main.js',
-            }),
-            'main.js': 'lol',
-          },
-          'x.y.z': {
-            'package.json': JSON.stringify({
-              name: 'x.y.z',
-              main: 'main.js',
-            }),
-            'main.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['sha.js', 'x.y.z'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'sha.js/main.js',
-              path: '/root/sha.js/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'x.y.z/main.js',
-              path: '/root/x.y.z/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should default main package to index.js', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'require("aPackage")',
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-            }),
-            'index.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: '/root/index.js',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/index.js',
-              path: '/root/aPackage/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should resolve using alternative ids', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'require("aPackage")',
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-            }),
-            'index.js': [
-              '/**',
-              ' * @providesModule EpicModule',
-              ' */',
-            ].join('\n'),
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: '/root/index.js',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'EpicModule',
-              path: '/root/aPackage/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should default use index.js if main is a dir', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'require("aPackage")',
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'lib',
-            }),
-            lib: {
-              'index.js': 'lol',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: '/root/index.js',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/lib/index.js',
-              path: '/root/aPackage/lib/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should resolve require to index if it is a dir', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'package.json': JSON.stringify({
-            name: 'test',
-          }),
-          'index.js': 'require("./lib/")',
-          lib: {
-            'index.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'test/index.js',
-              path: '/root/index.js',
-              dependencies: ['./lib/'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'test/lib/index.js',
-              path: '/root/lib/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should resolve require to main if it is a dir w/ a package.json', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'package.json': JSON.stringify({
-            name: 'test',
-          }),
-          'index.js': 'require("./lib/")',
-          lib: {
-            'package.json': JSON.stringify({
-              'main': 'main.js',
-            }),
-            'index.js': 'lol',
-            'main.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'test/index.js',
-              path: '/root/index.js',
-              dependencies: ['./lib/'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: '/root/lib/main.js',
-              path: '/root/lib/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should ignore malformed packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': 'lol',
-            'main.js': 'lol',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should fatal on multiple modules with the same name', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-          ].join('\n'),
-          'b.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      const _exit = process.exit;
-      const _error = console.error;
-
-      process.exit = jest.genMockFn();
-      console.error = jest.genMockFn();
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return dgraph.load().catch(() => {
-        expect(process.exit).toBeCalledWith(1);
-        expect(console.error).toBeCalled();
-        process.exit = _exit;
-        console.error = _error;
-      });
-    });
-
-    pit('should be forgiving with missing requires', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("lolomg")',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['lolomg'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with packages with subdirs', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage/subdir/lolynot")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'lol',
-            'subdir': {
-              'lolynot.js': 'lolynot',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage/subdir/lolynot'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/subdir/lolynot.js',
-              path: '/root/aPackage/subdir/lolynot.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with packages with symlinked subdirs', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'symlinkedPackage': {
-          'package.json': JSON.stringify({
-            name: 'aPackage',
-            main: 'main.js',
-          }),
-          'main.js': 'lol',
-          'subdir': {
-            'lolynot.js': 'lolynot',
-          },
-        },
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage/subdir/lolynot")',
-          ].join('\n'),
-          'aPackage': { SYMLINK: '/symlinkedPackage' },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage/subdir/lolynot'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/subdir/lolynot.js',
-              path: '/root/aPackage/subdir/lolynot.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with relative modules in packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'require("./subdir/lolynot")',
-            'subdir': {
-              'lolynot.js': 'require("../other")',
-            },
-            'other.js': 'some code',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/main.js',
-              path: '/root/aPackage/main.js',
-              dependencies: ['./subdir/lolynot'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/subdir/lolynot.js',
-              path: '/root/aPackage/subdir/lolynot.js',
-              dependencies: ['../other'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/other.js',
-              path: '/root/aPackage/other.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should support simple browser field in packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-              browser: 'client.js',
-            }),
-            'main.js': 'some other code',
-            'client.js': 'some code',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/client.js',
-              path: '/root/aPackage/client.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should support browser field in packages w/o .js ext', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-              browser: 'client',
-            }),
-            'main.js': 'some other code',
-            'client.js': 'some code',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/client.js',
-              path: '/root/aPackage/client.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should support mapping main in browser field json', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: './main.js',
-              browser: {
-                './main.js': './client.js',
-              },
-            }),
-            'main.js': 'some other code',
-            'client.js': 'some code',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'aPackage/client.js',
-              path: '/root/aPackage/client.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work do correct browser mapping w/o js ext', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: './main.js',
-              browser: {
-                './main': './client.js',
-              },
-            }),
-            'main.js': 'some other code',
-            'client.js': 'some code',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'aPackage/client.js',
-              path: '/root/aPackage/client.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should support browser mapping of files', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: './main.js',
-              browser: {
-                './main': './client.js',
-                './node.js': './not-node.js',
-                './not-browser': './browser.js',
-                './dir/server.js': './dir/client',
-                './hello.js': './bye.js',
-              },
-            }),
-            'main.js': 'some other code',
-            'client.js': 'require("./node")\nrequire("./dir/server.js")',
-            'not-node.js': 'require("./not-browser")',
-            'not-browser.js': 'require("./dir/server")',
-            'browser.js': 'some browser code',
-            'dir': {
-              'server.js': 'some node code',
-              'client.js': 'require("../hello")',
-            },
-            'hello.js': 'hello',
-            'bye.js': 'bye',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            { id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'aPackage/client.js',
-              path: '/root/aPackage/client.js',
-              dependencies: ['./node', './dir/server.js'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'aPackage/not-node.js',
-              path: '/root/aPackage/not-node.js',
-              dependencies: ['./not-browser'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'aPackage/browser.js',
-              path: '/root/aPackage/browser.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/dir/client.js',
-              path: '/root/aPackage/dir/client.js',
-              dependencies: ['../hello'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'aPackage/bye.js',
-              path: '/root/aPackage/bye.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should support browser mapping for packages', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              browser: {
-                'node-package': 'browser-package',
-              },
-            }),
-            'index.js': 'require("node-package")',
-            'node-package': {
-              'package.json': JSON.stringify({
-                'name': 'node-package',
-              }),
-              'index.js': 'some node code',
-            },
-            'browser-package': {
-              'package.json': JSON.stringify({
-                'name': 'browser-package',
-              }),
-              'index.js': 'some browser code',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            { id: 'index',
-              path: '/root/index.js',
-              dependencies: ['aPackage'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'aPackage/index.js',
-              path: '/root/aPackage/index.js',
-              dependencies: ['node-package'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'browser-package/index.js',
-              path: '/root/aPackage/browser-package/index.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-  });
-
-  describe('node_modules', function() {
-    pit('should work with nested node_modules', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-            'require("bar");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'require("bar");\nfoo module',
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                  }),
-                  'main.js': 'bar 1 module',
-                },
-              },
-            },
-            'bar': {
-              'package.json': JSON.stringify({
-                name: 'bar',
-                main: 'main.js',
-              }),
-              'main.js': 'bar 2 module',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo', 'bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/main.js',
-              path: '/root/node_modules/foo/main.js',
-              dependencies: ['bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main.js',
-              path: '/root/node_modules/foo/node_modules/bar/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main.js',
-              path: '/root/node_modules/bar/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('platform should work with node_modules', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.ios.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-            'require("bar");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-              }),
-              'index.ios.js': '',
-            },
-            'bar': {
-              'package.json': JSON.stringify({
-                name: 'bar',
-                main: 'main',
-              }),
-              'main.ios.js': '',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.ios.js',
-              dependencies: ['foo', 'bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/index.ios.js',
-              path: '/root/node_modules/foo/index.ios.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main.ios.js',
-              path: '/root/node_modules/bar/main.ios.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('nested node_modules with specific paths', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-            'require("bar/");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'require("bar/lol");\nfoo module',
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                  }),
-                  'main.js': 'bar 1 module',
-                  'lol.js': '',
-                },
-              },
-            },
-            'bar': {
-              'package.json': JSON.stringify({
-                name: 'bar',
-                main: 'main.js',
-              }),
-              'main.js': 'bar 2 module',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo', 'bar/'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/main.js',
-              path: '/root/node_modules/foo/main.js',
-              dependencies: ['bar/lol'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/lol.js',
-              path: '/root/node_modules/foo/node_modules/bar/lol.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main.js',
-              path: '/root/node_modules/bar/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('nested node_modules with browser field', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-            'require("bar");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'require("bar/lol");\nfoo module',
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                    browser: {
-                      './lol': './wow',
-                    },
-                  }),
-                  'main.js': 'bar 1 module',
-                  'lol.js': '',
-                  'wow.js': '',
-                },
-              },
-            },
-            'bar': {
-              'package.json': JSON.stringify({
-                name: 'bar',
-                browser: './main2',
-              }),
-              'main2.js': 'bar 2 module',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo', 'bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/main.js',
-              path: '/root/node_modules/foo/main.js',
-              dependencies: ['bar/lol'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/lol.js',
-              path: '/root/node_modules/foo/node_modules/bar/lol.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main2.js',
-              path: '/root/node_modules/bar/main2.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('node_modules should support multi level', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("bar");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': '',
-            },
-          },
-          'path': {
-            'to': {
-              'bar.js': [
-                '/**',
-                ' * @providesModule bar',
-                ' */',
-                'require("foo")',
-              ].join('\n'),
-            },
-            'node_modules': {},
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar',
-              path: '/root/path/to/bar.js',
-              dependencies: ['foo'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/main.js',
-              path: '/root/node_modules/foo/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should selectively ignore providesModule in node_modules', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("shouldWork");',
-            'require("dontWork");',
-            'require("wontWork");',
-          ].join('\n'),
-          'node_modules': {
-            'react-haste': {
-              'package.json': JSON.stringify({
-                name: 'react-haste',
-                main: 'main.js',
-              }),
-              'main.js': [
-                '/**',
-                ' * @providesModule shouldWork',
-                ' */',
-                'require("submodule");',
-              ].join('\n'),
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                  }),
-                  'main.js':[
-                    '/**',
-                    ' * @providesModule dontWork',
-                    ' */',
-                    'hi();',
-                  ].join('\n'),
-                },
-                'submodule': {
-                  'package.json': JSON.stringify({
-                    name: 'submodule',
-                    main: 'main.js',
-                  }),
-                  'main.js': 'log()',
-                },
-              },
-            },
-            'ember': {
-              'package.json': JSON.stringify({
-                name: 'ember',
-                main: 'main.js',
-              }),
-              'main.js':[
-                '/**',
-                ' * @providesModule wontWork',
-                ' */',
-                'hi();',
-              ].join('\n'),
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['shouldWork', 'dontWork', 'wontWork'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'shouldWork',
-              path: '/root/node_modules/react-haste/main.js',
-              dependencies: ['submodule'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'submodule/main.js',
-              path: '/root/node_modules/react-haste/node_modules/submodule/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should not be confused by prev occuring whitelisted names', function() {
-      var root = '/react-haste';
-      fs.__setMockFilesystem({
-        'react-haste': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("shouldWork");',
-          ].join('\n'),
-          'node_modules': {
-            'react-haste': {
-              'package.json': JSON.stringify({
-                name: 'react-haste',
-                main: 'main.js',
-              }),
-              'main.js': [
-                '/**',
-                ' * @providesModule shouldWork',
-                ' */',
-              ].join('\n'),
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/react-haste/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/react-haste/index.js',
-              dependencies: ['shouldWork'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'shouldWork',
-              path: '/react-haste/node_modules/react-haste/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-
-    pit('should ignore modules it cant find (assumes own require system)', function() {
-      // For example SourceMap.js implements it's own require system.
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo/lol");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'foo module',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo/lol'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with node packages with a .js in the name', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("sha.js")',
-          ].join('\n'),
-          'node_modules': {
-            'sha.js': {
-              'package.json': JSON.stringify({
-                name: 'sha.js',
-                main: 'main.js',
-              }),
-              'main.js': 'lol',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['sha.js'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'sha.js/main.js',
-              path: '/root/node_modules/sha.js/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with multiple platforms (haste)', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.ios.js': `
-            /**
-             * @providesModule index
-             */
-             require('a');
-          `,
-          'a.ios.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-          'a.android.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-          'a.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.ios.js',
-              dependencies: ['a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'a',
-              path: '/root/a.ios.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should pick the generic file', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.ios.js': `
-            /**
-             * @providesModule index
-             */
-             require('a');
-          `,
-          'a.android.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-          'a.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-          'a.web.js': `
-            /**
-             * @providesModule a
-             */
-          `,
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.ios.js',
-              dependencies: ['a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'a',
-              path: '/root/a.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should work with multiple platforms (node)', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.ios.js': `
-            /**
-             * @providesModule index
-             */
-             require('./a');
-          `,
-          'a.ios.js': '',
-          'a.android.js': '',
-          'a.js': '',
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.ios.js',
-              dependencies: ['./a'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: '/root/a.ios.js',
-              path: '/root/a.ios.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should require package.json', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo/package.json");',
-            'require("bar");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-            },
-            'bar': {
-              'package.json': JSON.stringify({
-                name: 'bar',
-                main: 'main.js',
-              }),
-              'main.js': 'require("./package.json")',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(deps => {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo/package.json', 'bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'foo/package.json',
-              path: '/root/node_modules/foo/package.json',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: true,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/main.js',
-              path: '/root/node_modules/bar/main.js',
-              dependencies: ['./package.json'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'bar/package.json',
-              path: '/root/node_modules/bar/package.json',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: true,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-
-          ]);
-      });
-    });
-  });
-
-  describe('file watch updating', function() {
-    var triggerFileChange;
-    var mockStat = {
-      isDirectory: () => false,
-    };
-
-    beforeEach(function() {
-      var callbacks = [];
-      triggerFileChange = (...args) =>
-        callbacks.map(callback => callback(...args));
-
-      defaults.fileWatcher = {
-        on: function(eventType, callback) {
-          if (eventType !== 'all') {
-            throw new Error('Can only handle "all" event in watcher.');
-          }
-          callbacks.push(callback);
-          return this;
-        },
-        isWatchman: () => Promise.resolve(false),
-      };
-    });
-
-    pit('updates module dependencies', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root['index.js'] =
-          filesystem.root['index.js'].replace('require("foo")', '');
-        triggerFileChange('change', 'index.js', root, mockStat);
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates module dependencies on file change', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root['index.js'] =
-          filesystem.root['index.js'].replace('require("foo")', '');
-        triggerFileChange('change', 'index.js', root, mockStat);
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates module dependencies on file delete', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        delete filesystem.root.foo;
-        triggerFileChange('delete', 'foo.js', root);
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage', 'foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates module dependencies on file add', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root['bar.js'] = [
-          '/**',
-          ' * @providesModule bar',
-          ' */',
-          'require("foo")',
-        ].join('\n');
-        triggerFileChange('add', 'bar.js', root, mockStat);
-
-        filesystem.root.aPackage['main.js'] = 'require("bar")';
-        triggerFileChange('change', 'aPackage/main.js', root, mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage', 'foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: ['bar'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              {
-                id: 'bar',
-                path: '/root/bar.js',
-                dependencies: ['foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'foo',
-                path: '/root/foo.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates module dependencies on deprecated asset add', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("image!foo")',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        assetRoots_DEPRECATED: [root],
-        assetExts: ['png'],
-      });
-
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['image!foo'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-
-        filesystem.root['foo.png'] = '';
-        triggerFileChange('add', 'foo.png', root, mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
-          expect(deps2)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['image!foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'image!foo',
-                path: '/root/foo.png',
-                dependencies: [],
-                isAsset_DEPRECATED: true,
-                resolution: 1,
-                isAsset: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates module dependencies on relative asset add', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("./foo.png")',
-          ].join('\n'),
-          'package.json': JSON.stringify({
-            name: 'aPackage',
-          }),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        assetExts: ['png'],
-      });
-
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            { id: 'index',
-              path: '/root/index.js',
-              dependencies: ['./foo.png'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-
-        filesystem.root['foo.png'] = '';
-        triggerFileChange('add', 'foo.png', root, mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
-          expect(deps2)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['./foo.png'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'aPackage/foo.png',
-                path: '/root/foo.png',
-                dependencies: [],
-                isAsset: true,
-                resolution: 1,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('runs changes through ignore filter', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        ignoreFilePath: function(filePath) {
-          if (filePath === '/root/bar.js') {
-            return true;
-          }
-          return false;
-        },
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root['bar.js'] = [
-          '/**',
-          ' * @providesModule bar',
-          ' */',
-          'require("foo")',
-        ].join('\n');
-        triggerFileChange('add', 'bar.js', root, mockStat);
-
-        filesystem.root.aPackage['main.js'] = 'require("bar")';
-        triggerFileChange('change', 'aPackage/main.js', root, mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage', 'foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: ['bar'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'foo',
-                path: '/root/foo.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('should ignore directory updates', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-            'require("foo")',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        triggerFileChange('change', 'aPackage', '/root', {
-          isDirectory: () => true,
-        });
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage', 'foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'aPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'foo',
-                path: '/root/foo.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('updates package.json', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage');
-        triggerFileChange('change', 'index.js', root, mockStat);
-
-        filesystem.root.aPackage['package.json'] = JSON.stringify({
-          name: 'bPackage',
-          main: 'main.js',
-        });
-        triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['bPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'bPackage/main.js',
-                path: '/root/aPackage/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('changes to browser field', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-            'browser.js': 'browser',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root.aPackage['package.json'] = JSON.stringify({
-          name: 'aPackage',
-          main: 'main.js',
-          browser: 'browser.js',
-        });
-        triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'aPackage/browser.js',
-                path: '/root/aPackage/browser.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('removes old package from cache', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              main: 'main.js',
-            }),
-            'main.js': 'main',
-            'browser.js': 'browser',
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        filesystem.root.aPackage['package.json'] = JSON.stringify({
-          name: 'bPackage',
-          main: 'main.js',
-        });
-        triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-          expect(deps)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['aPackage'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('should update node package changes', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'require("bar");\nfoo module',
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                  }),
-                  'main.js': 'bar 1 module',
-                },
-              },
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: ['foo'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'foo/main.js',
-              path: '/root/node_modules/foo/main.js',
-              dependencies: ['bar'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'bar/main.js',
-              path: '/root/node_modules/foo/node_modules/bar/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-
-        filesystem.root.node_modules.foo['main.js'] = 'lol';
-        triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
-          expect(deps2)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'foo/main.js',
-                path: '/root/node_modules/foo/main.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('should update node package main changes', function() {
-      var root = '/root';
-      var filesystem = fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("foo");',
-          ].join('\n'),
-          'node_modules': {
-            'foo': {
-              'package.json': JSON.stringify({
-                name: 'foo',
-                main: 'main.js',
-              }),
-              'main.js': 'foo module',
-              'browser.js': 'foo module',
-            },
-          },
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        filesystem.root.node_modules.foo['package.json'] = JSON.stringify({
-          name: 'foo',
-          main: 'main.js',
-          browser: 'browser.js',
-        });
-        triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat);
-
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
-          expect(deps2)
-            .toEqual([
-              {
-                id: 'index',
-                path: '/root/index.js',
-                dependencies: ['foo'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-              {
-                id: 'foo/browser.js',
-                path: '/root/node_modules/foo/browser.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-                resolveDependency: undefined,
-              },
-            ]);
-        });
-      });
-    });
-  });
-
-  describe('getAsyncDependencies', () => {
-    pit('should get dependencies', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'System.import("a")',
-          ].join('\n'),
-          'a.js': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return dgraph.getDependencies('/root/index.js')
-        .then(response => response.finalize())
-        .then(({ asyncDependencies }) => {
-          expect(asyncDependencies).toEqual([
-            ['/root/a.js'],
-          ]);
-        });
-    });
-  });
-
-  describe('Extensions', () => {
-    pit('supports custom file extensions', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.jsx': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("a")',
-          ].join('\n'),
-          'a.coffee': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-          ].join('\n'),
-          'X.js': '',
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        extensions: ['jsx', 'coffee'],
-      });
-
-      return dgraph.matchFilesByPattern('.*')
-        .then(files => {
-          expect(files).toEqual([
-            '/root/index.jsx', '/root/a.coffee',
-          ]);
-        })
-        .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx'))
-        .then(deps => {
-          expect(deps).toEqual([
-            {
-              dependencies: ['a'],
-              id: 'index',
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              path: '/root/index.jsx',
-              resolution: undefined,
-            },
-            {
-              dependencies: [],
-              id: 'a',
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              path: '/root/a.coffee',
-              resolution: undefined,
-            },
-          ]);
-        });
-    });
-  });
-
-  describe('Mocks', () => {
-    pit('resolves to null if mocksPattern is not specified', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          '__mocks': {
-            'A.js': '',
-          },
-          'index.js': '',
-        },
-      });
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return dgraph.getDependencies('/root/index.js')
-        .then(response => response.finalize())
-        .then(response => {
-          expect(response.mocks).toBe(null);
-        });
-    });
-
-    pit('retrieves a list of all mocks in the system', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          '__mocks__': {
-            'A.js': '',
-            'b.js': '',
-          },
-          'b.js': [
-            '/**',
-            ' * @providesModule b',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/,
-      });
-
-      return dgraph.getDependencies('/root/b.js')
-        .then(response => response.finalize())
-        .then(response => {
-          expect(response.mocks).toEqual({
-            A: '/root/__mocks__/A.js',
-            b: '/root/__mocks__/b.js',
-          });
-        });
-    });
-
-    pit('adds mocks as a dependency of their actual module', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          '__mocks__': {
-            'A.js': [
-              'require("b");',
-            ].join('\n'),
-            'b.js': '',
-          },
-          'A.js': [
-            '/**',
-            ' * @providesModule A',
-            ' */',
-            'require("foo");',
-          ].join('\n'),
-          'foo.js': [
-            '/**',
-            ' * @providesModule foo',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        mocksPattern: /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/,
-      });
-
-      return getOrderedDependenciesAsJSON(dgraph, '/root/A.js')
-        .then(deps => {
-          expect(deps).toEqual([
-            {
-              path: '/root/A.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: 'A',
-              dependencies: ['foo', 'A'],
-            },
-            {
-              path: '/root/foo.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: 'foo',
-              dependencies: [],
-            },
-            {
-              path: '/root/__mocks__/A.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: '/root/__mocks__/A.js',
-              dependencies: ['b'],
-            },
-          ]);
-        });
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js
deleted file mode 100644
index 131f09d1f6f2..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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.
- */
-
-'use strict';
-
-var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
-
-var ltrimRe = /^\s*/;
-/**
- * @param {String} contents
- * @return {String}
- */
-function extract(contents) {
-  var match = contents.match(docblockRe);
-  if (match) {
-    return match[0].replace(ltrimRe, '') || '';
-  }
-  return '';
-}
-
-
-var commentStartRe = /^\/\*\*?/;
-var commentEndRe = /\*\/$/;
-var wsRe = /[\t ]+/g;
-var stringStartRe = /(\r?\n|^) *\*/g;
-var multilineRe =
-      /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
-var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
-
-/**
- * @param {String} contents
- * @return {Array}
- */
-function parse(docblock) {
-  docblock = docblock
-    .replace(commentStartRe, '')
-    .replace(commentEndRe, '')
-    .replace(wsRe, ' ')
-    .replace(stringStartRe, '$1');
-
-  // Normalize multi-line directives
-  var prev = '';
-  while (prev !== docblock) {
-    prev = docblock;
-    docblock = docblock.replace(multilineRe, '\n$1 $2\n');
-  }
-  docblock = docblock.trim();
-
-  var result = [];
-  var match;
-  while ((match = propertyRe.exec(docblock))) {
-    result.push([match[1], match[2]]);
-  }
-
-  return result;
-}
-
-/**
- * Same as parse but returns an object of prop: value instead of array of paris
- * If a property appers more than once the last one will be returned
- *
- * @param {String} contents
- * @return {Object}
- */
-function parseAsObject(docblock) {
-  var pairs = parse(docblock);
-  var result = {};
-  for (var i = 0; i < pairs.length; i++) {
-    result[pairs[i][0]] = pairs[i][1];
-  }
-  return result;
-}
-
-
-exports.extract = extract;
-exports.parse = parse;
-exports.parseAsObject = parseAsObject;
diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js
deleted file mode 100644
index 9f87dcbc2ebf..000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js
+++ /dev/null
@@ -1,239 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const Fastfs = require('../fastfs');
-const ModuleCache = require('../ModuleCache');
-const Promise = require('promise');
-const crawl = require('../crawlers');
-const getPlatformExtension = require('../lib/getPlatformExtension');
-const isAbsolutePath = require('absolute-path');
-const path = require('path');
-const util = require('util');
-const Helpers = require('./Helpers');
-const ResolutionRequest = require('./ResolutionRequest');
-const ResolutionResponse = require('./ResolutionResponse');
-const HasteMap = require('./HasteMap');
-const DeprecatedAssetMap = require('./DeprecatedAssetMap');
-
-const defaultActivity = {
-  startEvent: () => {},
-  endEvent: () => {},
-};
-
-class DependencyGraph {
-  constructor({
-    activity,
-    roots,
-    ignoreFilePath,
-    fileWatcher,
-    assetRoots_DEPRECATED,
-    assetExts,
-    providesModuleNodeModules,
-    platforms,
-    cache,
-    extensions,
-    mocksPattern,
-    extractRequires,
-  }) {
-    this._opts = {
-      activity: activity || defaultActivity,
-      roots,
-      ignoreFilePath: ignoreFilePath || (() => {}),
-      fileWatcher,
-      assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
-      assetExts: assetExts || [],
-      providesModuleNodeModules,
-      platforms: platforms || [],
-      cache,
-      extensions: extensions || ['js', 'json'],
-      mocksPattern,
-      extractRequires,
-    };
-    this._cache = this._opts.cache;
-    this._helpers = new Helpers(this._opts);
-    this.load().catch((err) => {
-      // This only happens at initialization. Live errors are easier to recover from.
-      console.error('Error building DependencyGraph:\n', err.stack);
-      process.exit(1);
-    });
-  }
-
-  load() {
-    if (this._loading) {
-      return this._loading;
-    }
-
-    const {activity} = this._opts;
-    const depGraphActivity = activity.startEvent('Building Dependency Graph');
-    const crawlActivity = activity.startEvent('Crawling File System');
-    const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
-    this._crawling = crawl(allRoots, {
-      ignore: this._opts.ignoreFilePath,
-      exts: this._opts.extensions.concat(this._opts.assetExts),
-      fileWatcher: this._opts.fileWatcher,
-    });
-    this._crawling.then((files) => activity.endEvent(crawlActivity));
-
-    this._fastfs = new Fastfs(
-      'JavaScript',
-      this._opts.roots,
-      this._opts.fileWatcher,
-      {
-        ignore: this._opts.ignoreFilePath,
-        crawling: this._crawling,
-        activity: activity,
-      }
-    );
-
-    this._fastfs.on('change', this._processFileChange.bind(this));
-
-    this._moduleCache = new ModuleCache(
-      this._fastfs,
-      this._cache,
-      this._opts.extractRequires
-    );
-
-    this._hasteMap = new HasteMap({
-      fastfs: this._fastfs,
-      extensions: this._opts.extensions,
-      moduleCache: this._moduleCache,
-      assetExts: this._opts.exts,
-      helpers: this._helpers,
-    });
-
-    this._deprecatedAssetMap = new DeprecatedAssetMap({
-      fsCrawl: this._crawling,
-      roots: this._opts.assetRoots_DEPRECATED,
-      helpers: this._helpers,
-      fileWatcher: this._opts.fileWatcher,
-      ignoreFilePath: this._opts.ignoreFilePath,
-      assetExts: this._opts.assetExts,
-      activity: this._opts.activity,
-    });
-
-    this._loading = Promise.all([
-      this._fastfs.build()
-        .then(() => {
-          const hasteActivity = activity.startEvent('Building Haste Map');
-          return this._hasteMap.build().then(() => activity.endEvent(hasteActivity));
-        }),
-      this._deprecatedAssetMap.build(),
-    ]).then(() =>
-      activity.endEvent(depGraphActivity)
-    );
-
-    return this._loading;
-  }
-
-  getDependencies(entryPath, platform) {
-    return this.load().then(() => {
-      platform = this._getRequestPlatform(entryPath, platform);
-      const absPath = this._getAbsolutePath(entryPath);
-      const req = new ResolutionRequest({
-        platform,
-        entryPath: absPath,
-        deprecatedAssetMap: this._deprecatedAssetMap,
-        hasteMap: this._hasteMap,
-        helpers: this._helpers,
-        moduleCache: this._moduleCache,
-        fastfs: this._fastfs,
-      });
-
-      const response = new ResolutionResponse();
-
-      return Promise.all([
-        req.getOrderedDependencies(response, this._opts.mocksPattern),
-        req.getAsyncDependencies(response),
-      ]).then(() => response);
-    });
-  }
-
-  matchFilesByPattern(pattern) {
-    return this.load().then(() => this._fastfs.matchFilesByPattern(pattern));
-  }
-
-  _getRequestPlatform(entryPath, platform) {
-    if (platform == null) {
-      platform = getPlatformExtension(entryPath);
-      if (platform == null || this._opts.platforms.indexOf(platform) === -1) {
-        platform = null;
-      }
-    } else if (this._opts.platforms.indexOf(platform) === -1) {
-      throw new Error('Unrecognized platform: ' + platform);
-    }
-    return platform;
-  }
-
-  _getAbsolutePath(filePath) {
-    if (isAbsolutePath(filePath)) {
-      return path.resolve(filePath);
-    }
-
-    for (let i = 0; i < this._opts.roots.length; i++) {
-      const root = this._opts.roots[i];
-      const potentialAbsPath = path.join(root, filePath);
-      if (this._fastfs.fileExists(potentialAbsPath)) {
-        return path.resolve(potentialAbsPath);
-      }
-    }
-
-    throw new NotFoundError(
-      'Cannot find entry file %s in any of the roots: %j',
-      filePath,
-      this._opts.roots
-    );
-  }
-
-  _processFileChange(type, filePath, root, fstat) {
-    const absPath = path.join(root, filePath);
-    if (fstat && fstat.isDirectory() ||
-        this._opts.ignoreFilePath(absPath) ||
-        this._helpers.isNodeModulesDir(absPath)) {
-      return;
-    }
-
-    // Ok, this is some tricky promise code. Our requirements are:
-    // * we need to report back failures
-    // * failures shouldn't block recovery
-    // * Errors can leave `hasteMap` in an incorrect state, and we need to rebuild
-    // After we process a file change we record any errors which will also be
-    // reported via the next request. On the next file change, we'll see that
-    // we are in an error state and we should decide to do a full rebuild.
-    this._loading = this._loading.finally(() => {
-      if (this._hasteMapError) {
-        console.warn(
-          'Rebuilding haste map to recover from error:\n' +
-          this._hasteMapError.stack
-        );
-        this._hasteMapError = null;
-
-        // Rebuild the entire map if last change resulted in an error.
-        this._loading = this._hasteMap.build();
-      } else {
-        this._loading = this._hasteMap.processFileChange(type, absPath);
-        this._loading.catch((e) => this._hasteMapError = e);
-      }
-      return this._loading;
-    });
-  }
-
-}
-
-function NotFoundError() {
-  Error.call(this);
-  Error.captureStackTrace(this, this.constructor);
-  var msg = util.format.apply(util, arguments);
-  this.message = msg;
-  this.type = this.name = 'NotFoundError';
-  this.status = 404;
-}
-util.inherits(NotFoundError, Error);
-
-module.exports = DependencyGraph;
diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js
deleted file mode 100644
index a6112082027f..000000000000
--- a/packager/react-packager/src/DependencyResolver/Module.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const docblock = require('./DependencyGraph/docblock');
-const isAbsolutePath = require('absolute-path');
-const path = require('path');
-const extractRequires = require('./lib/extractRequires');
-
-class Module {
-
-  constructor(file, fastfs, moduleCache, cache, extractor) {
-    if (!isAbsolutePath(file)) {
-      throw new Error('Expected file to be absolute path but got ' + file);
-    }
-
-    this.path = path.resolve(file);
-    this.type = 'Module';
-
-    this._fastfs = fastfs;
-    this._moduleCache = moduleCache;
-    this._cache = cache;
-    this._extractor = extractor;
-  }
-
-  isHaste() {
-    return this._read().then(data => !!data.id);
-  }
-
-  getName() {
-    return this._cache.get(
-      this.path,
-      'name',
-      () => this._read().then(data => {
-        if (data.id) {
-          return data.id;
-        }
-
-        const p = this.getPackage();
-
-        if (!p) {
-          // Name is full path
-          return this.path;
-        }
-
-        return p.getName()
-          .then(name => {
-            if (!name) {
-              return this.path;
-            }
-
-            return path.join(name, path.relative(p.root, this.path)).replace(/\\/g, '/');
-          });
-      })
-    );
-  }
-
-  getPackage() {
-    return this._moduleCache.getPackageForModule(this);
-  }
-
-  getDependencies() {
-    return this._read().then(data => data.dependencies);
-  }
-
-  getAsyncDependencies() {
-    return this._read().then(data => data.asyncDependencies);
-  }
-
-  invalidate() {
-    this._cache.invalidate(this.path);
-  }
-
-  _read() {
-    if (!this._reading) {
-      this._reading = this._fastfs.readFile(this.path).then(content => {
-        const data = {};
-        const moduleDocBlock = docblock.parseAsObject(content);
-        if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
-          data.id = /^(\S*)/.exec(
-            moduleDocBlock.providesModule || moduleDocBlock.provides
-          )[1];
-        }
-
-        // Ignore requires in generated code. An example of this is prebuilt
-        // files like the SourceMap library.
-        if ('extern' in moduleDocBlock) {
-          data.dependencies = [];
-        } else {
-          var dependencies = (this._extractor || extractRequires)(content).deps;
-          data.dependencies = dependencies.sync;
-          data.asyncDependencies = dependencies.async;
-        }
-
-        return data;
-      });
-    }
-
-    return this._reading;
-  }
-
-  hash() {
-    return `Module : ${this.path}`;
-  }
-
-  isJSON() {
-    return path.extname(this.path) === '.json';
-  }
-
-  isAsset() {
-    return false;
-  }
-
-  isPolyfill() {
-    return false;
-  }
-
-  isAsset_DEPRECATED() {
-    return false;
-  }
-
-  toJSON() {
-    return {
-      hash: this.hash(),
-      isJSON: this.isJSON(),
-      isAsset: this.isAsset(),
-      isAsset_DEPRECATED: this.isAsset_DEPRECATED(),
-      type: this.type,
-      path: this.path,
-    };
-  }
-}
-
-module.exports = Module;
diff --git a/packager/react-packager/src/DependencyResolver/ModuleCache.js b/packager/react-packager/src/DependencyResolver/ModuleCache.js
deleted file mode 100644
index a889143972a4..000000000000
--- a/packager/react-packager/src/DependencyResolver/ModuleCache.js
+++ /dev/null
@@ -1,92 +0,0 @@
-'use strict';
-
-const AssetModule = require('./AssetModule');
-const Package = require('./Package');
-const Module = require('./Module');
-const path = require('path');
-
-class ModuleCache {
-
-  constructor(fastfs, cache, extractRequires) {
-    this._moduleCache = Object.create(null);
-    this._packageCache = Object.create(null);
-    this._fastfs = fastfs;
-    this._cache = cache;
-    this._extractRequires = extractRequires;
-    fastfs.on('change', this._processFileChange.bind(this));
-  }
-
-  getModule(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._moduleCache[filePath]) {
-      this._moduleCache[filePath] = new Module(
-        filePath,
-        this._fastfs,
-        this,
-        this._cache,
-        this._extractRequires
-      );
-    }
-    return this._moduleCache[filePath];
-  }
-
-  getAssetModule(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._moduleCache[filePath]) {
-      this._moduleCache[filePath] = new AssetModule(
-        filePath,
-        this._fastfs,
-        this,
-        this._cache,
-      );
-    }
-    return this._moduleCache[filePath];
-  }
-
-  getPackage(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._packageCache[filePath]) {
-      this._packageCache[filePath] = new Package(
-        filePath,
-        this._fastfs,
-        this._cache,
-      );
-    }
-    return this._packageCache[filePath];
-  }
-
-  getPackageForModule(module) {
-    // TODO(amasad): use ES6 Map.
-    if (module.__package) {
-      if (this._packageCache[module.__package]) {
-        return this._packageCache[module.__package];
-      } else {
-        delete module.__package;
-      }
-    }
-
-    const packagePath = this._fastfs.closest(module.path, 'package.json');
-
-    if (!packagePath) {
-      return null;
-    }
-
-    module.__package = packagePath;
-    return this.getPackage(packagePath);
-  }
-
-  _processFileChange(type, filePath, root) {
-    const absPath = path.join(root, filePath);
-
-    if (this._moduleCache[absPath]) {
-      this._moduleCache[absPath].invalidate();
-      delete this._moduleCache[absPath];
-    }
-    if (this._packageCache[absPath]) {
-      this._packageCache[absPath].invalidate();
-      delete this._packageCache[absPath];
-    }
-  }
-}
-
-module.exports = ModuleCache;
diff --git a/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js
deleted file mode 100644
index d45a8923e35e..000000000000
--- a/packager/react-packager/src/DependencyResolver/Package.js
+++ /dev/null
@@ -1,93 +0,0 @@
-'use strict';
-
-const isAbsolutePath = require('absolute-path');
-const path = require('path');
-
-class Package {
-
-  constructor(file, fastfs, cache) {
-    this.path = path.resolve(file);
-    this.root = path.dirname(this.path);
-    this._fastfs = fastfs;
-    this.type = 'Package';
-    this._cache = cache;
-  }
-
-  getMain() {
-    return this._read().then(json => {
-      if (typeof json.browser === 'string') {
-        return path.join(this.root, json.browser);
-      }
-
-      let main = json.main || 'index';
-
-      if (json.browser && typeof json.browser === 'object') {
-        main = json.browser[main] ||
-          json.browser[main + '.js'] ||
-          json.browser[main + '.json'] ||
-          json.browser[main.replace(/(\.js|\.json)$/, '')] ||
-          main;
-      }
-
-      return path.join(this.root, main);
-    });
-  }
-
-  isHaste() {
-    return this._cache.get(this.path, 'package-haste', () =>
-      this._read().then(json => !!json.name)
-    );
-  }
-
-  getName() {
-    return this._cache.get(this.path, 'package-name', () =>
-      this._read().then(json => json.name)
-    );
-  }
-
-  invalidate() {
-    this._cache.invalidate(this.path);
-  }
-
-  redirectRequire(name) {
-    return this._read().then(json => {
-      const {browser} = json;
-
-      if (!browser || typeof browser !== 'object') {
-        return name;
-      }
-
-      if (name[0] !== '/') {
-        return browser[name] || name;
-      }
-
-      if (!isAbsolutePath(name)) {
-        throw new Error(`Expected ${name} to be absolute path`);
-      }
-
-      const relPath = './' + path.relative(this.root, name);
-      const redirect = browser[relPath] ||
-              browser[relPath + '.js'] ||
-              browser[relPath + '.json'];
-      if (redirect) {
-        return path.join(
-          this.root,
-          redirect
-        );
-      }
-
-      return name;
-    });
-  }
-
-  _read() {
-    if (!this._reading) {
-      this._reading = this._fastfs.readFile(this.path)
-        .then(jsonStr => JSON.parse(jsonStr));
-    }
-
-    return this._reading;
-  }
-}
-
-module.exports = Package;
diff --git a/packager/react-packager/src/DependencyResolver/Polyfill.js b/packager/react-packager/src/DependencyResolver/Polyfill.js
deleted file mode 100644
index 752b864b894e..000000000000
--- a/packager/react-packager/src/DependencyResolver/Polyfill.js
+++ /dev/null
@@ -1,38 +0,0 @@
-'use strict';
-
-const Promise = require('promise');
-const Module = require('./Module');
-
-class Polyfill extends Module {
-  constructor({ path, id, dependencies }) {
-    super(path);
-    this._id = id;
-    this._dependencies = dependencies;
-  }
-
-  isHaste() {
-    return Promise.resolve(false);
-  }
-
-  getName() {
-    return Promise.resolve(this._id);
-  }
-
-  getPackage() {
-    return null;
-  }
-
-  getDependencies() {
-    return Promise.resolve(this._dependencies);
-  }
-
-  isJSON() {
-    return false;
-  }
-
-  isPolyfill() {
-    return true;
-  }
-}
-
-module.exports = Polyfill;
diff --git a/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js b/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js
deleted file mode 100644
index 656a1b003807..000000000000
--- a/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest
-  .dontMock('absolute-path')
-  .dontMock('../fastfs')
-  .dontMock('../lib/extractRequires')
-  .dontMock('../lib/replacePatterns')
-  .dontMock('../DependencyGraph/docblock')
-  .dontMock('../Module');
-
-jest
-  .mock('fs');
-
-const Fastfs = require('../fastfs');
-const Module = require('../Module');
-const ModuleCache = require('../ModuleCache');
-const Promise = require('promise');
-const fs = require('fs');
-
-describe('Module', () => {
-  const fileWatcher = {
-    on: () =>  this,
-    isWatchman: () => Promise.resolve(false),
-  };
-
-  const Cache = jest.genMockFn();
-  Cache.prototype.get = jest.genMockFn().mockImplementation(
-    (filepath, field, cb) => cb(filepath)
-  );
-  Cache.prototype.invalidate = jest.genMockFn();
-  Cache.prototype.end = jest.genMockFn();
-
-
-  describe('Async Dependencies', () => {
-    function expectAsyncDependenciesToEqual(expected) {
-      const fastfs = new Fastfs(
-        'test',
-        ['/root'],
-        fileWatcher,
-        {crawling: Promise.resolve(['/root/index.js']), ignore: []},
-      );
-      const cache = new Cache();
-
-      return fastfs.build().then(() => {
-        const module = new Module(
-          '/root/index.js',
-          fastfs,
-          new ModuleCache(fastfs, cache),
-          cache
-        );
-
-        return module.getAsyncDependencies().then(actual =>
-          expect(actual).toEqual(expected)
-        );
-      });
-    }
-
-    pit('should recognize single dependency', () => {
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'System.import("dep1")',
-        },
-      });
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-
-    pit('should parse single quoted dependencies', () => {
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'System.import(\'dep1\')',
-        },
-      });
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-
-    pit('should parse multiple async dependencies on the same module', () => {
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            'System.import("dep1")',
-            'System.import("dep2")',
-          ].join('\n'),
-        },
-      });
-
-      return expectAsyncDependenciesToEqual([
-        ['dep1'],
-        ['dep2'],
-      ]);
-    });
-
-    pit('parse fine new lines', () => {
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': 'System.import(\n"dep1"\n)',
-        },
-      });
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-  });
-
-  describe('Extrators', () => {
-
-    function createModuleWithExtractor(extractor) {
-      const fastfs = new Fastfs(
-        'test',
-        ['/root'],
-        fileWatcher,
-        {crawling: Promise.resolve(['/root/index.js']), ignore: []},
-      );
-      const cache = new Cache();
-
-      return fastfs.build().then(() => {
-        return new Module(
-          '/root/index.js',
-          fastfs,
-          new ModuleCache(fastfs, cache),
-          cache,
-          extractor
-        );
-      });
-    }
-
-    pit('uses custom require extractors if specified', () => {
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': '',
-        },
-      });
-
-      return createModuleWithExtractor(
-        code => ({deps: {sync: ['foo', 'bar']}})
-      ).then(module =>
-        module.getDependencies().then(actual =>
-          expect(actual).toEqual(['foo', 'bar'])
-        )
-      );
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/crawlers/index.js b/packager/react-packager/src/DependencyResolver/crawlers/index.js
deleted file mode 100644
index fe755bcb621e..000000000000
--- a/packager/react-packager/src/DependencyResolver/crawlers/index.js
+++ /dev/null
@@ -1,26 +0,0 @@
-'use strict';
-
-const nodeCrawl = require('./node');
-const watchmanCrawl = require('./watchman');
-
-function crawl(roots, options) {
-  const {fileWatcher} = options;
-  return fileWatcher.isWatchman().then(isWatchman => {
-    if (!isWatchman) {
-      return false;
-    }
-
-    // Make sure we're dealing with a version of watchman
-    // that's using `watch-project`
-    // TODO(amasad): properly expose (and document) used sane internals.
-    return fileWatcher.getWatchers().then(([watcher]) => !!watcher.watchProjectInfo.root);
-  }).then(isWatchman => {
-    if (isWatchman) {
-      return watchmanCrawl(roots, options);
-    }
-
-    return nodeCrawl(roots, options);
-  });
-}
-
-module.exports = crawl;
diff --git a/packager/react-packager/src/DependencyResolver/crawlers/node.js b/packager/react-packager/src/DependencyResolver/crawlers/node.js
deleted file mode 100644
index 528cd5e7648e..000000000000
--- a/packager/react-packager/src/DependencyResolver/crawlers/node.js
+++ /dev/null
@@ -1,61 +0,0 @@
-'use strict';
-
-const Promise = require('promise');
-const debug = require('debug')('ReactNativePackager:DependencyGraph');
-const fs = require('fs');
-const path = require('path');
-
-const readDir = Promise.denodeify(fs.readdir);
-const stat = Promise.denodeify(fs.stat);
-
-function nodeRecReadDir(roots, {ignore, exts}) {
-  const queue = roots.slice();
-  const retFiles = [];
-  const extPattern = new RegExp(
-    '\.(' + exts.join('|') + ')$'
-  );
-
-  function search() {
-    const currDir = queue.shift();
-    if (!currDir) {
-      return Promise.resolve();
-    }
-
-    return readDir(currDir)
-      .then(files => files.map(f => path.join(currDir, f)))
-      .then(files => Promise.all(
-        files.map(f => stat(f).catch(handleBrokenLink))
-      ).then(stats => [
-        // Remove broken links.
-        files.filter((file, i) => !!stats[i]),
-        stats.filter(Boolean),
-      ]))
-      .then(([files, stats]) => {
-        files.forEach((filePath, i) => {
-          if (ignore(filePath)) {
-            return;
-          }
-
-          if (stats[i].isDirectory()) {
-            queue.push(filePath);
-            return;
-          }
-
-          if (filePath.match(extPattern)) {
-            retFiles.push(filePath);
-          }
-        });
-
-        return search();
-      });
-  }
-
-  return search().then(() => retFiles);
-}
-
-function handleBrokenLink(e) {
-  debug('WARNING: error stating, possibly broken symlink', e.message);
-  return Promise.resolve();
-}
-
-module.exports = nodeRecReadDir;
diff --git a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js b/packager/react-packager/src/DependencyResolver/crawlers/watchman.js
deleted file mode 100644
index 1871e3ead8b5..000000000000
--- a/packager/react-packager/src/DependencyResolver/crawlers/watchman.js
+++ /dev/null
@@ -1,70 +0,0 @@
-'use strict';
-
-const Promise = require('promise');
-const path = require('path');
-
-function watchmanRecReadDir(roots, {ignore, fileWatcher, exts}) {
-  const files = [];
-  return Promise.all(
-    roots.map(
-      root => fileWatcher.getWatcherForRoot(root)
-    )
-  ).then(
-    watchers => {
-      // All watchman roots for all watches we have.
-      const watchmanRoots = watchers.map(
-        watcher => watcher.watchProjectInfo.root
-      );
-
-      // Actual unique watchers (because we use watch-project we may end up with
-      // duplicate "real" watches, and that's by design).
-      // TODO(amasad): push this functionality into the `FileWatcher`.
-      const uniqueWatchers = watchers.filter(
-        (watcher, i) => watchmanRoots.indexOf(watcher.watchProjectInfo.root) === i
-      );
-
-      return Promise.all(
-        uniqueWatchers.map(watcher => {
-          const watchedRoot = watcher.watchProjectInfo.root;
-
-          // Build up an expression to filter the output by the relevant roots.
-          const dirExpr = ['anyof'];
-          for (let i = 0; i < roots.length; i++) {
-            const root = roots[i];
-            if (isDescendant(watchedRoot, root)) {
-              dirExpr.push(['dirname', path.relative(watchedRoot, root)]);
-            }
-          }
-
-          const cmd = Promise.denodeify(watcher.client.command.bind(watcher.client));
-          return cmd(['query', watchedRoot, {
-            'suffix': exts,
-            'expression': ['allof', ['type', 'f'], 'exists', dirExpr],
-            'fields': ['name'],
-          }]).then(resp => {
-            if ('warning' in resp) {
-              console.warn('watchman warning: ', resp.warning);
-            }
-
-            resp.files.forEach(filePath => {
-              filePath = path.join(
-                watchedRoot,
-                filePath
-              );
-
-              if (!ignore(filePath)) {
-                files.push(filePath);
-              }
-              return false;
-            });
-          });
-        })
-      );
-    }).then(() => files);
-}
-
-function isDescendant(root, child) {
-  return path.relative(root, child).indexOf('..') !== 0;
-}
-
-module.exports = watchmanRecReadDir;
diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js
deleted file mode 100644
index 17738744b7ab..000000000000
--- a/packager/react-packager/src/DependencyResolver/fastfs.js
+++ /dev/null
@@ -1,329 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const Promise = require('promise');
-const {EventEmitter} = require('events');
-
-const fs = require('fs');
-const path = require('path');
-
-const readFile = Promise.denodeify(fs.readFile);
-const stat = Promise.denodeify(fs.stat);
-
-const hasOwn = Object.prototype.hasOwnProperty;
-
-const NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
-
-class Fastfs extends EventEmitter {
-  constructor(name, roots, fileWatcher, {ignore, crawling, activity}) {
-    super();
-    this._name = name;
-    this._fileWatcher = fileWatcher;
-    this._ignore = ignore;
-    this._roots = roots.map(root => new File(root, { isDir: true }));
-    this._fastPaths = Object.create(null);
-    this._crawling = crawling;
-    this._activity = activity;
-  }
-
-  build() {
-    const rootsPattern = new RegExp(
-      '^(' + this._roots.map(root => escapeRegExp(root.path)).join('|') + ')'
-    );
-
-    return this._crawling.then(files => {
-      let fastfsActivity;
-      const activity = this._activity;
-      if (activity) {
-        fastfsActivity = activity.startEvent('Building in-memory fs for ' + this._name);
-      }
-      files.forEach(filePath => {
-        if (filePath.match(rootsPattern)) {
-          const newFile = new File(filePath, { isDir: false });
-          const parent = this._fastPaths[path.dirname(filePath)];
-          if (parent) {
-            parent.addChild(newFile);
-          } else {
-            this._add(newFile);
-            for (let file = newFile; file; file = file.parent) {
-              if (!this._fastPaths[file.path]) {
-                this._fastPaths[file.path] = file;
-              }
-            }
-          }
-        }
-      });
-      if (activity) {
-        activity.endEvent(fastfsActivity);
-      }
-      this._fileWatcher.on('all', this._processFileChange.bind(this));
-    });
-  }
-
-  stat(filePath) {
-    return Promise.resolve().then(() => {
-      const file = this._getFile(filePath);
-      return file.stat();
-    });
-  }
-
-  getAllFiles() {
-    // one-level-deep flatten of files
-    return [].concat(...this._roots.map(root => root.getFiles()));
-  }
-
-  findFilesByExt(ext, { ignore } = {}) {
-    return this.findFilesByExts([ext], {ignore});
-  }
-
-  findFilesByExts(exts, { ignore } = {}) {
-    return this.getAllFiles()
-      .filter(file => (
-        exts.indexOf(file.ext()) !== -1 && (!ignore || !ignore(file.path))
-      ))
-      .map(file => file.path);
-  }
-
-  findFilesByName(name, { ignore } = {}) {
-    return this.getAllFiles()
-      .filter(
-        file => path.basename(file.path) === name &&
-          (!ignore || !ignore(file.path))
-      )
-      .map(file => file.path);
-  }
-
-  matchFilesByPattern(pattern) {
-    return this.getAllFiles()
-      .filter(file => file.path.match(pattern))
-      .map(file => file.path);
-  }
-
-  readFile(filePath) {
-    const file = this._getFile(filePath);
-    if (!file) {
-      throw new Error(`Unable to find file with path: ${file}`);
-    }
-    return file.read();
-  }
-
-  closest(filePath, name) {
-    for (let file = this._getFile(filePath).parent;
-         file;
-         file = file.parent) {
-      if (file.children[name]) {
-        return file.children[name].path;
-      }
-    }
-    return null;
-  }
-
-  fileExists(filePath) {
-    let file;
-    try {
-      file = this._getFile(filePath);
-    } catch (e) {
-      if (e.type === NOT_FOUND_IN_ROOTS) {
-        return false;
-      }
-      throw e;
-    }
-
-    return file && !file.isDir;
-  }
-
-  dirExists(filePath) {
-    let file;
-    try {
-      file = this._getFile(filePath);
-    } catch (e) {
-      if (e.type === NOT_FOUND_IN_ROOTS) {
-        return false;
-      }
-      throw e;
-    }
-
-    return file && file.isDir;
-  }
-
-  matches(dir, pattern) {
-    const dirFile = this._getFile(dir);
-    if (!dirFile.isDir) {
-      throw new Error(`Expected file ${dirFile.path} to be a directory`);
-    }
-
-    return Object.keys(dirFile.children)
-      .filter(name => name.match(pattern))
-      .map(name => path.join(dirFile.path, name));
-  }
-
-  _getRoot(filePath) {
-    for (let i = 0; i < this._roots.length; i++) {
-      const possibleRoot = this._roots[i];
-      if (isDescendant(possibleRoot.path, filePath)) {
-        return possibleRoot;
-      }
-    }
-    return null;
-  }
-
-  _getAndAssertRoot(filePath) {
-    const root = this._getRoot(filePath);
-    if (!root) {
-      const error = new Error(`File ${filePath} not found in any of the roots`);
-      error.type = NOT_FOUND_IN_ROOTS;
-      throw error;
-    }
-    return root;
-  }
-
-  _getFile(filePath) {
-    filePath = path.normalize(filePath);
-    if (!hasOwn.call(this._fastPaths, filePath)) {
-      this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
-    }
-
-    return this._fastPaths[filePath];
-  }
-
-  _add(file) {
-    this._getAndAssertRoot(file.path).addChild(file);
-  }
-
-  _processFileChange(type, filePath, root, fstat) {
-    const absPath = path.join(root, filePath);
-    if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
-      return;
-    }
-
-    // Make sure this event belongs to one of our roots.
-    if (!this._getRoot(absPath)) {
-      return;
-    }
-
-    if (type === 'delete' || type === 'change') {
-      const file = this._getFile(absPath);
-      if (file) {
-        file.remove();
-      }
-    }
-
-    delete this._fastPaths[path.normalize(absPath)];
-
-    if (type !== 'delete') {
-      this._add(new File(absPath, { isDir: false }));
-    }
-
-    this.emit('change', type, filePath, root, fstat);
-  }
-}
-
-class File {
-  constructor(filePath, { isDir }) {
-    this.path = filePath;
-    this.isDir = Boolean(isDir);
-    if (this.isDir) {
-      this.children = Object.create(null);
-    }
-  }
-
-  read() {
-    if (!this._read) {
-      this._read = readFile(this.path, 'utf8');
-    }
-    return this._read;
-  }
-
-  stat() {
-    if (!this._stat) {
-      this._stat = stat(this.path);
-    }
-
-    return this._stat;
-  }
-
-  addChild(file) {
-    const parts = path.relative(this.path, file.path).split(path.sep);
-
-    if (parts.length === 0) {
-      return;
-    }
-
-    if (parts.length === 1) {
-      this.children[parts[0]] = file;
-      file.parent = this;
-    } else if (this.children[parts[0]]) {
-      this.children[parts[0]].addChild(file);
-    } else {
-      const dir = new File(path.join(this.path, parts[0]), { isDir: true });
-      dir.parent = this;
-      this.children[parts[0]] = dir;
-      dir.addChild(file);
-    }
-  }
-
-  getFileFromPath(filePath) {
-    const parts = path.relative(this.path, filePath)
-            .split(path.sep);
-
-    /*eslint consistent-this:0*/
-    let file = this;
-    for (let i = 0; i < parts.length; i++) {
-      const fileName = parts[i];
-      if (!fileName) {
-        continue;
-      }
-
-      if (!file || !file.isDir) {
-        // File not found.
-        return null;
-      }
-
-      file = file.children[fileName];
-    }
-
-    return file;
-  }
-
-  getFiles() {
-    const files = [];
-    Object.keys(this.children).forEach(key => {
-      const file = this.children[key];
-      if (file.isDir) {
-        files.push(...file.getFiles());
-      } else {
-        files.push(file);
-      }
-    });
-    return files;
-  }
-
-  ext() {
-    return path.extname(this.path).replace(/^\./, '');
-  }
-
-  remove() {
-    if (!this.parent) {
-      throw new Error(`No parent to delete ${this.path} from`);
-    }
-
-    delete this.parent.children[path.basename(this.path)];
-  }
-}
-
-function isDescendant(root, child) {
-  return path.relative(root, child).indexOf('..') !== 0;
-}
-
-function escapeRegExp(str) {
-  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
-}
-
-module.exports = Fastfs;
diff --git a/packager/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js b/packager/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js
deleted file mode 100644
index ff61c40537d4..000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/__tests__/getAssetDataFromName-test.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest.dontMock('../getPlatformExtension')
-    .dontMock('../getAssetDataFromName');
-
-var getAssetDataFromName = require('../getAssetDataFromName');
-
-describe('getAssetDataFromName', () => {
-  it('should get data from name', () => {
-    expect(getAssetDataFromName('a/b/c.png')).toEqual({
-      resolution: 1,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: null,
-    });
-
-    expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({
-      resolution: 1,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: null,
-    });
-
-    expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({
-      resolution: 2.5,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: null,
-    });
-
-    expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({
-      resolution: 1,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: 'ios',
-    });
-
-    expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({
-      resolution: 1,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: 'ios',
-    });
-
-    expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({
-      resolution: 2.5,
-      assetName: 'a/b/c.png',
-      type: 'png',
-      name: 'c',
-      platform: 'ios',
-    });
-  });
-
-  describe('resolution extraction', () => {
-    it('should extract resolution simple case', () =>  {
-      var data = getAssetDataFromName('test@2x.png');
-      expect(data).toEqual({
-        assetName: 'test.png',
-        resolution: 2,
-        type: 'png',
-        name: 'test',
-        platform: null,
-      });
-    });
-
-    it('should default resolution to 1', () =>  {
-      var data = getAssetDataFromName('test.png');
-      expect(data).toEqual({
-        assetName: 'test.png',
-        resolution: 1,
-        type: 'png',
-        name: 'test',
-        platform: null,
-      });
-    });
-
-    it('should support float', () =>  {
-      var data = getAssetDataFromName('test@1.1x.png');
-      expect(data).toEqual({
-        assetName: 'test.png',
-        resolution: 1.1,
-        type: 'png',
-        name: 'test',
-        platform: null,
-      });
-
-      data = getAssetDataFromName('test@.1x.png');
-      expect(data).toEqual({
-        assetName: 'test.png',
-        resolution: 0.1,
-        type: 'png',
-        name: 'test',
-        platform: null,
-      });
-
-      data = getAssetDataFromName('test@0.2x.png');
-      expect(data).toEqual({
-        assetName: 'test.png',
-        resolution: 0.2,
-        type: 'png',
-        name: 'test',
-        platform: null,
-      });
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js b/packager/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js
deleted file mode 100644
index f5ccdc3ab9b4..000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/__tests__/getPlatformExtension-test.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest.dontMock('../getPlatformExtension');
-
-var getPlatformExtension = require('../getPlatformExtension');
-
-describe('getPlatformExtension', function() {
-  it('should get platform ext', function() {
-    expect(getPlatformExtension('a.ios.js')).toBe('ios');
-    expect(getPlatformExtension('a.android.js')).toBe('android');
-    expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios');
-    expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios');
-    expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios');
-    expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null);
-    expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null);
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/lib/extractRequires.js b/packager/react-packager/src/DependencyResolver/lib/extractRequires.js
deleted file mode 100644
index 107cc6ba9e36..000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/extractRequires.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const replacePatterns = require('./replacePatterns');
-
-/**
- * Extract all required modules from a `code` string.
- */
-const blockCommentRe = /\/\*(.|\n)*?\*\//g;
-const lineCommentRe = /\/\/.+(\n|$)/g;
-function extractRequires(code) {
-  var deps = {
-    sync: [],
-    async: [],
-  };
-
-  code = code
-    .replace(blockCommentRe, '')
-    .replace(lineCommentRe, '')
-    // Parse the sync dependencies this module has. When the module is
-    // required, all it's sync dependencies will be loaded into memory.
-    // Sync dependencies can be defined either using `require` or the ES6
-    // `import` or `export` syntaxes:
-    //   var dep1 = require('dep1');
-    .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
-      deps.sync.push(dep);
-      return match;
-    })
-    .replace(replacePatterns.EXPORT_RE, (match, pre, quot, dep, post) => {
-      deps.sync.push(dep);
-      return match;
-    })
-    .replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
-      deps.sync.push(dep);
-      return match;
-    })
-    // Parse async dependencies this module has. As opposed to what happens
-    // with sync dependencies, when the module is required, it's async
-    // dependencies won't be loaded into memory. This is deferred till the
-    // code path gets to the import statement:
-    //   System.import('dep1')
-    .replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => {
-      deps.async.push([dep]);
-      return match;
-    });
-
-  return {code, deps};
-}
-
-module.exports = extractRequires;
diff --git a/packager/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js b/packager/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js
deleted file mode 100644
index 33fa13cea18d..000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/getAssetDataFromName.js
+++ /dev/null
@@ -1,55 +0,0 @@
- /**
- * 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.
- */
-'use strict';
-
-const path = require('path');
-const getPlatformExtension = require('./getPlatformExtension');
-
-function getAssetDataFromName(filename) {
-  const ext = path.extname(filename);
-  const platformExt = getPlatformExtension(filename);
-
-  let pattern = '@([\\d\\.]+)x';
-  if (platformExt != null) {
-    pattern += '(\\.' + platformExt + ')?';
-  }
-  pattern += '\\' + ext + '$';
-  const re = new RegExp(pattern);
-
-  const match = filename.match(re);
-  let resolution;
-
-  if (!(match && match[1])) {
-    resolution = 1;
-  } else {
-    resolution = parseFloat(match[1], 10);
-    if (isNaN(resolution)) {
-      resolution = 1;
-    }
-  }
-
-  let assetName;
-  if (match) {
-    assetName = filename.replace(re, ext);
-  } else if (platformExt != null) {
-    assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext);
-  } else {
-    assetName = filename;
-  }
-
-  return {
-    resolution: resolution,
-    assetName: assetName,
-    type: ext.slice(1),
-    name: path.basename(assetName, ext),
-    platform: platformExt,
-  };
-}
-
-module.exports = getAssetDataFromName;
diff --git a/packager/react-packager/src/DependencyResolver/lib/getPlatformExtension.js b/packager/react-packager/src/DependencyResolver/lib/getPlatformExtension.js
deleted file mode 100644
index cfcb65016452..000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/getPlatformExtension.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const SUPPORTED_PLATFORM_EXTS = ['android', 'ios', 'web'];
-
-const re = new RegExp(
-  '[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$'
-);
-
-// Extract platform extension: index.ios.js -> ios
-function getPlatformExtension(file) {
-  const match = file.match(re);
-  if (match && match[1]) {
-    return match[1];
-  }
-  return null;
-}
-
-module.exports = getPlatformExtension;
diff --git a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js
deleted file mode 100644
index 973c4f7804bc..000000000000
--- a/packager/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-jest
-  .dontMock('util')
-  .dontMock('events')
-  .dontMock('../')
-  .dontMock('q')
-  .setMock('child_process', {
-    exec: function(cmd, cb) {
-      cb(null, '/usr/bin/watchman');
-    }
-  });
-
-var FileWatcher = require('../');
-var sane = require('sane');
-
-describe('FileWatcher', function() {
-  var Watcher;
-
-  beforeEach(function() {
-    Watcher = sane.WatchmanWatcher;
-    Watcher.prototype.once.mockImplementation(function(type, callback) {
-      callback();
-    });
-  });
-
-  pit('it should get the watcher instance when ready', function() {
-    var fileWatcher = new FileWatcher(['rootDir']);
-    return fileWatcher.getWatchers().then(function(watchers) {
-      watchers.forEach(function(watcher) {
-        expect(watcher instanceof Watcher).toBe(true);
-      });
-    });
-  });
-
-  pit('should emit events', function() {
-    var cb;
-    Watcher.prototype.on.mockImplementation(function(type, callback) {
-      cb = callback;
-    });
-    var fileWatcher = new FileWatcher(['rootDir']);
-    var handler = jest.genMockFn();
-    fileWatcher.on('all', handler);
-    return fileWatcher.getWatchers().then(function(){
-      cb(1, 2, 3, 4);
-      jest.runAllTimers();
-      expect(handler.mock.calls[0]).toEqual([1, 2, 3, 4]);
-    });
-  });
-
-  pit('it should end the watcher', function() {
-    var fileWatcher = new FileWatcher(['rootDir']);
-    Watcher.prototype.close.mockImplementation(function(callback) {
-      callback();
-    });
-
-    return fileWatcher.end().then(function() {
-      expect(Watcher.prototype.close).toBeCalled();
-    });
-  });
-});
diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js
deleted file mode 100644
index d9ed7f32fd25..000000000000
--- a/packager/react-packager/src/FileWatcher/index.js
+++ /dev/null
@@ -1,117 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const EventEmitter  = require('events').EventEmitter;
-const sane = require('sane');
-const Promise = require('promise');
-const exec = require('child_process').exec;
-const _ = require('underscore');
-
-const MAX_WAIT_TIME = 25000;
-
-// TODO(amasad): can we use watchman version command instead?r
-const detectingWatcherClass = new Promise(function(resolve) {
-  exec('which watchman', function(err, out) {
-    if (err || out.length === 0) {
-      resolve(sane.NodeWatcher);
-    } else {
-      resolve(sane.WatchmanWatcher);
-    }
-  });
-});
-
-let inited = false;
-
-class FileWatcher extends EventEmitter {
-
-  constructor(rootConfigs) {
-    if (inited) {
-      throw new Error('FileWatcher can only be instantiated once');
-    }
-    inited = true;
-
-    super();
-    this._watcherByRoot = Object.create(null);
-
-    this._loading = Promise.all(
-      rootConfigs.map(createWatcher)
-    ).then(watchers => {
-      watchers.forEach((watcher, i) => {
-        this._watcherByRoot[rootConfigs[i].dir] = watcher;
-        watcher.on(
-          'all',
-          // args = (type, filePath, root, stat)
-          (...args) => this.emit('all', ...args)
-        );
-      });
-      return watchers;
-    });
-
-    this._loading.done();
-  }
-
-  getWatchers() {
-    return this._loading;
-  }
-
-  getWatcherForRoot(root) {
-    return this._loading.then(() => this._watcherByRoot[root]);
-  }
-
-  isWatchman() {
-    return detectingWatcherClass.then(
-      Watcher => Watcher === sane.WatchmanWatcher
-    );
-  }
-
-  end() {
-    return this._loading.then(
-      (watchers) => watchers.map(
-        watcher => Promise.denodeify(watcher.close).call(watcher)
-      )
-    );
-  }
-
-  static createDummyWatcher() {
-    const ev = new EventEmitter();
-    _.extend(ev, {
-      isWatchman: () => Promise.resolve(false),
-      end: () => Promise.resolve(),
-    });
-
-    return ev;
-  }
-}
-
-function createWatcher(rootConfig) {
-  return detectingWatcherClass.then(function(Watcher) {
-    const watcher = new Watcher(rootConfig.dir, {
-      glob: rootConfig.globs,
-      dot: false,
-    });
-
-    return new Promise(function(resolve, reject) {
-      const rejectTimeout = setTimeout(function() {
-        reject(new Error([
-          'Watcher took too long to load',
-          'Try running `watchman version` from your terminal',
-          'https://facebook.github.io/watchman/docs/troubleshooting.html',
-        ].join('\n')));
-      }, MAX_WAIT_TIME);
-
-      watcher.once('ready', function() {
-        clearTimeout(rejectTimeout);
-        resolve(watcher);
-      });
-    });
-  });
-}
-
-module.exports = FileWatcher;
diff --git a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
index b182e1eb2aa8..cd73549c883e 100644
--- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
+++ b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
@@ -13,17 +13,21 @@ jest
   .dontMock('../');
 
 jest.mock('fs');
+jest.setMock('temp', {path: () => '/arbitrary/path'});
 
-var Cache = require('../../Cache');
 var Transformer = require('../');
 var fs = require('fs');
 
+var Cache;
 var options;
 
 describe('Transformer', function() {
   var workers;
 
   beforeEach(function() {
+    Cache = jest.genMockFn();
+    Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c());
+
     workers = jest.genMockFn();
     jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() {
       return workers;
diff --git a/packager/react-packager/src/JSTransformer/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/__tests__/worker-test.js
new file mode 100644
index 000000000000..f81907c2c8e0
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/__tests__/worker-test.js
@@ -0,0 +1,109 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+
+jest.mock('babel-core');
+
+const worker = require('../worker');
+const babel = require('babel-core');
+
+const code = 'code';
+
+describe('Resolver', function() {
+  beforeEach(() => {
+    babel.transform.mockImpl((source, options) => source);
+  });
+
+  describe('when no external transform is provided', () => {
+    xit('should invoke internal transform if available', () => {
+      transform({
+        sourceCode: 'code',
+        filename: 'test',
+        options: options({opts: {}, internalTransformsEnabled: true}),
+      });
+      expect(babel.transform.mock.calls.length).toBe(1);
+    });
+
+    it('should not invoke internal transform if unavailable', () => {
+      transform({
+        sourceCode: 'code',
+        filename: 'test',
+        options: options({opts: {}, internalTransformsEnabled: false}),
+      });
+      expect(babel.transform.mock.calls.length).toBe(0);
+    });
+  });
+
+  describe('when external transform is provided', () => {
+    xit('should invoke both transformers if internal is available', () => {
+      transform({
+        sourceCode: code,
+        filename: 'test',
+        options: options({
+          opts: {
+            externalTransformModulePath: require.resolve(
+              '../../../../transformer.js'
+            ),
+          },
+          internalTransformsEnabled: true,
+        }),
+      });
+      expect(babel.transform.mock.calls.length).toBe(2);
+    });
+
+    it('should invoke only external transformer if internal is not available', () => {
+      transform({
+        sourceCode: 'code',
+        filename: 'test',
+        options: options({
+          opts: {
+            externalTransformModulePath: require.resolve(
+              '../../../../transformer.js'
+            ),
+          },
+          internalTransformsEnabled: false,
+        }),
+      });
+      expect(babel.transform.mock.calls.length).toBe(1);
+    });
+
+    xit('should pipe errors through transform pipeline', () => {
+      const error = new Error('transform error');
+      babel.transform.mockImpl((source, options) => {
+        throw error;
+      });
+
+      const callback = transform({
+        sourceCode: 'code',
+        filename: 'test',
+        options: options({
+          opts: {
+            externalTransformModulePath: require.resolve(
+              '../../../../transformer.js'
+            ),
+          },
+          internalTransformsEnabled: true,
+        }),
+      });
+      expect(callback.mock.calls[0][0]).toBe(error);
+    });
+  });
+});
+
+function transform(data) {
+  const callback = jest.genMockFunction();
+  worker(data, callback);
+  return callback;
+}
+
+function options({opts, internalTransformsEnabled}) {
+  return Object.assign(opts, {hot: internalTransformsEnabled});
+}
diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js
index 5c819381caf8..a25cd57d9098 100644
--- a/packager/react-packager/src/JSTransformer/index.js
+++ b/packager/react-packager/src/JSTransformer/index.js
@@ -12,6 +12,8 @@ const ModuleTransport = require('../lib/ModuleTransport');
 const Promise = require('promise');
 const declareOpts = require('../lib/declareOpts');
 const fs = require('fs');
+const os = require('os');
+const temp = require('temp');
 const util = require('util');
 const workerFarm = require('worker-farm');
 const debug = require('debug')('ReactNativePackager:JStransformer');
@@ -24,10 +26,10 @@ const readFile = Promise.denodeify(fs.readFile);
 const MAX_CALLS_PER_WORKER = 600;
 
 // Worker will timeout if one of the callers timeout.
-const DEFAULT_MAX_CALL_TIME = 120000;
+const DEFAULT_MAX_CALL_TIME = 301000;
 
 // How may times can we tolerate failures from the worker.
-const MAX_RETRIES = 3;
+const MAX_RETRIES = 2;
 
 const validateOpts = declareOpts({
   projectRoots: {
@@ -52,23 +54,62 @@ const validateOpts = declareOpts({
   transformTimeoutInterval: {
     type: 'number',
     default: DEFAULT_MAX_CALL_TIME,
-  }
+  },
+  disableInternalTransforms: {
+    type: 'boolean',
+    default: false,
+  },
 });
 
+const maxConcurrentWorkers = ((cores, override) => {
+  if (override) {
+    return Math.min(cores, override);
+  }
+
+  if (cores < 3) {
+    return cores;
+  }
+  if (cores < 8) {
+    return Math.floor(cores * 0.75);
+  }
+  if (cores < 24) {
+    return Math.floor(3/8 * cores + 3); // between cores *.75 and cores / 2
+  }
+  return cores / 2;
+})(os.cpus().length, process.env.REACT_NATIVE_MAX_WORKERS);
+
 class Transformer {
   constructor(options) {
     const opts = this._opts = validateOpts(options);
 
     this._cache = opts.cache;
+    this._transformModulePath = opts.transformModulePath;
+    this._projectRoots = opts.projectRoots;
 
     if (opts.transformModulePath != null) {
+      let transformer;
+
+      if (opts.disableInternalTransforms) {
+        transformer = opts.transformModulePath;
+      } else {
+        transformer = this._workerWrapperPath = temp.path();
+        fs.writeFileSync(
+          this._workerWrapperPath,
+          `
+          module.exports = require(${JSON.stringify(require.resolve('./worker'))});
+          require(${JSON.stringify(String(opts.transformModulePath))});
+          `
+        );
+      }
+
       this._workers = workerFarm({
         autoStart: true,
         maxConcurrentCallsPerWorker: 1,
+        maxConcurrentWorkers: maxConcurrentWorkers,
         maxCallsPerWorker: MAX_CALLS_PER_WORKER,
         maxCallTime: opts.transformTimeoutInterval,
         maxRetries: MAX_RETRIES,
-      }, opts.transformModulePath);
+      }, transformer);
 
       this._transform = Promise.denodeify(this._workers);
     }
@@ -76,22 +117,28 @@ class Transformer {
 
   kill() {
     this._workers && workerFarm.end(this._workers);
+    if (this._workerWrapperPath &&
+        typeof this._workerWrapperPath === 'string') {
+      fs.unlink(this._workerWrapperPath, () => {}); // we don't care about potential errors here
+    }
   }
 
   invalidateFile(filePath) {
     this._cache.invalidate(filePath);
   }
 
-  loadFileAndTransform(filePath) {
+  loadFileAndTransform(filePath, options) {
     if (this._transform == null) {
       return Promise.reject(new Error('No transfrom module'));
     }
 
     debug('transforming file', filePath);
 
+    const optionsJSON = JSON.stringify(options);
+
     return this._cache.get(
       filePath,
-      'transformedSource',
+      'transformedSource-' + optionsJSON,
       // TODO: use fastfs to avoid reading file from disk again
       () => readFile(filePath).then(
         buffer => {
@@ -100,6 +147,11 @@ class Transformer {
           return this._transform({
             sourceCode,
             filename: filePath,
+            options: {
+              ...options,
+              projectRoots: this._projectRoots,
+              externalTransformModulePath: this._transformModulePath,
+            },
           }).then(res => {
             if (res.error) {
               console.warn(
diff --git a/packager/react-packager/src/JSTransformer/worker.js b/packager/react-packager/src/JSTransformer/worker.js
index b6b4f363d56e..c8d388a41e27 100644
--- a/packager/react-packager/src/JSTransformer/worker.js
+++ b/packager/react-packager/src/JSTransformer/worker.js
@@ -8,27 +8,70 @@
  */
 'use strict';
 
-var transformer = require('./transformer');
+var babel = require('babel-core');
+var makeInternalConfig = require('babel-preset-react-native/configs/internal');
 
-module.exports = function (data, callback) {
-  var result;
-  try {
-    result = transformer.transform(
-      data.transformSets,
-      data.sourceCode,
-      data.options
-    );
-  } catch (e) {
-    return callback(null, {
-      error: {
-        lineNumber: e.lineNumber,
-        column: e.column,
-        message: e.message,
-        stack: e.stack,
-        description: e.description
-      }
-    });
+// Runs internal transforms on the given sourceCode. Note that internal
+// transforms should be run after the external ones to ensure that they run on
+// Javascript code
+function internalTransforms(sourceCode, filename, options) {
+  var internalBabelConfig = makeInternalConfig(options);
+
+  if (!internalBabelConfig) {
+    return {
+      code: sourceCode,
+      filename: filename,
+    };
+  }
+
+  var result = babel.transform(sourceCode, Object.assign({
+    filename: filename,
+    sourceFileName: filename,
+  }, internalBabelConfig));
+
+  return {
+    code: result.code,
+    filename: filename,
+  };
+}
+
+function onExternalTransformDone(data, callback, error, externalOutput) {
+  if (error) {
+    callback(error);
+    return;
   }
 
+  var result = internalTransforms(
+    externalOutput.code,
+    externalOutput.filename,
+    data.options
+  );
+
   callback(null, result);
+}
+
+module.exports = function(data, callback) {
+  try {
+    if (data.options.externalTransformModulePath) {
+      var externalTransformModule = require(
+        data.options.externalTransformModulePath
+      );
+      externalTransformModule(
+        data,
+        onExternalTransformDone.bind(null, data, callback)
+      );
+    } else {
+      onExternalTransformDone(
+        data,
+        callback,
+        null,
+        {
+          code: data.sourceCode,
+          filename: data.filename
+        }
+      );
+    }
+  } catch (e) {
+    callback(e);
+  }
 };
diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js
new file mode 100644
index 000000000000..a39a1e970930
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js
@@ -0,0 +1,112 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+const babel = require('babel-core');
+const constantFolding = require('../constant-folding');
+
+function parse(code) {
+  return babel.transform(code, {code: false, babelrc: false, compact: true});
+}
+
+describe('constant expressions', () => {
+  it('can optimize conditional expressions with constant conditions', () => {
+    const code = `
+      a(
+        'production'=="production",
+        'production'!=='development',
+        false && 1 || 0 || 2,
+        true || 3,
+        'android'==='ios' ? null : {},
+        'android'==='android' ? {a:1} : {a:0},
+        'foo'==='bar' ? b : c,
+        f() ? g() : h()
+      );`;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`a(true,true,2,true,{},{a:1},c,f()?g():h());`);
+  });
+
+  it('can optimize ternary expressions with constant conditions', () => {
+    const code =
+      `var a = true ? 1 : 2;
+       var b = 'android' == 'android'
+         ? ('production' != 'production' ? 'a' : 'A')
+         : 'i';`;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`var a=1;var b='A';`);
+  });
+
+  it('can optimize logical operator expressions with constant conditions', () => {
+    const code = `
+      var a = true || 1;
+      var b = 'android' == 'android' &&
+        'production' != 'production' || null || "A";`;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`var a=true;var b="A";`);
+  });
+
+  it('can optimize logical operators with partly constant operands', () => {
+    const code = `
+      var a = "truthy" || z();
+      var b = "truthy" && z();
+      var c = null && z();
+      var d = null || z();
+      var e = !1 && z();
+    `;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`var a="truthy";var b=z();var c=null;var d=z();var e=false;`);
+  });
+
+  it('can remode an if statement with a falsy constant test', () => {
+    const code = `
+      if ('production' === 'development' || false) {
+        var a = 1;
+      }
+    `;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(``);
+  });
+
+  it('can optimize if-else-branches with constant conditions', () => {
+    const code = `
+      if ('production' == 'development') {
+        var a = 1;
+        var b = a + 2;
+      } else if ('development' == 'development') {
+        var a = 3;
+        var b = a + 4;
+      } else {
+        var a = 'b';
+      }
+    `;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`{var a=3;var b=a+4;}`);
+  });
+
+  it('can optimize nested if-else constructs', () => {
+    const code = `
+      if ('ios' === "android") {
+        if (true) {
+          require('a');
+        } else {
+          require('b');
+        }
+      } else if ('android' === 'android') {
+        if (true) {
+          require('c');
+        } else {
+          require('d');
+        }
+      }
+    `;
+    expect(constantFolding('arbitrary.js', parse(code)).code)
+      .toEqual(`{{require('c');}}`);
+  });
+});
diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js
new file mode 100644
index 000000000000..a27def1dc442
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js
@@ -0,0 +1,96 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+
+const extractDependencies = require('../extract-dependencies');
+
+describe('Dependency extraction:', () => {
+  it('can extract calls to require', () => {
+    const code = `require('foo/bar');
+      var React = require("React");
+      var A = React.createClass({
+        render: function() {
+          return require (  "Component" );
+        }
+      });
+      require
+      ('more');`
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies)
+      .toEqual(['foo/bar', 'React', 'Component', 'more']);
+    expect(dependencyOffsets).toEqual([8, 46, 147, 203]);
+  });
+
+  it('does not extract require method calls', () => {
+    const code = `
+      require('a');
+      foo.require('b');
+      bar.
+      require ( 'c').require('d')require('e')`;
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['a', 'e']);
+    expect(dependencyOffsets).toEqual([15, 97]);
+  });
+
+  it('does not extract require calls from strings', () => {
+    const code = `require('foo');
+      var React = '\\'require("React")';
+      var a = ' // require("yadda")';
+      var a = ' /* require("yadda") */';
+      var A = React.createClass({
+        render: function() {
+          return require (  "Component" );
+        }
+      });
+      " \\" require('more')";`
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['foo', 'Component']);
+    expect(dependencyOffsets).toEqual([8, 226]);
+  });
+
+  it('does not extract require calls in comments', () => {
+    const code = `require('foo')//require("not/this")
+      /* A comment here with a require('call') that should not be extracted */require('bar')
+    // ending comment without newline require("baz")`;
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['foo', 'bar']);
+    expect(dependencyOffsets).toEqual([8, 122]);
+  });
+
+  it('deduplicates dependencies', () => {
+    const code = `require('foo');require( "foo" );
+      require("foo");`;
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['foo']);
+    expect(dependencyOffsets).toEqual([8, 24, 47]);
+  });
+
+  it('does not extract calls to function with names that start with "require"', () => {
+    const code = `arbitraryrequire('foo');`;
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual([]);
+    expect(dependencyOffsets).toEqual([]);
+  });
+
+  it('does not get confused by previous states', () => {
+    // yes, this was a bug
+    const code = `require("a");/* a comment */ var a = /[a]/.test('a');`
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['a']);
+    expect(dependencyOffsets).toEqual([8]);
+  });
+});
diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js
new file mode 100644
index 000000000000..53fe1ed8f362
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/inline-test.js
@@ -0,0 +1,133 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+const inline = require('../inline');
+const {transform, transformFromAst} = require('babel-core');
+
+const babelOptions = {
+  babelrc: false,
+  compact: true,
+};
+
+function toString(ast) {
+  return normalize(transformFromAst(ast, babelOptions).code);
+}
+
+function normalize(code) {
+  return transform(code, babelOptions).code;
+}
+
+function toAst(code) {
+  return transform(code, {...babelOptions, code: false}).ast;
+}
+
+describe('inline constants', () => {
+  it('replaces __DEV__ in the code', () => {
+    const code = `function a() {
+      var a = __DEV__ ? 1 : 2;
+      var b = a.__DEV__;
+      var c = function __DEV__(__DEV__) {};
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {dev: true});
+    expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true')));
+  });
+
+  it('replaces Platform.OS in the code if Platform is a global', () => {
+    const code = `function a() {
+      var a = Platform.OS;
+      var b = a.Platform.OS;
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
+    expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
+  });
+
+  it('replaces Platform.OS in the code if Platform is a top level import', () => {
+    const code = `
+      var Platform = require('Platform');
+      function a() {
+        if (Platform.OS === 'android') a = function() {};
+        var b = a.Platform.OS;
+      }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
+    expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
+  });
+
+  it('replaces require("Platform").OS in the code', () => {
+    const code = `function a() {
+      var a = require('Platform').OS;
+      var b = a.require('Platform').OS;
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
+    expect(toString(ast)).toEqual(
+      normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')));
+  });
+
+  it('replaces React.Platform.OS in the code if React is a global', () => {
+    const code = `function a() {
+      var a = React.Platform.OS;
+      var b = a.React.Platform.OS;
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
+    expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"')));
+  });
+
+  it('replaces React.Platform.OS in the code if React is a top level import', () => {
+    const code = `
+      var React = require('React');
+      function a() {
+        if (React.Platform.OS === 'android') a = function() {};
+        var b = a.React.Platform.OS;
+      }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
+    expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"')));
+  });
+
+  it('replaces require("React").Platform.OS in the code', () => {
+    const code = `function a() {
+      var a = require('React').Platform.OS;
+      var b = a.require('React').Platform.OS;
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
+    expect(toString(ast)).toEqual(
+      normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"')));
+  });
+
+  it('replaces process.env.NODE_ENV in the code', () => {
+    const code = `function a() {
+      if (process.env.NODE_ENV === 'production') {
+        return require('Prod');
+      }
+      return require('Dev');
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {dev: false});
+    expect(toString(ast)).toEqual(
+      normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')));
+  });
+
+  it('replaces process.env.NODE_ENV in the code', () => {
+    const code = `function a() {
+      if (process.env.NODE_ENV === 'production') {
+        return require('Prod');
+      }
+      return require('Dev');
+    }`
+    const {ast} = inline('arbitrary.js', {code}, {dev: true});
+    expect(toString(ast)).toEqual(
+      normalize(code.replace(/process\.env\.NODE_ENV/, '"development"')));
+  });
+
+  it('accepts an AST as input', function() {
+    const code = `function ifDev(a,b){return __DEV__?a:b;}`;
+    const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false});
+    expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false'))
+  });
+});
+
diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js
new file mode 100644
index 000000000000..e65594e7cabe
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/minify-test.js
@@ -0,0 +1,111 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+
+const uglify = {
+  minify: jest.genMockFunction().mockImplementation(code => {
+    return {
+      code: code.replace(/(^|\W)\s+/g, '$1'),
+      map: {},
+    };
+  }),
+};
+jest.setMock('uglify-js', uglify);
+
+const minify = require('../minify');
+const {any} = jasmine;
+
+describe('Minification:', () => {
+  const fileName = '/arbitrary/file.js';
+  const DEPENDENCY_MARKER = '\u0002\ueffe\ue277\uead5';
+  let map;
+
+  beforeEach(() => {
+    uglify.minify.mockClear();
+    map = {version: 3, sources: [fileName], mappings: ''};
+  });
+
+  it('passes the transformed code to `uglify.minify`, wrapped in an immediately invoked function expression', () => {
+    const code = 'arbitrary(code)';
+    minify('', code, {}, [], []);
+    expect(uglify.minify).toBeCalledWith(
+      `(function(){${code}}());`, any(Object));
+  });
+
+  it('uses the passed module locals as parameters of the IIFE', () => {
+    const moduleLocals = ['arbitrary', 'parameters'];
+    minify('', '', {}, [], moduleLocals);
+    expect(uglify.minify).toBeCalledWith(
+      `(function(${moduleLocals}){}());`, any(Object));
+  });
+
+  it('passes the transformed source map to `uglify.minify`', () => {
+    minify('', '', map, [], []);
+    const [, options] = uglify.minify.mock.calls[0];
+    expect(options.inSourceMap).toEqual(map);
+  });
+
+  it('passes the file name as `outSourceMap` to `uglify.minify` (uglify uses it for the `file` field on the source map)', () => {
+    minify(fileName, '', {}, [], []);
+    const [, options] = uglify.minify.mock.calls[0];
+    expect(options.outSourceMap).toEqual(fileName);
+  });
+
+  it('inserts a marker for every dependency offset before minifing', () => {
+    const code = `
+      var React = require('React');
+      var Immutable = require('Immutable');`;
+    const dependencyOffsets = [27, 67];
+    const expectedCode =
+      code.replace(/require\('/g, '$&' + DEPENDENCY_MARKER);
+
+    minify('', code, {}, dependencyOffsets, []);
+    expect(uglify.minify).toBeCalledWith(
+      `(function(){${expectedCode}}());`, any(Object));
+  });
+
+  it('returns the code provided by uglify', () => {
+    const code = 'some(source) + code';
+    uglify.minify.mockReturnValue({code: `!function(a,b,c){${code}}()`});
+
+    const result = minify('', '', {}, [], []);
+    expect(result.code).toBe(code);
+  });
+
+  it('extracts dependency offsets from the code provided by uglify', () => {
+    const code = `
+      var a=r("${DEPENDENCY_MARKER}a-dependency");
+      var b=r("\\x02\\ueffe\\ue277\\uead5b-dependency");
+      var e=r(a()?'\\u0002\\ueffe\\ue277\\uead5c-dependency'
+                 :'\x02\ueffe\ue277\uead5d-dependency');`;
+    uglify.minify.mockReturnValue({code: `!function(){${code}}());`});
+
+    const result = minify('', '', {}, [], []);
+    expect(result.dependencyOffsets).toEqual([15, 46, 81, 114]);
+  });
+
+  it('returns the source map object provided by uglify', () => {
+    uglify.minify.mockReturnValue({map, code: ''});
+    const result = minify('', '', {}, [], []);
+    expect(result.map).toBe(map);
+  });
+
+  it('adds a `moduleLocals` object to the result that reflects the names of the minified module locals', () => {
+    const moduleLocals = ['arbitrary', 'parameters', 'here'];
+    uglify.minify.mockReturnValue({code: '(function(a,ll,d){}());'});
+    const result = minify('', '', {}, [], moduleLocals);
+    expect(result.moduleLocals).toEqual({
+      arbitrary: 'a',
+      parameters: 'll',
+      here: 'd',
+    });
+  });
+});
diff --git a/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js
new file mode 100644
index 000000000000..a6acb668960d
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js
@@ -0,0 +1,244 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest.autoMockOff();
+jest.mock('../constant-folding');
+jest.mock('../extract-dependencies');
+jest.mock('../inline');
+jest.mock('../minify');
+
+const {transformCode} = require('..');
+const {any, objectContaining} = jasmine;
+
+describe('code transformation worker:', () => {
+  let extractDependencies, transform;
+  beforeEach(() => {
+    extractDependencies =
+      require('../extract-dependencies').mockReturnValue({});
+    transform = jest.genMockFunction();
+  });
+
+  it('calls the transform with file name, source code, and transform options', function() {
+    const filename = 'arbitrary/file.js';
+    const sourceCode = 'arbitrary(code)';
+    const transformOptions = {arbitrary: 'options'};
+    transformCode(transform, filename, sourceCode, {transform: transformOptions});
+    expect(transform).toBeCalledWith(
+      {filename, sourceCode, options: transformOptions}, any(Function));
+  });
+
+  it('prefixes JSON files with an assignment to module.exports to make the code valid', function() {
+    const filename = 'arbitrary/file.json';
+    const sourceCode = '{"arbitrary":"property"}';
+    transformCode(transform, filename, sourceCode, {});
+    expect(transform).toBeCalledWith(
+      {filename, sourceCode: `module.exports=${sourceCode}`}, any(Function));
+  });
+
+  it('calls back with the result of the transform', done => {
+    const result = {
+      code: 'some.other(code)',
+      map: {}
+    };
+    transform.mockImplementation((_, callback) =>
+      callback(null, result));
+
+    transformCode(transform, 'filename', 'code', {}, (_, data) => {
+      expect(data).toEqual(objectContaining(result));
+      done();
+    });
+  });
+
+  it('removes the leading assignment to `module.exports` before passing on the result if the file is a JSON file, even if minified', done => {
+    const result = {
+      code: 'p.exports={a:1,b:2}',
+    };
+    transform.mockImplementation((_, callback) =>
+      callback(null, result));
+
+    transformCode(transform, 'aribtrary/file.json', 'b', {}, (_, data) => {
+      expect(data.code).toBe('{a:1,b:2}');
+      done();
+    });
+  });
+
+  it('calls back with any error yielded by the transform', done => {
+    const error = Error('arbitrary error');
+    transform.mockImplementation((_, callback) => callback(error));
+    transformCode(transform, 'filename', 'code', {}, e => {
+      expect(e).toBe(error);
+      done();
+    });
+  });
+
+  it('puts an empty `moduleLocals` object on the result', done => {
+    transform.mockImplementation(
+      (_, callback) => callback(null, {code: 'arbitrary'}));
+    transformCode(transform, 'filename', 'code', {}, (_, data) => {
+      expect(data.moduleLocals).toEqual({});
+      done();
+    });
+  });
+
+  it('if a `moduleLocals` array is passed, the `moduleLocals` object is a key mirror of its items', done => {
+    transform.mockImplementation(
+      (_, callback) => callback(null, {code: 'arbitrary'}));
+    const moduleLocals =
+      ['arbitrary', 'list', 'containing', 'variable', 'names'];
+
+    transformCode(transform, 'filename', 'code', {moduleLocals}, (_, data) => {
+      expect(data.moduleLocals).toEqual({
+        arbitrary: 'arbitrary',
+        list: 'list',
+        containing: 'containing',
+        variable: 'variable',
+        names: 'names',
+      });
+      done();
+    });
+  });
+
+  describe('dependency extraction:', () => {
+    let code;
+
+    beforeEach(() => {
+      transform.mockImplementation(
+        (_, callback) => callback(null, {code}));
+    });
+
+    it('passes the transformed code the `extractDependencies`', done => {
+      code = 'arbitrary(code)';
+
+      transformCode(transform, 'filename', 'code', {}, (_, data) => {
+        expect(extractDependencies).toBeCalledWith(code);
+        done();
+      });
+    });
+
+    it('uses `dependencies` and `dependencyOffsets` provided by `extractDependencies` for the result', done => {
+      const dependencyData = {
+        dependencies: ['arbitrary', 'list', 'of', 'dependencies'],
+        dependencyOffsets: [12,  119, 185, 328, 471],
+      };
+      extractDependencies.mockReturnValue(dependencyData);
+
+      transformCode(transform, 'filename', 'code', {}, (_, data) => {
+        expect(data).toEqual(objectContaining(dependencyData));
+        done();
+      });
+    });
+
+    it('does not extract requires if files are marked as "extern"', done => {
+      transformCode(transform, 'filename', 'code', {extern: true}, (_, {dependencies, dependencyOffsets}) => {
+        expect(extractDependencies).not.toBeCalled();
+        expect(dependencies).toEqual([]);
+        expect(dependencyOffsets).toEqual([]);
+        done();
+      });
+    });
+
+    it('does not extract requires of JSON files', done => {
+      transformCode(transform, 'arbitrary.json', '{"arbitrary":"json"}', {}, (_, {dependencies, dependencyOffsets}) => {
+        expect(extractDependencies).not.toBeCalled();
+        expect(dependencies).toEqual([]);
+        expect(dependencyOffsets).toEqual([]);
+        done();
+      });
+    });
+  });
+
+  describe('Minifications:', () => {
+    let constantFolding, extractDependencies, inline, minify, options;
+    let transformResult, dependencyData, moduleLocals;
+    const filename = 'arbitrary/file.js';
+    const foldedCode = 'arbitrary(folded(code));'
+    const foldedMap = {version: 3, sources: ['fold.js']}
+
+    beforeEach(() => {
+      constantFolding = require('../constant-folding')
+        .mockReturnValue({code: foldedCode, map: foldedMap});
+      extractDependencies = require('../extract-dependencies');
+      inline = require('../inline');
+      minify = require('../minify').mockReturnValue({});
+
+      moduleLocals = ['module', 'require', 'exports'];
+      options = {moduleLocals, minify: true};
+      dependencyData = {
+        dependencies: ['a', 'b', 'c'],
+        dependencyOffsets: [100, 120, 140]
+      };
+
+      extractDependencies.mockImplementation(
+        code => code === foldedCode ? dependencyData : {});
+
+      transform.mockImplementation(
+        (_, callback) => callback(null, transformResult));
+    });
+
+    it('passes the transform result to `inline` for constant inlining', done => {
+      transformResult = {map: {version: 3}, code: 'arbitrary(code)'};
+      transformCode(transform, filename, 'code', options, () => {
+        expect(inline).toBeCalledWith(filename, transformResult, options);
+        done();
+      });
+    });
+
+    it('passes the result obtained from `inline` on to `constant-folding`', done => {
+      const inlineResult = {map: {version: 3, sources: []}, ast: {}};
+      inline.mockReturnValue(inlineResult);
+      transformCode(transform, filename, 'code', options, () => {
+        expect(constantFolding).toBeCalledWith(filename, inlineResult);
+        done();
+      });
+    });
+
+    it('Uses the code obtained from `constant-folding` to extract dependencies', done => {
+      transformCode(transform, filename, 'code', options, () => {
+        expect(extractDependencies).toBeCalledWith(foldedCode);
+        done();
+      });
+    });
+
+    it('passes the code obtained from `constant-folding` to `minify`', done => {
+      transformCode(transform, filename, 'code', options, () => {
+        expect(minify).toBeCalledWith(
+          filename,
+          foldedCode,
+          foldedMap,
+          dependencyData.dependencyOffsets,
+          moduleLocals
+        );
+        done();
+      });
+    });
+
+    it('uses the dependencies obtained from the optimized result', done => {
+      transformCode(transform, filename, 'code', options, (_, result) => {
+        expect(result.dependencies).toEqual(dependencyData.dependencies);
+        done();
+      });
+    });
+
+    it('uses data produced by `minify` for the result', done => {
+      const minifyResult = {
+        code: 'minified(code)',
+        dependencyOffsets: [10, 30, 60],
+        map: {version: 3, sources: ['minified.js']},
+        moduleLocals: {module: 'x', require: 'y', exports: 'z'},
+      };
+      minify.mockReturnValue(minifyResult);
+
+      transformCode(transform, 'filename', 'code', options, (_, result) => {
+        expect(result).toEqual(objectContaining(minifyResult))
+        done();
+      });
+    });
+  });
+});
diff --git a/packager/react-packager/src/JSTransformer/worker/constant-folding.js b/packager/react-packager/src/JSTransformer/worker/constant-folding.js
new file mode 100644
index 000000000000..acf14f687b02
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/constant-folding.js
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2016-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.
+ */
+'use strict';
+
+const babel = require('babel-core');
+const t = babel.types;
+
+const isLiteral = binaryExpression =>
+  t.isLiteral(binaryExpression.left) && t.isLiteral(binaryExpression.right);
+
+const Conditional = {
+  exit(path) {
+    const node = path.node;
+    const test = node.test;
+    if (t.isLiteral(test)) {
+      if (test.value || node.alternate) {
+        path.replaceWith(test.value ? node.consequent : node.alternate);
+      } else if (!test.value) {
+        path.remove();
+      }
+    }
+  },
+};
+
+const plugin = {
+  visitor: {
+    BinaryExpression: {
+      exit(path) {
+        const node = path.node;
+        if (t.isLiteral(node.left) && t.isLiteral(node.right)) {
+          const result = path.evaluate();
+          if (result.confident) {
+            path.replaceWith(t.valueToNode(result.value));
+          }
+        }
+      },
+    },
+    ConditionalExpression: Conditional,
+    IfStatement: Conditional,
+    LogicalExpression: {
+      exit(path) {
+        const node = path.node;
+        const left = node.left;
+        if (t.isLiteral(left)) {
+          const value = t.isNullLiteral(left) ? null : left.value;
+          if (node.operator === '||') {
+            path.replaceWith(value ? left : node.right);
+          } else {
+            path.replaceWith(value ? node.right : left);
+          }
+        }
+      }
+    },
+    UnaryExpression: {
+      exit(path) {
+        const node = path.node;
+        if (node.operator === '!' && t.isLiteral(node.argument)) {
+          path.replaceWith(t.valueToNode(!node.argument.value));
+        }
+      }
+    },
+  },
+};
+
+function constantFolding(filename, transformResult) {
+  return babel.transformFromAst(transformResult.ast, transformResult.code, {
+    filename,
+    plugins: [plugin],
+    inputSourceMap: transformResult.map,
+    babelrc: false,
+    compact: true,
+  })
+}
+
+module.exports = constantFolding;
+
diff --git a/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js b/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js
new file mode 100644
index 000000000000..3ca221641ab7
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2016-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.
+ */
+'use strict';
+
+const SINGLE_QUOTE = "'".charCodeAt(0);
+const DOUBLE_QUOTE = '"'.charCodeAt(0);
+const BACKSLASH = '\\'.charCodeAt(0);
+const SLASH = '/'.charCodeAt(0);
+const NEWLINE = '\n'.charCodeAt(0);
+const ASTERISK = '*'.charCodeAt(0);
+
+// dollar is the only regex special character valid in identifiers
+const escapeRegExp = identifier => identifier.replace(/[$]/g, '\\$');
+
+function binarySearch(indexes, index) {
+  var low = 0;
+  var high = indexes.length - 1;
+  var i = 0;
+
+  if (indexes[low] === index) {
+    return low;
+  }
+  while (high - low > 1) {
+    var current = low + ((high - low) >>> 1); // right shift divides by 2 and floors
+    if (index === indexes[current]) {
+      return current;
+    }
+    if (index > indexes[current]) {
+      low = current;
+    } else {
+      high = current;
+    }
+  }
+  return low;
+}
+
+function indexOfCharCode(string, needle, i) {
+  for (var charCode; (charCode = string.charCodeAt(i)); i++) {
+    if (charCode === needle) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g;
+
+/**
+ * Extracts dependencies (module IDs imported with the `require` function) from
+ * a string containing code.
+ * The function is regular expression based for speed reasons.
+ *
+ * The code is traversed twice:
+ *  1. An array of ranges is built, where indexes 0-1, 2-3, 4-5, etc. are code,
+ *     and indexes 1-2, 3-4, 5-6, etc. are string literals and comments.
+ *  2. require calls are extracted with a regular expression.
+ *
+ * The result of the dependency extraction is an de-duplicated array of
+ * dependencies, and an array of offsets to the string literals with module IDs.
+ * The index points to the opening quote.
+ */
+function extractDependencies(code) {
+  const ranges = [0];
+  // are we currently in a quoted string? -> SINGLE_QUOTE or DOUBLE_QUOTE, else undefined
+  var currentQuote;
+  // scan the code for string literals and comments.
+  for (var i = 0, charCode; (charCode = code.charCodeAt(i)); i++) {
+    if (charCode === BACKSLASH) {
+      i += 1;
+      continue;
+    }
+
+    if (charCode === SLASH && currentQuote === undefined) {
+      var next = code.charCodeAt(i + 1);
+      var end = undefined;
+      if (next === SLASH) {
+        end = indexOfCharCode(code, NEWLINE, i + 2);
+      } else if (next === ASTERISK) {
+        end = code.indexOf('*/', i + 2) + 1; // assume valid JS input here
+      }
+      if (end === -1) {
+        // if the comment does not end, it goes to the end of the file
+        end += code.length;
+      }
+      if (end !== undefined) {
+        ranges.push(i, end);
+        i = end;
+        continue;
+      }
+    }
+
+    var isQuoteStart = currentQuote === undefined &&
+                       (charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE);
+    if (isQuoteStart || currentQuote === charCode) {
+      ranges.push(i);
+      currentQuote = currentQuote === charCode ? undefined : charCode;
+    }
+  }
+  ranges.push(i);
+
+  // extract dependencies
+  const dependencies = new Set();
+  const dependencyOffsets = [];
+  for (var match; (match = reRequire.exec(code)); ) {
+    // check whether the match is in a code range, and not inside of a string
+    // literal or a comment
+    if (binarySearch(ranges, match.index) % 2 === 0) {
+      dependencies.add(match[2]);
+      dependencyOffsets.push(
+        match[0].length - match[2].length - 2 + match.index);
+    }
+  }
+
+  return {
+    dependencyOffsets,
+    dependencies: Array.from(dependencies.values()),
+  };
+}
+
+module.exports = extractDependencies;
diff --git a/packager/react-packager/src/JSTransformer/worker/index.js b/packager/react-packager/src/JSTransformer/worker/index.js
new file mode 100644
index 000000000000..bc6e93338017
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/index.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2016-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.
+ */
+'use strict';
+
+const constantFolding = require('./constant-folding');
+const extractDependencies = require('./extract-dependencies');
+const inline = require('./inline');
+const minify = require('./minify');
+
+function keyMirrorFromArray(array) {
+  var keyMirror = {};
+  array.forEach(key => keyMirror[key] = key);
+  return keyMirror;
+}
+
+function makeTransformParams(filename, sourceCode, options) {
+  if (filename.endsWith('.json')) {
+    sourceCode = 'module.exports=' + sourceCode;
+  }
+  return {filename, sourceCode, options};
+}
+
+function transformCode(transform, filename, sourceCode, options, callback) {
+  const params = makeTransformParams(filename, sourceCode, options.transform);
+  const moduleLocals = options.moduleLocals || [];
+  const isJson = filename.endsWith('.json');
+
+  transform(params, (error, transformed) => {
+    if (error) {
+      callback(error);
+      return;
+    }
+
+    var code, map;
+    if (options.minify) {
+      const optimized =
+        constantFolding(filename, inline(filename, transformed, options));
+      code = optimized.code;
+      map = optimized.map;
+    } else {
+      code = transformed.code;
+      map = transformed.map;
+    }
+
+    if (isJson) {
+      code = code.replace(/^\w+\.exports=/, '');
+    }
+
+    const moduleLocals = options.moduleLocals || [];
+    const dependencyData = isJson || options.extern
+      ? {dependencies: [], dependencyOffsets: []}
+      : extractDependencies(code);
+
+    var result;
+    if (options.minify) {
+      result = minify(
+        filename, code, map, dependencyData.dependencyOffsets, moduleLocals);
+      result.dependencies = dependencyData.dependencies;
+    } else {
+      result = dependencyData;
+      result.code = code;
+      result.map = map;
+      result.moduleLocals = keyMirrorFromArray(moduleLocals);
+    }
+
+    callback(null, result);
+  });
+}
+
+module.exports = function(transform, filename, sourceCode, options, callback) {
+  transformCode(require(transform), filename, sourceCode, options || {}, callback);
+};
+module.exports.transformCode = transformCode; // for easier testing
diff --git a/packager/react-packager/src/JSTransformer/worker/inline.js b/packager/react-packager/src/JSTransformer/worker/inline.js
new file mode 100644
index 000000000000..2a419933af87
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/inline.js
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2016-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.
+ */
+'use strict';
+
+const babel = require('babel-core');
+const t = babel.types;
+
+const react = {name: 'React'};
+const platform = {name: 'Platform'};
+const os = {name: 'OS'};
+const requirePattern = {name: 'require'};
+
+const env = {name: 'env'};
+const nodeEnv = {name: 'NODE_ENV'};
+const processId = {name: 'process'};
+
+const dev = {name: '__DEV__'};
+
+const isGlobal = (binding) => !binding;
+
+const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent;
+
+const isRequireCall = (node, dependencyId, scope) =>
+  t.isCallExpression(node) &&
+  t.isIdentifier(node.callee, requirePattern) &&
+  t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId));
+
+const isImport = (node, scope, pattern) =>
+  t.isIdentifier(node, pattern) &&
+  isToplevelBinding(scope.getBinding(pattern.name)) ||
+  isRequireCall(node, pattern.name, scope);
+
+const isPlatformOS = (node, scope) =>
+  t.isIdentifier(node.property, os) &&
+  isImport(node.object, scope, platform);
+
+const isReactPlatformOS = (node, scope) =>
+  t.isIdentifier(node.property, os) &&
+  t.isMemberExpression(node.object) &&
+  t.isIdentifier(node.object.property, platform) &&
+  isImport(node.object.object, scope, react);
+
+const isProcessEnvNodeEnv = (node, scope) =>
+  t.isIdentifier(node.property, nodeEnv) &&
+  t.isMemberExpression(node.object) &&
+  t.isIdentifier(node.object.property, env) &&
+  t.isIdentifier(node.object.object, processId) &&
+  isGlobal(scope.getBinding(processId.name));
+
+const isDev = (node, parent, scope) =>
+  t.isIdentifier(node, dev) &&
+  isGlobal(scope.getBinding(dev.name)) &&
+  !(t.isMemberExpression(parent));
+
+const inlinePlugin = {
+  visitor: {
+    Identifier(path, state) {
+      if (isDev(path.node, path.parent, path.scope)) {
+        path.replaceWith(t.booleanLiteral(state.opts.dev));
+      }
+    },
+    MemberExpression(path, state) {
+      const node = path.node;
+      const scope = path.scope;
+
+      if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) {
+        path.replaceWith(t.stringLiteral(state.opts.platform));
+      }
+
+      if(isProcessEnvNodeEnv(node, scope)) {
+        path.replaceWith(
+          t.stringLiteral(state.opts.dev ? 'development' : 'production'));
+      }
+    },
+  },
+};
+
+const plugin = () => inlinePlugin;
+
+function inline(filename, transformResult, options) {
+  const code = transformResult.code;
+  const babelOptions = {
+    filename,
+    plugins: [[plugin, options]],
+    inputSourceMap: transformResult.map,
+    code: false,
+    babelrc: false,
+    compact: true,
+  };
+
+  return transformResult.ast
+      ? babel.transformFromAst(transformResult.ast, code, babelOptions)
+      : babel.transform(code, babelOptions);
+}
+
+module.exports = inline;
diff --git a/packager/react-packager/src/JSTransformer/worker/minify.js b/packager/react-packager/src/JSTransformer/worker/minify.js
new file mode 100644
index 000000000000..d343591874e4
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/minify.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2016-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.
+ */
+'use strict';
+
+const uglify = require('uglify-js');
+
+const MAGIC_MARKER = '\u0002\ueffe\ue277\uead5';
+const MAGIC_MARKER_SPLITTER =
+  /(?:\x02|\\u0002|\\x02)(?:\ueffe|\\ueffe)(?:\ue277|\\ue277)(?:\uead5|\\uead5)/;
+
+// IIFE = "immediately invoked function expression"
+// we wrap modules in functions to allow the minifier to mangle local variables
+function wrapCodeInIIFE(code, moduleLocals) {
+  return `(function(${moduleLocals.join(',')}){${code}}());`;
+}
+
+function extractCodeFromIIFE(code) {
+  return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
+}
+
+function extractModuleLocalsFromIIFE(code) {
+  return code.substring(code.indexOf('(', 1) + 1, code.indexOf(')')).split(',');
+}
+
+function splitFirstElementAt(array, offset) {
+  const first = array.shift();
+  array.unshift(first.slice(0, offset + 1), first.slice(offset + 1));
+  return array;
+}
+
+function insertMarkers(code, dependencyOffsets) {
+  return dependencyOffsets
+    .reduceRight(splitFirstElementAt, [code])
+    .join(MAGIC_MARKER);
+}
+
+function extractMarkers(codeWithMarkers) {
+  const dependencyOffsets = [];
+  const codeBits = codeWithMarkers.split(MAGIC_MARKER_SPLITTER);
+  var offset = 0;
+  for (var i = 0, max = codeBits.length - 1; i < max; i++) {
+    offset += codeBits[i].length;
+    dependencyOffsets.push(offset - 1);
+  }
+
+  return {code: codeBits.join(''), dependencyOffsets};
+}
+
+function minify(filename, code, map, dependencyOffsets, moduleLocals) {
+  // before minifying, code is wrapped in an immediately invoked function
+  // expression, so that top level variables can be shortened safely
+  code = wrapCodeInIIFE(
+    // since we don't know where the strings specifying dependencies will be
+    // located in the minified code, we mark them with a special marker string
+    // and extract them afterwards.
+    // That way, post-processing code can use these positions
+    insertMarkers(code, dependencyOffsets),
+    moduleLocals
+  );
+
+  const minifyResult = uglify.minify(code, {
+    fromString: true,
+    inSourceMap: map,
+    outSourceMap: filename,
+    output: {
+      ascii_only: true,
+      screw_ie8: true,
+    },
+  });
+
+  const minifiedModuleLocals = extractModuleLocalsFromIIFE(minifyResult.code);
+  const codeWithMarkers = extractCodeFromIIFE(minifyResult.code);
+  const result = extractMarkers(codeWithMarkers);
+  result.map = minifyResult.map;
+  result.moduleLocals = {};
+  moduleLocals.forEach(
+    (key, i) => result.moduleLocals[key] = minifiedModuleLocals[i]);
+
+  return result;
+}
+
+module.exports = minify;
diff --git a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js
index f58589215d5b..b12ebd48dfb4 100644
--- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js
+++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js
@@ -9,36 +9,49 @@
 'use strict';
 
 jest.dontMock('../')
-  .dontMock('underscore')
-  .dontMock('../../DependencyResolver/lib/extractRequires')
-  .dontMock('../../DependencyResolver/lib/replacePatterns');
+  .dontMock('underscore');
 
 jest.mock('path');
 
-var Promise = require('promise');
-var Resolver = require('../');
-var Module = require('../../DependencyResolver/Module');
-var Polyfill = require('../../DependencyResolver/Polyfill');
-var DependencyGraph = require('../../DependencyResolver/DependencyGraph');
+const Promise = require('promise');
+const Resolver = require('../');
 
-var path = require('path');
-var _ = require('underscore');
+const path = require('path');
+const _ = require('underscore');
+
+let DependencyGraph = jest.genMockFn();
+jest.setMock('node-haste', DependencyGraph);
+let Module;
+let Polyfill;
 
 describe('Resolver', function() {
   beforeEach(function() {
-    Polyfill.mockClear();
+    DependencyGraph.mockClear();
+    Module = jest.genMockFn().mockImpl(function() {
+      this.getName = jest.genMockFn();
+      this.getDependencies = jest.genMockFn();
+      this.isPolyfill = jest.genMockFn().mockReturnValue(false);
+    });
+    Polyfill = jest.genMockFn().mockImpl(function() {
+      var polyfill = new Module();
+      polyfill.isPolyfill.mockReturnValue(true);
+      return polyfill;
+    });
+
+    DependencyGraph.replacePatterns = require.requireActual('node-haste/lib/lib/replacePatterns');
+    DependencyGraph.prototype.createPolyfill = jest.genMockFn();
+    DependencyGraph.prototype.getDependencies = jest.genMockFn();
 
     // For the polyfillDeps
-    path.join.mockImpl(function(a, b) {
-      return b;
-    });
+    path.join = jest.genMockFn().mockImpl((a, b) => b);
+
+    DependencyGraph.prototype.load = jest.genMockFn().mockImpl(() => Promise.resolve());
   });
 
   class ResolutionResponseMock {
-    constructor({dependencies, mainModuleId, asyncDependencies}) {
+    constructor({dependencies, mainModuleId}) {
       this.dependencies = dependencies;
       this.mainModuleId = mainModuleId;
-      this.asyncDependencies = asyncDependencies;
     }
 
     prependDependency(dependency) {
@@ -51,12 +64,20 @@ describe('Resolver', function() {
   }
 
   function createModule(id, dependencies) {
-    var module = new Module();
+    var module = new Module({});
     module.getName.mockImpl(() => Promise.resolve(id));
     module.getDependencies.mockImpl(() => Promise.resolve(dependencies));
     return module;
   }
 
+  function createPolyfill(id, dependencies) {
+    var polyfill = new Polyfill({});
+    polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id));
+    polyfill.getDependencies =
+      jest.genMockFn().mockImpl(() => Promise.resolve(dependencies));
+    return polyfill;
+  }
+
   describe('getDependencies', function() {
     pit('should get dependencies with polyfills', function() {
       var module = createModule('index');
@@ -70,7 +91,6 @@ describe('Resolver', function() {
         return Promise.resolve(new ResolutionResponseMock({
           dependencies: deps,
           mainModuleId: 'index',
-          asyncDependencies: [],
         }));
       });
 
@@ -78,58 +98,35 @@ describe('Resolver', function() {
         .then(function(result) {
           expect(result.mainModuleId).toEqual('index');
           expect(result.dependencies[result.dependencies.length - 1]).toBe(module);
-          expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([
-            { path: 'polyfills/prelude.js',
-              id: 'polyfills/prelude.js',
-              isPolyfill: true,
-              dependencies: []
-            },
-            { path: 'polyfills/require.js',
-              id: 'polyfills/require.js',
-              isPolyfill: true,
-              dependencies: ['polyfills/prelude.js']
-            },
-            { path: 'polyfills/polyfills.js',
+          expect(_.pluck(DependencyGraph.prototype.createPolyfill.mock.calls, 0)).toEqual([
+            { file: 'polyfills/polyfills.js',
               id: 'polyfills/polyfills.js',
-              isPolyfill: true,
-              dependencies: ['polyfills/prelude.js', 'polyfills/require.js']
+              dependencies: []
             },
             { id: 'polyfills/console.js',
-              isPolyfill: true,
-              path: 'polyfills/console.js',
+              file: 'polyfills/console.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js'
               ],
             },
             { id: 'polyfills/error-guard.js',
-              isPolyfill: true,
-              path: 'polyfills/error-guard.js',
+              file: 'polyfills/error-guard.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js'
               ],
             },
             { id: 'polyfills/String.prototype.es6.js',
-              isPolyfill: true,
-              path: 'polyfills/String.prototype.es6.js',
+              file: 'polyfills/String.prototype.es6.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
                 'polyfills/error-guard.js'
               ],
             },
             { id: 'polyfills/Array.prototype.es6.js',
-              isPolyfill: true,
-              path: 'polyfills/Array.prototype.es6.js',
+              file: 'polyfills/Array.prototype.es6.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
                 'polyfills/error-guard.js',
@@ -137,11 +134,8 @@ describe('Resolver', function() {
               ],
             },
             { id: 'polyfills/Array.es6.js',
-              isPolyfill: true,
-              path: 'polyfills/Array.es6.js',
+              file: 'polyfills/Array.es6.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
                 'polyfills/error-guard.js',
@@ -149,18 +143,27 @@ describe('Resolver', function() {
                 'polyfills/Array.prototype.es6.js',
               ],
             },
+            { id: 'polyfills/Object.es7.js',
+              file: 'polyfills/Object.es7.js',
+              dependencies: [
+                'polyfills/polyfills.js',
+                'polyfills/console.js',
+                'polyfills/error-guard.js',
+                'polyfills/String.prototype.es6.js',
+                'polyfills/Array.prototype.es6.js',
+                'polyfills/Array.es6.js',
+              ],
+            },
             { id: 'polyfills/babelHelpers.js',
-              isPolyfill: true,
-              path: 'polyfills/babelHelpers.js',
+              file: 'polyfills/babelHelpers.js',
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
                 'polyfills/error-guard.js',
                 'polyfills/String.prototype.es6.js',
                 'polyfills/Array.prototype.es6.js',
                 'polyfills/Array.es6.js',
+                'polyfills/Object.es7.js',
               ],
             },
           ]);
@@ -179,15 +182,17 @@ describe('Resolver', function() {
         return Promise.resolve(new ResolutionResponseMock({
           dependencies: deps,
           mainModuleId: 'index',
-          asyncDependencies: [],
         }));
       });
 
+      const polyfill = {};
+      DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill);
       return depResolver.getDependencies('/root/index.js', { dev: true })
         .then(function(result) {
           expect(result.mainModuleId).toEqual('index');
-          expect(DependencyGraph.mock.instances[0].getDependencies).toBeCalledWith('/root/index.js', undefined);
-          expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
+          expect(DependencyGraph.mock.instances[0].getDependencies)
+              .toBeCalledWith({entryPath: '/root/index.js', recursive: true});
+          expect(result.dependencies[0]).toBe(polyfill);
           expect(result.dependencies[result.dependencies.length - 1])
               .toBe(module);
         });
@@ -206,26 +211,23 @@ describe('Resolver', function() {
         return Promise.resolve(new ResolutionResponseMock({
           dependencies: deps,
           mainModuleId: 'index',
-          asyncDependencies: [],
         }));
       });
 
       return depResolver.getDependencies('/root/index.js', { dev: false })
         .then((result) => {
           expect(result.mainModuleId).toEqual('index');
-          expect(Polyfill.mock.calls[result.dependencies.length - 2]).toEqual([
-            { path: 'some module',
+          expect(DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2]).toEqual([
+            { file: 'some module',
               id: 'some module',
-              isPolyfill: true,
               dependencies: [
-                'polyfills/prelude.js',
-                'polyfills/require.js',
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
                 'polyfills/error-guard.js',
                 'polyfills/String.prototype.es6.js',
                 'polyfills/Array.prototype.es6.js',
                 'polyfills/Array.es6.js',
+                'polyfills/Object.es7.js',
                 'polyfills/babelHelpers.js',
               ]
             },
@@ -636,7 +638,6 @@ describe('Resolver', function() {
       const resolutionResponse = new ResolutionResponseMock({
         dependencies: [module],
         mainModuleId: 'test module',
-        asyncDependencies: [],
       });
 
       resolutionResponse.getResolvedDependencyPairs = (module) => {
@@ -651,7 +652,8 @@ describe('Resolver', function() {
         createModule('test module', ['x', 'y']),
         code
       ).then(processedCode => {
-        expect(processedCode).toEqual([
+        expect(processedCode.name).toEqual('test module');
+        expect(processedCode.code).toEqual([
           '__d(\'test module\',function(global, require,' +
             ' module, exports) {  ' +
             // single line import
@@ -1042,5 +1044,26 @@ describe('Resolver', function() {
         ].join('\n'));
       });
     });
+
+    pit('should resolve polyfills', function () {
+      const depResolver = new Resolver({
+        projectRoot: '/root',
+      });
+      const polyfill = createPolyfill('test polyfill', []);
+      const code = [
+        'global.fetch = () => 1;',
+      ].join('');
+      return depResolver.wrapModule(
+        null,
+        polyfill,
+        code
+      ).then(processedCode => {
+        expect(processedCode.code).toEqual([
+          '(function(global) {',
+          'global.fetch = () => 1;',
+          "\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);",
+        ].join(''));
+      });
+    });
   });
 });
diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js
index 60820faa16a3..d60e0fb2b4d5 100644
--- a/packager/react-packager/src/Resolver/index.js
+++ b/packager/react-packager/src/Resolver/index.js
@@ -11,9 +11,8 @@
 
 const path = require('path');
 const Activity = require('../Activity');
-const DependencyGraph = require('../DependencyResolver/DependencyGraph');
-const replacePatterns = require('../DependencyResolver/lib/replacePatterns');
-const Polyfill = require('../DependencyResolver/Polyfill');
+const DependencyGraph = require('node-haste');
+const replacePatterns = require('node-haste').replacePatterns;
 const declareOpts = require('../lib/declareOpts');
 const Promise = require('promise');
 
@@ -60,6 +59,14 @@ const getDependenciesValidateOpts = declareOpts({
     type: 'string',
     required: false,
   },
+  unbundle: {
+    type: 'boolean',
+    default: false
+  },
+  recursive: {
+    type: 'boolean',
+    default: true,
+  },
 });
 
 class Resolver {
@@ -77,8 +84,7 @@ class Resolver {
           (opts.blacklistRE && opts.blacklistRE.test(filepath));
       },
       providesModuleNodeModules: [
-        'fbjs-haste',
-        'react-haste',
+        'react',
         'react-native',
         // Parse requires AsyncStorage. They will
         // change that to require('react-native') which
@@ -87,56 +93,93 @@ class Resolver {
         'parse',
       ],
       platforms: ['ios', 'android'],
+      preferNativePlatform: true,
       fileWatcher: opts.fileWatcher,
       cache: opts.cache,
+      shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios',
     });
 
     this._polyfillModuleNames = opts.polyfillModuleNames || [];
+
+    this._depGraph.load().catch(err => {
+      console.error(err.message + '\n' + err.stack);
+      process.exit(1);
+    });
+  }
+
+  getShallowDependencies(entryFile) {
+    return this._depGraph.getShallowDependencies(entryFile);
+  }
+
+  stat(filePath) {
+    return this._depGraph.getFS().stat(filePath);
+  }
+
+  getModuleForPath(entryFile) {
+    return this._depGraph.getModuleForPath(entryFile);
+  }
+
+  getDependencies(entryPath, options) {
+    const {platform, recursive} = getDependenciesValidateOpts(options);
+    return this._depGraph.getDependencies({
+      entryPath,
+      platform,
+      recursive,
+    }).then(resolutionResponse => {
+      this._getPolyfillDependencies().reverse().forEach(
+        polyfill => resolutionResponse.prependDependency(polyfill)
+      );
+
+      return resolutionResponse.finalize();
+    });
   }
 
-  getDependencies(main, options) {
+  getModuleSystemDependencies(options) {
     const opts = getDependenciesValidateOpts(options);
 
-    return this._depGraph.getDependencies(main, opts.platform).then(
-      resolutionResponse => {
-        this._getPolyfillDependencies(opts.dev).reverse().forEach(
-          polyfill => resolutionResponse.prependDependency(polyfill)
-        );
+    const prelude = opts.dev
+        ? path.join(__dirname, 'polyfills/prelude_dev.js')
+        : path.join(__dirname, 'polyfills/prelude.js');
 
-        return resolutionResponse.finalize();
-      }
-    );
+    const moduleSystem = opts.unbundle
+        ? path.join(__dirname, 'polyfills/require-unbundle.js')
+        : path.join(__dirname, 'polyfills/require.js');
+
+    return [
+      prelude,
+      moduleSystem
+    ].map(moduleName => this._depGraph.createPolyfill({
+      file: moduleName,
+      id: moduleName,
+      dependencies: [],
+    }));
   }
 
-  _getPolyfillDependencies(isDev) {
+  _getPolyfillDependencies() {
     const polyfillModuleNames = [
-     isDev
-        ? path.join(__dirname, 'polyfills/prelude_dev.js')
-        : path.join(__dirname, 'polyfills/prelude.js'),
-      path.join(__dirname, 'polyfills/require.js'),
       path.join(__dirname, 'polyfills/polyfills.js'),
       path.join(__dirname, 'polyfills/console.js'),
       path.join(__dirname, 'polyfills/error-guard.js'),
       path.join(__dirname, 'polyfills/String.prototype.es6.js'),
       path.join(__dirname, 'polyfills/Array.prototype.es6.js'),
       path.join(__dirname, 'polyfills/Array.es6.js'),
+      path.join(__dirname, 'polyfills/Object.es7.js'),
       path.join(__dirname, 'polyfills/babelHelpers.js'),
     ].concat(this._polyfillModuleNames);
 
     return polyfillModuleNames.map(
-      (polyfillModuleName, idx) => new Polyfill({
-        path: polyfillModuleName,
+      (polyfillModuleName, idx) => this._depGraph.createPolyfill({
+        file: polyfillModuleName,
         id: polyfillModuleName,
         dependencies: polyfillModuleNames.slice(0, idx),
-        isPolyfill: true,
       })
     );
   }
 
-  wrapModule(resolutionResponse, module, code) {
+  resolveRequires(resolutionResponse, module, code) {
     return Promise.resolve().then(() => {
       if (module.isPolyfill()) {
-        return Promise.resolve(code);
+        return Promise.resolve({code});
       }
 
       const resolvedDeps = Object.create(null);
@@ -163,25 +206,38 @@ class Resolver {
           }
         };
 
-        return module.getName().then(
-          name => defineModuleCode({
-            code: code.replace(replacePatterns.IMPORT_RE, relativizeCode)
-                      .replace(replacePatterns.EXPORT_RE, relativizeCode)
-                      .replace(replacePatterns.REQUIRE_RE, relativizeCode),
-            moduleName: name,
-          })
-        );
+        code = code
+          .replace(replacePatterns.IMPORT_RE, relativizeCode)
+          .replace(replacePatterns.EXPORT_RE, relativizeCode)
+          .replace(replacePatterns.REQUIRE_RE, relativizeCode);
+
+        return module.getName().then(name => {
+          return {name, code};
+        });
       });
     });
   }
 
+  wrapModule(resolutionResponse, module, code) {
+    if (module.isPolyfill()) {
+      return Promise.resolve({
+        code: definePolyfillCode(code),
+      });
+    }
+
+    return this.resolveRequires(resolutionResponse, module, code).then(
+      ({name, code}) => {
+        return {name, code: defineModuleCode(name, code)};
+      });
+  }
+
   getDebugInfo() {
     return this._depGraph.getDebugInfo();
   }
 
 }
 
-function defineModuleCode({moduleName, code}) {
+function defineModuleCode(moduleName, code) {
   return [
     `__d(`,
     `'${moduleName}',`,
@@ -191,4 +247,12 @@ function defineModuleCode({moduleName, code}) {
   ].join('');
 }
 
+function definePolyfillCode(code) {
+  return [
+    '(function(global) {',
+    code,
+    `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`,
+  ].join('');
+}
+
 module.exports = Resolver;
diff --git a/packager/react-packager/src/Resolver/polyfills/Array.prototype.es6.js b/packager/react-packager/src/Resolver/polyfills/Array.prototype.es6.js
index 80e62f05dee3..2752aab5e5d0 100644
--- a/packager/react-packager/src/Resolver/polyfills/Array.prototype.es6.js
+++ b/packager/react-packager/src/Resolver/polyfills/Array.prototype.es6.js
@@ -5,54 +5,51 @@
  * @polyfill
  */
 
-/*eslint-disable */
-/*jslint bitwise: true */
+/* eslint-disable */
 
-(function(undefined) {
-  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
-  function findIndex(predicate, context) {
-    if (this == null) {
-      throw new TypeError(
-        'Array.prototype.findIndex called on null or undefined'
-      );
-    }
-    if (typeof predicate !== 'function') {
-      throw new TypeError('predicate must be a function');
-    }
-    var list = Object(this);
-    var length = list.length >>> 0;
-    for (var i = 0; i < length; i++) {
-      if (predicate.call(context, list[i], i, list)) {
-        return i;
-      }
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
+function findIndex(predicate, context) {
+  if (this == null) {
+    throw new TypeError(
+      'Array.prototype.findIndex called on null or undefined'
+    );
+  }
+  if (typeof predicate !== 'function') {
+    throw new TypeError('predicate must be a function');
+  }
+  var list = Object(this);
+  var length = list.length >>> 0;
+  for (var i = 0; i < length; i++) {
+    if (predicate.call(context, list[i], i, list)) {
+      return i;
     }
-    return -1;
   }
+  return -1;
+}
 
-  if (!Array.prototype.findIndex) {
-    Object.defineProperty(Array.prototype, 'findIndex', {
-      enumerable: false,
-      writable: true,
-      configurable: true,
-      value: findIndex
-    });
-  }
+if (!Array.prototype.findIndex) {
+  Object.defineProperty(Array.prototype, 'findIndex', {
+    enumerable: false,
+    writable: true,
+    configurable: true,
+    value: findIndex
+  });
+}
 
-  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
-  if (!Array.prototype.find) {
-    Object.defineProperty(Array.prototype, 'find', {
-      enumerable: false,
-      writable: true,
-      configurable: true,
-      value: function(predicate, context) {
-        if (this == null) {
-          throw new TypeError(
-            'Array.prototype.find called on null or undefined'
-          );
-        }
-        var index = findIndex.call(this, predicate, context);
-        return index === -1 ? undefined : this[index];
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
+if (!Array.prototype.find) {
+  Object.defineProperty(Array.prototype, 'find', {
+    enumerable: false,
+    writable: true,
+    configurable: true,
+    value: function(predicate, context) {
+      if (this == null) {
+        throw new TypeError(
+          'Array.prototype.find called on null or undefined'
+        );
       }
-    });
-  }
-})();
+      var index = findIndex.call(this, predicate, context);
+      return index === -1 ? undefined : this[index];
+    }
+  });
+}
diff --git a/packager/react-packager/src/Resolver/polyfills/Object.es7.js b/packager/react-packager/src/Resolver/polyfills/Object.es7.js
new file mode 100644
index 000000000000..597c49e7fc22
--- /dev/null
+++ b/packager/react-packager/src/Resolver/polyfills/Object.es7.js
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @provides Object.es7
+ * @polyfill
+ */
+
+(function() {
+
+  const hasOwnProperty = Object.prototype.hasOwnProperty;
+
+  /**
+   * Returns an array of the given object's own enumerable entries.
+   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
+   *
+   */
+  if (typeof Object.entries !== 'function') {
+    Object.entries = function(object) {
+      // `null` and `undefined` values are not allowed.
+      if (object == null) {
+        throw new TypeError('Object.entries called on non-object');
+      }
+
+      let entries = [];
+      for (let key in object) {
+        if (hasOwnProperty.call(object, key)) {
+          entries.push([key, object[key]]);
+        }
+      }
+      return entries;
+    };
+  }
+
+  /**
+   * Returns an array of the given object's own enumerable entries.
+   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
+   *
+   */
+  if (typeof Object.values !== 'function') {
+    Object.values = function(object) {
+      // `null` and `undefined` values are not allowed.
+      if (object == null) {
+        throw new TypeError('Object.values called on non-object');
+      }
+
+      let values = [];
+      for (let key in object) {
+        if (hasOwnProperty.call(object, key)) {
+          values.push(object[key]);
+        }
+      }
+      return values;
+    };
+  }
+
+})();
\ No newline at end of file
diff --git a/packager/react-packager/src/Resolver/polyfills/String.prototype.es6.js b/packager/react-packager/src/Resolver/polyfills/String.prototype.es6.js
index afc68d76ab53..59e5351d3d6b 100644
--- a/packager/react-packager/src/Resolver/polyfills/String.prototype.es6.js
+++ b/packager/react-packager/src/Resolver/polyfills/String.prototype.es6.js
@@ -44,19 +44,6 @@ if (!String.prototype.endsWith) {
   };
 }
 
-if (!String.prototype.contains) {
-  String.prototype.contains = function(search) {
-    'use strict';
-    if (this == null) {
-      throw TypeError();
-    }
-    var string = String(this);
-    var pos = arguments.length > 1 ?
-      (Number(arguments[1]) || 0) : 0;
-    return string.indexOf(String(search), pos) !== -1;
-  };
-}
-
 if (!String.prototype.repeat) {
   String.prototype.repeat = function(count) {
     'use strict';
@@ -83,3 +70,18 @@ if (!String.prototype.repeat) {
     return result;
   };
 }
+
+if (!String.prototype.includes) {
+  String.prototype.includes = function(search, start) {
+    'use strict';
+    if (typeof start !== 'number') {
+      start = 0;
+    }
+    
+    if (start + search.length > this.length) {
+      return false;
+    } else {
+      return this.indexOf(search, start) !== -1;
+    }
+  };
+}
diff --git a/packager/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js b/packager/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js
new file mode 100644
index 000000000000..ee7782eb781a
--- /dev/null
+++ b/packager/react-packager/src/Resolver/polyfills/__tests__/Object.es7-test.js
@@ -0,0 +1,120 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @emails oncall+jsinfra
+ */
+
+ /* eslint-disable fb-www/object-create-only-one-param */
+
+jest.autoMockOff();
+
+describe('Object (ES7)', () => {
+  beforeEach(() => {
+    delete Object.entries;
+    delete Object.values;
+    jest.resetModuleRegistry();
+    require('../Object.es7');
+  });
+
+  describe('Object.entries', () => {
+    it('should have a length of 1', () => {
+      expect(Object.entries.length).toBe(1);
+    });
+
+    it('should check for type', () => {
+      expect(Object.entries.bind(null, null)).toThrow(TypeError(
+        'Object.entries called on non-object'
+      ));
+      expect(Object.entries.bind(null, undefined)).toThrow(TypeError(
+        'Object.entries called on non-object'
+      ));
+      expect(Object.entries.bind(null, [])).not.toThrow();
+      expect(Object.entries.bind(null, () => {})).not.toThrow();
+      expect(Object.entries.bind(null, {})).not.toThrow();
+      expect(Object.entries.bind(null, 'abc')).not.toThrow();
+    });
+
+    it('should return enumerable entries', () => {
+      let foo = Object.defineProperties({}, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.entries(foo)).toEqual([['x', 10]]);
+
+      let bar = {x: 10, y: 20};
+      expect(Object.entries(bar)).toEqual([['x', 10], ['y', 20]]);
+    });
+
+    it('should work with proto-less objects', () => {
+      let foo = Object.create(null, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.entries(foo)).toEqual([['x', 10]]);
+    });
+
+    it('should return only own entries', () => {
+      let foo = Object.create({z: 30}, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.entries(foo)).toEqual([['x', 10]]);
+    });
+
+    it('should convert to object primitive string', () => {
+      expect(Object.entries('ab')).toEqual([['0', 'a'], ['1', 'b']]);
+    });
+  });
+
+  describe('Object.values', () => {
+    it('should have a length of 1', () => {
+      expect(Object.values.length).toBe(1);
+    });
+
+    it('should check for type', () => {
+      expect(Object.values.bind(null, null)).toThrow(TypeError(
+        'Object.values called on non-object'
+      ));
+      expect(Object.values.bind(null, [])).not.toThrow();
+      expect(Object.values.bind(null, () => {})).not.toThrow();
+      expect(Object.values.bind(null, {})).not.toThrow();
+    });
+
+    it('should return enumerable values', () => {
+      let foo = Object.defineProperties({}, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.values(foo)).toEqual([10]);
+
+      let bar = {x: 10, y: 20};
+      expect(Object.values(bar)).toEqual([10, 20]);
+    });
+
+    it('should work with proto-less objects', () => {
+      let foo = Object.create(null, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.values(foo)).toEqual([10]);
+    });
+
+    it('should return only own values', () => {
+      let foo = Object.create({z: 30}, {
+        x: {value: 10, enumerable: true},
+        y: {value: 20},
+      });
+
+      expect(Object.values(foo)).toEqual([10]);
+    });
+
+    it('should convert to object primitive string', () => {
+      expect(Object.values('ab')).toEqual(['a', 'b']);
+    });
+  });
+});
\ No newline at end of file
diff --git a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js
index b1254a688e6a..ff4422574089 100644
--- a/packager/react-packager/src/Resolver/polyfills/babelHelpers.js
+++ b/packager/react-packager/src/Resolver/polyfills/babelHelpers.js
@@ -7,189 +7,223 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-/* global self:true */
-/* eslint-disable strict */
+/* eslint-disable */
 
 // Created by running:
-// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits objectWithoutProperties possibleConstructorReturn slicedToArray toConsumableArray'.split(' '))
+// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits  interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray '.split(' '))
 // then replacing the `global` reference in the last line to also use `this`.
-
-(function (global) {
-  var babelHelpers = global.babelHelpers = {};
-
-  babelHelpers.createRawReactElement = (function () {
-    var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7;
-    return function createRawReactElement(type, key, props) {
-      return {
-        $$typeof: REACT_ELEMENT_TYPE,
-        type: type,
-        key: key,
-        ref: null,
-        props: props,
-        _owner: null
-      };
+//
+// actually, that's a lie, because babel6 omits _extends and createRawReactElement
+
+var babelHelpers = global.babelHelpers = {};
+
+babelHelpers.createRawReactElement = (function () {
+  var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7;
+  return function createRawReactElement(type, key, props) {
+    return {
+      $$typeof: REACT_ELEMENT_TYPE,
+      type: type,
+      key: key,
+      ref: null,
+      props: props,
+      _owner: null
     };
-  })();
-
-  babelHelpers.classCallCheck = function (instance, Constructor) {
-    if (!(instance instanceof Constructor)) {
-      throw new TypeError("Cannot call a class as a function");
-    }
   };
-
-  babelHelpers.createClass = (function () {
-    function defineProperties(target, props) {
-      for (var i = 0; i < props.length; i++) {
-        var descriptor = props[i];
-        descriptor.enumerable = descriptor.enumerable || false;
-        descriptor.configurable = true;
-        if ("value" in descriptor) descriptor.writable = true;
-        Object.defineProperty(target, descriptor.key, descriptor);
-      }
-    }
-
-    return function (Constructor, protoProps, staticProps) {
-      if (protoProps) defineProperties(Constructor.prototype, protoProps);
-      if (staticProps) defineProperties(Constructor, staticProps);
-      return Constructor;
-    };
-  })();
-
-  babelHelpers.defineProperty = function (obj, key, value) {
-    if (key in obj) {
-      Object.defineProperty(obj, key, {
-        value: value,
-        enumerable: true,
-        configurable: true,
-        writable: true
-      });
-    } else {
-      obj[key] = value;
+})();
+
+babelHelpers.classCallCheck = function (instance, Constructor) {
+  if (!(instance instanceof Constructor)) {
+    throw new TypeError("Cannot call a class as a function");
+  }
+};
+
+babelHelpers.createClass = (function () {
+  function defineProperties(target, props) {
+    for (var i = 0; i < props.length; i++) {
+      var descriptor = props[i];
+      descriptor.enumerable = descriptor.enumerable || false;
+      descriptor.configurable = true;
+      if ("value" in descriptor) descriptor.writable = true;
+      Object.defineProperty(target, descriptor.key, descriptor);
     }
+  }
 
-    return obj;
+  return function (Constructor, protoProps, staticProps) {
+    if (protoProps) defineProperties(Constructor.prototype, protoProps);
+    if (staticProps) defineProperties(Constructor, staticProps);
+    return Constructor;
   };
+})();
+
+babelHelpers.defineProperty = function (obj, key, value) {
+  if (key in obj) {
+    Object.defineProperty(obj, key, {
+      value: value,
+      enumerable: true,
+      configurable: true,
+      writable: true
+    });
+  } else {
+    obj[key] = value;
+  }
+
+  return obj;
+};
 
-  babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) {
-    for (var i = 1; i < arguments.length; i++) {
-      var source = arguments[i];
+babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) {
+  for (var i = 1; i < arguments.length; i++) {
+    var source = arguments[i];
 
-      for (var key in source) {
-        if (Object.prototype.hasOwnProperty.call(source, key)) {
-          target[key] = source[key];
-        }
+    for (var key in source) {
+      if (Object.prototype.hasOwnProperty.call(source, key)) {
+        target[key] = source[key];
       }
     }
+  }
 
-    return target;
-  };
+  return target;
+};
 
-  babelHelpers.get = function get(object, property, receiver) {
-    if (object === null) object = Function.prototype;
-    var desc = Object.getOwnPropertyDescriptor(object, property);
+babelHelpers.get = function get(object, property, receiver) {
+  if (object === null) object = Function.prototype;
+  var desc = Object.getOwnPropertyDescriptor(object, property);
 
-    if (desc === undefined) {
-      var parent = Object.getPrototypeOf(object);
+  if (desc === undefined) {
+    var parent = Object.getPrototypeOf(object);
 
-      if (parent === null) {
-        return undefined;
-      } else {
-        return get(parent, property, receiver);
-      }
-    } else if ("value" in desc) {
-      return desc.value;
+    if (parent === null) {
+      return undefined;
     } else {
-      var getter = desc.get;
-
-      if (getter === undefined) {
-        return undefined;
-      }
+      return get(parent, property, receiver);
+    }
+  } else if ("value" in desc) {
+    return desc.value;
+  } else {
+    var getter = desc.get;
 
-      return getter.call(receiver);
+    if (getter === undefined) {
+      return undefined;
     }
-  };
 
-  babelHelpers.inherits = function (subClass, superClass) {
-    if (typeof superClass !== "function" && superClass !== null) {
-      throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+    return getter.call(receiver);
+  }
+};
+
+babelHelpers.inherits = function (subClass, superClass) {
+  if (typeof superClass !== "function" && superClass !== null) {
+    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+  }
+
+  subClass.prototype = Object.create(superClass && superClass.prototype, {
+    constructor: {
+      value: subClass,
+      enumerable: false,
+      writable: true,
+      configurable: true
     }
+  });
+  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+};
 
-    subClass.prototype = Object.create(superClass && superClass.prototype, {
-      constructor: {
-        value: subClass,
-        enumerable: false,
-        writable: true,
-        configurable: true
-      }
-    });
-    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+babelHelpers.interopRequireDefault = function (obj) {
+  return obj && obj.__esModule ? obj : {
+    default: obj
   };
+};
 
-  babelHelpers.objectWithoutProperties = function (obj, keys) {
-    var target = {};
+babelHelpers.interopRequireWildcard = function (obj) {
+  if (obj && obj.__esModule) {
+    return obj;
+  } else {
+    var newObj = {};
 
-    for (var i in obj) {
-      if (keys.indexOf(i) >= 0) continue;
-      if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
-      target[i] = obj[i];
+    if (obj != null) {
+      for (var key in obj) {
+        if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key];
+      }
     }
 
-    return target;
-  };
+    newObj.default = obj;
+    return newObj;
+  }
+};
 
-  babelHelpers.possibleConstructorReturn = function (self, call) {
-    if (!self) {
-      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
-    }
+babelHelpers.objectWithoutProperties = function (obj, keys) {
+  var target = {};
 
-    return call && (typeof call === "object" || typeof call === "function") ? call : self;
-  };
+  for (var i in obj) {
+    if (keys.indexOf(i) >= 0) continue;
+    if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
+    target[i] = obj[i];
+  }
+
+  return target;
+};
+
+babelHelpers.possibleConstructorReturn = function (self, call) {
+  if (!self) {
+    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+  }
 
-  babelHelpers.slicedToArray = (function () {
-    function sliceIterator(arr, i) {
-      var _arr = [];
-      var _n = true;
-      var _d = false;
-      var _e = undefined;
+  return call && (typeof call === "object" || typeof call === "function") ? call : self;
+};
 
+babelHelpers.slicedToArray = (function () {
+  function sliceIterator(arr, i) {
+    var _arr = [];
+    var _n = true;
+    var _d = false;
+    var _e = undefined;
+
+    try {
+      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
+        _arr.push(_s.value);
+
+        if (i && _arr.length === i) break;
+      }
+    } catch (err) {
+      _d = true;
+      _e = err;
+    } finally {
       try {
-        for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
-          _arr.push(_s.value);
-
-          if (i && _arr.length === i) break;
-        }
-      } catch (err) {
-        _d = true;
-        _e = err;
+        if (!_n && _i["return"]) _i["return"]();
       } finally {
-        try {
-          if (!_n && _i["return"]) _i["return"]();
-        } finally {
-          if (_d) throw _e;
-        }
+        if (_d) throw _e;
       }
-
-      return _arr;
     }
 
-    return function (arr, i) {
-      if (Array.isArray(arr)) {
-        return arr;
-      } else if (Symbol.iterator in Object(arr)) {
-        return sliceIterator(arr, i);
-      } else {
-        throw new TypeError("Invalid attempt to destructure non-iterable instance");
-      }
-    };
-  })();
+    return _arr;
+  }
 
-  babelHelpers.toConsumableArray = function (arr) {
+  return function (arr, i) {
     if (Array.isArray(arr)) {
-      for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
-
-      return arr2;
+      return arr;
+    } else if (Symbol.iterator in Object(arr)) {
+      return sliceIterator(arr, i);
     } else {
-      return Array.from(arr);
+      throw new TypeError("Invalid attempt to destructure non-iterable instance");
     }
   };
-})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);
+})();
+
+babelHelpers.taggedTemplateLiteral = function (strings, raw) {
+  return Object.freeze(Object.defineProperties(strings, {
+    raw: {
+      value: Object.freeze(raw)
+    }
+  }));
+};
+
+babelHelpers.toArray = function (arr) {
+  return Array.isArray(arr) ? arr : Array.from(arr);
+};
+
+babelHelpers.toConsumableArray = function (arr) {
+  if (Array.isArray(arr)) {
+    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
+
+    return arr2;
+  } else {
+    return Array.from(arr);
+  }
+};
diff --git a/packager/react-packager/src/Resolver/polyfills/console.js b/packager/react-packager/src/Resolver/polyfills/console.js
index e08e8ef7ce71..5406db73e67e 100644
--- a/packager/react-packager/src/Resolver/polyfills/console.js
+++ b/packager/react-packager/src/Resolver/polyfills/console.js
@@ -14,476 +14,492 @@
  * @nolint
  */
 
-(function(global) {
-  'use strict';
-
-  var inspect = (function() {
-    // Copyright Joyent, Inc. and other Node contributors.
-    //
-    // Permission is hereby granted, free of charge, to any person obtaining a
-    // copy of this software and associated documentation files (the
-    // "Software"), to deal in the Software without restriction, including
-    // without limitation the rights to use, copy, modify, merge, publish,
-    // distribute, sublicense, and/or sell copies of the Software, and to permit
-    // persons to whom the Software is furnished to do so, subject to the
-    // following conditions:
-    //
-    // The above copyright notice and this permission notice shall be included
-    // in all copies or substantial portions of the Software.
-    //
-    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-    // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-    // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-    // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-    // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-    // USE OR OTHER DEALINGS IN THE SOFTWARE.
-    //
-    // https://github.com/joyent/node/blob/master/lib/util.js
-
-    function inspect(obj, opts) {
-      var ctx = {
-        seen: [],
-        stylize: stylizeNoColor
-      };
-      return formatValue(ctx, obj, opts.depth);
-    }
-
-    function stylizeNoColor(str, styleType) {
-      return str;
-    }
-
-    function arrayToHash(array) {
-      var hash = {};
-
-      array.forEach(function(val, idx) {
-        hash[val] = true;
-      });
+/* eslint-disable */
+
+var inspect = (function() {
+  // Copyright Joyent, Inc. and other Node contributors.
+  //
+  // Permission is hereby granted, free of charge, to any person obtaining a
+  // copy of this software and associated documentation files (the
+  // "Software"), to deal in the Software without restriction, including
+  // without limitation the rights to use, copy, modify, merge, publish,
+  // distribute, sublicense, and/or sell copies of the Software, and to permit
+  // persons to whom the Software is furnished to do so, subject to the
+  // following conditions:
+  //
+  // The above copyright notice and this permission notice shall be included
+  // in all copies or substantial portions of the Software.
+  //
+  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+  // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+  // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+  // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+  // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+  // USE OR OTHER DEALINGS IN THE SOFTWARE.
+  //
+  // https://github.com/joyent/node/blob/master/lib/util.js
+
+  function inspect(obj, opts) {
+    var ctx = {
+      seen: [],
+      stylize: stylizeNoColor
+    };
+    return formatValue(ctx, obj, opts.depth);
+  }
 
-      return hash;
-    }
+  function stylizeNoColor(str, styleType) {
+    return str;
+  }
 
+  function arrayToHash(array) {
+    var hash = {};
 
-    function formatValue(ctx, value, recurseTimes) {
-      // Primitive types cannot have properties
-      var primitive = formatPrimitive(ctx, value);
-      if (primitive) {
-        return primitive;
-      }
+    array.forEach(function(val, idx) {
+      hash[val] = true;
+    });
 
-      // Look up the keys of the object.
-      var keys = Object.keys(value);
-      var visibleKeys = arrayToHash(keys);
+    return hash;
+  }
 
-      // IE doesn't make error fields non-enumerable
-      // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
-      if (isError(value)
-          && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
-        return formatError(value);
-      }
 
-      // Some type of object without properties can be shortcutted.
-      if (keys.length === 0) {
-        if (isFunction(value)) {
-          var name = value.name ? ': ' + value.name : '';
-          return ctx.stylize('[Function' + name + ']', 'special');
-        }
-        if (isRegExp(value)) {
-          return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
-        }
-        if (isDate(value)) {
-          return ctx.stylize(Date.prototype.toString.call(value), 'date');
-        }
-        if (isError(value)) {
-          return formatError(value);
-        }
-      }
+  function formatValue(ctx, value, recurseTimes) {
+    // Primitive types cannot have properties
+    var primitive = formatPrimitive(ctx, value);
+    if (primitive) {
+      return primitive;
+    }
 
-      var base = '', array = false, braces = ['{', '}'];
+    // Look up the keys of the object.
+    var keys = Object.keys(value);
+    var visibleKeys = arrayToHash(keys);
 
-      // Make Array say that they are Array
-      if (isArray(value)) {
-        array = true;
-        braces = ['[', ']'];
-      }
+    // IE doesn't make error fields non-enumerable
+    // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
+    if (isError(value)
+        && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
+      return formatError(value);
+    }
 
-      // Make functions say that they are functions
+    // Some type of object without properties can be shortcutted.
+    if (keys.length === 0) {
       if (isFunction(value)) {
-        var n = value.name ? ': ' + value.name : '';
-        base = ' [Function' + n + ']';
+        var name = value.name ? ': ' + value.name : '';
+        return ctx.stylize('[Function' + name + ']', 'special');
       }
-
-      // Make RegExps say that they are RegExps
       if (isRegExp(value)) {
-        base = ' ' + RegExp.prototype.toString.call(value);
+        return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
       }
-
-      // Make dates with properties first say the date
       if (isDate(value)) {
-        base = ' ' + Date.prototype.toUTCString.call(value);
+        return ctx.stylize(Date.prototype.toString.call(value), 'date');
       }
-
-      // Make error with message first say the error
       if (isError(value)) {
-        base = ' ' + formatError(value);
+        return formatError(value);
       }
+    }
 
-      if (keys.length === 0 && (!array || value.length == 0)) {
-        return braces[0] + base + braces[1];
-      }
+    var base = '', array = false, braces = ['{', '}'];
 
-      if (recurseTimes < 0) {
-        if (isRegExp(value)) {
-          return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
-        } else {
-          return ctx.stylize('[Object]', 'special');
-        }
-      }
+    // Make Array say that they are Array
+    if (isArray(value)) {
+      array = true;
+      braces = ['[', ']'];
+    }
 
-      ctx.seen.push(value);
+    // Make functions say that they are functions
+    if (isFunction(value)) {
+      var n = value.name ? ': ' + value.name : '';
+      base = ' [Function' + n + ']';
+    }
 
-      var output;
-      if (array) {
-        output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
-      } else {
-        output = keys.map(function(key) {
-          return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
-        });
-      }
+    // Make RegExps say that they are RegExps
+    if (isRegExp(value)) {
+      base = ' ' + RegExp.prototype.toString.call(value);
+    }
 
-      ctx.seen.pop();
+    // Make dates with properties first say the date
+    if (isDate(value)) {
+      base = ' ' + Date.prototype.toUTCString.call(value);
+    }
 
-      return reduceToSingleString(output, base, braces);
+    // Make error with message first say the error
+    if (isError(value)) {
+      base = ' ' + formatError(value);
     }
 
+    if (keys.length === 0 && (!array || value.length == 0)) {
+      return braces[0] + base + braces[1];
+    }
 
-    function formatPrimitive(ctx, value) {
-      if (isUndefined(value))
-        return ctx.stylize('undefined', 'undefined');
-      if (isString(value)) {
-        var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
-                                                 .replace(/'/g, "\\'")
-                                                 .replace(/\\"/g, '"') + '\'';
-        return ctx.stylize(simple, 'string');
+    if (recurseTimes < 0) {
+      if (isRegExp(value)) {
+        return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
+      } else {
+        return ctx.stylize('[Object]', 'special');
       }
-      if (isNumber(value))
-        return ctx.stylize('' + value, 'number');
-      if (isBoolean(value))
-        return ctx.stylize('' + value, 'boolean');
-      // For some reason typeof null is "object", so special case here.
-      if (isNull(value))
-        return ctx.stylize('null', 'null');
     }
 
+    ctx.seen.push(value);
 
-    function formatError(value) {
-      return '[' + Error.prototype.toString.call(value) + ']';
+    var output;
+    if (array) {
+      output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
+    } else {
+      output = keys.map(function(key) {
+        return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
+      });
     }
 
+    ctx.seen.pop();
 
-    function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
-      var output = [];
-      for (var i = 0, l = value.length; i < l; ++i) {
-        if (hasOwnProperty(value, String(i))) {
-          output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
-              String(i), true));
-        } else {
-          output.push('');
-        }
+    return reduceToSingleString(output, base, braces);
+  }
+
+
+  function formatPrimitive(ctx, value) {
+    if (isUndefined(value))
+      return ctx.stylize('undefined', 'undefined');
+    if (isString(value)) {
+      var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
+                                               .replace(/'/g, "\\'")
+                                               .replace(/\\"/g, '"') + '\'';
+      return ctx.stylize(simple, 'string');
+    }
+    if (isNumber(value))
+      return ctx.stylize('' + value, 'number');
+    if (isBoolean(value))
+      return ctx.stylize('' + value, 'boolean');
+    // For some reason typeof null is "object", so special case here.
+    if (isNull(value))
+      return ctx.stylize('null', 'null');
+  }
+
+
+  function formatError(value) {
+    return '[' + Error.prototype.toString.call(value) + ']';
+  }
+
+
+  function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
+    var output = [];
+    for (var i = 0, l = value.length; i < l; ++i) {
+      if (hasOwnProperty(value, String(i))) {
+        output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+            String(i), true));
+      } else {
+        output.push('');
       }
-      keys.forEach(function(key) {
-        if (!key.match(/^\d+$/)) {
-          output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
-              key, true));
-        }
-      });
-      return output;
     }
+    keys.forEach(function(key) {
+      if (!key.match(/^\d+$/)) {
+        output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
+            key, true));
+      }
+    });
+    return output;
+  }
 
 
-    function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
-      var name, str, desc;
-      desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
-      if (desc.get) {
-        if (desc.set) {
-          str = ctx.stylize('[Getter/Setter]', 'special');
-        } else {
-          str = ctx.stylize('[Getter]', 'special');
-        }
+  function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
+    var name, str, desc;
+    desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
+    if (desc.get) {
+      if (desc.set) {
+        str = ctx.stylize('[Getter/Setter]', 'special');
       } else {
-        if (desc.set) {
-          str = ctx.stylize('[Setter]', 'special');
-        }
+        str = ctx.stylize('[Getter]', 'special');
       }
-      if (!hasOwnProperty(visibleKeys, key)) {
-        name = '[' + key + ']';
+    } else {
+      if (desc.set) {
+        str = ctx.stylize('[Setter]', 'special');
       }
-      if (!str) {
-        if (ctx.seen.indexOf(desc.value) < 0) {
-          if (isNull(recurseTimes)) {
-            str = formatValue(ctx, desc.value, null);
+    }
+    if (!hasOwnProperty(visibleKeys, key)) {
+      name = '[' + key + ']';
+    }
+    if (!str) {
+      if (ctx.seen.indexOf(desc.value) < 0) {
+        if (isNull(recurseTimes)) {
+          str = formatValue(ctx, desc.value, null);
+        } else {
+          str = formatValue(ctx, desc.value, recurseTimes - 1);
+        }
+        if (str.indexOf('\n') > -1) {
+          if (array) {
+            str = str.split('\n').map(function(line) {
+              return '  ' + line;
+            }).join('\n').substr(2);
           } else {
-            str = formatValue(ctx, desc.value, recurseTimes - 1);
+            str = '\n' + str.split('\n').map(function(line) {
+              return '   ' + line;
+            }).join('\n');
           }
-          if (str.indexOf('\n') > -1) {
-            if (array) {
-              str = str.split('\n').map(function(line) {
-                return '  ' + line;
-              }).join('\n').substr(2);
-            } else {
-              str = '\n' + str.split('\n').map(function(line) {
-                return '   ' + line;
-              }).join('\n');
-            }
-          }
-        } else {
-          str = ctx.stylize('[Circular]', 'special');
         }
+      } else {
+        str = ctx.stylize('[Circular]', 'special');
       }
-      if (isUndefined(name)) {
-        if (array && key.match(/^\d+$/)) {
-          return str;
-        }
-        name = JSON.stringify('' + key);
-        if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
-          name = name.substr(1, name.length - 2);
-          name = ctx.stylize(name, 'name');
-        } else {
-          name = name.replace(/'/g, "\\'")
-                     .replace(/\\"/g, '"')
-                     .replace(/(^"|"$)/g, "'");
-          name = ctx.stylize(name, 'string');
-        }
+    }
+    if (isUndefined(name)) {
+      if (array && key.match(/^\d+$/)) {
+        return str;
+      }
+      name = JSON.stringify('' + key);
+      if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
+        name = name.substr(1, name.length - 2);
+        name = ctx.stylize(name, 'name');
+      } else {
+        name = name.replace(/'/g, "\\'")
+                   .replace(/\\"/g, '"')
+                   .replace(/(^"|"$)/g, "'");
+        name = ctx.stylize(name, 'string');
       }
-
-      return name + ': ' + str;
     }
 
+    return name + ': ' + str;
+  }
 
-    function reduceToSingleString(output, base, braces) {
-      var numLinesEst = 0;
-      var length = output.reduce(function(prev, cur) {
-        numLinesEst++;
-        if (cur.indexOf('\n') >= 0) numLinesEst++;
-        return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
-      }, 0);
-
-      if (length > 60) {
-        return braces[0] +
-               (base === '' ? '' : base + '\n ') +
-               ' ' +
-               output.join(',\n  ') +
-               ' ' +
-               braces[1];
-      }
 
-      return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+  function reduceToSingleString(output, base, braces) {
+    var numLinesEst = 0;
+    var length = output.reduce(function(prev, cur) {
+      numLinesEst++;
+      if (cur.indexOf('\n') >= 0) numLinesEst++;
+      return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
+    }, 0);
+
+    if (length > 60) {
+      return braces[0] +
+             (base === '' ? '' : base + '\n ') +
+             ' ' +
+             output.join(',\n  ') +
+             ' ' +
+             braces[1];
     }
 
+    return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
+  }
 
-    // NOTE: These type checking functions intentionally don't use `instanceof`
-    // because it is fragile and can be easily faked with `Object.create()`.
-    function isArray(ar) {
-      return Array.isArray(ar);
-    }
-
-    function isBoolean(arg) {
-      return typeof arg === 'boolean';
-    }
 
-    function isNull(arg) {
-      return arg === null;
-    }
+  // NOTE: These type checking functions intentionally don't use `instanceof`
+  // because it is fragile and can be easily faked with `Object.create()`.
+  function isArray(ar) {
+    return Array.isArray(ar);
+  }
 
-    function isNullOrUndefined(arg) {
-      return arg == null;
-    }
+  function isBoolean(arg) {
+    return typeof arg === 'boolean';
+  }
 
-    function isNumber(arg) {
-      return typeof arg === 'number';
-    }
+  function isNull(arg) {
+    return arg === null;
+  }
 
-    function isString(arg) {
-      return typeof arg === 'string';
-    }
+  function isNullOrUndefined(arg) {
+    return arg == null;
+  }
 
-    function isSymbol(arg) {
-      return typeof arg === 'symbol';
-    }
+  function isNumber(arg) {
+    return typeof arg === 'number';
+  }
 
-    function isUndefined(arg) {
-      return arg === void 0;
-    }
+  function isString(arg) {
+    return typeof arg === 'string';
+  }
 
-    function isRegExp(re) {
-      return isObject(re) && objectToString(re) === '[object RegExp]';
-    }
+  function isSymbol(arg) {
+    return typeof arg === 'symbol';
+  }
 
-    function isObject(arg) {
-      return typeof arg === 'object' && arg !== null;
-    }
+  function isUndefined(arg) {
+    return arg === void 0;
+  }
 
-    function isDate(d) {
-      return isObject(d) && objectToString(d) === '[object Date]';
-    }
+  function isRegExp(re) {
+    return isObject(re) && objectToString(re) === '[object RegExp]';
+  }
 
-    function isError(e) {
-      return isObject(e) &&
-          (objectToString(e) === '[object Error]' || e instanceof Error);
-    }
+  function isObject(arg) {
+    return typeof arg === 'object' && arg !== null;
+  }
 
-    function isFunction(arg) {
-      return typeof arg === 'function';
-    }
+  function isDate(d) {
+    return isObject(d) && objectToString(d) === '[object Date]';
+  }
 
-    function isPrimitive(arg) {
-      return arg === null ||
-             typeof arg === 'boolean' ||
-             typeof arg === 'number' ||
-             typeof arg === 'string' ||
-             typeof arg === 'symbol' ||  // ES6 symbol
-             typeof arg === 'undefined';
-    }
+  function isError(e) {
+    return isObject(e) &&
+        (objectToString(e) === '[object Error]' || e instanceof Error);
+  }
 
-    function objectToString(o) {
-      return Object.prototype.toString.call(o);
-    }
+  function isFunction(arg) {
+    return typeof arg === 'function';
+  }
 
-    function hasOwnProperty(obj, prop) {
-      return Object.prototype.hasOwnProperty.call(obj, prop);
-    }
+  function isPrimitive(arg) {
+    return arg === null ||
+           typeof arg === 'boolean' ||
+           typeof arg === 'number' ||
+           typeof arg === 'string' ||
+           typeof arg === 'symbol' ||  // ES6 symbol
+           typeof arg === 'undefined';
+  }
 
-    return inspect;
-  })();
+  function objectToString(o) {
+    return Object.prototype.toString.call(o);
+  }
 
+  function hasOwnProperty(obj, prop) {
+    return Object.prototype.hasOwnProperty.call(obj, prop);
+  }
 
-  var OBJECT_COLUMN_NAME = '(index)';
-  var LOG_LEVELS = {
-    trace: 0,
-    info: 1,
-    warn: 2,
-    error: 3
-  };
+  return inspect;
+})();
 
-  function setupConsole(global) {
 
-    var originalConsole = global.console;
+var OBJECT_COLUMN_NAME = '(index)';
+var LOG_LEVELS = {
+  trace: 0,
+  info: 1,
+  warn: 2,
+  error: 3
+};
 
-    if (!global.nativeLoggingHook) {
-      return;
-    }
+function setupConsole(global) {
+  if (!global.nativeLoggingHook) {
+    return;
+  }
 
-    function getNativeLogFunction(level) {
-      return function() {
-        var str = Array.prototype.map.call(arguments, function(arg) {
+  function getNativeLogFunction(level) {
+    return function() {
+      var str;
+      if (arguments.length === 1 && typeof arguments[0] === 'string') {
+        str = arguments[0];
+      } else {
+        str = Array.prototype.map.call(arguments, function(arg) {
           return inspect(arg, {depth: 10});
         }).join(', ');
-        if (str.slice(0, 10) === "'Warning: " && level >= LOG_LEVELS.error) {
-          // React warnings use console.error so that a stack trace is shown,
-          // but we don't (currently) want these to show a redbox
-          // (Note: Logic duplicated in ExceptionsManager.js.)
-          level = LOG_LEVELS.warn;
-        }
-        global.nativeLoggingHook(str, level);
-      };
-    }
+      }
 
-    var repeat = function(element, n) {
-      return Array.apply(null, Array(n)).map(function() { return element; });
+      var logLevel = level;
+      if (str.slice(0, 9) === 'Warning: ' && logLevel >= LOG_LEVELS.error) {
+        // React warnings use console.error so that a stack trace is shown,
+        // but we don't (currently) want these to show a redbox
+        // (Note: Logic duplicated in ExceptionsManager.js.)
+        logLevel = LOG_LEVELS.warn;
+      }
+      global.nativeLoggingHook(str, logLevel);
     };
+  }
 
-    function consoleTablePolyfill(rows) {
-      // convert object -> array
-      if (!Array.isArray(rows)) {
-        var data = rows;
-        rows = [];
-        for (var key in data) {
-          if (data.hasOwnProperty(key)) {
-            var row = data[key];
-            row[OBJECT_COLUMN_NAME] = key;
-            rows.push(row);
-          }
-        }
-      }
-      if (rows.length === 0) {
-        global.nativeLoggingHook('', LOG_LEVELS.info);
-        return;
-      }
+  var repeat = function(element, n) {
+    return Array.apply(null, Array(n)).map(function() { return element; });
+  };
 
-      var columns = Object.keys(rows[0]).sort();
-      var stringRows = [];
-      var columnWidths = [];
-
-      // Convert each cell to a string. Also
-      // figure out max cell width for each column
-      columns.forEach(function(k, i) {
-        columnWidths[i] = k.length;
-        for (var j = 0; j < rows.length; j++) {
-          var cellStr = rows[j][k].toString();
-          stringRows[j] = stringRows[j] || [];
-          stringRows[j][i] = cellStr;
-          columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
+  function consoleTablePolyfill(rows) {
+    // convert object -> array
+    if (!Array.isArray(rows)) {
+      var data = rows;
+      rows = [];
+      for (var key in data) {
+        if (data.hasOwnProperty(key)) {
+          var row = data[key];
+          row[OBJECT_COLUMN_NAME] = key;
+          rows.push(row);
         }
-      });
+      }
+    }
+    if (rows.length === 0) {
+      global.nativeLoggingHook('', LOG_LEVELS.info);
+      return;
+    }
 
-      // Join all elements in the row into a single string with | separators
-      // (appends extra spaces to each cell to make separators  | alligned)
-      var joinRow = function(row, space) {
-        var cells = row.map(function(cell, i) {
-          var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
-          return cell + extraSpaces;
-        });
-        space = space || ' ';
-        return cells.join(space + '|' + space);
-      };
-
-      var separators = columnWidths.map(function(columnWidth) {
-        return repeat('-', columnWidth).join('');
+    var columns = Object.keys(rows[0]).sort();
+    var stringRows = [];
+    var columnWidths = [];
+
+    // Convert each cell to a string. Also
+    // figure out max cell width for each column
+    columns.forEach(function(k, i) {
+      columnWidths[i] = k.length;
+      for (var j = 0; j < rows.length; j++) {
+        var cellStr = rows[j][k].toString();
+        stringRows[j] = stringRows[j] || [];
+        stringRows[j][i] = cellStr;
+        columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
+      }
+    });
+
+    // Join all elements in the row into a single string with | separators
+    // (appends extra spaces to each cell to make separators  | alligned)
+    var joinRow = function(row, space) {
+      var cells = row.map(function(cell, i) {
+        var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
+        return cell + extraSpaces;
       });
-      var separatorRow = joinRow(separators, '-');
-      var header = joinRow(columns);
-      var table = [header, separatorRow];
+      space = space || ' ';
+      return cells.join(space + '|' + space);
+    };
 
-      for (var i = 0; i < rows.length; i++) {
-        table.push(joinRow(stringRows[i]));
-      }
+    var separators = columnWidths.map(function(columnWidth) {
+      return repeat('-', columnWidth).join('');
+    });
+    var separatorRow = joinRow(separators, '-');
+    var header = joinRow(columns);
+    var table = [header, separatorRow];
 
-      // Notice extra empty line at the beginning.
-      // Native logging hook adds "RCTLog >" at the front of every
-      // logged string, which would shift the header and screw up
-      // the table
-      global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info);
+    for (var i = 0; i < rows.length; i++) {
+      table.push(joinRow(stringRows[i]));
     }
 
-    global.console = {
-      error: getNativeLogFunction(LOG_LEVELS.error),
-      info: getNativeLogFunction(LOG_LEVELS.info),
-      log: getNativeLogFunction(LOG_LEVELS.info),
-      warn: getNativeLogFunction(LOG_LEVELS.warn),
-      trace: getNativeLogFunction(LOG_LEVELS.trace),
-      table: consoleTablePolyfill
-    };
+    // Notice extra empty line at the beginning.
+    // Native logging hook adds "RCTLog >" at the front of every
+    // logged string, which would shift the header and screw up
+    // the table
+    global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info);
+  }
 
-    // If available, also call the original `console` method since that is
-    // sometimes useful. Ex: on OS X, this will let you see rich output in
-    // the Safari Web Inspector console.
-    if (__DEV__ && originalConsole) {
-      Object.keys(global.console).forEach(methodName => {
-        var reactNativeMethod = global.console[methodName];
-        if (originalConsole[methodName]) {
-          global.console[methodName] = function() {
-            originalConsole[methodName](...arguments);
-            reactNativeMethod.apply(global.console, arguments);
-          };
-        }
-      });
-    }
+  // Preserve the original `console` as `originalConsole`
+  var originalConsole = global.console;
+  var descriptor = Object.getOwnPropertyDescriptor(global, 'console');
+  if (descriptor) {
+    Object.defineProperty(global, 'originalConsole', descriptor);
   }
 
-  if (typeof module !== 'undefined') {
-    module.exports = setupConsole;
-  } else {
-    setupConsole(global);
+  var console = {
+    error: getNativeLogFunction(LOG_LEVELS.error),
+    info: getNativeLogFunction(LOG_LEVELS.info),
+    log: getNativeLogFunction(LOG_LEVELS.info),
+    warn: getNativeLogFunction(LOG_LEVELS.warn),
+    trace: getNativeLogFunction(LOG_LEVELS.trace),
+    table: consoleTablePolyfill
+  };
+
+  // don't reassign to the original descriptor. breaks on ios7
+  Object.defineProperty(global, 'console', {
+    value: console,
+    configurable: descriptor ? descriptor.configurable : true,
+    enumerable: descriptor ? descriptor.enumerable : true,
+    writable: descriptor ? descriptor.writable : true,
+  });
+
+  // If available, also call the original `console` method since that is
+  // sometimes useful. Ex: on OS X, this will let you see rich output in
+  // the Safari Web Inspector console.
+  if (__DEV__ && originalConsole) {
+    Object.keys(console).forEach(methodName => {
+      var reactNativeMethod = console[methodName];
+      if (originalConsole[methodName]) {
+        console[methodName] = function() {
+          originalConsole[methodName](...arguments);
+          reactNativeMethod.apply(console, arguments);
+        };
+      }
+    });
   }
+}
 
-})(this);
+if (typeof module !== 'undefined') {
+  module.exports = setupConsole;
+} else {
+  setupConsole(global);
+}
diff --git a/packager/react-packager/src/Resolver/polyfills/error-guard.js b/packager/react-packager/src/Resolver/polyfills/error-guard.js
index d4aa41225d84..d013ec1104c8 100644
--- a/packager/react-packager/src/Resolver/polyfills/error-guard.js
+++ b/packager/react-packager/src/Resolver/polyfills/error-guard.js
@@ -13,79 +13,75 @@
  * before any of the modules, this ErrorUtils must be defined (and the handler
  * set) globally before requiring anything.
  */
-/* eslint global-strict:0 */
-(function(global) {
-  var ErrorUtils = {
-    _inGuard: 0,
-    _globalHandler: null,
-    setGlobalHandler: function(fun) {
-      ErrorUtils._globalHandler = fun;
-    },
-    reportError: function(error) {
-      ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);
-    },
-    reportFatalError: function(error) {
-      ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true);
-    },
-    applyWithGuard: function(fun, context, args) {
-      try {
-        ErrorUtils._inGuard++;
-        return fun.apply(context, args);
-      } catch (e) {
-        ErrorUtils.reportError(e);
-      } finally {
-        ErrorUtils._inGuard--;
-      }
-    },
-    applyWithGuardIfNeeded: function(fun, context, args) {
-      if (ErrorUtils.inGuard()) {
-        return fun.apply(context, args);
-      } else {
-        ErrorUtils.applyWithGuard(fun, context, args);
-      }
-    },
-    inGuard: function() {
-      return ErrorUtils._inGuard;
-    },
-    guard: function(fun, name, context) {
-      if (typeof fun !== 'function') {
-        console.warn('A function must be passed to ErrorUtils.guard, got ', fun);
-        return null;
-      }
-      name = name || fun.name || '<generated guard>';
-      function guarded() {
-        return (
-          ErrorUtils.applyWithGuard(
-            fun,
-            context || this,
-            arguments,
-            null,
-            name
-          )
-        );
-      }
-
-      return guarded;
+/* eslint strict:0 */
+var ErrorUtils = {
+  _inGuard: 0,
+  _globalHandler: null,
+  setGlobalHandler: function(fun) {
+    ErrorUtils._globalHandler = fun;
+  },
+  getGlobalHandler: function() {
+    return ErrorUtils._globalHandler;
+  },
+  reportError: function(error) {
+    ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);
+  },
+  reportFatalError: function(error) {
+    ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true);
+  },
+  applyWithGuard: function(fun, context, args) {
+    try {
+      ErrorUtils._inGuard++;
+      return fun.apply(context, args);
+    } catch (e) {
+      ErrorUtils.reportError(e);
+    } finally {
+      ErrorUtils._inGuard--;
     }
-  };
-  global.ErrorUtils = ErrorUtils;
-
-  /**
-   * This is the error handler that is called when we encounter an exception
-   * when loading a module.
-   */
-  function setupErrorGuard() {
-    var onError = function(e) {
-      global.console.error(
-        'Error: ' +
-        '\n stack: ' + e.stack +
-        '\n line: ' + e.line +
-        '\n message: ' + e.message,
-        e
+  },
+  applyWithGuardIfNeeded: function(fun, context, args) {
+    if (ErrorUtils.inGuard()) {
+      return fun.apply(context, args);
+    } else {
+      ErrorUtils.applyWithGuard(fun, context, args);
+    }
+  },
+  inGuard: function() {
+    return ErrorUtils._inGuard;
+  },
+  guard: function(fun, name, context) {
+    if (typeof fun !== 'function') {
+      console.warn('A function must be passed to ErrorUtils.guard, got ', fun);
+      return null;
+    }
+    name = name || fun.name || '<generated guard>';
+    function guarded() {
+      return (
+        ErrorUtils.applyWithGuard(
+          fun,
+          context || this,
+          arguments,
+          null,
+          name
+        )
       );
-    };
-    global.ErrorUtils.setGlobalHandler(onError);
+    }
+
+    return guarded;
   }
+};
+global.ErrorUtils = ErrorUtils;
+
+/**
+ * This is the error handler that is called when we encounter an exception
+ * when loading a module. This will report any errors encountered before
+ * ExceptionsManager is configured.
+ */
+function setupErrorGuard() {
+  var onError = function(e) {
+    global.console.error('Error: ' + e.message + ', stack:\n' + e.stack);
+  };
+  global.ErrorUtils.setGlobalHandler(onError);
+}
 
-  setupErrorGuard();
-})(this);
+setupErrorGuard();
diff --git a/packager/react-packager/src/Resolver/polyfills/polyfills.js b/packager/react-packager/src/Resolver/polyfills/polyfills.js
index 84bf2d3d2f9b..5652334218a7 100644
--- a/packager/react-packager/src/Resolver/polyfills/polyfills.js
+++ b/packager/react-packager/src/Resolver/polyfills/polyfills.js
@@ -15,7 +15,7 @@
 
 // WARNING: This is an optimized version that fails on hasOwnProperty checks
 // and non objects. It's not spec-compliant. It's a perf optimization.
-/* eslint global-strict:0 */
+/* eslint strict:0 */
 Object.assign = function(target, sources) {
   if (__DEV__) {
     if (target == null) {
@@ -39,7 +39,7 @@ Object.assign = function(target, sources) {
       if (typeof nextSource !== 'object' &&
           typeof nextSource !== 'function') {
         throw new TypeError(
-          'In this environment the target of assign MUST be an object.' +
+          'In this environment the sources for assign MUST be an object.' +
           'This error is a performance optimization and not spec compliant.'
         );
       }
@@ -54,7 +54,7 @@ Object.assign = function(target, sources) {
         var hasOwnProperty = Object.prototype.hasOwnProperty;
         if (!hasOwnProperty.call(nextSource, key)) {
           throw new TypeError(
-            'One of the sources to assign has an enumerable key on the ' +
+            'One of the sources for assign has an enumerable key on the ' +
             'prototype chain. This is an edge case that we do not support. ' +
             'This error is a performance optimization and not spec compliant.'
           );
diff --git a/packager/react-packager/src/Resolver/polyfills/prelude.js b/packager/react-packager/src/Resolver/polyfills/prelude.js
index a1004b7d4cfd..02386ae1016a 100644
--- a/packager/react-packager/src/Resolver/polyfills/prelude.js
+++ b/packager/react-packager/src/Resolver/polyfills/prelude.js
@@ -1,5 +1,4 @@
-/* eslint global-strict:0 */
-__DEV__ = false;
+/* eslint strict:0 */
+global.__DEV__ = false;
 
-/* global __BUNDLE_START_TIME__:true */
-__BUNDLE_START_TIME__ = Date.now();
+global.__BUNDLE_START_TIME__ = Date.now();
diff --git a/packager/react-packager/src/Resolver/polyfills/prelude_dev.js b/packager/react-packager/src/Resolver/polyfills/prelude_dev.js
index 14b97faad6d5..99b9de2c1137 100644
--- a/packager/react-packager/src/Resolver/polyfills/prelude_dev.js
+++ b/packager/react-packager/src/Resolver/polyfills/prelude_dev.js
@@ -1,5 +1,4 @@
-/* eslint global-strict:0 */
-__DEV__ = true;
+/* eslint strict:0 */
+global.__DEV__ = true;
 
-/* global __BUNDLE_START_TIME__:true */
-__BUNDLE_START_TIME__ = Date.now();
+global.__BUNDLE_START_TIME__ = Date.now();
diff --git a/packager/react-packager/src/Resolver/polyfills/require-unbundle.js b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js
new file mode 100644
index 000000000000..9c1e36e77ff5
--- /dev/null
+++ b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js
@@ -0,0 +1,94 @@
+'use strict';
+
+const {ErrorUtils, nativeRequire} = global;
+global.require = require;
+global.__d = define;
+
+const modules = Object.create(null);
+
+const loadModule = ErrorUtils ?
+  guardedLoadModule : loadModuleImplementation;
+
+function define(moduleId, factory) {
+  if (moduleId in modules) {
+    // prevent repeated calls to `global.nativeRequire` to overwrite modules
+    // that are already loaded
+    return;
+  }
+  modules[moduleId] = {
+    factory,
+    hasError: false,
+    exports: undefined,
+  };
+}
+
+function require(moduleId) {
+  const module = modules[moduleId];
+  return module && module.exports || loadModule(moduleId, module);
+}
+
+function guardedLoadModule(moduleId, module) {
+  try {
+    return loadModuleImplementation(moduleId, module);
+  } catch (e) {
+    ErrorUtils.reportFatalError(e);
+  }
+}
+
+function loadModuleImplementation(moduleId, module) {
+  if (!module) {
+    nativeRequire(moduleId);
+    module = modules[moduleId];
+  }
+
+  if (!module) {
+    throw unknownModuleError(moduleId);
+  }
+
+  if (module.hasError) {
+    throw moduleThrewError(moduleId);
+  }
+
+  // `require` calls int  the require polyfill itself are not analyzed and
+  // replaced so that they use numeric module IDs.
+  // The systrace module will expose itself on the require function so that
+  // it can be used here.
+  // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686)
+  const {Systrace} = require;
+
+  const exports = module.exports = {};
+  const {factory} = module;
+  try {
+    if (__DEV__) {
+      Systrace.beginEvent('JS_require_' + moduleId);
+    }
+
+    const moduleObject = {exports};
+    factory(global, require, moduleObject, exports);
+
+    if (__DEV__) {
+      Systrace.endEvent();
+    }
+    return (module.exports = moduleObject.exports);
+  } catch (e) {
+    module.hasError = true;
+    module.exports = undefined;
+  }
+}
+
+function unknownModuleError(id) {
+  let message = 'Requiring unknown module "' + id + '".';
+  if (__DEV__) {
+    message +=
+      'If you are sure the module is there, try restarting the packager or running "npm install".';
+  }
+  return Error(message);
+}
+
+function moduleThrewError(id) {
+  return Error('Requiring module "' + id + '", which threw an exception.');
+}
+
+if (__DEV__) {
+  require.Systrace = { beginEvent: () => {}, endEvent: () => {} };
+}
diff --git a/packager/react-packager/src/Resolver/polyfills/require.js b/packager/react-packager/src/Resolver/polyfills/require.js
index 6ebfee90a1b2..d5b0cd81a2f5 100644
--- a/packager/react-packager/src/Resolver/polyfills/require.js
+++ b/packager/react-packager/src/Resolver/polyfills/require.js
@@ -1,71 +1,157 @@
 /* eslint strict:0 */
-(function(global) {
-  var modules = Object.create(null);
-  var inGuard = false;
-
-  function define(id, factory) {
-    modules[id] = {
-      factory,
-      module: {exports: {}},
-      isInitialized: false,
-      hasError: false,
-    };
+var modules = Object.create(null);
+var inGuard = false;
+
+function define(id, factory) {
+  modules[id] = {
+    factory,
+    module: {exports: {}},
+    isInitialized: false,
+    hasError: false,
+  };
+
+  if (__DEV__) { // HMR
+    Object.assign(modules[id].module, {
+      hot: {
+        acceptCallback: null,
+        accept: function(callback) {
+          modules[id].module.hot.acceptCallback = callback;
+        }
+      }
+    });
   }
+}
 
-  function require(id) {
-    var mod = modules[id];
-    if (mod && mod.isInitialized) {
-      return mod.module.exports;
-    }
+function require(id) {
+  var mod = modules[id];
+  if (mod && mod.isInitialized) {
+    return mod.module.exports;
+  }
+
+  return requireImpl(id);
+}
 
-    return requireImpl(id);
+function requireImpl(id) {
+  if (global.ErrorUtils && !inGuard) {
+    inGuard = true;
+    var returnValue;
+    try {
+      returnValue = requireImpl.apply(this, arguments);
+    } catch (e) {
+      global.ErrorUtils.reportFatalError(e);
+    }
+    inGuard = false;
+    return returnValue;
   }
 
-  function requireImpl(id) {
-    if (global.ErrorUtils && !inGuard) {
-      inGuard = true;
-      var returnValue;
-      try {
-        returnValue = requireImpl.apply(this, arguments);
-      } catch (e) {
-        global.ErrorUtils.reportFatalError(e);
-      }
-      inGuard = false;
-      return returnValue;
+  var mod = modules[id];
+  if (!mod) {
+    var msg = 'Requiring unknown module "' + id + '"';
+    if (__DEV__) {
+      msg += '. If you are sure the module is there, try restarting the packager or running "npm install".';
     }
+    throw new Error(msg);
+  }
+
+  if (mod.hasError) {
+    throw new Error(
+      'Requiring module "' + id + '" which threw an exception'
+    );
+  }
+
+  try {
+    // We must optimistically mark mod as initialized before running the factory to keep any
+    // require cycles inside the factory from causing an infinite require loop.
+    mod.isInitialized = true;
+
+    __DEV__ && Systrace().beginEvent('JS_require_' + id);
+
+    // keep args in sync with with defineModuleCode in
+    // packager/react-packager/src/Resolver/index.js
+    mod.factory.call(global, global, require, mod.module, mod.module.exports);
+
+    __DEV__ && Systrace().endEvent();
+  } catch (e) {
+    mod.hasError = true;
+    mod.isInitialized = false;
+    throw e;
+  }
 
+  return mod.module.exports;
+}
+
+const Systrace = __DEV__ && (() => {
+  var _Systrace;
+  try {
+    _Systrace = require('Systrace');
+  } catch (e) {}
+
+  return _Systrace && _Systrace.beginEvent ?
+    _Systrace : { beginEvent: () => {}, endEvent: () => {} };
+});
+
+global.__d = define;
+global.require = require;
+
+if (__DEV__) { // HMR
+  function accept(id, factory, inverseDependencies) {
     var mod = modules[id];
+
     if (!mod) {
-      var msg = 'Requiring unknown module "' + id + '"';
-      if (__DEV__) {
-        msg += '. If you are sure the module is there, try restarting the packager.';
-      }
-      throw new Error(msg);
+      define(id, factory);
+      return true; // new modules don't need to be accepted
     }
 
-    if (mod.hasError) {
-      throw new Error(
-        'Requiring module "' + id + '" which threw an exception'
+    if (!mod.module.hot) {
+      console.warn(
+        'Cannot accept module because Hot Module Replacement ' +
+        'API was not installed.'
       );
+      return false;
     }
 
-    try {
-      // We must optimistically mark mod as initialized before running the factory to keep any
-      // require cycles inside the factory from causing an infinite require loop.
-      mod.isInitialized = true;
+    // replace and initialize factory
+    if (factory) {
+      mod.factory = factory;
+    }
+    mod.isInitialized = false;
+    require(id);
 
-      // keep args in sync with with defineModuleCode in
-      // packager/react-packager/src/Resolver/index.js
-      mod.factory.call(global, global, require, mod.module, mod.module.exports);
-    } catch (e) {
-      mod.hasError = true;
-      mod.isInitialized = false;
-      throw e;
+    if (mod.module.hot.acceptCallback) {
+      mod.module.hot.acceptCallback();
+      return true;
+    } else {
+      // need to have inverseDependencies to bubble up accept
+      if (!inverseDependencies) {
+        throw new Error('Undefined `inverseDependencies`');
+      }
+
+      // accept parent modules recursively up until all siblings are accepted
+      return acceptAll(inverseDependencies[id], inverseDependencies);
     }
+  }
 
-    return mod.module.exports;
+  function acceptAll(modules, inverseDependencies) {
+    if (modules.length === 0) {
+      return true;
+    }
+
+    var notAccepted = modules.filter(function(module) {
+      return !accept(module, /*factory*/ undefined, inverseDependencies);
+    });
+
+    var parents = [];
+    for (var i = 0; i < notAccepted.length; i++) {
+      // if this the module has no parents then the change cannot be hot loaded
+      if (inverseDependencies[notAccepted[i]].length === 0) {
+        return false;
+      }
+
+      parents.pushAll(inverseDependencies[notAccepted[i]]);
+    }
+
+    return acceptAll(parents, inverseDependencies);
   }
 
-  global.__d = define;
-  global.require = require;
-})(this);
+  global.__accept = accept;
+}
diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js
index d68e04105613..a2248842371d 100644
--- a/packager/react-packager/src/Server/__tests__/Server-test.js
+++ b/packager/react-packager/src/Server/__tests__/Server-test.js
@@ -9,21 +9,25 @@
 'use strict';
 
 jest.setMock('worker-farm', function() { return () => {}; })
+    .dontMock('node-haste/node_modules/throat')
     .dontMock('os')
+    .dontMock('underscore')
     .dontMock('path')
     .dontMock('url')
     .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) })
     .setMock('uglify-js')
-    .dontMock('../');
+    .dontMock('../')
+    .setMock('crypto');
 
 const Promise = require('promise');
 
 var Bundler = require('../../Bundler');
-var FileWatcher = require('../../FileWatcher');
 var Server = require('../');
 var Server = require('../../Server');
 var AssetServer = require('../../AssetServer');
 
+var FileWatcher;
+
 describe('processRequest', () => {
   var server;
 
@@ -34,12 +38,17 @@ describe('processRequest', () => {
      polyfillModuleNames: null
   };
 
-  const makeRequest = (reqHandler, requrl) => new Promise(resolve =>
+  const makeRequest = (reqHandler, requrl, reqOptions) => new Promise(resolve =>
     reqHandler(
-      { url: requrl },
+      { url: requrl, headers:{}, ...reqOptions },
       {
-        setHeader: jest.genMockFunction(),
-        end: res => resolve(res),
+        headers: {},
+        getHeader(header) { return this.headers[header]; },
+        setHeader(header, value) { this.headers[header] = value; },
+        end(body) {
+          this.body = body;
+          resolve(this);
+        },
       },
       { next: () => {} },
     )
@@ -51,10 +60,12 @@ describe('processRequest', () => {
   var triggerFileChange;
 
   beforeEach(() => {
+    FileWatcher = require('node-haste').FileWatcher;
     Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() =>
       Promise.resolve({
         getSource: () => 'this is the source',
         getSourceMap: () => 'this is the source map',
+        getEtag: () => 'this is an etag',
       })
     );
 
@@ -76,9 +87,10 @@ describe('processRequest', () => {
   pit('returns JS bundle source on request of *.bundle', () => {
     return makeRequest(
       requestHandler,
-      'mybundle.bundle?runModule=true'
+      'mybundle.bundle?runModule=true',
+      null
     ).then(response =>
-      expect(response).toEqual('this is the source')
+      expect(response.body).toEqual('this is the source')
     );
   });
 
@@ -87,16 +99,35 @@ describe('processRequest', () => {
       requestHandler,
       'mybundle.runModule.bundle'
     ).then(response =>
-      expect(response).toEqual('this is the source')
+      expect(response.body).toEqual('this is the source')
     );
   });
 
+  pit('returns ETag header on request of *.bundle', () => {
+    return makeRequest(
+      requestHandler,
+      'mybundle.bundle?runModule=true'
+    ).then(response => {
+      expect(response.getHeader('ETag')).toBeDefined()
+    });
+  });
+
+  pit('returns 304 on request of *.bundle when if-none-match equals the ETag', () => {
+    return makeRequest(
+      requestHandler,
+      'mybundle.bundle?runModule=true',
+      { headers : { 'if-none-match' : 'this is an etag' } }
+    ).then(response => {
+      expect(response.statusCode).toEqual(304)
+    });
+  });
+
   pit('returns sourcemap on request of *.map', () => {
     return makeRequest(
       requestHandler,
       'mybundle.map?runModule=true'
     ).then(response =>
-      expect(response).toEqual('this is the source map')
+      expect(response.body).toEqual('this is the source map')
     );
   });
 
@@ -105,16 +136,19 @@ describe('processRequest', () => {
       requestHandler,
       'index.ios.includeRequire.bundle'
     ).then(response => {
-      expect(response).toEqual('this is the source');
+      expect(response.body).toEqual('this is the source');
       expect(Bundler.prototype.bundle).toBeCalledWith({
         entryFile: 'index.ios.js',
         inlineSourceMap: false,
         minify: false,
+        hot: false,
         runModule: true,
         sourceMapUrl: 'index.ios.includeRequire.map',
         dev: true,
         platform: undefined,
         runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
+        unbundle: false,
+        entryModuleOnly: false,
       });
     });
   });
@@ -124,16 +158,19 @@ describe('processRequest', () => {
       requestHandler,
       'index.bundle?platform=ios'
     ).then(function(response) {
-      expect(response).toEqual('this is the source');
+      expect(response.body).toEqual('this is the source');
       expect(Bundler.prototype.bundle).toBeCalledWith({
         entryFile: 'index.js',
         inlineSourceMap: false,
         minify: false,
+        hot: false,
         runModule: true,
         sourceMapUrl: 'index.map?platform=ios',
         dev: true,
         platform: 'ios',
         runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
+        unbundle: false,
+        entryModuleOnly: false,
       });
     });
   });
@@ -167,12 +204,14 @@ describe('processRequest', () => {
           Promise.resolve({
             getSource: () => 'this is the first source',
             getSourceMap: () => {},
+            getEtag: () => () => 'this is an etag',
           })
         )
         .mockReturnValue(
           Promise.resolve({
             getSource: () => 'this is the rebuilt source',
             getSourceMap: () => {},
+            getEtag: () => () => 'this is an etag',
           })
         );
 
@@ -184,7 +223,7 @@ describe('processRequest', () => {
 
       makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
         .done(response => {
-          expect(response).toEqual('this is the first source');
+          expect(response.body).toEqual('this is the first source');
           expect(bundleFunc.mock.calls.length).toBe(1);
         });
 
@@ -198,10 +237,58 @@ describe('processRequest', () => {
 
       makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
         .done(response =>
-          expect(response).toEqual('this is the rebuilt source')
+          expect(response.body).toEqual('this is the rebuilt source')
         );
       jest.runAllTicks();
     });
+
+    it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => {
+      const bundleFunc = jest.genMockFunction();
+      bundleFunc
+        .mockReturnValueOnce(
+          Promise.resolve({
+            getSource: () => 'this is the first source',
+            getSourceMap: () => {},
+            getEtag: () => () => 'this is an etag',
+          })
+        )
+        .mockReturnValue(
+          Promise.resolve({
+            getSource: () => 'this is the rebuilt source',
+            getSourceMap: () => {},
+            getEtag: () => () => 'this is an etag',
+          })
+        );
+
+      Bundler.prototype.bundle = bundleFunc;
+
+      const server = new Server(options);
+      server.setHMRFileChangeListener(() => {});
+
+      requestHandler = server.processRequest.bind(server);
+
+      makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
+        .done(response => {
+          expect(response.body).toEqual('this is the first source');
+          expect(bundleFunc.mock.calls.length).toBe(1);
+        });
+
+      jest.runAllTicks();
+
+      triggerFileChange('all','path/file.js', options.projectRoots[0]);
+      jest.runAllTimers();
+      jest.runAllTicks();
+
+      expect(bundleFunc.mock.calls.length).toBe(1);
+      server.setHMRFileChangeListener(null);
+
+      makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
+        .done(response => {
+          expect(response.body).toEqual('this is the rebuilt source');
+          expect(bundleFunc.mock.calls.length).toBe(2);
+        });
+      jest.runAllTicks();
+    });
   });
 
   describe('/onchange endpoint', () => {
@@ -270,10 +357,13 @@ describe('processRequest', () => {
           entryFile: 'foo file',
           inlineSourceMap: false,
           minify: false,
+          hot: false,
           runModule: true,
           dev: true,
           platform: undefined,
           runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
+          unbundle: false,
+          entryModuleOnly: false,
         })
       );
     });
@@ -287,11 +377,14 @@ describe('processRequest', () => {
             entryFile: 'path/to/foo.js',
             inlineSourceMap: false,
             minify: false,
+            hot: false,
             runModule: false,
             sourceMapUrl: '/path/to/foo.map?dev=false&runModule=false',
             dev: false,
             platform: undefined,
             runBeforeMainModule: ['InitializeJavaScriptAppEngine'],
+            unbundle: false,
+            entryModuleOnly: false,
           })
         );
     });
diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js
index 1abb78cf6ece..5949bd0c72f2 100644
--- a/packager/react-packager/src/Server/index.js
+++ b/packager/react-packager/src/Server/index.js
@@ -10,8 +10,8 @@
 
 const Activity = require('../Activity');
 const AssetServer = require('../AssetServer');
-const FileWatcher = require('../FileWatcher');
-const getPlatformExtension = require('../DependencyResolver/lib/getPlatformExtension');
+const FileWatcher = require('node-haste').FileWatcher;
+const getPlatformExtension = require('node-haste').getPlatformExtension;
 const Bundler = require('../Bundler');
 const Promise = require('promise');
 
@@ -58,12 +58,25 @@ const validateOpts = declareOpts({
   },
   assetExts: {
     type: 'array',
-    default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
+    default: [
+      'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats
+      'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats
+      'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats
+      'html', // Document formats
+    ],
   },
   transformTimeoutInterval: {
     type: 'number',
     required: false,
   },
+  getTransformOptionsModulePath: {
+    type: 'string',
+    required: false,
+  },
+  disableInternalTransforms: {
+    type: 'boolean',
+    default: false,
+  },
 });
 
 const bundleOpts = declareOpts({
@@ -102,6 +115,18 @@ const bundleOpts = declareOpts({
       'InitializeJavaScriptAppEngine'
     ],
   },
+  unbundle: {
+    type: 'boolean',
+    default: false,
+  },
+  hot: {
+    type: 'boolean',
+    default: false,
+  },
+  entryModuleOnly: {
+    type: 'boolean',
+    default: false,
+  },
 });
 
 const dependencyOpts = declareOpts({
@@ -117,6 +142,10 @@ const dependencyOpts = declareOpts({
     type: 'string',
     required: true,
   },
+  recursive: {
+    type: 'boolean',
+    default: true,
+  },
 });
 
 class Server {
@@ -126,6 +155,7 @@ class Server {
     this._projectRoots = opts.projectRoots;
     this._bundles = Object.create(null);
     this._changeWatchers = [];
+    this._fileChangeListeners = [];
 
     const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext);
 
@@ -152,7 +182,7 @@ class Server {
 
     this._fileWatcher = options.nonPersistent
       ? FileWatcher.createDummyWatcher()
-      : new FileWatcher(watchRootConfigs);
+      : new FileWatcher(watchRootConfigs, {useWatchman: true});
 
     this._assetServer = new AssetServer({
       projectRoots: opts.projectRoots,
@@ -179,6 +209,10 @@ class Server {
     ]);
   }
 
+  setHMRFileChangeListener(listener) {
+    this._hmrFileChangeListener = listener;
+  }
+
   buildBundle(options) {
     return Promise.resolve().then(() => {
       if (!options.platform) {
@@ -190,11 +224,34 @@ class Server {
     });
   }
 
+  buildPrepackBundle(options) {
+    return Promise.resolve().then(() => {
+      if (!options.platform) {
+        options.platform = getPlatformExtension(options.entryFile);
+      }
+
+      const opts = bundleOpts(options);
+      return this._bundler.prepackBundle(opts);
+    });
+  }
+
   buildBundleFromUrl(reqUrl) {
     const options = this._getOptionsFromUrl(reqUrl);
     return this.buildBundle(options);
   }
 
+  buildBundleForHMR(modules, host, port) {
+    return this._bundler.hmrBundle(modules, host, port);
+  }
+
+  getShallowDependencies(entryFile) {
+    return this._bundler.getShallowDependencies(entryFile);
+  }
+
+  getModuleForPath(entryFile) {
+    return this._bundler.getModuleForPath(entryFile);
+  }
+
   getDependencies(options) {
     return Promise.resolve().then(() => {
       if (!options.platform) {
@@ -206,6 +263,7 @@ class Server {
         opts.entryFile,
         opts.dev,
         opts.platform,
+        opts.recursive,
       );
     });
   }
@@ -220,11 +278,26 @@ class Server {
   _onFileChange(type, filepath, root) {
     const absPath = path.join(root, filepath);
     this._bundler.invalidateFile(absPath);
+
+    // If Hot Loading is enabled avoid rebuilding bundles and sending live
+    // updates. Instead, send the HMR updates right away and clear the bundles
+    // cache so that if the user reloads we send them a fresh bundle
+    if (this._hmrFileChangeListener) {
+      // Clear cached bundles in case user reloads
+      this._clearBundles();
+      this._hmrFileChangeListener(absPath, this._bundler.stat(absPath));
+      return;
+    }
+
     // Make sure the file watcher event runs through the system before
     // we rebuild the bundles.
     this._debouncedFileChangeHandler(absPath);
   }
 
+  _clearBundles() {
+    this._bundles = Object.create(null);
+  }
+
   _rebuildBundles() {
     const buildBundle = this.buildBundle.bind(this);
     const bundles = this._bundles;
@@ -370,7 +443,13 @@ class Server {
             dev: options.dev,
           });
           res.setHeader('Content-Type', 'application/javascript');
-          res.end(bundleSource);
+          res.setHeader('ETag', p.getEtag());
+          if (req.headers['if-none-match'] === res.getHeader('ETag')){
+            res.statusCode = 304;
+            res.end();
+          } else {
+            res.end(bundleSource);
+          }
           Activity.endEvent(startReqEventId);
         } else if (requestType === 'map') {
           var sourceMap = p.getSourceMap({
@@ -454,6 +533,7 @@ class Server {
       entryFile: entryFile,
       dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true),
       minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'),
+      hot: this._getBoolOptionFromQuery(urlObj.query, 'hot', false),
       runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true),
       inlineSourceMap: this._getBoolOptionFromQuery(
         urlObj.query,
@@ -461,6 +541,11 @@ class Server {
         false
       ),
       platform: platform,
+      entryModuleOnly: this._getBoolOptionFromQuery(
+        urlObj.query,
+        'entryModuleOnly',
+        false,
+      ),
     };
   }
 
diff --git a/packager/react-packager/src/SocketInterface/SocketClient.js b/packager/react-packager/src/SocketInterface/SocketClient.js
index 41ce0df14ee2..c0016e17d5b0 100644
--- a/packager/react-packager/src/SocketInterface/SocketClient.js
+++ b/packager/react-packager/src/SocketInterface/SocketClient.js
@@ -9,6 +9,7 @@
 'use strict';
 
 const Bundle = require('../Bundler/Bundle');
+const PrepackBundle = require('../Bundler/PrepackBundle');
 const Promise = require('promise');
 const bser = require('bser');
 const debug = require('debug')('ReactNativePackager:SocketClient');
@@ -100,6 +101,13 @@ class SocketClient {
     }).then(json => Bundle.fromJSON(json));
   }
 
+  buildPrepackBundle(options) {
+    return this._send({
+      type: 'buildPrepackBundle',
+      data: options,
+    }).then(json => PrepackBundle.fromJSON(json));
+  }
+
   _send(message) {
     message.id = uid();
     this._sock.write(bser.dumpToBuffer(message));
@@ -128,9 +136,12 @@ class SocketClient {
     delete this._resolvers[message.id];
 
     if (message.type === 'error') {
-      resolver.reject(new Error(
-        message.data + '\n' + 'See logs ' + LOG_PATH
-      ));
+      const errorLog =
+        message.data && message.data.indexOf('TimeoutError') === -1
+          ? 'See logs ' + LOG_PATH
+          : getServerLogs();
+
+      resolver.reject(new Error(message.data + '\n' + errorLog));
     } else {
       resolver.resolve(message.data);
     }
diff --git a/packager/react-packager/src/SocketInterface/SocketServer.js b/packager/react-packager/src/SocketInterface/SocketServer.js
index 8258873a4bd5..2e5335270d57 100644
--- a/packager/react-packager/src/SocketInterface/SocketServer.js
+++ b/packager/react-packager/src/SocketInterface/SocketServer.js
@@ -52,13 +52,11 @@ class SocketServer {
       setImmediate(() => process.exit(1));
     });
 
-    this._numConnections = 0;
     this._server.on('connection', (sock) => this._handleConnection(sock));
 
     // Disable the file watcher.
     options.nonPersistent = true;
     this._packagerServer = new Server(options);
-    this._jobs = 0;
     this._dieEventually(MAX_STARTUP_TIME);
   }
 
@@ -68,8 +66,6 @@ class SocketServer {
 
   _handleConnection(sock) {
     debug('connection to server', process.pid);
-    this._numConnections++;
-    sock.on('close', () => this._numConnections--);
 
     const bunser = new bser.BunserBuf();
     sock.on('data', (buf) => bunser.append(buf));
@@ -95,7 +91,6 @@ class SocketServer {
 
     const handleError = (error) => {
       debug('request error', error);
-      this._jobs--;
       this._reply(sock, m.id, 'error', error.stack);
 
       // Fatal error from JSTransformer transform workers.
@@ -106,7 +101,6 @@ class SocketServer {
 
     switch (m.type) {
       case 'getDependencies':
-        this._jobs++;
         this._packagerServer.getDependencies(m.data).then(
           ({ dependencies }) => this._reply(sock, m.id, 'result', dependencies),
           handleError,
@@ -114,15 +108,20 @@ class SocketServer {
         break;
 
       case 'buildBundle':
-        this._jobs++;
         this._packagerServer.buildBundle(m.data).then(
           (result) => this._reply(sock, m.id, 'result', result),
           handleError,
         );
         break;
 
+      case 'buildPrepackBundle':
+        this._packagerServer.buildPrepackBundle(m.data).then(
+          (result) => this._reply(sock, m.id, 'result', result),
+          handleError,
+        );
+        break;
+
       case 'getOrderedDependencyPaths':
-        this._jobs++;
         this._packagerServer.getOrderedDependencyPaths(m.data).then(
           (dependencies) => this._reply(sock, m.id, 'result', dependencies),
           handleError,
@@ -148,17 +147,19 @@ class SocketServer {
     // Debounce the kill timer to make sure all the bytes are sent through
     // the socket and the client has time to fully finish and disconnect.
     this._dieEventually();
-    this._jobs--;
   }
 
   _dieEventually(delay = MAX_IDLE_TIME) {
     clearTimeout(this._deathTimer);
     this._deathTimer = setTimeout(() => {
-      if (this._jobs <= 0 && this._numConnections <= 0) {
-        debug('server dying', process.pid);
-        process.exit();
-      }
-      this._dieEventually();
+      this._server.getConnections((error, numConnections) => {
+        // error is passed when connection count is below 0
+        if (error || numConnections <= 0) {
+          debug('server dying', process.pid);
+          process.exit();
+        }
+        this._dieEventually();
+      });
     }, delay);
   }
 
diff --git a/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
index 729d2a1ddfb2..b5a7b91605a8 100644
--- a/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
+++ b/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
@@ -8,9 +8,9 @@
  */
 'use strict';
 
-jest.setMock('worker-farm', function() { return () => {}; })
-    .setMock('uglify-js')
+jest.setMock('uglify-js')
     .mock('child_process')
+    .dontMock('underscore')
     .dontMock('../');
 
 var SocketInterface = require('../');
diff --git a/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js
index 39c28d965f5e..12512eedc69e 100644
--- a/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js
+++ b/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js
@@ -8,10 +8,10 @@
  */
 'use strict';
 
-jest.setMock('worker-farm', function() { return () => {}; })
-    .setMock('uglify-js')
+jest.setMock('uglify-js')
     .mock('net')
     .mock('fs')
+    .dontMock('node-haste/node_modules/throat')
     .dontMock('../SocketServer');
 
 var PackagerServer = require('../../Server');
diff --git a/packager/react-packager/src/SocketInterface/index.js b/packager/react-packager/src/SocketInterface/index.js
index c56026ead4c3..69b52a4f9579 100644
--- a/packager/react-packager/src/SocketInterface/index.js
+++ b/packager/react-packager/src/SocketInterface/index.js
@@ -30,17 +30,26 @@ const SocketInterface = {
         const value = options[key];
         if (value) {
           hash.update(
-            typeof value === 'string' ? value : JSON.stringify(value)
+            options[key] != null && typeof value === 'string'
+              ? value
+              : JSON.stringify(value)
           );
         }
       });
 
-      const sockPath = path.join(
+      let sockPath = path.join(
         tmpdir,
         'react-packager-' + hash.digest('hex')
       );
+      if (process.platform === 'win32'){
+        // on Windows, use a named pipe, convert sockPath into a valid pipe name
+        // based on https://gist.github.com/domenic/2790533
+        sockPath = sockPath.replace(/^\//, '')
+        sockPath = sockPath.replace(/\//g, '-')
+        sockPath = '\\\\.\\pipe\\' + sockPath
+      }
 
-      if (fs.existsSync(sockPath)) {
+      if (existsSync(sockPath)) {
         var sock = net.connect(sockPath);
         sock.on('connect', () => {
           SocketClient.create(sockPath).then(
@@ -126,4 +135,13 @@ function createServer(resolve, reject, options, sockPath) {
   });
 }
 
+function existsSync(filename) {
+  try {
+    fs.accessSync(filename);
+    return true;
+  } catch(ex) {
+    return false;
+  }
+}
+
 module.exports = SocketInterface;
diff --git a/packager/react-packager/__mocks__/debug.js b/packager/react-packager/src/__mocks__/debug.js
similarity index 100%
rename from packager/react-packager/__mocks__/debug.js
rename to packager/react-packager/src/__mocks__/debug.js
diff --git a/packager/react-packager/src/__mocks__/fs.js b/packager/react-packager/src/__mocks__/fs.js
index 1aca92500ae0..adcbf11ad8eb 100644
--- a/packager/react-packager/src/__mocks__/fs.js
+++ b/packager/react-packager/src/__mocks__/fs.js
@@ -8,131 +8,4 @@
  */
 'use strict';
 
-var fs = jest.genMockFromModule('fs');
-
-function asyncCallback(callback) {
-  return function() {
-    setImmediate(() => callback.apply(this, arguments));
-  };
-}
-
-fs.realpath.mockImpl(function(filepath, callback) {
-  callback = asyncCallback(callback);
-  var node;
-  try {
-    node = getToNode(filepath);
-  } catch (e) {
-    return callback(e);
-  }
-  if (node && typeof node === 'object' && node.SYMLINK != null) {
-    return callback(null, node.SYMLINK);
-  }
-  callback(null, filepath);
-});
-
-fs.readdir.mockImpl(function(filepath, callback) {
-  callback = asyncCallback(callback);
-  var node;
-  try {
-    node = getToNode(filepath);
-    if (node && typeof node === 'object' && node.SYMLINK != null) {
-      node = getToNode(node.SYMLINK);
-    }
-  } catch (e) {
-    return callback(e);
-  }
-
-  if (!(node && typeof node === 'object' && node.SYMLINK == null)) {
-    return callback(new Error(filepath + ' is not a directory.'));
-  }
-
-  callback(null, Object.keys(node));
-});
-
-fs.readFile.mockImpl(function(filepath, encoding, callback) {
-  callback = asyncCallback(callback);
-  if (arguments.length === 2) {
-    callback = encoding;
-    encoding = null;
-  }
-
-  try {
-    var node = getToNode(filepath);
-    // dir check
-    if (node && typeof node === 'object' && node.SYMLINK == null) {
-      callback(new Error('Error readFile a dir: ' + filepath));
-    }
-    return callback(null, node);
-  } catch (e) {
-    return callback(e);
-  }
-});
-
-fs.stat.mockImpl(function(filepath, callback) {
-  callback = asyncCallback(callback);
-  var node;
-  try {
-    node = getToNode(filepath);
-  } catch (e) {
-    callback(e);
-    return;
-  }
-
-  var mtime = {
-    getTime: function() {
-      return Math.ceil(Math.random() * 10000000);
-    },
-  };
-
-  if (node.SYMLINK) {
-    fs.stat(node.SYMLINK, callback);
-    return;
-  }
-
-  if (node && typeof node === 'object') {
-    callback(null, {
-      isDirectory: function() {
-        return true;
-      },
-      isSymbolicLink: function() {
-        return false;
-      },
-      mtime: mtime,
-    });
-  } else {
-    callback(null, {
-      isDirectory: function() {
-        return false;
-      },
-      isSymbolicLink: function() {
-        return false;
-      },
-      mtime: mtime,
-    });
-  }
-});
-
-var filesystem;
-
-fs.__setMockFilesystem = function(object) {
-  filesystem = object;
-  return filesystem;
-};
-
-function getToNode(filepath) {
-  var parts = filepath.split('/');
-  if (parts[0] !== '') {
-    throw new Error('Make sure all paths are absolute.');
-  }
-  var node = filesystem;
-  parts.slice(1).forEach(function(part) {
-    if (node && node.SYMLINK) {
-      node = getToNode(node.SYMLINK);
-    }
-    node = node[part];
-  });
-
-  return node;
-}
-
-module.exports = fs;
+module.exports = require.requireActual('node-haste/mocks/graceful-fs');
diff --git a/packager/react-packager/src/__mocks__/graceful-fs.js b/packager/react-packager/src/__mocks__/graceful-fs.js
new file mode 100644
index 000000000000..c6b9a53e068d
--- /dev/null
+++ b/packager/react-packager/src/__mocks__/graceful-fs.js
@@ -0,0 +1,11 @@
+/**
+ * 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.
+ */
+'use strict';
+
+module.exports = require('fs');
diff --git a/packager/react-packager/src/lib/ModuleTransport.js b/packager/react-packager/src/lib/ModuleTransport.js
index a5f1d5689106..afb660d386dd 100644
--- a/packager/react-packager/src/lib/ModuleTransport.js
+++ b/packager/react-packager/src/lib/ModuleTransport.js
@@ -9,6 +9,8 @@
 'use strict';
 
 function ModuleTransport(data) {
+  this.name = data.name;
+
   assertExists(data, 'code');
   this.code = data.code;
 
diff --git a/packager/react-packager/src/lib/getCacheFilePath.js b/packager/react-packager/src/lib/getCacheFilePath.js
deleted file mode 100644
index 1d8585297271..000000000000
--- a/packager/react-packager/src/lib/getCacheFilePath.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const crypto = require('crypto');
-const path = require('path');
-const tmpdir = require('os').tmpDir();
-
-function getCacheFilePath(...args) {
-  args = Array.prototype.slice.call(args);
-  const prefix = args.shift();
-
-  let hash = crypto.createHash('md5');
-  args.forEach(arg => hash.update(arg));
-
-  return path.join(tmpdir, prefix + hash.digest('hex'));
-}
-
-module.exports = getCacheFilePath;
diff --git a/packager/react-packager/src/lib/loadCacheSync.js b/packager/react-packager/src/lib/loadCacheSync.js
deleted file mode 100644
index d04ec093688b..000000000000
--- a/packager/react-packager/src/lib/loadCacheSync.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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.
- */
-'use strict';
-
-const fs = require('fs');
-
-function loadCacheSync(cachePath) {
-  if (!fs.existsSync(cachePath)) {
-    return Object.create(null);
-  }
-
-  try {
-    return JSON.parse(fs.readFileSync(cachePath));
-  } catch (e) {
-    if (e instanceof SyntaxError) {
-      console.warn('Unable to parse cache file. Will clear and continue.');
-      try {
-        fs.unlinkSync(cachePath);
-      } catch (err) {
-        // Someone else might've deleted it.
-      }
-      return Object.create(null);
-    }
-    throw e;
-  }
-}
-
-module.exports = loadCacheSync;
diff --git a/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js b/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js
deleted file mode 100644
index 43aa4b56b8b1..000000000000
--- a/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @emails oncall+jsinfra
- */
-
-'use strict';
-
-jest.autoMockOff();
-jest.mock('../../../BundlesLayout');
-
-const babel = require('babel-core');
-const BundlesLayout = require('../../../BundlesLayout');
-
-const testData = {
-  isolated: {
-    input:  'System.import("moduleA");',
-    output: 'loadBundles(["bundle.0"]);'
-  },
-  single: {
-    input:  'System.import("moduleA").then(function (bundleA) {});',
-    output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});'
-  },
-  multiple: {
-    input: [
-      'Promise.all([',
-        'System.import("moduleA"), System.import("moduleB"),',
-      ']).then(function (bundlesA, bundlesB) {});',
-    ].join('\n'),
-    output: [
-      'Promise.all([',
-        'loadBundles(["bundle.0"]), loadBundles(["bundle.1"])',
-      ']).then(function (bundlesA, bundlesB) {});',
-    ].join(''),
-  },
-};
-
-describe('System.import', () => {
-  let layout = new BundlesLayout();
-  BundlesLayout.prototype.getBundleIDForModule.mockImpl(module => {
-    switch (module) {
-      case 'moduleA': return 'bundle.0';
-      case 'moduleB': return 'bundle.1';
-    }
-  });
-
-  function transform(source) {
-    return babel.transform(source, {
-      plugins: [
-        [require('../'), { bundlesLayout: layout }]
-      ],
-    }).code;
-  }
-
-  function test(data) {
-    // transform and remove new lines
-    expect(transform(data.input).replace(/(\r\n|\n|\r)/gm,'')).toEqual(data.output);
-  }
-
-  it('should transform isolated `System.import`', () => {
-    test(testData.isolated);
-  });
-
-  it('should transform single `System.import`', () => {
-    test(testData.single);
-  });
-
-  it('should transform multiple `System.import`s', () => {
-    test(testData.multiple);
-  });
-});
diff --git a/packager/react-packager/src/transforms/babel-plugin-system-import/index.js b/packager/react-packager/src/transforms/babel-plugin-system-import/index.js
deleted file mode 100644
index 8a6cbcf0c2e6..000000000000
--- a/packager/react-packager/src/transforms/babel-plugin-system-import/index.js
+++ /dev/null
@@ -1,63 +0,0 @@
- /**
-  * 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.
-  *
-  */
-/*jslint node: true */
-'use strict';
-
-const t = require('babel-types');
-
-/**
- * Transforms asynchronous module importing into a function call
- * that includes which bundle needs to be loaded
- *
- * Transforms:
- *
- *  System.import('moduleA')
- *
- * to:
- *
- *  loadBundles('bundleA')
- */
-module.exports = function() {
-  return {
-    visitor: {
-      CallExpression: function (path, state) {
-        if (!isAppropriateSystemImportCall(path.node)) {
-          return;
-        }
-
-        var bundlesLayout = state.opts.bundlesLayout;
-        var bundleID = bundlesLayout.getBundleIDForModule(
-          path.node.arguments[0].value
-        );
-
-        var bundles = bundleID.split('.');
-        bundles.splice(0, 1);
-        bundles = bundles.map(function(id) {
-          return t.stringLiteral('bundle.' + id);
-        });
-
-        path.replaceWith(t.callExpression(
-          t.identifier('loadBundles'),
-          [t.arrayExpression(bundles)]
-        ));
-      },
-    },
-  };
-};
-
-function isAppropriateSystemImportCall(node) {
-  return (
-    node.callee.type === 'MemberExpression' &&
-    node.callee.object.name === 'System' &&
-    node.callee.property.name === 'import' &&
-    node.arguments.length === 1 &&
-    node.arguments[0].type === 'StringLiteral'
-  );
-}
diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js b/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js
index 7525fd0f0152..4d7d142c48a9 100644
--- a/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js
+++ b/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.js
@@ -33,7 +33,7 @@ const trim = (str) =>
 describe('dead-module-elimination', () => {
   it('should inline __DEV__', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       var foo = __DEV__;`,
       `var foo = false;`
     );
@@ -41,7 +41,7 @@ describe('dead-module-elimination', () => {
 
   it('should accept unary operators with literals', () => {
     compare(
-      `__DEV__ = !1;
+      `global.__DEV__ = !1;
       var foo = __DEV__;`,
       `var foo = false;`
     );
@@ -49,7 +49,7 @@ describe('dead-module-elimination', () => {
 
   it('should kill dead branches', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       if (__DEV__) {
         doSomething();
       }`,
@@ -74,7 +74,7 @@ describe('dead-module-elimination', () => {
 
   it('should kill modules referenced only from dead branches', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       __d('bar', function() {});
       if (__DEV__) { require('bar'); }`,
       ``
@@ -83,7 +83,7 @@ describe('dead-module-elimination', () => {
 
   it('should replace logical expressions with the result', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       __d('bar', function() {});
       __DEV__ && require('bar');`,
       `false;`
@@ -92,7 +92,7 @@ describe('dead-module-elimination', () => {
 
   it('should keep if result branch', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       __d('bar', function() {});
       if (__DEV__) {
         killWithFire();
@@ -106,7 +106,7 @@ describe('dead-module-elimination', () => {
 
   it('should replace falsy ternaries with alternate expression', () => {
     compare(
-      `__DEV__ = false;
+      `global.__DEV__ = false;
       __DEV__ ? foo() : bar();
       `,
       `bar();`
diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js b/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js
index 39eeb0a6c229..b5f33b4e09be 100644
--- a/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js
+++ b/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js
@@ -60,7 +60,12 @@ module.exports = function () {
     AssignmentExpression(path) {
       const { node } = path;
 
-      if (node.left.type === 'Identifier' && node.left.name === '__DEV__') {
+      if (
+        node.left.type === 'MemberExpression' &&
+        node.left.object.name === 'global' &&
+        node.left.property.type === 'Identifier' &&
+        node.left.property.name === '__DEV__'
+      ) {
         var value;
         if (node.right.type === 'BooleanLiteral') {
           value = node.right.value;
@@ -73,7 +78,7 @@ module.exports = function () {
         } else {
           return;
         }
-        globals[node.left.name] = value;
+        globals[node.left.property.name] = value;
 
         // workaround babel/source map bug - the minifier should strip it
         path.replaceWith(t.booleanLiteral(value));
@@ -130,10 +135,12 @@ module.exports = function () {
     visitor: {
       Program(path) {
         path.traverse(firstPass);
-        while (hasDeadModules(requires)) {
+        var counter = 0;
+        while (hasDeadModules(requires) && counter < 3) {
           _requires = requires;
           requires = {};
           path.traverse(secondPass);
+          counter++;
         }
       }
     }
diff --git a/packager/transformer.js b/packager/transformer.js
index ce984be9edb7..071f4007fab1 100644
--- a/packager/transformer.js
+++ b/packager/transformer.js
@@ -11,36 +11,99 @@
 'use strict';
 
 const babel = require('babel-core');
+const externalHelpersPlugin = require('babel-plugin-external-helpers');
 const fs = require('fs');
-const inlineRequires = require('fbjs-scripts/babel-6/inline-requires');
+const makeHMRConfig = require('babel-preset-react-native/configs/hmr');
+const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins');
+const inlineRequiresPlugin = require('fbjs-scripts/babel-6/inline-requires');
 const json5 = require('json5');
 const path = require('path');
 
-const babelRC =
-  json5.parse(
-    fs.readFileSync(
-      path.resolve(__dirname, 'react-packager', '.babelrc')));
+/**
+ * Return a memoized function that checks for the existence of a
+ * project level .babelrc file, and if it doesn't exist, reads the
+ * default RN babelrc file and uses that.
+ */
+const getBabelRC = (function() {
+  let babelRC = null;
 
-function transform(src, filename, options) {
-  options = options || {};
+  return function _getBabelRC(projectRoots) {
+    if (babelRC !== null) {
+      return babelRC;
+    }
+
+    babelRC = { plugins: [] }; // empty babelrc
+
+    // Let's look for the .babelrc in the first project root.
+    // In the future let's look into adding a command line option to specify
+    // this location.
+    //
+    // NOTE: we're not reading the project's .babelrc here. We leave it up to
+    // Babel to do that automatically and apply the transforms accordingly
+    // (which works because we pass in `filename` and `sourceFilename` to
+    // Babel when we transform).
+    let projectBabelRCPath;
+    if (projectRoots && projectRoots.length > 0) {
+      projectBabelRCPath = path.resolve(projectRoots[0], '.babelrc');
+    }
+
+    // If a .babelrc file doesn't exist in the project,
+    // use the Babel config provided with react-native.
+    if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) {
+      babelRC = json5.parse(
+        fs.readFileSync(
+          path.resolve(__dirname, 'react-packager', 'rn-babelrc.json'))
+        );
+
+      // Require the babel-preset's listed in the default babel config
+      babelRC.presets = babelRC.presets.map((preset) => require('babel-preset-' + preset));
+      babelRC.plugins = resolvePlugins(babelRC.plugins);
+    }
+
+    return babelRC;
+  }
+})();
+
+/**
+ * Given a filename and options, build a Babel
+ * config object with the appropriate plugins.
+ */
+function buildBabelConfig(filename, options) {
+  const babelRC = getBabelRC(options.projectRoots);
 
-  const extraPlugins = ['external-helpers-2'];
   const extraConfig = {
     filename,
     sourceFileName: filename,
   };
 
-  const config = Object.assign({}, babelRC, extraConfig);
+  let config = Object.assign({}, babelRC, extraConfig);
+
+  // Add extra plugins
+  const extraPlugins = [externalHelpersPlugin];
 
   if (options.inlineRequires) {
-    extraPlugins.push(inlineRequires);
+    extraPlugins.push(inlineRequiresPlugin);
   }
+
   config.plugins = extraPlugins.concat(config.plugins);
 
-  const result = babel.transform(src, Object.assign({}, babelRC, config));
+  if (options.hot) {
+    const hmrConfig = makeHMRConfig(options);
+    config = Object.assign({}, config, hmrConfig);
+  }
+
+  return Object.assign({}, babelRC, config);
+}
+
+function transform(src, filename, options) {
+  options = options || {};
+
+  const babelConfig = buildBabelConfig(filename, options);
+  const result = babel.transform(src, babelConfig);
 
   return {
-    code: result.code
+    code: result.code,
+    filename: filename,
   };
 }
 
diff --git a/react-native-cli/index.js b/react-native-cli/index.js
index ccddab423631..37a7b2b42dd8 100755
--- a/react-native-cli/index.js
+++ b/react-native-cli/index.js
@@ -138,7 +138,7 @@ function createAfterConfirmation(name, verbose) {
 
   var property = {
     name: 'yesno',
-    message: 'Directory ' + name + ' already exist. Continue?',
+    message: 'Directory ' + name + ' already exists. Continue?',
     validator: /y[es]*|n[o]?/,
     warning: 'Must respond yes or no',
     default: 'no'
@@ -172,7 +172,7 @@ function createProject(name, verbose) {
     version: '0.0.1',
     private: true,
     scripts: {
-      start: 'react-native start'
+      start: 'node node_modules/react-native/local-cli/cli.js start'
     }
   };
   fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));
@@ -234,8 +234,12 @@ function checkNodeVersion() {
 
 function checkForVersionArgument() {
   if (process.argv.indexOf('-v') >= 0 || process.argv.indexOf('--version') >= 0) {
-    var pjson = require('./package.json');
-    console.log(pjson.version);
+    console.log('react-native-cli: ' + require('./package.json').version);
+    try {
+      console.log('react-native: ' + require(REACT_NATIVE_PACKAGE_JSON_PATH()).version);
+    } catch (e) {
+      console.log('react-native: n/a - not inside a React Native project directory')
+    }
     process.exit();
   }
 }
diff --git a/react-native-cli/package.json b/react-native-cli/package.json
index 6c6bd533c56d..d1de18e2d205 100644
--- a/react-native-cli/package.json
+++ b/react-native-cli/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-native-cli",
-  "version": "0.1.7",
+  "version": "0.1.10",
   "license" : "BSD-3-Clause",
   "description": "The React Native CLI tools",
   "main": "index.js",
diff --git a/runXcodeTests.sh b/runXcodeTests.sh
index ab7ce69c1acc..fda82d67f0be 100755
--- a/runXcodeTests.sh
+++ b/runXcodeTests.sh
@@ -10,16 +10,9 @@ if [ -z "$1" ]
     exit 255
 fi
 
-xctool \
-  -project IntegrationTests/IntegrationTests.xcodeproj \
-  -scheme IntegrationTests \
-  -sdk iphonesimulator8.1 \
-  -destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
-  build test
-
 xctool \
   -project Examples/UIExplorer/UIExplorer.xcodeproj \
   -scheme UIExplorer \
-  -sdk iphonesimulator8.1 \
+  -sdk iphonesimulator${1} \
   -destination "platform=iOS Simulator,OS=${1},name=iPhone 5" \
   build test
diff --git a/scripts/circle-ci-android-setup.sh b/scripts/circle-ci-android-setup.sh
new file mode 100644
index 000000000000..2697ed9925e7
--- /dev/null
+++ b/scripts/circle-ci-android-setup.sh
@@ -0,0 +1,22 @@
+# inspired by https://github.com/Originate/guide/blob/master/android/guide/Continuous%20Integration.md
+
+function getAndroidSDK {
+  export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"
+
+  DEPS="$ANDROID_HOME/installed-dependencies"
+
+  if [ ! -e $DEPS ]; then
+    echo no | android create avd -n testAVD -f -t android-19 --abi default/armeabi-v7a &&
+    touch $DEPS
+  fi
+}
+
+function waitForAVD {
+  local bootanim=""
+  export PATH=$(dirname $(dirname $(which android)))/platform-tools:$PATH
+  until [[ "$bootanim" =~ "stopped" ]]; do
+    sleep 5
+    bootanim=$(adb -e shell getprop init.svc.bootanim 2>&1)
+    echo "emulator status=$bootanim"
+  done
+}
diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh
index e51a40827222..63e6647e420e 100755
--- a/scripts/e2e-test.sh
+++ b/scripts/e2e-test.sh
@@ -1,7 +1,18 @@
 #!/bin/bash
 
+# The script has one required argument:
+# --packager: react-native init, make sure the packager starts
+# --ios: react-native init, start the packager and run the iOS app
+# --android: same but run the Android app
+
 # Abort the mission if any command fails
 set -e
+set -x
+
+if [ -z $1 ]; then
+  echo "Please run the script with --ios, --android or --packager"
+  exit 1
+fi
 
 SCRIPTS=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
 ROOT=$(dirname $SCRIPTS)
@@ -15,7 +26,8 @@ export REACT_PACKAGER_LOG="$TEMP/server.log"
 # To make sure we actually installed the local version
 # of react-native, we will create a temp file inside the template
 # and check that it exists after `react-native init`
-MARKER=$(mktemp $ROOT/local-cli/generator-ios/templates/app/XXXXXXXX)
+MARKER_IOS=$(mktemp $ROOT/local-cli/generator-ios/templates/app/XXXXXXXX)
+MARKER_ANDROID=$(mktemp $ROOT/local-cli/generator-android/templates/src/XXXXXXXX)
 
 function cleanup {
   EXIT_CODE=$?
@@ -29,7 +41,8 @@ function cleanup {
     [ -f $REACT_PACKAGER_LOG ] && cat $REACT_PACKAGER_LOG
   fi
 
-  rm $MARKER
+  rm $MARKER_IOS
+  rm $MARKER_ANDROID
   [ $SINOPIA_PID ] && kill -9 $SINOPIA_PID
   [ $SERVER_PID ] && kill -9 $SERVER_PID
   [ -f ~/.npmrc.bak ] && mv ~/.npmrc.bak ~/.npmrc
@@ -41,7 +54,9 @@ cd $TEMP
 # sinopia is npm registry proxy, it is used to make npm
 # think react-native and react-native-cli are actually
 # published on npm
-which sinopia || npm install -g sinopia
+# Temporarily installing sinopia from a github fork
+# TODO t10060166 use npm repo when bug is fixed
+which sinopia || npm install -g git://github.com/bestander/sinopia.git#057155985fe955ed6066d1fc2edc159c63dec675
 
 # but in order to make npm use sinopia we temporarily
 # replace its config file
@@ -58,16 +73,40 @@ npm unpublish react-native-cli --force
 npm publish $ROOT
 npm publish $ROOT/react-native-cli
 
-
 npm install -g react-native-cli
 react-native init EndToEndTest
-cd EndToEndTest/ios
-
-# Make sure we installed local version of react-native
-ls EndToEndTest/`basename $MARKER` > /dev/null
+cd EndToEndTest
 
+npm install --g flow-bin@0.21.0
 flow --retries 10
 
-../node_modules/react-native/packager/packager.sh --nonPersistent &
-SERVER_PID=$!
-xctool -scheme EndToEndTest -sdk iphonesimulator test
+case $1 in
+"--packager"*)
+  echo "Running a basic packager test"
+  # Check the packager produces a bundle (doesn't throw an error)
+  react-native bundle --platform android --dev true --entry-file index.android.js --bundle-output android-bundle.js
+  ;;
+"--ios"*)
+  echo "Running an iOS app"
+  cd ios
+  # Make sure we installed local version of react-native
+  ls EndToEndTest/`basename $MARKER_IOS` > /dev/null
+  ../node_modules/react-native/packager/packager.sh --nonPersistent &
+  SERVER_PID=$!
+  # Start the app on the simulator
+  xctool -scheme EndToEndTest -sdk iphonesimulator test
+  ;;
+"--android"*)
+  echo "Running an Android app"
+  cd android
+  # Make sure we installed local version of react-native
+  ls `basename $MARKER_ANDROID` > /dev/null
+  ../node_modules/react-native/packager/packager.sh --nonPersistent &
+  SERVER_PID=$!
+  # TODO Start the app and check it renders "Welcome to React Native"
+  echo "The Android e2e test is not implemented yet"
+  ;;
+*)
+  echo "Please run the script with --ios, --android or --packager"
+  ;;
+esac
diff --git a/scripts/objc-test.sh b/scripts/objc-test.sh
index 7a68e2107e72..ce41e798d6e8 100755
--- a/scripts/objc-test.sh
+++ b/scripts/objc-test.sh
@@ -28,5 +28,5 @@ trap cleanup EXIT
 SERVER_PID=$!
 xctool \
   -project Examples/UIExplorer/UIExplorer.xcodeproj \
-  -scheme UIExplorer -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5,OS=9.0' \
+  -scheme UIExplorer -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 5,OS=9.2' \
   test
diff --git a/scripts/release.sh b/scripts/release.sh
new file mode 100755
index 000000000000..04776a5a3d14
--- /dev/null
+++ b/scripts/release.sh
@@ -0,0 +1,116 @@
+#! /bin/bash
+
+RELEASE="$1"
+JAVA_VERSION="1.7"
+
+RED="\033[0;31m"
+GREEN="\033[0;32m"
+BLUE="\033[0;35m"
+ENDCOLOR="\033[0m"
+
+error() {
+    echo -e $RED"$@"$ENDCOLOR
+    exit 1
+}
+
+success() {
+    echo -e $GREEN"$@"$ENDCOLOR
+}
+
+info() {
+    echo -e $BLUE"$@"$ENDCOLOR
+}
+
+[[ -z $RELEASE ]] && error "Please specify a version. Example usage: release.sh 0.18"
+
+repo_root=$(pwd)
+
+git branch | grep -o ${RELEASE}-stable && error "Branch already exists"
+java -version 2>&1 | grep ${JAVA_VERSION} || error "Java version must be 1.7.x in order to generate Javadoc. Check: java -version"
+
+git pull || error "Couldn't pull from remote repository"
+git checkout -b ${RELEASE}-stable || error "Couldn't create branch"
+
+success "Created release branch: ${RELEASE}-stable"
+
+sed -i.bak s/^VERSION_NAME=[0-9\.]*-SNAPSHOT/VERSION_NAME=${RELEASE}.0/g "ReactAndroid/gradle.properties" || error "Couldn't update version for Gradle"
+# Uncomment Javadoc generation
+sed -i.bak s:\/\/\ archives\ androidJavadocJar:archives\ androidJavadocJar:g "ReactAndroid/release.gradle" || error "Couldn't enable Javadoc generation"
+./gradlew :ReactAndroid:installArchives || error "Couldn't generate artifacts"
+# Revert Javadoc generation
+sed -i.bak s:archives\ androidJavadocJar:\/\/\ archives\ androidJavadocJar:g "ReactAndroid/release.gradle" || error "Couldn't enable Javadoc generation"
+
+artifacts_list=( -javadoc.jar -sources.jar .aar .pom )
+# ./gradlew :ReactAndroid:installArchives put the artifacts in react-native/android, ready to be published to npm
+artifacts_dir="${repo_root}/android/com/facebook/react/react-native/${RELEASE}.0"
+
+for i in "${artifacts_list[@]}"; do
+   artifact_file="${artifacts_dir}/react-native-${RELEASE}.0${i}"
+   [ -e "${artifact_file}" ] || error "Couldn't find file: ${artifact_file}"
+done
+
+success "Generated artifacts for Maven"
+
+sed -i.bak -E "s/(\"version\":[[:space:]]*\").+(\")/\"version\": \"${RELEASE}.0-rc\"/g" "package.json" || error "Couldn't update version for npm"
+sed -i.bak -E "s/(s.version[[:space:]]{13}=[[:space:]].+)/s.version             = \"${RELEASE}.0-rc\"/g" "React.podspec" || error "Couldn't update version for CocoaPods"
+
+success "Updated version numbers"
+
+npm_registry="http://localhost:4873/"
+
+npm set registry "${npm_registry}" && [[ $(npm config list | grep registry) == "registry = \"${npm_registry}\"" ]] || error "Couldn't set registry to ${npm_registry}"
+
+info "npm registry set. Run 'sinopia' in a new Terminal"
+info "   - Make sure it prints 'http address - ${npm_registry}'"
+info "   - Make sure ${npm_registry} shows no old versions"
+info ""
+info "Press any key to continue"
+read -n 1
+
+npm unpublish --force || error "Couldn't unpublish package from sinopia (${npm_registry})"
+npm publish || error "Couldn't publish package to sinopia (${npm_registry})"
+
+success "Published package to sinopia (${npm_registry})"
+
+project_name="RNTestProject"
+
+cd /tmp/
+rm -rf "$project_name"
+react-native init "$project_name"
+
+info "Double checking the versions in package.json and build.gradle are correct:"
+grep "\"react-native\": \"\^${RELEASE}.0-rc\"" "/tmp/${project_name}/package.json" || error "Incorrect version number in /tmp/${project_name}/package.json"
+grep -E "com.facebook.react:react-native:\\+" "${project_name}/android/app/build.gradle" || error "Dependency in /tmp/${project_name}/android/app/build.gradle must be com.facebook.react:react-native:+"
+
+success "New sample project generated at /tmp/${project_name}"
+
+info "Test the following both on Android and iOS:"
+info "   - Verify that packager opens in new Window"
+info "   - Verify that you see the 'Welcome to React Native' screen"
+info "   - Verify 'Reload JS' works"
+info "   - Test Chrome debugger by adding breakpoints. We don't have tests for Chrome debugging."
+info ""
+
+info "Press any key to run the sample in Android emulator/device"
+read -n 1
+cd "${project_name}" && react-native run-android
+
+info "Press any key to open the project in XCode"
+read -n 1
+open "/tmp/${project_name}/ios/${project_name}.xcodeproj"
+
+cd "$repo_root"
+
+info "Press any key to commit changes"
+read -n 1
+git commit -am "[${RELEASE}.0-rc] Bump version numbers"
+
+find . -path "*.bak" | xargs rm
+
+info "Next steps:"
+info "   - git push origin ${RELEASE}-stable"
+info "   - git tag v${RELEASE}.0-rc ${RELEASE}-stable"
+info "   - git push --tags"
+info "   - npm set registry https://registry.npmjs.org/"
+info "   - When doing a RC release: npm publish --tag next"
+info "   - When doing a non-RC release: npm publish  # Sets the latest tag automatically"
diff --git a/scripts/run-android-instrumentation-tests.sh b/scripts/run-android-instrumentation-tests.sh
new file mode 100755
index 000000000000..fb2960847ac7
--- /dev/null
+++ b/scripts/run-android-instrumentation-tests.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"
+
+# clear the logs
+adb logcat -c
+
+# run tests and check output
+python - $1 << END
+import re
+import subprocess as sp
+import sys
+import threading
+import time
+
+done = False
+test_app = sys.argv[1]
+
+def update():
+  # prevent CircleCI from killing the process for inactivity
+  while not done:
+    time.sleep(5)
+    print "Running in background.  Waiting for 'adb' command reponse..."
+
+t = threading.Thread(target=update)
+t.dameon = True
+t.start()
+
+def run():
+  sp.Popen(['adb', 'wait-for-device']).communicate()
+  p = sp.Popen('adb shell am instrument -w %s/android.support.test.runner.AndroidJUnitRunner' % test_app,
+               shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
+  return p.communicate()
+
+success = re.compile(r'OK \(\d+ tests\)')
+stdout, stderr = run()
+
+done = True
+print stderr
+print stdout
+
+if success.search(stderr + stdout):
+  sys.exit(0)
+else:
+  # dump the logs
+  sp.Popen(['adb', 'logcat', '-d']).communicate()
+  sys.exit(1) # make sure we fail if the test failed
+END
+
+RETVAL=$?
+
+exit $RETVAL
diff --git a/website/.gitignore b/website/.gitignore
index 850f11c1b9cd..9ece2944657e 100644
--- a/website/.gitignore
+++ b/website/.gitignore
@@ -1,3 +1,4 @@
 src/react-native/docs/**
 core/metadata.js
 *.log
+/build/
diff --git a/website/README.md b/website/README.md
index 43d3debf7803..3c633b91216b 100644
--- a/website/README.md
+++ b/website/README.md
@@ -2,35 +2,23 @@
 
 The first time, get all the dependencies loaded via
 
-```
+```sh
 npm install
 ```
 
 Then, run the server via
 
-```
+```sh
 npm start
-open http://localhost:8080/react-native/index.html
+open http://localhost:8079/react-native/index.html
 ```
 
 Anytime you change the contents, just refresh the page and it's going to be updated.
 
 # Publish the website
 
-First setup your environment by having two folders, one `react-native` and one `react-native-gh-pages`. The publish script expects those exact names.
-
-```
-cd ../../
-git clone git@github.com:facebook/react-native.git react-native-gh-pages
-cd react-native-gh-pages
-git checkout origin/gh-pages
-git checkout -b gh-pages
-git push --set-upstream origin gh-pages
-cd ../react-native/website
+```sh
+cd website
+npm run publish-website
 ```
 
-Then, after you've done changes, just run the command and it'll automatically build the static version of the site and publish it to gh-pages.
-
-```
-./publish.sh
-```
diff --git a/website/core/AlgoliaDocSearch.js b/website/core/AlgoliaDocSearch.js
new file mode 100644
index 000000000000..b65e7e303bb4
--- /dev/null
+++ b/website/core/AlgoliaDocSearch.js
@@ -0,0 +1,22 @@
+/**
+ * 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 AlgoliaDocSearch
+ */
+
+var AlgoliaDocSearch = React.createClass({
+  render: function() {
+    return (
+      <div className="algolia-search-wrapper">
+        <input id="algolia-doc-search" type="text" placeholder="Search docs..." />
+      </div>
+    );
+  }
+});
+
+module.exports = AlgoliaDocSearch;
diff --git a/website/core/DocsSidebar.js b/website/core/DocsSidebar.js
index 2c18c7c8e757..d8640e40d790 100644
--- a/website/core/DocsSidebar.js
+++ b/website/core/DocsSidebar.js
@@ -72,7 +72,7 @@ var DocsSidebar = React.createClass({
     if (metadata.permalink.match(/^https?:/)) {
       return metadata.permalink;
     }
-    return '/react-native/' + metadata.permalink + '#content';
+    return metadata.permalink + '#content';
   },
 
   render: function() {
diff --git a/website/core/H2.js b/website/core/H2.js
index 02cd417329c5..7bf4e9278850 100644
--- a/website/core/H2.js
+++ b/website/core/H2.js
@@ -14,9 +14,7 @@ var Header = require('Header');
 
 var H2 = React.createClass({
   render: function() {
-    return this.transferPropsTo(
-      <Header level={2}>{this.props.children}</Header>
-    );
+    return <Header {...this.props} level={2}>{this.props.children}</Header>;
   }
 });
 
diff --git a/website/core/Header.js b/website/core/Header.js
index c84c98c401b0..c83d7334b1b7 100644
--- a/website/core/Header.js
+++ b/website/core/Header.js
@@ -13,14 +13,19 @@ var React = require('React');
 var slugify = require('slugify');
 
 var Header = React.createClass({
+  contextTypes: {
+    permalink: React.PropTypes.string
+  },
+
   render: function() {
     var slug = slugify(this.props.toSlug || this.props.children);
     var H = 'h' + this.props.level;
+    var base = this.context.permalink || '';
     return (
       <H {...this.props}>
         <a className="anchor" name={slug}></a>
         {this.props.children}
-        {' '}<a className="hash-link" href={'#' + slug}>#</a>
+        {' '}<a className="hash-link" href={base + '#' + slug}>#</a>
       </H>
     );
   }
diff --git a/website/core/HeaderLinks.js b/website/core/HeaderLinks.js
index c70b3c92d4be..6feaa91940fb 100644
--- a/website/core/HeaderLinks.js
+++ b/website/core/HeaderLinks.js
@@ -10,14 +10,15 @@
  */
 
 var React = require('React');
+var AlgoliaDocSearch = require('AlgoliaDocSearch');
 
 var HeaderLinks = React.createClass({
   linksInternal: [
-    {section: 'docs', href: '/react-native/docs/getting-started.html', text: 'Docs'},
-    {section: 'support', href: '/react-native/support.html', text: 'Support'},
+    {section: 'docs', href: 'docs/getting-started.html', text: 'Docs'},
+    {section: 'support', href: 'support.html', text: 'Support'},
     {section: 'releases', href: 'https://github.com/facebook/react-native/releases', text: 'Releases'},
     {section: 'newsletter', href: 'http://reactnative.cc', text: 'Newsletter'},
-    {section: 'showcase', href: '/react-native/showcase.html', text: 'Showcase'},
+    {section: 'showcase', href: 'showcase.html', text: 'Showcase'},
   ],
   linksExternal: [
     {section: 'github', href: 'https://github.com/facebook/react-native', text: 'GitHub'},
@@ -44,6 +45,9 @@ var HeaderLinks = React.createClass({
         <ul className="nav-site nav-site-internal">
           {this.makeLinks(this.linksInternal)}
         </ul>
+
+        <AlgoliaDocSearch />
+
         <ul className="nav-site nav-site-external">
           {this.makeLinks(this.linksExternal)}
         </ul>
diff --git a/website/core/HeaderWithGithub.js b/website/core/HeaderWithGithub.js
new file mode 100644
index 000000000000..7304caad40b7
--- /dev/null
+++ b/website/core/HeaderWithGithub.js
@@ -0,0 +1,40 @@
+/**
+ * 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 HeaderWithGithub
+ */
+
+var H = require('Header');
+var React = require('React');
+
+var HeaderWithGithub = React.createClass({
+  render: function() {
+    return (
+      <table width="100%">
+        <tbody>
+          <tr>
+            <td>
+              <H level={this.props.level || 3} toSlug={this.props.title}>
+                {this.props.title}
+              </H>
+            </td>
+            <td style={{textAlign: 'right'}}>
+              <a
+                target="_blank"
+                href={'https://github.com/facebook/react-native/blob/master/' + this.props.path}>
+                Edit on GitHub
+              </a>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    );
+  }
+});
+
+module.exports = HeaderWithGithub;
diff --git a/website/core/Marked.js b/website/core/Marked.js
index c769ad5c55db..7d3fa4626449 100644
--- a/website/core/Marked.js
+++ b/website/core/Marked.js
@@ -595,6 +595,13 @@ InlineLexer.prototype.output = function(src) {
     // tag
     if (cap = this.rules.tag.exec(src)) {
       src = src.substring(cap[0].length);
+
+      var color = cap[0].match('<color ([^ ]+) />');
+      if (color) {
+        out.push(React.DOM.span({className: 'color', style: {backgroundColor: color[1]}}));
+        continue;
+      }
+
       // TODO(alpert): Don't escape if sanitize is false
       out.push(cap[0]);
       continue;
@@ -811,13 +818,16 @@ Parser.prototype.tok = function() {
       return React.DOM.hr(null, null);
     }
     case 'heading': {
-      return Header(
-        {level: this.token.depth, toSlug: this.token.text},
-        this.inline.output(this.token.text)
+      return (
+        <Header
+          level={this.token.depth}
+          toSlug={this.token.text}>
+          {this.inline.output(this.token.text)}
+        </Header>
       );
     }
     case 'code': {
-      return Prism(null, this.token.text);
+      return <Prism>{this.token.text}</Prism>;
     }
     case 'table': {
       var table = []
diff --git a/website/core/Site.js b/website/core/Site.js
index e855a9666a78..19e3eaed058f 100644
--- a/website/core/Site.js
+++ b/website/core/Site.js
@@ -11,10 +11,15 @@
 
 var React = require('React');
 var HeaderLinks = require('HeaderLinks');
+var Metadata = require('Metadata');
 
 var Site = React.createClass({
   render: function() {
+    const path = Metadata.config.RN_DEPLOYMENT_PATH;
+    const version = Metadata.config.RN_VERSION;
+    var basePath = '/react-native/' + (path ? path + '/' : '');
     var title = this.props.title ? this.props.title + ' – ' : '';
+    var currentYear = (new Date()).getFullYear();
     title += 'React Native | A framework for building native apps using React';
     return (
       <html>
@@ -29,8 +34,12 @@ var Site = React.createClass({
           <meta property="og:image" content="http://facebook.github.io/react-native/img/opengraph.png?2" />
           <meta property="og:description" content="A framework for building native apps using React" />
 
-          <link rel="shortcut icon" href="/react-native/img/favicon.png?2" />
-          <link rel="stylesheet" href="/react-native/css/react-native.css" />
+          <base href={basePath} />
+
+          <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
+
+          <link rel="shortcut icon" href="img/favicon.png?2" />
+          <link rel="stylesheet" href="css/react-native.css" />
 
           <script type="text/javascript" src="//use.typekit.net/vqa1hcx.js"></script>
           <script type="text/javascript">{'try{Typekit.load();}catch(e){}'}</script>
@@ -40,10 +49,13 @@ var Site = React.createClass({
           <div className="container">
             <div className="nav-main">
               <div className="wrap">
-                <a className="nav-home" href="/react-native/">
-                  <img src="/react-native/img/header_logo.png" />
+                <a className="nav-home" href="">
+                  <img src="img/header_logo.png" />
                   React Native
                 </a>
+                <a className="nav-version" href="/react-native/versions.html">
+                  {version}
+                </a>
                 <HeaderLinks section={this.props.section} />
               </div>
             </div>
@@ -51,7 +63,7 @@ var Site = React.createClass({
             {this.props.children}
 
             <footer className="wrap">
-              <div className="center">© 2015 Facebook Inc.</div>
+              <div className="center">© {currentYear} Facebook Inc.</div>
             </footer>
           </div>
 
@@ -68,7 +80,8 @@ var Site = React.createClass({
             ){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";
             fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
           `}} />
-          <script src="/react-native/js/scripts.js" />
+          <script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
+          <script src="js/scripts.js" />
         </body>
       </html>
     );
diff --git a/website/layout/AutodocsLayout.js b/website/layout/AutodocsLayout.js
index 2b23a41daf3a..269314cb1e04 100644
--- a/website/layout/AutodocsLayout.js
+++ b/website/layout/AutodocsLayout.js
@@ -12,6 +12,7 @@
 var DocsSidebar = require('DocsSidebar');
 var H = require('Header');
 var Header = require('Header');
+var HeaderWithGithub = require('HeaderWithGithub');
 var Marked = require('Marked');
 var Prism = require('Prism');
 var React = require('React');
@@ -20,12 +21,20 @@ var slugify = require('slugify');
 
 var styleReferencePattern = /^[^.]+\.propTypes\.style$/;
 
+function renderEnumValue(value) {
+  // Use single quote strings even when we are given double quotes
+  if (value.match(/^"(.+)"$/)) {
+    return "'" + value.slice(1, -1) + "'";
+  }
+  return value;
+}
+
 function renderType(type) {
   if (type.name === 'enum') {
     if (typeof type.value === 'string') {
       return type.value;
     }
-    return 'enum(' + type.value.map((v) => v.value).join(', ') + ')';
+    return 'enum(' + type.value.map((v) => renderEnumValue(v.value)).join(', ') + ')';
   }
 
   if (type.name === 'shape') {
@@ -47,7 +56,10 @@ function renderType(type) {
   if (type.name === 'custom') {
     if (styleReferencePattern.test(type.raw)) {
       var name = type.raw.substring(0, type.raw.indexOf('.'));
-      return <a href={slugify(name) + '.html#style'}>{name}#style</a>
+      return <a href={'docs/' + slugify(name) + '.html#style'}>{name}#style</a>
+    }
+    if (type.raw === 'ColorPropType') {
+      return <a href={'docs/colors.html'}>color</a>
     }
     if (type.raw === 'EdgeInsetsPropType') {
       return '{top: number, left: number, bottom: number, right: number}';
@@ -65,6 +77,35 @@ function renderType(type) {
   return type.name;
 }
 
+function sortByPlatform(props, nameA, nameB) {
+  var a = props[nameA];
+  var b = props[nameB];
+
+  if (a.platforms && !b.platforms) {
+    return 1;
+  }
+  if (b.platforms && !a.platforms) {
+    return -1;
+  }
+
+  // Cheap hack: use < on arrays of strings to compare the two platforms
+  if (a.platforms < b.platforms) {
+    return -1;
+  }
+  if (a.platforms > b.platforms) {
+    return 1;
+  }
+
+  if (nameA < nameB) {
+    return -1;
+  }
+  if (nameA > nameB) {
+    return 1;
+  }
+
+  return 0;
+}
+
 var ComponentDoc = React.createClass({
   renderProp: function(name, prop) {
     return (
@@ -79,6 +120,15 @@ var ComponentDoc = React.createClass({
             {renderType(prop.type)}
           </span>}
         </Header>
+        {prop.deprecationMessage && <div className="deprecated">
+          <div className="deprecatedTitle">
+            <img className="deprecatedIcon" src="img/Warning.png" />
+            <span>Deprecated</span>
+          </div>
+          <div className="deprecatedMessage">
+            <Marked>{prop.deprecationMessage}</Marked>
+          </div>
+        </div>}
         {prop.type && prop.type.name === 'stylesheet' &&
           this.renderStylesheetProps(prop.type.value)}
         {prop.description && <Marked>{prop.description}</Marked>}
@@ -90,14 +140,34 @@ var ComponentDoc = React.createClass({
     return (
       <div className="prop" key={name}>
         <Header level={4} className="propTitle" toSlug={name}>
-          <a href={slugify(name) + '.html#props'}>{name} props...</a>
+          <a href={'docs/' + slugify(name) + '.html#props'}>{name} props...</a>
         </Header>
       </div>
     );
   },
 
+  renderStylesheetProp: function(name, prop) {
+    return (
+      <div className="prop" key={name}>
+        <h6 className="propTitle">
+          {prop.platforms && prop.platforms.map(platform =>
+            <span className="platform">{platform}</span>
+          )}
+          {name}
+          {' '}
+          {prop.type && <span className="propType">
+            {renderType(prop.type)}
+          </span>}
+          {' '}
+          {prop.description && <Marked>{prop.description}</Marked>}
+        </h6>
+      </div>
+    );
+  },
+
   renderStylesheetProps: function(stylesheetName) {
     var style = this.props.content.styles[stylesheetName];
+    this.extractPlatformFromProps(style.props);
     return (
       <div className="compactProps">
         {(style.composes || []).map((name) => {
@@ -105,15 +175,15 @@ var ComponentDoc = React.createClass({
           if (name === 'LayoutPropTypes') {
             name = 'Flexbox';
             link =
-              <a href={slugify(name) + '.html#proptypes'}>{name}...</a>;
+              <a href={'docs/' + slugify(name) + '.html#proptypes'}>{name}...</a>;
           } else if (name === 'TransformPropTypes') {
             name = 'Transforms';
             link =
-              <a href={slugify(name) + '.html#proptypes'}>{name}...</a>;
+              <a href={'docs/' + slugify(name) + '.html#proptypes'}>{name}...</a>;
           } else {
             name = name.replace('StylePropTypes', '');
             link =
-              <a href={slugify(name) + '.html#style'}>{name}#style...</a>;
+              <a href={'docs/' + slugify(name) + '.html#style'}>{name}#style...</a>;
           }
           return (
             <div className="prop" key={name}>
@@ -121,17 +191,10 @@ var ComponentDoc = React.createClass({
             </div>
           );
         })}
-        {Object.keys(style.props).map((name) =>
-          <div className="prop" key={name}>
-            <h6 className="propTitle">
-              {name}
-              {' '}
-              {style.props[name].type && <span className="propType">
-                {renderType(style.props[name].type)}
-              </span>}
-            </h6>
-          </div>
-        )}
+        {Object.keys(style.props)
+          .sort(sortByPlatform.bind(null, style.props))
+          .map((name) => this.renderStylesheetProp(name, style.props[name]))
+        }
       </div>
     );
   },
@@ -143,27 +206,9 @@ var ComponentDoc = React.createClass({
           this.renderCompose(name)
         )}
         {Object.keys(props)
-          .sort((nameA, nameB) => {
-            var a = props[nameA];
-            var b = props[nameB];
-
-            if (a.platforms && !b.platforms) {
-              return 1;
-            }
-            if (b.platforms && !a.platforms) {
-              return -1;
-            }
-            if (nameA < nameB) {
-              return -1;
-            }
-            if (nameA > nameB) {
-              return 1;
-            }
-            return 0;
-          })
-          .map((name) =>
-          this.renderProp(name, props[name])
-        )}
+          .sort(sortByPlatform.bind(null, props))
+          .map((name) => this.renderProp(name, props[name]))
+        }
       </div>
     );
   },
@@ -189,10 +234,7 @@ var ComponentDoc = React.createClass({
         <Marked>
           {content.description}
         </Marked>
-        <HeaderWithGithub
-          title="Props"
-          path={content.filepath}
-        />
+        <H level={3}>Props</H>
         {this.renderProps(content.props, content.composes)}
       </div>
     );
@@ -362,37 +404,58 @@ var APIDoc = React.createClass({
   }
 });
 
-var HeaderWithGithub = React.createClass({
-
-  renderRunnableLink: function() {
-    if (this.props.metadata && this.props.metadata.runnable) {
-      return (
-        <a
-          className="run-example"
-          target="_blank"
-          href={'https://rnplay.org/apps/l3Zi2g?route='+this.props.metadata.title+'&file=' + this.props.metadata.title+ "Example.js"}>
-          Run this example
-        </a>
-      );
+var EmbeddedSimulator = React.createClass({
+  render: function() {
+    if (!this.props.shouldRender) {
+      return null;
     }
-  },
 
+    var metadata = this.props.metadata;
+
+    return (
+      <div className="column-left">
+        <p><a className="modal-button-open"><strong>Run this example</strong></a></p>
+        <div className="modal-button-open modal-button-open-img">
+          <img alt="Run example in simulator" width="170" height="358" src="img/alertIOS.png" />
+        </div>
+        <Modal />
+      </div>
+    );
+  }
+});
+
+var Modal = React.createClass({
   render: function() {
+    var appParams = {route: 'AlertIOS'};
+    var encodedParams = encodeURIComponent(JSON.stringify(appParams));
+    var url = `https://appetize.io/embed/bypdk4jnjra5uwyj2kzd2aenv4?device=iphone5s&scale=70&autoplay=false&orientation=portrait&deviceColor=white&params=${encodedParams}`;
+
     return (
-      <H level={3} toSlug={this.props.title}>
-        <a
-          className="edit-github"
-          href={'https://github.com/facebook/react-native/blob/master/' + this.props.path}>
-          Edit on GitHub
-        </a>
-        {this.renderRunnableLink()}
-        {this.props.title}
-      </H>
+      <div>
+        <div className="modal">
+          <div className="modal-content">
+            <button className="modal-button-close">&times;</button>
+            <div className="center">
+              <iframe className="simulator" src={url} width="256" height="550" frameborder="0" scrolling="no"></iframe>
+              <p>Powered by <a target="_blank" href="https://appetize.io">appetize.io</a></p>
+            </div>
+          </div>
+        </div>
+        <div className="modal-backdrop" />
+      </div>
     );
   }
 });
 
 var Autodocs = React.createClass({
+  childContextTypes: {
+    permalink: React.PropTypes.string
+  },
+
+  getChildContext: function() {
+    return {permalink: this.props.metadata.permalink};
+  },
+
   renderFullDescription: function(docs) {
     if (!docs.fullDescription) {
       return;
@@ -442,13 +505,17 @@ var Autodocs = React.createClass({
           <DocsSidebar metadata={metadata} />
           <div className="inner-content">
             <a id="content" />
-            <h1>{metadata.title}</h1>
+            <HeaderWithGithub
+              title={metadata.title}
+              level={1}
+              path={metadata.path}
+            />
             {content}
             {this.renderFullDescription(docs)}
             {this.renderExample(docs, metadata)}
             <div className="docs-prevnext">
-              {metadata.previous && <a className="docs-prev" href={metadata.previous + '.html#content'}>&larr; Prev</a>}
-              {metadata.next && <a className="docs-next" href={metadata.next + '.html#content'}>Next &rarr;</a>}
+              {metadata.previous && <a className="docs-prev" href={'docs/' + metadata.previous + '.html#content'}>&larr; Prev</a>}
+              {metadata.next && <a className="docs-next" href={'docs/' + metadata.next + '.html#content'}>Next &rarr;</a>}
             </div>
           </div>
 
@@ -460,47 +527,4 @@ var Autodocs = React.createClass({
   }
 });
 
-var EmbeddedSimulator = React.createClass({
-  render: function() {
-    if (!this.props.shouldRender) {
-      return null;
-    }
-
-    var metadata = this.props.metadata;
-
-    return (
-      <div className="column-left">
-        <p><a className="modal-button-open"><strong>Run this example</strong></a></p>
-        <div className="modal-button-open modal-button-open-img">
-          <img alt="Run example in simulator" width="170" height="358" src="/react-native/img/alertIOS.png" />
-        </div>
-        <Modal />
-      </div>
-    );
-  }
-});
-
-var Modal = React.createClass({
-  render: function() {
-    var appParams = {route: 'AlertIOS'};
-    var encodedParams = encodeURIComponent(JSON.stringify(appParams));
-    var url = `https://appetize.io/embed/bypdk4jnjra5uwyj2kzd2aenv4?device=iphone5s&scale=70&autoplay=false&orientation=portrait&deviceColor=white&params=${encodedParams}`;
-
-    return (
-      <div>
-        <div className="modal">
-          <div className="modal-content">
-            <button className="modal-button-close">&times;</button>
-            <div className="center">
-              <iframe className="simulator" src={url} width="256" height="550" frameborder="0" scrolling="no"></iframe>
-              <p>Powered by <a target="_blank" href="https://appetize.io">appetize.io</a></p>
-            </div>
-          </div>
-        </div>
-        <div className="modal-backdrop" />
-      </div>
-    );
-  }
-});
-
 module.exports = Autodocs;
diff --git a/website/layout/DocsLayout.js b/website/layout/DocsLayout.js
index 12ba1ebf3a2e..d84d9682d7a1 100644
--- a/website/layout/DocsLayout.js
+++ b/website/layout/DocsLayout.js
@@ -9,11 +9,21 @@
  * @providesModule DocsLayout
  */
 
+var DocsSidebar = require('DocsSidebar');
+var HeaderWithGithub = require('HeaderWithGithub');
+var Marked = require('Marked');
 var React = require('React');
 var Site = require('Site');
-var Marked = require('Marked');
-var DocsSidebar = require('DocsSidebar');
+
 var DocsLayout = React.createClass({
+  childContextTypes: {
+    permalink: React.PropTypes.string
+  },
+
+  getChildContext: function() {
+    return {permalink: this.props.metadata.permalink};
+  },
+
   render: function() {
     var metadata = this.props.metadata;
     var content = this.props.children;
@@ -23,11 +33,15 @@ var DocsLayout = React.createClass({
           <DocsSidebar metadata={metadata} />
           <div className="inner-content">
             <a id="content" />
-            <h1>{metadata.title}</h1>
+            <HeaderWithGithub
+              title={metadata.title}
+              level={1}
+              path={'docs/' + metadata.filename}
+            />
             <Marked>{content}</Marked>
             <div className="docs-prevnext">
-              {metadata.previous && <a className="docs-prev" href={metadata.previous + '.html#content'}>&larr; Prev</a>}
-              {metadata.next && <a className="docs-next" href={metadata.next + '.html#content'}>Next &rarr;</a>}
+              {metadata.previous && <a className="docs-prev" href={'docs/' + metadata.previous + '.html#content'}>&larr; Prev</a>}
+              {metadata.next && <a className="docs-next" href={'docs/' + metadata.next + '.html#content'}>Next &rarr;</a>}
             </div>
           </div>
         </section>
diff --git a/website/layout/PageLayout.js b/website/layout/PageLayout.js
index a4ff02c60458..94697a207d6e 100644
--- a/website/layout/PageLayout.js
+++ b/website/layout/PageLayout.js
@@ -14,6 +14,14 @@ var Site = require('Site');
 var Marked = require('Marked');
 
 var support = React.createClass({
+  childContextTypes: {
+    permalink: React.PropTypes.string
+  },
+
+  getChildContext: function() {
+    return {permalink: this.props.metadata.permalink};
+  },
+
   render: function() {
     var metadata = this.props.metadata;
     var content = this.props.children;
diff --git a/website/package.json b/website/package.json
index 587e54060929..69e3093b99fd 100644
--- a/website/package.json
+++ b/website/package.json
@@ -1,19 +1,22 @@
 {
   "scripts": {
-    "start": "node server/server.js"
+    "start": "RN_VERSION=next node server/server.js",
+    "gh-pages": "node publish-gh-pages.js"
   },
   "dependencies": {
     "bluebird": "^2.9.21",
     "connect": "2.8.3",
-    "esprima-fb": "latest",
-    "fs.extra": "latest",
-    "glob": "latest",
-    "jstransform": "latest",
-    "mkdirp": "latest",
+    "esprima-fb": "15001.1001.0-dev-harmony-fb",
+    "fs.extra": "1.3.2",
+    "glob": "6.0.4",
+    "jstransform": "11.0.3",
+    "mkdirp": "^0.5.1",
     "optimist": "0.6.0",
-    "react": "~0.12.0",
+    "react": "~0.13.0",
     "react-docgen": "^2.0.1",
     "react-page-middleware": "git://github.com/facebook/react-page-middleware.git",
-    "request": "latest"
+    "request": "^2.69.0",
+    "semver-compare": "^1.0.0",
+    "shelljs": "^0.6.0"
   }
 }
diff --git a/website/publish-android.sh b/website/publish-android.sh
deleted file mode 100755
index 0476036b35c4..000000000000
--- a/website/publish-android.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-# This script publishes to gh-pages of the private github repo.
-# It assumes you have a react-native-android-gh-pages folder next to your react-native-android folder.
-# You can clone that using:
-# git clone -b gh-pages git@github.com:facebook/react-native-android.git react-native-android-gh-pages
-
-set -e
-
-# Start in website/ even if run from root directory
-cd "$(dirname "$0")"
-
-cd ../../react-native-android-gh-pages
-git checkout -- .
-git clean -dfx
-git fetch
-git rebase
-rm -Rf *
-cd ../react-native-android/website
-node server/generate.js
-cp -R build/react-native/* ../../react-native-android-gh-pages/
-rm -Rf build/
-cd ../../react-native-android-gh-pages
-git status
-if ! git diff-index --quiet HEAD --; then
-  git add -A .
-  git commit -m "update website"
-  git push origin gh-pages
-fi
diff --git a/website/publish-gh-pages.js b/website/publish-gh-pages.js
new file mode 100644
index 000000000000..25e43d6ea4df
--- /dev/null
+++ b/website/publish-gh-pages.js
@@ -0,0 +1,120 @@
+/**
+ * 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.
+ */
+'use strict';
+
+var semverCmp = require('semver-compare');
+require(`shelljs/global`);
+
+const CIRCLE_BRANCH = process.env.CIRCLE_BRANCH;
+const CIRCLE_PROJECT_USERNAME = process.env.CIRCLE_PROJECT_USERNAME;
+const CI_PULL_REQUEST = process.env.CI_PULL_REQUEST;
+const GIT_USER = process.env.GIT_USER;
+const remoteBranch = `https://${GIT_USER}@github.com/facebook/react-native.git`;
+
+if (!which(`git`)) {
+  echo(`Sorry, this script requires git`);
+  exit(1);
+}
+
+let version;
+if (CIRCLE_BRANCH.indexOf(`-stable`) !== -1) {
+  version = CIRCLE_BRANCH.slice(0, CIRCLE_BRANCH.indexOf(`-stable`));
+} else if (CIRCLE_BRANCH === `master`) {
+  version = `next`;
+}
+
+rm(`-rf`, `build`);
+mkdir(`-p`, `build`);
+// if current commit is tagged "latest" we do a release to gh-pages root
+const currentCommit = exec(`git rev-parse HEAD`).stdout.trim();
+const latestTagCommit = exec(`git ls-remote origin latest`).stdout.split(/\s/)[0];
+// pass along which branch contains latest version so that gh-pages root could mark it as latest
+const branchWithLatestTag = exec(`git branch -r --contains ${latestTagCommit}`).stdout.split('/')[1];
+let latestVersion = ``;
+if (branchWithLatestTag.indexOf(`-stable`) !== -1) {
+  latestVersion = branchWithLatestTag.slice(0, branchWithLatestTag.indexOf(`-stable`));
+}
+
+if (!CI_PULL_REQUEST && CIRCLE_PROJECT_USERNAME === `facebook`) {
+  echo(`Building branch ${version}, preparing to push to gh-pages`);
+  // if code is running in a branch in CI, commit changes to gh-pages branch
+  cd(`build`);
+  rm(`-rf`, `react-native-gh-pages`);
+
+  if (exec(`git clone ${remoteBranch} react-native-gh-pages`).code !== 0) {
+    echo(`Error: Git clone failed`);
+    exit(1);
+  }
+
+  cd(`react-native-gh-pages`);
+
+  if (exec(`git checkout origin/gh-pages`).code +
+    exec(`git checkout -b gh-pages`).code +
+    exec(`git branch --set-upstream-to=origin/gh-pages`).code !== 0
+    ) {
+    echo(`Error: Git checkout gh-pages failed`);
+    exit(1);
+  }
+  cd(`releases`);
+  let releasesFolders = ls(`-d`, `*`);
+  cd(`..`);
+  let versions = releasesFolders.filter(name => name !== `next`);
+  if (version !== `next` && versions.indexOf(version) === -1) {
+    versions.push(version);
+  }
+
+  versions.sort(semverCmp).reverse();
+
+  // generate to releases/XX when branch name indicates that it is some sort of release
+  if (!!version) {
+    echo(`------------ DEPLOYING /releases/${version}`);
+    rm(`-rf`, `releases/${version}`);
+    mkdir(`-p`, `releases/${version}`);
+    cd(`../..`);
+    if (exec(`RN_DEPLOYMENT_PATH=releases/${version} RN_VERSION=${version} RN_LATEST_VERSION=${latestVersion} \
+    RN_AVAILABLE_DOCS_VERSIONS=${versions.join(',')} node server/generate.js`).code !== 0) {
+      echo(`Error: Generating HTML failed`);
+      exit(1);
+    }
+    cd(`build/react-native-gh-pages`);
+    exec(`cp -R ../react-native/* releases/${version}`);
+    // versions.html is located in root of website and updated with every release
+    exec(`cp ../react-native/versions.html .`);
+  }
+  // generate to root folder when commit is tagged as latest, i.e. stable and needs to be shown at the root of repo
+  if (currentCommit === latestTagCommit) {
+    echo(`------------ DEPLOYING latest`);
+    // leave only releases folder
+    rm(`-rf`, ls(`*`).filter(name => name !== 'releases'));
+    cd(`../..`);
+    if (exec(`RN_VERSION=${version} RN_LATEST_VERSION=${latestVersion} \
+    RN_AVAILABLE_DOCS_VERSIONS=${versions} node server/generate.js`).code !== 0) {
+      echo(`Error: Generating HTML failed`);
+      exit(1);
+    }
+    cd(`build/react-native-gh-pages`);
+    exec(`cp -R ../react-native/* .`);
+  }
+  if (currentCommit === latestTagCommit || version) {
+    exec(`git status`);
+    exec(`git add -A .`);
+    if (exec(`git diff-index --quiet HEAD --`).code !== 0) {
+      if (exec(`git commit -m "Updated docs for ${version}"`).code !== 0) {
+        echo(`Error: Git commit gh-pages failed`);
+        exit(1);
+      }
+      if (exec(`git push origin gh-pages`).code !== 0) {
+        echo(`Error: Git push gh-pages failed`);
+        exit(1);
+      }
+    }
+    echo(`------------ gh-pages updated`);
+  }
+
+}
diff --git a/website/publish.sh b/website/publish.sh
deleted file mode 100755
index 7d34e18758f7..000000000000
--- a/website/publish.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-# 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.
-
-set -e
-
-# Start in website/ even if run from root directory
-cd "$(dirname "$0")"
-
-cd ../../react-native-gh-pages
-git checkout -- .
-git clean -dfx
-git fetch
-git rebase
-rm -Rf *
-cd ../react-native/website
-node server/generate.js
-cp -R build/react-native/* ../../react-native-gh-pages/
-rm -Rf build/
-cd ../../react-native-gh-pages
-git status
-git add -A .
-if ! git diff-index --quiet HEAD --; then
-  git commit -m "update website"
-  git push origin gh-pages
-fi
-cd ../react-native/website
diff --git a/website/server/convert.js b/website/server/convert.js
index 8edc1b190ba3..c1096758c53c 100644
--- a/website/server/convert.js
+++ b/website/server/convert.js
@@ -16,7 +16,7 @@ var extractDocs = require('./extractDocs');
 var argv = optimist.argv;
 
 function splitHeader(content) {
-  var lines = content.split('\n');
+  var lines = content.split(/\r?\n/);
   for (var i = 1; i < lines.length - 1; ++i) {
     if (lines[i] === '---') {
       break;
@@ -52,7 +52,7 @@ function execute() {
     files: [],
   };
 
-  function handleMarkdown(content) {
+  function handleMarkdown(content, filename) {
     var metadata = {};
 
     // Extract markdown metadata header
@@ -78,6 +78,8 @@ function execute() {
       return;
     }
 
+    metadata.filename = filename;
+
     // Create a dummy .js version that just calls the associated layout
     var layout = metadata.layout[0].toUpperCase() + metadata.layout.substr(1) + 'Layout';
 
@@ -87,15 +89,16 @@ function execute() {
       ' * @jsx React.DOM\n' +
       ' */\n' +
       'var React = require("React");\n' +
-      'var layout = require("' + layout + '");\n' +
+      'var Layout = require("' + layout + '");\n' +
       'var content = ' + backtickify(both.content) + '\n' +
       'var Post = React.createClass({\n' +
+      '  statics: {\n' +
+      '    content: content\n' +
+      '  },\n' +
       '  render: function() {\n' +
-      '    return layout({metadata: ' + JSON.stringify(metadata) + '}, content);\n' +
+      '    return <Layout metadata={' + JSON.stringify(metadata) + '}>{content}</Layout>;\n' +
       '  }\n' +
       '});\n' +
-      // TODO: Use React statics after upgrading React
-      'Post.content = content;\n' +
       'module.exports = Post;\n'
     );
 
@@ -104,14 +107,16 @@ function execute() {
     fs.writeFileSync(targetFile, content);
   }
 
-  extractDocs().forEach(handleMarkdown);
+  extractDocs().forEach(function(content) {
+    handleMarkdown(content, null);
+  });
 
   var files = glob.sync(MD_DIR + '**/*.*');
   files.forEach(function(file) {
     var extension = path.extname(file);
     if (extension === '.md' || extension === '.markdown') {
       var content = fs.readFileSync(file, {encoding: 'utf8'});
-      handleMarkdown(content);
+      handleMarkdown(content, path.basename(file));
     }
 
     if (extension === '.json') {
@@ -120,6 +125,17 @@ function execute() {
     }
   });
 
+  // we need to pass globals for the components to be configurable
+  // metadata is generated in this process which has access to process.env
+  // but the web pages are generated in a sandbox context and have only access to CommonJS module files
+  metadatas.config = Object.create(null);
+  Object
+    .keys(process.env)
+    .filter(key => key.startsWith('RN_'))
+    .forEach((key) => {
+      metadatas.config[key] = process.env[key];
+    });
+
   fs.writeFileSync(
     'core/metadata.js',
     '/**\n' +
diff --git a/website/server/docgenHelpers.js b/website/server/docgenHelpers.js
index 89762e0cf073..36873fdee75c 100644
--- a/website/server/docgenHelpers.js
+++ b/website/server/docgenHelpers.js
@@ -6,6 +6,7 @@ function stylePropTypeHandler(documentation, path) {
   if (!propTypesPath) {
     return;
   }
+
   propTypesPath = docgen.utils.resolveToValue(propTypesPath);
   if (!propTypesPath || propTypesPath.node.type !== 'ObjectExpression') {
     return;
@@ -34,6 +35,43 @@ function stylePropTypeHandler(documentation, path) {
   });
 }
 
+function deprecatedPropTypeHandler(documentation, path) {
+  var propTypesPath = docgen.utils.getMemberValuePath(path, 'propTypes');
+  if (!propTypesPath) {
+    return;
+  }
+
+  propTypesPath = docgen.utils.resolveToValue(propTypesPath);
+  if (!propTypesPath || propTypesPath.node.type !== 'ObjectExpression') {
+    return;
+  }
+
+  // Checks for deprecatedPropType function and add deprecation info.
+  propTypesPath.get('properties').each(function(propertyPath) {
+    var valuePath = docgen.utils.resolveToValue(propertyPath.get('value'));
+    // If it's a call to deprecatedPropType, do stuff
+    if (valuePath.node.type !== 'CallExpression' ||
+        valuePath.node.callee.name !== 'deprecatedPropType') {
+      return;
+    }
+    var propDescriptor = documentation.getPropDescriptor(
+      docgen.utils.getPropertyName(propertyPath)
+    );
+    // The 2nd argument of deprecatedPropType is the deprecation message.
+    // Used printValue to get the string otherwise there was issues with template
+    // strings.
+    propDescriptor.deprecationMessage = docgen.utils
+      .printValue(valuePath.get('arguments', 1))
+      // Remove the quotes printValue adds.
+      .slice(1, -1);
+
+    // Get the actual prop type.
+    propDescriptor.type = docgen.utils.getPropType(
+      valuePath.get('arguments', 0)
+    );
+  });
+}
+
 function findExportedOrFirst(node, recast) {
   return docgen.resolver.findExportedComponentDefinition(node, recast) ||
     docgen.resolver.findAllComponentDefinitions(node, recast)[0];
@@ -86,5 +124,6 @@ function findExportedObject(ast, recast) {
 }
 
 exports.stylePropTypeHandler = stylePropTypeHandler;
+exports.deprecatedPropTypeHandler = deprecatedPropTypeHandler;
 exports.findExportedOrFirst = findExportedOrFirst;
 exports.findExportedObject = findExportedObject;
diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js
index 1d8abc600257..782d006fbe6e 100644
--- a/website/server/extractDocs.js
+++ b/website/server/extractDocs.js
@@ -137,6 +137,7 @@ function componentsToMarkdown(type, json, filepath, i, styles) {
     'next: ' + next,
     'sidebar: ' + shouldDisplayInSidebar(componentName),
     'runnable:' + isRunnable(componentName),
+    'path:' + json.filepath,
     '---',
     JSON.stringify(json, null, 2),
   ].filter(function(line) { return line; }).join('\n');
@@ -149,7 +150,10 @@ function renderComponent(filepath) {
   var json = docgen.parse(
     fs.readFileSync(filepath),
     docgenHelpers.findExportedOrFirst,
-    docgen.defaultHandlers.concat(docgenHelpers.stylePropTypeHandler)
+    docgen.defaultHandlers.concat([
+      docgenHelpers.stylePropTypeHandler,
+      docgenHelpers.deprecatedPropTypeHandler,
+    ])
   );
 
   return componentsToMarkdown('component', json, filepath, n++, styleDocs);
@@ -192,17 +196,19 @@ var components = [
   '../Libraries/Image/Image.ios.js',
   '../Libraries/CustomComponents/ListView/ListView.js',
   '../Libraries/Components/MapView/MapView.js',
-  '../Libraries/CustomComponents/Navigator/Navigator.js',
   '../Libraries/Modal/Modal.js',
+  '../Libraries/CustomComponents/Navigator/Navigator.js',
   '../Libraries/Components/Navigation/NavigatorIOS.ios.js',
   '../Libraries/Picker/PickerIOS.ios.js',
+  '../Libraries/Components/Picker/Picker.js',
   '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js',
   '../Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js',
+  '../Libraries/Components/RefreshControl/RefreshControl.js',
   '../Libraries/Components/ScrollView/ScrollView.js',
   '../Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js',
   '../Libraries/Components/SliderIOS/SliderIOS.ios.js',
-  '../Libraries/Components/SwitchAndroid/SwitchAndroid.android.js',
-  '../Libraries/Components/SwitchIOS/SwitchIOS.ios.js',
+  '../Libraries/Components/StatusBar/StatusBar.js',
+  '../Libraries/Components/Switch/Switch.js',
   '../Libraries/Components/TabBarIOS/TabBarIOS.ios.js',
   '../Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js',
   '../Libraries/Text/Text.js',
@@ -219,16 +225,22 @@ var components = [
 
 var apis = [
   '../Libraries/ActionSheetIOS/ActionSheetIOS.js',
+  '../Libraries/Utilities/Alert.js',
   '../Libraries/Utilities/AlertIOS.js',
   '../Libraries/Animated/src/AnimatedImplementation.js',
   '../Libraries/AppRegistry/AppRegistry.js',
   '../Libraries/AppStateIOS/AppStateIOS.ios.js',
+  '../Libraries/AppState/AppState.js',
   '../Libraries/Storage/AsyncStorage.js',
   '../Libraries/Utilities/BackAndroid.android.js',
   '../Libraries/CameraRoll/CameraRoll.js',
+  '../Libraries/Components/Clipboard/Clipboard.js',
+  '../Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js',
   '../Libraries/Utilities/Dimensions.js',
+  '../Libraries/Components/Intent/IntentAndroid.android.js',
   '../Libraries/Interaction/InteractionManager.js',
   '../Libraries/LayoutAnimation/LayoutAnimation.js',
+  '../Libraries/Linking/Linking.js',
   '../Libraries/LinkingIOS/LinkingIOS.js',
   '../Libraries/ReactIOS/NativeMethodsMixin.js',
   '../Libraries/Network/NetInfo.js',
@@ -237,33 +249,43 @@ var apis = [
   '../Libraries/PushNotificationIOS/PushNotificationIOS.js',
   '../Libraries/Components/StatusBar/StatusBarIOS.ios.js',
   '../Libraries/StyleSheet/StyleSheet.js',
+  '../Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js',
   '../Libraries/Components/ToastAndroid/ToastAndroid.android.js',
   '../Libraries/Vibration/VibrationIOS.ios.js',
+  '../Libraries/Vibration/Vibration.js',
 ];
 
-var styles = [
+var stylesWithPermalink = [
   '../Libraries/StyleSheet/LayoutPropTypes.js',
   '../Libraries/StyleSheet/TransformPropTypes.js',
+  '../Libraries/Components/View/ShadowPropTypesIOS.js',
+];
+
+var stylesForEmbed = [
   '../Libraries/Components/View/ViewStylePropTypes.js',
   '../Libraries/Text/TextStylePropTypes.js',
   '../Libraries/Image/ImageStylePropTypes.js',
 ];
 
 var polyfills = [
-  '../Libraries/GeoLocation/Geolocation.js',
+  '../Libraries/Geolocation/Geolocation.js',
 ];
 
 var all = components
   .concat(apis)
-  .concat(styles.slice(0, 2))
+  .concat(stylesWithPermalink)
   .concat(polyfills);
 
-var styleDocs = styles.slice(2).reduce(function(docs, filepath) {
+var styleDocs = stylesForEmbed.reduce(function(docs, filepath) {
   docs[path.basename(filepath).replace(path.extname(filepath), '')] =
     docgen.parse(
       fs.readFileSync(filepath),
       docgenHelpers.findExportedObject,
-      [docgen.handlers.propTypeHandler, docgen.handlers.propTypeCompositionHandler]
+      [
+        docgen.handlers.propTypeHandler,
+        docgen.handlers.propTypeCompositionHandler,
+        docgen.handlers.propDocBlockHandler,
+      ]
     );
 
   return docs;
@@ -274,7 +296,7 @@ module.exports = function() {
   return [].concat(
     components.map(renderComponent),
     apis.map(renderAPI('api')),
-    styles.slice(0, 2).map(renderStyle),
+    stylesWithPermalink.map(renderStyle),
     polyfills.map(renderAPI('Polyfill'))
   );
 };
diff --git a/website/server/generate.js b/website/server/generate.js
index ab59a51ad048..dc96e2e8de8d 100644
--- a/website/server/generate.js
+++ b/website/server/generate.js
@@ -6,6 +6,7 @@
  * 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.
  */
+'use strict';
 
 var Promise = require('bluebird');
 var request = require('request');
@@ -56,7 +57,7 @@ glob('src/**/*.*', function(er, files) {
   });
 
   queue = queue.then(function() {
-    console.log('It is live at: http://facebook.github.io/react-native/');
+    console.log('Generated HTML files from JS');
   }).finally(function() {
     server.close();
   }).catch(function(e) {
diff --git a/website/setup.sh b/website/setup.sh
index 094926583d9e..9c496abf69d3 100755
--- a/website/setup.sh
+++ b/website/setup.sh
@@ -7,6 +7,9 @@
 # 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.
 
+# Start in website/ even if run from root directory
+cd "$(dirname "$0")"
+
 cd ../../
 if [ "$TRAVIS" ]; then
   git clone "https://reactjs-bot@github.com/facebook/react-native.git" react-native-gh-pages
@@ -18,4 +21,3 @@ git checkout origin/gh-pages
 git checkout -b gh-pages
 git branch --set-upstream-to=origin/gh-pages
 cd ../react-native/website
-
diff --git a/website/src/react-native/circle.yml b/website/src/react-native/circle.yml
new file mode 100644
index 000000000000..56ad41b2f149
--- /dev/null
+++ b/website/src/react-native/circle.yml
@@ -0,0 +1,4 @@
+general:
+  branches:
+    ignore:
+      - gh-pages
diff --git a/website/src/react-native/css/react-native.css b/website/src/react-native/css/react-native.css
index 54e3930f94b7..98e60504a1bf 100644
--- a/website/src/react-native/css/react-native.css
+++ b/website/src/react-native/css/react-native.css
@@ -312,6 +312,14 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
   display: inline;
 }
 
+.nav-main a.nav-version {
+  font-size: 16px;
+  font-weight: 800;
+  color: #05A5D1;
+  margin-left: 5px;
+  text-decoration: underline;
+}
+
 .hero {
   background: #05A5D1;
   padding: 50px 0;
@@ -478,6 +486,15 @@ h1:hover .hash-link, h2:hover .hash-link, h3:hover .hash-link, h4:hover .hash-li
   box-shadow: 5px 5px 5px #888888;
 }
 
+.versions ul {
+  list-style: none;
+}
+
+.versions li {
+  font-size: 16px;
+  padding-top: 10px;
+}
+
 #examples h3, .home-presentation h3 {
   color: #2d2d2d;
   font-size: 24px;
@@ -701,6 +718,10 @@ figure {
   width: 650px;
 }
 
+.showcaseSection .inner-content {
+  width: 800px;
+}
+
 .nosidebar .inner-content {
   float: none;
   margin: 0 auto;
@@ -1037,6 +1058,11 @@ div[data-twttr-id] iframe {
   margin-top: 0;
 }
 
+.compactProps .propTitle div {
+  font-weight: normal;
+  margin-left: 20px;
+}
+
 .prop {
   padding: 5px 10px;
 }
@@ -1067,16 +1093,46 @@ div[data-twttr-id] iframe {
   -ms-user-select: none;
 }
 
-.edit-github {
-  font-size: 15px;
-  font-weight: normal;
-  float: right;
+.color {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  margin-right: 5px;
+  position: relative;
+  top: 5px;
 }
 
-.run-example {
-  font-size: 15px;
-  float: right;
-  margin-right: 20px;
+.color::before {
+  content: '';
+  display: block;
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+.deprecated {
+  margin-bottom: 24px;
+}
+
+.deprecatedTitle {
+  margin-bottom: 6px;
+  line-height: 18px;
+  font-weight: bold;
+  color: #ffa500;
+}
+
+.deprecatedIcon {
+  width: 18px;
+  height: 18px;
+  margin-right: 8px;
+  vertical-align: top;
+}
+
+.deprecatedMessage {
+  margin-left: 26px;
 }
 
 #content {
@@ -1084,18 +1140,17 @@ div[data-twttr-id] iframe {
 }
 
 .showcaseHeader {
-  text-align: center;
   padding-bottom: 15px;
   padding-top: 15px;
+  text-align: center;
 }
 
 .showcase {
-  margin: 30px auto;
+  margin: 30px auto 10px auto;
   width: 100%;
   display: inline-block;
   text-align: center;
   vertical-align: top;
-  transition: 0.2s opacity ease-in;
 }
 
 @media only screen
@@ -1109,7 +1164,7 @@ div[data-twttr-id] iframe {
 @media only screen
   and (min-device-width: 1024px) {
   .showcase {
-    width: 25%;
+    width: 20%;
   }
 }
 
@@ -1128,6 +1183,7 @@ div[data-twttr-id] iframe {
 
 .showcase p {
   margin-top: 5px;
+  font-weight: normal;
 }
 
 .showcase a {
@@ -1210,3 +1266,105 @@ div[data-twttr-id] iframe {
     margin: 0;
   }
 }
+
+
+@media only screen and (max-device-width: 840px) {
+  .showcaseSection .inner-content {
+    width: 100%;
+  }
+}
+
+/** Algolia Doc Search **/
+
+
+div.algolia-search-wrapper {
+  display: inline-block;
+  vertical-align: top;
+  margin-left: 15px;
+}
+
+@media screen and (max-width: 960px) {
+  div.algolia-search-wrapper {
+    display: none;
+  }
+}
+
+input#algolia-doc-search {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-family: proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+  background: transparent url('../img/search.png') no-repeat left center;
+  background-size: 16px 16px;
+
+  padding-left: 30px;
+  font-size: 16px;
+  line-height: 20px;
+  background-color: #3B3738;
+  border-bottom: solid 3px #3B3738;
+  color: white;
+  outline: none;
+  width: 130px;
+  height: 53px;
+
+  transition: border-color .2s ease, width .2s ease;
+  -webkit-transition: border-color .2s ease, width .2s ease;
+  -moz-transition: border-color .2s ease, width .2s ease;
+  -o-transition: border-color .2s ease, width .2s ease;
+}
+
+input#algolia-doc-search:focus {
+  border-color: #05A5D1;
+  width: 240px;
+}
+
+@media screen and (max-width: 1085px) {
+  input#algolia-doc-search:focus {
+    width: 178px;
+  }
+}
+
+.algolia-autocomplete {
+  vertical-align: top;
+  height: 53px;
+
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-family: proxima-nova, "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+/* Bottom border of each suggestion */
+.algolia-docsearch-suggestion {
+  border-bottom-color: #05A5D1;
+}
+/* Main category headers */
+.algolia-docsearch-suggestion--category-header {
+  background-color: #3B3738;
+}
+/* Highlighted search terms */
+.algolia-docsearch-suggestion--highlight {
+  color: #05A5D1;
+}
+/* Highligted search terms in the main category headers */
+.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight  {
+  background-color: #05A5D1;
+}
+/* Currently selected suggestion */
+.aa-cursor .algolia-docsearch-suggestion--content {
+  color: #05A5D1;
+}
+.aa-cursor .algolia-docsearch-suggestion {
+  background: hsl(198, 100%, 96%);
+}
+
+/* For bigger screens, when displaying results in two columns */
+@media (min-width: 768px) {
+  /* Bottom border of each suggestion */
+  .algolia-docsearch-suggestion {
+    border-bottom-color: hsl(198, 100%, 94%);
+  }
+  /* Left column, with secondary category header */
+  .algolia-docsearch-suggestion--subcategory-column {
+    border-right-color: hsl(198, 100%, 94%);
+    background-color: hsl(198, 100%, 96%);
+    color: #3B3738;
+  }
+}
diff --git a/website/src/react-native/img/Warning.png b/website/src/react-native/img/Warning.png
new file mode 100644
index 000000000000..ceeb6edf2c66
Binary files /dev/null and b/website/src/react-native/img/Warning.png differ
diff --git a/website/src/react-native/img/search.png b/website/src/react-native/img/search.png
new file mode 100644
index 000000000000..222fb660dae6
Binary files /dev/null and b/website/src/react-native/img/search.png differ
diff --git a/website/src/react-native/index.js b/website/src/react-native/index.js
index 769a5f0866f8..a85130d54412 100644
--- a/website/src/react-native/index.js
+++ b/website/src/react-native/index.js
@@ -68,7 +68,7 @@ var App = React.createClass({
 {`// Android
 
 var React = require('react-native');
-var { DrawerLayoutAndroid, ProgressBarAndroid } = React;
+var { DrawerLayoutAndroid, ProgressBarAndroid, Text } = React;
 
 var App = React.createClass({
   render: function() {
@@ -89,7 +89,7 @@ var App = React.createClass({
           <p>
             See <a href="docs/debugging.html#content">Debugging</a>.
           </p>
-          <img src="/react-native/img/chrome_breakpoint.png" width="800" height="443" />
+          <img src="img/chrome_breakpoint.png" width="800" height="443" />
 
           <h2>Touch Handling</h2>
           <p>
@@ -345,7 +345,7 @@ public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
   }
 
   @Override
-  public void updateView(MyCustomView view, CatalystStylesDiffMap props) {
+  public void updateView(MyCustomView view, ReactStylesDiffMap props) {
     super.updateView(view, props);
 
     if (props.hasKey(PROP_MY_CUSTOM_PROPERTY)) {
diff --git a/website/src/react-native/js/scripts.js b/website/src/react-native/js/scripts.js
index 85e92a78c3bd..8e3d9d440a70 100644
--- a/website/src/react-native/js/scripts.js
+++ b/website/src/react-native/js/scripts.js
@@ -8,6 +8,8 @@
 
   function init() {
     var backdrop = document.querySelector('.modal-backdrop');
+    if (!backdrop) return;
+
     var modalButtonOpenList = document.querySelectorAll('.modal-button-open');
     var modalButtonClose = document.querySelector('.modal-button-close');
 
@@ -22,6 +24,8 @@
 
   function showModal(e) {
     var backdrop = document.querySelector('.modal-backdrop');
+    if (!backdrop) return;
+
     var modal = document.querySelector('.modal');
 
     backdrop.classList.add('modal-open');
@@ -30,10 +34,19 @@
 
   function hideModal(e) {
     var backdrop = document.querySelector('.modal-backdrop');
+    if (!backdrop) return;
+
     var modal = document.querySelector('.modal');
 
     backdrop.classList.remove('modal-open');
     modal.classList.remove('modal-open');
   }
 
+  // Algolia
+  docsearch({
+    apiKey: 'e3d767b736584dbe6d4c35f7cf7d4633',
+    indexName: 'react-native',
+    inputSelector: '#algolia-doc-search'
+  });
+
 }());
diff --git a/website/src/react-native/showcase.js b/website/src/react-native/showcase.js
index 5d485df69d2f..5746d97a1f6d 100644
--- a/website/src/react-native/showcase.js
+++ b/website/src/react-native/showcase.js
@@ -11,19 +11,181 @@ var React = require('React');
 var Site = require('Site');
 var center = require('center');
 
-var apps = [
+var featured = [
   {
-    name: 'AIGA Design Conference 2015: New Orleans',
+    name: 'Facebook Groups',
+    icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple69/v4/57/f8/4c/57f84c0c-793d-5f9a-95ee-c212d0369e37/mzl.ugjwfhzx.png',
+    link: 'https://itunes.apple.com/us/app/facebook-groups/id931735837?mt=8',
+    author: 'Facebook',
+  },
+  {
+    name: 'Facebook Ads Manager',
+    icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple5/v4/9e/16/86/9e1686ef-cc55-805a-c977-538ddb5e6832/mzl.gqbhwitj.png',
+    linkAppStore: 'https://itunes.apple.com/us/app/facebook-ads-manager/id964397083?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.facebook.adsmanager',
+    author: 'Facebook',
+    blogs: [
+      "https://code.facebook.com/posts/1014532261909640/react-native-bringing-modern-web-techniques-to-mobile/",
+      "https://code.facebook.com/posts/1189117404435352/react-native-for-android-how-we-built-the-first-cross-platform-react-native-app/",
+      "https://code.facebook.com/posts/435862739941212/making-react-native-apps-accessible/",
+    ],
+  },
+  {
+    name: 'AIGA Design Conference 2015',
     icon: 'http://a5.mzstatic.com/us/r30/Purple69/v4/b0/4b/29/b04b2939-88d2-f61f-dec9-24fae083d8b3/icon175x175.png',
     link: 'https://itunes.apple.com/us/app/aiga-design-conference-2015/id1038145272?ls=1&mt=8',
     author: 'W&Co',
   },
+  {
+    name: 'Discord',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg',
+    link:  'https://itunes.apple.com/us/app/discord-chat-for-gamers/id985746746?mt=8',
+    author: 'Hammer & Chisel',
+  },
+  {
+    name: 'Discovery VR',
+    icon: 'http://a2.mzstatic.com/us/r30/Purple6/v4/d1/d5/f4/d1d5f437-9f6b-b5aa-5fe7-47bd19f934bf/icon175x175.png',
+    link:  'https://itunes.apple.com/us/app/discovery-vr/id1030815031?mt=8',
+    author: 'Discovery Communications',
+    blog: [
+      "https://medium.com/ios-os-x-development/an-ios-developer-on-react-native-1f24786c29f0",
+    ],
+  },
+  {
+    name: 'Exponent',
+    icon: 'http://a4.mzstatic.com/us/r30/Purple2/v4/3a/d3/c9/3ad3c96c-5e14-f988-4bdd-0fdc95efd140/icon175x175.png',
+    link:  'http://exponentjs.com/',
+    author: 'Exponent',
+  },
+  {
+    name: 'Lrn',
+    icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple1/v4/41/a9/e9/41a9e9b6-7801-aef7-2400-2eca14923321/mzl.adyswxad.png',
+    link: 'https://itunes.apple.com/us/app/lrn-learn-to-code-at-your/id1019622677',
+    author: 'Lrn Labs, Inc',
+  },
+  {
+    name: 'Movie Trailers by MovieLaLa',
+    icon: 'https://lh3.googleusercontent.com/16aug4m_6tvJB7QZden9w1SOMqpZgNp7rHqDhltZNvofw1a4V_ojGGXUMPGiK0dDCqzL=w300',
+    linkAppStore: 'https://itunes.apple.com/us/app/movie-trailers-by-movielala/id1001416601?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.movielala.trailers',
+    author: 'MovieLaLa'
+  },
+  {
+    name: 'MyMuesli',
+    icon: 'https://lh3.googleusercontent.com/1dCCeiyjuWRgY-Cnv-l-lOA1sVH3Cn0vkVWWZnaBQbhHOhsngLcnfy68WEerudPUysc=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.mymuesli',
+    author: 'Shawn Khameneh (@shawnscode), 3DWD'
+  },
+  {
+    name: 'Myntra',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/9c/78/df/9c78dfa6-0061-1af2-5026-3e1d5a073c94/icon350x350.png',
+    link: 'https://itunes.apple.com/in/app/myntra-fashion-shopping-app/id907394059',
+    author: 'Myntra Designs',
+  },
+  {
+    name: 'Noodler',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/d9/9a/69/d99a6919-7f11-35ad-76ea-f1741643d875/icon175x175.png',
+    link: 'http://www.noodler-app.com/',
+    author: 'Michele Humes & Joshua Sierles',
+  },
+  {
+    name: 'React Native Playground',
+    icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple1/v4/20/ec/8e/20ec8eb8-9e12-6686-cd16-7ac9e3ef1d52/mzl.ngvuoybx.png',
+    linkAppStore: 'https://itunes.apple.com/us/app/react-native-playground/id1002032944?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=org.rnplay.playground',
+    author: 'Joshua Sierles',
+  },
+  {
+    name: 'Round - A better way to remember your medicine',
+    icon: 'https://s3.mzstatic.com/us/r30/Purple69/v4/d3/ee/54/d3ee54cf-13b6-5f56-0edc-6c70ac90b2be/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/round-beautiful-medication/id1059591124?mt=8',
+    author: 'Circadian Design',
+  },
+  {
+    name: 'Running',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple3/v4/33/eb/4f/33eb4f73-c7e3-8659-9285-f758e403485b/icon175x175.jpeg',
+    link: 'https://gyrosco.pe/running/',
+    author: 'Gyroscope Innovations',
+    blogs: [
+      'https://blog.gyrosco.pe/the-making-of-gyroscope-running-a4ad10acc0d0',
+    ],
+  },
+  {
+    name: 'Spero for Cancer',
+    icon: 'https://s3-us-west-1.amazonaws.com/cancerspot/site_images/Spero1024.png',
+    link: 'https://geo.itunes.apple.com/us/app/spero-for-cancer/id1033923573?mt=8',
+    author: 'Spero.io',
+    videos: [
+      'https://www.youtube.com/watch?v=JImX3L6qnj8',
+    ],
+  },
+  {
+    name: 'Squad',
+    icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/e8/5b/3f/e85b3f52-72f3-f427-a32e-a73efe2e9682/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/us/app/squad-snaps-for-groups-friends/id1043626975?mt=8',
+    author: 'Tackk Inc.',
+  },
+  {
+    name: 'Start - medication manager for depression',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple49/v4/de/9b/6f/de9b6fe8-84ea-7a12-ba2c-0a6d6c7b10b0/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/start-medication-manager-for/id1012099928?mt=8',
+    author: 'Iodine Inc.',
+  },
+  {
+    name: 'This AM',
+    icon: 'http://s3.r29static.com//bin/public/efe/x/1542038/image.png',
+    link: 'https://itunes.apple.com/us/app/refinery29-this-am-top-breaking/id988472315?mt=8',
+    author: 'Refinery29',
+  },
+  {
+    name: 'Townske',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/8b/42/20/8b4220af-5165-91fd-0f05-014332df73ef/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/townske-stunning-city-guides/id1018136179?ls=1&mt=8',
+    author: 'Townske PTY LTD',
+  },
+  {
+    name: 'Tucci',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple3/v4/c0/0c/95/c00c95ce-4cc5-e516-db77-5c5164b89189/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/app/apple-store/id1039661754?mt=8',
+    author: 'Clay Allsopp & Tiffany Young',
+    blogs: [
+      'https://medium.com/@clayallsopp/making-tucci-what-is-it-and-why-eaa2bf94c1df#.lmm3dmkaf',
+      'https://medium.com/@clayallsopp/making-tucci-the-technical-details-cc7aded6c75f#.wf72nq372',
+    ],
+  },
+  {
+    name: 'WPV',
+    icon: 'http://a2.mzstatic.com/us/r30/Purple49/v4/a8/26/d7/a826d7bf-337b-c6b8-488d-aca98027754d/icon350x350.png',
+    link: 'https://itunes.apple.com/us/app/wpv/id725222647?mt=8',
+    author: 'Yamill Vallecillo',
+  },
+];
+
+var apps = [
+  {
+    name: 'Accio',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple3/v4/03/a1/5b/03a15b9f-04d7-a70a-620a-9c9850a859aa/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/accio-on-demand-delivery/id1047060673?mt=8',
+    author: 'Accio Delivery Inc.',
+  },
+  {
+    name: 'ArcChat.com',
+    icon: 'https://lh3.googleusercontent.com/mZJjidMobu3NAZApdtp-vdBBzIWzCNTaIcKShbGqwXRRzL3B9bbi6E0eRuykgT6vmg=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.arcchat',
+    author: 'Lukas Liesis',
+  },
   {
     name: 'Beetroot',
     icon: 'http://is1.mzstatic.com/image/pf/us/r30/Purple5/v4/66/fd/dd/66fddd70-f848-4fc5-43ee-4d52197ccab8/pr_source.png',
     link: 'https://itunes.apple.com/us/app/beetroot/id1016159001?ls=1&mt=8',
     author: 'Alex Duckmanton',
   },
+  {
+    name: 'Bhagavad Gita Lite',
+    icon: 'https://s3-us-west-2.amazonaws.com/bhagavadgitaapp/gita-free.png',
+    link: 'https://itunes.apple.com/us/app/bhagavad-gita-lite/id1071711190?ls=1&mt=8',
+    author: 'Tom Goldenberg & Nick Brown'
+  },
   {
     name: 'Bionic eStore',
     icon: 'http://a5.mzstatic.com/us/r30/Purple7/v4/c1/9a/3f/c19a3f82-ecc3-d60b-f983-04acc203705f/icon175x175.jpeg',
@@ -38,53 +200,78 @@ var apps = [
     author: 'CANDDi LTD.',
   },
   {
-    name: 'Company name search',
-    icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/fd/47/53/fd47537c-5861-e208-d1d1-1e26b5e45a36/icon350x350.jpeg',
-    link: 'https://itunes.apple.com/us/app/company-name-search/id1043824076',
-    author: 'The Formations Factory Ltd',
+    name: 'CaratLane',
+    icon: 'https://lh3.googleusercontent.com/wEN-Vvpbnw_n89dbXPxWkNnXB7sALKBKvpX_hbzrWbuC4tFi5tVkWHq8k5TAvdbf5UQ=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.caratlane.android&hl=en',
+    author: 'CaratLane',
   },
   {
-    name: 'Discord',
-    icon: 'http://a5.mzstatic.com/us/r30/Purple5/v4/c1/2f/4c/c12f4cba-1d9a-f6bf-2240-04085d3470ec/icon175x175.jpeg',
-    link:  'https://itunes.apple.com/us/app/discord-chat-for-gamers/id985746746?mt=8',
-    author: 'Hammer & Chisel',
+    name: 'CBS Sports Franchise Football',
+    icon: 'http://a2.mzstatic.com/us/r30/Purple69/v4/7b/0c/a0/7b0ca007-885a-7cfc-9fa2-2ec4394c2ecc/icon175x175.png',
+    link: 'https://play.google.com/store/apps/details?id=com.cbssports.fantasy.franchisefootball2015',
+    author: 'CBS Sports',
   },
   {
-    name: 'Discovery VR',
-    icon: 'http://a2.mzstatic.com/us/r30/Purple6/v4/d1/d5/f4/d1d5f437-9f6b-b5aa-5fe7-47bd19f934bf/icon175x175.png',
-    link:  'https://itunes.apple.com/us/app/discovery-vr/id1030815031?mt=8',
-    author: 'Discovery Communications',
+    name: 'Choke - Rap Battle With Friends',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple49/v4/3e/83/85/3e8385d8-140f-da38-a100-1393cef3e816/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/choke-rap-battle-with-friends/id1077937445?ls=1&mt=8',
+    author: 'Henry Kirkness',
   },
   {
-    name: 'DropBot',
-    icon: 'http://a2.mzstatic.com/us/r30/Purple69/v4/fb/df/73/fbdf73e0-22d2-c936-3115-1defa195acba/icon175x175.png',
-    link:  'https://itunes.apple.com/us/app/dropbot-phone-replacement/id1000855694?mt=8',
-    author: 'Peach Labs',
+    name: 'Codementor - Live 1:1 Expert Developer Help',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple3/v4/db/cf/35/dbcf3523-bac7-0f54-c6a8-a80bf4f43c38/icon175x175.jpeg',
+    link: 'https://www.codementor.io/downloads',
+    author: 'Codementor',
   },
   {
-    name: 'Exponent',
-    icon: 'http://a4.mzstatic.com/us/r30/Purple2/v4/3a/d3/c9/3ad3c96c-5e14-f988-4bdd-0fdc95efd140/icon175x175.png',
-    link:  'https://itunes.apple.com/ca/app/exponent/id982107779?mt=8',
-    author: 'Exponent',
+    name: 'Collegiate - Learn Anywhere',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/17/a9/60/17a960d3-8cbd-913a-9f08-ebd9139c116c/icon175x175.png',
+    link: 'https://itunes.apple.com/app/id1072463482',
+    author: 'Gaurav Arora',
   },
   {
-    name: 'F8',
-    icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple5/v4/bf/d9/50/bfd9504e-a1bd-67c5-b50b-24e97016dae9/pr_source.jpg',
-    link: 'https://itunes.apple.com/us/app/f8/id853467066?mt=8',
-    author: 'Facebook',
+    name: 'Company name search',
+    icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/fd/47/53/fd47537c-5861-e208-d1d1-1e26b5e45a36/icon350x350.jpeg',
+    link: 'https://itunes.apple.com/us/app/company-name-search/id1043824076',
+    author: 'The Formations Factory Ltd',
+    blogs: [
+      'https://medium.com/formations-factory/creating-company-name-search-app-with-react-native-36a049b0848d',
+    ],
   },
   {
-    name: 'Facebook Groups',
-    icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple69/v4/57/f8/4c/57f84c0c-793d-5f9a-95ee-c212d0369e37/mzl.ugjwfhzx.png',
-    link: 'https://itunes.apple.com/us/app/facebook-groups/id931735837?mt=8',
-    author: 'Facebook',
+    name: 'DareU',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple6/v4/10/fb/6a/10fb6a50-57c8-061a-d865-503777bf7f00/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/dareu-dare-your-friends-dare/id1046434563?mt=8',
+    author: 'Rishabh Mehan',
   },
   {
-    name: 'Facebook Ads Manager',
-    icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple5/v4/9e/16/86/9e1686ef-cc55-805a-c977-538ddb5e6832/mzl.gqbhwitj.png',
-    linkAppStore: 'https://itunes.apple.com/us/app/facebook-ads-manager/id964397083?mt=8',
-    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.facebook.adsmanager',
-    author: 'Facebook',
+    name: 'Deskbookers',
+    icon: 'http://a4.mzstatic.com/eu/r30/Purple69/v4/be/61/7d/be617d63-88f5-5629-7ac0-bc2c9eb4802a/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/nl/app/deskbookers/id964447401?mt=8',
+    author: 'Emilio Rodriguez'
+  },
+  {
+    name: 'DockMan',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple49/v4/91/b5/75/91b57552-d9bc-d8bc-10a1-617de920aaa6/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/app/dockman/id1061469696',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.s21g.DockMan',
+    blogs: [
+      'http://www.s21g.com/DockMan.html',
+    ],
+    author: 'Genki Takiuchi (s21g Inc.)',
+  },
+  {
+    name: 'Due',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a2/41/5d/a2415d5f-407a-c565-ffb4-4f27f23d8ac2/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/due-countdown-reminders-for/id1050909468?mt=8',
+    author: 'Dotan Nahum',
+  },
+  {
+    name: 'Eat or Not',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple5/v4/51/be/34/51be3462-b015-ebf2-11c5-69165b37fadc/icon175x175.jpeg',
+    linkAppStore: 'https://itunes.apple.com/us/app/eat-or-not/id1054565697?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.eon',
+    author: 'Sharath Prabhal',
   },
   {
     name: 'Fan of it',
@@ -98,6 +285,19 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/fast-paper/id1001174614',
     author: 'Liubomyr Mykhalchenko (@liubko)',
   },
+  {
+    name: 'Feline for Product Hunt',
+    icon: 'https://lh3.googleusercontent.com/MCoiCCwUan0dxzqRR_Mrr7kO308roYdI2aTsIpUGYWzUmpJT1-R2_J04weQKFEd3Mg=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.arjunkomath.product_hunt&hl=en',
+    author: 'Arjun Komath',
+  },
+  {
+    name: 'Fixt',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple69/v4/46/bc/66/46bc66a2-7775-4d24-235d-e1fe28d55d7f/icon175x175.png',
+    linkAppStore:  'https://itunes.apple.com/us/app/dropbot-phone-replacement/id1000855694?mt=8',
+    linkPlayStore:  'https://play.google.com/store/apps/details?id=co.fixt',
+    author: 'Fixt',
+  },
   {
     name: 'Foodstand',
     icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/33/c1/3b/33c13b88-8ec2-23c1-56bb-712ad9938290/icon350x350.jpeg',
@@ -128,12 +328,37 @@ var apps = [
     link: 'https://play.google.com/store/apps/details?id=io.scrollback.neighborhoods',
     author: 'Scrollback',
   },
+  {
+    name: 'Honest Reviews',
+    icon: 'http://honestreviews.techulus.com/icon.png',
+    link: 'https://play.google.com/store/apps/details?id=com.techulus.honestreviews&hl=en',
+    author: 'Arjun Komath',
+  },
+  {
+    name: 'Hover',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple3/v4/ba/55/e6/ba55e6ee-71cf-b843-f592-0917c9b6c645/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/us/app/hover-1-drone-uav-pilot-app!/id947641516?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.analyticadevelopment.android.hover',
+    author: 'KevinEJohn',
+  },
   {
     name: 'HSK Level 1 Chinese Flashcards',
     icon: 'http://is2.mzstatic.com/image/pf/us/r30/Purple1/v4/b2/4f/3a/b24f3ae3-2597-cc70-1040-731b425a5904/mzl.amxdcktl.jpg',
     link: 'https://itunes.apple.com/us/app/hsk-level-1-chinese-flashcards/id936639994',
     author: 'HS Schaaf',
   },
+  {
+    name: 'Hubhopper',
+    icon: 'http://hubhopper.com/images/h_logo.jpg',
+    link: 'https://play.google.com/store/apps/details?id=com.hubhopper',
+    author: 'Soch Technologies',
+  },
+  {
+    name: 'Jukebox',
+    icon: 'https://s3.amazonaws.com/theartistunion-production/Jukebox-logo.png',
+    link: 'https://itunes.apple.com/us/app/jukebox-offline-music-player/id1072583255?ls=1&mt=8',
+    author: 'The Beat Drop Inc'
+  },
   {
     name: 'Kakapo',
     icon: 'http://a2.mzstatic.com/eu/r30/Purple3/v4/12/ab/2a/12ab2a01-3a3c-9482-b8df-ab38ad281165/icon175x175.png',
@@ -141,6 +366,12 @@ var apps = [
     linkPlayStore: 'https://play.google.com/store/apps/details?id=com.kakaponative',
     author: 'Daniel Levitt',
   },
+  {
+    name: 'Koza Gujarati Dictionary',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/77/95/83/77958377-05ae-4754-684a-3c9dbb67b517/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/in/app/koza-english-to-gujarati-dictionary/id982752928?mt=8',
+    author: 'KozaApps',
+  },
   {
     name: 'Leanpub',
     icon: 'http://a2.mzstatic.com/us/r30/Purple6/v4/9f/4a/6f/9f4a6f8c-8951-ed89-4083-74ace23df9ef/icon350x350.jpeg',
@@ -154,10 +385,10 @@ var apps = [
     author: 'LoadDocs',
   },
   {
-    name: 'Lrn',
-    icon: 'http://is4.mzstatic.com/image/pf/us/r30/Purple1/v4/41/a9/e9/41a9e9b6-7801-aef7-2400-2eca14923321/mzl.adyswxad.png',
-    link: 'https://itunes.apple.com/us/app/lrn-learn-to-code-at-your/id1019622677',
-    author: 'Lrn Labs, Inc',
+    name: 'Lugg – Your On-Demand Mover',
+    icon: 'http://lh3.googleusercontent.com/EV9z7kRRME2KPMBRNHnje7bBNEl_Why2CFq-MfKzBC88uSFJTYr1HO3-nPt-JuVJwKFb=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.lugg',
+    author: 'Lugg',
   },
   {
     name: 'Lumpen Radio',
@@ -174,13 +405,8 @@ var apps = [
   {
     name: 'MaxReward - Android',
     icon: 'https://lh3.googleusercontent.com/yynCUCdEnyW6T96xCto8KzWQr4Yeiy0M6c2p8auYMIyFgAZVBsjf4JCEX7QkPijhBg=w175-rw',
-    link: 'https://play.google.com/store/apps/details?id=com.bitstrek.maxreward&hl=en',
-    author: 'Neil Ma',
-  },
-  {
-    name: 'MaxReward - iOS',
-    icon: 'https://lh3.googleusercontent.com/yynCUCdEnyW6T96xCto8KzWQr4Yeiy0M6c2p8auYMIyFgAZVBsjf4JCEX7QkPijhBg=w175-rw',
-    link: 'https://itunes.apple.com/us/app/maxreward/id1050479192?ls=1&mt=8',
+    linkAppStore: 'https://itunes.apple.com/us/app/maxreward/id1050479192?ls=1&mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.bitstrek.maxreward&hl=en',
     author: 'Neil Ma',
   },
   {
@@ -189,6 +415,24 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/mintrain/id1015739031?mt=8',
     author: 'Peter Cottle',
   },
+  {
+    name: 'Mobabuild',
+    icon: 'http://mobabuild.co/images/applogo.png',
+    link: 'http://mobabuild.co',
+    author: 'Sercan Demircan ( @sercanov )',
+  },
+  {
+    name: 'MockingBot',
+    icon: 'https://s3.cn-north-1.amazonaws.com.cn/modao/downloads/images/MockingBot175.png',
+    link: 'https://itunes.apple.com/cn/app/mockingbot/id1050565468?l=en&mt=8',
+    author: 'YuanYi Zhang (@mockingbot)',
+  },
+  {
+    name: 'MoneyLion',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple69/v4/d7/9d/ad/d79daddc-8d67-8a6c-61e2-950425946dd2/icon350x350.jpeg',
+    link: 'https://itunes.apple.com/us/app/moneylion/id1064677082?mt=8',
+    author: 'MoneyLion LLC',
+  },
   {
     name: 'Mr. Dapper',
     icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple4/v4/e8/3f/7c/e83f7cb3-2602-f8e8-de9a-ce0a775a4a14/mzl.hmdjhfai.png',
@@ -196,22 +440,22 @@ var apps = [
     author: 'wei ping woon',
   },
   {
-    name: 'Myntra',
-    icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/9c/78/df/9c78dfa6-0061-1af2-5026-3e1d5a073c94/icon350x350.png',
-    link: 'https://itunes.apple.com/in/app/myntra-fashion-shopping-app/id907394059',
-    author: 'Myntra Designs',
+    name: 'My IP',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/a2/61/58/a261584d-a4cd-cbfa-cf9d-b5f1f15a7139/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/app/id1031729525?mt=8&at=11l7ss&ct=reactnativeshowcase',
+    author: 'Josh Buchea',
   },
   {
-    name: 'Ncredible',
-    icon: 'http://a3.mzstatic.com/us/r30/Purple2/v4/a9/00/74/a9007400-7ccf-df10-553b-3b6cb67f3f5f/icon350x350.png',
-    link: 'https://itunes.apple.com/ca/app/ncredible/id1019662810?mt=8',
-    author: 'NBC News Digital, LLC',
+    name: 'MyPED',
+    icon: 'http://a2.mzstatic.com/eu/r30/Purple69/v4/88/1f/fb/881ffb3b-7986-d427-7fcf-eb5920a883af/icon175x175.png',
+    link: 'https://itunes.apple.com/it/app/myped/id1064907558?ls=1&mt=8',
+    author: 'Impronta Advance',
   },
   {
-    name: 'Noodler',
-    icon: 'http://a5.mzstatic.com/us/r30/Purple6/v4/d9/9a/69/d99a6919-7f11-35ad-76ea-f1741643d875/icon175x175.png',
-    link: 'https://itunes.apple.com/us/app/noodler-noodle-soup-oracle/id1013183002?mt=8',
-    author: 'Michele Humes & Joshua Sierles',
+    name: 'Nalathe Kerala',
+    icon: 'https://lh3.googleusercontent.com/5N0WYat5WuFbhi5yR2ccdbqmiZ0wbTtKRG9GhT3YK7Z-qRvmykZyAgk0HNElOxD2JOPr=w300-rw',
+    link: 'https://play.google.com/store/apps/details?id=com.rhyble.nalathekerala',
+    author: 'Rhyble',
   },
   {
     name: 'Night Light',
@@ -225,6 +469,12 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/okanagan-news-reader-for-viewing/id1049147148?mt=8',
     author: 'Levi Cabral',
   },
+  {
+    name: 'Pimmr',
+    icon: 'http://a2.mzstatic.com/eu/r30/Purple69/v4/99/da/0e/99da0ee6-bc87-e1a6-1d95-7027c78f50e1/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/nl/app/pimmr/id1023343303?mt=8',
+    author: 'Pimmr'
+  },
   {
     name: 'Posyt - Tinder for ideas',
     icon: 'http://a3.mzstatic.com/us/r30/Purple6/v4/a5/b3/86/a5b38618-a5e9-6089-7425-7fa51ecd5d30/icon175x175.jpeg',
@@ -243,6 +493,15 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/reactto36/id989009293?mt=8',
     author: 'Jonathan Solichin',
   },
+  {
+    name: 'Reading',
+    icon: 'http://7xr0xq.com1.z0.glb.clouddn.com/about_logo.png',
+    link: 'http://www.wandoujia.com/apps/com.reading',
+    author: 'RichardCao',
+    blogs: [
+      'http://richard-cao.github.io/2016/02/06/Reading-App-Write-In-React-Native/',
+    ],
+  },
   {
     name: 'RenovationFind',
     icon: 'http://a2.mzstatic.com/us/r30/Purple3/v4/4f/89/af/4f89af72-9733-2f59-6876-161983a0ee82/icon175x175.png',
@@ -255,13 +514,6 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/repairshopr-payments-lite/id1023262888?mt=8',
     author: 'Jed Tiotuico',
   },
-  {
-    name: 'RN Playground',
-    icon: 'http://is5.mzstatic.com/image/pf/us/r30/Purple1/v4/20/ec/8e/20ec8eb8-9e12-6686-cd16-7ac9e3ef1d52/mzl.ngvuoybx.png',
-    linkAppStore: 'https://itunes.apple.com/us/app/react-native-playground/id1002032944?mt=8',
-    linkPlayStore: 'https://play.google.com/store/apps/details?id=org.rnplay.playground',
-    author: 'Joshua Sierles',
-  },
   {
     name: 'Rota Employer - Hire On Demand',
     link: 'https://itunes.apple.com/us/app/rota-employer-hire-on-demand/id1042270305?mt=8',
@@ -274,6 +526,12 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/rota-worker-shifts-on-demand/id1042111289?mt=8',
     author: 'Rota',
   },
+  {
+    name: 'RWpodPlayer - audio player for RWpod podcast',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a8/c0/b1/a8c0b130-e44b-742d-6458-0c89fcc15b6b/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/rwpodplayer/id1053885042?mt=8',
+    author: 'Alexey Vasiliev aka leopard',
+  },
   {
     name: 'SG Toto 4d',
     icon: 'http://a4.mzstatic.com/us/r30/Purple7/v4/d2/bc/46/d2bc4696-84d6-9681-a49f-7f660d6b04a7/icon175x175.jpeg',
@@ -281,22 +539,23 @@ var apps = [
     author: 'Steve Ng'
   },
   {
-    name: 'Spero for Cancer',
-    icon: 'https://s3-us-west-1.amazonaws.com/cancerspot/site_images/Spero1024.png',
-    link: 'https://geo.itunes.apple.com/us/app/spero-for-cancer/id1033923573?mt=8',
-    author: 'Spero.io',
+    name: 'ShareHows',
+    icon: 'http://a4.mzstatic.com/us/r30/Purple5/v4/78/1c/83/781c8325-a1e1-4afc-f106-a629bcf3c6ef/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/kr/app/sweeohauseu-sesang-ui-modeun/id1060914858?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=kr.dobbit.sharehows',
+    author: 'Dobbit Co., Ltd.'
   },
   {
-    name: 'Squad',
-    icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/e8/5b/3f/e85b3f52-72f3-f427-a32e-a73efe2e9682/icon175x175.jpeg',
-    link: 'https://itunes.apple.com/us/app/squad-snaps-for-groups-friends/id1043626975?mt=8',
-    author: 'Tackk Inc.',
+    name: 'sneat: réservez les meilleurs restaurants de Paris',
+    icon: 'http://a3.mzstatic.com/eu/r30/Purple49/v4/71/71/df/7171df47-6e03-8619-19a8-07f52186b0ed/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/fr/app/sneat-reservez-les-meilleurs/id1062510079?l=en&mt=8',
+    author: 'sneat'
   },
   {
-    name: 'Start - medication manager for depression',
-    icon: 'http://a1.mzstatic.com/us/r30/Purple49/v4/de/9b/6f/de9b6fe8-84ea-7a12-ba2c-0a6d6c7b10b0/icon175x175.png',
-    link: 'https://itunes.apple.com/us/app/start-medication-manager-for/id1012099928?mt=8',
-    author: 'Iodine Inc.',
+    name: 'Spero for Cancer',
+    icon: 'https://s3-us-west-1.amazonaws.com/cancerspot/site_images/Spero1024.png',
+    link: 'https://geo.itunes.apple.com/us/app/spero-for-cancer/id1033923573?mt=8',
+    author: 'Spero.io',
   },
   {
     name: 'Tabtor Parent',
@@ -304,6 +563,25 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/tabtor-math/id1018651199?utm_source=ParentAppLP',
     author: 'PrazAs Learning Inc.',
   },
+  {
+    name: 'TeamWarden',
+    icon: 'http://a1.mzstatic.com/eu/r30/Purple69/v4/09/37/61/0937613a-46e3-3278-5457-5de49a4ee9ab/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/gb/app/teamwarden/id1052570507?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.teamwarden',
+    author: 'nittygritty.net',
+  },
+  {
+    name: 'Text Blast',
+    icon: 'http://a3.mzstatic.com/us/r30/Purple49/v4/4f/29/58/4f2958a1-7f35-9260-6340-c67ac29d7740/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/text-blast-2016/id1023852862?mt=8',
+    author: 'Sesh',
+  },
+  {
+    name: 'Thai Tone',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple2/v4/b1/e6/2b/b1e62b3d-6747-0d0b-2a21-b6ba316a7890/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/thai-tone/id1064086189?mt=8',
+    author: 'Alexey Ledak',
+  },
   {
     name: 'Tong Xing Wang',
     icon: 'http://a3.mzstatic.com/us/r30/Purple1/v4/7d/52/a7/7d52a71f-9532-82a5-b92f-87076624fdb2/icon175x175.jpeg',
@@ -311,10 +589,17 @@ var apps = [
     author: 'Ho Yin Tsun Eugene',
   },
   {
-    name: 'Townske - Stunning City Guides Made By Locals & Traveller',
-    icon: 'http://a3.mzstatic.com/us/r30/Purple69/v4/8b/42/20/8b4220af-5165-91fd-0f05-014332df73ef/icon175x175.png',
-    link: 'https://itunes.apple.com/us/app/townske-stunning-city-guides/id1018136179?ls=1&mt=8',
-    author: 'Townske PTY LTD',
+    name: 'WEARVR',
+    icon: 'http://a2.mzstatic.com/eu/r30/Purple69/v4/4f/5a/28/4f5a2876-9530-ef83-e399-c5ef5b2dab80/icon175x175.png',
+    linkAppStore: 'https://itunes.apple.com/gb/app/wearvr/id1066288171?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.wearvr.app',
+    author: 'WEARVR LLC',
+  },
+  {
+    name: 'Whammy',
+    icon: 'http://a4.mzstatic.com/us/r30/Purple49/v4/8f/1c/21/8f1c2158-c7fb-1bbb-94db-e77b867aad1a/icon175x175.jpeg',
+    link: 'https://itunes.apple.com/us/app/whammy/id899759777',
+    author: 'Play Company',
   },
   {
     name: 'WOOP',
@@ -322,12 +607,6 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/woop-app/id790247988?mt=8',
     author: 'Moritz Schwörer (@mosch)',
   },
-  {
-    name: 'WPV',
-    icon: 'http://a1.mzstatic.com/us/r30/Purple3/v4/f1/ae/51/f1ae516b-d8e9-1b6d-acfe-755623a88327/icon350x350.png',
-    link: 'https://itunes.apple.com/us/app/wpv/id725222647?mt=8',
-    author: 'Yamill Vallecillo (@yamill3)',
-  },
   {
     name: 'Yoloci',
     icon: 'http://a5.mzstatic.com/eu/r30/Purple7/v4/fa/e5/26/fae52635-b97c-bd53-2ade-89e2a4326745/icon175x175.jpeg',
@@ -352,41 +631,156 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/yazboz-batak-esli-batak-okey/id1048620855?ls=1&mt=8',
     author: 'Melih Mucuk',
   },
+  {
+    name: '天才段子手',
+    icon: 'http://pp.myapp.com/ma_icon/0/icon_12236104_1451810987/96',
+    linkAppStore: 'https://itunes.apple.com/us/app/tian-cai-duan-zi-shou-shen/id992312701?l=zh&ls=1&mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.geniusJokeWriter.app',
+    author: 'Ran Zhao&Ji Zhao'
+  },
+  {
+    name: '你造么',
+    icon: 'http://7xk1ez.com2.z0.glb.qiniucdn.com/logo-mobile-0114logo_welcom.png',
+    link: 'https://itunes.apple.com/us/app/ni-zao-me/id1025294933?l=zh&ls=1&mt=8',
+    author: 'Scott Chen(@NZAOM)'
+  },
+  {
+    name: 'うたよみん',
+    icon: 'http://a4.mzstatic.com/jp/r30/Purple69/v4/19/8f/fa/198ffafe-66a6-d682-c8d1-47faf2b0badb/icon175x175.jpeg',
+    linkAppStore: 'https://itunes.apple.com/jp/app/minnano-duan-ge-tou-gaokomyuniti/id675671254?mt=8',
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.plasticaromantica.utayomin',
+    author: 'Takayuki IMAI'
+  },
+  {
+    name: '烘焙帮',
+    icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/79/85/ba/7985ba1d-a807-7c34-98f1-e9e2ed5d2cb5/icon175x175.jpeg',
+    linkAppStore: 'https://itunes.apple.com/cn/app/hong-bei-bang-hai-liang-hong/id1007812319?mt=8',
+    author: 'Hongbeibang'
+  },
 ];
 
-var showcase = React.createClass({
-  renderLinks: function(app) {
+var AppList = React.createClass({
+
+  render: function() {
     return (
-      <p><a href={app.linkAppStore} target="blank">iOS</a> - <a href={app.linkPlayStore} target="blank">Android</a></p>
+      <div>
+        {this.props.apps.map(this._renderApp)}
+      </div>
+    )
+  },
+
+  _renderApp: function(app, i) {
+    var inner = (
+      <div>
+        <img src={app.icon} alt={app.name} />
+        <h3>{app.name}</h3>
+        {app.linkAppStore && app.linkPlayStore ? this._renderLinks(app) : null}
+        <p>By {app.author}</p>
+        {this._renderBlogPosts(app)}
+        {this._renderVideos(app)}
+      </div>
+    );
+
+    if (app.linkAppStore && app.linkPlayStore) {
+      return (<div className="showcase" key={i}>{inner}</div>);
+    }
+
+    return (
+      <div className="showcase" key={i}>
+        <a href={app.link} target="blank">
+          {inner}
+        </a>
+      </div>
+    );
+  },
+
+  _renderBlogPosts: function(app) {
+    if (!app.blogs) {
+      return;
+    }
+
+    if (app.blogs.length === 1) {
+      return (
+        <p><a href={app.blogs[0]} target="blank">Blog post</a></p>
+      );
+    } else if (app.blogs.length > 1) {
+      return (
+        <p>Blog posts: {app.blogs.map(this._renderBlogPost)}</p>
+      );
+    }
+  },
+
+  _renderBlogPost: function(url, i) {
+    return (
+      <a href={url} target="blank">
+        {i + 1}&nbsp;
+      </a>
+    );
+  },
+
+  _renderVideos: function(app) {
+    if (!app.videos) {
+      return;
+    }
+
+    if (app.videos.length === 1) {
+      return (
+        <p><a href={app.videos[0]} target="blank">Video</a></p>
+      );
+    } else if (app.videos.length > 1) {
+      return (
+        <p>Videos: {app.videos.map(this._renderVideo)}</p>
+      );
+    }
+  },
+
+  _renderVideo: function(url, i) {
+    return (
+      <a href={url} target="blank">
+        {i + 1}&nbsp;
+      </a>
     );
   },
+
+  _renderLinks: function(app) {
+    return (
+      <p>
+        <a href={app.linkAppStore} target="blank">iOS</a>
+        {" - "}
+        <a href={app.linkPlayStore} target="blank">Android</a>
+      </p>
+    );
+  },
+});
+
+var showcase = React.createClass({
   render: function() {
     return (
       <Site section="showcase" title="Showcase">
-        <section className="content wrap documentationContent nosidebar">
+        <section className="content wrap documentationContent nosidebar showcaseSection">
+          <div className="inner-content showcaseHeader">
+            <h1 style={{textAlign: 'center'}}>Apps using React Native</h1>
+            <div className="subHeader"></div>
+            <p>The following is a list of some of the public apps using <strong>React Native</strong> and are published on the Apple App Store or the Google Play Store. Not all are implemented 100% in React Native -- many are hybrid native/React Native. Can you tell which parts are which? :)</p>
+            <p>Want to add your app? Found an app that no longer works or no longer uses React Native? Please submit a pull request on <a href="https://github.com/facebook/react-native">GitHub</a> to update this page!</p>
+          </div>
+
           <div className="inner-content showcaseHeader">
-            <h1>Apps using React Native</h1>
+            <h1 style={{textAlign: 'center'}}>Featured Apps</h1>
             <div className="subHeader"></div>
-            <p>
-              Here is a list of apps using <strong>React Native</strong>. Submit a pull request on <a href="https://github.com/facebook/react-native">GitHub</a> to list your app.
-            </p>
+            <p>These are some of the most well-crafted React Native apps that we have come across.<br/>Be sure to check them out to get a feel for what React Native is capable of!</p>
+          </div>
+          <div className="inner-content">
+            <AppList apps={featured} />
+          </div>
+
+          <div className="inner-content showcaseHeader">
+            <h1 style={{textAlign: 'center'}}>All Apps</h1>
+            <p>Not all apps can be featured, otherwise we would have to create some other category like &quot;super featured&quot; and that's just silly. But that doesn't mean you shouldn't check these apps out!</p>
+          </div>
+          <div className="inner-content">
+            <AppList apps={apps} />
           </div>
-          {
-            apps.map((app, i) => {
-              var inner = (
-                <div>
-                  <img src={app.icon} alt={app.name} />
-                  <h3>{app.name}</h3>
-                  {app.linkAppStore && app.linkPlayStore ? this.renderLinks(app) : null}
-                  <p>By {app.author}</p>
-                </div>
-              );
-              if (app.linkAppStore && app.linkPlayStore) {
-                return (<div className="showcase" key={i}>{inner}</div>);
-              }
-              return (<a href={app.link} className="showcase" key={i} target="blank">{inner}</a>);
-            })
-          }
         </section>
       </Site>
     );
diff --git a/website/src/react-native/support.js b/website/src/react-native/support.js
index 721faed999a7..de1b4bbe7f41 100644
--- a/website/src/react-native/support.js
+++ b/website/src/react-native/support.js
@@ -13,6 +13,13 @@ var center = require('center');
 var H2 = require('H2');
 
 var support = React.createClass({
+  childContextTypes: {
+    permalink: React.PropTypes.string
+  },
+
+  getChildContext: function() {
+    return {permalink: 'support.html'};
+  },
   render: function() {
     return (
       <Site section="support" title="Support">
@@ -28,6 +35,12 @@ var support = React.createClass({
               questions.
             </p>
 
+            <H2>Community translation</H2>
+            <p>The following is a list of translated docs offered by community volunteers. Send a pull request to fill the list!</p>
+            <ul>
+              <li><a href="http://reactnative.cn">Chinese</a> (<a href="https://github.com/reactnativecn/react-native-docs-cn">source</a>)</li>
+            </ul>
+            
             <H2>Stack Overflow</H2>
             <p>Many members of the community use Stack Overflow to ask questions. Read through the <a href="http://stackoverflow.com/questions/tagged/react-native">existing questions</a> tagged with <strong>react-native</strong> or <a href="http://stackoverflow.com/questions/ask">ask your own</a>!</p>
 
diff --git a/website/src/react-native/versions.js b/website/src/react-native/versions.js
new file mode 100644
index 000000000000..fecf68d5e175
--- /dev/null
+++ b/website/src/react-native/versions.js
@@ -0,0 +1,57 @@
+/**
+ * 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.
+ */
+
+var React = require('React');
+var Site = require('Site');
+var Metadata = require('Metadata');
+
+var versions = React.createClass({
+  render: function() {
+
+    var availableDocs = (Metadata.config.RN_AVAILABLE_DOCS_VERSIONS || '').split(',');
+
+    var versions = [
+      {
+        title: 'master',
+        path: '/react-native/releases/next',
+      },
+    ].concat(availableDocs.map((version) => {
+      const isLatest =  Metadata.config.RN_LATEST_VERSION === version;
+      return {
+        title: isLatest ? `${version} (current)` : version,
+        path: isLatest ? '/react-native' : '/react-native/releases/' + version
+      }
+    }));
+
+    if (!Metadata.config.RN_LATEST_VERSION) {
+      versions = [
+        {
+          title: 'current',
+          path: '/react-native',
+        },
+      ].concat(versions);
+    }
+
+    var versionsLi = versions.map((version) =>
+      <li><a href={version.path}>{version.title}</a></li>
+    );
+    return (
+      <Site section="versions" title="Documentation archive">
+        <section className="content wrap versions documentationContent">
+          <h1>Documentation archive</h1>
+          <ul>
+            {versionsLi}
+          </ul>
+        </section>
+      </Site>
+    );
+  }
+});
+
+module.exports = versions;
