diff --git a/.buckconfig b/.buckconfig
index 934256cb29d4a3..9f6c686b2fc18a 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -4,3 +4,6 @@
 
 [maven_repositories]
   central = https://repo1.maven.org/maven2
+
+[alias]
+  movies = //Examples/Movies/android/app:app
diff --git a/.eslintrc b/.eslintrc
index 8c33d4d3842db6..d707517a0d6271 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -113,7 +113,6 @@
     "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
@@ -182,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)
@@ -205,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)
@@ -227,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 78c0582045e172..f3270cc7fd9c6e 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -15,11 +15,8 @@
 # Ignore react and fbjs where there are overlaps, but don't ignore
 # anything that react-native relies on
 .*/node_modules/fbjs/lib/Map.js
-.*/node_modules/fbjs/lib/Promise.js
 .*/node_modules/fbjs/lib/fetch.js
 .*/node_modules/fbjs/lib/ExecutionEnvironment.js
-.*/node_modules/fbjs/lib/isEmpty.js
-.*/node_modules/fbjs/lib/crc32.js
 .*/node_modules/fbjs/lib/ErrorUtils.js
 
 # Flow has a built-in definition for the 'react' module which we prefer to use
@@ -28,6 +25,11 @@
 .*/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/.*
 
@@ -40,14 +42,35 @@
 # Ignore Website
 .*/website/.*
 
+.*/node_modules/is-my-json-valid/test/.*\.json
+.*/node_modules/iconv-lite/encodings/tables/.*\.json
+.*/node_modules/y18n/test/.*\.json
+.*/node_modules/spdx-license-ids/spdx-license-ids.json
+.*/node_modules/spdx-exceptions/index.json
+.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json
+.*/node_modules/resolve/lib/core.json
+.*/node_modules/jsonparse/samplejson/.*\.json
+.*/node_modules/json5/test/.*\.json
+.*/node_modules/ua-parser-js/test/.*\.json
+.*/node_modules/builtin-modules/builtin-modules.json
+.*/node_modules/binary-extensions/binary-extensions.json
+.*/node_modules/url-regex/tlds.json
+.*/node_modules/joi/.*\.json
+.*/build/.*\.json
+.*/\.buckd/.*
+
 [include]
 
 [libs]
 Libraries/react-native/react-native-interface.js
+flow/
 
 [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'
@@ -57,9 +80,9 @@ suppress_type=$FlowIssue
 suppress_type=$FlowFixMe
 suppress_type=$FixMe
 
-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\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
+suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-2]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
 suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
 
 [version]
-0.21.0
+0.22.0
diff --git a/.gitignore b/.gitignore
index fe61f843f91de6..cd545b912fdc72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,8 @@ project.xcworkspace
 # 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 67ac1cc42ecd4e..f76d6bad0337de 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,11 +2,6 @@ language: objective-c
 
 osx_image: xcode7.2
 
-cache:
-    directories:
-        - node_modules
-        - .nvm
-
 install:
   - brew reinstall nvm
   - mkdir -p .nvm
@@ -15,7 +10,7 @@ install:
   - 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,43 +18,30 @@ script:
   if [ "$TEST_TYPE" = objc ]
   then
 
-    ./scripts/objc-test.sh
+    travis_retry ./scripts/objc-test.sh
 
   elif [ "$TEST_TYPE" = js ]
   then
 
     npm install github@0.2.4
-    cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; flow --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" node bots/code-analysis-bot.js
-    flow check && npm test -- '\/Libraries\/'
-
-  elif [ "$TEST_TYPE" = packager ]
-  then
-
-    npm test -- '\/packager\/'
-
-  elif [ "$TEST_TYPE" = cli ]
+    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
+    # testing js e2e with npm3
+    npm install -g npm@3
+    npm --version
+    ./scripts/e2e-test.sh --packager
+    # testing js e2e with npm2
+    rm -rf node_modules
+    npm install -g npm@2
+    npm install
+    npm --version
+    ./scripts/e2e-test.sh --packager
+
+  elif [ "$TEST_TYPE" = e2e-objc ]
   then
 
-    npm test -- '\/(local|private|react-native)-cli\/'
+    travis_retry ./scripts/e2e-test.sh --ios
 
-  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
@@ -69,19 +51,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 3171208c89d3cb..ee69e85cea7199 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,7 +17,7 @@ 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. Add the copyright notice to the top of any new files you've added.
 5. Ensure tests pass on Travis and Circle CI.
@@ -73,6 +73,7 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
 
 #### General
 
+* **Most important: Look around.** Match the style you see used in the rest of the project. This includes formatting, naming things in code, naming things in documentation.
 * Add trailing commas,
 * 2 spaces for indentation (no tabs)
 * "Attractive"
diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js
index 239d1dadeca4f9..a6c97a3e3edaa5 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);
diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js
index ad1f9b72bfdd64..2e681ed34f50b5 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');
diff --git a/Examples/Movies/android/app/BUCK b/Examples/Movies/android/app/BUCK
new file mode 100644
index 00000000000000..25c73fd5441544
--- /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 185ffa5060219d..8aaec60819da5b 100644
--- a/Examples/Movies/android/app/src/main/AndroidManifest.xml
+++ b/Examples/Movies/android/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
 
     <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 3b2626601a34bd..a5dc16093b8dc3 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 6d5c460cbea30a..3b562194a04427 100755
--- a/Examples/TicTacToe/TicTacToeApp.js
+++ b/Examples/TicTacToe/TicTacToeApp.js
@@ -305,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 ccc86fba4a50d3..b749400c037ab2 100644
--- a/Examples/UIExplorer/ActionSheetIOSExample.js
+++ b/Examples/UIExplorer/ActionSheetIOSExample.js
@@ -20,6 +20,7 @@ var {
   ActionSheetIOS,
   StyleSheet,
   Text,
+  UIManager,
   View,
 } = React;
 
@@ -98,7 +99,6 @@ var ActionSheetTintExample = React.createClass({
   }
 });
 
-
 var ShareActionSheetExample = React.createClass({
   getInitialState() {
     return {
@@ -121,16 +121,14 @@ var ShareActionSheetExample = React.createClass({
 
   showShareActionSheet() {
     ActionSheetIOS.showShareActionSheetWithOptions({
-      url: 'https://code.facebook.com',
+      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) => {
-      console.error(error);
-    },
+    (error) => alert(error),
     (success, method) => {
       var text;
       if (success) {
@@ -143,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,
@@ -163,6 +205,20 @@ exports.examples = [
   },
   {
     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/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js
index a52c1ab79521c3..d7f0bcf0d464fa 100644
--- a/Examples/UIExplorer/AlertIOSExample.js
+++ b/Examples/UIExplorer/AlertIOSExample.js
@@ -37,7 +37,7 @@ exports.examples = [{
 },
 {
   title: 'Prompt Options',
-  render(): React.Component {
+  render(): ReactElement {
     return <PromptOptions />;
   }
 },
@@ -85,9 +85,13 @@ exports.examples = [{
 }];
 
 class PromptOptions extends React.Component {
+  state: any;
+  customButtons: Array<Object>;
+
   constructor(props) {
     super(props);
 
+    // $FlowFixMe this seems to be a Flow bug, `saveResponse` is defined below
     this.saveResponse = this.saveResponse.bind(this);
 
     this.customButtons = [{
diff --git a/Examples/UIExplorer/AnimatedExample.js b/Examples/UIExplorer/AnimatedExample.js
index 5f60963381153e..1068ac00026809 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 = {
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExApp.js
index d713b5f66d903e..c87bc29823bce4 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 d3e603738c34d0..39f15a8ec8ea9b 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 = {};
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExChained.js
index f2c932a2449c1f..596617f1780ec1 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 = {
diff --git a/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js b/Examples/UIExplorer/AnimatedGratuitousApp/AnExScroll.js
index f96acb380a8dc0..e7712cfc52e86e 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 f25301f7756f40..769a0f79a2d25f 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 3cea77917088eb..42b5523c011c75 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/BorderExample.js b/Examples/UIExplorer/BorderExample.js
index 1c547ea6ea21c9..2dedfdd8e6ae1d 100644
--- a/Examples/UIExplorer/BorderExample.js
+++ b/Examples/UIExplorer/BorderExample.js
@@ -110,8 +110,22 @@ var styles = StyleSheet.create({
     borderTopLeftRadius: 10,
     borderBottomRightRadius: 20,
     borderColor: 'black',
-    elevation: 10
-  }
+    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';
@@ -209,4 +223,11 @@ exports.examples = [
       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/ExampleTypes.js b/Examples/UIExplorer/ExampleTypes.js
index ac9deaadfaf768..691f4c6cf22f72 100644
--- a/Examples/UIExplorer/ExampleTypes.js
+++ b/Examples/UIExplorer/ExampleTypes.js
@@ -18,7 +18,7 @@
 
 export type Example = {
   title: string,
-  render: () => ?ReactElement<any, any, any>,
+  render: () => ?ReactElement<any>,
   description?: string,
   platform?: string;
 };
diff --git a/Examples/UIExplorer/ImageEditingExample.js b/Examples/UIExplorer/ImageEditingExample.js
index 1a8c931221d526..868bbd25f333da 100644
--- a/Examples/UIExplorer/ImageEditingExample.js
+++ b/Examples/UIExplorer/ImageEditingExample.js
@@ -47,10 +47,11 @@ type ImageCropData = {
   offset: ImageOffset;
   size: ImageSize;
   displaySize?: ?ImageSize;
-  resizeMode?: ?any; 
+  resizeMode?: ?any;
 };
 
 class SquareImageCropper extends React.Component {
+  state: any;
   _isMounted: boolean;
   _transformData: ImageCropData;
 
diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js
index 1a9af6c877cd6a..350844ec88b2c4 100644
--- a/Examples/UIExplorer/ImageExample.js
+++ b/Examples/UIExplorer/ImageExample.js
@@ -381,37 +381,41 @@ exports.examples = [
       'rendered within the frame.',
     render: function() {
       return (
-        <View style={styles.horizontal}>
-          <View>
-            <Text style={[styles.resizeModeText]}>
-              Contain
-            </Text>
-            <Image
-              style={styles.resizeMode}
-              resizeMode={Image.resizeMode.contain}
-              source={fullImage}
-            />
-          </View>
-          <View style={styles.leftMargin}>
-            <Text style={[styles.resizeModeText]}>
-              Cover
-            </Text>
-            <Image
-              style={styles.resizeMode}
-              resizeMode={Image.resizeMode.cover}
-              source={fullImage}
-            />
-          </View>
-          <View style={styles.leftMargin}>
-            <Text style={[styles.resizeModeText]}>
-              Stretch
-            </Text>
-            <Image
-              style={styles.resizeMode}
-              resizeMode={Image.resizeMode.stretch}
-              source={fullImage}
-            />
-          </View>
+        <View>
+          {[smallImage, fullImage].map((image, index) => {
+            return <View style={styles.horizontal} key={index}>
+              <View>
+                <Text style={[styles.resizeModeText]}>
+                  Contain
+                </Text>
+                <Image
+                  style={styles.resizeMode}
+                  resizeMode={Image.resizeMode.contain}
+                  source={image}
+                />
+              </View>
+              <View style={styles.leftMargin}>
+                <Text style={[styles.resizeModeText]}>
+                  Cover
+                </Text>
+                <Image
+                  style={styles.resizeMode}
+                  resizeMode={Image.resizeMode.cover}
+                  source={image}
+                />
+              </View>
+              <View style={styles.leftMargin}>
+                <Text style={[styles.resizeModeText]}>
+                  Stretch
+                </Text>
+                <Image
+                  style={styles.resizeMode}
+                  resizeMode={Image.resizeMode.stretch}
+                  source={image}
+                />
+            </View>
+          </View>;
+        })}
         </View>
       );
     },
@@ -455,7 +459,7 @@ exports.examples = [
   {
     title: 'Image Size',
     render: function() {
-      return <ImageSizeExample source={fullImage} />; 
+      return <ImageSizeExample source={fullImage} />;
     },
     platform: 'ios',
   },
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 e655bd9650c802..fbf36bfc071dc8 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/ListViewGridLayoutExample.js b/Examples/UIExplorer/ListViewGridLayoutExample.js
index ac7d56b8611272..4d8017335d5642 100644
--- a/Examples/UIExplorer/ListViewGridLayoutExample.js
+++ b/Examples/UIExplorer/ListViewGridLayoutExample.js
@@ -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}
       />
     );
diff --git a/Examples/UIExplorer/ListViewPagingExample.js b/Examples/UIExplorer/ListViewPagingExample.js
index f6cdcae62aa476..a1406c52ff95ab 100644
--- a/Examples/UIExplorer/ListViewPagingExample.js
+++ b/Examples/UIExplorer/ListViewPagingExample.js
@@ -32,7 +32,6 @@ var {
   UIManager,
 } = NativeModules;
 
-var PAGE_SIZE = 4;
 var THUMB_URLS = [
   require('./Thumbnails/like.png'),
   require('./Thumbnails/dislike.png'),
@@ -182,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/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/BreadcrumbNavSample.js
new file mode 100644
index 00000000000000..15251a4cf2f7e7
--- /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 00000000000000..aa2b9fd7df6cc6
--- /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 00000000000000..ed4169751418e9
--- /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 00000000000000..f479b2a83e7ee5
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/LegacyNavigator/NavigationBarSample.js
@@ -0,0 +1,201 @@
+/**
+ * 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 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: '#373E4D',
+    fontWeight: '500',
+    marginVertical: 9,
+  },
+  navBarLeftButton: {
+    paddingLeft: 10,
+  },
+  navBarRightButton: {
+    paddingRight: 10,
+  },
+  navBarButtonText: {
+    color: '#5890FF',
+  },
+  scene: {
+    flex: 1,
+    paddingTop: 20,
+    backgroundColor: '#EAEAEA',
+  },
+});
+
+module.exports = NavigationBarSample;
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js
index e9f85ed1cb680c..c27f3170d613b5 100644
--- a/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationAnimatedExample.js
@@ -15,6 +15,7 @@
 
 var React = require('react-native');
 var {
+  Animated,
   NavigationExperimental,
   StyleSheet,
   ScrollView,
@@ -29,24 +30,36 @@ var {
 } = NavigationExperimental;
 
 const NavigationBasicReducer = NavigationReducer.StackReducer({
-  initialStates: [
-    {key: 'First Route'}
-  ],
-  matchAction: action => true,
-  actionStateMap: actionString => ({key: actionString}),
+  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);
+    this._renderNavigation = this._renderNavigation.bind(this);
+    this._renderCard = this._renderCard.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+    this._renderHeader = this._renderHeader.bind(this);
   }
   render() {
     return (
       <NavigationRootContainer
         reducer={NavigationBasicReducer}
         ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
-        persistenceKey="NavigationAnimatedExampleState"
-        renderNavigation={this._renderNavigated}
+        persistenceKey="NavigationAnimExampleState"
+        renderNavigation={this._renderNavigation}
       />
     );
   }
@@ -56,7 +69,7 @@ class NavigationAnimatedExample extends React.Component {
       this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
     );
   }
-  _renderNavigated(navigationState, onNavigate) {
+  _renderNavigation(navigationState, onNavigate) {
     if (!navigationState) {
       return null;
     }
@@ -64,40 +77,56 @@ class NavigationAnimatedExample extends React.Component {
       <NavigationAnimatedView
         navigationState={navigationState}
         style={styles.animatedView}
-        renderOverlay={(position, layout) => (
-          <NavigationHeader
-            navigationState={navigationState}
-            position={position}
-            getTitle={state => state.key}
-          />
-        )}
-        renderScene={(state, index, position, layout) => (
-          <NavigationCard
-            key={state.key}
-            index={index}
-            navigationState={navigationState}
-            position={position}
-            layout={layout}>
-            <ScrollView style={styles.scrollView}>
-              <NavigationExampleRow
-                text={navigationState.children[navigationState.index].key}
-              />
-              <NavigationExampleRow
-                text="Push!"
-                onPress={() => {
-                  onNavigate('Route #' + navigationState.children.length);
-                }}
-              />
-              <NavigationExampleRow
-                text="Exit Animated Nav Example"
-                onPress={this.props.onExampleExit}
-              />
-            </ScrollView>
-          </NavigationCard>
-        )}
+        renderOverlay={this._renderHeader}
+        applyAnimation={(pos, navState) => {
+          Animated.timing(pos, {toValue: navState.index, duration: 1000}).start();
+        }}
+        renderScene={this._renderCard}
       />
     );
   }
+
+  _renderHeader(/*NavigationSceneRendererProps*/ props) {
+    return (
+      <NavigationHeader
+        {...props}
+        getTitle={state => state.key}
+      />
+    );
+  }
+
+  _renderCard(/*NavigationSceneRendererProps*/ props) {
+    return (
+      <NavigationCard
+        {...props}
+        key={'card_' + props.scene.navigationState.key}
+        renderScene={this._renderScene}
+      />
+    );
+  }
+
+  _renderScene(/*NavigationSceneRendererProps*/ props) {
+    return (
+      <ScrollView style={styles.scrollView}>
+        <NavigationExampleRow
+          text={props.scene.navigationState.key}
+        />
+        <NavigationExampleRow
+          text="Push!"
+          onPress={() => {
+            props.onNavigate({
+              type: 'push',
+              key: 'Route #' + props.scenes.length,
+            });
+          }}
+        />
+        <NavigationExampleRow
+          text="Exit Animated Nav Example"
+          onPress={this.props.onExampleExit}
+        />
+      </ScrollView>
+    );
+  }
 }
 
 const styles = StyleSheet.create({
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js
index 8f8669fb4c238c..447ab2051fba08 100644
--- a/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationBasicExample.js
@@ -26,12 +26,21 @@ const {
 } = NavigationExperimental;
 const StackReducer = NavigationReducer.StackReducer;
 
-const NavigationBasicReducer = StackReducer({
-  initialStates: [
-    {key: 'first_page'}
-  ],
-  matchAction: action => true,
-  actionStateMap: action => ({key: action}),
+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({
@@ -51,13 +60,13 @@ const NavigationBasicExample = React.createClass({
               <NavigationExampleRow
                 text={`Push page #${navState.children.length}`}
                 onPress={() => {
-                  onNavigate('page #' + navState.children.length);
+                  onNavigate({ type: 'push', key: 'page #' + navState.children.length });
                 }}
               />
               <NavigationExampleRow
                 text="pop"
                 onPress={() => {
-                  onNavigate(StackReducer.PopAction());
+                  onNavigate(NavigationRootContainer.getBackAction());
                 }}
               />
               <NavigationExampleRow
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.js
new file mode 100644
index 00000000000000..b6aa01a7993937
--- /dev/null
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationCardStackExample.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';
+
+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,
+  key: 'exmaple',
+  children: [{key: 'First Route'}],
+});
+
+class NavigationCardStackExample extends React.Component {
+
+  constructor(props, context) {
+    super(props, context);
+    this.state = {isHorizontal: true};
+  }
+
+  componentWillMount() {
+    this._renderNavigation = this._renderNavigation.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+    this._toggleDirection = this._toggleDirection.bind(this);
+  }
+
+  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(/*NavigationSceneRendererProps*/ props) {
+    return (
+      <ScrollView style={styles.scrollView}>
+        <NavigationExampleRow
+          text={
+            this.state.isHorizontal ?
+            'direction = "horizontal"' :
+            'direction = "vertical"'
+          }
+          onPress={this._toggleDirection}
+        />
+        <NavigationExampleRow
+          text={'route = ' + props.scene.navigationState.key}
+        />
+        <NavigationExampleRow
+          text="Push Route"
+          onPress={() => {
+            props.onNavigate({
+              type: 'push',
+              key: 'Route ' + props.scenes.length,
+            });
+          }}
+        />
+        <NavigationExampleRow
+          text="Pop Route"
+          onPress={() => {
+            props.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
index ee8472242c4a8f..084dc1f39b0fe7 100644
--- a/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationCompositionExample.js
@@ -10,57 +10,77 @@
  * 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 NavigationExampleRow = require('./NavigationExampleRow');
+const NavigationExampleTabBar = require('./NavigationExampleTabBar');
+
 const {
   NavigationExperimental,
   ScrollView,
   StyleSheet,
   View,
 } = React;
+
 const {
   AnimatedView: NavigationAnimatedView,
-  Card: NavigationCard,
+  CardStack: NavigationCardStack,
   Container: NavigationContainer,
-  RootContainer: NavigationRootContainer,
   Header: NavigationHeader,
   Reducer: NavigationReducer,
+  RootContainer: NavigationRootContainer,
   View: NavigationView,
 } = NavigationExperimental;
-const NavigationExampleRow = require('./NavigationExampleRow');
-const NavigationExampleTabBar = require('./NavigationExampleTabBar');
+
+
+import type {
+  NavigationParentState,
+  NavigationSceneRenderer,
+  NavigationSceneRendererProps,
+} from 'NavigationTypeDefinition';
+
+type Action = {
+  isExitAction?: boolean,
+};
 
 const ExampleExitAction = () => ({
   isExitAction: true,
 });
-ExampleExitAction.match = (action) => (
+
+ExampleExitAction.match = (action: Action) => (
   action && action.isExitAction === true
 );
 
-const ExamplePageAction = (type) => ({
+const PageAction = (type) => ({
   type,
   isPageAction: true,
 });
-ExamplePageAction.match = (action) => (
+
+PageAction.match = (action) => (
   action && action.isPageAction === true
 );
 
-const ExampleSettingsPageAction = (type) => ({
-  ...ExamplePageAction(type),
-  isSettingsPageAction: true,
+const ExampleProfilePageAction = (type) => ({
+  ...PageAction(type),
+  isProfilePageAction: true,
 });
-ExampleSettingsPageAction.match = (action) => (
-  action && action.isSettingsPageAction === true
+
+ExampleProfilePageAction.match = (action) => (
+  action && action.isProfilePageAction === true
 );
 
-const ExampleInfoAction = () => ExamplePageAction('InfoPage');
+const ExampleInfoAction = () => PageAction('InfoPage');
 
-const ExampleNotifSettingsAction = () => ExampleSettingsPageAction('NotifSettingsPage');
+const ExampleNotifProfileAction = () => ExampleProfilePageAction('NotifProfilePage');
 
 const _jsInstanceUniqueId = '' + Date.now();
+
 let _uniqueIdCount = 0;
+
 function pageStateActionMap(action) {
   return {
     key: 'page-' + _jsInstanceUniqueId + '-' + (_uniqueIdCount++),
@@ -68,147 +88,150 @@ function pageStateActionMap(action) {
   };
 }
 
-function getTabActionMatcher(key) {
-  return function (action) {
-    if (!ExamplePageAction.match(action)) {
-      return false;
-    }
-    if (ExampleSettingsPageAction.match(action)) {
-      return key === 'settings';
-    }
-    return true;
-  };
-}
-
-var ExampleTabs = [
-  {
-    label: 'Account',
-    reducer: NavigationReducer.StackReducer({
-      initialStates: [
-        {type: 'AccountPage', key: 'base'}
-      ],
-      key: 'account',
-      matchAction: getTabActionMatcher('account'),
-      actionStateMap: pageStateActionMap,
+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'},
+        ],
+      },
     }),
-  },
-  {
-    label: 'Notifications',
-    reducer: NavigationReducer.StackReducer({
-      initialStates: [
-        {type: 'NotifsPage', key: 'base'}
-      ],
-      key: 'notifs',
-      matchAction: getTabActionMatcher('notifs'),
-      actionStateMap: pageStateActionMap,
+    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'},
+        ],
+      },
     }),
-  },
-  {
-    label: 'Settings',
-    reducer: NavigationReducer.StackReducer({
-      initialStates: [
-        {type: 'SettingsPage', key: 'base'}
-      ],
-      key: 'settings',
-      matchAction: getTabActionMatcher('settings'),
-      actionStateMap: pageStateActionMap,
+    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'},
+        ],
+      },
     }),
-  },
-];
-
-const ExampleAppReducer = NavigationReducer.TabsReducer({
-  tabReducers: ExampleTabs.map(tab => tab.reducer),
+  ],
 });
 
 function stateTypeTitleMap(pageState) {
   switch (pageState.type) {
-    case 'AccountPage':
-      return 'Account Page';
+    case 'ProfilePage':
+      return 'Profile Page';
     case 'NotifsPage':
       return 'Notifications';
     case 'SettingsPage':
       return 'Settings';
     case 'InfoPage':
       return 'Info Page';
-    case 'NotifSettingsPage':
-      return 'Notification Settings';
+    case 'NotifProfilePage':
+      return 'Page in Profile';
   }
 }
 
 class ExampleTabScreen extends React.Component {
+  _renderCard: NavigationSceneRenderer;
+  _renderHeader: NavigationSceneRenderer;
+  _renderScene: NavigationSceneRenderer;
+
+  componentWillMount() {
+    this._renderHeader = this._renderHeader.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+  }
+
   render() {
     return (
-      <NavigationAnimatedView
+      <NavigationCardStack
         style={styles.tabContent}
         navigationState={this.props.navigationState}
-        renderOverlay={this._renderHeader.bind(this)}
-        renderScene={this._renderScene.bind(this)}
+        renderOverlay={this._renderHeader}
+        renderScene={this._renderScene}
       />
     );
   }
-  _renderHeader(position, layout) {
+  _renderHeader(props: NavigationSceneRendererProps) {
     return (
       <NavigationHeader
-        navigationState={this.props.navigationState}
-        position={position}
-        layout={layout}
+        {...props}
         getTitle={state => stateTypeTitleMap(state)}
       />
     );
   }
-  _renderScene(child, index, position, layout) {
+
+  _renderScene(props: NavigationSceneRendererProps) {
+    const {onNavigate} = props;
     return (
-      <NavigationCard
-        key={child.key}
-        index={index}
-        childState={child}
-        navigationState={this.props.navigationState}
-        position={position}
-        layout={layout}>
-        <ScrollView style={styles.scrollView}>
-          <NavigationExampleRow
-            text="Open page"
-            onPress={() => {
-              this.props.onNavigate(ExampleInfoAction());
-            }}
-          />
-          <NavigationExampleRow
-            text="Open notifs settings in settings tab"
-            onPress={() => {
-              this.props.onNavigate(ExampleNotifSettingsAction());
-            }}
-          />
-          <NavigationExampleRow
-            text="Exit Composition Example"
-            onPress={() => {
-              this.props.onNavigate(ExampleExitAction());
-            }}
-          />
-        </ScrollView>
-      </NavigationCard>
+      <ScrollView style={styles.scrollView}>
+        <NavigationExampleRow
+          text="Open page"
+          onPress={() => {
+            onNavigate(ExampleInfoAction());
+          }}
+        />
+        <NavigationExampleRow
+          text="Open a page in the profile tab"
+          onPress={() => {
+            onNavigate(ExampleNotifProfileAction());
+          }}
+        />
+        <NavigationExampleRow
+          text="Exit Composition Example"
+          onPress={() => {
+            onNavigate(ExampleExitAction());
+          }}
+        />
+      </ScrollView>
     );
   }
 }
 ExampleTabScreen = NavigationContainer.create(ExampleTabScreen);
 
 class NavigationCompositionExample extends React.Component {
+  navRootContainer: NavigationRootContainer;
+
   render() {
     return (
       <NavigationRootContainer
         reducer={ExampleAppReducer}
-        persistenceKey="NavigationCompositionExampleState"
+        persistenceKey="NavigationCompositionState"
         ref={navRootContainer => { this.navRootContainer = navRootContainer; }}
         renderNavigation={this.renderApp.bind(this)}
       />
     );
   }
-  handleBackAction() {
+  handleBackAction(): boolean {
     return (
       this.navRootContainer &&
       this.navRootContainer.handleNavigation(NavigationRootContainer.getBackAction())
     );
   }
-  renderApp(navigationState, onNavigate) {
+  renderApp(navigationState: NavigationParentState, onNavigate: Function) {
     if (!navigationState) {
       return null;
     }
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js
index 4aade5303c173f..0781570da28df0 100644
--- a/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js
+++ b/Examples/UIExplorer/NavigationExperimental/NavigationExperimentalExample.js
@@ -30,11 +30,13 @@ var NavigationExampleRow = require('./NavigationExampleRow');
 var EXAMPLES = {
   'Tabs': require('./NavigationTabsExample'),
   'Basic': require('./NavigationBasicExample'),
-  'Animated Card Stack': require('./NavigationAnimatedExample'),
+  'Animated Example': require('./NavigationAnimatedExample'),
   'Composition': require('./NavigationCompositionExample'),
+  'Card Stack Example': require('./NavigationCardStackExample'),
+  'Tic Tac Toe': require('./NavigationTicTacToeExample'),
 };
 
-var EXAMPLE_STORAGE_KEY = 'NavigationExampleExample';
+var EXAMPLE_STORAGE_KEY = 'NavigationExperimentalExample';
 
 var NavigationExperimentalExample = React.createClass({
   statics: {
@@ -45,13 +47,13 @@ var NavigationExperimentalExample = React.createClass({
 
   getInitialState: function() {
     return {
-      exampe: null,
+      example: null,
     };
   },
 
   componentDidMount() {
     AsyncStorage.getItem(EXAMPLE_STORAGE_KEY, (err, example) => {
-      if (err || !example) {
+      if (err || !example || !EXAMPLES[example]) {
         this.setState({
           example: 'menu',
         });
diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js b/Examples/UIExplorer/NavigationExperimental/NavigationTicTacToeExample.js
new file mode 100644
index 00000000000000..d0604ec08eae3e
--- /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/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js
index 25f45f0ab30366..ff44f35220fdcb 100644
--- a/Examples/UIExplorer/Navigator/NavigationBarSample.js
+++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js
@@ -24,8 +24,6 @@ var {
   TouchableOpacity,
 } = React;
 
-var cssVar = require('cssVar');
-
 class NavButton extends React.Component {
   render() {
     return (
@@ -178,7 +176,7 @@ var styles = StyleSheet.create({
     marginVertical: 10,
   },
   navBarTitleText: {
-    color: cssVar('fbui-bluegray-60'),
+    color: '#373E4D',
     fontWeight: '500',
     marginVertical: 9,
   },
@@ -189,7 +187,7 @@ var styles = StyleSheet.create({
     paddingRight: 10,
   },
   navBarButtonText: {
-    color: cssVar('fbui-accent-blue'),
+    color: '#5890FF',
   },
   scene: {
     flex: 1,
diff --git a/Examples/UIExplorer/Navigator/NavigatorExample.js b/Examples/UIExplorer/Navigator/NavigatorExample.js
index d15939886f062e..7346af4542d495 100644
--- a/Examples/UIExplorer/Navigator/NavigatorExample.js
+++ b/Examples/UIExplorer/Navigator/NavigatorExample.js
@@ -10,7 +10,9 @@
  * 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');
diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js
index 4078cac6db6421..1735633c9d3497 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 08d227c3f0a0f0..3c6f19f94dfcc1 100644
--- a/Examples/UIExplorer/NavigatorIOSExample.js
+++ b/Examples/UIExplorer/NavigatorIOSExample.js
@@ -15,11 +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,
+  NavigatorIOS,
   ScrollView,
   StyleSheet,
   Text,
@@ -27,8 +28,7 @@ var {
   View,
 } = React;
 
-var EmptyPage = React.createClass({
-
+const EmptyPage = React.createClass({
   render: function() {
     return (
       <View style={styles.emptyPage}>
@@ -38,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', () => {
@@ -122,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', () => {
@@ -171,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', () => {
@@ -203,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',
   },
diff --git a/Examples/UIExplorer/NetInfoExample.js b/Examples/UIExplorer/NetInfoExample.js
index a79ed67e2043c9..97af9e1e00bed4 100644
--- a/Examples/UIExplorer/NetInfoExample.js
+++ b/Examples/UIExplorer/NetInfoExample.js
@@ -134,8 +134,8 @@ const IsConnectionExpensive = React.createClass({
     };
   },
   _checkIfExpensive() {
-    NetInfo.isConnectionExpensive(
-        (isConnectionExpensive) => { this.setState({isConnectionExpensive}); }
+    NetInfo.isConnectionExpensive().then(
+        isConnectionExpensive => { this.setState({isConnectionExpensive}); }
     );
   },
   render() {
diff --git a/Examples/UIExplorer/PickerAndroidExample.js b/Examples/UIExplorer/PickerAndroidExample.js
index 0f76f6e8480a34..cdbc649626b4de 100644
--- a/Examples/UIExplorer/PickerAndroidExample.js
+++ b/Examples/UIExplorer/PickerAndroidExample.js
@@ -44,8 +44,6 @@ const PickerExample = React.createClass({
     };
   },
 
-  displayName: 'Android Picker',
-
   render: function() {
     return (
       <UIExplorerPage title="<Picker>">
diff --git a/Examples/UIExplorer/PointerEventsExample.js b/Examples/UIExplorer/PointerEventsExample.js
index 735a5952d49a45..4ca8e9a164dcc7 100644
--- a/Examples/UIExplorer/PointerEventsExample.js
+++ b/Examples/UIExplorer/PointerEventsExample.js
@@ -182,7 +182,7 @@ var BoxOnlyExample = React.createClass({
 });
 
 type ExampleClass = {
-  Component: ReactClass<any, any, any>,
+  Component: ReactClass<any>,
   title: string,
   description: string,
 };
diff --git a/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js b/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js
deleted file mode 100644
index 3d22f7608e93f9..00000000000000
--- a/Examples/UIExplorer/PullToRefreshViewAndroidExample.android.js
+++ /dev/null
@@ -1,128 +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.
-*
-*/
-'use strict';
-
-const React = require('react-native');
-const {
-  ScrollView,
-  StyleSheet,
-  PullToRefreshViewAndroid,
-  Text,
-  TouchableWithoutFeedback,
-  View,
-} = React;
-
-const styles = StyleSheet.create({
-  row: {
-    borderColor: 'grey',
-    borderWidth: 1,
-    padding: 20,
-    backgroundColor: '#3a5795',
-    margin: 5,
-  },
-  text: {
-    alignSelf: 'center',
-    color: '#fff',
-
-  },
-  layout: {
-    flex: 1,
-  },
-  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 PullToRefreshViewAndroidExample = React.createClass({
-  statics: {
-    title: '<PullToRefreshViewAndroid>',
-    description: 'Container that adds pull-to-refresh support to its child view.'
-  },
-
-  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 (
-      <PullToRefreshViewAndroid
-        style={styles.layout}
-        refreshing={this.state.isRefreshing}
-        onRefresh={this._onRefresh}
-        colors={['#ff0000', '#00ff00', '#0000ff']}
-        progressBackgroundColor={'#ffff00'}
-        >
-        <ScrollView style={styles.scrollview}>
-          {rows}
-        </ScrollView>
-      </PullToRefreshViewAndroid>
-    );
-  },
-
-  _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 = PullToRefreshViewAndroidExample;
diff --git a/Examples/UIExplorer/PushNotificationIOSExample.js b/Examples/UIExplorer/PushNotificationIOSExample.js
index bd6109f1f09bd4..9fcf8cd1c343ed 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 69ecab0a7655a1..5aa56cf881e44d 100644
--- a/Examples/UIExplorer/RCTRootViewIOSExample.js
+++ b/Examples/UIExplorer/RCTRootViewIOSExample.js
@@ -82,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/>
     );
@@ -90,7 +90,7 @@ exports.examples = [
 },
 {
   title: 'RCTRootView\'s size flexibility',
-  render(): React.Component {
+  render(): ReactElement {
     return (
       <RootViewSizeFlexibilityExample/>
     );
diff --git a/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js b/Examples/UIExplorer/RootViewSizeFlexibilityExampleApp.js
new file mode 100644
index 00000000000000..9c48357033fb99
--- /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/SetPropertiesExampleApp.js b/Examples/UIExplorer/SetPropertiesExampleApp.js
index 748bbc3ccb4a6d..f5dc9c8eab39b0 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/SnapshotExample.js b/Examples/UIExplorer/SnapshotExample.js
new file mode 100644
index 00000000000000..d843acee80d3fb
--- /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
index 094148c4d71dfd..e33dbd71a98b39 100644
--- a/Examples/UIExplorer/StatusBarExample.js
+++ b/Examples/UIExplorer/StatusBarExample.js
@@ -57,12 +57,16 @@ const showHideTransitions = [
   'slide',
 ];
 
+function getValue(values: Array<any>, index: number): any {
+  return values[index % values.length];
+}
+
 const StatusBarExample = React.createClass({
   getInitialState(): State {
     return {
       animated: true,
-      backgroundColor: this._getValue(colors, 0),
-      showHideTransition: this._getValue(showHideTransitions, 0),
+      backgroundColor: getValue(colors, 0),
+      showHideTransition: getValue(showHideTransitions, 0),
     };
   },
 
@@ -70,10 +74,6 @@ const StatusBarExample = React.createClass({
   _barStyleIndex: 0,
   _showHideTransitionIndex: 0,
 
-  _getValue(values: Array<any>, index: number): any {
-    return values[index % values.length];
-  },
-
   render() {
     return (
       <View>
@@ -110,10 +110,10 @@ const StatusBarExample = React.createClass({
             style={styles.wrapper}
             onPress={() => {
               this._barStyleIndex++;
-              this.setState({barStyle: this._getValue(barStyles, this._barStyleIndex)});
+              this.setState({barStyle: getValue(barStyles, this._barStyleIndex)});
             }}>
             <View style={styles.button}>
-              <Text>style: '{this._getValue(barStyles, this._barStyleIndex)}'</Text>
+              <Text>style: '{getValue(barStyles, this._barStyleIndex)}'</Text>
             </View>
           </TouchableHighlight>
         </View>
@@ -138,13 +138,13 @@ const StatusBarExample = React.createClass({
               this._showHideTransitionIndex++;
               this.setState({
                 showHideTransition:
-                this._getValue(showHideTransitions, this._showHideTransitionIndex),
+                getValue(showHideTransitions, this._showHideTransitionIndex),
               });
             }}>
             <View style={styles.button}>
               <Text>
                 showHideTransition:
-                '{this._getValue(showHideTransitions, this._showHideTransitionIndex)}'
+                '{getValue(showHideTransitions, this._showHideTransitionIndex)}'
               </Text>
             </View>
           </TouchableHighlight>
@@ -155,10 +155,10 @@ const StatusBarExample = React.createClass({
             style={styles.wrapper}
             onPress={() => {
               this._colorIndex++;
-              this.setState({backgroundColor: this._getValue(colors, this._colorIndex)});
+              this.setState({backgroundColor: getValue(colors, this._colorIndex)});
             }}>
             <View style={styles.button}>
-              <Text>backgroundColor: '{this._getValue(colors, this._colorIndex)}'</Text>
+              <Text>backgroundColor: '{getValue(colors, this._colorIndex)}'</Text>
             </View>
           </TouchableHighlight>
         </View>
@@ -181,11 +181,116 @@ const StatusBarExample = React.createClass({
   },
 });
 
+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: 'Status Bar',
+  title: 'StatusBar',
   render() {
     return <StatusBarExample />;
   },
+}, {
+  title: 'StatusBar static API',
+  render() {
+    return <StatusBarStaticExample />;
+  },
 }];
 
 var styles = StyleSheet.create({
diff --git a/Examples/UIExplorer/TextInputExample.android.js b/Examples/UIExplorer/TextInputExample.android.js
index 3652b974c8fc19..0c5f1c0c8698c6 100644
--- a/Examples/UIExplorer/TextInputExample.android.js
+++ b/Examples/UIExplorer/TextInputExample.android.js
@@ -75,17 +75,24 @@ var TextEventsExample = React.createClass({
 class AutoExpandingTextInput extends React.Component {
   constructor(props) {
     super(props);
-    this.state = {text: '', height: 0};
+    this.state = {
+      text: `React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and 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.`,
+      height: 0,
+    };
   }
   render() {
     return (
       <TextInput
         {...this.props}
         multiline={true}
+        onChangeContentSize={(event) => {
+          this.setState({
+            height: event.nativeEvent.contentSize.height,
+          });
+        }}
         onChange={(event) => {
           this.setState({
             text: event.nativeEvent.text,
-            height: event.nativeEvent.contentSize.height,
           });
         }}
         style={[styles.default, {height: Math.max(35, this.state.height)}]}
@@ -178,6 +185,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,
@@ -286,6 +347,10 @@ exports.examples = [
       return <View>{examples}</View>;
     }
   },
+  {
+    title: 'Blur on submit',
+    render: function(): ReactElement { return <BlurOnSubmitExample />; },
+  },
   {
     title: 'Event handling',
     render: function(): ReactElement { return <TextEventsExample />; },
diff --git a/Examples/UIExplorer/TextInputExample.ios.js b/Examples/UIExplorer/TextInputExample.ios.js
index eac262d7706157..239197dbacbb9e 100644
--- a/Examples/UIExplorer/TextInputExample.ios.js
+++ b/Examples/UIExplorer/TextInputExample.ios.js
@@ -97,6 +97,8 @@ var TextEventsExample = React.createClass({
 });
 
 class AutoExpandingTextInput extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: '', height: 0};
@@ -120,6 +122,8 @@ class AutoExpandingTextInput extends React.Component {
 }
 
 class RewriteExample extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: ''};
@@ -149,6 +153,8 @@ class RewriteExample extends React.Component {
 }
 
 class RewriteExampleInvalidCharacters extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: ''};
@@ -170,6 +176,8 @@ class RewriteExampleInvalidCharacters extends React.Component {
 }
 
 class TokenizedTextExample extends React.Component {
+  state: any;
+
   constructor(props) {
     super(props);
     this.state = {text: 'Hello #World'};
diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index 515a0c2b858c03..5b28f2e134d40c 100644
--- a/Examples/UIExplorer/TouchableExample.js
+++ b/Examples/UIExplorer/TouchableExample.js
@@ -24,6 +24,8 @@ var {
   TouchableHighlight,
   TouchableOpacity,
   UIManager,
+  Platform,
+  TouchableNativeFeedback,
   View,
 } = React;
 
@@ -55,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!
@@ -93,7 +95,21 @@ exports.examples = [
     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() {
@@ -243,6 +259,114 @@ var ForceTouchExample = React.createClass({
   },
 });
 
+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({
@@ -261,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,
   },
@@ -271,6 +409,10 @@ var styles = StyleSheet.create({
     borderRadius: 8,
     padding: 6,
   },
+  hitSlopWrapper: {
+    backgroundColor: 'red',
+    marginVertical: 30,
+  },
   logBox: {
     padding: 20,
     margin: 10,
diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
index e6dfc36585b773..95f9215cee9840 100644
--- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
+++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj
@@ -8,6 +8,7 @@
 
 /* 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 */; };
@@ -18,6 +19,7 @@
 		1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
 		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 */; };
@@ -178,6 +180,7 @@
 /* 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>"; };
@@ -188,6 +191,7 @@
 		134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; 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>"; };
@@ -422,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 */,
@@ -882,7 +888,9 @@
 				1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
 				1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.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 */,
diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m
index 577e6dd94b5d1c..b0d19a59c170ac 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/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist
index 9e91b1cef8cc6b..d2e82af921427a 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/UIExplorerActions.js b/Examples/UIExplorer/UIExplorerActions.js
new file mode 100644
index 00000000000000..8f7a59f4e23e30
--- /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 cdf92c6848de45..026567bf5d9d79 100644
--- a/Examples/UIExplorer/UIExplorerApp.android.js
+++ b/Examples/UIExplorer/UIExplorerApp.android.js
@@ -16,87 +16,119 @@
  */
 '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,
-    });
-  },
+    const {stack} = navigationState;
+    const title = UIExplorerStateTitleMap(stack.children[stack.index]);
+    const index = stack.children.length <= 1 ?  1 : stack.index;
 
-  _renderHome: function() {
-    var onSelectExample = this.onSelectExample;
-    return React.createClass({
-      render: function() {
-        return (
-          <UIExplorerList
-            onSelectExample={onSelectExample}
-            isInDrawer={false}
+    if (stack && stack.children[index]) {
+      const {key} = stack.children[index];
+      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
@@ -107,16 +139,24 @@ var UIExplorerApp = React.createClass({
           navIcon={require('image!ic_menu_black_24dp')}
           onIconClicked={() => this.drawer.openDrawer()}
           style={styles.toolbar}
-          title={this.state.example.title}
+          title={title}
         />
-        <Component
-          ref={(example) => { this._exampleRef = example; }}
+        <UIExplorerExampleList
+          list={UIExplorerList}
+          {...stack.children[0]}
         />
       </View>
     );
-  },
+  }
 
-  _handleBackButtonPress: function() {
+  _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 &&
@@ -124,15 +164,16 @@ var UIExplorerApp = React.createClass({
     ) {
       return true;
     }
-    if (this.state.example.title !== this._getUIExplorerHome().title) {
-      this.onSelectExample(this._getUIExplorerHome());
-      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 f93cc2a66a64e8..384ea1c18c73d7 100644
--- a/Examples/UIExplorer/UIExplorerApp.ios.js
+++ b/Examples/UIExplorer/UIExplorerApp.ios.js
@@ -16,137 +16,179 @@
  */
 '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,
-  StatusBar,
 } = React;
 
-var UIExplorerApp = React.createClass({
+const {
+  CardStack: NavigationCardStack,
+  Header: NavigationHeader,
+  Reducer: NavigationReducer,
+  RootContainer: NavigationRootContainer,
+} = NavigationExperimental;
 
-  getInitialState: function() {
-    return {
-      openExternalExample: (null: ?React.Component),
-    };
-  },
+import type { Value } from 'Animated';
+
+import type { NavigationSceneRendererProps } from 'NavigationTypeDefinition';
+
+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);
+  }
+  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 (
-      <View style={{flex: 1}}>
-        <StatusBar barStyle="default" />
-        <NavigatorIOS
-          style={styles.container}
-          initialRoute={{
-            title: 'UIExplorer',
-            component: UIExplorerList,
-            passProps: {
-              onExternalExampleRequested: (example) => {
-                this.setState({ openExternalExample: example, });
-              },
-            }
-          }}
-          itemWrapperStyle={styles.itemWrapper}
-          tintColor="#008888"
-        />
-      </View>
+      <NavigationCardStack
+        navigationState={stack}
+        style={styles.container}
+        renderOverlay={this._renderOverlay}
+        renderScene={this._renderScene}
+      />
     );
   }
-});
-
-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',
-  }
-});
-
-var SetPropertiesExampleApp = React.createClass({
-
-  render: function() {
-    var wrapperStyle = {
-      backgroundColor: this.props.color,
-      flex: 1,
-      alignItems: 'center',
-      justifyContent: 'center'
-    };
 
+  _renderOverlay(props: NavigationSceneRendererProps): ReactElement {
     return (
-      <View style={wrapperStyle}>
-        <Text>
-          Embedded React Native view
-        </Text>
-      </View>
+      <NavigationHeader
+        {...props}
+        key={'header_' + props.scene.navigationState.key}
+        getTitle={UIExplorerStateTitleMap}
+      />
     );
-  },
-});
+  }
 
-var RootViewSizeFlexibilityExampleApp = React.createClass({
+  _renderScene(props: NavigationSceneRendererProps): ?ReactElement {
+    const state = props.scene.navigationState;
+    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 00000000000000..4a81314c293c3c
--- /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> {
+  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/UIExplorerList.android.js b/Examples/UIExplorer/UIExplorerList.android.js
index 8959a0919f9a63..68be333e51095d 100644
--- a/Examples/UIExplorer/UIExplorerList.android.js
+++ b/Examples/UIExplorer/UIExplorerList.android.js
@@ -15,103 +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('./ListViewExample'),
-  require('./PickerAndroidExample'),
-  require('./ProgressBarAndroidExample'),
-  require('./PullToRefreshViewAndroidExample.android'),
-  require('./RefreshControlExample'),
-  require('./ScrollViewSimpleExample'),
-  require('./StatusBarExample'),
-  require('./SwitchExample'),
-  require('./TextExample.android'),
-  require('./TextInputExample.android'),
-  require('./ToolbarAndroidExample'),
-  require('./TouchableExample'),
-  require('./ViewExample'),
-  require('./ViewPagerAndroidExample.android'),
-  require('./WebViewExample'),
+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('./AlertExample').AlertExample,
-  require('./AppStateExample'),
-  require('./BorderExample'),
-  require('./CameraRollExample'),
-  require('./ClipboardExample'),
-  require('./DatePickerAndroidExample'),
-  require('./GeolocationExample'),
-  require('./ImageEditingExample'),
-  require('./IntentAndroidExample.android'),
-  require('./LayoutEventsExample'),
-  require('./LayoutExample'),
-  require('./NavigationExperimental/NavigationExperimentalExample'),
-  require('./NetInfoExample'),
-  require('./PanResponderExample'),
-  require('./PointerEventsExample'),
-  require('./TimePickerAndroidExample'),
-  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 5c33d7f645e539..8d770d1f40be7a 100644
--- a/Examples/UIExplorer/UIExplorerList.ios.js
+++ b/Examples/UIExplorer/UIExplorerList.ios.js
@@ -15,151 +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('./RefreshControlExample'),
-  require('./ScrollViewExample'),
-  require('./SegmentedControlIOSExample'),
-  require('./SliderIOSExample'),
-  require('./StatusBarExample'),
-  require('./SwitchExample'),
-  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('./AppStateExample'),
-  require('./AsyncStorageExample'),
-  require('./BorderExample'),
-  require('./BoxShadowExample'),
-  require('./CameraRollExample'),
-  require('./ClipboardExample'),
-  require('./GeolocationExample'),
-  require('./ImageEditingExample'),
-  require('./LayoutExample'),
-  require('./NavigationExperimental/NavigationExperimentalExample'),
-  require('./NetInfoExample'),
-  require('./PanResponderExample'),
-  require('./PointerEventsExample'),
-  require('./PushNotificationIOSExample'),
-  require('./RCTRootViewIOSExample'),
-  require('./StatusBarIOSExample'),
-  require('./TimerExample'),
-  require('./TransformExample'),
-  require('./VibrationIOSExample'),
-  require('./XHRExample.ios'),
+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 17ecd8dc2c02b0..00000000000000
--- a/Examples/UIExplorer/UIExplorerListBase.js
+++ /dev/null
@@ -1,190 +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,
-  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 (
-      <Text style={styles.sectionHeader}>
-        {section.toUpperCase()}
-      </Text>
-    );
-  }
-
-  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(String(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,
-    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',
-    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 00000000000000..854e5b36eed75a
--- /dev/null
+++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js
@@ -0,0 +1,106 @@
+/**
+ * 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 'NavigationTypeDefinition';
+
+import type {UIExplorerAction} from './UIExplorerActions';
+
+export type UIExplorerNavigationState = {
+  externalExample: ?string;
+  stack: NavigationState;
+};
+
+const UIExplorerStackReducer = StackReducer({
+  getPushedReducerForAction: (action, lastState) => {
+    if (action.type === 'UIExplorerExampleAction' && UIExplorerList.Modules[action.openExample]) {
+      if (lastState.children.find(child => child.key === action.openExample)) {
+        // The example is already open, we should avoid pushing examples twice
+        return null;
+      }
+      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/UIExplorerPage.js b/Examples/UIExplorer/UIExplorerPage.js
index 2c74497a7fef3b..14bf2bed010e68 100644
--- a/Examples/UIExplorer/UIExplorerPage.js
+++ b/Examples/UIExplorer/UIExplorerPage.js
@@ -37,9 +37,9 @@ var UIExplorerPage = React.createClass({
     var ContentWrapper;
     var wrapperProps = {};
     if (this.props.noScroll) {
-      ContentWrapper = (View: ReactClass<any, any, any>);
+      ContentWrapper = (View: ReactClass<any>);
     } else {
-      ContentWrapper = (ScrollView: ReactClass<any, any, any>);
+      ContentWrapper = (ScrollView: ReactClass<any>);
       wrapperProps.automaticallyAdjustContentInsets = !this.props.title;
       wrapperProps.keyboardShouldPersistTaps = true;
       wrapperProps.keyboardDismissMode = 'interactive';
diff --git a/Examples/UIExplorer/UIExplorerStateTitleMap.js b/Examples/UIExplorer/UIExplorerStateTitleMap.js
new file mode 100644
index 00000000000000..a7facd838e6a90
--- /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 'NavigationTypeDefinition';
+
+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/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
index 31fc8f74ee9e1b..08a1a1e37fec81 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m
@@ -24,8 +24,12 @@
 #define RUN_RUNLOOP_WHILE(CONDITION) \
 { \
   NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:5]; \
-  while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
+  while ((CONDITION)) { \
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; \
+    if ([timeout timeIntervalSinceNow] <= 0) { \
+      XCTFail(@"Runloop timed out before condition was met"); \
+      break; \
+    } \
   } \
 }
 
@@ -37,6 +41,8 @@ @interface TestExecutor : NSObject <RCTJavaScriptExecutor>
 
 @implementation TestExecutor
 
+@synthesize valid = _valid;
+
 RCT_EXPORT_MODULE()
 
 - (void)setUp {}
@@ -51,7 +57,7 @@ - (instancetype)init
 
 - (BOOL)isValid
 {
-  return YES;
+  return _valid;
 }
 
 - (void)flushedQueue:(RCTJavaScriptCallback)onComplete
@@ -94,7 +100,10 @@ - (void)injectJSONText:(NSString *)script
   onComplete(nil);
 }
 
-- (void)invalidate {}
+- (void)invalidate
+{
+  _valid = NO;
+}
 
 @end
 
@@ -128,7 +137,7 @@ - (void)setUp
   [_bridge invalidate];
   [_bridge setUp];
 
-  _jsExecutor = [_bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
+  _jsExecutor = _bridge.batchedBridge.javaScriptExecutor;
   XCTAssertNotNil(_jsExecutor);
 }
 
@@ -139,10 +148,8 @@ - (void)tearDown
   _testMethodCalled = NO;
 
   [_bridge invalidate];
+  RUN_RUNLOOP_WHILE(_jsExecutor.isValid);
   _bridge = nil;
-
-  RUN_RUNLOOP_WHILE(_jsExecutor != nil);
-  XCTAssertNotNil(_jsExecutor);
 }
 
 - (void)testHookRegistration
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
index 5713d03473a7ca..5a7cc12bd28eee 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
@@ -47,10 +47,9 @@ - (void)testImageLoading
     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:RCTResizeModeContain 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) {
@@ -78,10 +77,9 @@ - (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
     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:RCTResizeModeContain 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) {
@@ -103,10 +101,9 @@ - (void)testImageDecoding
     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:RCTResizeModeStretch 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);
   }];
@@ -133,10 +130,9 @@ - (void)testImageLoaderUsesImageDecoderWithHighestPriority
     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:RCTResizeModeStretch 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/RCTModuleInitNotificationRaceTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleInitNotificationRaceTests.m
new file mode 100644
index 00000000000000..77a997a4baa59c
--- /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 00000000000000..83a549736809ff
--- /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/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m
index f2715947a4eaf8..cfe3959bc756e6 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/VibrationExample.js b/Examples/UIExplorer/VibrationExample.js
new file mode 100644
index 00000000000000..11f17d368b86b7
--- /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/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js
index f7346149395cd8..1b5f836df601ef 100644
--- a/Examples/UIExplorer/WebViewExample.js
+++ b/Examples/UIExplorer/WebViewExample.js
@@ -20,6 +20,7 @@ var {
   StyleSheet,
   Text,
   TextInput,
+  TouchableWithoutFeedback,
   TouchableOpacity,
   View,
   WebView
@@ -160,6 +161,60 @@ var WebViewExample = React.createClass({
 
 });
 
+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,
@@ -229,6 +284,21 @@ 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 = `
@@ -267,6 +337,10 @@ exports.examples = [
     title: 'Simple Browser',
     render(): ReactElement { return <WebViewExample />; }
   },
+  {
+    title: 'Scale Page to Fit',
+    render(): ReactElement { return <ScaledWebView/>; }
+  },
   {
     title: 'Bundled HTML',
     render(): ReactElement {
diff --git a/Examples/UIExplorer/XHRExample.ios.js b/Examples/UIExplorer/XHRExample.ios.js
index 5d909b3cb7e2ef..a9de630e2c2dde 100644
--- a/Examples/UIExplorer/XHRExample.ios.js
+++ b/Examples/UIExplorer/XHRExample.ios.js
@@ -33,6 +33,7 @@ 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;
diff --git a/Examples/UIExplorer/XHRExampleCookies.js b/Examples/UIExplorer/XHRExampleCookies.js
index 310accc19b6010..d4cfad0e5f1489 100644
--- a/Examples/UIExplorer/XHRExampleCookies.js
+++ b/Examples/UIExplorer/XHRExampleCookies.js
@@ -26,6 +26,9 @@ var {
 var RCTNetworking = require('RCTNetworking');
 
 class XHRExampleCookies extends React.Component {
+  state: any;
+  cancelled: boolean;
+
   constructor(props: any) {
     super(props);
     this.cancelled = false;
diff --git a/Examples/UIExplorer/XHRExampleFetch.js b/Examples/UIExplorer/XHRExampleFetch.js
index 893dd75912012d..87e8e1bb6465b9 100644
--- a/Examples/UIExplorer/XHRExampleFetch.js
+++ b/Examples/UIExplorer/XHRExampleFetch.js
@@ -26,6 +26,8 @@ var {
 
 
 class XHRExampleFetch extends React.Component {
+  state: any;
+  responseURL: ?string;
 
   constructor(props: any) {
     super(props);
diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
index abb275d7055dc0..c1e8475153eef1 100644
--- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
+++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml
@@ -5,6 +5,8 @@
   <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 c1dd73f6d52487..f9a50e93f44b44 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/createExamplePage.js b/Examples/UIExplorer/createExamplePage.js
index 55ebd159961dd5..3d1d57898c6ad3 100644
--- a/Examples/UIExplorer/createExamplePage.js
+++ b/Examples/UIExplorer/createExamplePage.js
@@ -24,12 +24,12 @@ 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';
 
 var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
-  : ReactClass<any, any, any> {
+  : ReactClass<any> {
   invariant(!!exampleModule.examples, 'The module must have examples');
 
   var ExamplePage = React.createClass({
@@ -50,6 +50,7 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
       // 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;
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000000000..5e2ddd3107c8d1
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,21 @@
+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.
+
+Do the checklist before filing an issue:
+
+- [ ] Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
+- [ ] Have a usage question? Ask your question on [StackOverflow](http://stackoverflow.com/questions/tagged/react-native). We use StackOverflow for usage question and GitHub for bugs.
+- [ ] Have an idea for a feature? Post the feature request on [Product Pains](https://productpains.com/product/react-native/). It has a voting system to surface the important issues. GitHub issues should only be used for bugs.
+
+
+None of the above, create a bug report
+------------------------------------------------------------------
+
+Make sure to add **all the information needed to understand the bug** 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.
+
+- [ ] Provide a **minimal code snippet** / [rnplay](https://rnplay.org/) example that reproduces the bug.
+- [ ] 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/IntegrationTestHarnessTest.js b/IntegrationTests/IntegrationTestHarnessTest.js
index 27ef7428dc4b8c..bfc9c5d88bae08 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/LoggingTestModule.js b/IntegrationTests/LoggingTestModule.js
index 5d3073a377159a..cb801a0af088e2 100644
--- a/IntegrationTests/LoggingTestModule.js
+++ b/IntegrationTests/LoggingTestModule.js
@@ -12,8 +12,8 @@
 
 var BatchedBridge = require('BatchedBridge');
 
-var warning = require('warning');
-var invariant = require('invariant');
+var warning = require('fbjs/lib/warning');
+var invariant = require('fbjs/lib/invariant');
 
 var LoggingTestModule = {
   logToConsole: function(str) {
diff --git a/IntegrationTests/SimpleSnapshotTest.js b/IntegrationTests/SimpleSnapshotTest.js
index 2f0e93358a572e..181077acd17585 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/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js
index c63618441bb0aa..a6f5dc2342af8a 100644
--- a/Libraries/ActionSheetIOS/ActionSheetIOS.js
+++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js
@@ -13,7 +13,7 @@
 
 var RCTActionSheetManager = require('NativeModules').ActionSheetManager;
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var processColor = require('processColor');
 
 var ActionSheetIOS = {
@@ -31,7 +31,18 @@ var ActionSheetIOS = {
       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,
diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
index ab1a8c8d05be71..18fc8596b115b5 100644
--- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m
+++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m
@@ -156,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");
diff --git a/Libraries/Animated/examples/pic1.jpg b/Libraries/Animated/examples/pic1.jpg
new file mode 100644
index 00000000000000..a9f2e824fe6903
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 00000000000000..34d30966ae9868
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 00000000000000..47a97c80180407
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 3d70feef8b387e..ba5208a5a8517d 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';
 
@@ -130,6 +130,7 @@ function _flush(rootNode: AnimatedValue): void {
     }
   }
   findAnimatedStyles(rootNode);
+  /* $FlowFixMe */
   animatedStyles.forEach(animatedStyle => animatedStyle.update());
 }
 
@@ -844,10 +845,10 @@ class AnimatedAddition extends AnimatedWithChildren {
   _a: Animated;
   _b: Animated;
 
-  constructor(a: Animated, b: Animated) {
+  constructor(a: Animated | number, b: Animated | number) {
     super();
-    this._a = a;
-    this._b = b;
+    this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
+    this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
   }
 
   __getValue(): number {
@@ -873,10 +874,10 @@ class AnimatedMultiplication extends AnimatedWithChildren {
   _a: Animated;
   _b: Animated;
 
-  constructor(a: Animated, b: Animated) {
+  constructor(a: Animated | number, b: Animated | number) {
     super();
-    this._a = a;
-    this._b = b;
+    this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
+    this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
   }
 
   __getValue(): number {
@@ -898,6 +899,33 @@ class AnimatedMultiplication extends AnimatedWithChildren {
   }
 }
 
+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>;
 
@@ -1233,6 +1261,14 @@ var multiply = function(
   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,
@@ -1556,7 +1592,7 @@ 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 performance way, independent of the normal JavaScript
@@ -1604,6 +1640,12 @@ module.exports = {
    */
   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.
    */
diff --git a/Libraries/Animated/src/Interpolation.js b/Libraries/Animated/src/Interpolation.js
index b97d063b949a45..50f77ed7102da1 100644
--- a/Libraries/Animated/src/Interpolation.js
+++ b/Libraries/Animated/src/Interpolation.js
@@ -12,17 +12,9 @@
 /* eslint no-bitwise: 0 */
 'use strict';
 
+var invariant = require('fbjs/lib/invariant');
 var normalizeColor = require('normalizeColor');
 
-// 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;
-  }
-};
-
 type ExtrapolateType = 'extend' | 'identity' | 'clamp';
 
 export type InterpolationConfigType = {
diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js
index 57bafd2da85ad4..ab533b3acfb141 100644
--- a/Libraries/AppRegistry/AppRegistry.js
+++ b/Libraries/AppRegistry/AppRegistry.js
@@ -14,7 +14,7 @@
 var BatchedBridge = require('BatchedBridge');
 var ReactNative = require('ReactNative');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 var renderApplication = require('renderApplication');
 
 if (__DEV__) {
@@ -25,7 +25,7 @@ if (__DEV__) {
 
 var runnables = {};
 
-type ComponentProvider = () => ReactClass<any, any, any>;
+type ComponentProvider = () => ReactClass<any>;
 
 type AppConfig = {
   appKey: string;
diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js
index ed06c9203b3232..8f9a7cf4be9aba 100644
--- a/Libraries/AppState/AppState.js
+++ b/Libraries/AppState/AppState.js
@@ -17,7 +17,7 @@ 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/AppStateIOS/AppStateIOS.android.js b/Libraries/AppStateIOS/AppStateIOS.android.js
index 0f59cbea06cf9a..51117413856223 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 d482b4e7d7b14c..807f7701e7f8f6 100644
--- a/Libraries/AppStateIOS/AppStateIOS.ios.js
+++ b/Libraries/AppStateIOS/AppStateIOS.ios.js
@@ -16,7 +16,7 @@ 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(),
@@ -35,8 +35,9 @@ var _eventHandlers = {
  *  - `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.
+ *  - `inactive` - This is a state that occurs when transitioning between
+ *  	 foreground & background, and during periods of inactivity such as
+ *  	 entering the Multitasking view or in the event of an incoming call
  *
  * For more information, see
  * [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js
index 985fc2c74cbaba..8f9cc1bc6186ad 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',
diff --git a/Libraries/CameraRoll/RCTCameraRollManager.m b/Libraries/CameraRoll/RCTCameraRollManager.m
index fc97c9493b4af8..d060224ccc7afa 100644
--- a/Libraries/CameraRoll/RCTCameraRollManager.m
+++ b/Libraries/CameraRoll/RCTCameraRollManager.m
@@ -98,7 +98,7 @@ @implementation RCTCameraRollManager
           RCTLogWarn(@"Error saving cropped image: %@", saveError);
           reject(RCTErrorUnableToSave, nil, saveError);
         } else {
-          resolve(@[assetURL.absoluteString]);
+          resolve(assetURL.absoluteString);
         }
       }];
     });
@@ -110,22 +110,22 @@ static void RCTResolvePromise(RCTPromiseResolveBlock resolve,
                               BOOL hasNextPage)
 {
   if (!assets.count) {
-    resolve(@[@{
+    resolve(@{
       @"edges": assets,
       @"page_info": @{
         @"has_next_page": @NO,
       }
-    }]);
+    });
     return;
   }
-  resolve(@[@{
+  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
diff --git a/Libraries/Components/Clipboard/Clipboard.js b/Libraries/Components/Clipboard/Clipboard.js
index b09bc5b2f7b91b..1a1e157238a873 100644
--- a/Libraries/Components/Clipboard/Clipboard.js
+++ b/Libraries/Components/Clipboard/Clipboard.js
@@ -7,10 +7,12 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule Clipboard
+ * @flow
  */
 'use strict';
 
-var Clipboard = require('NativeModules').Clipboard;
+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
@@ -24,14 +26,13 @@ module.exports = {
    * }
    * ```
    */
-  getString() {
-    if (arguments.length > 0) {
-      let callback = arguments[0];
-      console.warn('Clipboard.getString(callback) is deprecated. Use the returned Promise instead');
-      Clipboard.getString().then(callback);
-      return;
-    }
-    return 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
@@ -42,7 +43,7 @@ module.exports = {
    * ```
    * @param the content to be stored in the clipboard.
    */
-  setString(content) {
+  setString(content: string) {
     Clipboard.setString(content);
   }
 };
diff --git a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js
index d446544c6aec54..64ae4a506a754b 100644
--- a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js
+++ b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.android.js
@@ -52,7 +52,7 @@ class DatePickerAndroid {
    *   * `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
diff --git a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js
index b0ab643fa23d45..4a6d99423f3d93 100644
--- a/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js
+++ b/Libraries/Components/DatePickerAndroid/DatePickerAndroid.ios.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 const DatePickerAndroid = {
   async open(options: Object): Promise<Object> {
@@ -19,6 +19,6 @@ const DatePickerAndroid = {
       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 4383f9f84d25fa..2f4410d523c62b 100644
--- a/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js
+++ b/Libraries/Components/DrawerAndroid/DrawerLayoutAndroid.android.js
@@ -28,6 +28,7 @@ var INNERVIEW_REF = 'innerView';
 var DrawerLayoutValidAttributes = {
   drawerWidth: true,
   drawerPosition: true,
+  drawerLockMode: true
 };
 
 var DRAWER_STATES = [
@@ -95,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.
      */
@@ -142,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}
diff --git a/Libraries/Components/Intent/IntentAndroid.android.js b/Libraries/Components/Intent/IntentAndroid.android.js
index 48877dd6b1eef0..0e563d2860d6eb 100644
--- a/Libraries/Components/Intent/IntentAndroid.android.js
+++ b/Libraries/Components/Intent/IntentAndroid.android.js
@@ -12,7 +12,7 @@
 'use strict';
 
 var Linking = require('Linking');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 /**
  * NOTE: `IntentAndroid` is being deprecated. Use `Linking` instead.
diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js
index 13ee06ee8618d8..e78a6d048f3d07 100644
--- a/Libraries/Components/MapView/MapView.js
+++ b/Libraries/Components/MapView/MapView.js
@@ -16,8 +16,6 @@ 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');
@@ -310,6 +308,21 @@ const MapView = React.createClass({
     active: React.PropTypes.bool,
   },
 
+  statics: {
+    /**
+     * 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.
+     */
+    PinColors: {
+      RED: '#ff3b30',
+      GREEN: '#4cd964',
+      PURPLE: '#c969e0',
+    },
+  },
+
   render: function() {
     let children = [], {annotations, overlays, followUserLocation} = this.props;
     annotations = annotations && annotations.map((annotation: Object) => {
@@ -491,20 +504,6 @@ const styles = StyleSheet.create({
   },
 });
 
-/**
- * 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,
diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js
index ea5b395a3cf165..c4a2405258df38 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');
diff --git a/Libraries/Components/Picker/PickerAndroid.android.js b/Libraries/Components/Picker/PickerAndroid.android.js
index 2e95b32f1a6a53..797653d001df98 100644
--- a/Libraries/Components/Picker/PickerAndroid.android.js
+++ b/Libraries/Components/Picker/PickerAndroid.android.js
@@ -100,9 +100,9 @@ var PickerAndroid = React.createClass({
       var position = event.nativeEvent.position;
       if (position >= 0) {
         var value = this.props.children[position].props.value;
-        this.props.onValueChange(value);
+        this.props.onValueChange(value, position);
       } else {
-        this.props.onValueChange(null);
+        this.props.onValueChange(null, position);
       }
     }
 
@@ -134,7 +134,8 @@ var cfg = {
     items: true,
     selected: true,
   }
-}
+};
+
 var DropdownPicker = requireNativeComponent('AndroidDropdownPicker', PickerAndroid, cfg);
 var DialogPicker = requireNativeComponent('AndroidDialogPicker', PickerAndroid, cfg);
 
diff --git a/Libraries/Components/RefreshControl/RefreshControl.js b/Libraries/Components/RefreshControl/RefreshControl.js
index 1ae8119a1e2e0f..ee285a3c752bee 100644
--- a/Libraries/Components/RefreshControl/RefreshControl.js
+++ b/Libraries/Components/RefreshControl/RefreshControl.js
@@ -7,6 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule RefreshControl
+ * @flow
  */
 'use strict';
 
@@ -17,10 +18,10 @@ const View = require('View');
 
 const requireNativeComponent = require('requireNativeComponent');
 
-if (Platform.OS === 'ios') {
-  var RefreshLayoutConsts = {SIZE: {}};
-} else if (Platform.OS === 'android') {
+if (Platform.OS === 'android') {
   var RefreshLayoutConsts = require('NativeModules').UIManager.AndroidSwipeRefreshLayout.Constants;
+} else {
+  var RefreshLayoutConsts = {SIZE: {}};
 }
 
 /**
diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js
index 52c6c946b862f0..f2372fdccb08ea 100644
--- a/Libraries/Components/ScrollResponder.js
+++ b/Libraries/Components/ScrollResponder.js
@@ -21,8 +21,8 @@ var UIManager = require('UIManager');
 
 var { ScrollViewManager } = require('NativeModules');
 
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
 
 import type ReactComponent from 'ReactComponent';
 
@@ -391,7 +391,7 @@ var ScrollResponderMixin = {
    */
   scrollResponderScrollWithoutAnimationTo: function(offsetX: number, offsetY: number) {
     console.warn('`scrollResponderScrollWithoutAnimationTo` is deprecated. Use `scrollResponderScrollTo` instead');
-    this.scrollResponderScrollTo(offsetX, offsetY, false);
+    this.scrollResponderScrollTo({x: offsetX, y: offsetY, animated: false});
   },
 
   /**
@@ -461,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/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js
index cb07b740918d7f..d3b375a6cf5b87 100644
--- a/Libraries/Components/ScrollView/ScrollView.js
+++ b/Libraries/Components/ScrollView/ScrollView.js
@@ -29,7 +29,7 @@ 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');
@@ -134,8 +134,8 @@ var ScrollView = React.createClass({
      * shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
      * for `UIScrollViewDecelerationRateNormal` and
      * `UIScrollViewDecelerationRateFast` respectively.
-     *   - Normal: 0.998 (the default)
-     *   - Fast: 0.9
+     *   - normal: 0.998 (the default)
+     *   - fast: 0.99
      * @platform ios
      */
     decelerationRate: PropTypes.oneOfType([
@@ -315,7 +315,7 @@ var ScrollView = React.createClass({
      * A RefreshControl component, used to provide pull-to-refresh
      * functionality for the ScrollView.
      *
-     * See [RefreshControl](http://facebook.github.io/react-native/docs/refreshcontrol.html).
+     * See [RefreshControl](docs/refreshcontrol.html).
      */
     refreshControl: PropTypes.element,
 
diff --git a/Libraries/Components/ScrollView/processDecelerationRate.js b/Libraries/Components/ScrollView/processDecelerationRate.js
index 82be66cef4a6da..71955a025bcfef 100644
--- a/Libraries/Components/ScrollView/processDecelerationRate.js
+++ b/Libraries/Components/ScrollView/processDecelerationRate.js
@@ -10,18 +10,11 @@
  */
 '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;
-    }
+  if (decelerationRate === 'normal') {
+    decelerationRate = 0.998;
+  } else if (decelerationRate === 'fast') {
+    decelerationRate = 0.99;
   }
   return decelerationRate;
 }
diff --git a/Libraries/Components/StatusBar/StatusBar.js b/Libraries/Components/StatusBar/StatusBar.js
index b6571146af68a5..159577b585f113 100644
--- a/Libraries/Components/StatusBar/StatusBar.js
+++ b/Libraries/Components/StatusBar/StatusBar.js
@@ -19,6 +19,17 @@ 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;
 };
@@ -26,16 +37,10 @@ type DefaultProps = {
 /**
  * Merges the prop stack with the default values.
  */
-function mergePropsStack(propsStack: Array<Object>): Object {
+function mergePropsStack(propsStack: Array<Object>, defaultValues: Object): Object {
   return propsStack.reduce((prev, cur) => {
     return Object.assign(prev, cur);
-  }, {
-    backgroundColor: 'black',
-    barStyle: 'default',
-    translucent: false,
-    hidden: false,
-    networkActivityIndicatorVisible: false,
-  });
+  }, defaultValues);
 }
 
 /**
@@ -64,10 +69,75 @@ function mergePropsStack(propsStack: Array<Object>): Object {
  *    />
  *  </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: {
@@ -156,7 +226,7 @@ const StatusBar = React.createClass({
    * Updates the native status bar with the props from the stack.
    */
   _updatePropsStack() {
-    const mergedProps = mergePropsStack(StatusBar._propsStack);
+    const mergedProps = mergePropsStack(StatusBar._propsStack, StatusBar._defaultProps);
 
     if (Platform.OS === 'ios') {
       if (mergedProps.barStyle !== undefined) {
diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js
index 59369ef143dd37..a4c0357b0cf0ba 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 9816db385c92d2..273139e7160ff4 100644
--- a/Libraries/Components/Switch/Switch.js
+++ b/Libraries/Components/Switch/Switch.js
@@ -44,7 +44,7 @@ var Switch = React.createClass({
      * Used to locate this view in end-to-end tests.
      */
     testID: React.PropTypes.string,
-    
+
     /**
      * Background color when the switch is turned off.
      * @platform ios
diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js
index 7a902576c7ef3e..6d6678d6b022f5 100644
--- a/Libraries/Components/TextInput/TextInput.js
+++ b/Libraries/Components/TextInput/TextInput.js
@@ -27,8 +27,8 @@ 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 = {
@@ -68,7 +68,17 @@ type Event = Object;
  *   />
  * ```
  *
- * Note that some props are only available with `multiline={true/false}`:
+ * Note that some props are only available with `multiline={true/false}`.
+ * Additionally, border styles that apply to only one side of the element
+ * (e.g., `borderBottomColor`, `borderLeftWidth`, etc.) will not be applied if
+ * `multiline=false`. To achieve the same effect, you can wrap your `TextInput`
+ * in a `View`:
+ *
+ * ```
+ *  <View style={{ borderBottomColor: '#000000', borderBottomWidth: 1, }}>
+ *    <TextInput {...props} />
+ *  </View>
+ * ```
  */
 var TextInput = React.createClass({
   statics: {
@@ -189,6 +199,10 @@ var TextInput = React.createClass({
      * Callback that is called when the text input's text changes.
      */
     onChange: PropTypes.func,
+    /**
+     * Callback that is called when the text input's content size changes.
+     */
+    onChangeContentSize: PropTypes.func,
     /**
      * Callback that is called when the text input's text changes.
      * Changed text is passed as an argument to the callback handler.
@@ -283,7 +297,6 @@ var TextInput = React.createClass({
      * 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.
-     * @platform ios
      */
     blurOnSubmit: PropTypes.bool,
     /**
@@ -474,6 +487,13 @@ var TextInput = React.createClass({
       };
     }
 
+    var onChangeContentSize;
+    if (this.props.onChangeContentSize) {
+      onChangeContentSize = (event: Event) => {
+        this.props.onChangeContentSize(event);
+      }
+    }
+
     var autoCapitalize =
       UIManager.AndroidTextInput.Constants.AutoCapitalizationType[this.props.autoCapitalize];
     var children = this.props.children;
@@ -501,10 +521,12 @@ var TextInput = React.createClass({
         onFocus={this._onFocus}
         onBlur={this._onBlur}
         onChange={this._onChange}
+        onChangeContentSize={onChangeContentSize}
         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}
diff --git a/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js
index 98bf034a8e0231..14480040ca5413 100644
--- a/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js
+++ b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js
@@ -62,6 +62,6 @@ class TimePickerAndroid {
    * 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
index df993ba92275a9..0d7ceee4b64b9e 100644
--- a/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js
+++ b/Libraries/Components/TimePickerAndroid/TimePickerAndroid.ios.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 const TimePickerAndroid = {
   async open(options: Object): Promise<Object> {
@@ -19,6 +19,6 @@ const TimePickerAndroid = {
       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 8eb79dfb0dff77..4fc912777abb2f 100644
--- a/Libraries/Components/ToastAndroid/ToastAndroid.ios.js
+++ b/Libraries/Components/ToastAndroid/ToastAndroid.ios.js
@@ -11,7 +11,7 @@
  */
 'use strict';
 
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var ToastAndroid = {
 
diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js
index 9f457c5c043a2f..4f5000c5ce88a7 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');
 
 /**
@@ -337,7 +337,7 @@ var TouchableMixin = {
    * Must return true to start the process of `Touchable`.
    */
   touchableHandleStartShouldSetResponder: function() {
-    return true;
+    return !this.props.disabled;
   },
 
   /**
@@ -432,6 +432,16 @@ var TouchableMixin = {
     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;
diff --git a/Libraries/Components/Touchable/TouchableBounce.js b/Libraries/Components/Touchable/TouchableBounce.js
index 1d7abcc71e8397..5ea367ceba5905 100644
--- a/Libraries/Components/Touchable/TouchableBounce.js
+++ b/Libraries/Components/Touchable/TouchableBounce.js
@@ -54,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 {
@@ -108,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;
   },
@@ -121,6 +134,7 @@ var TouchableBounce = React.createClass({
         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 4c7324d6f6e72c..a15501bebba8e2 100644
--- a/Libraries/Components/Touchable/TouchableHighlight.js
+++ b/Libraries/Components/Touchable/TouchableHighlight.js
@@ -25,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');
 
@@ -176,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;
   },
@@ -189,7 +193,7 @@ var TouchableHighlight = React.createClass({
   },
 
   _showUnderlay: function() {
-    if (!this.isMounted()) {
+    if (!this.isMounted() || !this._hasPressHandler()) {
       return;
     }
 
@@ -201,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,
@@ -211,6 +215,15 @@ var TouchableHighlight = React.createClass({
     }
   },
 
+  _hasPressHandler: function() {
+    return !!(
+      this.props.onPress ||
+      this.props.onPressIn ||
+      this.props.onPressOut ||
+      this.props.onLongPress
+    );
+  },
+
   render: function() {
     return (
       <View
@@ -221,6 +234,7 @@ var TouchableHighlight = React.createClass({
         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 19439d4e5ff0ba..37b010e25ff09d 100644
--- a/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
+++ b/Libraries/Components/Touchable/TouchableNativeFeedback.android.js
@@ -44,6 +44,8 @@ var TouchableView = requireNativeComponent('RCTView', null, {
   }
 });
 
+type Event = Object;
+
 var PRESS_RETENTION_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
 
 /**
@@ -138,23 +140,23 @@ var TouchableNativeFeedback = React.createClass({
    * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are
    * defined on your component.
    */
-  touchableHandleActivePressIn: function() {
-    this.props.onPressIn && this.props.onPressIn();
+  touchableHandleActivePressIn: function(e: Event) {
+    this.props.onPressIn && this.props.onPressIn(e);
     this._dispatchPressedStateChange(true);
     this._dispatchHotspotUpdate(this.pressInLocation.locationX, this.pressInLocation.locationY);
   },
 
-  touchableHandleActivePressOut: function() {
-    this.props.onPressOut && this.props.onPressOut();
+  touchableHandleActivePressOut: function(e: Event) {
+    this.props.onPressOut && this.props.onPressOut(e);
     this._dispatchPressedStateChange(false);
   },
 
-  touchableHandlePress: function() {
-    this.props.onPress && this.props.onPress();
+  touchableHandlePress: function(e: Event) {
+    this.props.onPress && this.props.onPress(e);
   },
 
-  touchableHandleLongPress: function() {
-    this.props.onLongPress && this.props.onLongPress();
+  touchableHandleLongPress: function(e: Event) {
+    this.props.onLongPress && this.props.onLongPress(e);
   },
 
   touchableGetPressRectOffset: function() {
@@ -162,6 +164,10 @@ var TouchableNativeFeedback = React.createClass({
     return this.props.pressRetentionOffset || PRESS_RETENTION_OFFSET;
   },
 
+  touchableGetHitSlop: function() {
+    return this.props.hitSlop;
+  },
+
   touchableGetHighlightDelayMS: function() {
     return this.props.delayPressIn;
   },
@@ -205,6 +211,7 @@ var TouchableNativeFeedback = React.createClass({
       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 2cd0abefb4705e..29ceda28947ea1 100644
--- a/Libraries/Components/Touchable/TouchableOpacity.js
+++ b/Libraries/Components/Touchable/TouchableOpacity.js
@@ -124,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;
   },
@@ -160,6 +164,7 @@ var TouchableOpacity = React.createClass({
         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 5a578a205a7c93..d48e15856303fe 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,7 +29,7 @@ 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.
@@ -44,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).
@@ -80,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() {
@@ -118,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;
   },
@@ -140,6 +157,7 @@ var TouchableWithoutFeedback = React.createClass({
       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 944093f78e13f1..c66735b508b16c 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 4c6525a54130ca..d2f6a09e8c89ed 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/View/ReactNativeStyleAttributes.js b/Libraries/Components/View/ReactNativeStyleAttributes.js
index b9e909a17ecf82..1e91b1ef265f15 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');
diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index a7f31f9a0ac820..20b9d9288344e3 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -11,6 +11,7 @@
  */
 'use strict';
 
+const EdgeInsetsPropType = require('EdgeInsetsPropType');
 const NativeMethodsMixin = require('NativeMethodsMixin');
 const PropTypes = require('ReactPropTypes');
 const React = require('React');
@@ -53,7 +54,7 @@ const AccessibilityComponentType = [
 
 const forceTouchAvailable = (UIManager.RCTView.Constants &&
   UIManager.RCTView.Constants.forceTouchAvailable) || false;
-  
+
 const statics = {
   AccessibilityTraits,
   AccessibilityComponentType,
@@ -201,6 +202,19 @@ const 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
      *
diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js
index 44ad8a9257216d..ebf06456671dd7 100644
--- a/Libraries/Components/WebView/WebView.android.js
+++ b/Libraries/Components/WebView/WebView.android.js
@@ -18,7 +18,7 @@ var UIManager = require('UIManager');
 var View = require('View');
 
 var deprecatedPropType = require('deprecatedPropType');
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 var merge = require('merge');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
@@ -122,6 +122,11 @@ var WebView = React.createClass({
      */
     injectedJavaScript: PropTypes.string,
 
+    /**
+     * 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.
@@ -142,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});
@@ -170,23 +182,13 @@ var WebView = React.createClass({
       webViewStyles.push(styles.hidden);
     }
 
-    var {javaScriptEnabled, domStorageEnabled} = this.props;
-    if (this.props.javaScriptEnabledAndroid) {
-      console.warn('javaScriptEnabledAndroid is deprecated. Use javaScriptEnabled instead');
-      javaScriptEnabled = this.props.javaScriptEnabledAndroid;
-    }
-    if (this.props.domStorageEnabledAndroid) {
-      console.warn('domStorageEnabledAndroid is deprecated. Use domStorageEnabled instead');
-      domStorageEnabled = this.props.domStorageEnabledAndroid;
-    }
-
     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) {
@@ -199,10 +201,11 @@ var WebView = React.createClass({
         key="webViewKey"
         style={webViewStyles}
         source={resolveAssetSource(source)}
+        scalesPageToFit={this.props.scalesPageToFit}
         injectedJavaScript={this.props.injectedJavaScript}
         userAgent={this.props.userAgent}
-        javaScriptEnabled={javaScriptEnabled}
-        domStorageEnabled={domStorageEnabled}
+        javaScriptEnabled={this.props.javaScriptEnabled}
+        domStorageEnabled={this.props.domStorageEnabled}
         contentInset={this.props.contentInset}
         automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
         onLoadingStart={this.onLoadingStart}
diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js
index 7714d383df8718..653bff61e8dcd6 100644
--- a/Libraries/Components/WebView/WebView.ios.js
+++ b/Libraries/Components/WebView/WebView.ios.js
@@ -18,11 +18,11 @@ var StyleSheet = require('StyleSheet');
 var Text = require('Text');
 var UIManager = require('UIManager');
 var View = require('View');
-var ScrollView = require('ScrollView')
+var ScrollView = require('ScrollView');
 
 var deprecatedPropType = require('deprecatedPropType');
-var invariant = require('invariant');
-var keyMirror = require('keyMirror');
+var invariant = require('fbjs/lib/invariant');
+var keyMirror = require('fbjs/lib/keyMirror');
 var processDecelerationRate = require('processDecelerationRate');
 var requireNativeComponent = require('requireNativeComponent');
 var resolveAssetSource = require('resolveAssetSource');
@@ -39,16 +39,16 @@ var WebViewState = keyMirror({
   ERROR: null,
 });
 
-var NavigationType = {
-  click: RCTWebViewManager.NavigationType.LinkClicked,
-  formsubmit: RCTWebViewManager.NavigationType.FormSubmitted,
-  backforward: RCTWebViewManager.NavigationType.BackForward,
-  reload: RCTWebViewManager.NavigationType.Reload,
-  formresubmit: RCTWebViewManager.NavigationType.FormResubmitted,
-  other: RCTWebViewManager.NavigationType.Other,
-};
+const NavigationType = keyMirror({
+  click: true,
+  formsubmit: true,
+  backforward: true,
+  reload: true,
+  formresubmit: true,
+  other: true,
+});
 
-var JSNavigationScheme = RCTWebViewManager.JSNavigationScheme;
+const JSNavigationScheme = 'react-js-navigation';
 
 type ErrorEvent = {
   domain: any;
@@ -179,8 +179,8 @@ var WebView = React.createClass({
      * 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)
+     *   - normal: 0.998
+     *   - fast: 0.99 (the default for iOS WebView)
      * @platform ios
      */
     decelerationRate: ScrollView.propTypes.decelerationRate,
@@ -213,7 +213,6 @@ var WebView = React.createClass({
 
     /**
      * Sets whether the webpage scales to fit the view and the user can change the scale.
-     * @platform ios
      */
     scalesPageToFit: PropTypes.bool,
 
@@ -285,16 +284,6 @@ var WebView = React.createClass({
       RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
     });
 
-    var {javaScriptEnabled, domStorageEnabled} = this.props;
-    if (this.props.javaScriptEnabledAndroid) {
-      console.warn('javaScriptEnabledAndroid is deprecated. Use javaScriptEnabled instead');
-      javaScriptEnabled = this.props.javaScriptEnabledAndroid;
-    }
-    if (this.props.domStorageEnabledAndroid) {
-      console.warn('domStorageEnabledAndroid is deprecated. Use domStorageEnabled instead');
-      domStorageEnabled = this.props.domStorageEnabledAndroid;
-    }
-
     var decelerationRate = processDecelerationRate(this.props.decelerationRate);
 
     var source = this.props.source || {};
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 1a9bef47b3fb33..eb642d73fbecb4 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -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,
     /**
@@ -227,19 +231,23 @@ var ListView = React.createClass({
 
   /**
    * 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] && 
+    return this.refs[SCROLLVIEW_REF] &&
+      this.refs[SCROLLVIEW_REF].getScrollResponder &&
       this.refs[SCROLLVIEW_REF].getScrollResponder();
   },
 
   scrollTo: function(...args) {
-    this.refs[SCROLLVIEW_REF] && 
+    this.refs[SCROLLVIEW_REF] &&
+      this.refs[SCROLLVIEW_REF].scrollTo &&
       this.refs[SCROLLVIEW_REF].scrollTo(...args);
   },
 
   setNativeProps: function(props) {
-    this.refs[SCROLLVIEW_REF] && 
+    this.refs[SCROLLVIEW_REF] &&
       this.refs[SCROLLVIEW_REF].setNativeProps(props);
   },
 
@@ -386,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;
@@ -512,11 +522,7 @@ var ListView = React.createClass({
   },
 
   _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 0c18aa646c0bc7..1d14e50f5b74e2 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,
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js
index 096e99d1c3ea37..258ce5bc8394bb 100644
--- a/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCard.js
@@ -28,142 +28,260 @@
 'use strict';
 
 const Animated = require('Animated');
-const NavigationReducer = require('NavigationReducer');
 const NavigationContainer = require('NavigationContainer');
-const PanResponder = require('PanResponder');
-const Platform = require('Platform');
+const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
+const NavigationPropTypes = require('NavigationPropTypes');
 const React = require('React');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
 const StyleSheet = require('StyleSheet');
 const View = require('View');
 
-const ENABLE_GESTURES = Platform.OS !== 'android';
+const {Directions} = NavigationLinearPanResponder;
+
+import type  {
+  NavigationAnimatedValue,
+  NavigationLayout,
+  NavigationPosition,
+  NavigationSceneRenderer,
+  NavigationSceneRendererProps,
+} from 'NavigationTypeDefinition';
 
 import type {
-  NavigationParentState
-} from 'NavigationState';
-
-type Layout = {
-  initWidth: number,
-  initHeight: number,
-  width: Animated.Value;
-  height: Animated.Value;
+  NavigationGestureDirection
+} from 'NavigationLinearPanResponder';
+
+type State = {
+  hash: string,
+  height: number,
+  width: number,
+};
+
+type Props = NavigationSceneRendererProps & {
+  direction: NavigationGestureDirection,
+  renderScene: NavigationSceneRenderer,
+};
+
+const {PropTypes} = React;
+
+const propTypes = {
+  ...NavigationPropTypes.SceneRenderer,
+  direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
+  renderScene: PropTypes.func.isRequired,
 };
 
-type Props = {
-  navigationState: NavigationParentState;
-  index: number;
-  position: Animated.Value;
-  layout: Layout;
-  onNavigate: Function;
-  children: Object;
+const defaultProps = {
+  direction: Directions.HORIZONTAL,
 };
 
+class AmimatedValueSubscription {
+  _value: NavigationAnimatedValue;
+  _token: string;
+
+  constructor(value: NavigationAnimatedValue, 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(): NavigationGestureDirection {
+    return this._props.direction;
+  }
+
+  getIndex(): number {
+    return this._props.navigationState.index;
+  }
+
+  getLayout(): NavigationLayout {
+    return this._props.layout;
+  }
+
+  getPosition(): NavigationPosition {
+    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 NavigationCard extends React.Component {
-  _responder: ?Object;
-  _lastHeight: number;
-  _lastWidth: number;
-  _widthListener: string;
-  _heightListener: string;
   props: Props;
-  componentWillMount() {
-    if (ENABLE_GESTURES) {
-      this._enableGestures();
-    }
+  state: State;
+  _calculateState: (t: NavigationLayout) => State;
+  _layoutListeners: Array<AmimatedValueSubscription>;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+
+    this.state = this._calculateState(props.layout);
+    this._layoutListeners = [];
   }
-  _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(NavigationReducer.StackReducer.PopAction());
-          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],
+
+  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+      this,
+      nextProps,
+      nextState
+    );
+  }
+
+  componentWillMount(): void {
+    this._calculateState = this._calculateState.bind(this);
+  }
+
+  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,
+      layout,
+      navigationState,
+      onNavigate,
+      position,
+      scene,
+      scenes,
+    } = this.props;
+
+    const {
+      height,
+      width,
+    } = this.state;
+
+    const index = scene.index;
+    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],
             }),
-            opacity: cardPosition.interpolate({
-              inputRange: [-1,0,1],
-              outputRange: [0,1,1],
+        },
+        {
+          translateY: !isVertical ? 0 :
+            position.interpolate({
+              inputRange,
+              outputRange: [height, 0, -10],
             }),
-          }
-        ]}>
-        {this.props.children}
+        },
+      ],
+    };
+
+    let panHandlers = null;
+    if (navigationState.index === index) {
+      const delegate = new PanResponderDelegate(this.props);
+      const panResponder = new NavigationLinearPanResponder(delegate);
+      panHandlers = panResponder.panHandlers;
+    }
+
+    const sceneProps = {
+      layout,
+      navigationState,
+      onNavigate,
+      position,
+      scene,
+      scenes,
+    };
+
+    return (
+      <Animated.View
+        {...panHandlers}
+        style={[styles.main, animatedStyle]}>
+        {this.props.renderScene(sceneProps)}
       </Animated.View>
     );
   }
+
+  _calculateState(layout: NavigationLayout): 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: NavigationLayout) {
+    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);
+    }
+  }
 }
 
-NavigationCard = NavigationContainer.create(NavigationCard);
+NavigationCard.propTypes = propTypes;
+NavigationCard.defaultProps = defaultProps;
 
 const styles = StyleSheet.create({
-  card: {
+  main: {
     backgroundColor: '#E9E9EF',
+    bottom: 0,
+    left: 0,
+    position: 'absolute',
+    right: 0,
     shadowColor: 'black',
-    shadowOpacity: 0.4,
     shadowOffset: {width: 0, height: 0},
+    shadowOpacity: 0.4,
     shadowRadius: 10,
     top: 0,
-    bottom: 0,
-    position: 'absolute',
   },
 });
 
-module.exports = NavigationCard;
+module.exports = NavigationContainer.create(NavigationCard);
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
new file mode 100644
index 00000000000000..8b6cc316d4fbae
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationCardStack.js
@@ -0,0 +1,146 @@
+/**
+ * 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 NavigationCard = require('NavigationCard');
+const NavigationContainer = require('NavigationContainer');
+const NavigationLinearPanResponder = require('NavigationLinearPanResponder');
+const NavigationPropTypes = require('NavigationPropTypes');
+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 {
+  NavigationAnimatedValue,
+  NavigationAnimationSetter,
+  NavigationParentState,
+  NavigationSceneRenderer,
+  NavigationSceneRendererProps,
+} from 'NavigationTypeDefinition';
+
+import type {
+  NavigationGestureDirection,
+} from 'NavigationLinearPanResponder';
+
+type Props = {
+  direction: NavigationGestureDirection,
+  navigationState: NavigationParentState,
+  renderOverlay: ?NavigationSceneRenderer,
+  renderScene: NavigationSceneRenderer,
+};
+
+const propTypes = {
+  direction: PropTypes.oneOf([Directions.HORIZONTAL, Directions.VERTICAL]),
+  navigationState: NavigationPropTypes.navigationParentState.isRequired,
+  renderOverlay: PropTypes.func,
+  renderScene: PropTypes.func.isRequired,
+};
+
+const defaultProps = {
+  direction: Directions.HORIZONTAL,
+  renderOverlay: emptyFunction.thatReturnsNull,
+};
+
+/**
+ * A controlled navigation view that renders a list of cards.
+ */
+class NavigationCardStack extends React.Component {
+  _applyAnimation: NavigationAnimationSetter;
+  _renderScene : NavigationSceneRenderer;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+  }
+
+  componentWillMount(): void {
+    this._applyAnimation = this._applyAnimation.bind(this);
+    this._renderScene = this._renderScene.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}
+        applyAnimation={this._applyAnimation}
+        style={[styles.animatedView, this.props.style]}
+      />
+    );
+  }
+
+  _renderScene(props: NavigationSceneRendererProps): ReactElement {
+    return (
+      <NavigationCard
+        {...props}
+        direction={this.props.direction}
+        key={'card_' + props.scene.navigationState.key}
+        renderScene={this.props.renderScene}
+      />
+    );
+  }
+
+  _applyAnimation(
+    position: NavigationAnimatedValue,
+    navigationState: NavigationParentState,
+  ): void {
+    Animated.timing(
+      position,
+      {
+        duration: 500,
+        toValue: navigationState.index,
+      }
+    ).start();
+  }
+}
+
+NavigationCardStack.propTypes = propTypes;
+NavigationCardStack.defaultProps = defaultProps;
+
+const styles = StyleSheet.create({
+  animatedView: {
+    flex: 1,
+  },
+});
+
+module.exports = NavigationContainer.create(NavigationCardStack);
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js
index 98a4c5e9140caa..3b1c5aa4dfce4b 100644
--- a/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js
@@ -30,32 +30,40 @@
 const Animated = require('Animated');
 const Image = require('Image');
 const NavigationContainer = require('NavigationContainer');
-const NavigationReducer = require('NavigationReducer');
+const NavigationPropTypes = require('NavigationPropTypes');
+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 {
+import type  {
   NavigationState,
-  NavigationParentState
-} from 'NavigationState';
+  NavigationSceneRendererProps,
+} from 'NavigationTypeDefinition';
 
-type Props = {
-  navigationState: NavigationParentState,
-  onNavigate: Function,
-  position: Animated.Value,
+type Props = NavigationSceneRendererProps & {
   getTitle: (navState: NavigationState) => string,
 };
 
+const {PropTypes} = React;
+
+const NavigationHeaderPropTypes = {
+  ...NavigationPropTypes.SceneRenderer,
+  getTitle: PropTypes.func.isRequired,
+};
+
 class NavigationHeader extends React.Component {
   _handleBackPress: Function;
+
   props: Props;
-  componentWillMount() {
+
+  componentWillMount(): void {
     this._handleBackPress = this._handleBackPress.bind(this);
   }
-  render() {
+
+  render(): ReactElement {
     var state = this.props.navigationState;
     return (
       <Animated.View
@@ -67,7 +75,8 @@ class NavigationHeader extends React.Component {
       </Animated.View>
     );
   }
-  _renderBackButton() {
+
+  _renderBackButton(): ?ReactElement {
     if (this.props.navigationState.index === 0) {
       return null;
     }
@@ -77,7 +86,8 @@ class NavigationHeader extends React.Component {
       </TouchableOpacity>
     );
   }
-  _renderTitle(childState, index) {
+
+  _renderTitle(childState: NavigationState, index:number): ?ReactElement {
     return (
       <Animated.Text
         key={childState.key}
@@ -102,11 +112,14 @@ class NavigationHeader extends React.Component {
       </Animated.Text>
     );
   }
-  _handleBackPress() {
-    this.props.onNavigate(NavigationReducer.StackReducer.PopAction());
+
+  _handleBackPress(): void {
+    this.props.onNavigate(NavigationRootContainer.getBackAction());
   }
 }
 
+NavigationHeader.propTypes = NavigationHeaderPropTypes;
+
 NavigationHeader = NavigationContainer.create(NavigationHeader);
 
 const styles = StyleSheet.create({
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js
new file mode 100644
index 00000000000000..94a97b62548fd8
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigator.js
@@ -0,0 +1,292 @@
+/**
+ * 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';
+
+const NavigationAnimatedView = require('NavigationAnimatedView');
+const NavigationCard = require('NavigationCard');
+const NavigationContext = require('NavigationContext');
+const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
+const NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
+const NavigatorNavigationBar = require('NavigatorNavigationBar');
+const NavigatorSceneConfigs = require('NavigatorSceneConfigs');
+const React = require('react-native');
+const ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
+
+const invariant = require('fbjs/lib/invariant');
+const guid = require('guid');
+
+import type  {
+  NavigationSceneRenderer,
+  NavigationSceneRendererProps,
+} from 'NavigationTypeDefinition';
+
+type State = any;
+
+type Props = {
+  configureScene: any,
+  initialRoute: any,
+  initialRouteStack: any,
+  renderScene: any,
+  style: any,
+};
+
+function getConfigPopDirection(config: any): ?string {
+  if (config && config.gestures && config.gestures.pop) {
+    const direction = config.gestures.pop.direction;
+    return direction ? String(direction) : null;
+  }
+
+  return null;
+}
+
+const RouteStack = NavigationLegacyNavigatorRouteStack;
+
+/**
+ * NavigationLegacyNavigator is meant to replace Navigator seemlessly with
+ * minimum API changes.
+ *
+ * While the APIs remain compatible with Navigator, it is built with good
+ * intention by using the new Navigation API such as
+ * `NavigationAnimatedView`...etc.
+ */
+class NavigationLegacyNavigator extends React.Component<any, Props, State> {
+  static BreadcrumbNavigationBar;
+  static NavigationBar: any;
+  static SceneConfigs: any;
+
+  _renderCard: NavigationSceneRenderer;
+  _renderHeader: NavigationSceneRenderer;
+  _renderScene: NavigationSceneRenderer;
+
+  navigationContext: NavigationContext;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+
+    this.navigationContext = new NavigationContext();
+
+    const stack = this._getInitialRouteStack();
+    this.state = {
+      key: guid(),
+      stack,
+    };
+  }
+
+  jumpTo(route: any): void {
+    const index = this.state.stack.indexOf(route);
+    invariant(
+      index > -1,
+      'Cannot jump to route that is not in the route stack'
+    );
+    this._jumpToIndex(index);
+  }
+
+  jumpForward(): void {
+    this._jumpToIndex(this.state.stack.index + 1);
+  }
+
+  jumpBack(): void {
+    this._jumpToIndex(this.state.stack.index - 1);
+  }
+
+  push(route: any): void {
+    this.setState({stack: this.state.stack.push(route)});
+  }
+
+  pop(): void {
+    const {stack} = this.state;
+    if (stack.size > 1) {
+      this.setState({stack: stack.pop()});
+    }
+  }
+
+  replaceAtIndex(route: any, index: number): void {
+    const {stack} = this.state;
+
+    if (index < 0) {
+      index += stack.size;
+    }
+
+    if (index >= stack.size) {
+      // Nothing to replace.
+      return;
+    }
+
+    this.setState({stack: stack.replaceAtIndex(index, route)});
+  }
+
+  replace(route: any): void {
+    this.replaceAtIndex(route, this.state.stack.index);
+  }
+
+  replacePrevious(route: any): void {
+    this.replaceAtIndex(route, this.state.stack.index - 1);
+  }
+
+  popToTop(): void {
+    this.setState({stack: this.state.stack.slice(0, 1)});
+  }
+
+  popToRoute(route: any): void {
+    const {stack} = this.state;
+    const nextIndex = stack.indexOf(route);
+    invariant(
+      nextIndex > -1,
+      'Calling popToRoute for a route that doesn\'t exist!'
+    );
+    this.setState({stack: stack.slice(0, nextIndex + 1)});
+  }
+
+  replacePreviousAndPop(route: any): void {
+    const {stack} = this.state;
+    const nextIndex = stack.index - 1;
+    if (nextIndex < 0) {
+      return;
+    }
+    this.setState({stack: stack.replaceAtIndex(nextIndex, route).pop()});
+  }
+
+  resetTo(route: any): void {
+    invariant(!!route, 'Must supply route');
+    this.setState({stack: this.state.stack.slice(0).replaceAtIndex(0, route)});
+  }
+
+  immediatelyResetRouteStack(routes: Array<any>): void {
+    const index = routes.length - 1;
+    const stack = new RouteStack(index, routes);
+    this.setState({
+      key: guid(),
+      stack,
+    });
+  }
+
+  getCurrentRoutes(): Array<any> {
+    return this.state.stack.toArray();
+  }
+
+  _jumpToIndex(index: number): void {
+    const {stack} = this.state;
+    if (index < 0 || index >= stack.size) {
+      return;
+    }
+    const nextStack = stack.jumpToIndex(index);
+    this.setState({stack: nextStack});
+  }
+
+  // Lyfe cycle and private methods below.
+
+  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
+    return ReactComponentWithPureRenderMixin.shouldComponentUpdate.call(
+      this,
+      nextProps,
+      nextState
+    );
+  }
+
+  componentWillMount(): void {
+    this._renderCard = this._renderCard.bind(this);
+    this._renderHeader = this._renderHeader.bind(this);
+    this._renderScene = this._renderScene.bind(this);
+  }
+
+  render(): ReactElement {
+    return (
+      <NavigationAnimatedView
+        key={'main_' + this.state.key}
+        navigationState={this.state.stack.toNavigationState()}
+        renderOverlay={this._renderHeader}
+        renderScene={this._renderCard}
+        style={this.props.style}
+      />
+    );
+  }
+
+  _getInitialRouteStack(): RouteStack {
+    const {initialRouteStack, initialRoute} = this.props;
+    const routes = initialRouteStack || [initialRoute];
+    const index = initialRoute ?
+      routes.indexOf(initialRoute) :
+      routes.length - 1;
+    return new RouteStack(index, routes);
+  }
+
+  _renderHeader(props: NavigationSceneRendererProps): ?ReactElement {
+    // TODO(hedger): Render the legacy header.
+    return null;
+  }
+
+  _renderCard(props: NavigationSceneRendererProps): ReactElement {
+    let direction = 'horizontal';
+
+    const {navigationState} = props.scene;
+    const {configureScene} = this.props;
+
+    if (configureScene) {
+      const route = RouteStack.getRouteByNavigationState(navigationState);
+      const config = configureScene(route, this.state.stack.toArray());
+
+      switch (getConfigPopDirection(config)) {
+        case 'left-to-right':
+          direction = 'horizontal';
+          break;
+
+        case 'top-to-bottom':
+          direction = 'vertical';
+          break;
+
+        default:
+          // unsupported config.
+          if (__DEV__) {
+            console.warn('unsupported scene configuration');
+          }
+      }
+    }
+
+    return (
+      <NavigationCard
+        {...props}
+        direction={direction}
+        key={'card_' + navigationState.key}
+        renderScene={this._renderScene}
+      />
+    );
+  }
+
+  _renderScene(props: NavigationSceneRendererProps): ReactElement {
+    const {navigationState} = props.scene;
+    const route = RouteStack.getRouteByNavigationState(navigationState);
+    return this.props.renderScene(route, this);
+  }
+}
+
+// Legacy static members.
+NavigationLegacyNavigator.BreadcrumbNavigationBar = NavigatorBreadcrumbNavigationBar;
+NavigationLegacyNavigator.NavigationBar = NavigatorNavigationBar;
+NavigationLegacyNavigator.SceneConfigs = NavigatorSceneConfigs;
+
+module.exports = NavigationLegacyNavigator;
diff --git a/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js
new file mode 100644
index 00000000000000..302678f3e0f5a7
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/NavigationLegacyNavigatorRouteStack.js
@@ -0,0 +1,309 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule NavigationLegacyNavigatorRouteStack
+ * @flow
+ */
+'use strict';
+
+const invariant = require('fbjs/lib/invariant');
+
+import type {
+  NavigationState,
+  NavigationParentState,
+} from 'NavigationTypeDefinition';
+
+type IterationCallback = (route: any, index: number, key: string) => void;
+
+function isRouteEmpty(route: any): boolean {
+  return (route === undefined || route === null || route === '') || false;
+}
+
+function areRouteNodesEqual(
+  one: Array<RouteNode>,
+  two: Array<RouteNode>,
+): boolean {
+  if (one === two) {
+    return true;
+  }
+
+  if (one.length !== two.length) {
+    return false;
+  }
+  for (let ii = 0, jj = one.length; ii < jj; ii++) {
+    if (one[ii] !== two[ii]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+let _nextRouteNodeID = 0;
+
+/**
+ * Private struct class that holds the key for a route.
+ */
+class RouteNode {
+  key: string;
+  route: any;
+
+  /**
+   * Cast `navigationState` as `RouteNode`.
+   * Also see `RouteNode#toNavigationState`.
+   */
+  static fromNavigationState(navigationState: NavigationState): RouteNode {
+    invariant(
+      navigationState instanceof RouteNode,
+      'navigationState should be an instacne of RouteNode'
+    );
+    return navigationState;
+  }
+
+  constructor(route: any) {
+    // Key value gets bigger incrementally. Developer can compare the
+    // keys of two routes then know which route is added to the stack
+    // earlier.
+    const key = String(_nextRouteNodeID++);
+    if (__DEV__ ) {
+      // Ensure the immutability of the node.
+      Object.defineProperty(this, 'key' , {
+        enumerable: true,
+        configurable: false,
+        writable: false,
+        value: key,
+      });
+      Object.defineProperty(this, 'route' , {
+        enumerable: true,
+        configurable: false,
+        writable: false,
+        value: route,
+      });
+    } else {
+      this.key = key;
+      this.route = route;
+    }
+  }
+
+  toNavigationState(): NavigationState {
+    const state: NavigationState = this;
+    return state;
+  }
+}
+
+let _nextRouteStackID = 0;
+
+/**
+ * The data structure that holds a list of routes and the focused index
+ * of the routes. This data structure is implemented as immutable data
+ * and mutation (e.g. push, pop...etc) will yields a new instance.
+ */
+class RouteStack {
+  _index: number;
+  _key: string;
+  _routeNodes: Array<RouteNode>;
+
+  static getRouteByNavigationState(navigationState: NavigationState): any {
+    return RouteNode.fromNavigationState(navigationState).route;
+  }
+
+  constructor(index: number, routes: Array<any>) {
+    invariant(
+      routes.length > 0,
+      'routes must not be an empty array'
+    );
+
+    invariant(
+      index > -1 && index <= routes.length - 1,
+      'index out of bound'
+    );
+
+
+    let routeNodes;
+    if (routes[0] instanceof RouteNode) {
+      // The array is already an array of <RouteNode>.
+      routeNodes = routes;
+    } else {
+      // Wrap the route with <RouteNode>.
+      routeNodes = routes.map((route) => {
+        invariant(!isRouteEmpty(route), 'route must not be mepty');
+        return new RouteNode(route);
+      });
+    }
+
+    this._routeNodes = routeNodes;
+    this._index = index;
+    this._key = String(_nextRouteStackID++);
+  }
+
+  /* $FlowFixMe - get/set properties not yet supported */
+  get size(): number {
+    return this._routeNodes.length;
+  }
+
+  /* $FlowFixMe - get/set properties not yet supported */
+  get index(): number {
+    return this._index;
+  }
+
+  // Export as...
+  toArray(): Array<any> {
+    return this._routeNodes.map(node => node.route);
+  }
+
+  toNavigationState(): NavigationParentState {
+    return {
+      index: this._index,
+      key: this._key,
+      children: this._routeNodes.map(node => node.toNavigationState()),
+    };
+  }
+
+  get(index: number): any {
+    if (index < 0 || index > this._routeNodes.length - 1) {
+      return null;
+    }
+    return this._routeNodes[index].route;
+  }
+
+  /**
+   * Returns the key associated with the route.
+   * When a route is added to a stack, the stack creates a key for this route.
+   * The key will persist until the initial stack and its derived stack
+   * no longer contains this route.
+   */
+  keyOf(route: any): ?string {
+    if (isRouteEmpty(route)) {
+      return null;
+    }
+    const index = this.indexOf(route);
+    return index > -1 ?
+      this._routeNodes[index].key :
+      null;
+  }
+
+  indexOf(route: any): number {
+    if (isRouteEmpty(route)) {
+      return -1;
+    }
+
+    for (let ii = 0, jj = this._routeNodes.length; ii < jj; ii++) {
+      let node = this._routeNodes[ii];
+      if (node.route === route) {
+        return ii;
+      }
+    }
+
+    return -1;
+  }
+
+  slice(begin: ?number, end: ?number): RouteStack {
+    // check `begin` and `end` first to keep @flow happy.
+    const routeNodes = (end === undefined || end === null) ?
+      this._routeNodes.slice(begin || 0) :
+      this._routeNodes.slice(begin || 0, end || 0);
+
+    const index = Math.min(this._index, routeNodes.length - 1);
+    return this._update(index, routeNodes);
+  }
+
+  /**
+   * Returns a new stack with the provided route appended,
+   * starting at this stack size.
+   */
+  push(route: any): RouteStack {
+
+    invariant(
+      !isRouteEmpty(route),
+      'Must supply route to push'
+    );
+
+    invariant(this.indexOf(route) === -1, 'route must be unique');
+
+    // When pushing, removes the rest of the routes past the current index.
+    const routeNodes = this._routeNodes.slice(0, this._index + 1);
+    routeNodes.push(new RouteNode(route));
+    return this._update(routeNodes.length - 1, routeNodes);
+  }
+
+  /**
+   * Returns a new stack a size ones less than this stack,
+   * excluding the last index in this stack.
+   */
+  pop(): RouteStack {
+    invariant(
+      this._routeNodes.length > 1,
+      'should not pop routeNodes stack to empty'
+    );
+
+    // When popping, removes the rest of the routes past the current index.
+    const routeNodes = this._routeNodes.slice(0, this._index);
+    return this._update(routeNodes.length - 1, routeNodes);
+  }
+
+  jumpToIndex(index: number): RouteStack {
+    invariant(
+      index > -1 && index < this._routeNodes.length,
+      'jumpToIndex: index out of bound'
+    );
+
+    return this._update(index, this._routeNodes);
+  }
+
+  /**
+   * Replace a route in the navigation stack.
+   *
+   * `index` specifies the route in the stack that should be replaced.
+   * If it's negative, it counts from the back.
+   */
+  replaceAtIndex(index: number, route: any): RouteStack {
+    invariant(
+      !isRouteEmpty(route),
+      'Must supply route to replace'
+    );
+
+    if (this.get(index) === route) {
+      return this;
+    }
+
+    invariant(this.indexOf(route) === -1, 'route must be unique');
+
+    if (index < 0) {
+      index += this._routeNodes.length;
+    }
+
+    invariant(
+      index > -1 && index < this._routeNodes.length,
+      'replaceAtIndex: index out of bound'
+    );
+
+    const routeNodes = this._routeNodes.slice(0);
+    routeNodes[index] = new RouteNode(route);
+    return this._update(index, routeNodes);
+  }
+
+  // Iterations
+  forEach(callback: IterationCallback, context: ?Object): void {
+    this._routeNodes.forEach((node, index) => {
+      callback.call(context, node.route, index, node.key);
+    });
+  }
+
+  mapToArray(callback: IterationCallback, context: ?Object): Array<any> {
+    return this._routeNodes.map((node, index) => {
+      return callback.call(context, node.route, index, node.key);
+    });
+  }
+
+  _update(index: number, routeNodes: Array<RouteNode>): RouteStack {
+    if (
+      this._index === index &&
+      areRouteNodesEqual(this._routeNodes, routeNodes)
+    ) {
+      return this;
+    }
+
+    return new RouteStack(index, routeNodes);
+  }
+}
+
+module.exports = RouteStack;
diff --git a/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js b/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js
new file mode 100644
index 00000000000000..30a965d1cc3175
--- /dev/null
+++ b/Libraries/CustomComponents/NavigationExperimental/__tests__/NavigationLegacyNavigatorRouteStack-test.js
@@ -0,0 +1,395 @@
+/**
+ * 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.
+ */
+'use strict';
+
+jest
+ .autoMockOff()
+ .mock('ErrorUtils');
+
+const NavigationLegacyNavigatorRouteStack = require('NavigationLegacyNavigatorRouteStack');
+
+function assetStringNotEmpty(str) {
+  expect(!!str && typeof str === 'string').toBe(true);
+}
+
+describe('NavigationLegacyNavigatorRouteStack:', () => {
+  // Different types of routes.
+  const ROUTES = [
+    'foo',
+    1,
+    true,
+    {foo: 'bar'},
+    ['foo'],
+  ];
+
+  // Basic
+  it('gets index', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
+    expect(stack.index).toBe(1);
+  });
+
+  it('gets size', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
+    expect(stack.size).toBe(3);
+  });
+
+  it('gets route', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b', 'c']);
+    expect(stack.get(2)).toBe('c');
+  });
+
+  it('converts to an array', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    expect(stack.toArray()).toEqual(['a', 'b']);
+  });
+
+  it('creates a new stack after mutation', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    const stack2 = stack1.push('c');
+    expect(stack1).not.toBe(stack2);
+  });
+
+  it('throws at index out of bound', () => {
+    expect(
+      () => new NavigationLegacyNavigatorRouteStack(-1, ['a', 'b'])
+    ).toThrow();
+
+    expect(
+      () => new NavigationLegacyNavigatorRouteStack(100, ['a', 'b'])
+    ).toThrow();
+  });
+
+  it('finds index', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    expect(stack.indexOf('b')).toBe(1);
+    expect(stack.indexOf('c')).toBe(-1);
+  });
+
+  // Key
+  it('gets key for route', () => {
+    const test = (route) => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+      const key = stack.push(route).keyOf(route);
+      expect(typeof key).toBe('string');
+      expect(!!key).toBe(true);
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  it('gets a key of larger value for route', () => {
+    let lastKey = '';
+    const test = (route) => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+      const key = stack.push(route).keyOf(route);
+      expect(key > lastKey).toBe(true);
+      lastKey = key;
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  it('gets an unique key for a different route', () => {
+    let stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+    const keys = {};
+
+    const test = (route) => {
+      stack = stack.push(route);
+      const key = stack.keyOf(route);
+      expect(keys[key]).toBe(undefined);
+      keys[key] = true;
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  it('gets the same unique key for the same route', () => {
+    const test = (route) => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, [route]);
+      expect(stack.keyOf(route)).toBe(stack.keyOf(route));
+    };
+
+    ROUTES.forEach(test);
+  });
+
+
+  it('gets the same unique key form the derived stack', () => {
+    const test = (route) => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, [route]);
+      const derivedStack = stack.push('wow').pop().slice(0, 10).push('blah');
+      expect(derivedStack.keyOf(route)).toBe(stack.keyOf(route));
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  it('gets a different key from a different stack', () => {
+    const test = (route) => {
+      const stack1 = new NavigationLegacyNavigatorRouteStack(0, [route]);
+      const stack2 = new NavigationLegacyNavigatorRouteStack(0, [route]);
+      expect(stack1.keyOf(route)).not.toBe(stack2.keyOf(route));
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  it('gets no key for a route that does not contains this route', () => {
+     const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+     expect(stack.keyOf('b')).toBe(null);
+  });
+
+  it('gets a new key for a route that was removed and added again', () => {
+    const test = (route) => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+
+      const key1 = stack.push(route).keyOf(route);
+      const key2 = stack.push(route).pop().push(route).keyOf(route);
+      expect(key1).not.toBe(key2);
+    };
+
+    ROUTES.forEach(test);
+  });
+
+  // Slice
+  it('slices', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c', 'd']);
+    const stack2 = stack1.slice(1, 3);
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['b', 'c']);
+  });
+
+  it('may update index after slicing', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(2, ['a', 'b', 'c']);
+    expect(stack.slice().index).toBe(2);
+    expect(stack.slice(0, 1).index).toBe(0);
+    expect(stack.slice(0, 2).index).toBe(1);
+    expect(stack.slice(0, 3).index).toBe(2);
+    expect(stack.slice(0, 100).index).toBe(2);
+    expect(stack.slice(-2).index).toBe(1);
+  });
+
+  it('slices without specifying params', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
+    const stack2 = stack1.slice();
+    expect(stack2).toBe(stack1);
+  });
+
+  it('slices to from the end', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c', 'd']);
+    const stack2 = stack1.slice(-2);
+    expect(stack2.toArray()).toEqual(['c', 'd']);
+  });
+
+  it('throws when slicing to empty', () => {
+      expect(() => {
+        const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+        stack.slice(100);
+      }).toThrow();
+  });
+
+  // Push
+  it('pushes route', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+    const stack2 = stack1.push('c');
+
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['a', 'b', 'c']);
+    expect(stack2.index).toBe(2);
+    expect(stack2.size).toBe(3);
+  });
+
+  it('throws when pushing empty route', () => {
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.push(null);
+    }).toThrow();
+
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.push('');
+    }).toThrow();
+
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.push(undefined);
+    }).toThrow();
+  });
+
+  it('replaces routes on push', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
+    const stack2 = stack1.push('d');
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['a', 'b', 'd']);
+    expect(stack2.index).toBe(2);
+  });
+
+  // Pop
+  it('pops route', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(2, ['a', 'b', 'c']);
+    const stack2 = stack1.pop();
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['a', 'b']);
+    expect(stack2.index).toBe(1);
+    expect(stack2.size).toBe(2);
+  });
+
+  it('replaces routes on pop', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b', 'c']);
+    const stack2 = stack1.pop();
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['a']);
+    expect(stack2.index).toBe(0);
+  });
+
+  it('throws when popping to empty stack', () => {
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(0, ['a']);
+      stack.pop();
+    }).toThrow();
+  });
+
+  // Jump
+  it('jumps to index', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b', 'c']);
+    const stack2 = stack1.jumpToIndex(2);
+
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.index).toBe(2);
+  });
+
+  it('throws then jumping to index out of bound', () => {
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.jumpToIndex(2);
+    }).toThrow();
+
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.jumpToIndex(-1);
+    }).toThrow();
+  });
+
+  // Replace
+  it('replaces route at index', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+    const stack2 = stack1.replaceAtIndex(0, 'x');
+
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['x', 'b']);
+    expect(stack2.index).toBe(0);
+  });
+
+  it('replaces route at negative index', () => {
+    const stack1 = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+    const stack2 = stack1.replaceAtIndex(-1, 'x');
+
+    expect(stack2).not.toBe(stack1);
+    expect(stack2.toArray()).toEqual(['a', 'x']);
+    expect(stack2.index).toBe(1);
+  });
+
+  it('throws when replacing empty route', () => {
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.replaceAtIndex(1, null);
+    }).toThrow();
+  });
+
+  it('throws when replacing at index out of bound', () => {
+    expect(() => {
+      const stack = new NavigationLegacyNavigatorRouteStack(1, ['a', 'b']);
+      stack.replaceAtIndex(100, 'x');
+    }).toThrow();
+  });
+
+  // Iteration
+  it('iterates each item', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    const logs = [];
+    const keys = {};
+    const context = {name: 'yo'};
+
+    stack.forEach(function (route, index, key) {
+      assetStringNotEmpty(key);
+      if (!keys.hasOwnProperty(key)) {
+        keys[key] = true;
+        logs.push([
+          route,
+          index,
+          this.name,
+        ]);
+      }
+    }, context);
+
+    expect(logs).toEqual([
+      ['a', 0, 'yo'],
+      ['b', 1, 'yo'],
+    ]);
+  });
+
+  it('Maps to an array', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    const keys = {};
+    const context = {name: 'yo'};
+
+    const logs = stack.mapToArray(function(route, index, key) {
+      assetStringNotEmpty(key);
+      if (!keys.hasOwnProperty(key)) {
+        keys[key] = true;
+        return [
+          route,
+          index,
+          this.name,
+        ];
+      }
+    }, context);
+
+    expect(logs).toEqual([
+      ['a', 0, 'yo'],
+      ['b', 1, 'yo'],
+    ]);
+  });
+
+  // Navigation State
+  it('coverts to navigation state', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    const state = stack.toNavigationState();
+    expect(state).toEqual({
+      index: 0,
+      key: '0',
+      children:[
+        {key: '0', route: 'a'},
+        {key: '1', route: 'b'},
+      ],
+    });
+  });
+
+  it('coverts from navigation state', () => {
+    const stack = new NavigationLegacyNavigatorRouteStack(0, ['a', 'b']);
+    const state = stack.toNavigationState().children[0];
+    const route = NavigationLegacyNavigatorRouteStack.getRouteByNavigationState(state);
+    expect(route).toBe('a');
+  });
+});
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
index 7024081d207911..406e8be2fc6368 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js
@@ -33,8 +33,8 @@ 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 EventSubscription from 'EventSubscription';
 
diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js
index 6e27f5d5e70816..5307e1f869c754 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 a2abd3d55bdfd2..339c957340389f 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 16a95b55f9b7f4..fd0d93222ab739 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 b100ab5e43e300..5fdb573f223fa8 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 c3fc0f5ef6cc01..81fb1695ef130d 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 361892084e53ca..9e9aef47680771 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 7a00f72ddc5ed2..68de370caf75bf 100644
--- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js
+++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationTreeNode-test.js
@@ -6,7 +6,7 @@
 
 jest
   .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 af1ceb2a4c0816..e5a8a3a846b434 100644
--- a/Libraries/CustomComponents/Navigator/Navigator.js
+++ b/Libraries/CustomComponents/Navigator/Navigator.js
@@ -44,7 +44,7 @@ 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;
@@ -227,19 +227,13 @@ var Navigator = React.createClass({
     /**
      * Will emit the target route upon mounting and before each nav transition
      */
-    onWillFocus: deprecatedPropType(
-      PropTypes.func,
-      "Use `navigationContext.addListener('willfocus', callback)` instead."
-    ),
+    onWillFocus: PropTypes.func,
 
     /**
      * Will be called with the new route of each scene after the transition is
      * complete or after the initial mounting
      */
-    onDidFocus: deprecatedPropType(
-      PropTypes.func,
-      "Use `navigationContext.addListener('didfocus', callback)` instead."
-    ),
+    onDidFocus: PropTypes.func,
 
     /**
      * Optionally provide a navigation bar that persists across scene
@@ -782,7 +776,7 @@ var Navigator = React.createClass({
   },
 
   _matchGestureAction: function(eligibleGestures, gestures, gestureState) {
-    if (!gestures) {
+    if (!gestures || !eligibleGestures || !eligibleGestures.some) {
       return null;
     }
     var matchedGesture = null;
diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
index e224253c21be2a..6ec0a546d7be61 100644
--- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
+++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js
@@ -37,7 +37,7 @@ var View = require('View');
 var { Map } = require('immutable');
 
 var guid = require('guid');
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
 var NavigatorNavigationBarStyles = Platform.OS === 'android' ?
diff --git a/Libraries/Devtools/setupDevtools.js b/Libraries/Devtools/setupDevtools.js
index ed85e79d9bf3d8..4f75be24f10a46 100644
--- a/Libraries/Devtools/setupDevtools.js
+++ b/Libraries/Devtools/setupDevtools.js
@@ -30,18 +30,21 @@ function setupDevtools() {
       },
     },
   };
-  ws.onclose = () => {
-    setTimeout(setupDevtools, 200);
-    closeListeners.forEach(fn => fn());
-  };
-  ws.onerror = error => {
-    setTimeout(setupDevtools, 200);
-    closeListeners.forEach(fn => fn());
-  };
+  ws.onclose = handleClose;
+  ws.onerror = handleClose;
   ws.onopen = function () {
     tryToConnect();
   };
 
+  var hasClosed = false;
+  function handleClose() {
+    if (!hasClosed) {
+      hasClosed = true;
+      setTimeout(setupDevtools, 200);
+      closeListeners.forEach(fn => fn());
+    }
+  }
+
   function tryToConnect() {
     ws.send('attach:agent');
     var _interval = setInterval(() => ws.send('attach:agent'), 500);
diff --git a/Libraries/Experimental/Incremental.js b/Libraries/Experimental/Incremental.js
new file mode 100644
index 00000000000000..086215fd910c44
--- /dev/null
+++ b/Libraries/Experimental/Incremental.js
@@ -0,0 +1,171 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in 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 Incremental
+ * @flow
+ */
+'use strict';
+
+const InteractionManager = require('InteractionManager');
+const React = require('React');
+
+const DEBUG = false;
+
+/**
+ * WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
+ * not be reliably announced.  The whole thing might be deleted, who knows? Use
+ * at your own risk.
+ *
+ * React Native helps make apps smooth by doing all the heavy lifting off the
+ * main thread, in JavaScript.  That works great a lot of the time, except that
+ * heavy operations like rendering may block the JS thread from responding
+ * quickly to events like taps, making the app feel sluggish.
+ *
+ * `<Incremental>` solves this by slicing up rendering into chunks that are
+ * spread across multiple event loops. Expensive components can be sliced up
+ * recursively by wrapping pieces of them and their decendents in
+ * `<Incremental>` components. `<IncrementalGroup>` can be used to make sure
+ * everything in the group is rendered recursively before calling `onDone` and
+ * moving on to another sibling group (e.g. render one row at a time, even if
+ * rendering the top level row component produces more `<Incremental>` chunks).
+ * `<IncrementalPresenter>` is a type of `<IncrementalGroup>` that keeps it's
+ * children invisible and out of the layout tree until all rendering completes
+ * recursively. This means the group will be presented to the user as one unit,
+ * rather than pieces popping in sequentially.
+ *
+ * `<Incremental>` only affects initial render - `setState` and other render
+ * updates are unaffected.
+ *
+ * The chunks are rendered sequentially using the `InteractionManager` queue,
+ * which means that rendering will pause if it's interrupted by an interaction,
+ * such as an animation or gesture.
+ *
+ * Note there is some overhead, so you don't want to slice things up too much.
+ * A target of 100-200ms of total work per event loop on old/slow devices might
+ * be a reasonable place to start.
+ *
+ * Below is an example that will incrementally render all the parts of `Row` one
+ * first, then present them together, then repeat the process for `Row` two, and
+ * so on:
+ *
+ *   render: function() {
+ *     return (
+ *       <ScrollView>
+ *         {Array(10).fill().map((rowIdx) => (
+ *           <IncrementalPresenter key={rowIdx}>
+ *             <Row>
+ *               {Array(20).fill().map((widgetIdx) => (
+ *                 <Incremental key={widgetIdx}>
+ *                   <SlowWidget />
+ *                 </Incremental>
+ *               ))}
+ *             </Row>
+ *           </IncrementalPresenter>
+ *         ))}
+ *       </ScrollView>
+ *     );
+ *   };
+ *
+ * If SlowWidget takes 30ms to render, then without `Incremental`, this would
+ * block the JS thread for at least `10 * 20 * 30ms = 6000ms`, but with
+ * `Incremental` it will probably not block for more than 50-100ms at a time,
+ * allowing user interactions to take place which might even unmount this
+ * component, saving us from ever doing the remaining rendering work.
+ */
+export type Props = {
+ /**
+  * Called when all the decendents have finished rendering and mounting
+  * recursively.
+  */
+ onDone?: () => void;
+ /**
+  * Tags instances and associated tasks for easier debugging.
+  */
+ name: string;
+ children: any;
+ disabled: boolean;
+};
+class Incremental extends React.Component {
+  props: Props;
+  state: {
+    doIncrementalRender: boolean;
+  };
+  context: Context;
+  _incrementId: number;
+  _mounted: boolean;
+
+  constructor(props: Props, context: Context) {
+    super(props, context);
+    this._mounted = false;
+    this.state = {
+      doIncrementalRender: false,
+    };
+  }
+
+  getName(): string {
+    var ctx = this.context.incrementalGroup || {};
+    return ctx.groupId + ':' + this._incrementId + '-' + this.props.name;
+  }
+
+  componentWillMount() {
+    var ctx = this.context.incrementalGroup;
+    if (!ctx) {
+      return;
+    }
+    this._incrementId = ++(ctx.incrementalCount);
+    InteractionManager.runAfterInteractions({
+      name: 'Incremental:' + this.getName(),
+      gen: () => new Promise(resolve => {
+        if (!this._mounted) {
+          resolve();
+          return;
+        }
+        DEBUG && console.log('set doIncrementalRender for ' + this.getName());
+        this.setState({doIncrementalRender: true}, resolve);
+      }),
+    }).then(() => {
+      this._mounted && this.props.onDone && this.props.onDone();
+    });
+  }
+
+  render(): ?ReactElement {
+    if (this.props.disabled || !this.context.incrementalGroupEnabled || this.state.doIncrementalRender) {
+      DEBUG && console.log('render ' + this.getName());
+      return this.props.children;
+    }
+    return null;
+  }
+
+  componentDidMount() {
+    this._mounted = true;
+    if (!this.context.incrementalGroup) {
+      this.props.onDone && this.props.onDone();
+    }
+  }
+
+  componentWillUnmount() {
+    this._mounted = false;
+  }
+}
+Incremental.defaultProps = {
+  name: '',
+};
+Incremental.contextTypes = {
+  incrementalGroup: React.PropTypes.object,
+  incrementalGroupEnabled: React.PropTypes.bool,
+};
+
+export type Context = {
+  incrementalGroupEnabled: boolean;
+  incrementalGroup: ?{
+    groupId: string;
+    incrementalCount: number;
+  };
+};
+
+module.exports = Incremental;
diff --git a/Libraries/Experimental/IncrementalExample.js b/Libraries/Experimental/IncrementalExample.js
new file mode 100644
index 00000000000000..1c1e107cc5b07f
--- /dev/null
+++ b/Libraries/Experimental/IncrementalExample.js
@@ -0,0 +1,197 @@
+/**
+ * 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 IncrementalExample
+ * @flow
+ */
+'use strict';
+
+const React = require('react-native');
+const {
+  InteractionManager,
+  ScrollView,
+  StyleSheet,
+  Text,
+  TouchableOpacity,
+  View,
+} = React;
+
+const Incremental = require('Incremental');
+const IncrementalGroup = require('IncrementalGroup');
+const IncrementalPresenter = require('IncrementalPresenter');
+
+const JSEventLoopWatchdog = require('JSEventLoopWatchdog');
+const StaticContainer = require('StaticContainer.react');
+
+const performanceNow = require('performanceNow');
+
+InteractionManager.setDeadline(1000);
+JSEventLoopWatchdog.install({thresholdMS: 200});
+
+const NUM_ITEMS = 20;
+
+let totalWidgets = 0;
+
+class SlowWidget extends React.Component {
+  state: {ctorTimestamp: number, timeToMount: number};
+  constructor(props, context) {
+    super(props, context);
+    this.state = {
+      ctorTimestamp: performanceNow(),
+      timeToMount: 0,
+    };
+  }
+  render() {
+    this.state.timeToMount === 0 && burnCPU(20);
+    return (
+      <View style={styles.widgetContainer}>
+        <Text style={styles.widgetText}>
+          {`${this.state.timeToMount || '?'} ms`}
+        </Text>
+      </View>
+    );
+  }
+  componentDidMount() {
+    const timeToMount = performanceNow() - this.state.ctorTimestamp;
+    this.setState({timeToMount});
+    totalWidgets++;
+  }
+}
+
+let imHandle;
+function startInteraction() {
+  imHandle = InteractionManager.createInteractionHandle();
+}
+function stopInteraction() {
+  InteractionManager.clearInteractionHandle(imHandle);
+}
+
+function Block(props: Object) {
+  const IncrementalContainer = props.stream ? IncrementalGroup : IncrementalPresenter;
+  return (
+    <IncrementalContainer name={'b_' + props.idx}>
+      <TouchableOpacity
+        onPressIn={startInteraction}
+        onPressOut={stopInteraction}>
+        <View style={styles.block}>
+          <Text>
+            {props.idx + ': ' + (props.stream ? 'Streaming' : 'Presented')}
+          </Text>
+          {props.children}
+        </View>
+      </TouchableOpacity>
+    </IncrementalContainer>
+  );
+}
+
+const Row = (props: Object) => <View style={styles.row} {...props} />;
+
+class IncrementalExample extends React.Component {
+  static title = '<Incremental*>';
+  static description = 'Enables incremental rendering of complex components.';
+  start: number;
+  state: {stats: ?Object};
+  constructor(props: mixed, context: mixed) {
+    super(props, context);
+    this.start = performanceNow();
+    this.state = {
+      stats: null,
+    };
+    (this: any)._onDone = this._onDone.bind(this);
+  }
+  _onDone() {
+    const onDoneElapsed = performanceNow() - this.start;
+    setTimeout(() => {
+      const stats = {
+        onDoneElapsed,
+        totalWidgets,
+        ...JSEventLoopWatchdog.getStats(),
+        setTimeoutElapsed: performanceNow() - this.start,
+      };
+      stats.avgStall = stats.totalStallTime / stats.stallCount;
+      this.setState({stats});
+      console.log('onDone:', stats);
+    }, 0);
+  }
+  render(): ReactElement {
+    return (
+      <IncrementalGroup
+        disabled={false}
+        name="root"
+        onDone={this._onDone}>
+        <ScrollView style={styles.scrollView}>
+          <Text style={styles.headerText}>
+            Press and hold on a row to pause rendering.
+          </Text>
+          {this.state.stats && <Text>
+            Finished: {JSON.stringify(this.state.stats, null, 2)}
+          </Text>}
+          {Array(8).fill().map((_, blockIdx) => {
+            return (
+              <Block key={blockIdx} idx={blockIdx} stream={blockIdx < 2}>
+                {Array(4).fill().map((_b, rowIdx) => (
+                  <Row key={rowIdx}>
+                    {Array(14).fill().map((_c, widgetIdx) => (
+                      <Incremental key={widgetIdx} name={'w_' + widgetIdx}>
+                        <SlowWidget idx={widgetIdx} />
+                      </Incremental>
+                    ))}
+                  </Row>
+                ))}
+              </Block>
+            );
+          })}
+        </ScrollView>
+      </IncrementalGroup>
+    );
+  }
+}
+
+function burnCPU(milliseconds) {
+  const start = performanceNow();
+  while (performanceNow() < (start + milliseconds)) {}
+}
+
+var styles = StyleSheet.create({
+  scrollView: {
+    margin: 10,
+    backgroundColor: 'white',
+    flex: 1,
+  },
+  headerText: {
+    fontSize: 20,
+    margin: 10,
+  },
+  block: {
+    borderRadius: 6,
+    borderWidth: 2,
+    borderColor: '#a52a2a',
+    padding: 14,
+    margin: 5,
+    backgroundColor: 'white',
+  },
+  row: {
+    flexDirection: 'row',
+  },
+  widgetContainer: {
+    backgroundColor: '#dddddd',
+    padding: 2,
+    margin: 2,
+  },
+  widgetText: {
+    color: 'black',
+    fontSize: 4,
+  },
+});
+
+module.exports = IncrementalExample;
diff --git a/Libraries/Experimental/IncrementalGroup.js b/Libraries/Experimental/IncrementalGroup.js
new file mode 100644
index 00000000000000..27b92a07709bfb
--- /dev/null
+++ b/Libraries/Experimental/IncrementalGroup.js
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in 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 IncrementalGroup
+ * @flow
+ */
+'use strict';
+
+const Incremental = require('Incremental');
+const React = require('React');
+
+let _groupCounter = -1;
+const DEBUG = false;
+
+import type {Props, Context} from 'Incremental';
+
+/**
+ * WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
+ * not be reliably announced.  The whole thing might be deleted, who knows? Use
+ * at your own risk.
+ *
+ * `<Incremental>` components must be wrapped in an `<IncrementalGroup>` (e.g.
+ * via `<IncrementalPresenter>`) in order to provide the incremental group
+ * context, otherwise they will do nothing.
+ *
+ * See Incremental.js for more info.
+ */
+class IncrementalGroup extends React.Component {
+  props: Props;
+  context: Context;
+  _groupInc: string;
+  componentWillMount() {
+    this._groupInc = `g${++_groupCounter}-`;
+    DEBUG && console.log(
+      'create IncrementalGroup with id ' + this.getGroupId()
+    );
+  }
+
+  getGroupId(): string {
+    const ctx = this.context.incrementalGroup;
+    const prefix = ctx ? ctx.groupId + ':' : '';
+    return prefix + this._groupInc + this.props.name;
+  }
+
+  getChildContext(): Context {
+    if (this.props.disabled || this.context.incrementalGroupEnabled === false) {
+      return {
+        incrementalGroupEnabled: false,
+        incrementalGroup: null,
+      };
+    }
+    return {
+      incrementalGroupEnabled: true,
+      incrementalGroup: {
+        groupId: this.getGroupId(),
+        incrementalCount: -1,
+      },
+    };
+  }
+
+  render(): ReactElement {
+    return (
+      <Incremental
+        onDone={this.props.onDone}
+        children={this.props.children}
+      />
+    );
+  }
+}
+IncrementalGroup.contextTypes = {
+  incrementalGroup: React.PropTypes.object,
+  incrementalGroupEnabled: React.PropTypes.bool,
+};
+IncrementalGroup.childContextTypes = IncrementalGroup.contextTypes;
+
+module.exports = IncrementalGroup;
diff --git a/Libraries/Experimental/IncrementalPresenter.js b/Libraries/Experimental/IncrementalPresenter.js
new file mode 100644
index 00000000000000..053b4f9a141e54
--- /dev/null
+++ b/Libraries/Experimental/IncrementalPresenter.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.
+ *
+ * @providesModule IncrementalPresenter
+ * @flow
+ */
+'use strict';
+
+const IncrementalGroup = require('IncrementalGroup');
+const React = require('React');
+const View = require('View');
+
+import type {Context} from 'Incremental';
+
+/**
+ * WARNING: EXPERIMENTAL. Breaking changes will probably happen a lot and will
+ * not be reliably announced.  The whole thing might be deleted, who knows? Use
+ * at your own risk.
+ *
+ * `<IncrementalPresenter>` can be used to group sets of `<Incremental>` renders
+ * such that they are initially invisible and removed from layout until all
+ * decendents have finished rendering, at which point they are drawn all at once
+ * so the UI doesn't jump around during the incremental rendering process.
+ *
+ * See Incremental.js for more info.
+ */
+type Props = {
+  name: string;
+  disabled: boolean;
+  onDone: () => void;
+  onLayout: (event: Object) => void;
+  style: mixed;
+  children: any;
+}
+class IncrementalPresenter extends React.Component {
+  props: Props;
+  context: Context;
+  _isDone: boolean;
+  constructor(props: Props, context: Context) {
+    super(props, context);
+    this._isDone = false;
+    (this: any).onDone = this.onDone.bind(this);
+  }
+  onDone() {
+    this._isDone = true;
+    if (this.props.disabled !== true &&
+        this.context.incrementalGroupEnabled !== false) {
+      // Avoid expensive re-renders and use setNativeProps
+      this.refs.view.setNativeProps(
+        {style: [this.props.style, {opacity: 1, position: 'relative'}]}
+      );
+    }
+    this.props.onDone && this.props.onDone();
+  }
+  render() {
+    if (this.props.disabled !== true &&
+        this.context.incrementalGroupEnabled !== false &&
+        !this._isDone) {
+      var style = [this.props.style, {opacity: 0, position: 'absolute'}];
+    } else {
+      var style = this.props.style;
+    }
+    return (
+      <IncrementalGroup
+        onDone={this.onDone}
+        name={this.props.name}
+        disabled={this.props.disabled}>
+        <View
+          children={this.props.children}
+          ref="view"
+          style={style}
+          onLayout={this.props.onLayout}
+        />
+      </IncrementalGroup>
+    );
+  }
+}
+IncrementalPresenter.propTypes = {
+  name: React.PropTypes.string,
+  disabled: React.PropTypes.bool,
+  onDone: React.PropTypes.func,
+  onLayout: React.PropTypes.func,
+  style: View.propTypes.style,
+};
+IncrementalPresenter.contextTypes = {
+  incrementalGroup: React.PropTypes.object,
+  incrementalGroupEnabled: React.PropTypes.bool,
+};
+
+module.exports = IncrementalPresenter;
diff --git a/Libraries/Fetch/fetch.js b/Libraries/Fetch/fetch.js
index ff5ef29bb7c573..7cb122a07f4cc2 100644
--- a/Libraries/Fetch/fetch.js
+++ b/Libraries/Fetch/fetch.js
@@ -159,13 +159,13 @@ var self = {};
         return false
       }
     })(),
-    formData: typeof FormData === 'function'
+    formData: typeof FormData === 'function',
+    arrayBuffer: typeof ArrayBuffer === 'function'
   }
 
   function Body() {
     this.bodyUsed = false
 
-
     this._initBody = function(body) {
       this._bodyInit = body
       if (typeof body === 'string') {
@@ -176,9 +176,20 @@ var self = {};
         this._bodyFormData = body
       } else if (!body) {
         this._bodyText = ''
+      } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
+        // Only support ArrayBuffers for POST method.
+        // Receiving ArrayBuffers happens via Blobs, instead.
       } else {
         throw new Error('unsupported BodyInit type')
       }
+
+      if (!this.headers.get('content-type')) {
+        if (typeof body === 'string') {
+          this.headers.set('content-type', 'text/plain;charset=UTF-8')
+        } else if (this._bodyBlob && this._bodyBlob.type) {
+          this.headers.set('content-type', this._bodyBlob.type)
+        }
+      }
     }
 
     if (support.blob) {
@@ -278,7 +289,7 @@ var self = {};
     }
     this._initBody(body)
   }
-  
+
   Request.prototype.clone = function() {
     return new Request(this)
   }
@@ -315,16 +326,16 @@ var self = {};
       options = {}
     }
 
-    this._initBody(bodyInit)
     this.type = 'default'
-    this.url = null
     this.status = options.status
     this.ok = this.status >= 200 && this.status < 300
     this.statusText = options.statusText
     this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
     this.url = options.url || ''
+    this._initBody(bodyInit)
   }
-  
+  Body.call(Response.prototype)
+
   Response.prototype.clone = function() {
     return new Response(this._bodyInit, {
       status: this.status,
@@ -334,21 +345,35 @@ var self = {};
     })
   }
 
-  Body.call(Response.prototype)
+  Response.error = function() {
+    var response = new Response(null, {status: 0, statusText: ''})
+    response.type = 'error'
+    return response
+  }
+
+  var redirectStatuses = [301, 302, 303, 307, 308]
+
+  Response.redirect = function(url, status) {
+    if (redirectStatuses.indexOf(status) === -1) {
+      throw new RangeError('Invalid status code')
+    }
+
+    return new Response(null, {status: status, headers: {location: url}})
+  }
 
   self.Headers = Headers;
   self.Request = Request;
   self.Response = Response;
 
   self.fetch = function(input, init) {
-    var request
-    if (Request.prototype.isPrototypeOf(input) && !init) {
-      request = input
-    } else {
-      request = new Request(input, init)
-    }
-
     return new Promise(function(resolve, reject) {
+      var request
+      if (Request.prototype.isPrototypeOf(input) && !init) {
+        request = input
+      } else {
+        request = new Request(input, init)
+      }
+
       var xhr = new XMLHttpRequest()
 
       function responseURL() {
@@ -370,6 +395,7 @@ var self = {};
           reject(new TypeError('Network request failed'))
           return
         }
+
         var options = {
           status: status,
           statusText: xhr.statusText,
@@ -405,5 +431,4 @@ var self = {};
 })();
 
 /** End of the third-party code */
-
 module.exports = self;
diff --git a/Libraries/Geolocation/Geolocation.js b/Libraries/Geolocation/Geolocation.js
index 42d7378eb102be..3ed401a5d37c80 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 = [];
 
diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m
index 7320fae8980c42..f2e76f3612f97b 100644
--- a/Libraries/Geolocation/RCTLocationObserver.m
+++ b/Libraries/Geolocation/RCTLocationObserver.m
@@ -130,7 +130,7 @@ - (dispatch_queue_t)methodQueue
 
 #pragma mark - Private API
 
-- (void)beginLocationUpdates
+- (void)beginLocationUpdatesWithDesiredAccuracy:(CLLocationAccuracy)desiredAccuracy
 {
   if (!_locationManager) {
     _locationManager = [CLLocationManager new];
@@ -147,6 +147,7 @@ - (void)beginLocationUpdates
     [_locationManager requestWhenInUseAuthorization];
   }
 
+  _locationManager.desiredAccuracy = desiredAccuracy;
   // Start observing location
   [_locationManager startUpdatingLocation];
 }
@@ -178,8 +179,7 @@ - (void)timeout:(NSTimer *)timer
     _observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
   }
 
-  _locationManager.desiredAccuracy = _observerOptions.accuracy;
-  [self beginLocationUpdates];
+  [self beginLocationUpdatesWithDesiredAccuracy:_observerOptions.accuracy];
   _observingLocation = YES;
 }
 
@@ -225,8 +225,8 @@ - (void)timeout:(NSTimer *)timer
 
   // Check if previous recorded location exists and is good enough
   if (_lastLocationEvent &&
-      CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
-      [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
+      [NSDate date].timeIntervalSince1970 - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
+      [_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] <= options.accuracy) {
 
     // Call success block with most recent known location
     successBlock(@[_lastLocationEvent]);
@@ -249,8 +249,11 @@ - (void)timeout:(NSTimer *)timer
   [_pendingRequests addObject:request];
 
   // Configure location manager and begin updating location
-  _locationManager.desiredAccuracy = MIN(_locationManager.desiredAccuracy, options.accuracy);
-  [self beginLocationUpdates];
+  CLLocationAccuracy accuracy = options.accuracy;
+  if (_locationManager) {
+    accuracy = MIN(_locationManager.desiredAccuracy, accuracy);
+  }
+  [self beginLocationUpdatesWithDesiredAccuracy:accuracy];
 }
 
 #pragma mark - CLLocationManagerDelegate
@@ -270,7 +273,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
@@ -286,7 +289,7 @@ - (void)locationManager:(CLLocationManager *)manager
   }
   [_pendingRequests removeAllObjects];
 
-  // Stop updating if not not observing
+  // Stop updating if not observing
   if (!_observingLocation) {
     [_locationManager stopUpdatingLocation];
   }
diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js
index 4c920dcf2aa55a..d2e1a177cc6007 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');
@@ -63,7 +63,7 @@ var ImageViewAttributes = merge(ReactNativeViewAttributes.UIView, {
 var Image = React.createClass({
   propTypes: {
     ...View.propTypes,
-    style: StyleSheetPropType(ImageStylePropTypes), 
+    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 a static image
@@ -75,7 +75,7 @@ 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
@@ -121,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 = {
@@ -161,6 +161,10 @@ var Image = React.createClass({
       console.warn('source.uri should not be an empty string');
     }
 
+    if (this.props.src) {
+      console.warn('The <Image> component requires a `source` property rather than `src`.');
+    }
+
     if (source && source.uri) {
       var {width, height} = source;
       var style = flattenStyle([{width, height}, styles.base, this.props.style]);
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 4830a6dec3365e..785c9f08376d03 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -23,10 +23,10 @@ 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,
@@ -210,10 +210,14 @@ var Image = React.createClass({
       RawImage = RCTImageView;
     }
 
+    if (this.props.src) {
+      console.warn('The <Image> component requires a `source` property rather than `src`.');
+    }
+
     if (this.context.isInAParentText) {
       RawImage = RCTVirtualImage;
       if (!width || !height) {
-        console.warn('You must specify a width and height for the image %s', uri); 
+        console.warn('You must specify a width and height for the image %s', uri);
       }
     }
 
diff --git a/Libraries/Image/ImageResizeMode.js b/Libraries/Image/ImageResizeMode.js
index 538287258ffc13..531212d86794b4 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
diff --git a/Libraries/Image/RCTImageEditingManager.m b/Libraries/Image/RCTImageEditingManager.m
index 2a2ea967161b22..9ff231d64f7f1a 100644
--- a/Libraries/Image/RCTImageEditingManager.m
+++ b/Libraries/Image/RCTImageEditingManager.m
@@ -62,8 +62,8 @@ @implementation RCTImageEditingManager
       targetSize = [RCTConvert CGSize:cropData[@"displaySize"]]; // in pixels
       RCTResizeMode resizeMode = [RCTConvert RCTResizeMode:cropData[@"resizeMode"] ?: @"contain"];
       targetRect = RCTTargetRect(croppedImage.size, targetSize, 1, resizeMode);
-      transform = RCTTransformFromTargetRect(image.size, targetRect);
-      croppedImage = RCTTransformImage(image, targetSize, image.scale, transform);
+      transform = RCTTransformFromTargetRect(croppedImage.size, targetRect);
+      croppedImage = RCTTransformImage(croppedImage, targetSize, image.scale, transform);
     }
 
     // Store image
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index e7805b135cfaf7..d11c424103d3ea 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -27,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.
@@ -66,6 +91,15 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
                                         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.
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index 0d7b06f5c1d578..207ecf7024b2ad 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -38,8 +38,14 @@ @implementation RCTImageLoader
 {
   NSArray<id<RCTImageURLLoader>> *_loaders;
   NSArray<id<RCTImageDataDecoder>> *_decoders;
+  NSOperationQueue *_imageDecodeQueue;
   dispatch_queue_t _URLCacheQueue;
   NSURLCache *_URLCache;
+  NSMutableArray *_pendingTasks;
+  NSInteger _activeTasks;
+  NSMutableArray *_pendingDecodes;
+  NSInteger _scheduledDecodes;
+  NSUInteger _activeBytes;
 }
 
 @synthesize bridge = _bridge;
@@ -48,54 +54,36 @@ @implementation RCTImageLoader
 
 - (void)setUp
 {
-  // Get image loaders and decoders
-  NSMutableArray<id<RCTImageURLLoader>> *loaders = [NSMutableArray array];
-  NSMutableArray<id<RCTImageDataDecoder>> *decoders = [NSMutableArray array];
-  for (Class moduleClass in _bridge.moduleClasses) {
-    if ([moduleClass conformsToProtocol:@protocol(RCTImageURLLoader)]) {
-      [loaders addObject:[_bridge moduleForClass:moduleClass]];
-    }
-    if ([moduleClass conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
-      [decoders addObject:[_bridge moduleForClass:moduleClass]];
-    }
-  }
-
-  // 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;
-    }
-  }];
+  // Set defaults
+  _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
+  _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
+  _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 *1024; // 30MB
 
-  // 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;
-    }
-  }];
-
-  _loaders = loaders;
-  _decoders = decoders;
+  _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
 }
 
 - (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
 {
-  if (!_loaders) {
+  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;
@@ -133,10 +121,26 @@ - (void)setUp
 
 - (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
 {
-  if (!_decoders) {
+  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;
@@ -202,6 +206,50 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
                 completionBlock:callback];
 }
 
+- (void)dequeueTasks
+{
+  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
@@ -240,7 +288,7 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
 
   // All access to URL cache must be serialized
   if (!_URLCacheQueue) {
-    _URLCacheQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLCacheQueue", DISPATCH_QUEUE_SERIAL);
+    [self setUp];
   }
   dispatch_async(_URLCacheQueue, ^{
 
@@ -326,8 +374,7 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
     }
 
     // Download image
-    RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
-                            ^(NSURLResponse *response, NSData *data, NSError *error) {
+    RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
       if (error) {
         completionHandler(error, nil);
         return;
@@ -347,14 +394,26 @@ - (RCTImageLoaderCancellationBlock)loadImageOrDataWithTag:(NSString *)imageTag
         // Process image data
         processResponse(response, data, nil);
 
+        //clean up
+        [weakSelf dequeueTasks];
+
       });
 
     }];
     task.downloadProgressBlock = progressHandler;
-    [task start];
+
+    if (!_pendingTasks) {
+      _pendingTasks = [NSMutableArray new];
+    }
+    [_pendingTasks addObject:task];
+    if (MAX(_activeTasks, _scheduledDecodes) < _maxConcurrentLoadingTasks) {
+      [task start];
+      _activeTasks++;
+    }
 
     cancelLoad = ^{
       [task cancel];
+      [weakSelf dequeueTasks];
     };
 
   });
@@ -452,7 +511,6 @@ - (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
   __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), ^{
@@ -474,33 +532,73 @@ - (RCTImageLoaderCancellationBlock)decodeImageDataWithoutClipping:(NSData *)data
                        completionHandler:completionHandler];
   } else {
 
-    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;
 
-      UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
+        // 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));
-      }
+            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);
+            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];
+      }
+      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 ^{
diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m
index edc1cb926c1696..5aeefad1247d4d 100644
--- a/Libraries/Image/RCTImageUtils.m
+++ b/Libraries/Image/RCTImageUtils.m
@@ -316,7 +316,8 @@ BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
     return nil;
   }
 
-  UIGraphicsBeginImageContextWithOptions(destSize, NO, destScale);
+  BOOL opaque = !RCTImageHasAlpha(image.CGImage);
+  UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
   CGContextRef currentContext = UIGraphicsGetCurrentContext();
   CGContextConcatCTM(currentContext, transform);
   [image drawAtPoint:CGPointZero];
diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js
index b406316a1a906a..ae7ce3c5ea2a76 100644
--- a/Libraries/Image/__tests__/resolveAssetSource-test.js
+++ b/Libraries/Image/__tests__/resolveAssetSource-test.js
@@ -160,7 +160,7 @@ describe('resolveAssetSource', () => {
       });
     });
   });
-  
+
   describe('bundle was loaded from file on Android', () => {
     beforeEach(() => {
       NativeModules.SourceCode.scriptURL =
@@ -189,6 +189,34 @@ describe('resolveAssetSource', () => {
     });
   });
 
+  describe('bundle was loaded from raw file on Android', () => {
+    beforeEach(() => {
+      NativeModules.SourceCode.scriptURL =
+        '/sdcard/Path/To/Simulator/main.bundle';
+      Platform.OS = 'android';
+    });
+
+    it('uses sideloaded 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,
+      });
+    });
+  });
+
 });
 
 describe('resolveAssetSource.pickScale', () => {
diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js
index 6527e91df096ea..e42b2d1da09213 100644
--- a/Libraries/Image/resolveAssetSource.js
+++ b/Libraries/Image/resolveAssetSource.js
@@ -47,12 +47,22 @@ function getDevServerURL() {
 
 function getOfflinePath() {
   if (_offlinePath === undefined) {
-    var scriptURL = SourceCode.scriptURL;
-    var match = scriptURL && scriptURL.match(/^file:\/\/(\/.*\/)/);
-    if (match) {
-      _offlinePath = match[1];
-    } else {
+    const scriptURL = SourceCode.scriptURL;
+    if (!scriptURL) {
+      // scriptURL is falsy, we have nothing to go on here
       _offlinePath = '';
+      return _offlinePath;
+    }
+    if (scriptURL.startsWith('assets://')) {
+      // running from within assets, no offline path to use
+      _offlinePath = '';
+      return _offlinePath;
+    }
+    if (scriptURL.startsWith('file://')) {
+      // cut off the protocol
+      _offlinePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
+    } else {
+      _offlinePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
     }
   }
 
diff --git a/Libraries/Inspector/PerformanceOverlay.js b/Libraries/Inspector/PerformanceOverlay.js
index c0be512421a88c..3329dc341d377a 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 96d13ad6a74e5f..96db90e8a0763d 100644
--- a/Libraries/Inspector/StyleInspector.js
+++ b/Libraries/Inspector/StyleInspector.js
@@ -25,13 +25,13 @@ 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 => {
             var value = typeof this.props.style[name] === 'object' ? JSON.stringify(this.props.style[name]) : this.props.style[name];
-            return <Text style={styles.value}>{value}</Text>;
+            return <Text key={name} style={styles.value}>{value}</Text>;
           } ) }
         </View>
       </View>
diff --git a/Libraries/Interaction/InteractionManager.js b/Libraries/Interaction/InteractionManager.js
index 8fac8edb8a6459..9d0547251a215d 100644
--- a/Libraries/Interaction/InteractionManager.js
+++ b/Libraries/Interaction/InteractionManager.js
@@ -16,8 +16,8 @@ const EventEmitter = require('EventEmitter');
 const Set = require('Set');
 const TaskQueue = require('TaskQueue');
 
-const invariant = require('invariant');
-const keyMirror = require('keyMirror');
+const invariant = require('fbjs/lib/invariant');
+const keyMirror = require('fbjs/lib/keyMirror');
 const setImmediate = require('setImmediate');
 
 type Handle = number;
diff --git a/Libraries/Interaction/JSEventLoopWatchdog.js b/Libraries/Interaction/JSEventLoopWatchdog.js
new file mode 100644
index 00000000000000..5a23ad3bd76440
--- /dev/null
+++ b/Libraries/Interaction/JSEventLoopWatchdog.js
@@ -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.
+ *
+ * @providesModule JSEventLoopWatchdog
+ * @flow
+ */
+'use strict';
+
+const performanceNow = require('performanceNow');
+
+type Handler = {
+  onIterate?: () => void;
+  onStall: (params: {lastInterval: number}) => string;
+};
+
+/**
+ * A utility for tracking stalls in the JS event loop that prevent timers and
+ * other events from being processed in a timely manner.
+ *
+ * The "stall" time is defined as the amount of time in access of the acceptable
+ * threshold, which is typically around 100-200ms. So if the treshold is set to
+ * 100 and a timer fires 150 ms later than it was scheduled because the event
+ * loop was tied up, that would be considered a 50ms stall.
+ *
+ * By default, logs stall events to the console when installed. Can also be
+ * queried with `getStats`.
+ */
+const JSEventLoopWatchdog = {
+  getStats: function(): Object {
+    return {stallCount, totalStallTime, longestStall, acceptableBusyTime};
+  },
+  reset: function() {
+    console.log('JSEventLoopWatchdog: reset');
+    totalStallTime = 0;
+    stallCount = 0;
+    longestStall = 0;
+  },
+  addHandler: function(handler: Handler) {
+    handlers.push(handler);
+  },
+  install: function({thresholdMS}: {thresholdMS: number}) {
+    acceptableBusyTime = thresholdMS;
+    if (installed) {
+      return;
+    }
+    installed = true;
+    let lastInterval = performanceNow();
+    function iteration() {
+      const now = performanceNow();
+      const busyTime = now - lastInterval;
+      if (busyTime >= thresholdMS) {
+        const stallTime = busyTime - thresholdMS;
+        stallCount++;
+        totalStallTime += stallTime;
+        longestStall = Math.max(longestStall, stallTime);
+        let msg = `JSEventLoopWatchdog: JS thread busy for ${busyTime}ms. ` +
+          `${totalStallTime}ms in ${stallCount} stalls so far. `;
+        handlers.forEach((handler) => {
+          msg += handler.onStall({lastInterval});
+        });
+        console.log(msg);
+      }
+      handlers.forEach((handler) => {
+        handler.onIterate && handler.onIterate();
+      });
+      lastInterval = now;
+      setTimeout(iteration, thresholdMS / 5);
+    }
+    iteration();
+  },
+};
+
+let acceptableBusyTime = 0;
+let installed = false;
+let totalStallTime = 0;
+let stallCount = 0;
+let longestStall = 0;
+const handlers: Array<Handler> = [];
+
+module.exports = JSEventLoopWatchdog;
diff --git a/Libraries/Interaction/TaskQueue.js b/Libraries/Interaction/TaskQueue.js
index 91698470dfc347..f623f7a3ad9d7e 100644
--- a/Libraries/Interaction/TaskQueue.js
+++ b/Libraries/Interaction/TaskQueue.js
@@ -13,7 +13,7 @@
 
 const ErrorUtils = require('ErrorUtils');
 
-const invariant = require('invariant');
+const invariant = require('fbjs/lib/invariant');
 
 type SimpleTask = {
   name: string;
diff --git a/Libraries/Interaction/__tests__/InteractionManager-test.js b/Libraries/Interaction/__tests__/InteractionManager-test.js
index 736fc93c585f64..032acf2555465b 100644
--- a/Libraries/Interaction/__tests__/InteractionManager-test.js
+++ b/Libraries/Interaction/__tests__/InteractionManager-test.js
@@ -6,6 +6,7 @@
 
 jest
   .autoMockOff()
+  .mock('ErrorUtils')
   .mock('BatchedBridge');
 
 function expectToBeCalledOnce(fn) {
diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js
index ecd6d8272597d2..cc85004d29b69d 100644
--- a/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js
+++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMapsUtils.js
@@ -12,7 +12,6 @@
 
 'use strict';
 
-var HMRClient = require('../../Utilities/HMRClient');
 var Promise = require('Promise');
 var NativeModules = require('NativeModules');
 var SourceMapConsumer = require('SourceMap').SourceMapConsumer;
diff --git a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimers.js
index f6a8bddc7a438d..0ec0a4c08c2816 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 c85c155a772699..df1b609723e6dd 100644
--- a/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js
+++ b/Libraries/JavaScriptAppEngine/System/JSTimers/JSTimersExecution.js
@@ -10,10 +10,10 @@
  */
 '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');
 
 /**
diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js
index 140c35ff87850c..108c4d5428e54b 100644
--- a/Libraries/LayoutAnimation/LayoutAnimation.js
+++ b/Libraries/LayoutAnimation/LayoutAnimation.js
@@ -15,7 +15,7 @@ var PropTypes = require('ReactPropTypes');
 var UIManager = require('UIManager');
 
 var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker');
-var keyMirror = require('keyMirror');
+var keyMirror = require('fbjs/lib/keyMirror');
 
 var TypesEnum = {
   spring: true,
diff --git a/Libraries/Linking/Linking.js b/Libraries/Linking/Linking.js
index 97282ab663918b..280b48ec8a85d7 100644
--- a/Libraries/Linking/Linking.js
+++ b/Libraries/Linking/Linking.js
@@ -18,7 +18,7 @@ const {
   LinkingManager: LinkingManagerIOS
 } = require('NativeModules');
 const LinkingManager = Platform.OS === 'android' ? IntentAndroid : LinkingManagerIOS;
-const invariant = require('invariant');
+const invariant = require('fbjs/lib/invariant');
 const Map = require('Map');
 
 const _notifHandlers = new Map();
@@ -38,7 +38,7 @@ const DEVICE_NOTIF_EVENT = 'openURL';
  *
  * ```
  * componentDidMount() {
- *   var url = Linking.getInitialURL().then(url) => {
+ *   var url = Linking.getInitialURL().then((url) => {
  *     if (url) {
  *       console.log('Initial url is: ' + url);
  *     }
@@ -53,10 +53,12 @@ const DEVICE_NOTIF_EVENT = 'openURL';
  * 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 [LinkingManager application:application openURL:url
+ *   return [RCTLinkingManager application:application openURL:url
  *                       sourceApplication:sourceApplication annotation:annotation];
  * }
  *
@@ -64,7 +66,7 @@ const DEVICE_NOTIF_EVENT = 'openURL';
  * - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
  *  restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
  * {
- *  return [LinkingManager application:application
+ *  return [RCTLinkingManager application:application
  *                   continueUserActivity:userActivity
  *                     restorationHandler:restorationHandler];
  * }
diff --git a/Libraries/LinkingIOS/LinkingIOS.js b/Libraries/LinkingIOS/LinkingIOS.js
index aa252c7d62aa81..b5ec9f7a6c6068 100644
--- a/Libraries/LinkingIOS/LinkingIOS.js
+++ b/Libraries/LinkingIOS/LinkingIOS.js
@@ -13,7 +13,7 @@
 
 var Linking = require('Linking');
 var RCTLinkingManager = require('NativeModules').LinkingManager;
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var _initialURL = RCTLinkingManager && RCTLinkingManager.initialURL;
 
diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m
index d720631674c939..9538981298a0b1 100644
--- a/Libraries/LinkingIOS/RCTLinkingManager.m
+++ b/Libraries/LinkingIOS/RCTLinkingManager.m
@@ -33,7 +33,19 @@ - (void)setBridge:(RCTBridge *)bridge
 
 - (NSDictionary<NSString *, id> *)constantsToExport
 {
-  NSURL *initialURL = _bridge.launchOptions[UIApplicationLaunchOptionsURLKey];
+  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 @{@"initialURL": RCTNullIfNil(initialURL.absoluteString)};
 }
 
@@ -77,10 +89,8 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
                   resolve:(RCTPromiseResolveBlock)resolve
                   reject:(__unused RCTPromiseRejectBlock)reject)
 {
-  // TODO: we should really report success/failure via the promise here
-  // Doesn't really matter what thread we call this on since it exits the app
-  [RCTSharedApplication() openURL:URL];
-  resolve(@[@YES]);
+  BOOL opened = [RCTSharedApplication() openURL:URL];
+  resolve(@(opened));
 }
 
 RCT_EXPORT_METHOD(canOpenURL:(NSURL *)URL
@@ -90,13 +100,13 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
   if (RCTRunningInAppExtension()) {
     // Technically Today widgets can open urls, but supporting that would require
     // a reference to the NSExtensionContext
-    resolve(@[@NO]);
+    resolve(@NO);
     return;
   }
 
   // This can be expensive, so we deliberately don't call on main thread
   BOOL canOpen = [RCTSharedApplication() canOpenURL:URL];
-  resolve(@[@(canOpen)]);
+  resolve(@(canOpen));
 }
 
 @end
diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js
index 9df6208d7860a9..318171e3de0919 100644
--- a/Libraries/Modal/Modal.js
+++ b/Libraries/Modal/Modal.js
@@ -49,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}
@@ -63,6 +64,7 @@ Modal.propTypes = {
   transparent: PropTypes.bool,
   visible: PropTypes.bool,
   onDismiss: PropTypes.func,
+  onShow: PropTypes.func,
 };
 
 Modal.defaultProps = {
diff --git a/Libraries/NavigationExperimental/NavigationAbstractPanResponder.js b/Libraries/NavigationExperimental/NavigationAbstractPanResponder.js
new file mode 100644
index 00000000000000..98fdbf32d0055f
--- /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
index 8e6dce68045287..8eb8e832c74fd2 100644
--- a/Libraries/NavigationExperimental/NavigationAnimatedView.js
+++ b/Libraries/NavigationExperimental/NavigationAnimatedView.js
@@ -11,23 +11,22 @@
  */
 'use strict';
 
-var Animated = require('Animated');
-var Map = require('Map');
-var NavigationStateUtils = require('NavigationState');
-var NavigationContainer = require('NavigationContainer');
-var React = require('react-native');
-var View = require('View');
+const Animated = require('Animated');
+const NavigationContainer = require('NavigationContainer');
+const NavigationPropTypes = require('NavigationPropTypes');
+const NavigationStateUtils = require('NavigationStateUtils');
+const React = require('react-native');
+const View = require('View');
 
 import type {
-  NavigationState,
+  NavigationAnimatedValue,
+  NavigationAnimationSetter,
+  NavigationLayout,
   NavigationParentState,
-} from 'NavigationState';
-
-type NavigationScene = {
-  index: number,
-  state: NavigationState,
-  isStale: boolean,
-};
+  NavigationScene,
+  NavigationSceneRenderer,
+  NavigationState,
+} from 'NavigationTypeDefinition';
 
 /**
  * Helper function to compare route keys (e.g. "9", "11").
@@ -48,7 +47,7 @@ function compareKey(one: string, two: string): number {
  */
 function compareScenes(
   one: NavigationScene,
-  two: NavigationScene
+  two: NavigationScene,
 ): number {
   if (one.index > two.index) {
     return 1;
@@ -58,168 +57,236 @@ function compareScenes(
   }
 
   return compareKey(
-    one.state.key,
-    two.state.key
+    one.navigationState.key,
+    two.navigationState.key,
   );
 }
 
-type Layout = {
-  initWidth: number,
-  initHeight: number,
-  width: Animated.Value;
-  height: Animated.Value;
+type Props = {
+  applyAnimation: NavigationAnimationSetter,
+  navigationState: NavigationParentState,
+  onNavigate: (action: any) => void,
+  renderOverlay: ?NavigationSceneRenderer,
+  renderScene: NavigationSceneRenderer,
+  style: any,
 };
 
-type OverlayRenderer = (
-  position: Animated.Value,
-  layout: Layout
-) => ReactElement;
+type State = {
+  position: NavigationAnimatedValue,
+  scenes: Array<NavigationScene>,
+};
 
-type SceneRenderer = (
-  state: NavigationState,
-  index: number,
-  position: Animated.Value,
-  layout: Layout
-) => ReactElement
+const {PropTypes} = React;
 
-type Props = {
-  navigationState: NavigationParentState;
-  renderScene: SceneRenderer;
-  renderOverlay: ?OverlayRenderer;
-  style: any;
+const propTypes = {
+  applyAnimation: PropTypes.func,
+  navigationState: NavigationPropTypes.navigationState.isRequired,
+  onNavigate: PropTypes.func.isRequired,
+  renderOverlay: PropTypes.func,
+  renderScene: PropTypes.func.isRequired,
 };
 
-class NavigationAnimatedView extends React.Component {
-  _animatedHeight: Animated.Value;
-  _animatedWidth: Animated.Value;
-  _lastHeight: number;
-  _lastWidth: number;
+const defaultProps = {
+  applyAnimation: (
+    position: NavigationAnimatedValue,
+    navigationState: NavigationParentState,
+  ) => {
+    Animated.spring(
+      position,
+      {
+        bounciness: 0,
+        toValue: navigationState.index,
+      }
+    ).start();
+  },
+};
+
+class NavigationAnimatedView
+  extends React.Component<any, Props, State> {
+
+  _layout: NavigationLayout;
+  _onLayout: (event: any) => void;
+  _onProgressChange: (data: {value: number}) => void;
+  _positionListener: any;
+
   props: Props;
-  constructor(props) {
-    super(props);
-    this._animatedHeight = new Animated.Value(0);
-    this._animatedWidth = new Animated.Value(0);
+  state: State;
+
+  constructor(props: Props, context: any) {
+    super(props, context);
+
+    this._layout = {
+      initWidth: 0,
+      initHeight: 0,
+      width: new Animated.Value(0),
+      height: new Animated.Value(0),
+    };
+
     this.state = {
       position: new Animated.Value(this.props.navigationState.index),
-      scenes: new Map(),
+      scenes: this._reduceScenes([], this.props.navigationState),
     };
   }
-  componentWillMount() {
-    this.setState({
-      scenes: this._reduceScenes(this.state.scenes, this.props.navigationState),
-    });
+
+  componentWillMount(): void {
+    this._onLayout = this._onLayout.bind(this);
+    this._onProgressChange = this._onProgressChange.bind(this);
   }
-  componentDidMount() {
-    this.postionListener = this.state.position.addListener(this._onProgressChange.bind(this));
+
+  componentDidMount(): void {
+    this._positionListener =
+      this.state.position.addListener(this._onProgressChange);
   }
-  componentWillReceiveProps(nextProps) {
+
+  componentWillReceiveProps(nextProps: Props): void {
     if (nextProps.navigationState !== this.props.navigationState) {
       this.setState({
-        scenes: this._reduceScenes(this.state.scenes, nextProps.navigationState, this.props.navigationState),
+        scenes: this._reduceScenes(
+          this.state.scenes,
+          nextProps.navigationState,
+          this.props.navigationState
+        ),
       });
     }
   }
-  componentDidUpdate(lastProps) {
+
+  componentDidUpdate(lastProps: Props): void {
     if (lastProps.navigationState.index !== this.props.navigationState.index) {
-      Animated.spring(
+      this.props.applyAnimation(
         this.state.position,
-        {toValue: this.props.navigationState.index}
-      ).start();
+        this.props.navigationState,
+        lastProps.navigationState
+      );
     }
   }
-  componentWillUnmount() {
-    if (this.postionListener) {
-      this.state.position.removeListener(this.postionListener);
-      this.postionListener = null;
-    }
+
+  componentWillUnmount(): void {
+    this.state.position.removeListener(this._positionListener);
   }
+
   _onProgressChange(data: Object): void {
-    if (Math.abs(data.value - this.props.navigationState.index) > Number.EPSILON) {
+    const delta = Math.abs(data.value - this.props.navigationState.index);
+    if (delta > 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, });
-      }
+
+    const scenes = this.state.scenes.filter(scene => {
+      return !scene.isStale;
     });
+
+    if (scenes.length !== this.state.scenes.length) {
+      this.setState({ scenes });
+    }
   }
+
   _reduceScenes(
     scenes: Array<NavigationScene>,
     nextState: NavigationParentState,
     lastState: ?NavigationParentState
   ): Array<NavigationScene> {
-    let nextScenes = nextState.children.map((child, index) => {
+    const nextScenes = nextState.children.map((child, index) => {
       return {
         index,
-        state: child,
         isStale: false,
+        navigationState: child,
       };
     });
 
     if (lastState) {
-      lastState.children.forEach((child, index) => {
-        if (!NavigationStateUtils.get(nextState, child.key)) {
+      lastState.children.forEach((child: NavigationState, index: number) => {
+        if (
+          !NavigationStateUtils.get(nextState, child.key) &&
+          index !== nextState.index
+        ) {
           nextScenes.push({
             index,
-            state: child,
             isStale: true,
+            navigationState: child,
           });
         }
       });
     }
 
-    nextScenes = nextScenes.sort(compareScenes);
-
-    return nextScenes;
+    return nextScenes.sort(compareScenes);
   }
-  render() {
+
+  render(): ReactElement {
     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;
-        }}
+        onLayout={this._onLayout}
         style={this.props.style}>
         {this.state.scenes.map(this._renderScene, this)}
-        {this._renderOverlay(this._renderOverlay, 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(
-      scene.state,
-      scene.index,
-      this.state.position,
-      this._getLayout()
-    );
+
+  _renderScene(scene: NavigationScene): ?ReactElement {
+    const {
+      navigationState,
+      onNavigate,
+      renderScene,
+    } = this.props;
+
+    const {
+      position,
+      scenes,
+    } = this.state;
+
+    return renderScene({
+      layout: this._layout,
+      navigationState,
+      onNavigate,
+      position,
+      scene,
+      scenes,
+    });
   }
-  _renderOverlay() {
-    const {renderOverlay} = this.props;
-    if (renderOverlay) {
-      return renderOverlay(
-        this.state.position,
-        this._getLayout()
-      );
+
+  _renderOverlay(): ?ReactElement {
+    if (this.props.renderOverlay) {
+      const {
+        navigationState,
+        onNavigate,
+        renderOverlay,
+      } = this.props;
+
+      const {
+        position,
+        scenes,
+      } = this.state;
+
+      return renderOverlay({
+        layout: this._layout,
+        navigationState,
+        onNavigate,
+        position,
+        scene: scenes[navigationState.index],
+        scenes,
+      });
     }
     return null;
   }
+
+  _onLayout(event: any): void {
+    const {height, width} = event.nativeEvent.layout;
+
+    const layout = {
+      ...this._layout,
+      initHeight: height,
+      initWidth: width,
+    };
+
+    this._layout = layout;
+
+    layout.height.setValue(height);
+    layout.width.setValue(width);
+  }
 }
 
+NavigationAnimatedView.propTypes = propTypes;
+NavigationAnimatedView.defaultProps = defaultProps;
+
 NavigationAnimatedView = NavigationContainer.create(NavigationAnimatedView);
 
 module.exports = NavigationAnimatedView;
diff --git a/Libraries/NavigationExperimental/NavigationContainer.js b/Libraries/NavigationExperimental/NavigationContainer.js
index 8e178f1c6faa56..9ebc8a925e3f94 100644
--- a/Libraries/NavigationExperimental/NavigationContainer.js
+++ b/Libraries/NavigationExperimental/NavigationContainer.js
@@ -11,10 +11,12 @@
  */
 'use strict';
 
-var React = require('react-native');
+var React = require('React');
 var NavigationRootContainer = require('NavigationRootContainer');
 
-function createNavigationContainer(Component: React.Component): React.Component {
+function createNavigationContainer(
+  Component: ReactClass<any>,
+): ReactClass {
   class NavigationComponent extends React.Component {
     render() {
       return (
diff --git a/Libraries/NavigationExperimental/NavigationExperimental.js b/Libraries/NavigationExperimental/NavigationExperimental.js
index 9d37015f6a17b2..ca6bb627d5597b 100644
--- a/Libraries/NavigationExperimental/NavigationExperimental.js
+++ b/Libraries/NavigationExperimental/NavigationExperimental.js
@@ -13,16 +13,18 @@
 
 const NavigationAnimatedView = require('NavigationAnimatedView');
 const NavigationCard = require('NavigationCard');
+const NavigationCardStack = require('NavigationCardStack');
 const NavigationContainer = require('NavigationContainer');
 const NavigationHeader = require('NavigationHeader');
-const NavigationRootContainer = require('NavigationRootContainer');
+const NavigationLegacyNavigator = require('NavigationLegacyNavigator');
 const NavigationReducer = require('NavigationReducer');
-const NavigationState = require('NavigationState');
+const NavigationRootContainer = require('NavigationRootContainer');
+const NavigationStateUtils = require('NavigationStateUtils');
 const NavigationView = require('NavigationView');
 
 const NavigationExperimental = {
   // Core
-  State: NavigationState,
+  StateUtils: NavigationStateUtils,
   Reducer: NavigationReducer,
 
   // Containers
@@ -34,8 +36,10 @@ const NavigationExperimental = {
   AnimatedView: NavigationAnimatedView,
 
   // CustomComponents:
-  Header: NavigationHeader,
   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 00000000000000..2318a70c6ca30c
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationLinearPanResponder.js
@@ -0,0 +1,182 @@
+/**
+ * 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');
+
+import type {
+  NavigationActionCaller,
+  NavigationLayout,
+  NavigationPosition,
+} from 'NavigationTypeDefinition';
+
+/**
+ * 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',
+};
+
+export type NavigationGestureDirection =  $Enum<typeof Directions>;
+
+/**
+ * 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'},
+};
+
+/**
+ * The type interface of the object that provides the information required by
+ * NavigationLinearPanResponder.
+ */
+export type NavigationLinearPanResponderDelegate = {
+  getDirection: () => NavigationGestureDirection;
+  getIndex: () => number,
+  getLayout: () => NavigationLayout,
+  getPosition: () => NavigationPosition,
+  onNavigate: NavigationActionCaller,
+};
+
+/**
+ * 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/NavigationPropTypes.js b/Libraries/NavigationExperimental/NavigationPropTypes.js
new file mode 100644
index 00000000000000..b5d87834199644
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationPropTypes.js
@@ -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.
+ *
+ * @providesModule NavigationPropTypes
+ * @flow
+ */
+'use strict';
+
+/**
+ * React component PropTypes Definitions. Consider using this as a supplementary
+ * measure with `NavigationTypeDefinition`. This helps to capture the propType
+ * error at run-time, where as `NavigationTypeDefinition` capture the flow
+ * type check errors at build time.
+ */
+
+const Animated = require('Animated');
+const React = require('react-native');
+
+const {PropTypes} = React;
+
+/* NavigationAction */
+const action =  PropTypes.shape({
+  type: PropTypes.string.isRequired,
+});
+
+/* NavigationAnimatedValue  */
+const animatedValue = PropTypes.instanceOf(Animated.Value);
+
+/* NavigationState  */
+const navigationState = PropTypes.shape({
+  key: PropTypes.string.isRequired,
+});
+
+/* NavigationParentState  */
+const navigationParentState = PropTypes.shape({
+  index: PropTypes.number.isRequired,
+  key: PropTypes.string.isRequired,
+  children: PropTypes.arrayOf(navigationState),
+});
+
+/* NavigationLayout */
+const layout = PropTypes.shape({
+  height: animatedValue,
+  initHeight: PropTypes.number.isRequired,
+  initWidth: PropTypes.number.isRequired,
+  width: animatedValue,
+});
+
+/* NavigationScene */
+const scene = PropTypes.shape({
+  index: PropTypes.number.isRequired,
+  isStale: PropTypes.bool.isRequired,
+  navigationState,
+});
+
+/* NavigationSceneRendererProps */
+const SceneRenderer = {
+  layout: layout.isRequired,
+  navigationState: navigationParentState.isRequired,
+  onNavigate: PropTypes.func.isRequired,
+  position: animatedValue.isRequired,
+  scene: scene.isRequired,
+  scenes: PropTypes.arrayOf(scene).isRequired,
+};
+
+module.exports = {
+  SceneRenderer,
+  action,
+  navigationParentState,
+  navigationState,
+};
diff --git a/Libraries/NavigationExperimental/NavigationRootContainer.js b/Libraries/NavigationExperimental/NavigationRootContainer.js
index b40c3114fdd36e..1cefdc6d8707cf 100644
--- a/Libraries/NavigationExperimental/NavigationRootContainer.js
+++ b/Libraries/NavigationExperimental/NavigationRootContainer.js
@@ -12,22 +12,19 @@
 'use strict';
 
 const AsyncStorage = require('AsyncStorage');
-const React = require('React');
-const BackAndroid = require('BackAndroid');
+const Linking = require('Linking');
 const Platform = require('Platform');
+const React = require('React');
+const NavigationPropTypes = require('NavigationPropTypes');
 
 import type {
-  NavigationState,
-  NavigationReducer
-} from 'NavigationState';
-
-export type NavigationRenderer = (
-  navigationState: NavigationState,
-  onNavigate: Function
-) => ReactElement;
+  NavigationAction,
+  NavigationReducer,
+  NavigationRenderer,
+} from 'NavigationTypeDefinition';
 
 export type BackAction = {
-  type: 'BackAction';
+  type: 'BackAction',
 };
 
 function getBackAction(): BackAction {
@@ -35,28 +32,81 @@ function getBackAction(): BackAction {
 }
 
 type Props = {
-  renderNavigation: NavigationRenderer;
-  reducer: NavigationReducer;
-  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),
+
+  /*
+   * Provide this key, and the container will store the navigation state in
+   * AsyncStorage through refreshes, with the provided key
+   */
+  persistenceKey: ?string,
+
+
+  /*
+   * A function that will output the latest navigation state as a function of
+   * the (optional) previous state, and an action
+   */
+  reducer: NavigationReducer,
+
+
+  /*
+   * Set up the rendering of the app for a given navigation state
+   */
+  renderNavigation: NavigationRenderer,
+};
+
+const {PropTypes} = React;
+
+const propTypes = {
+  initialAction: NavigationPropTypes.action.isRequired,
+  linkingActionMap: PropTypes.func,
+  persistenceKey: PropTypes.string,
+  reducer: PropTypes.func.isRequired,
+  renderNavigation: PropTypes.func.isRequired,
+};
+
+const defaultProps = {
+  initialAction: {
+    type: 'RootContainerInitialAction',
+  },
 };
 
 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, null);
+      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, null),
+            navState: this.props.reducer(null, this.props.initialAction),
           });
           return;
         }
@@ -66,11 +116,31 @@ class NavigationRootContainer extends React.Component {
       });
     }
   }
+
+  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) {
@@ -79,11 +149,14 @@ class NavigationRootContainer extends React.Component {
     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,
@@ -94,9 +167,11 @@ class NavigationRootContainer extends React.Component {
 }
 
 NavigationRootContainer.childContextTypes = {
-  onNavigate: React.PropTypes.func,
+  onNavigate: PropTypes.func,
 };
 
+NavigationRootContainer.propTypes = propTypes;
+NavigationRootContainer.defaultProps = defaultProps;
 NavigationRootContainer.getBackAction = getBackAction;
 
 module.exports = NavigationRootContainer;
diff --git a/Libraries/NavigationExperimental/NavigationState.js b/Libraries/NavigationExperimental/NavigationStateUtils.js
similarity index 64%
rename from Libraries/NavigationExperimental/NavigationState.js
rename to Libraries/NavigationExperimental/NavigationStateUtils.js
index d78d3559177ca4..63da07faf4c53d 100644
--- a/Libraries/NavigationExperimental/NavigationState.js
+++ b/Libraries/NavigationExperimental/NavigationStateUtils.js
@@ -6,38 +6,19 @@
  * LICENSE file in 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 NavigationState
+ * @providesModule NavigationStateUtils
  * @flow
  */
 'use strict';
 
-const invariant = require('invariant');
+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;
-};
+import type {
+  NavigationState,
+  NavigationParentState,
+} from 'NavigationTypeDefinition';
 
-export type NavigationReducer = (
-  state: ?NavigationState,
-  action: ?NavigationAction
-) => ?NavigationState;
-
-export type NavigationReducerWithDefault = (
-  state: ?NavigationState,
-  action: ?any
-) => NavigationState;
-
-export function getParent(state: NavigationState): ?NavigationParentState {
+function getParent(state: NavigationState): ?NavigationParentState {
   if (
     (state instanceof Object) &&
     (state.children instanceof Array) &&
@@ -50,7 +31,7 @@ export function getParent(state: NavigationState): ?NavigationParentState {
   return null;
 }
 
-export function get(state: NavigationState, key: string): ?NavigationState {
+function get(state: NavigationState, key: string): ?NavigationState {
   const parentState = getParent(state);
   if (!parentState) {
     return null;
@@ -59,7 +40,7 @@ export function get(state: NavigationState, key: string): ?NavigationState {
   return childState || null;
 }
 
-export function indexOf(state: NavigationState, key: string): ?number {
+function indexOf(state: NavigationState, key: string): ?number {
   const parentState = getParent(state);
   if (!parentState) {
     return null;
@@ -71,14 +52,10 @@ export function indexOf(state: NavigationState, key: string): ?number {
   return index;
 }
 
-export function push(state: NavigationState, newChildState: NavigationState): NavigationState {
-  const parentState = getParent(state);
-  if (!parentState) {
-    return state;
-  }
-  var lastChildren: Array<NavigationState> = parentState.children;
+function push(state: NavigationParentState, newChildState: NavigationState): NavigationParentState {
+  var lastChildren: Array<NavigationState> = state.children;
   return {
-    ...parentState,
+    ...state,
     children: [
       ...lastChildren,
       newChildState,
@@ -87,20 +64,16 @@ export function push(state: NavigationState, newChildState: NavigationState): Na
   };
 }
 
-export function pop(state: NavigationState): NavigationState {
-  const parentState = getParent(state);
-  if (!parentState) {
-    return state;
-  }
-  const lastChildren = parentState.children;
+function pop(state: NavigationParentState): NavigationParentState {
+  const lastChildren = state.children;
   return {
-    ...parentState,
+    ...state,
     children: lastChildren.slice(0, lastChildren.length - 1),
     index: lastChildren.length - 2,
   };
 }
 
-export function reset(state: NavigationState, nextChildren: ?Array<NavigationState>, nextIndex: ?number): NavigationState {
+function reset(state: NavigationState, nextChildren: ?Array<NavigationState>, nextIndex: ?number): NavigationState {
   const parentState = getParent(state);
   if (!parentState) {
     return state;
@@ -117,7 +90,7 @@ export function reset(state: NavigationState, nextChildren: ?Array<NavigationSta
   };
 }
 
-export function set(state: ?NavigationState, key: string, nextChildren: Array<NavigationState>, nextIndex: number): NavigationState {
+function set(state: ?NavigationState, key: string, nextChildren: Array<NavigationState>, nextIndex: number): NavigationState {
   if (!state) {
     return {
       children: nextChildren,
@@ -144,7 +117,7 @@ export function set(state: ?NavigationState, key: string, nextChildren: Array<Na
   };
 }
 
-export function jumpToIndex(state: NavigationState, index: number): NavigationState {
+function jumpToIndex(state: NavigationState, index: number): NavigationState {
   const parentState = getParent(state);
   if (parentState && parentState.index === index) {
     return parentState;
@@ -155,7 +128,7 @@ export function jumpToIndex(state: NavigationState, index: number): NavigationSt
   };
 }
 
-export function jumpTo(state: NavigationState, key: string): NavigationState {
+function jumpTo(state: NavigationState, key: string): NavigationState {
   const parentState = getParent(state);
   if (!parentState) {
     return state;
@@ -171,7 +144,7 @@ export function jumpTo(state: NavigationState, key: string): NavigationState {
   };
 }
 
-export function replaceAt(state: NavigationState, key: string, newState: NavigationState): NavigationState {
+function replaceAt(state: NavigationState, key: string, newState: NavigationState): NavigationState {
   const parentState = getParent(state);
   if (!parentState) {
     return state;
@@ -189,7 +162,7 @@ export function replaceAt(state: NavigationState, key: string, newState: Navigat
   };
 }
 
-export function replaceAtIndex(state: NavigationState, index: number, newState: NavigationState): NavigationState {
+function replaceAtIndex(state: NavigationState, index: number, newState: NavigationState): NavigationState {
   const parentState = getParent(state);
   if (!parentState) {
     return state;
@@ -201,3 +174,19 @@ export function replaceAtIndex(state: NavigationState, index: number, newState:
     children,
   };
 }
+
+const NavigationStateUtils = {
+  getParent,
+  get: get,
+  indexOf,
+  push,
+  pop,
+  reset,
+  set: set,
+  jumpToIndex,
+  jumpTo,
+  replaceAt,
+  replaceAtIndex,
+};
+
+module.exports = NavigationStateUtils;
diff --git a/Libraries/NavigationExperimental/NavigationTypeDefinition.js b/Libraries/NavigationExperimental/NavigationTypeDefinition.js
new file mode 100644
index 00000000000000..3f6f6e43922af1
--- /dev/null
+++ b/Libraries/NavigationExperimental/NavigationTypeDefinition.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in 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 NavigationTypeDefinition
+ * @flow
+ */
+'use strict';
+
+const Animated = require('Animated');
+
+// Object Instances
+
+export type NavigationAnimatedValue = Animated.Value;
+
+// Value  & Structs.
+
+export type NavigationGestureDirection = 'horizontal' | 'vertical';
+
+export type NavigationState = {
+  key: string,
+};
+
+export type NavigationParentState = {
+  index: number,
+  key: string,
+  children: Array<NavigationState>,
+};
+
+export type NavigationAction = any;
+
+export type NavigationLayout = {
+  height: NavigationAnimatedValue,
+  initHeight: number,
+  initWidth: number,
+  width: NavigationAnimatedValue,
+};
+
+export type NavigationPosition = NavigationAnimatedValue;
+
+export type NavigationScene = {
+  index: number,
+  isStale: boolean,
+  navigationState: NavigationState,
+};
+
+export type NavigationSceneRendererProps = {
+  // The layout of the containing view of the scenes.
+  layout: NavigationLayout,
+
+  // The navigation state of the containing view.
+  navigationState: NavigationParentState,
+
+  // Callback to navigation with an action.
+  onNavigate: NavigationActionCaller,
+
+  // The progressive index of the containing view's navigation state.
+  position: NavigationPosition,
+
+  // The scene to render.
+  scene: NavigationScene,
+
+  // All the scenes of the containing view's.
+  scenes: Array<NavigationScene>,
+};
+
+// Functions.
+
+export type NavigationActionCaller = Function;
+
+export type NavigationAnimationSetter = (
+  position: NavigationAnimatedValue,
+  newState: NavigationParentState,
+  lastState: NavigationParentState,
+) => void;
+
+export type NavigationRenderer = (
+  navigationState: NavigationState,
+  onNavigate: NavigationActionCaller,
+) => ReactElement;
+
+export type NavigationReducer = (
+  state: ?NavigationState,
+  action: ?NavigationAction,
+) => NavigationState;
+
+export type NavigationSceneRenderer = (
+  props: NavigationSceneRendererProps,
+) => ?ReactElement;
diff --git a/Libraries/NavigationExperimental/NavigationView.js b/Libraries/NavigationExperimental/NavigationView.js
index 5e0fc8b28f71e2..5789cfbf3a9eb9 100644
--- a/Libraries/NavigationExperimental/NavigationView.js
+++ b/Libraries/NavigationExperimental/NavigationView.js
@@ -11,12 +11,10 @@
  */
 'use strict';
 
-var React = require('react-native');
+var React = require('React');
 var NavigationContainer = require('NavigationContainer');
-var {
-  StyleSheet,
-  View,
-} = React;
+var StyleSheet = require('StyleSheet');
+var View = require('View');
 
 var NavigationView = React.createClass({
   propTypes: {
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js
index f0947fd56645ff..d7e2d1afa5fd55 100644
--- a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js
+++ b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js
@@ -20,18 +20,21 @@
 import type {
   NavigationState,
   NavigationReducer
-} from 'NavigationState';
+} from 'NavigationTypeDefinition';
 
-function NavigationFindReducer(reducers: Array<NavigationReducer>): NavigationReducer {
-  return function(lastState: ?NavigationState, action: ?any): ?NavigationState {
+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;
+        return newState || defaultState;
       }
     }
-    return lastState;
+    return lastState || defaultState;
   };
 }
 
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js
index 5dcf34966daf8c..9a22d1482db03c 100644
--- a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js
+++ b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js
@@ -11,12 +11,13 @@
  */
 'use strict';
 
-var NavigationStateUtils = require('NavigationState');
+var NavigationStateUtils = require('NavigationStateUtils');
 
 import type {
   NavigationState,
+  NavigationParentState,
   NavigationReducer,
-} from 'NavigationState';
+} from 'NavigationTypeDefinition';
 
 import type {
   BackAction,
@@ -26,121 +27,82 @@ export type NavigationStackReducerAction = BackAction | {
   type: string,
 };
 
-const ActionTypes = {
-  PUSH: 'react-native/NavigationExperimental/stack-push',
-  POP: 'react-native/NavigationExperimental/stack-pop',
-  JUMP_TO: 'react-native/NavigationExperimental/stack-jumpTo',
-  JUMP_TO_INDEX: 'react-native/NavigationExperimental/stack-jumpToIndex',
-  RESET: 'react-native/NavigationExperimental/stack-reset',
-};
-
-const DEFAULT_KEY = 'NAV_STACK_DEFAULT_KEY';
-
-function NavigationStackPushAction(state: NavigationState): NavigationStackReducerAction {
-  return {
-    type: ActionTypes.PUSH,
-    state,
-  };
-}
+export type ReducerForStateHandler = (state: NavigationState) => NavigationReducer;
 
-function NavigationStackPopAction(): NavigationStackReducerAction {
-  return {
-    type: ActionTypes.POP,
-  };
-}
+export type PushedReducerForActionHandler = (action: any, lastState: NavigationParentState) => ?NavigationReducer;
 
-function NavigationStackJumpToAction(key: string): NavigationStackReducerAction {
-  return {
-    type: ActionTypes.JUMP_TO,
-    key,
-  };
-}
+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;
 
-function NavigationStackJumpToIndexAction(index: number): NavigationStackReducerAction {
-  return {
-    type: ActionTypes.JUMP_TO_INDEX,
-    index,
-  };
-}
+  /*
+   * 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;
 
-function NavigationStackResetAction(children: Array<NavigationState>, index: number): NavigationStackReducerAction {
-  return {
-    type: ActionTypes.RESET,
-    index,
-    children,
-  };
-}
-
-type StackReducerConfig = {
-  initialStates: Array<NavigationState>;
-  initialIndex: ?number;
-  key: ?string;
-  matchAction: (action: any) => boolean;
-  actionStateMap: (action: any) => NavigationState;
+  /*
+   * 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;
 };
 
-function NavigationStackReducer({initialStates, initialIndex, key, matchAction, actionStateMap}: StackReducerConfig): NavigationReducer {
+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 (key == null) {
-      key = DEFAULT_KEY;
-    }
-    if (initialIndex == null) {
-      initialIndex = initialStates.length - 1;
-    }
     if (!lastState) {
-      lastState = {
-        index: initialIndex,
-        children: initialStates,
-        key,
-      };
+      return initialState;
     }
     const lastParentState = NavigationStateUtils.getParent(lastState);
-    if (!action || !lastParentState) {
+    if (!lastParentState) {
       return lastState;
     }
     switch (action.type) {
-      case ActionTypes.PUSH:
-        return NavigationStateUtils.push(
-          lastParentState,
-          action.state
-        );
-      case ActionTypes.POP:
+      case 'back':
       case 'BackAction':
         if (lastParentState.index === 0 || lastParentState.children.length === 1) {
           return lastParentState;
         }
         return NavigationStateUtils.pop(lastParentState);
-      case ActionTypes.JUMP_TO:
-        return NavigationStateUtils.jumpTo(
-          lastParentState,
-          action.key
-        );
-      case ActionTypes.JUMP_TO_INDEX:
-        return NavigationStateUtils.jumpToIndex(
-          lastParentState,
-          action.index
-        );
-      case ActionTypes.RESET:
-        return {
-          ...lastParentState,
-          index: action.index,
-          children: action.children,
-        };
     }
-    if (matchAction(action)) {
+
+    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, lastParentState);
+    if (subReducerToPush) {
       return NavigationStateUtils.push(
         lastParentState,
-        actionStateMap(action)
+        subReducerToPush(null, action)
       );
     }
     return lastParentState;
   };
 }
 
-NavigationStackReducer.PushAction = NavigationStackPushAction;
-NavigationStackReducer.PopAction = NavigationStackPopAction;
-NavigationStackReducer.JumpToAction = NavigationStackJumpToAction;
-NavigationStackReducer.JumpToIndexAction = NavigationStackJumpToIndexAction;
-NavigationStackReducer.ResetAction = NavigationStackResetAction;
-
 module.exports = NavigationStackReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js
index 9d6d76a4ff2224..5063b72bb07958 100644
--- a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js
+++ b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js
@@ -12,22 +12,17 @@
 'use strict';
 
 const NavigationFindReducer = require('NavigationFindReducer');
-const NavigationStateUtils = require('NavigationState');
+const NavigationStateUtils = require('NavigationStateUtils');
 
 import type {
   NavigationReducer,
-  NavigationReducerWithDefault,
   NavigationState,
-  NavigationParentState
-} from 'NavigationState';
+} from 'NavigationTypeDefinition';
 
 const ActionTypes = {
   JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo',
-  ON_TAB_ACTION: 'react-native/NavigationExperimental/tabs-onTabAction',
 };
 
-const DEFAULT_KEY = 'TABS_STATE_DEFAULT_KEY';
-
 export type JumpToAction = {
   type: typeof ActionTypes.JUMP_TO,
   index: number,
@@ -39,37 +34,18 @@ function NavigationTabsJumpToAction(index: number): JumpToAction {
   };
 }
 
-export type OnTabAction = {
-  type: string,
-  index: number,
-  action: any,
-};
-function NavigationTabsOnTabAction(index: number, action: any): OnTabAction {
-  return {
-    type: ActionTypes.ON_TAB_ACTION,
-    index,
-    action,
-  };
-}
-
 type TabsReducerConfig = {
   key: string;
-  initialIndex: ?number;
-  tabReducers: Array<NavigationReducerWithDefault>;
+  initialIndex: number;
+  tabReducers: Array<NavigationReducer>;
 };
 
 function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer {
-  if (initialIndex == null) {
-    initialIndex = 0;
-  }
-  if (key == null) {
-    key = DEFAULT_KEY;
-  }
-  return function(lastNavState: ?NavigationState, action: ?any): ?NavigationState {
+  return function(lastNavState: ?NavigationState, action: ?any): NavigationState {
     if (!lastNavState) {
       lastNavState = {
         children: tabReducers.map(reducer => reducer(null, null)),
-        index: initialIndex,
+        index: initialIndex || 0,
         key,
       };
     }
@@ -86,37 +62,17 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf
         action.index,
       );
     }
-    if (action.type === ActionTypes.ON_TAB_ACTION) {
-      const onTabAction: OnTabAction = action;
-      const lastTabRoute = lastParentNavState.children[onTabAction.index];
-      const tabReducer = tabReducers[onTabAction.index];
-      if (tabReducer) {
-        const newTabRoute = tabReducer(lastTabRoute, action.action);
-        if (newTabRoute && newTabRoute !== lastTabRoute) {
-          let navState = NavigationStateUtils.replaceAtIndex(
-            lastParentNavState,
-            onTabAction.index,
-            newTabRoute
-          );
-          navState = NavigationStateUtils.jumpToIndex(
-            navState,
-            onTabAction.index
-          );
-          return navState;
-        }
-      }
-    }
     const subReducers = tabReducers.map((tabReducer, tabIndex) => {
-      return function reduceTab(lastNavState: ?NavigationState, tabAction: ?any): ?NavigationState {
-        if (!tabReducer || !lastNavState) {
-          return lastNavState;
+      return function(navState: ?NavigationState, tabAction: any): NavigationState {
+        if (!navState) {
+          return lastParentNavState;
         }
-        const lastParentNavState = NavigationStateUtils.getParent(lastNavState);
-        const lastSubTabState = lastParentNavState && lastParentNavState.children[tabIndex];
-        const nextSubTabState = tabReducer(lastSubTabState, tabAction);
-        if (nextSubTabState && lastSubTabState !== nextSubTabState) {
-          const tabs = lastParentNavState && lastParentNavState.children || [];
-          tabs[tabIndex] = nextSubTabState;
+        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,
@@ -127,22 +83,21 @@ function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConf
       };
     });
     let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0];
-    subReducers.unshift(selectedTabReducer);
-    subReducers.push((lastParentNavState: ?NavigationState, action: ?any) => {
-      if (lastParentNavState && action && action.type === 'BackAction') {
+    subReducers.unshift(function(navState: ?NavigationState, action: any): NavigationState {
+      if (navState && action.type === 'BackAction') {
         return NavigationStateUtils.jumpToIndex(
           lastParentNavState,
-          0
+          initialIndex || 0
         );
       }
       return lastParentNavState;
     });
-    const findReducer = NavigationFindReducer(subReducers);
+    subReducers.unshift(selectedTabReducer);
+    const findReducer = NavigationFindReducer(subReducers, lastParentNavState);
     return findReducer(lastParentNavState, action);
   };
 }
 
 NavigationTabsReducer.JumpToAction = NavigationTabsJumpToAction;
-NavigationTabsReducer.OnTabAction = NavigationTabsOnTabAction;
 
 module.exports = NavigationTabsReducer;
diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js
index e67e64f2e481b7..660e1c03ef9464 100644
--- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js
@@ -11,8 +11,7 @@
 'use strict';
 
 jest
- .autoMockOff()
- .mock('ErrorUtils');
+ .dontMock('NavigationFindReducer');
 
 const NavigationFindReducer = require('NavigationFindReducer');
 
diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js
index 4d497346cfeb9d..42edc9d343840f 100644
--- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js
@@ -11,187 +11,96 @@
 'use strict';
 
 jest
- .autoMockOff()
- .mock('ErrorUtils');
+ .dontMock('NavigationStackReducer')
+ .dontMock('NavigationStateUtils');
 
-const NavigationStackReducer = require('NavigationStackReducer');
+jest.setMock('React', {Component() {}, PropTypes: {}});
 
-const {
-  JumpToAction,
-  JumpToIndexAction,
-  PopAction,
-  PushAction,
-  ResetAction,
-} = NavigationStackReducer;
+const NavigationStackReducer = require('NavigationStackReducer');
+const NavigationRootContainer = require('NavigationRootContainer');
 
 describe('NavigationStackReducer', () => {
 
-  it('handles PushAction', () => {
-    const initialStates = [
-      {key: 'route0'},
-      {key: 'route1'},
-    ];
-    let reducer = NavigationStackReducer({
-      initialStates,
-      matchAction: () => true,
-      actionStateMap: (action) => action,
-    });
-
-    let state = reducer();
-    expect(state.children).toBe(initialStates);
-    expect(state.index).toBe(1);
-    expect(state.key).toBe('NAV_STACK_DEFAULT_KEY');
-
-    state = reducer(state, PushAction({key: 'route2'}));
-    expect(state.children[0].key).toBe('route0');
-    expect(state.children[1].key).toBe('route1');
-    expect(state.children[2].key).toBe('route2');
-    expect(state.index).toBe(2);
-  });
-
-  it('handles PopAction', () => {
-    let reducer = NavigationStackReducer({
-      initialStates: [
-        {key: 'a'},
-        {key: 'b'},
-      ],
-      initialIndex: 1,
-      key: 'myStack',
-      matchAction: () => true,
-      actionStateMap: (action) => action,
-    });
-
-    let state = reducer();
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children.length).toBe(2);
-    expect(state.index).toBe(1);
-    expect(state.key).toBe('myStack');
-
-    state = reducer(state, PopAction());
-    expect(state.children[0].key).toBe('a');
-    expect(state.children.length).toBe(1);
-    expect(state.index).toBe(0);
-    expect(state.key).toBe('myStack');
-
-    // make sure Pop on an single-route state is a no-op
-    state = reducer(state, PopAction());
-    expect(state.children[0].key).toBe('a');
-    expect(state.children.length).toBe(1);
-    expect(state.index).toBe(0);
-    expect(state.key).toBe('myStack');
-  });
-
-  it('handles JumpToAction', () => {
-    let reducer = NavigationStackReducer({
-      initialStates: [
+  it('provides default/initial state', () => {
+    const initialState = {
+      children: [
         {key: 'a'},
-        {key: 'b'},
-        {key: 'c'},
       ],
-      initialIndex: 0,
+      index: 0,
       key: 'myStack',
-      matchAction: () => true,
-      actionStateMap: (action) => action,
+    };
+    const reducer = NavigationStackReducer({
+      getPushedReducerForAction: (action) => null,
+      getReducerForState: (state) => () => state,
+      initialState,
     });
-
-    let state = reducer();
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children[2].key).toBe('c');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(0);
-
-    state = reducer(state, JumpToAction('b'));
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children[2].key).toBe('c');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(1);
-
-    state = reducer(state, JumpToAction('c'));
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children[2].key).toBe('c');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(2);
-
-    state = reducer(state, JumpToAction('c'));
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children[2].key).toBe('c');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(2);
-    expect(state.key).toBe('myStack');
+    const dummyAction = {type: 'dummyAction'};
+    expect(reducer(null, dummyAction)).toBe(initialState);
   });
 
-  it('handles JumpToIndexAction', () => {
-    let reducer = NavigationStackReducer({
-      initialStates: [
-        {key: 'a'},
-        {key: 'b'},
-        {key: 'c'},
-      ],
-      initialIndex: 2,
-      key: 'myStack',
-      matchAction: () => true,
-      actionStateMap: (action) => action,
+  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'
+      }
     });
-
-    let state = reducer();
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(2);
-
-    state = reducer(state, JumpToIndexAction(0));
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(0);
-
-    state = reducer(state, JumpToIndexAction(1));
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children[2].key).toBe('c');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(1);
-    expect(state.key).toBe('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 ResetAction', () => {
-    let reducer = NavigationStackReducer({
-      initialStates: [
-        {key: 'a'},
-        {key: 'b'},
-      ],
-      initialIndex: 1,
-      key: 'myStack',
-      matchAction: () => true,
-      actionStateMap: (action) => action,
+  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',
+      },
     });
 
-    let state = reducer();
-    expect(state.children[0].key).toBe('a');
-    expect(state.children[1].key).toBe('b');
-    expect(state.children.length).toBe(2);
-    expect(state.index).toBe(1);
-
-    state = reducer(state, ResetAction([{key: 'c'}, {key: 'd'}], 0));
-    expect(state.children[0].key).toBe('c');
-    expect(state.children[1].key).toBe('d');
-    expect(state.children.length).toBe(2);
-    expect(state.index).toBe(0);
+    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 newStates = [
-      {key: 'e'},
-      {key: 'f'},
-      {key: 'g'},
-    ];
+    const state2 = reducer(state1, NavigationRootContainer.getBackAction());
+    expect(state2.children[0].key).toBe('a');
+    expect(state2.children.length).toBe(1);
+    expect(state2.index).toBe(0);
 
-    state = reducer(state, ResetAction(newStates, 1));
-    expect(state.children[0].key).toBe('e');
-    expect(state.children[1].key).toBe('f');
-    expect(state.children[2].key).toBe('g');
-    expect(state.children.length).toBe(3);
-    expect(state.index).toBe(1);
-    expect(state.key).toBe('myStack');
+    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
index a39aa76bfd2311..9228d0c7b9763a 100644
--- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js
+++ b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js
@@ -11,8 +11,9 @@
 'use strict';
 
 jest
- .autoMockOff()
- .mock('ErrorUtils');
+ .dontMock('NavigationTabsReducer')
+ .dontMock('NavigationFindReducer')
+ .dontMock('NavigationStateUtils');
 
 const NavigationTabsReducer = require('NavigationTabsReducer');
 
diff --git a/packager/react-packager/src/transforms/whole-program-optimisations/index.js b/Libraries/NavigationExperimental/__mocks__/NavigationRootContainer.js
similarity index 68%
rename from packager/react-packager/src/transforms/whole-program-optimisations/index.js
rename to Libraries/NavigationExperimental/__mocks__/NavigationRootContainer.js
index f802c0f7758711..87b23c71bea6fa 100644
--- a/packager/react-packager/src/transforms/whole-program-optimisations/index.js
+++ b/Libraries/NavigationExperimental/__mocks__/NavigationRootContainer.js
@@ -6,9 +6,11 @@
  * LICENSE file in 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';
 
-// Return the list of plugins use for Whole Program Optimisations
-module.exports = [
-  require('./dead-module-elimination'),
-];
+const NavigationRootContainer = {
+  getBackAction: () => {
+    return { type: 'BackAction' };
+  }
+};
+
+module.exports = NavigationRootContainer;
\ No newline at end of file
diff --git a/Libraries/NavigationExperimental/__tests__/NavigationState-test.js b/Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js
similarity index 74%
rename from Libraries/NavigationExperimental/__tests__/NavigationState-test.js
rename to Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js
index b28aaaca20dc78..5b8cf98b5ba40e 100644
--- a/Libraries/NavigationExperimental/__tests__/NavigationState-test.js
+++ b/Libraries/NavigationExperimental/__tests__/NavigationStateUtils-test.js
@@ -11,10 +11,9 @@
 'use strict';
 
 jest
- .autoMockOff()
- .mock('ErrorUtils');
+ .dontMock('NavigationStateUtils');
 
-var NavigationState = require('NavigationState');
+var NavigationStateUtils = require('NavigationStateUtils');
 
 var VALID_PARENT_STATES = [
   {children: ['a','b'], index: 0},
@@ -35,23 +34,23 @@ var INVALID_PARENT_STATES = [
   [],
 ];
 
-describe('NavigationState', () => {
+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(NavigationState.getParent(navState)).toBe(navState);
+      expect(NavigationStateUtils.getParent(navState)).toBe(navState);
     }
     for (var i = 0; i <= INVALID_PARENT_STATES.length; i++) {
       var navState = INVALID_PARENT_STATES[0];
-      expect(NavigationState.getParent(navState)).toBe(null);
+      expect(NavigationStateUtils.getParent(navState)).toBe(null);
     }
   });
 
   it('can get children', () => {
     var fooState = {key: 'foo'};
     var navState = {children: [{key: 'foobar'}, fooState], index: 0};
-    expect(NavigationState.get(navState, 'foo')).toBe(fooState);
-    expect(NavigationState.get(navState, 'missing')).toBe(null);
+    expect(NavigationStateUtils.get(navState, 'foo')).toBe(fooState);
+    expect(NavigationStateUtils.get(navState, 'missing')).toBe(null);
   });
 });
diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js
index fff16c59860f2d..521e65f64f608c 100644
--- a/Libraries/Network/NetInfo.js
+++ b/Libraries/Network/NetInfo.js
@@ -16,6 +16,7 @@ const NativeModules = require('NativeModules');
 const Platform = require('Platform');
 const RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
 const RCTNetInfo = NativeModules.NetInfo;
+const deprecatedCallback = require('deprecatedCallback');
 
 const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange';
 
@@ -135,8 +136,12 @@ const _isConnectedSubscriptions = new Map();
  * monetary costs, data limitations or battery/performance issues.
  *
  * ```
- * NetInfo.isConnectionExpensive((isConnectionExpensive) => {
+ * NetInfo.isConnectionExpensive()
+ * .then(isConnectionExpensive => {
  *   console.log('Connection is ' + (isConnectionExpensive ? 'Expensive' : 'Not Expensive'));
+ * })
+ * .catch(error => {
+ *   console.error(error);
  * });
  * ```
  *
@@ -146,7 +151,7 @@ const _isConnectedSubscriptions = new Map();
  * internet connectivity.
  *
  * ```
- * NetInfo.isConnected.fetch().done((isConnected) => {
+ * NetInfo.isConnected.fetch().then(isConnected => {
  *   console.log('First, is ' + (isConnected ? 'online' : 'offline'));
  * });
  * function handleFirstConnectivityChange(isConnected) {
@@ -166,7 +171,7 @@ const NetInfo = {
   addEventListener(
     eventName: ChangeEventName,
     handler: Function
-  ): void {
+  ): {remove: () => void} {
     const listener = RCTDeviceEventEmitter.addListener(
       DEVICE_CONNECTIVITY_EVENT,
       (appStateData) => {
@@ -174,6 +179,9 @@ const NetInfo = {
       }
     );
     _subscriptions.set(handler, listener);
+    return {
+      remove: () => NetInfo.removeEventListener(eventName, handler)
+    };
   },
 
   removeEventListener(
@@ -189,21 +197,14 @@ const NetInfo = {
   },
 
   fetch(): Promise {
-    return new Promise((resolve, reject) => {
-      RCTNetInfo.getCurrentConnectivity(
-        function(resp) {
-          resolve(resp.network_info);
-        },
-        reject
-      );
-    });
+    return RCTNetInfo.getCurrentConnectivity().then(resp => resp.network_info);
   },
 
   isConnected: {
     addEventListener(
       eventName: ChangeEventName,
       handler: Function
-    ): void {
+    ): {remove: () => void} {
       const listener = (connection) => {
         handler(_isConnected(connection));
       };
@@ -212,12 +213,16 @@ const NetInfo = {
         eventName,
         listener
       );
+      return {
+        remove: () => NetInfo.isConnected.removeEventListener(eventName, handler)
+      };
     },
 
     removeEventListener(
       eventName: ChangeEventName,
       handler: Function
     ): void {
+      /* $FlowFixMe */
       const listener = _isConnectedSubscriptions.get(handler);
       NetInfo.removeEventListener(
         eventName,
@@ -233,15 +238,13 @@ const NetInfo = {
     },
   },
 
-  isConnectionExpensive(callback: (metered: ?boolean, error?: string) => void): void {
-    if (Platform.OS === 'android') {
-      RCTNetInfo.isConnectionMetered((_isMetered) => {
-        callback(_isMetered);
-      });
-    } else {
-      // TODO t9296080 consider polyfill and more features later on
-      callback(null, "Unsupported");
-    }
+  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.'
+    );
   },
 };
 
diff --git a/Libraries/Network/RCTNetInfo.m b/Libraries/Network/RCTNetInfo.m
index 78e1c3ab732d0f..c5440ce5d1ca4a 100644
--- a/Libraries/Network/RCTNetInfo.m
+++ b/Libraries/Network/RCTNetInfo.m
@@ -87,10 +87,10 @@ - (void)dealloc
 #pragma mark - Public API
 
 // TODO: remove error callback - not needed except by Subscribable interface
-RCT_EXPORT_METHOD(getCurrentConnectivity:(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 5265d8ef30c294..3f2de225ff8ce2 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 13aba182b9c373..ead015bccc27f2 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.m b/Libraries/Network/RCTNetworking.m
index 47cbc74d222a98..88c2de0e06fe42 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -141,17 +141,8 @@ @implementation RCTNetworking
   }
 
   if (!_handlers) {
-
-    // get handlers
-    NSMutableArray<id<RCTURLRequestHandler>> *handlers = [NSMutableArray array];
-    for (Class moduleClass in _bridge.moduleClasses) {
-      if ([moduleClass conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
-        [handlers addObject:[_bridge moduleForClass:moduleClass]];
-      }
-    }
-
-    // Sort handlers in reverse priority order (highest priority first)
-    [handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
+    // 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) {
@@ -162,8 +153,6 @@ @implementation RCTNetworking
         return NSOrderedSame;
       }
     }];
-
-    _handlers = handlers;
   }
 
   if (RCT_DEBUG) {
diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js
index 29073b7d1ca24d..e82ce515afbd54 100644
--- a/Libraries/Network/XMLHttpRequestBase.js
+++ b/Libraries/Network/XMLHttpRequestBase.js
@@ -13,12 +13,25 @@
 
 var RCTNetworking = require('RCTNetworking');
 var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
+var invariant = require('fbjs/lib/invariant');
+
+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,6 +44,8 @@ class XMLHttpRequestBase {
   readyState: number;
   responseHeaders: ?Object;
   responseText: ?string;
+  response: ?string;
+  responseType: '' | 'text';
   status: number;
   timeout: number;
   responseURL: ?string;
@@ -50,11 +65,11 @@ 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;
@@ -71,6 +86,8 @@ class XMLHttpRequestBase {
     this.readyState = this.UNSENT;
     this.responseHeaders = undefined;
     this.responseText = '';
+    this.response = null;
+    this.responseType = '';
     this.status = 0;
     delete this.responseURL;
 
@@ -134,6 +151,22 @@ class XMLHttpRequestBase {
       } else {
         this.responseText += responseText;
       }
+      switch(this.responseType) {
+      case '':
+      case 'text':
+        this.response = this.responseText;
+        break;
+      case 'blob': // whatwg-fetch sets this in Chrome
+        /* global Blob: true */
+        invariant(
+          typeof Blob === 'function',
+          `responseType "blob" is only supported on platforms with native Blob support`
+        );
+        this.response = new Blob([this.responseText]);
+        break;
+      default: //TODO: Support other types, eg: document, arraybuffer, json
+        invariant(false, `responseType "${this.responseType}" is unsupported`);
+      }
       this.setReadyState(this.LOADING);
     }
   }
@@ -265,4 +298,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 0aa82bb5f8f8bd..84bb1c0e7f0fdc 100644
--- a/Libraries/Picker/PickerIOS.ios.js
+++ b/Libraries/Picker/PickerIOS.ios.js
@@ -54,7 +54,7 @@ var PickerIOS = React.createClass({
     });
     return {selectedIndex, items};
   },
-  
+
   render: function() {
     return (
       <View style={this.props.style}>
@@ -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
diff --git a/Libraries/Promise.js b/Libraries/Promise.js
index 004741948e4d14..16cf30bf4706e3 100644
--- a/Libraries/Promise.js
+++ b/Libraries/Promise.js
@@ -1,30 +1,18 @@
 /**
+ * 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');
+const Promise = require('fbjs/lib/Promise.native');
+
 if (__DEV__) {
   require('promise/setimmediate/rejection-tracking').enable({
     allRejections: true,
@@ -46,12 +34,4 @@ if (__DEV__) {
   });
 }
 
-/**
- * Handle either fulfillment or rejection with the same callback.
- */
-Promise.prototype.finally = function(onSettled) {
-  return this.then(onSettled, onSettled);
-};
-
-
 module.exports = Promise;
diff --git a/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js
index c0f498ba253266..e784052c3960a0 100644
--- a/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js
+++ b/Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js
@@ -21,9 +21,11 @@ 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`).
  */
@@ -56,6 +58,10 @@ var PullToRefreshViewAndroid = React.createClass({
     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];
   },
diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js
index 189bc7ead83a60..f22beb36501f0a 100644
--- a/Libraries/PushNotificationIOS/PushNotificationIOS.js
+++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js
@@ -13,7 +13,7 @@
 
 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 &&
@@ -30,7 +30,7 @@ var DEVICE_LOCAL_NOTIF_EVENT = 'localNotificationReceived';
  * 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).
  *
- * [Manually link](https://facebook.github.io/react-native/docs/linking-libraries-ios.html#manual-linking) the PushNotificationIOS library
+ * [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`
@@ -79,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);
@@ -92,7 +95,9 @@ 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) {
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
index e9c68a5ec6f043..5dd5b4774464cc 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
@@ -36,8 +36,10 @@ + (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;
 }
 
@@ -180,7 +182,6 @@ - (void)handleRemoteNotificationsRegistered:(NSNotification *)notification
     UIUserNotificationSettings *notificationSettings =
       [UIUserNotificationSettings settingsForTypes:(NSUInteger)types categories:nil];
     [app registerUserNotificationSettings:notificationSettings];
-    [app registerForRemoteNotifications];
   } else {
     [app registerForRemoteNotificationTypes:(NSUInteger)types];
   }
diff --git a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
index 6c1d887e555de1..d1ffc1d402e23f 100644
--- a/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
+++ b/Libraries/ReactIOS/IOSNativeBridgeEventPlugin.js
@@ -16,7 +16,7 @@ var SyntheticEvent = require('SyntheticEvent');
 var UIManager = require('UIManager');
 
 var merge = require('merge');
-var warning = require('warning');
+var warning = require('fbjs/lib/warning');
 
 var customBubblingEventTypes = UIManager.customBubblingEventTypes;
 var customDirectEventTypes = UIManager.customDirectEventTypes;
diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js
index 33b99994a72d17..128752d1ac7555 100644
--- a/Libraries/ReactIOS/NativeMethodsMixin.js
+++ b/Libraries/ReactIOS/NativeMethodsMixin.js
@@ -16,7 +16,7 @@ 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,
@@ -27,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,
@@ -56,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 = {
   /**
@@ -74,7 +81,7 @@ 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) {
     UIManager.measure(
@@ -83,6 +90,28 @@ var NativeMethodsMixin = {
     );
   },
 
+  /**
+   * 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)
+    );
+  },
+
   /**
    * Like [`measure()`](#measure), but measures the view relative an ancestor,
    * specified as `relativeToNativeNode`. This means that the returned x, y
@@ -108,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__) {
diff --git a/Libraries/ReactIOS/renderApplication.android.js b/Libraries/ReactIOS/renderApplication.android.js
index 05b87178b6f545..03191bbc03785e 100644
--- a/Libraries/ReactIOS/renderApplication.android.js
+++ b/Libraries/ReactIOS/renderApplication.android.js
@@ -19,7 +19,7 @@ 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;
 
@@ -107,7 +107,7 @@ var AppContainer = React.createClass({
 });
 
 function renderApplication<D, P, S>(
-  RootComponent: ReactClass<D, P, S>,
+  RootComponent: ReactClass<P>,
   initialProps: P,
   rootTag: any
 ) {
diff --git a/Libraries/ReactIOS/renderApplication.ios.js b/Libraries/ReactIOS/renderApplication.ios.js
index ad000f64305183..c3e4f8178044b1 100644
--- a/Libraries/ReactIOS/renderApplication.ios.js
+++ b/Libraries/ReactIOS/renderApplication.ios.js
@@ -18,7 +18,7 @@ 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 YellowBox = __DEV__ ? require('YellowBox') : null;
@@ -66,7 +66,7 @@ var AppContainer = React.createClass({
 });
 
 function renderApplication<D, P, S>(
-  RootComponent: ReactClass<D, P, S>,
+  RootComponent: ReactClass<P>,
   initialProps: P,
   rootTag: any
 ) {
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index 4f8ddaf2e4f435..61993f799fb9be 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -24,7 +24,7 @@ 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
diff --git a/Libraries/ReactIOS/verifyPropTypes.js b/Libraries/ReactIOS/verifyPropTypes.js
index 07432eacb58bcd..d284d60ec0aec4 100644
--- a/Libraries/ReactIOS/verifyPropTypes.js
+++ b/Libraries/ReactIOS/verifyPropTypes.js
@@ -13,7 +13,7 @@
 
 var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
 
-export type ComponentInterface = ReactClass<any, any, any> | {
+export type ComponentInterface = ReactClass<any> | {
   name?: string;
   displayName?: string;
   propTypes: Object;
diff --git a/Libraries/ReactNative/ReactNative.js b/Libraries/ReactNative/ReactNative.js
index e2314ba581f999..148a366663efdd 100644
--- a/Libraries/ReactNative/ReactNative.js
+++ b/Libraries/ReactNative/ReactNative.js
@@ -27,9 +27,9 @@ var ReactPropTypes = require('ReactPropTypes');
 var ReactUpdates = require('ReactUpdates');
 
 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();
 
diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js
index 0beef933454f6e..b879b45b6a51a0 100644
--- a/Libraries/ReactNative/ReactNativeBaseComponent.js
+++ b/Libraries/ReactNative/ReactNativeBaseComponent.js
@@ -19,8 +19,8 @@ var ReactMultiChild = require('ReactMultiChild');
 var UIManager = require('UIManager');
 
 var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
-var invariant = require('invariant');
-var warning = require('warning');
+var invariant = require('fbjs/lib/invariant');
+var warning = require('fbjs/lib/warning');
 
 var registrationNames = ReactNativeEventEmitter.registrationNames;
 var putListener = ReactNativeEventEmitter.putListener;
@@ -115,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(
@@ -181,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(
diff --git a/Libraries/ReactNative/ReactNativeDefaultInjection.js b/Libraries/ReactNative/ReactNativeDefaultInjection.js
index 0d55d670c40e14..ee97c2560e61a3 100644
--- a/Libraries/ReactNative/ReactNativeDefaultInjection.js
+++ b/Libraries/ReactNative/ReactNativeDefaultInjection.js
@@ -38,7 +38,7 @@ var ReactUpdates = require('ReactUpdates');
 var ResponderEventPlugin = require('ResponderEventPlugin');
 var UniversalWorkerNodeHandle = require('UniversalWorkerNodeHandle');
 
-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');
diff --git a/Libraries/ReactNative/ReactNativeEventEmitter.js b/Libraries/ReactNative/ReactNativeEventEmitter.js
index ac8854b6cb65d2..c7680257aff577 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;
 
diff --git a/Libraries/ReactNative/ReactNativeMount.js b/Libraries/ReactNative/ReactNativeMount.js
index 2a38c3dbba8c07..cf8c5bb336fdd3 100644
--- a/Libraries/ReactNative/ReactNativeMount.js
+++ b/Libraries/ReactNative/ReactNativeMount.js
@@ -19,7 +19,7 @@ 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');
 
diff --git a/Libraries/ReactNative/ReactNativeTagHandles.js b/Libraries/ReactNative/ReactNativeTagHandles.js
index ab350817c6e90c..5a0de4cda393cc 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 e9f67ce59fdb0a..0eb5e0a6b0d0be 100644
--- a/Libraries/ReactNative/ReactNativeTextComponent.js
+++ b/Libraries/ReactNative/ReactNativeTextComponent.js
@@ -15,7 +15,7 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles');
 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.
diff --git a/Libraries/ReactNative/createReactNativeComponentClass.js b/Libraries/ReactNative/createReactNativeComponentClass.js
index 61a69a56060524..199e268891a0d2 100644
--- a/Libraries/ReactNative/createReactNativeComponentClass.js
+++ b/Libraries/ReactNative/createReactNativeComponentClass.js
@@ -27,7 +27,7 @@ type ReactNativeBaseComponentViewConfig = {
  */
 var createReactNativeComponentClass = function(
   viewConfig: ReactNativeBaseComponentViewConfig
-): ReactClass<any, any, any> {
+): ReactClass<any> {
   var Constructor = function(element) {
     this._currentElement = element;
 
diff --git a/Libraries/ReactNative/findNodeHandle.js b/Libraries/ReactNative/findNodeHandle.js
index 37c772760c503f..dd3191b82d9210 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 2024dbdaeafc80..1a66fb9bf57506 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/Settings.ios.js b/Libraries/Settings/Settings.ios.js
index 73d66ca587c3a6..3d1036fedf490a 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/StyleSheet/StyleSheetValidation.js b/Libraries/StyleSheet/StyleSheetValidation.js
index f61decb3cc2ccb..2be4ca2f24961f 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/flattenStyle.js b/Libraries/StyleSheet/flattenStyle.js
index 621c614ffd9f97..a3af4f46e63d8d 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/processTransform.js b/Libraries/StyleSheet/processTransform.js
index 4a523f90910870..6978efdcfbe67b 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');
 
 /**
diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m
index a4f110ed63e885..e57b93aa191f7a 100644
--- a/Libraries/Text/RCTText.m
+++ b/Libraries/Text/RCTText.m
@@ -74,8 +74,10 @@ - (void)removeReactSubview:(UIView *)subview
 
 - (void)setTextStorage:(NSTextStorage *)textStorage
 {
-  _textStorage = textStorage;
-  [self setNeedsDisplay];
+  if (_textStorage != textStorage) {
+    _textStorage = textStorage;
+    [self setNeedsDisplay];
+  }
 }
 
 - (void)drawRect:(CGRect)rect
diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js
index ec8c926c6edb15..1da237b0c23e5f 100644
--- a/Libraries/Utilities/AlertIOS.js
+++ b/Libraries/Utilities/AlertIOS.js
@@ -45,7 +45,7 @@ type ButtonsArray = Array<{
 class AlertIOS {
   /**
    * Creates a popup to alert the user. See
-   * [Alert](/react-native/docs/alert.html).
+   * [Alert](docs/alert.html).
    *
    *  - title: string -- The dialog's title.
    *  - message: string -- An optional message that appears above the text input.
diff --git a/Libraries/Utilities/CSSVarConfig.js b/Libraries/Utilities/CSSVarConfig.js
deleted file mode 100644
index d3177310ce9bef..00000000000000
--- a/Libraries/Utilities/CSSVarConfig.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule CSSVarConfig
- */
-'use strict';
-
-// this a partial list of the constants 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',
-  'fbui-bluegray-30': '#9197a3',
-  'fbui-bluegray-20': '#bdc1c9',
-  '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',
-  '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 93e2cc21aa1503..5cf5f21edc21f2 100644
--- a/Libraries/Utilities/Dimensions.js
+++ b/Libraries/Utilities/Dimensions.js
@@ -11,9 +11,10 @@
  */
 'use strict';
 
+var Platform = require('Platform');
 var UIManager = require('UIManager');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var dimensions = UIManager.Dimensions;
 
@@ -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;
 }
diff --git a/Libraries/Utilities/HMRClient.js b/Libraries/Utilities/HMRClient.js
index ed55ee6b912ca7..14850f860ba671 100644
--- a/Libraries/Utilities/HMRClient.js
+++ b/Libraries/Utilities/HMRClient.js
@@ -7,34 +7,38 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  *
  * @providesModule HMRClient
+ * @flow
  */
 'use strict';
 
-const invariant = require('invariant');
-const processColor = require('processColor');
+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, bundleEntry) {
+  enable(platform: string, bundleEntry: string, host: string, port: number) {
     invariant(platform, 'Missing required parameter `platform`');
     invariant(bundleEntry, 'Missing required paramenter `bundleEntry`');
-
-    // TODO(martinb) receive host and port as parameters
-    const host = 'localhost';
-    const port = '8081';
+    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 activeWS = new WebSocket(
-      `ws://${host}:${port}/hot?platform=${platform}&` +
-      `bundleEntry=${bundleEntry.replace('.bundle', '.js')}`
-    );
+    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.
@@ -50,27 +54,33 @@ Error: ${e.message}`
       );
     };
     activeWS.onmessage = ({data}) => {
-      const DevLoadingView = require('NativeModules').DevLoadingView;
+      // Moving to top gives errors due to NativeModules not being initialized
+      const HMRLoadingView = require('HMRLoadingView');
+
       data = JSON.parse(data);
 
-      switch(data.type) {
+      switch (data.type) {
         case 'update-start': {
-          DevLoadingView.showMessage(
-            'Hot Loading...',
-            processColor('#000000'),
-            processColor('#aaaaaa'),
-          );
+          HMRLoadingView.showMessage('Hot Loading...');
           break;
         }
         case 'update': {
-          const modules = data.body.modules;
-          const sourceMappingURLs = data.body.sourceMappingURLs;
-          const sourceURLs = data.body.sourceURLs;
-
-          const RCTRedBox = require('NativeModules').RedBox;
-          RCTRedBox && RCTRedBox.dismiss && RCTRedBox.dismiss();
-
-          modules.forEach((code, i) => {
+          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({
@@ -82,22 +92,32 @@ Error: ${e.message}`
             // 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 __injectHMRUpdate === 'function'
-              ? __injectHMRUpdate
+            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]);
           });
 
-          DevLoadingView.hide();
+          HMRLoadingView.hide();
           break;
         }
         case 'update-done': {
-          DevLoadingView.hide();
+          HMRLoadingView.hide();
           break;
         }
         case 'error': {
-          DevLoadingView.hide();
+          HMRLoadingView.hide();
           throw new Error(data.body.type + ' ' + data.body.description);
         }
         default: {
diff --git a/Libraries/Utilities/HMRLoadingView.android.js b/Libraries/Utilities/HMRLoadingView.android.js
new file mode 100644
index 00000000000000..82075ffa1920b6
--- /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 00000000000000..ea0559fc41d0dd
--- /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 32f3cd32f4deb8..6900106a6e0f72 100755
--- a/Libraries/Utilities/MatrixMath.js
+++ b/Libraries/Utilities/MatrixMath.js
@@ -7,7 +7,7 @@
 /* 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"
diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js
index 8e58ee06e3c71f..2e542b6c40f389 100644
--- a/Libraries/Utilities/MessageQueue.js
+++ b/Libraries/Utilities/MessageQueue.js
@@ -18,8 +18,8 @@ let ErrorUtils = require('ErrorUtils');
 let JSTimersExecution = require('JSTimersExecution');
 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;
@@ -324,11 +324,7 @@ class MessageQueue {
             method,
             args,
             (data) => {
-              // iOS always wraps the data in an Array regardless of what the
-              // shape of the data so we strip it out
-              // Android sends the data back properly
-              // TODO: Remove this once iOS has support for Promises natively (t9774697)
-              resolve(Platform.OS == 'ios' ? data[0] : data);
+              resolve(data);
             },
             (errorData) => {
               var error = createErrorFromErrorData(errorData);
diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js
index e2d0e5c81ad2e6..03e5df830bbe58 100644
--- a/Libraries/Utilities/PerformanceLogger.js
+++ b/Libraries/Utilities/PerformanceLogger.js
@@ -12,7 +12,7 @@
 
 var BatchedBridge = require('BatchedBridge');
 
-var performanceNow = require('performanceNow');
+var performanceNow = require('fbjs/lib/performanceNow');
 
 var timespans = {};
 var extras = {};
diff --git a/Libraries/Utilities/RCTLog.js b/Libraries/Utilities/RCTLog.js
index 65abc5883d0355..32394684cb9d72 100644
--- a/Libraries/Utilities/RCTLog.js
+++ b/Libraries/Utilities/RCTLog.js
@@ -13,7 +13,7 @@
 
 var BatchedBridge = require('BatchedBridge');
 
-var invariant = require('invariant');
+var invariant = require('fbjs/lib/invariant');
 
 var levelsMap = {
   log: 'log',
diff --git a/Libraries/Utilities/RCTRenderingPerf.js b/Libraries/Utilities/RCTRenderingPerf.js
index 126c8a0328528d..3979780ba36473 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/UIManager.js b/Libraries/Utilities/UIManager.js
index ff2fc23541de42..efd8ce5b072866 100644
--- a/Libraries/Utilities/UIManager.js
+++ b/Libraries/Utilities/UIManager.js
@@ -12,6 +12,7 @@
 'use strict';
 
 var UIManager = require('NativeModules').UIManager;
+var findNodeHandle = require('findNodeHandle');
 
 if (!UIManager.setChildren) {
 
@@ -39,7 +40,45 @@ if (!UIManager.setChildren) {
   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/__tests__/MatrixMath-test.js b/Libraries/Utilities/__tests__/MatrixMath-test.js
index 5bfa1e9d7b810b..b8c10ac402e2d7 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');
 
diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js
index ab04d0fe54cf6c..14c50e746d7d96 100644
--- a/Libraries/Utilities/__tests__/MessageQueue-test.js
+++ b/Libraries/Utilities/__tests__/MessageQueue-test.js
@@ -9,7 +9,7 @@
 'use strict';
 
 jest.dontMock('MessageQueue')
-  .dontMock('keyMirror');
+  .dontMock('fbjs/lib/keyMirror');
 var MessageQueue = require('MessageQueue');
 
 let MODULE_IDS = 0;
diff --git a/Libraries/Utilities/buildStyleInterpolator.js b/Libraries/Utilities/buildStyleInterpolator.js
index 0c0fa7fd1221c5..ff51699b033d45 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});
diff --git a/Libraries/Utilities/createStrictShapeTypeChecker.js b/Libraries/Utilities/createStrictShapeTypeChecker.js
index 883da2459f7c58..b5fe14a1b95051 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(
diff --git a/Libraries/Utilities/cssVar.js b/Libraries/Utilities/cssVar.js
deleted file mode 100644
index 0ab64e7f9ca5cb..00000000000000
--- a/Libraries/Utilities/cssVar.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Copyright 2004-present Facebook. All Rights Reserved.
- *
- * @providesModule cssVar
- * @typechecks
- */
-'use strict';
-
-var invariant = require('invariant');
-var CSSVarConfig = require('CSSVarConfig');
-
-var cssVar = function(/*string*/ key) /*string*/ {
-  invariant(CSSVarConfig[key], 'invalid css variable ' + key);
-
-  return CSSVarConfig[key];
-};
-
-module.exports = cssVar;
diff --git a/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js b/Libraries/Utilities/deepFreezeAndThrowOnMutationInDev.js
index 9263116ed7075f..b1dac7777dea5f 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 00000000000000..3beefdc483a8dd
--- /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/Vibration/Vibration.js b/Libraries/Vibration/Vibration.js
new file mode 100644
index 00000000000000..9258eadaa2a7b8
--- /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 f52be6b7501b7c..37cdf859c1eb8c 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 2a1dc701c17383..56da5a815b12dc 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/RCTWebSocket.xcodeproj/project.pbxproj b/Libraries/WebSocket/RCTWebSocket.xcodeproj/project.pbxproj
index 62dbee6a7606fd..b1a8757069187e 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/packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js b/Libraries/WebSocket/RCTWebSocketManager.h
similarity index 64%
rename from packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js
rename to Libraries/WebSocket/RCTWebSocketManager.h
index 6f7632f66c6992..debc3f7676f3e9 100644
--- a/packager/react-packager/src/DependencyResolver/Cache/__mocks__/index.js
+++ b/Libraries/WebSocket/RCTWebSocketManager.h
@@ -6,15 +6,14 @@
  * LICENSE file in 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 Cache {
-  get(filepath, field, cb) {
-    return cb(filepath);
-  }
+#import "RCTDefines.h"
 
-  invalidate(filepath) { }
-  end() { }
-}
+#if RCT_DEV // Only supported in dev mode
 
-module.exports = Cache;
+#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 00000000000000..7288dda09765bc
--- /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/WebSocket.js b/Libraries/WebSocket/WebSocket.js
index b9eb255cc76a02..ec6f722ef7a8ab 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';
 
@@ -116,6 +117,7 @@ class WebSocket extends WebSocketBase {
         var event = new WebSocketEvent('error');
         event.message = ev.message;
         this.onerror && this.onerror(event);
+        this.onclose && this.onclose(event);
         this.dispatchEvent(event);
         this._unregisterEvents();
         this.close();
diff --git a/Libraries/WebSocket/WebSocketBase.js b/Libraries/WebSocket/WebSocketBase.js
index 931b297494c484..843ae403540c04 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.
  */
@@ -35,10 +41,10 @@ class WebSocketBase extends EventTarget {
 
   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];
@@ -84,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');
   }
 
@@ -92,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');
   }
 
@@ -101,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 e8eca3c72a5ad9..2b5a0bc964f613 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -86,6 +86,7 @@ var ReactNative = {
   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
diff --git a/Libraries/react-native/react-native.js.flow b/Libraries/react-native/react-native.js.flow
index 6b4f333c03d2c6..70d05ead71ca06 100644
--- a/Libraries/react-native/react-native.js.flow
+++ b/Libraries/react-native/react-native.js.flow
@@ -23,7 +23,7 @@
 //
 //   var ReactNative = {...require('React'), /* additions */}
 //
-var ReactNative = Object.assign(Object.create(require('React')), {
+var ReactNative = Object.assign(Object.create(require('react')), {
   // Components
   ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
   ART: require('ReactNativeART'),
@@ -88,6 +88,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
   LayoutAnimation: require('LayoutAnimation'),
   Linking: require('Linking'),
   LinkingIOS: require('LinkingIOS'),
+  NavigationExperimental: require('NavigationExperimental'),
   NetInfo: require('NetInfo'),
   PanResponder: require('PanResponder'),
   PixelRatio: require('PixelRatio'),
@@ -97,6 +98,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
   StyleSheet: require('StyleSheet'),
   TimePickerAndroid: require('TimePickerAndroid'),
   UIManager: require('UIManager'),
+  Vibration: require('Vibration'),
   VibrationIOS: require('VibrationIOS'),
 
   // Plugins
diff --git a/Libraries/vendor/core/Map.js b/Libraries/vendor/core/Map.js
index 114add77b308ca..b3153b39df1fef 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 58fa238d4f2bd4..d0c506b3d924ff 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 540d60aa832b6e..00000000000000
--- 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/EventEmitter.js b/Libraries/vendor/emitter/EventEmitter.js
index b5c14a66a7c7a9..ba6781a57e3f98 100644
--- a/Libraries/vendor/emitter/EventEmitter.js
+++ b/Libraries/vendor/emitter/EventEmitter.js
@@ -14,8 +14,8 @@
 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
diff --git a/Libraries/vendor/emitter/EventHolder.js b/Libraries/vendor/emitter/EventHolder.js
index 4345c3d387cdf4..76cfd33141e821 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 add1f408e4a897..d992a69a553696 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 4dc938199d401b..fc39f7b649e13f 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});
 
diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
index 36490a84302696..c633dd724be79f 100644
--- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
+++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js
@@ -23,7 +23,7 @@ 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
  * native event object:
  *
diff --git a/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js b/Libraries/vendor/react/platformImplementations/universal/worker/UniversalWorkerNodeHandle.js
index b91feba9b1e2a2..ca7faa7fecfec0 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/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000000000..4ef624d5845783
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+Thanks for submitting a pull request! Please provide enough information so that others can review your pull request:
+
+(You can skip this if you're fixing a typo or adding an app to the Showcase.)
+
+Explain the **motivation** for making this change. What existing problem does the pull request solve?
+
+Example: When "Adding a function to do X", explain why it is necessary to have a way to do X.
+
+**Test plan (required)**
+
+Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
+
+Make sure tests pass on both Travis and Circle CI.
+
+**Code formatting**
+
+See the simple [style guide](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#style-guide).
diff --git a/React.podspec b/React.podspec
index 4891cb3b247f69..a1e7207e02188c 100644
--- a/React.podspec
+++ b/React.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name                = "React"
-  s.version             = "0.0.0-master"
+  s.version             = "0.0.1-master"
   s.summary             = "Build high quality mobile apps using React."
   s.description         = <<-DESC
                             React Native apps are built using the React JS
diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m
index debc00d9a02651..9547262cb926cf 100644
--- a/React/Base/RCTBatchedBridge.m
+++ b/React/Base/RCTBatchedBridge.m
@@ -44,34 +44,29 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
 @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<dispatch_block_t> *_pendingCalls;
   NSMutableDictionary<NSString *, RCTModuleData *> *_moduleDataByName;
   NSArray<RCTModuleData *> *_moduleDataByID;
-  NSDictionary<NSString *, id<RCTBridgeModule>> *_modulesByName_DEPRECATED;
   NSArray<Class> *_moduleClassesByID;
   CADisplayLink *_jsDisplayLink;
   NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
-
-  // Bridge startup stats (TODO: capture in perf logger)
-  NSUInteger _syncInitializedModules;
-  NSUInteger _asyncInitializedModules;
 }
 
 @synthesize flowID = _flowID;
 @synthesize flowIDMap = _flowIDMap;
+@synthesize loading = _loading;
+@synthesize valid = _valid;
 
 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
 {
-  RCTAssertMainThread();
   RCTAssertParam(bridge);
 
   if ((self = [super initWithBundleURL:bridge.bundleURL
@@ -122,11 +117,7 @@ - (void)start
   }];
 
   // Synchronously initialize all native modules that cannot be loaded lazily
-  [self initModules];
-
-#if RCT_DEBUG
-  _syncInitializedModules = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
-#endif
+  [self initModulesWithDispatchGroup:initModulesAndLoadSource];
 
   if (RCTProfileIsProfiling()) {
     // Depends on moduleDataByID being loaded
@@ -145,17 +136,10 @@ - (void)start
 
     // Asynchronously gather the module config
     dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
-      if (weakSelf.isValid) {
-
+      if (weakSelf.valid) {
         RCTPerformanceLoggerStart(RCTPLNativeModulePrepareConfig);
         config = [weakSelf moduleConfig];
         RCTPerformanceLoggerEnd(RCTPLNativeModulePrepareConfig);
-
-#if RCT_DEBUG
-        NSInteger total = [[_moduleDataByID valueForKeyPath:@"@sum.hasInstance"] integerValue];
-        _asyncInitializedModules = total - _syncInitializedModules;
-#endif
-
       }
     });
 
@@ -217,7 +201,7 @@ - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad
 
 - (NSArray<Class> *)moduleClasses
 {
-  if (RCT_DEBUG && self.isValid && _moduleClassesByID == nil) {
+  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.");
   }
@@ -234,8 +218,12 @@ - (RCTModuleData *)moduleDataForName:(NSString *)moduleName
 
 - (id)moduleForName:(NSString *)moduleName
 {
-  RCTModuleData *moduleData = _moduleDataByName[moduleName];
-  return moduleData.instance;
+  return _moduleDataByName[moduleName].instance;
+}
+
+- (BOOL)moduleIsInitialized:(Class)moduleClass
+{
+  return _moduleDataByName[RCTBridgeModuleNameForClass(moduleClass)].hasInstance;
 }
 
 - (NSArray *)configForModuleName:(NSString *)moduleName
@@ -250,14 +238,10 @@ - (NSArray *)configForModuleName:(NSString *)moduleName
   return (id)kCFNull;
 }
 
-- (void)initModules
+- (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:)]) {
@@ -267,90 +251,147 @@ - (void)initModules
     extraModules = self.moduleProvider();
   }
 
-  for (id<RCTBridgeModule> module in extraModules) {
-    preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
-  }
+  if (RCT_DEBUG && !RCTRunningInTestEnvironment()) {
 
-  SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
-  IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];
+    // Check for unexported modules
+    static Class *classes;
+    static unsigned int classCount;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+      classes = objc_copyClassList(&classCount);
+    });
 
-  // Set up moduleData and pre-initialize module instances
-  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
-  NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
-  for (Class moduleClass in RCTGetModuleClasses()) {
-    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
-    id module = preregisteredModules[moduleName];
-    if (!module) {
-      // Check if the module class, or any of its superclasses override init
-      // or setBridge:, or has exported constants. If they do, we assume that
-      // they are expecting to be initialized when the bridge first loads.
-      if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
-          [moduleClass instancesRespondToSelector:setBridgeSelector] ||
-          RCTClassOverridesInstanceMethod(moduleClass, @selector(constantsToExport))) {
-        module = [moduleClass new];
-        if (!module) {
-          module = (id)kCFNull;
+    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);
       }
     }
+  }
 
-    // Check for module name collisions.
-    // It's OK to have a name collision as long as the second instance is null.
-    if (module != (id)kCFNull && moduleDataByName[moduleName] && !preregisteredModules[moduleName]) {
-      RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the name "
-                  "'%@', but name was already registered by class %@", moduleClass,
-                  moduleName, moduleDataByName[moduleName].moduleClass);
-    }
+  NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
+  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
+  NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
 
-    // Instantiate moduleData (TODO: defer this until config generation)
-    RCTModuleData *moduleData;
-    if (module) {
-      if (module != (id)kCFNull) {
-        moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
-                                                            bridge:self];
+  // Set up moduleData for pre-initialized module instances
+  for (id<RCTBridgeModule> module in extraModules) {
+    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;
       }
-    } else {
-       moduleData = [[RCTModuleData alloc] initWithModuleClass:moduleClass
-                                                        bridge:self];
     }
+
+    // Instantiate moduleData container
+    RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
+                                                                       bridge:self];
+    moduleDataByName[moduleName] = moduleData;
+    [moduleClassesByID addObject:moduleClass];
+    [moduleDataByID addObject:moduleData];
+  }
+
+  // Set up moduleData for automatically-exported modules
+  for (Class moduleClass in RCTGetModuleClasses()) {
+    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
+
+    // Check for module name collisions
+    RCTModuleData *moduleData = moduleDataByName[moduleName];
     if (moduleData) {
-      moduleDataByName[moduleName] = moduleData;
-      [moduleDataByID addObject: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
   _moduleDataByID = [moduleDataByID copy];
   _moduleDataByName = [moduleDataByName copy];
-  _moduleClassesByID = [moduleDataByID valueForKey:@"moduleClass"];
+  _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
-   */
+  // 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];
 
+  // Dispatch module init onto main thead for those modules that require it
   for (RCTModuleData *moduleData in _moduleDataByID) {
     if (moduleData.hasInstance) {
-      [moduleData setBridgeForInstance];
+      // 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];
     }
   }
 
+  // From this point on, RCTDidInitializeModuleNotification notifications will
+  // be sent the first time a module is accessed.
+  _moduleSetupComplete = YES;
+
+  // Set up modules that require main thread init or constants export
   for (RCTModuleData *moduleData in _moduleDataByID) {
-    if (moduleData.hasInstance) {
-      [moduleData finishSetupForInstance];
+    __weak RCTBatchedBridge *weakSelf = self;
+    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.
+      dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
+        if (weakSelf.valid) {
+          (void)[moduleData instance];
+          [moduleData gatherConstants];
+        }
+      });
+    } else if (moduleData.hasConstantsToExport) {
+      // Constants must be exported on the main thread, but module setup can
+      // be done on any queue
+      (void)[moduleData instance];
+      dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
+        if (weakSelf.valid) {
+          [moduleData gatherConstants];
+        }
+      });
     }
   }
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-
-  [[NSNotificationCenter defaultCenter]
-   postNotificationName:RCTDidCreateNativeModules
-   object:self userInfo:@{@"bridge": self}];
-
-#pragma clang diagnostic pop
-
   RCTPerformanceLoggerEnd(RCTPLNativeModuleInit);
 }
 
@@ -412,7 +453,7 @@ - (void)updateJSDisplayLinkState
 - (void)injectJSONConfiguration:(NSString *)configJSON
                      onComplete:(void (^)(NSError *))onComplete
 {
-  if (!self.valid) {
+  if (!_valid) {
     return;
   }
 
@@ -423,7 +464,7 @@ - (void)injectJSONConfiguration:(NSString *)configJSON
 
 - (void)executeSourceCode:(NSData *)sourceCode
 {
-  if (!self.valid || !_javaScriptExecutor) {
+  if (!_valid || !_javaScriptExecutor) {
     return;
   }
 
@@ -432,7 +473,7 @@ - (void)executeSourceCode:(NSData *)sourceCode
   sourceCodeModule.scriptData = sourceCode;
 
   [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) {
-    if (!self.isValid) {
+    if (!_valid) {
       return;
     }
 
@@ -461,7 +502,9 @@ - (void)executeSourceCode:(NSData *)sourceCode
 
   if (RCTGetURLQueryParam(self.bundleURL, @"hot")) {
     NSString *path = [self.bundleURL.path substringFromIndex:1]; // strip initial slash
-    [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path]];
+    NSString *host = self.bundleURL.host;
+    NSNumber *port = self.bundleURL.port;
+    [self enqueueJSCall:@"HMRClient.enable" args:@[@"ios", path, host, RCTNullIfNil(port)]];
   }
 
 #endif
@@ -482,7 +525,7 @@ - (void)stopLoadingWithError:(NSError *)error
 {
   RCTAssertMainThread();
 
-  if (!self.isValid || !self.loading) {
+  if (!_valid || !_loading) {
     return;
   }
 
@@ -562,7 +605,7 @@ - (void)dispatchBlock:(dispatch_block_t)block
 
 - (void)invalidate
 {
-  if (!self.valid) {
+  if (!_valid) {
     return;
   }
 
@@ -607,7 +650,6 @@ - (void)invalidate
       _moduleDataByName = nil;
       _moduleDataByID = nil;
       _moduleClassesByID = nil;
-      _modulesByName_DEPRECATED = nil;
       _frameUpdateObservers = nil;
       _pendingCalls = nil;
 
@@ -763,7 +805,7 @@ - (void)_actuallyInvokeAndProcessModule:(NSString *)module
       RCTFatal(error);
     }
 
-    if (!self.isValid) {
+    if (!_valid) {
       return;
     }
     [self handleBuffer:json batchEnded:YES];
@@ -785,7 +827,7 @@ - (void)_actuallyInvokeCallback:(NSNumber *)cbID
       RCTFatal(error);
     }
 
-    if (!self.isValid) {
+    if (!_valid) {
       return;
     }
     [self handleBuffer:json batchEnded:YES];
@@ -923,7 +965,7 @@ - (BOOL)_handleRequestNumber:(NSUInteger)i
                     methodID:(NSUInteger)methodID
                       params:(NSArray *)params
 {
-  if (!self.isValid) {
+  if (!_valid) {
     return NO;
   }
 
@@ -981,7 +1023,6 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink
 
   [self updateJSDisplayLinkState];
 
-
   RCTProfileImmediateEvent(0, @"JS Thread Tick", displayLink.timestamp, 'g');
 
   RCT_PROFILE_END_EVENT(0, @"objc_call", nil);
@@ -1014,24 +1055,3 @@ - (BOOL)isBatchActive
 }
 
 @end
-
-@implementation RCTBatchedBridge(Deprecated)
-
-- (NSDictionary *)modules
-{
-  if (!_modulesByName_DEPRECATED) {
-    // Check classes are set up
-    [self moduleClasses];
-    NSMutableDictionary *modulesByName = [NSMutableDictionary new];
-    for (NSString *moduleName in _moduleDataByName) {
-      id module = [self moduleForName:moduleName];
-      if (module) {
-         modulesByName[moduleName] = module;
-      }
-    };
-    _modulesByName_DEPRECATED = [modulesByName copy];
-  }
-  return _modulesByName_DEPRECATED;
-}
-
-@end
diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h
index eedda8cd8b771b..19f33c1a3ffe93 100644
--- a/React/Base/RCTBridge+Private.h
+++ b/React/Base/RCTBridge+Private.h
@@ -54,6 +54,16 @@
 
 @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.
diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
index 0b9fc69605605f..9666b2cbeed4b9 100644
--- a/React/Base/RCTBridge.h
+++ b/React/Base/RCTBridge.h
@@ -110,11 +110,25 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
  * 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,
- * blocking both the calling thread and main thread for a short time.
+ * 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;
+
+/**
+ * Test if a module has been initialized. Use this prior to calling
+ * `moduleForClass:` or `moduleForName:` if you do not want to cause the module 
+ * to be instantiated if it hasn't been already.
+ */
+- (BOOL)moduleIsInitialized:(Class)moduleClass;
+
 /**
  * All registered bridge module classes.
  */
@@ -173,31 +187,3 @@ RCT_EXTERN BOOL RCTBridgeModuleClassIsRegistered(Class);
 - (BOOL)isBatchActive;
 
 @end
-
-/**
- * These features are deprecated and should not be used.
- */
-@interface RCTBridge (Deprecated)
-
-/**
- * This notification used to fire after all native modules has been initialized,
- * but now that native modules are instantiated lazily on demand, its original
- * purpose is meaningless.
- *
- * If you need to access a module, you can do so as soon as the bridge has been
- * initialized, by calling `[bridge moduleForClass:]`. If you need to know when
- * an individual module has been instantiated, add an observer for the
- * `RCTDidInitializeModuleNotification` instead.
- */
-RCT_EXTERN NSString *const RCTDidCreateNativeModules
-__deprecated_msg("Use RCTDidInitializeModuleNotification to observe init of individual modules");
-
-/**
- * Accessing the modules property causes all modules to be eagerly initialized,
- * which stalls the main thread. Use moduleClasses to enumerate through modules
- * without causing them to be instantiated.
- */
-@property (nonatomic, copy, readonly) NSDictionary *modules
-__deprecated_msg("Use moduleClasses and/or moduleForName: instead");
-
-@end
diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
index 650d3c958d6379..bd3cf74ef41fc2 100644
--- a/React/Base/RCTBridge.m
+++ b/React/Base/RCTBridge.m
@@ -15,6 +15,7 @@
 #import "RCTEventDispatcher.h"
 #import "RCTKeyCommands.h"
 #import "RCTLog.h"
+#import "RCTModuleData.h"
 #import "RCTPerformanceLogger.h"
 #import "RCTUtils.h"
 
@@ -68,7 +69,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];
@@ -103,35 +105,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);
-          }
-          break;
-        }
-        superclass = class_getSuperclass(superclass);
-      }
-    }
-
-    free(classes);
-
-#endif
-
   });
 }
 
@@ -156,15 +129,13 @@ + (void)setCurrentBridge:(RCTBridge *)currentBridge
 - (instancetype)initWithDelegate:(id<RCTBridgeDelegate>)delegate
                    launchOptions:(NSDictionary *)launchOptions
 {
-  RCTAssertMainThread();
-
   if ((self = [super init])) {
     RCTPerformanceLoggerStart(RCTPLTTI);
 
     _delegate = delegate;
     _launchOptions = [launchOptions copy];
     [self setUp];
-    [self bindKeys];
+    RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO);
   }
   return self;
 }
@@ -173,8 +144,6 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
                    moduleProvider:(RCTBridgeModuleProviderBlock)block
                     launchOptions:(NSDictionary *)launchOptions
 {
-  RCTAssertMainThread();
-
   if ((self = [super init])) {
     RCTPerformanceLoggerStart(RCTPLTTI);
 
@@ -182,7 +151,7 @@ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
     _moduleProvider = block;
     _launchOptions = [launchOptions copy];
     [self setUp];
-    [self bindKeys];
+    RCTExecuteOnMainThread(^{ [self bindKeys]; }, NO);
   }
   return self;
 }
@@ -238,6 +207,25 @@ - (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];
+}
+
+- (BOOL)moduleIsInitialized:(Class)moduleClass
+{
+  return [self.batchedBridge moduleIsInitialized:moduleClass];
+}
+
 - (RCTEventDispatcher *)eventDispatcher
 {
   return [self moduleForClass:[RCTEventDispatcher class]];
@@ -246,7 +234,7 @@ - (RCTEventDispatcher *)eventDispatcher
 - (void)reload
 {
   /**
-   * AnyThread
+   * Any thread
    */
   dispatch_async(dispatch_get_main_queue(), ^{
     [self invalidate];
@@ -256,8 +244,6 @@ - (void)reload
 
 - (void)setUp
 {
-  RCTAssertMainThread();
-
   // Only update bundleURL from delegate if delegate bundleURL has changed
   NSURL *previousDelegateURL = _delegateBundleURL;
   _delegateBundleURL = [self.delegate sourceURLForBridge:self];
@@ -268,6 +254,11 @@ - (void)setUp
   // Sanitize the bundle URL
   _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
 
+  [self createBatchedBridge];
+}
+
+- (void)createBatchedBridge
+{
   self.batchedBridge = [[RCTBatchedBridge alloc] initWithParentBridge:self];
 }
 
@@ -288,7 +279,7 @@ - (BOOL)isBatchActive
 
 - (void)invalidate
 {
-  RCTBatchedBridge *batchedBridge = (RCTBatchedBridge *)self.batchedBridge;
+  RCTBridge *batchedBridge = self.batchedBridge;
   self.batchedBridge = nil;
 
   if (batchedBridge) {
@@ -309,14 +300,3 @@ - (void)enqueueCallback:(NSNumber *)cbID args:(NSArray *)args
 }
 
 @end
-
-@implementation RCTBridge(Deprecated)
-
-NSString *const RCTDidCreateNativeModules = @"RCTDidCreateNativeModules";
-
-- (NSDictionary *)modules
-{
-  return self.batchedBridge.modules;
-}
-
-@end
diff --git a/React/Base/RCTBridgeDelegate.h b/React/Base/RCTBridgeDelegate.h
index e29dc64a6e9688..d8606c8d953ace 100644
--- a/React/Base/RCTBridgeDelegate.h
+++ b/React/Base/RCTBridgeDelegate.h
@@ -47,10 +47,4 @@ typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source);
 - (void)loadSourceForBridge:(RCTBridge *)bridge
                   withBlock:(RCTSourceLoadBlock)loadCallback;
 
-/**
- * Indicates whether Hot Loading is supported or not.
- * Note: this method will be removed soon, once Hot Loading is supported on OSS.
- */
-- (BOOL)bridgeSupportsHotLoading:(RCTBridge *)bridge;
-
 @end
diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m
index efcc12659b846d..459d8a6660d543 100644
--- a/React/Base/RCTLog.m
+++ b/React/Base/RCTLog.m
@@ -16,6 +16,7 @@
 #import "RCTBridge+Private.h"
 #import "RCTDefines.h"
 #import "RCTRedBox.h"
+#import "RCTUtils.h"
 
 static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
 
@@ -226,8 +227,10 @@ 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 ec922c8042c9f2..7bd297739f6886 100644
--- a/React/Base/RCTModuleData.h
+++ b/React/Base/RCTModuleData.h
@@ -21,21 +21,15 @@
                              bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
-                                bridge:(RCTBridge *)bridge;
+                                bridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
 
 /**
- * Sets the bridge for the module instance. This is only needed when using the
- * `initWithModuleInstance:bridge:` constructor. Otherwise, the bridge will be set
- * automatically when the module is first accessed.
+ * 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)setBridgeForInstance;
-
-/**
- * Sets the methodQueue and performs the remaining setup for the module. This is
- * only needed when using the `initWithModuleInstance:bridge:` constructor.
- * Otherwise it will be done automatically when the module is first accessed.
- */
-- (void)finishSetupForInstance;
+- (void)gatherConstants;
 
 @property (nonatomic, strong, readonly) Class moduleClass;
 @property (nonatomic, copy, readonly) NSString *name;
@@ -51,6 +45,16 @@
  */
 @property (nonatomic, assign, readonly) BOOL hasInstance;
 
+/**
+ * Returns YES if module instance must be created on the main thread.
+ */
+@property (nonatomic, assign, readonly) BOOL requiresMainThreadSetup;
+
+/**
+ * Returns YES if module has constants to export.
+ */
+@property (nonatomic, assign, readonly) BOOL hasConstantsToExport;
+
 /**
  * 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
@@ -65,9 +69,8 @@
 @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue;
 
 /**
- * Returns the module config. Note that this will init the module if it has
- * not already been created. This method can be called on any thread, but will
- * block the main thread briefly if the module implements `constantsToExport`.
+ * Returns the module config. Calls `gatherConstants` internally, so the same
+ * usage caveats apply.
  */
 @property (nonatomic, copy, readonly) NSArray *config;
 
diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m
index 78ef8471e6f6a4..bf2f7f8461306c 100644
--- a/React/Base/RCTModuleData.m
+++ b/React/Base/RCTModuleData.m
@@ -28,17 +28,40 @@ @implementation RCTModuleData
 @synthesize instance = _instance;
 @synthesize methodQueue = _methodQueue;
 
+- (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:` then we must assume that it
+  // expects for both of those methods to be called on the main thread, because
+  // they may need to access UIKit.
+  _requiresMainThreadSetup =
+  [_moduleClass instancesRespondToSelector:setBridgeSelector] ||
+  (!_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod);
+
+  // If a module overrides `constantsToExport` then we must assume that it
+  // must be called on the main thread, because it may need to access UIKit.
+  _hasConstantsToExport = RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport));
+}
+
 - (instancetype)initWithModuleClass:(Class)moduleClass
                              bridge:(RCTBridge *)bridge
 {
   if ((self = [super init])) {
-    _moduleClass = moduleClass;
     _bridge = bridge;
-
-    _implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector(batchDidComplete)];
-    _implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector(partialBatchDidFlush)];
-
-    _instanceLock = [NSLock new];
+    _moduleClass = moduleClass;
+    [self setUp];
   }
   return self;
 }
@@ -46,8 +69,11 @@ - (instancetype)initWithModuleClass:(Class)moduleClass
 - (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
                                 bridge:(RCTBridge *)bridge
 {
-  if ((self = [self initWithModuleClass:[instance class] bridge:bridge])) {
+  if ((self = [super init])) {
+    _bridge = bridge;
     _instance = instance;
+    _moduleClass = [instance class];
+    [self setUp];
   }
   return self;
 }
@@ -56,9 +82,47 @@ - (instancetype)initWithModuleInstance:(id<RCTBridgeModule>)instance
 
 #pragma mark - private setup methods
 
+- (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];
+
+  // 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];
+  }
+}
+
 - (void)setBridgeForInstance
 {
-  RCTAssert(_instance, @"setBridgeForInstance called before %@ initialized", self.name);
   if ([_instance respondsToSelector:@selector(bridge)] && _instance.bridge != _bridge) {
     @try {
       [(id)_instance setValue:_bridge forKey:@"bridge"];
@@ -73,30 +137,23 @@ - (void)setBridgeForInstance
 
 - (void)finishSetupForInstance
 {
-  if (!_setupComplete) {
+  if (!_setupComplete && _instance) {
     _setupComplete = YES;
-    [self setUpMethodQueue];
     [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
     [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
                                                         object:_bridge
                                                       userInfo:@{@"module": _instance}];
-
-    if (RCTClassOverridesInstanceMethod(_moduleClass, @selector(constantsToExport))) {
-      RCTAssertMainThread();
-      _constantsToExport = [_instance constantsToExport];
-    }
   }
 }
 
 - (void)setUpMethodQueue
 {
-  if (!_methodQueue) {
-    RCTAssert(_instance, @"setUpMethodQueue called before %@ initialized", self.name);
+  if (_instance && !_methodQueue && _bridge.valid) {
     BOOL implementsMethodQueue = [_instance respondsToSelector:@selector(methodQueue)];
-    if (implementsMethodQueue) {
+    if (implementsMethodQueue && _bridge.valid) {
       _methodQueue = _instance.methodQueue;
     }
-    if (!_methodQueue) {
+    if (!_methodQueue && _bridge.valid) {
 
       // Create new queue (store queueName, as it isn't retained by dispatch_queue)
       _queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", self.name];
@@ -127,20 +184,19 @@ - (BOOL)hasInstance
 
 - (id<RCTBridgeModule>)instance
 {
-  [_instanceLock lock];
   if (!_setupComplete) {
-    if (!_instance) {
-      _instance = [_moduleClass new];
+    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];
     }
-    // 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];
-
-  [self finishSetupForInstance];
-
   return _instance;
 }
 
@@ -185,10 +241,21 @@ - (NSString *)name
   return _methods;
 }
 
+- (void)gatherConstants
+{
+  if (_hasConstantsToExport && !_constantsToExport) {
+    (void)[self instance];
+    RCTExecuteOnMainThread(^{
+      _constantsToExport = [_instance constantsToExport] ?: @{};
+    }, YES);
+  }
+}
+
 - (NSArray *)config
 {
+  [self gatherConstants];
   __block NSDictionary<NSString *, id> *constants = _constantsToExport;
-  _constantsToExport = nil; // Not needed any more
+  _constantsToExport = nil; // Not needed anymore
 
   if (constants.count == 0 && self.methods.count == 0) {
     return (id)kCFNull; // Nothing to export
@@ -222,7 +289,7 @@ - (NSArray *)config
 
 - (dispatch_queue_t)methodQueue
 {
-  [self instance];
+  (void)[self instance];
   return _methodQueue;
 }
 
@@ -231,4 +298,9 @@ - (void)invalidate
   _methodQueue = nil;
 }
 
+- (NSString *)description
+{
+  return [NSString stringWithFormat:@"<%@: %p; name=\"%@\">", [self class], self, self.name];
+}
+
 @end
diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h
index ba1fe8712c536c..de59089747193e 100644
--- a/React/Base/RCTRootView.h
+++ b/React/Base/RCTRootView.h
@@ -83,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 RCTJSCExecutor.
- * Changes will take effect next time the bundle is reloaded.
- */
-@property (nonatomic, strong) Class executorClass;
-
 /**
  * The size flexibility mode of the root view.
  */
diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m
index d5726a2bcfa3c5..d571a0690b2d80 100644
--- a/React/Base/RCTRootView.m
+++ b/React/Base/RCTRootView.m
@@ -38,10 +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
 
@@ -74,6 +75,11 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
     _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
@@ -83,6 +89,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
                                              selector:@selector(hideLoadingView)
                                                  name:RCTContentDidAppearNotification
                                                object:self];
+
     if (!_bridge.loading) {
       [self bundleFinishedLoading:_bridge.batchedBridge];
     }
@@ -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,7 +209,9 @@ - (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;
@@ -247,11 +279,6 @@ - (void)setIntrinsicSize:(CGSize)intrinsicSize
   [_delegate rootViewDidChangeIntrinsicSize:self];
 }
 
-- (NSNumber *)reactTag
-{
-  return _contentView.reactTag;
-}
-
 - (void)contentViewInvalidated
 {
   [_contentView removeFromSuperview];
@@ -291,10 +318,14 @@ @implementation RCTRootContentView
 
 - (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;
@@ -337,21 +368,6 @@ - (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];
-  _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
-  [self addGestureRecognizer:_touchHandler];
-  [_bridge.uiManager registerRootView:self];
-}
-
 - (void)invalidate
 {
   if (self.userInteractionEnabled) {
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 9d63bdbf3379d5..ea319814588cf2 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -109,6 +109,9 @@ RCT_EXTERN NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL);
 // Determines if a given image URL actually refers to an XCAsset
 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);
 
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index 68e3a15e520f28..c693ea1a702ef2 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -589,6 +589,51 @@ BOOL RCTIsXCAssetURL(NSURL *__nullable 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));
diff --git a/React/Base/RCTWebSocketProxy.h b/React/Base/RCTWebSocketProxy.h
new file mode 100644
index 00000000000000..e823c3c8fe68a4
--- /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 00000000000000..f668bf1610a344
--- /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/RCTJSCExecutor.h b/React/Executors/RCTJSCExecutor.h
index cbcf224fcd8a24..f8beaa67373f5a 100644
--- a/React/Executors/RCTJSCExecutor.h
+++ b/React/Executors/RCTJSCExecutor.h
@@ -11,6 +11,20 @@
 
 #import "RCTJavaScriptExecutor.h"
 
+/**
+ * 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.
  */
diff --git a/React/Executors/RCTJSCExecutor.m b/React/Executors/RCTJSCExecutor.m
index f96d2992a9c372..2a0e5f137998d0 100644
--- a/React/Executors/RCTJSCExecutor.m
+++ b/React/Executors/RCTJSCExecutor.m
@@ -26,6 +26,10 @@
 #import "RCTRedBox.h"
 #import "RCTSourceCode.h"
 
+NSString *const RCTJSCThreadName = @"com.facebook.React.JavaScript";
+
+NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification";
+
 static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";
 
 @interface RCTJavaScriptContext : NSObject <RCTInvalidating>
@@ -172,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];
@@ -330,7 +334,16 @@ - (void)setUp
   }];
 
   [self executeBlockOnJavaScriptQueue:^{
-    RCTInstallJSCProfiler(_bridge, self.context.ctx);
+    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]) {
@@ -339,6 +352,20 @@ - (void)setUp
                                                  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
 }
 
@@ -361,6 +388,11 @@ - (void)invalidate
 #if RCT_DEV
   [[NSNotificationCenter defaultCenter] removeObserver:self];
 #endif
+}
+
+- (void)dealloc
+{
+  [self invalidate];
 
   [_context performSelector:@selector(invalidate)
                    onThread:_javaScriptThread
@@ -369,11 +401,6 @@ - (void)invalidate
   _context = nil;
 }
 
-- (void)dealloc
-{
-  [self invalidate];
-}
-
 - (void)flushedQueue:(RCTJavaScriptCallback)onComplete
 {
   // TODO: Make this function handle first class instead of dynamically dispatching it. #9317773
@@ -511,21 +538,6 @@ - (void)executeApplicationScript:(NSData *)script
   RCTAssertParam(sourceURL);
 
   __weak RCTJSCExecutor *weakSelf = self;
-#if RCT_DEV
-  _context.context[@"__injectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
-    RCTJSCExecutor *strongSelf = weakSelf;
-
-    if (!strongSelf) {
-      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
 
   [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
     RCTJSCExecutor *strongSelf = weakSelf;
diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m
index b2e7fe0a3ae055..7ab8f49e71b263 100644
--- a/React/Modules/RCTAlertManager.m
+++ b/React/Modules/RCTAlertManager.m
@@ -75,7 +75,6 @@ - (void)invalidate
   NSString *message = [RCTConvert NSString:args[@"message"]];
   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"]];
 
@@ -113,6 +112,7 @@ - (void)invalidate
     alertView.message = message;
 
     if (type != UIAlertViewStyleDefault) {
+      NSString *defaultValue = [RCTConvert NSString:args[@"defaultValue"]];
       [alertView textFieldAtIndex:0].text = defaultValue;
     }
 
diff --git a/React/Modules/RCTAppState.m b/React/Modules/RCTAppState.m
index 6a22ae626f0e18..a71e2b3a8c7f6d 100644
--- a/React/Modules/RCTAppState.m
+++ b/React/Modules/RCTAppState.m
@@ -21,8 +21,7 @@
   dispatch_once(&onceToken, ^{
     states = @{
       @(UIApplicationStateActive): @"active",
-      @(UIApplicationStateBackground): @"background",
-      @(UIApplicationStateInactive): @"inactive"
+      @(UIApplicationStateBackground): @"background"
     };
   });
 
@@ -53,9 +52,12 @@ - (void)setBridge:(RCTBridge *)bridge
 
   for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
                            UIApplicationDidEnterBackgroundNotification,
-                           UIApplicationDidFinishLaunchingNotification]) {
+                           UIApplicationDidFinishLaunchingNotification,
+                           UIApplicationWillResignActiveNotification,
+                           UIApplicationWillEnterForegroundNotification]) {
+
     [[NSNotificationCenter defaultCenter] addObserver:self
-                                             selector:@selector(handleAppStateDidChange)
+                                             selector:@selector(handleAppStateDidChange:)
                                                  name:name
                                                object:nil];
   }
@@ -79,9 +81,18 @@ - (void)dealloc
 
 #pragma mark - App Notification Methods
 
-- (void)handleAppStateDidChange
+- (void)handleAppStateDidChange:(NSNotification *)notification
 {
-  NSString *newState = RCTCurrentAppBackgroundState();
+  NSString *newState;
+
+  if ([notification.name isEqualToString:UIApplicationWillResignActiveNotification]) {
+    newState = @"inactive";
+  } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) {
+    newState = @"active";
+  } else {
+    newState = RCTCurrentAppBackgroundState();
+  }
+
   if (![newState isEqualToString:_lastKnownState]) {
     _lastKnownState = newState;
     [_bridge.eventDispatcher sendDeviceEventWithName:@"appStateDidChange"
diff --git a/React/Modules/RCTClipboard.m b/React/Modules/RCTClipboard.m
index 0a0bf14816f868..8d059c4a374555 100644
--- a/React/Modules/RCTClipboard.m
+++ b/React/Modules/RCTClipboard.m
@@ -26,14 +26,14 @@ - (dispatch_queue_t)methodQueue
 RCT_EXPORT_METHOD(setString:(NSString *)content)
 {
   UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
-  clipboard.string = content;
+  clipboard.string = (content ? : @"");
 }
 
 RCT_EXPORT_METHOD(getString:(RCTPromiseResolveBlock)resolve
                   rejecter:(__unused RCTPromiseRejectBlock)reject)
 {
   UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
-  resolve(@[RCTNullIfNil(clipboard.string)]);
+  resolve((clipboard.string ? : @""));
 }
 
 @end
diff --git a/React/Modules/RCTDevLoadingView.m b/React/Modules/RCTDevLoadingView.m
index 5c799791cf2abb..708454754fd50f 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
 
@@ -68,6 +69,9 @@ - (void)setBridge:(RCTBridge *)bridge
       _window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenWidth, 22)];
       _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.textAlignment = NSTextAlignmentCenter;
diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m
index c3a47cb274976d..92cfaaacd6fd07 100644
--- a/React/Modules/RCTDevMenu.m
+++ b/React/Modules/RCTDevMenu.m
@@ -19,6 +19,7 @@
 #import "RCTRootView.h"
 #import "RCTSourceCode.h"
 #import "RCTUtils.h"
+#import "RCTWebSocketProxy.h"
 
 #if RCT_DEV
 
@@ -117,7 +118,7 @@ - (void)callHandler
 
 @end
 
-@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate, RCTInvalidating>
+@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate, RCTInvalidating, RCTWebSocketProxyDelegate>
 
 @property (nonatomic, strong) Class executorClass;
 
@@ -194,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
@@ -228,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();
@@ -319,7 +369,7 @@ - (void)jsLoaded:(NSNotification *)notification
   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) {
@@ -422,7 +472,7 @@ - (void)addItem:(RCTDevMenuItem *)item
   }
 
   if ([self hotLoadingAvailable]) {
-    NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Loading" : @"Enable Hot Loading";
+    NSString *hotLoadingTitle = _hotLoadingEnabled ? @"Disable Hot Reloading" : @"Enable Hot Reloading";
     [items addObject:[RCTDevMenuItem buttonItemWithTitle:hotLoadingTitle handler:^{
       weakSelf.hotLoadingEnabled = !_hotLoadingEnabled;
     }]];
@@ -531,9 +581,7 @@ - (void)setLiveReloadEnabled:(BOOL)enabled
 
 - (BOOL)hotLoadingAvailable
 {
-  return !_bridge.bundleURL.fileURL // Only works when running from server
-  && [_bridge.delegate respondsToSelector:@selector(bridgeSupportsHotLoading:)]
-  && [_bridge.delegate bridgeSupportsHotLoading:_bridge];
+  return _bridge.bundleURL && !_bridge.bundleURL.fileURL; // Only works when running from server
 }
 
 - (void)setHotLoadingEnabled:(BOOL)enabled
diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m
index e538fa5d0c9608..f8d96fac68e88d 100644
--- a/React/Modules/RCTSourceCode.m
+++ b/React/Modules/RCTSourceCode.m
@@ -32,7 +32,7 @@ - (void)setScriptText:(NSString *)scriptText {}
   if (RCT_DEV && self.scriptData && self.scriptURL) {
     NSString *scriptText = [[NSString alloc] initWithData:self.scriptData encoding:NSUTF8StringEncoding];
 
-    resolve(@[@{@"text": scriptText, @"url": self.scriptURL.absoluteString}]);
+    resolve(@{@"text": scriptText, @"url": self.scriptURL.absoluteString});
   } else {
     reject(RCTErrorUnavailable, nil, RCTErrorWithMessage(@"Source code is not available"));
   }
diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m
index 3aa084d3073f0f..8e2f865bd726d6 100644
--- a/React/Modules/RCTTiming.m
+++ b/React/Modules/RCTTiming.m
@@ -174,15 +174,7 @@ - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
     return;
   }
 
-  NSTimeInterval jsSchedulingOverhead = -jsSchedulingTime.timeIntervalSinceNow;
-  if (jsSchedulingOverhead < 0) {
-    RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
-
-    /**
-     * Probably debugging on device, set to 0 so we don't ignore the interval
-     */
-    jsSchedulingOverhead = 0;
-  }
+  NSTimeInterval jsSchedulingOverhead = MAX(-jsSchedulingTime.timeIntervalSinceNow, 0);
 
   NSTimeInterval targetTime = jsDuration - jsSchedulingOverhead;
   if (jsDuration < 0.018) { // Make sure short intervals run each frame
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index bdcf2e2c53502c..8a14a4a02eee7d 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -60,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.
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index d98cf73363df8d..d0db1c40aa5bb0 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -375,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;
+    }
+
+    // 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);
 
-    [rootShadowView dirtyLayout];
+    shadowView.intrinsicContentSize = size;
 
     [self batchDidComplete];
   });
@@ -955,7 +978,7 @@ - (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];
   }
 
@@ -1045,18 +1068,16 @@ - (void)setNeedsLayout
       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(@[
@@ -1070,6 +1091,29 @@ - (void)setNeedsLayout
   }];
 }
 
+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)
@@ -1182,6 +1226,73 @@ static void RCTMeasureLayout(RCTShadowView *view,
   callback(@[results]);
 }
 
+RCT_EXPORT_METHOD(takeSnapshot:(id /* NSString or NSNumber */)target
+                  withOptions:(NSDictionary *)options
+                  resolve:(RCTPromiseResolveBlock)resolve
+                  reject:(RCTPromiseRejectBlock)reject)
+{
+  [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;
+      }
+    }
+
+    // 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();
+
+    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;
+        }
+      }
+
+      // If we reached here, something went wrong
+      reject(RCTErrorUnspecified, error.localizedDescription, error);
+    });
+  }];
+}
+
 /**
  * JS sets what *it* considers to be the responder. Later, scroll views can use
  * this in order to determine if scrolling is appropriate.
@@ -1265,10 +1376,6 @@ static void RCTMeasureLayout(RCTShadowView *view,
         @"height": @(RCTScreenSize().height),
         @"scale": @(RCTScreenScale()),
       },
-      @"modalFullscreenView": @{
-        @"width": @(RCTScreenSize().width),
-        @"height": @(RCTScreenSize().height),
-      },
     },
   }];
 
diff --git a/React/Profiler/RCTJSCProfiler.m b/React/Profiler/RCTJSCProfiler.m
index 8ad34b62682e8a..fc0a21fff8bb7e 100644
--- a/React/Profiler/RCTJSCProfiler.m
+++ b/React/Profiler/RCTJSCProfiler.m
@@ -99,7 +99,9 @@ void RCTJSCProfilerStart(JSContextRef ctx)
     if (isProfiling) {
       NSString *filename = [NSString stringWithFormat:@"cpu_profile_%ld.json", (long)CFAbsoluteTimeGetCurrent()];
       outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
-      RCTNativeProfilerEnd(ctx, JSCProfileName, outputFile.UTF8String);
+      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.");
diff --git a/React/Profiler/RCTPerfMonitor.m b/React/Profiler/RCTPerfMonitor.m
index 93a6fd70a47d4e..caf660385e8ba7 100644
--- a/React/Profiler/RCTPerfMonitor.m
+++ b/React/Profiler/RCTPerfMonitor.m
@@ -501,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.m b/React/Profiler/RCTProfile.m
index 32f63e91658c97..14dbefb1f1406e 100644
--- a/React/Profiler/RCTProfile.m
+++ b/React/Profiler/RCTProfile.m
@@ -27,6 +27,7 @@
 #import "RCTModuleData.h"
 #import "RCTUtils.h"
 #import "RCTUIManager.h"
+#import "RCTJSCExecutor.h"
 
 NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling";
 NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling";
@@ -372,7 +373,6 @@ BOOL RCTProfileIsProfiling(void)
 void RCTProfileInit(RCTBridge *bridge)
 {
   // TODO: enable assert JS thread from any file (and assert here)
-
   if (RCTProfileIsProfiling()) {
     return;
   }
@@ -395,6 +395,20 @@ 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]
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 40147b76de6f0a..30a1710b4a48a4 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -251,6 +251,8 @@
 		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>"; };
@@ -511,15 +513,15 @@
 		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 */,
-				830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
 				13AFBCA11C07287B00BBAEAA /* RCTBridgeMethod.h */,
+				830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
 				83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
 				83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
 				13AF1F851AE6E777005F5298 /* RCTDefines.h */,
@@ -527,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 */,
@@ -549,6 +553,7 @@
 				830A229C1A66C68A008503DA /* RCTRootView.h */,
 				830A229D1A66C68A008503DA /* RCTRootView.m */,
 				13AFBCA21C07287B00BBAEAA /* RCTRootViewDelegate.h */,
+				6A15FB0C1BDF663500531DFB /* RCTRootViewInternal.h */,
 				391E86A31C623EC800009732 /* RCTTouchEvent.h */,
 				391E86A21C623EC800009732 /* RCTTouchEvent.m */,
 				83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
@@ -557,9 +562,8 @@
 				1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
 				83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
 				83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
-				13BB3D001BECD54500932C10 /* RCTImageSource.h */,
-				13BB3D011BECD54500932C10 /* RCTImageSource.m */,
-				14A43DB81C1F849600794BC8 /* RCTBridge+Private.h */,
+				3DB910701C74B21600838BBE /* RCTWebSocketProxy.h */,
+				3DB910711C74B21600838BBE /* RCTWebSocketProxyDelegate.h */,
 			);
 			path = Base;
 			sourceTree = "<group>";
diff --git a/React/Views/RCTComponentData.h b/React/Views/RCTComponentData.h
index 38b242602d8367..596dcc8399b7df 100644
--- a/React/Views/RCTComponentData.h
+++ b/React/Views/RCTComponentData.h
@@ -11,10 +11,10 @@
 
 #import "RCTComponent.h"
 #import "RCTDefines.h"
+#import "RCTViewManager.h"
 
 @class RCTBridge;
 @class RCTShadowView;
-@class RCTViewManager;
 @class UIView;
 
 @interface RCTComponentData : NSObject
@@ -33,4 +33,6 @@
 
 - (NSDictionary<NSString *, id> *)viewConfig;
 
+- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)registry;
+
 @end
diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m
index 34f2742a6dd7f6..e182f896bea285 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);
@@ -40,10 +39,10 @@ - (instancetype)initWithType:(NSString *)type
 
 @implementation RCTComponentData
 {
-  id<RCTComponent> _defaultView;
-  RCTShadowView *_defaultShadowView;
+  id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
   NSMutableDictionary<NSString *, RCTPropBlock> *_viewPropBlocks;
   NSMutableDictionary<NSString *, RCTPropBlock> *_shadowPropBlocks;
+  BOOL _implementsUIBlockToAmendWithShadowViewRegistry;
   __weak RCTBridge *_bridge;
 }
 
@@ -63,6 +62,14 @@ - (instancetype)initWithManagerClass:(Class)managerClass
     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;
 }
@@ -97,10 +104,10 @@ - (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
   return shadowView;
 }
 
-- (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
+- (RCTPropBlock)propBlockForKey:(NSString *)name
+                   inDictionary:(NSMutableDictionary<NSString *, RCTPropBlock> *)propBlocks
 {
-  BOOL shadowView = [defaultView isKindOfClass:[RCTShadowView class]];
-  NSMutableDictionary<NSString *, RCTPropBlock> *propBlocks = shadowView ? _shadowPropBlocks : _viewPropBlocks;
+  BOOL shadowView = (propBlocks == _shadowPropBlocks);
   RCTPropBlock propBlock = propBlocks[name];
   if (!propBlock) {
 
@@ -129,8 +136,16 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
       SEL customSetter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, shadowView ? @"Shadow" : @""]);
 
       propBlock = ^(id<RCTComponent> view, id json) {
+        RCTComponentData *strongSelf = weakSelf;
+        if (!strongSelf) {
+          return;
+        }
+        if (!json && !strongSelf->_defaultView) {
+          // Only create default view if json is null
+          strongSelf->_defaultView = [strongSelf createViewWithTag:nil];
+        }
         ((void (*)(id, SEL, id, id, id))objc_msgSend)(
-          weakSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, defaultView
+          strongSelf.manager, customSetter, json == (id)kCFNull ? nil : json, view, strongSelf->_defaultView
         );
       };
 
@@ -153,13 +168,13 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
                                          [key substringFromIndex:1]]);
 
       // Build setter block
-      void (^setterBlock)(id target, id source, id json) = nil;
+      void (^setterBlock)(id target, id json) = nil;
       if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
           type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
 
         // Special case for event handlers
         __weak RCTViewManager *weakManager = _manager;
-        setterBlock = ^(id target, __unused id source, id json) {
+        setterBlock = ^(id target, id json) {
           __weak id<RCTComponent> weakTarget = target;
           ((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
             body = [NSMutableDictionary dictionaryWithDictionary:body];
@@ -180,11 +195,23 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
 
   #define RCT_CASE(_value, _type) \
           case _value: { \
+            __block BOOL setDefaultValue = NO; \
+            __block _type defaultValue; \
             _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
             _type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
             void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
-            setterBlock = ^(id target, id source, id json) { \
-              set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
+            setterBlock = ^(id target, id json) { \
+              if (json) { \
+                if (!setDefaultValue && target) { \
+                  if ([target respondsToSelector:getter]) { \
+                    defaultValue = get(target, getter); \
+                  } \
+                  setDefaultValue = YES; \
+                } \
+                set(target, setter, convert([RCTConvert class], type, json)); \
+              } else if (setDefaultValue) { \
+                set(target, setter, defaultValue); \
+              } \
             }; \
             break; \
           }
@@ -214,36 +241,60 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
             typeInvocation.selector = type;
             typeInvocation.target = [RCTConvert class];
 
-            __block NSInvocation *sourceInvocation = nil;
             __block NSInvocation *targetInvocation = nil;
+            __block NSMutableData *defaultValue = nil;
+
+            setterBlock = ^(id target, id json) { \
+
+              if (!target) {
+                return;
+              }
 
-            setterBlock = ^(id target, id source, id json) { \
+              // Get default value
+              if (!defaultValue) {
+                if (!json) {
+                  // We only set the defaultValue when we first pass a non-null
+                  // value, so if the first value sent for a prop is null, it's
+                  // a no-op (we'd be resetting it to its default when its
+                  // value is already the default).
+                  return;
+                }
+                // Use NSMutableData to store defaultValue instead of malloc, so
+                // it will be freed automatically when setterBlock is released.
+                defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
+                if ([target respondsToSelector:getter]) {
+                  NSMethodSignature *signature = [target methodSignatureForSelector:getter];
+                  NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
+                  sourceInvocation.selector = getter;
+                  [sourceInvocation invokeWithTarget:target];
+                  [sourceInvocation getReturnValue:defaultValue.mutableBytes];
+                }
+              }
 
               // Get value
-              void *value = malloc(typeSignature.methodReturnLength);
+              BOOL freeValueOnCompletion = NO;
+              void *value = defaultValue.mutableBytes;
               if (json) {
+                freeValueOnCompletion = YES;
+                value = malloc(typeSignature.methodReturnLength);
                 [typeInvocation setArgument:&json atIndex:2];
                 [typeInvocation invoke];
                 [typeInvocation getReturnValue:value];
-              } else {
-                if (!sourceInvocation && source) {
-                  NSMethodSignature *signature = [source methodSignatureForSelector:getter];
-                  sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
-                  sourceInvocation.selector = getter;
-                }
-                [sourceInvocation invokeWithTarget:source];
-                [sourceInvocation getReturnValue:value];
               }
 
               // Set value
-              if (!targetInvocation && target) {
+              if (!targetInvocation) {
                 NSMethodSignature *signature = [target methodSignatureForSelector:setter];
                 targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
                 targetInvocation.selector = setter;
               }
               [targetInvocation setArgument:value atIndex:2];
               [targetInvocation invokeWithTarget:target];
-              free(value);
+              if (freeValueOnCompletion) {
+                // Only free the value if we `malloc`d it locally, otherwise it
+                // points to `defaultValue.mutableBytes`, which is managed by ARC.
+                free(value);
+              }
             };
             break;
           }
@@ -258,20 +309,8 @@ - (RCTPropBlock)propBlockForKey:(NSString *)name defaultView:(id)defaultView
           target = [target valueForKey:part];
         }
 
-        if (json == (id)kCFNull) {
-
-          // Copy default property
-          id source = defaultView;
-          for (NSString *part in parts) {
-            source = [source valueForKey:part];
-          }
-          setterBlock(target, source, nil);
-
-        } else {
-
-          // Set property with json
-          setterBlock(target, nil, json);
-        }
+        // Set property with json
+        setterBlock(target, RCTNilIfNull(json));
       };
     }
 
@@ -299,12 +338,8 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>
     return;
   }
 
-  if (!_defaultView) {
-    _defaultView = [self createViewWithTag:nil];
-  }
-
   [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
-    [self propBlockForKey:key defaultView:_defaultView](view, json);
+    [self propBlockForKey:key inDictionary:_viewPropBlocks](view, json);
   }];
 
   if ([view respondsToSelector:@selector(didSetProps:)]) {
@@ -318,12 +353,8 @@ - (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowV
     return;
   }
 
-  if (!_defaultShadowView) {
-    _defaultShadowView = [self createShadowViewWithTag:nil];
-  }
-
   [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
-    [self propBlockForKey:key defaultView:_defaultShadowView](shadowView, json);
+    [self propBlockForKey:key inDictionary:_shadowPropBlocks](shadowView, json);
   }];
 
   if ([shadowView respondsToSelector:@selector(didSetProps:)]) {
@@ -412,4 +443,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/RCTMapManager.m b/React/Views/RCTMapManager.m
index ec4595a5a3a584..ad623c50434fbe 100644
--- a/React/Views/RCTMapManager.m
+++ b/React/Views/RCTMapManager.m
@@ -106,35 +106,6 @@ - (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
diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h
index cafe771c8f5e41..c67579da023611 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 aecb77dbaec9f3..75e50105c48820 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 ce40551bddc0e2..192e7ffb9d3623 100644
--- a/React/Views/RCTModalHostViewController.m
+++ b/React/Views/RCTModalHostViewController.m
@@ -24,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 ae3b21f2c06317..be4e6b79e05038 100644
--- a/React/Views/RCTModalHostViewManager.m
+++ b/React/Views/RCTModalHostViewManager.m
@@ -40,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/RCTRefreshControl.m b/React/Views/RCTRefreshControl.m
index b066f1b632afdc..5d24ffadc7d113 100644
--- a/React/Views/RCTRefreshControl.m
+++ b/React/Views/RCTRefreshControl.m
@@ -30,7 +30,7 @@ - (instancetype)init
 - (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) {
@@ -46,9 +46,7 @@ - (void)beginRefreshing
   CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - self.frame.size.height};
   // Don't animate when the prop is set initialy.
   if (_isInitialRender) {
-    // Must use `[scrollView setContentOffset:offset animated:NO]` instead of just setting
-    // `scrollview.contentOffset` or it doesn't work, don't ask me why!
-    [scrollView setContentOffset:offset animated:NO];
+    scrollView.contentOffset = offset;
     [super beginRefreshing];
   } else {
     // `beginRefreshing` must be called after the animation is done. This is why it is impossible
@@ -64,6 +62,26 @@ - (void)beginRefreshing
   }
 }
 
+- (void)endRefreshing
+{
+  // The contentOffset of the scrollview MUST be greater than 0 before calling
+  // endRefreshing otherwise the next pull to refresh will not work properly.
+  UIScrollView *scrollView = (UIScrollView *)self.superview;
+  if (scrollView.contentOffset.y < 0) {
+    CGPoint offset = {scrollView.contentOffset.x, 0};
+    [UIView animateWithDuration:0.25
+                          delay:0
+                        options:UIViewAnimationOptionBeginFromCurrentState
+                     animations:^(void) {
+                       [scrollView setContentOffset:offset];
+                     } completion:^(__unused BOOL finished) {
+                       [super endRefreshing];
+                     }];
+  } else {
+    [super endRefreshing];
+  }
+}
+
 - (NSString *)title
 {
   return self.attributedTitle.string;
diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m
index 10f2e5ec9c5336..4df421f9169b7b 100644
--- a/React/Views/RCTScrollView.m
+++ b/React/Views/RCTScrollView.m
@@ -145,7 +145,7 @@ @interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
 
 @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
 @property (nonatomic, assign) BOOL centerContent;
-@property (nonatomic, strong) UIRefreshControl *refreshControl;
+@property (nonatomic, strong) RCTRefreshControl *refreshControl;
 
 @end
 
@@ -287,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;
     }
 
@@ -349,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);
@@ -359,7 +367,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
   return hitView ?: [super hitTest:point withEvent:event];
 }
 
-- (void)setRefreshControl:(UIRefreshControl *)refreshControl
+- (void)setRefreshControl:(RCTRefreshControl *)refreshControl
 {
   if (_refreshControl) {
     [_refreshControl removeFromSuperview];
@@ -487,6 +495,12 @@ - (void)layoutSubviews
   _scrollView.frame = self.bounds;
   _scrollView.contentOffset = originalOffset;
 
+  // Adjust the refresh control frame if the scrollview layout changes.
+  RCTRefreshControl *refreshControl = _scrollView.refreshControl;
+  if (refreshControl && refreshControl.refreshing) {
+    refreshControl.frame = (CGRect){_scrollView.contentOffset, {_scrollView.frame.size.width, refreshControl.frame.size.height}};
+  }
+
   [self updateClippedSubviews];
 }
 
@@ -820,6 +834,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];
 }
 
@@ -828,49 +853,36 @@ - (void)reactBridgeDidFinishTransaction
 // setters here that will record the contentOffset beforehand, and
 // restore it after the property has been set.
 
-#define RCT_SET_AND_PRESERVE_OFFSET(setter, type)    \
-- (void)setter:(type)value                           \
-{                                                    \
-  CGPoint contentOffset = _scrollView.contentOffset; \
-  [_scrollView setter:value];                        \
-  _scrollView.contentOffset = contentOffset;         \
-}
-
-RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setBounces, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, BOOL)
-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)
-RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, BOOL)
-RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, CGFloat);
-RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
-
-#pragma mark - Forward methods and properties to underlying UIScrollView
-
-- (BOOL)respondsToSelector:(SEL)aSelector
-{
-  return [super respondsToSelector:aSelector] || [_scrollView respondsToSelector:aSelector];
-}
-
-- (void)setValue:(id)value forUndefinedKey:(NSString *)key
-{
-  [_scrollView setValue:value forKey:key];
-}
-
-- (id)valueForUndefinedKey:(NSString *)key
-{
-  return [_scrollView valueForKey:key];
-}
+#define RCT_SET_AND_PRESERVE_OFFSET(setter, getter, type) \
+- (void)setter:(type)value                                \
+{                                                         \
+  CGPoint contentOffset = _scrollView.contentOffset;      \
+  [_scrollView setter:value];                             \
+  _scrollView.contentOffset = contentOffset;              \
+}                                                         \
+- (type)getter                                            \
+{                                                         \
+  return [_scrollView getter];                            \
+}
+
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, alwaysBounceHorizontal, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, alwaysBounceVertical, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBounces, bounces, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, bouncesZoom, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, canCancelContentTouches, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, decelerationRate, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, isDirectionalLockEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setIndicatorStyle, indicatorStyle, UIScrollViewIndicatorStyle)
+RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, keyboardDismissMode, UIScrollViewKeyboardDismissMode)
+RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, maximumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, minimumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, isPagingEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, isScrollEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, scrollsToTop, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, showsHorizontalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, showsVerticalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, zoomScale, CGFloat);
+RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIEdgeInsets);
 
 - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
 {
@@ -882,7 +894,7 @@ - (void)setOnRefreshStart:(RCTDirectEventBlock)onRefreshStart
   _onRefreshStart = [onRefreshStart copy];
 
   if (!_scrollView.refreshControl) {
-    UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
+    RCTRefreshControl *refreshControl = [[RCTRefreshControl alloc] init];
     [refreshControl addTarget:self action:@selector(refreshControlValueChanged) forControlEvents:UIControlEventValueChanged];
     _scrollView.refreshControl = refreshControl;
   }
diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m
index 9ab7ca806bc4ba..77f916b4e821ad 100644
--- a/React/Views/RCTScrollViewManager.m
+++ b/React/Views/RCTScrollViewManager.m
@@ -74,16 +74,6 @@ - (UIView *)view
 RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
 RCT_EXPORT_VIEW_PROPERTY(onRefreshStart, RCTDirectEventBlock)
 
-- (NSDictionary<NSString *, id> *)constantsToExport
-{
-  return @{
-    @"DecelerationRate": @{
-      @"normal": @(UIScrollViewDecelerationRateNormal),
-      @"fast": @(UIScrollViewDecelerationRateFast),
-    },
-  };
-}
-
 RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag
                   callback:(RCTResponseSenderBlock)callback)
 {
diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h
index 40917304d48331..e845060f95958a 100644
--- a/React/Views/RCTShadowView.h
+++ b/React/Views/RCTShadowView.h
@@ -68,6 +68,12 @@ typedef void (^RCTApplierBlock)(NSDictionary<NSNumber *, UIView *> *viewRegistry
 - (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
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index 42a2f1b8847fb1..c755bbade0425e 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -535,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;
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 3f312abd1d87f3..7dc5bf3187ad7d 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -90,4 +90,9 @@
  */
 @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 010c62c492933a..ba0aa82a4337ee 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -109,6 +109,7 @@ - (instancetype)initWithFrame:(CGRect)frame
     _borderBottomLeftRadius = -1;
     _borderBottomRightRadius = -1;
     _borderStyle = RCTBorderStyleSolid;
+    _hitTestEdgeInsets = UIEdgeInsetsZero;
 
     _backgroundColor = super.backgroundColor;
   }
@@ -180,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) {
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index 5c5cb8f7c2d406..eef814a2bf7958 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -52,6 +52,9 @@ @implementation RCTViewManager
 
 - (dispatch_queue_t)methodQueue
 {
+  RCTAssert(_bridge, @"Bridge not set");
+  RCTAssert(_bridge.uiManager || !_bridge.valid, @"UIManager not initialized");
+  RCTAssert(_bridge.uiManager.methodQueue || !_bridge.valid, @"UIManager.methodQueue not initialized");
   return _bridge.uiManager.methodQueue;
 }
 
@@ -193,6 +196,17 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
     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)
 
diff --git a/React/Views/RCTWebView.h b/React/Views/RCTWebView.h
index 386ce8990cef66..05246a09b78888 100644
--- a/React/Views/RCTWebView.h
+++ b/React/Views/RCTWebView.h
@@ -35,6 +35,7 @@ shouldStartLoadForRequest:(NSMutableDictionary<NSString *, id> *)request
 @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 cddd9ede1784bf..48307674f0bd55 100644
--- a/React/Views/RCTWebView.m
+++ b/React/Views/RCTWebView.m
@@ -80,6 +80,9 @@ - (void)setSource:(NSDictionary *)source
     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;
     }
@@ -115,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);
@@ -154,12 +170,25 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR
 {
   BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme];
 
+  static NSDictionary<NSNumber *, NSString *> *navigationTypes;
+  static dispatch_once_t onceToken;
+  dispatch_once(&onceToken, ^{
+    navigationTypes = @{
+      @(UIWebViewNavigationTypeLinkClicked): @"click",
+      @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit",
+      @(UIWebViewNavigationTypeBackForward): @"backforward",
+      @(UIWebViewNavigationTypeReload): @"reload",
+      @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit",
+      @(UIWebViewNavigationTypeOther): @"other",
+    };
+  });
+
   // skip this for the JS Navigation handler
   if (!isJSNavigation && _onShouldStartLoadWithRequest) {
     NSMutableDictionary<NSString *, id> *event = [self baseEvent];
     [event addEntriesFromDictionary: @{
       @"url": (request.URL).absoluteString,
-      @"navigationType": @(navigationType)
+      @"navigationType": navigationTypes[@(navigationType)]
     }];
     if (![self.delegate webView:self
       shouldStartLoadForRequest:event
@@ -175,7 +204,7 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR
       NSMutableDictionary<NSString *, id> *event = [self baseEvent];
       [event addEntriesFromDictionary: @{
         @"url": (request.URL).absoluteString,
-        @"navigationType": @(navigationType)
+        @"navigationType": navigationTypes[@(navigationType)]
       }];
       _onLoadingStart(event);
     }
diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m
index 33a5d77937fb40..c8d237dda000a8 100644
--- a/React/Views/RCTWebViewManager.m
+++ b/React/Views/RCTWebViewManager.m
@@ -36,8 +36,8 @@ - (UIView *)view
 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(scalesPageToFit, _webView.scalesPageToFit, 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)
@@ -47,21 +47,6 @@ - (UIView *)view
 RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
 RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL)
 
-- (NSDictionary<NSString *, id> *)constantsToExport
-{
-  return @{
-    @"JSNavigationScheme": RCTJSNavigationScheme,
-    @"NavigationType": @{
-      @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked),
-      @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted),
-      @"BackForward": @(UIWebViewNavigationTypeBackForward),
-      @"Reload": @(UIWebViewNavigationTypeReload),
-      @"FormResubmitted": @(UIWebViewNavigationTypeFormResubmitted),
-      @"Other": @(UIWebViewNavigationTypeOther)
-    },
-  };
-}
-
 RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
 {
   [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTWebView *> *viewRegistry) {
diff --git a/ReactAndroid/DEFS b/ReactAndroid/DEFS
index 1ba010a939887a..104c161896f566 100644
--- a/ReactAndroid/DEFS
+++ b/ReactAndroid/DEFS
@@ -6,7 +6,6 @@
 
 import os
 
-
 # Example: react_native_target('java/com/facebook/react/common:common')
 def react_native_target(path):
   return '//ReactAndroid/src/main/' + path
@@ -24,6 +23,18 @@ def react_native_integration_tests_target(path):
 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',
+]
+
+FBGLOGINIT_TARGET = '//ReactAndroid/src/main/jni/first-party/fbgloginit:fbgloginit'
+
 INTERNAL_APP = 'PUBLIC'
 
 # React property preprocessor
@@ -86,4 +97,4 @@ def robolectric3_test(name, deps, vm_args=None, *args, **kwargs):
     vm_args=vm_args + extra_vm_args,
     *args,
     **kwargs
-  )
\ No newline at end of file
+  )
diff --git a/ReactAndroid/README.md b/ReactAndroid/README.md
index 891b008ac52274..08c34b99249214 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 913dcbb73347d5..8f26d79976562e 100644
--- a/ReactAndroid/build.gradle
+++ b/ReactAndroid/build.gradle
@@ -204,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"
@@ -219,7 +224,7 @@ android {
         }
 
         buildConfigField 'boolean', 'IS_INTERNAL_BUILD', 'false'
-        testApplicationId "com.facebook.react.tests"
+        testApplicationId "com.facebook.react.tests.gradle"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
diff --git a/ReactAndroid/src/androidTest/AndroidManifest.xml b/ReactAndroid/src/androidTest/AndroidManifest.xml
index 4275a7ea68567b..f6ce0fa0511cd0 100644
--- a/ReactAndroid/src/androidTest/AndroidManifest.xml
+++ b/ReactAndroid/src/androidTest/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.facebook.react.tests"
+          package="com.facebook.react.tests.gradle"
           android:versionCode="1"
           android:versionName="1.0" >
   <uses-sdk android:targetSdkVersion="7" />
@@ -10,10 +10,11 @@
   <!-- needed for screenshot tests -->
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-  <application>
+  <application
+    android:hardwareAccelerated="false">
     <activity
-            android:name="com.facebook.react.testing.ReactAppTestActivity"
-            android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen"
+      android:name="com.facebook.react.testing.ReactAppTestActivity"
+      android:theme="@style/Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen"
     />
   </application>
 </manifest>
diff --git a/ReactAndroid/src/androidTest/BUCK_temp b/ReactAndroid/src/androidTest/BUCK_temp
deleted file mode 100644
index d33209cedb5706..00000000000000
--- a/ReactAndroid/src/androidTest/BUCK_temp
+++ /dev/null
@@ -1,66 +0,0 @@
-include_defs('//ReactAndroid/DEFS')
-
-
-CATALYST_PRIMERY_DEX_PATTERNS =  [
-  '/CatalystAppShell^',
-  '/CatalystApplicationImpl^',
-  '^com/facebook/buck/android/support/exopackage/',
-  '/FbInstrumentationTestRunner^',
-  '/PrimaryDexFactories^',
-  '/Screenshot^',
-  '/DexmakerMockMaker^',
-  '/AndroidJUnitRunner^',
-  '/InstrumentationRunListener^',
-  '/ExposedInstrumentationApi^',
-  '/TestApplication^',
-  '/ApplicationWithInjector^',
-  '/RunListener^',
-]
-
-JS_BUNDLE_DEPS = [
-  #':intern-schema',
-#   '//java/com/facebook/graphql:schema',
-#   '//java/com/facebook/graphql/graphql-data:locate_node'
-]
-
-
-# instrumentation tests that will run for tests located in GitHub open sourced folder
-android_binary (
-  name = 'react_oss',
-  manifest = 'AndroidManifest.xml',
-  keystore = '//keystores:debug',
-  use_split_dex = True,
-  linear_alloc_hard_limit = 10 * 1024 * 1024,
-  primary_dex_patterns = CATALYST_PRIMERY_DEX_PATTERNS,
-  deps = [
-#     ':integration_test_oss_bundle_js',
-    react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
-  #     '//java/com/facebook/catalyst/js/react-native-github/ReactAndroid/src/main/res:shell',
-  ],
-)
-
-# Building this rule will produce a file named messenger_test.apk
-android_instrumentation_apk(
-  name = 'react_oss_test_apk',
-  manifest = 'AndroidManifest.xml',
-  apk = ':react_oss',
-  deps = [
-#     react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
-    #     react_native_integration_tests_target('java/com/facebook/react/tests:tests'),
-    #     '//java/com/facebook/catalyst:integration_test_oss_bundle_js#dev',
-  ],
-)
-
-
-
-android_instrumentation_test(
-  name = 'react_oss_test',
-  apk = ':react_oss_test_apk',
-)
-
-sh_binary(
-  name = 'integration_test_oss_bundle_js',
-  main = 'buildBundle.sh',
-#     deps = [':InstallReactNativeNodeModules'],
-  visibility = ['PUBLIC'],
-)
diff --git a/ReactAndroid/src/androidTest/assets/BUCK b/ReactAndroid/src/androidTest/assets/BUCK
new file mode 100644
index 00000000000000..4648c5ef94f5f3
--- /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/buck-runner/AndroidManifest.xml b/ReactAndroid/src/androidTest/buck-runner/AndroidManifest.xml
new file mode 100644
index 00000000000000..8fd01fc76607a3
--- /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 00000000000000..337e846c1e0541
--- /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/buildBundle.sh b/ReactAndroid/src/androidTest/buildBundle.sh
deleted file mode 100755
index 6a61faa90ed204..00000000000000
--- a/ReactAndroid/src/androidTest/buildBundle.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-# TODO put output to temp folder?
-node ./local-cli/cli.js bundle --entry-file ReactAndroid/src/androidTest/assets/TestBundle.js --dev --platform android --bundle-output ReactAndroid/src/androidTest/assets/ReactAndroidTestBundle.js
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
index 9297f59a246035..37f0a4db395587 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java
@@ -28,7 +28,7 @@
 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
@@ -75,7 +75,7 @@ protected void onPause() {
     overridePendingTransition(0, 0);
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onPause();
+      mReactInstanceManager.onHostPause();
     }
   }
 
@@ -86,7 +86,7 @@ protected void onResume() {
     mLifecycleState = LifecycleState.RESUMED;
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onResume(this, this);
+      mReactInstanceManager.onHostResume(this, this);
     }
   }
 
@@ -96,7 +96,7 @@ protected void onDestroy() {
     mDestroyCountDownLatch.countDown();
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onDestroy();
+      mReactInstanceManager.destroy();
     }
   }
 
@@ -112,9 +112,17 @@ public void loadApp(String appKey, ReactInstanceSpecForTest spec, String bundleN
     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.onDestroy();
+      mReactInstanceManager.destroy();
       mReactInstanceManager = null;
     }
     mReactRootView = new ReactRootView(this);
@@ -128,6 +136,16 @@ public void loadApp(
       @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();
@@ -145,10 +163,11 @@ public void loadApp(
         .addPackage(new InstanceSpecForTestPackage(spec))
         .setUseDeveloperSupport(useDevSupport)
         .setBridgeIdleDebugListener(mBridgeIdleSignaler)
-        .setInitialLifecycleState(mLifecycleState);
+        .setInitialLifecycleState(mLifecycleState)
+        .setUIImplementationProvider(uiImplementationProvider);
 
     mReactInstanceManager = builder.build();
-    mReactInstanceManager.onResume(this, this);
+    mReactInstanceManager.onHostResume(this, this);
 
     Assertions.assertNotNull(mReactRootView).getViewTreeObserver().addOnGlobalLayoutListener(
         new ViewTreeObserver.OnGlobalLayoutListener() {
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
index 5161a4aaab60d0..b8d85fe0ffdbb8 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java
@@ -70,14 +70,17 @@ public void shutDownContext() {
       mReactContext = null;
       mInstance = null;
 
+      final SimpleSettableFuture<Void> semaphore = new SimpleSettableFuture<>();
       UiThreadUtil.runOnUiThread(new Runnable() {
         @Override
         public void run() {
           if (contextToDestroy != null) {
-            contextToDestroy.onDestroy();
+            contextToDestroy.destroy();
           }
+          semaphore.set(null);
         }
       });
+      semaphore.getOrThrow();
     }
   }
 
diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
index fbe1c0db23954b..20afcb2341066d 100644
--- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
+++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactTestHelper.java
@@ -12,6 +12,7 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.support.test.InstrumentationRegistry;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -24,9 +25,9 @@
 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 android.support.test.InstrumentationRegistry;
 import com.android.internal.util.Predicate;
 
 public class ReactTestHelper {
@@ -60,7 +61,7 @@ public ReactInstanceEasyBuilder addJSModule(Class moduleInterfaceClass) {
       public CatalystInstance build() {
         return new CatalystInstanceImpl.Builder()
           .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
-          .setJSExecutor(new JSCJavaScriptExecutor())
+          .setJSExecutor(new JSCJavaScriptExecutor(new WritableNativeMap()))
           .setRegistry(mNativeModuleRegistryBuilder.build())
           .setJSModulesConfig(mJSModulesConfigBuilder.build())
           .setJSBundleLoader(JSBundleLoader.createFileLoader(
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 00000000000000..8dd771536fa144
--- /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/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java
index 711aed2ab2d5c7..5efd88e3b2a1ea 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<<df03fd95c4520badeed398c76e70242d>>
+// @generated SignedSource<<dcc87213906997ff353adb1148c8e77c>>
 
 package com.facebook.csslayout;
 
@@ -258,7 +258,7 @@ private static void layoutNodeImpl(
     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)) {
@@ -274,7 +274,7 @@ private static void layoutNodeImpl(
       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]])) {
+      } 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 -
@@ -320,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;
@@ -383,8 +383,8 @@ private static void layoutNodeImpl(
       float mainDim = leadingPaddingAndBorderMain;
       float crossDim = 0;
   
-      float maxWidth;
-      float maxHeight;
+      float maxWidth = CSSConstants.UNDEFINED;
+      float maxHeight = CSSConstants.UNDEFINED;
       for (i = startLine; i < childCount; ++i) {
         child = node.getChildAt(i);
         child.lineIndex = linesCount;
@@ -421,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]])) {
@@ -468,7 +468,7 @@ private static void layoutNodeImpl(
           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 {
@@ -477,7 +477,7 @@ private static void layoutNodeImpl(
                 paddingAndBorderAxisResolvedRow;
             }
           } else {
-            if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+            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 {
@@ -528,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;
         }
@@ -611,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) {
@@ -620,7 +620,7 @@ private static void layoutNodeImpl(
               paddingAndBorderAxisResolvedRow;
           }
           maxHeight = CSSConstants.UNDEFINED;
-          if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) {
+          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) {
@@ -738,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
@@ -824,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]))
@@ -917,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 955473ba0b5555..0de9f29a65f688 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/219bdaed15c16bbf7c1f2bab17ad629d04cc4199
+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 8d78b2695779ec..fc97204649102a 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/219bdaed15c16bbf7c1f2bab17ad629d04cc4199
+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 3006da53a95c1d..a0c845dd62878a 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 18f754bf474999..13090a18ce30a3 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 fa6e971f6d414d..45e9bfe0cee0dd 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/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
index 07b18b066b305f..d7b34e270acedc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java
@@ -19,6 +19,7 @@
 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;
@@ -95,6 +96,7 @@ public List<Class<? extends JavaScriptModule>> createJSModules() {
         RCTNativeAppEventEmitter.class,
         AppRegistry.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 00000000000000..959a6f8d36c92e
--- /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 f8598e90884572..068bd063fb0c36 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
index 58f2833110e2de..e9fd7574895f3b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java
@@ -2,20 +2,22 @@
 
 package com.facebook.react;
 
-import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 import android.annotation.TargetApi;
-import android.app.Activity;
+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.CatalystInstance;
 import com.facebook.react.bridge.MemoryPressure;
-import com.facebook.react.bridge.ReactContext;
+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;
 
@@ -24,10 +26,14 @@
  */
 public class MemoryPressureRouter {
   // Trigger this by sending an intent to your activity with adb shell:
-  // am start -a "com.facebook.catalyst.ACTION_TRIM_MEMORY" --activity-single-top -n <activity>
-  private static final String ACTION_TRIM_MEMORY ="com.facebook.catalyst.ACTION_TRIM_MEMORY";
-
-  private @Nullable CatalystInstance mCatalystInstance;
+  // 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) {
@@ -44,11 +50,13 @@ public void onLowMemory() {
   };
 
   @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
-  public static boolean handleDebugIntent(Activity activity, String action) {
+  public static boolean handleDebugIntent(Application application, String action) {
     switch (action) {
-      case ACTION_TRIM_MEMORY:
-        simulateTrimMemory(activity, TRIM_MEMORY_MODERATE);
+      case ACTION_TRIM_MEMORY_MODERATE:
+        simulateTrimMemory(application, TRIM_MEMORY_MODERATE);
         break;
+      case ACTION_TRIM_MEMORY_CRITICAL:
+        simulateTrimMemory(application, TRIM_MEMORY_COMPLETE);
       default:
         return false;
     }
@@ -60,12 +68,18 @@ public static boolean handleDebugIntent(Activity activity, String action) {
     context.getApplicationContext().registerComponentCallbacks(mCallbacks);
   }
 
-  public void onNewReactContextCreated(ReactContext reactContext) {
-    mCatalystInstance = reactContext.getCatalystInstance();
+  /**
+   * Add a listener to be notified of memory pressure events.
+   */
+  public void addMemoryPressureListener(MemoryPressureListener listener) {
+    mListeners.add(listener);
   }
 
-  public void onReactInstanceDestroyed() {
-    mCatalystInstance = null;
+  /**
+   * Remove a listener previously added with {@link #addMemoryPressureListener}.
+   */
+  public void removeMemoryPressureListener(MemoryPressureListener listener) {
+    mListeners.remove(listener);
   }
 
   public void destroy(Context context) {
@@ -81,13 +95,16 @@ private void trimMemory(int level) {
   }
 
   private void dispatchMemoryPressure(MemoryPressure level) {
-    if (mCatalystInstance != null) {
-      mCatalystInstance.handleMemoryPressure(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(Activity activity, int level) {
-    activity.getApplication().onTrimMemory(level);
-    activity.onTrimMemory(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
index c9679fafb96d71..d708e7dc2e6bad 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@@ -150,7 +150,7 @@ protected void onPause() {
     mLifecycleState = LifecycleState.BEFORE_RESUME;
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onPause();
+      mReactInstanceManager.onHostPause();
     }
   }
 
@@ -161,7 +161,7 @@ protected void onResume() {
     mLifecycleState = LifecycleState.RESUMED;
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onResume(this, this);
+      mReactInstanceManager.onHostResume(this, this);
     }
   }
 
@@ -170,7 +170,7 @@ protected void onDestroy() {
     super.onDestroy();
 
     if (mReactInstanceManager != null) {
-      mReactInstanceManager.onDestroy();
+      mReactInstanceManager.destroy();
     }
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
index 17fb538b826d47..2e2ca160301f4a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
@@ -41,8 +41,8 @@
  * 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}).
  *
  * 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.
@@ -62,6 +62,8 @@ public interface ReactInstanceEventListener {
 
   public abstract DevSupportManager getDevSupportManager();
 
+  public abstract MemoryPressureRouter getMemoryPressureRouter();
+
   /**
    * Trigger react context initialization asynchronously in a background async task. This enables
    * applications to pre-load the application JS, and execute global code before
@@ -84,22 +86,32 @@ public interface ReactInstanceEventListener {
    * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
    */
   public abstract void onBackPressed();
-  public abstract void onPause();
+
+  /**
+   * 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 abstract void onResume(
+  public abstract void onHostResume(
     Activity activity,
     DefaultHardwareBackBtnHandler defaultBackButtonImpl);
-  public abstract void onDestroy();
+
+  /**
+   * 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();
 
@@ -125,6 +137,11 @@ public abstract void onResume(
    */
   public abstract void detachRootView(ReactRootView rootView);
 
+  /**
+   * Destroy this React instance and the attached JS context.
+   */
+  public abstract void destroy();
+
   /**
    * Uses configured {@link ReactPackage} instances to create all view managers
    */
@@ -136,9 +153,16 @@ public abstract List<ViewManager> createAllViewManagers(
    */
   public abstract void addReactInstanceEventListener(ReactInstanceEventListener listener);
 
+  /**
+   * Remove a listener previously added with {@link #addReactInstanceEventListener}.
+   */
+  public abstract void removeReactInstanceEventListener(ReactInstanceEventListener listener);
+
   @VisibleForTesting
   public abstract @Nullable ReactContext getCurrentReactContext();
 
+  public abstract LifecycleState getLifecycleState();
+
   /**
    * Creates a builder that is capable of creating an instance of {@link ReactInstanceManagerImpl}.
    */
@@ -161,6 +185,7 @@ public static class Builder {
     protected @Nullable LifecycleState mInitialLifecycleState;
     protected @Nullable UIImplementationProvider mUIImplementationProvider;
     protected @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+    protected @Nullable JSCConfig mJSCConfig;
 
     protected Builder() {
     }
@@ -254,6 +279,11 @@ public Builder setNativeModuleCallExceptionHandler(NativeModuleCallExceptionHand
       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:
@@ -287,7 +317,8 @@ public ReactInstanceManager build() {
           mBridgeIdleDebugListener,
           Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
           mUIImplementationProvider,
-          mNativeModuleCallExceptionHandler);
+          mNativeModuleCallExceptionHandler,
+          mJSCConfig);
     }
   }
 }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
index a92b151e1d17b0..0c480eb58680e8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java
@@ -15,8 +15,9 @@
 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 java.util.concurrent.ConcurrentLinkedQueue;
 
 import android.app.Activity;
 import android.app.Application;
@@ -58,8 +59,7 @@
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.react.devsupport.DevServerHelper;
 import com.facebook.react.devsupport.DevSupportManager;
-import com.facebook.react.devsupport.DevSupportManagerImpl;
-import com.facebook.react.devsupport.DisabledDevSupportManager;
+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;
@@ -71,7 +71,18 @@
 import com.facebook.soloader.SoLoader;
 import com.facebook.systrace.Systrace;
 
-import static com.facebook.react.bridge.ReactMarkerConstants.*;
+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
@@ -85,8 +96,8 @@
  * 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 #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}.
  */
@@ -111,11 +122,12 @@
   private String mSourceUrl;
   private @Nullable Activity mCurrentActivity;
   private final Collection<ReactInstanceEventListener> mReactInstanceEventListeners =
-      new ConcurrentLinkedQueue<>();
+      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() {
@@ -182,7 +194,9 @@ protected void onPreExecute() {
     protected Result<ReactApplicationContext> doInBackground(ReactContextInitParams... params) {
       Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
       try {
-        JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
+        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
@@ -263,7 +277,8 @@ public T get() throws Exception {
       @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
       LifecycleState initialLifecycleState,
       UIImplementationProvider uiImplementationProvider,
-      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
+      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
+      @Nullable JSCConfig jscConfig) {
     initializeSoLoaderIfNecessary(applicationContext);
 
     // TODO(9577825): remove this
@@ -275,20 +290,17 @@ public T get() throws Exception {
     mJSMainModuleName = jsMainModuleName;
     mPackages = packages;
     mUseDeveloperSupport = useDeveloperSupport;
-    if (mUseDeveloperSupport) {
-      mDevSupportManager = new DevSupportManagerImpl(
-          applicationContext,
-          mDevInterface,
-          mJSMainModuleName,
-          useDeveloperSupport);
-    } else {
-      mDevSupportManager = new DisabledDevSupportManager();
-    }
+    mDevSupportManager = DevSupportManagerFactory.create(
+        applicationContext,
+        mDevInterface,
+        mJSMainModuleName,
+        useDeveloperSupport);
     mBridgeIdleDebugListener = bridgeIdleDebugListener;
     mLifecycleState = initialLifecycleState;
     mUIImplementationProvider = uiImplementationProvider;
     mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
     mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+    mJSCConfig = jscConfig;
   }
 
   @Override
@@ -296,6 +308,11 @@ 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
@@ -308,8 +325,11 @@ private static void initializeSoLoaderIfNecessary(Context applicationContext) {
   }
 
   private static void setDisplayMetrics(Context context) {
-    DisplayMetrics displayMetrics = new DisplayMetrics();
-    displayMetrics.setTo(context.getResources().getDisplayMetrics());
+    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();
 
@@ -317,9 +337,8 @@ private static void setDisplayMetrics(Context context) {
     // 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(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.
@@ -330,13 +349,13 @@ private static void setDisplayMetrics(Context context) {
       try {
         Method mGetRawH = Display.class.getMethod("getRawHeight");
         Method mGetRawW = Display.class.getMethod("getRawWidth");
-        displayMetrics.widthPixels = (Integer) mGetRawW.invoke(display);
-        displayMetrics.heightPixels = (Integer) mGetRawH.invoke(display);
+        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.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(screenDisplayMetrics);
   }
 
   /**
@@ -458,20 +477,16 @@ private void toggleElementInspector() {
   }
 
   @Override
-  public void onPause() {
+  public void onHostPause() {
     UiThreadUtil.assertOnUiThread();
 
-    mLifecycleState = LifecycleState.BEFORE_RESUME;
-
     mDefaultBackButtonImpl = null;
     if (mUseDeveloperSupport) {
       mDevSupportManager.setDevSupportEnabled(false);
     }
 
+    moveToBeforeResumeLifecycleState();
     mCurrentActivity = null;
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onPause();
-    }
   }
 
   /**
@@ -479,17 +494,16 @@ public void onPause() {
    *
    * 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 #onDestroy} event it will
+   * 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 onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
+  public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
     UiThreadUtil.assertOnUiThread();
 
-    mLifecycleState = LifecycleState.RESUMED;
 
     mDefaultBackButtonImpl = defaultBackButtonImpl;
     if (mUseDeveloperSupport) {
@@ -497,32 +511,86 @@ public void onResume(Activity activity, DefaultHardwareBackBtnHandler defaultBac
     }
 
     mCurrentActivity = activity;
-    if (mCurrentReactContext != null) {
-      mCurrentReactContext.onResume(activity);
+    moveToResumedLifecycleState(false);
+  }
+
+  @Override
+  public void onHostDestroy() {
+    UiThreadUtil.assertOnUiThread();
+
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(false);
     }
+
+    moveToBeforeCreateLifecycleState();
+    mCurrentActivity = null;
   }
 
   @Override
-  public void onDestroy() {
+  public void destroy() {
     UiThreadUtil.assertOnUiThread();
 
+    if (mUseDeveloperSupport) {
+      mDevSupportManager.setDevSupportEnabled(false);
+    }
+
+    moveToBeforeCreateLifecycleState();
+
     if (mReactContextInitAsyncTask != null) {
       mReactContextInitAsyncTask.cancel(true);
     }
 
     mMemoryPressureRouter.destroy(mApplicationContext);
-    if (mUseDeveloperSupport) {
-      mDevSupportManager.setDevSupportEnabled(false);
-    }
 
     if (mCurrentReactContext != null) {
-      mCurrentReactContext.onDestroy();
+      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) {
@@ -602,6 +670,11 @@ public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
     mReactInstanceEventListeners.add(listener);
   }
 
+  @Override
+  public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
+    mReactInstanceEventListeners.remove(listener);
+  }
+
   @VisibleForTesting
   @Override
   public @Nullable ReactContext getCurrentReactContext() {
@@ -651,14 +724,18 @@ private void setupReactContext(ReactApplicationContext reactContext) {
 
     catalystInstance.initialize();
     mDevSupportManager.onNewReactContextCreated(reactContext);
-    mMemoryPressureRouter.onNewReactContextCreated(reactContext);
-    moveReactContextToCurrentLifecycleState(reactContext);
+    mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
+    moveReactContextToCurrentLifecycleState();
 
     for (ReactRootView rootView : mAttachedRootViews) {
       attachMeasuredRootViewToInstance(rootView, catalystInstance);
     }
 
-    for (ReactInstanceEventListener listener : mReactInstanceEventListeners) {
+    ReactInstanceEventListener[] listeners =
+      new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
+    listeners = mReactInstanceEventListeners.toArray(listeners);
+
+    for (ReactInstanceEventListener listener : listeners) {
       listener.onReactContextInitialized(reactContext);
     }
   }
@@ -697,14 +774,14 @@ private void detachViewFromInstance(
   private void tearDownReactContext(ReactContext reactContext) {
     UiThreadUtil.assertOnUiThread();
     if (mLifecycleState == LifecycleState.RESUMED) {
-      reactContext.onPause();
+      reactContext.onHostPause();
     }
     for (ReactRootView rootView : mAttachedRootViews) {
       detachViewFromInstance(rootView, reactContext.getCatalystInstance());
     }
-    reactContext.onDestroy();
+    reactContext.destroy();
     mDevSupportManager.onReactInstanceDestroyed(reactContext);
-    mMemoryPressureRouter.onReactInstanceDestroyed();
+    mMemoryPressureRouter.removeMemoryPressureListener(reactContext.getCatalystInstance());
   }
 
   /**
@@ -822,9 +899,9 @@ private void processPackage(
     }
   }
 
-  private void moveReactContextToCurrentLifecycleState(ReactApplicationContext reactContext) {
+  private void moveReactContextToCurrentLifecycleState() {
     if (mLifecycleState == LifecycleState.RESUMED) {
-      reactContext.onResume(mCurrentActivity);
+      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 2271b20fd46a87..bf4b8b4bc8438f 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;
@@ -151,7 +151,7 @@ private void handleTouchEvent(MotionEvent ev) {
       eventDispatcher.dispatchEvent(
           TouchEvent.obtain(
               mTargetTag,
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               TouchEventType.START,
               ev,
               mTargetCoordinates[0],
@@ -173,7 +173,7 @@ private void handleTouchEvent(MotionEvent ev) {
       eventDispatcher.dispatchEvent(
           TouchEvent.obtain(
               mTargetTag,
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               TouchEventType.END,
               ev,
               mTargetCoordinates[0],
@@ -184,7 +184,7 @@ private void handleTouchEvent(MotionEvent ev) {
       eventDispatcher.dispatchEvent(
           TouchEvent.obtain(
               mTargetTag,
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               TouchEventType.MOVE,
               ev,
               mTargetCoordinates[0],
@@ -194,7 +194,7 @@ private void handleTouchEvent(MotionEvent ev) {
       eventDispatcher.dispatchEvent(
           TouchEvent.obtain(
               mTargetTag,
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               TouchEventType.START,
               ev,
               mTargetCoordinates[0],
@@ -204,7 +204,7 @@ private void handleTouchEvent(MotionEvent ev) {
       eventDispatcher.dispatchEvent(
           TouchEvent.obtain(
               mTargetTag,
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               TouchEventType.END,
               ev,
               mTargetCoordinates[0],
@@ -255,7 +255,7 @@ private void dispatchCancelEvent(MotionEvent androidEvent) {
     Assertions.assertNotNull(eventDispatcher).dispatchEvent(
         TouchEvent.obtain(
             mTargetTag,
-            SystemClock.uptimeMillis(),
+            SystemClock.nanoTime(),
             TouchEventType.CANCEL,
             androidEvent,
             mTargetCoordinates[0],
@@ -390,7 +390,7 @@ private KeyboardListener getKeyboardListener() {
   private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
     private final Rect mVisibleViewArea;
     private final int mMinKeyboardHeightDetected;
-    
+
     private int mKeyboardHeight = 0;
 
     /* package */ KeyboardListener() {
@@ -410,7 +410,7 @@ public void onGlobalLayout() {
 
       getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
       final int heightDiff =
-          DisplayMetricsHolder.getDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
+          DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
       if (mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected) {
         // keyboard is now showing, or the keyboard height has changed
         mKeyboardHeight = heightDiff;
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 1a84c05fa4102f..afa121acb40232 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) {
@@ -332,8 +351,18 @@ 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 8b5153e5cc51eb..a7bc858026eece 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 466f229d151d76..17c01f542869e5 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
@@ -14,7 +14,6 @@
 import com.facebook.react.bridge.queue.ReactQueueConfiguration;
 import com.facebook.react.common.annotations.VisibleForTesting;
 import com.facebook.proguard.annotations.DoNotStrip;
-import com.facebook.react.common.annotations.VisibleForTesting;
 
 /**
  * A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -22,12 +21,12 @@
  * Java APIs be invokable from JavaScript as well.
  */
 @DoNotStrip
-public interface CatalystInstance {
+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
-  void invokeCallback(final int callbackID, final NativeArray arguments);
+  void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray 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
@@ -45,11 +44,10 @@ public interface CatalystInstance {
   ReactQueueConfiguration getReactQueueConfiguration();
 
   <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();
 
-  void handleMemoryPressure(MemoryPressure 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
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
index d3bcc1abd09aad..6975d1ea973e1f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
@@ -27,6 +27,7 @@
 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;
 
@@ -52,7 +53,14 @@ public class CatalystInstanceImpl implements CatalystInstance {
   private final TraceListener mTraceListener;
   private final JavaScriptModuleRegistry mJSModuleRegistry;
   private final JSBundleLoader mJSBundleLoader;
-  private volatile int mTraceID = 0;
+  private @Nullable ExecutorToken mMainExecutorToken;
+
+  // These locks prevent additional calls from going JS<->Java after the bridge has been torn down.
+  // There are separate ones for each direction because a JS to Java call can trigger a Java to JS
+  // call: this would cause a deadlock with a traditional mutex (maybe we should be using a reader-
+  // writer lock but then we'd have to worry about starving the destroy call).
+  private final Object mJSToJavaCallsTeardownLock = new Object();
+  private final Object mJavaToJSCallsTeardownLock = new Object();
 
   // Access from native modules thread
   private final NativeModuleRegistry mJavaRegistry;
@@ -70,6 +78,7 @@ private CatalystInstanceImpl(
       final JavaScriptModulesConfig jsModulesConfig,
       final JSBundleLoader jsBundleLoader,
       NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
+    FLog.d(ReactConstants.TAG, "Initializing React Bridge.");
     mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
         ReactQueueConfigurationSpec,
         new NativeExceptionHandler());
@@ -111,6 +120,7 @@ private ReactBridge initializeBridge(
           jsExecutor,
           new NativeModulesReactCallback(),
           mReactQueueConfiguration.getNativeModulesQueueThread());
+      mMainExecutorToken = bridge.getMainExecutorToken();
     } finally {
       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     }
@@ -127,6 +137,7 @@ private ReactBridge initializeBridge(
       Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
     }
 
+    mJavaRegistry.notifyReactBridgeInitialized(bridge);
     return bridge;
   }
 
@@ -162,89 +173,38 @@ public Boolean call() throws Exception {
   }
 
   /* 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();
-
-    final int traceID = mTraceID++;
-    Systrace.startAsyncFlow(
-        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-        tracingName,
-        traceID);
-
-    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
-        new Runnable() {
-          @Override
-          public void run() {
-            mReactQueueConfiguration.getJSQueueThread().assertIsOnThread();
+      ExecutorToken executorToken,
+      int moduleId,
+      int methodId,
+      NativeArray arguments,
+      String tracingName) {
+    synchronized (mJavaToJSCallsTeardownLock) {
+      if (mDestroyed) {
+        FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
+        return;
+      }
 
-            Systrace.endAsyncFlow(
-                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-                tracingName,
-                traceID);
+      incrementPendingJSCalls();
 
-            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);
-            }
-          }
-        });
+      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(final int callbackID, final NativeArray arguments) {
-    if (mDestroyed) {
-      FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
-      return;
-    }
-
-    incrementPendingJSCalls();
-
-    final int traceID = mTraceID++;
-    Systrace.startAsyncFlow(
-        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-        "<callback>",
-        traceID);
-
-    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
-        new Runnable() {
-          @Override
-          public void run() {
-            mReactQueueConfiguration.getJSQueueThread().assertIsOnThread();
-
-            Systrace.endAsyncFlow(
-                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
-                "<callback>",
-                traceID);
+  public void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments) {
+    synchronized (mJavaToJSCallsTeardownLock) {
+      if (mDestroyed) {
+        FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
+        return;
+      }
 
-            if (mDestroyed) {
-              return;
-            }
+      incrementPendingJSCalls();
 
-            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);
-            }
-          }
-        });
+      Assertions.assertNotNull(mBridge).invokeCallback(executorToken, callbackID, arguments);
+    }
   }
 
   /**
@@ -256,26 +216,46 @@ public void run() {
   public void destroy() {
     UiThreadUtil.assertOnUiThread();
 
-    if (mDestroyed) {
-      return;
+    // This ordering is important. A JS to Java call that triggers a Java to JS call will also
+    // acquire these locks in the same order
+    synchronized (mJSToJavaCallsTeardownLock) {
+      synchronized (mJavaToJSCallsTeardownLock) {
+        if (mDestroyed) {
+          return;
+        }
+
+        // TODO: tell all APIs to shut down
+        mDestroyed = true;
+        mJavaRegistry.notifyCatalystInstanceDestroy();
+
+        Systrace.unregisterListener(mTraceListener);
+
+        synchronouslyDisposeBridgeOnJSThread();
+      }
     }
 
-    // TODO: tell all APIs to shut down
-    mDestroyed = true;
-    mJavaRegistry.notifyCatalystInstanceDestroy();
     mReactQueueConfiguration.destroy();
+
     boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
     if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
       for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
         listener.onTransitionToBridgeIdle();
       }
     }
+  }
 
-    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 ReactQueueConfiguration#destroy()
-    mBridge.dispose();
+  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
@@ -304,7 +284,12 @@ public ReactQueueConfiguration getReactQueueConfiguration() {
 
   @Override
   public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
-    return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(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
@@ -318,8 +303,14 @@ public Collection<NativeModule> getNativeModules() {
   }
 
   @Override
-  public void handleMemoryPressure(MemoryPressure level) {
-    Assertions.assertNotNull(mBridge).handleMemoryPressure(level);
+  public void handleMemoryPressure(final MemoryPressure level) {
+    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
+      new Runnable() {
+        @Override
+        public void run() {
+          Assertions.assertNotNull(mBridge).handleMemoryPressure(level);
+        }
+      });
   }
 
   /**
@@ -417,15 +408,17 @@ private void decrementPendingJSCalls() {
   private class NativeModulesReactCallback implements ReactCallback {
 
     @Override
-    public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
+    public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
       mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
 
-      // Suppress any callbacks if destroyed - will only lead to sadness.
-      if (mDestroyed) {
-        return;
-      }
+      synchronized (mJSToJavaCallsTeardownLock) {
+        // Suppress any callbacks if destroyed - will only lead to sadness.
+        if (mDestroyed) {
+          return;
+        }
 
-      mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
+        mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
+      }
     }
 
     @Override
@@ -436,17 +429,38 @@ public void onBatchComplete() {
       // 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);
+      synchronized (mJSToJavaCallsTeardownLock) {
+        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 (mJSToJavaCallsTeardownLock) {
+        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 {
@@ -470,12 +484,13 @@ public void run() {
   private class JSProfilerTraceListener implements TraceListener {
     @Override
     public void onTraceStarted() {
-      getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
+      getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(
+          true);
     }
 
     @Override
     public void onTraceStopped() {
-      getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
+      getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(false);
     }
   }
 
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 00000000000000..e23bbebfb7d1df
--- /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/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
index db9fb05df8eb7c..799863b087c4df 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java
@@ -31,7 +31,7 @@ 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);
         }
       }
 
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 32b2619309f380..b4431c0d66d9cb 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSCJavaScriptExecutor.java
@@ -16,8 +16,8 @@
 public class JSCJavaScriptExecutor extends JavaScriptExecutor {
   public static class Factory implements JavaScriptExecutor.Factory {
     @Override
-    public JavaScriptExecutor create() throws Exception {
-      return new JSCJavaScriptExecutor();
+    public JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception {
+      return new JSCJavaScriptExecutor(jscConfig);
     }
   }
 
@@ -25,10 +25,10 @@ public JavaScriptExecutor create() throws Exception {
     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 13c99c043d4361..849a0ea79c9c53 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) {
diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java
index 5c1b01b2222ea4..991fdfb6f292b9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaJSExecutor.java
@@ -40,7 +40,7 @@ public ProxyExecutorException(Throwable cause) {
    * @param sourceURL url or file location from which script content was loaded
    */
   @DoNotStrip
-  void executeApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
+  void loadApplicationScript(String script, String sourceURL) throws ProxyExecutorException;
 
   /**
    * Execute javascript method within js context
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 bdf7e4fc6136c1..92499d31441258 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptExecutor.java
@@ -15,7 +15,7 @@
 @DoNotStrip
 public abstract class JavaScriptExecutor extends Countable {
   public interface Factory {
-    JavaScriptExecutor create() throws Exception;
+    JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception;
   }
 
   /**
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 093770fe05fd68..9a48e686bb550e 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(
       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 WeakReference<ExecutorToken> mExecutorToken;
     private final CatalystInstanceImpl mCatalystInstance;
     private final JavaScriptModuleRegistration mModuleRegistration;
 
     public JavaScriptModuleInvocationHandler(
+        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 abeba7230c2f39..e6241f6e41cfa8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModulesConfig.java
@@ -52,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/MemoryPressureListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressureListener.java
new file mode 100644
index 00000000000000..e4640b96e277b4
--- /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 905b5be9352183..e954bc901552b5 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
@@ -23,7 +23,7 @@
  */
 public interface NativeModule {
   interface NativeMethod {
-    void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
+    void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters);
     String getType();
   }
 
@@ -59,8 +59,40 @@ interface NativeMethod {
    */
   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}
    */
   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 389fd66b4616b2..adb48a3647649d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
@@ -30,6 +30,7 @@ public class NativeModuleRegistry {
   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(
       List<ModuleDefinition> moduleTable,
@@ -37,17 +38,22 @@ private NativeModuleRegistry(
     mModuleTable = moduleTable;
     mModuleInstances = moduleInstances;
 
-    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) {
@@ -55,7 +61,7 @@ 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 */ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
@@ -65,6 +71,7 @@ private NativeModuleRegistry(
       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);
@@ -114,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));
   }
@@ -150,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);
       }
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 00000000000000..d7cbe6cee3342b
--- /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/ProxyJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
index 80cd6f4b3259b5..37d83b3386174c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ProxyJavaScriptExecutor.java
@@ -32,7 +32,7 @@ public Factory(JavaJSExecutor.Factory javaJSExecutorFactory) {
     }
 
     @Override
-    public JavaScriptExecutor create() throws Exception {
+    public JavaScriptExecutor create(WritableNativeMap jscConfig) throws Exception {
       return new ProxyJavaScriptExecutor(mJavaJSExecutorFactory.create());
     }
   }
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 b809e680b6a8d0..3c8128244f4b90 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
@@ -79,12 +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 7e4376c5685156..3e5c36be13f08a 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 084ed5c2015078..db25b0d731b886 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
@@ -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,25 +151,33 @@ 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();
     for (LifecycleEventListener listener : mLifecycleEventListeners) {
       listener.onHostPause();
     }
+    mCurrentActivity = null;
   }
 
   /**
    * 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();
     }
+  }
+
+  /**
+   * Destroy this instance, making it unusable.
+   */
+  public void destroy() {
+    UiThreadUtil.assertOnUiThread();
+
     if (mCatalystInstance != null) {
       mCatalystInstance.destroy();
     }
-    mCurrentActivity = null;
   }
 
   /**
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 00000000000000..ad3dc87f4b2b90
--- /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/WebsocketJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
index cddadba0074498..b9e1da2adbf016 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WebsocketJavaScriptExecutor.java
@@ -159,10 +159,10 @@ public void close() {
   }
 
   @Override
-  public void executeApplicationScript(String script, String sourceURL)
+  public void loadApplicationScript(String script, String sourceURL)
       throws ProxyExecutorException {
     JSExecutorCallbackFuture callback = new JSExecutorCallbackFuture();
-    Assertions.assertNotNull(mWebSocketClient).executeApplicationScript(
+    Assertions.assertNotNull(mWebSocketClient).loadApplicationScript(
         sourceURL,
         mInjectedObjects,
         callback);
@@ -190,7 +190,7 @@ public void executeApplicationScript(String script, String sourceURL)
 
   @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 9829ddfc5cfc93..84cc9accb2ba50 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
@@ -36,12 +36,14 @@ public interface MessageQueueThread {
   /**
    * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
    */
+  @DoNotStrip
   boolean isOnThread();
 
   /**
    * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
    * {@link AssertionError}) if the assertion fails.
    */
+  @DoNotStrip
   void assertIsOnThread();
 
   /**
@@ -49,5 +51,6 @@ public interface MessageQueueThread {
    * thing the thread runs. If called from a separate thread, this will block until the thread can
    * be quit and joined.
    */
+  @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
index f4377f4fe420fa..5593cb02f7aa47 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/queue/MessageQueueThreadImpl.java
@@ -82,6 +82,7 @@ public void run() {
   /**
    * @return whether the current Thread is also the Thread associated with this MessageQueueThread.
    */
+  @DoNotStrip
   @Override
   public boolean isOnThread() {
     return mLooper.getThread() == Thread.currentThread();
@@ -91,6 +92,7 @@ public boolean isOnThread() {
    * Asserts {@link #isOnThread()}, throwing a {@link AssertionException} (NOT an
    * {@link AssertionError}) if the assertion fails.
    */
+  @DoNotStrip
   @Override
   public void assertIsOnThread() {
     SoftAssertions.assertCondition(isOnThread(), mAssertionErrorMessage);
@@ -100,6 +102,7 @@ public void assertIsOnThread() {
    * 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;
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
index 1cc874e9cb326b..c2ab78571fe51a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/webworkers/WebWorkers.java
@@ -9,10 +9,20 @@
 
 package com.facebook.react.bridge.webworkers;
 
+import java.io.File;
+import java.io.IOException;
+
 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;
+import com.facebook.react.common.build.ReactBuildConfig;
+
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+import okio.Okio;
+import okio.Sink;
 
 @DoNotStrip
 public class WebWorkers {
@@ -21,9 +31,47 @@ public class WebWorkers {
    * Creates a new MessageQueueThread for a background web worker owned by the JS thread with the
    * given MessageQueueThread.
    */
+  @DoNotStrip
   public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) {
     return MessageQueueThreadImpl.startNewBackgroundThread(
         "web-worker-" + id,
         new ProxyQueueThreadExceptionHandler(ownerThread));
   }
+
+  /**
+   * Utility method used to help develop web workers on debug builds. In release builds, worker
+   * scripts need to be packaged with the app, but in dev mode we want to fetch/reload the worker
+   * script on the fly from the packager. This method fetches the given URL *synchronously* and
+   * writes it to the specified temp file.
+   *
+   * This is exposed from Java only because we don't want to add a C++ networking library dependency
+   *
+   * NB: The caller is responsible for deleting the file specified by outFileName when they're done
+   * with it.
+   * NB: We write to a temp file instead of returning a String because, depending on the size of the
+   * worker script, allocating the full script string on the Java heap can cause an OOM.
+   */
+  @DoNotStrip
+  public static void downloadScriptToFileSync(String url, String outFileName) {
+    if (!ReactBuildConfig.DEBUG) {
+      throw new RuntimeException(
+          "For security reasons, downloading scripts is only allowed in debug builds.");
+    }
+
+    OkHttpClient client = new OkHttpClient();
+    final File out = new File(outFileName);
+
+    Request request = new Request.Builder()
+        .url(url)
+        .build();
+
+    try {
+      Response response = client.newCall(request).execute();
+
+      Sink output = Okio.sink(out);
+      Okio.buffer(response.body().source()).readAll(output);
+    } catch (IOException e) {
+      throw new RuntimeException("Exception downloading web worker script to file", e);
+    }
+  }
 }
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 55fc9eb089e2ba..9caf34cd62312a 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
@@ -30,7 +30,7 @@ public class SimpleSettableFuture<T> implements Future<T> {
    * Sets the result. If another thread has called {@link #get}, they will immediately receive the
    * value. set or setException must only be called once.
    */
-  public void set(T result) {
+  public void set(@Nullable T result) {
     checkNotSet();
     mResult = result;
     mReadyLatch.countDown();
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 4f114ded6099a5..4b27023c06bf23 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManager.java
@@ -25,6 +25,7 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {
   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();
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 00000000000000..22d925672598bd
--- /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
index 120443eca3d162..75d7282a7f61dc 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
@@ -13,6 +13,8 @@
 
 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;
@@ -40,7 +42,6 @@
 import com.facebook.react.bridge.CatalystInstance;
 import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
 import com.facebook.react.bridge.JavaJSExecutor;
-import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
 import com.facebook.react.bridge.ReactContext;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.UiThreadUtil;
@@ -216,6 +217,14 @@ public void run() {
         });
   }
 
+  @Override
+  public void hideRedboxDialog() {
+    // dismiss redbox if exists
+    if (mRedBoxDialog != null) {
+      mRedBoxDialog.dismiss();
+    }
+  }
+
   private void showNewError(
       final String message,
       final StackFrame[] stack,
@@ -522,6 +531,18 @@ private void resetCurrentContext(@Nullable ReactContext reactContext) {
       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();
   }
 
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java
index 54f5335cd21bbf..5388f98b7d1a18 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java
@@ -46,6 +46,11 @@ public void updateJSError(String message, ReadableArray details, int errorCookie
 
   }
 
+  @Override
+  public void hideRedboxDialog() {
+
+  }
+
   @Override
   public void showDevOptionsDialog() {
 
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 00000000000000..8ef0e5e35bc114
--- /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/modules/camera/ImageEditingManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java
index cbcfbb596e003c..be9ef2f892ea52 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/camera/ImageEditingManager.java
@@ -24,13 +24,19 @@
 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;
@@ -40,6 +46,7 @@
 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.
@@ -54,6 +61,35 @@ public class ImageEditingManager extends ReactContextBaseJavaModule {
   /** 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);
@@ -241,6 +277,10 @@ protected void doInBackgroundGuarded(Void... params) {
         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) {
@@ -352,6 +392,47 @@ private Bitmap cropAndResize(
 
   // 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)) {
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 1329a5b7c6d8be..37a34283c8139e 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 876a2fca2c5de5..5b185d64b1d8a7 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 67f5ca2312f107..9e0ba2a3d72893 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 36954a125f8632..91a6ddc7ca24ee 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,6 +12,8 @@
 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;
 
@@ -20,7 +22,9 @@
 
 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.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
@@ -31,16 +35,24 @@
 /**
  * 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;
@@ -50,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.
      */
@@ -60,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);
@@ -77,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);
@@ -88,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 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) {
@@ -113,15 +130,13 @@ 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);
   }
 
@@ -172,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,
@@ -182,23 +217,41 @@ public void createTimer(
     long adjustedDuration = (long) Math.max(
         0,
         jsSchedulingTime - SystemClock.currentTimeMillis() + duration);
+    if (duration == 0 && !repeat) {
+      WritableArray timerToCall = Arguments.createArray();
+      timerToCall.pushInt(callbackID);
+      getReactApplicationContext().getJSModule(executorToken, JSTimersExecution.class)
+        .callTimers(timerToCall);
+      return;
+    }
+
     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);
-      if (timer != null) {
-        // We may have already called/removed it
-        mTimerIdsToTimers.remove(timerId);
-        mTimers.remove(timer);
+      SparseArray<Timer> timersForContext = mTimerIdsToTimers.get(executorToken);
+      if (timersForContext == null) {
+        return;
+      }
+      Timer timer = timersForContext.get(timerId);
+      if (timer == null) {
+        return;
       }
+      // We may have already called/removed it
+      mTimerIdsToTimers.remove(timerId);
+      mTimers.remove(timer);
     }
   }
 }
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 8f57f570ea7337..8139797ae63346 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
@@ -10,6 +10,7 @@
 package com.facebook.react.modules.intent;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 
@@ -80,10 +81,20 @@ public void openURL(String url, Promise promise) {
       Activity currentActivity = getCurrentActivity();
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
 
+      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 {
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         getReactApplicationContext().startActivity(intent);
       }
 
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
index 463ad17c4d591b..1b8b93cbe30f72 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/netinfo/NetInfoModule.java
@@ -17,15 +17,13 @@
 import android.net.NetworkInfo;
 import android.support.v4.net.ConnectivityManagerCompat;
 
-import com.facebook.common.logging.FLog;
-import com.facebook.react.bridge.Callback;
 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 com.facebook.react.common.ReactConstants;
 
 import static com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
 
@@ -41,8 +39,9 @@ public class NetInfoModule extends ReactContextBaseJavaModule
       "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 ConnectivityManagerCompat mConnectivityManagerCompat;
   private final ConnectivityBroadcastReceiver mConnectivityBroadcastReceiver;
   private boolean mNoNetworkPermission = false;
 
@@ -52,7 +51,6 @@ public NetInfoModule(ReactApplicationContext reactContext) {
     super(reactContext);
     mConnectivityManager =
         (ConnectivityManager) reactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-    mConnectivityManagerCompat = new ConnectivityManagerCompat();
     mConnectivityBroadcastReceiver = new ConnectivityBroadcastReceiver();
   }
 
@@ -81,25 +79,21 @@ public String getName() {
   }
 
   @ReactMethod
-  public void getCurrentConnectivity(Callback successCallback, Callback errorCallback) {
+  public void getCurrentConnectivity(Promise promise) {
     if (mNoNetworkPermission) {
-      if (errorCallback == null) {
-        FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE);
-        return;
-      }
-      errorCallback.invoke(MISSING_PERMISSION_MESSAGE);
+      promise.reject(ERROR_MISSING_PERMISSION, MISSING_PERMISSION_MESSAGE, null);
       return;
     }
-    successCallback.invoke(createConnectivityEventMap());
+    promise.resolve(createConnectivityEventMap());
   }
 
   @ReactMethod
-  public void isConnectionMetered(Callback successCallback) {
+  public void isConnectionMetered(Promise promise) {
     if (mNoNetworkPermission) {
-      FLog.e(ReactConstants.TAG, MISSING_PERMISSION_MESSAGE);
+      promise.reject(ERROR_MISSING_PERMISSION, MISSING_PERMISSION_MESSAGE, null);
       return;
     }
-    successCallback.invoke(mConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
+    promise.resolve(ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
   }
 
   private void registerReceiver() {
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 50cd58a08da0c0..cd9e6b98ca379c 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
@@ -18,6 +18,7 @@
 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;
@@ -116,9 +117,10 @@ public void onCatalystInstanceDestroy() {
 
   @ReactMethod
   /**
-  * @param timeout value of 0 results in no timeout
-  */
+   * @param timeout value of 0 results in no timeout
+   */
   public void sendRequest(
+      final ExecutorToken executorToken,
       String method,
       String url,
       final int requestId,
@@ -144,7 +146,7 @@ public void sendRequest(
 
     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);
@@ -155,7 +157,10 @@ public void sendRequest(
       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);
@@ -163,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);
@@ -172,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(
@@ -190,15 +198,15 @@ 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));
     }
 
     client.newCall(requestBuilder.build()).enqueue(
@@ -208,7 +216,7 @@ public void onFailure(Request request, IOException e) {
             if (mShuttingDown) {
               return;
             }
-            onRequestError(requestId, e.getMessage());
+            onRequestError(executorToken, requestId, e.getMessage());
           }
 
           @Override
@@ -218,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));
@@ -247,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();
@@ -274,31 +285,34 @@ 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();
@@ -307,7 +321,7 @@ private void onResponseReceived(int requestId, Response response) {
     args.pushMap(headers);
     args.pushString(response.request().urlString());
 
-    getEventEmitter().emit("didReceiveNetworkResponse", args);
+    getEventEmitter(ExecutorToken).emit("didReceiveNetworkResponse", args);
   }
 
   private static WritableMap translateHeaders(Headers headers) {
@@ -327,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()) {
@@ -339,11 +353,21 @@ protected void doInBackgroundGuarded(Void... params) {
   }
 
   @ReactMethod
-  public void clearCookies(com.facebook.react.bridge.Callback callback) {
+  public void clearCookies(
+      ExecutorToken executorToken,
+      com.facebook.react.bridge.Callback callback) {
     mCookieHandler.clearCookies(callback);
   }
 
-  private @Nullable MultipartBuilder constructMultipartBody(
+  @Override
+  public boolean supportsWebWorkers() {
+    return true;
+  }
+
+  private
+  @Nullable
+  MultipartBuilder constructMultipartBody(
+      ExecutorToken ExecutorToken,
       ReadableArray body,
       String contentType,
       int requestId) {
@@ -357,7 +381,10 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
       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;
@@ -374,19 +401,25 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
         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;
@@ -395,7 +428,9 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
   /**
    * 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) {
@@ -424,8 +459,8 @@ public void clearCookies(com.facebook.react.bridge.Callback callback) {
     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/statusbar/StatusBarModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
index ed905797a539cd..29d08a833f659f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.java
@@ -17,6 +17,7 @@
 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;
@@ -26,11 +27,10 @@
 
 public class StatusBarModule extends ReactContextBaseJavaModule {
 
-  private static final String ERROR_NO_ACTIVITY =
+  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";
 
-  private int mWindowFlags = 0;
-
   public StatusBarModule(ReactApplicationContext reactContext) {
     super(reactContext);
   }
@@ -44,7 +44,7 @@ public String getName() {
   public void setColor(final int color, final boolean animated, final Promise res) {
     final Activity activity = getCurrentActivity();
     if (activity == null) {
-      res.reject(ERROR_NO_ACTIVITY);
+      res.reject(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
       return;
     }
 
@@ -85,21 +85,20 @@ public void onAnimationUpdate(ValueAnimator animator) {
   public void setTranslucent(final boolean translucent, final Promise res) {
     final Activity activity = getCurrentActivity();
     if (activity == null) {
-      res.reject(ERROR_NO_ACTIVITY);
+      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) {
-            mWindowFlags |=
-              View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            flags |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
           } else {
-            mWindowFlags &=
-              ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+            flags &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
           }
-          activity.getWindow().getDecorView().setSystemUiVisibility(mWindowFlags);
+          activity.getWindow().getDecorView().setSystemUiVisibility(flags);
           ViewCompat.requestApplyInsets(activity.getWindow().getDecorView());
           res.resolve(null);
         }
@@ -111,7 +110,7 @@ public void run() {
   public void setHidden(final boolean hidden, final Promise res) {
     final Activity activity = getCurrentActivity();
     if (activity == null) {
-      res.reject(ERROR_NO_ACTIVITY);
+      res.reject(ERROR_NO_ACTIVITY, ERROR_NO_ACTIVITY_MESSAGE);
       return;
     }
     UiThreadUtil.runOnUiThread(
@@ -119,11 +118,13 @@ public void setHidden(final boolean hidden, final Promise res) {
         @Override
         public void run() {
           if (hidden) {
-            mWindowFlags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
           } else {
-            mWindowFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
+            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
           }
-          activity.getWindow().getDecorView().setSystemUiVisibility(mWindowFlags);
+
           res.resolve(null);
         }
       }
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 00000000000000..19ceed59d6f912
--- /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 00000000000000..1a0023ac052421
--- /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/WebSocketModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.java
index 61e1057bf5834e..af8d79597884e5 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 java.lang.IllegalStateException;
 import javax.annotation.Nullable;
 
 import com.facebook.common.logging.FLog;
@@ -176,7 +177,7 @@ public void send(String message, int id) {
       client.sendMessage(
         WebSocket.PayloadType.TEXT,
         new Buffer().writeUtf8(message));
-    } catch (IOException e) {
+    } catch (IOException | IllegalStateException e) {
       notifyWebSocketFailed(id, e.getMessage());
     }
   }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
index 88b94ebdaae30a..198de5a74dc70b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK
@@ -19,12 +19,14 @@ android_library(
     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'),
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 d3ec2880ff71af..f75c8d92bea2f6 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java
@@ -22,8 +22,8 @@
 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.dialog.DialogModule;
 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;
@@ -33,6 +33,7 @@
 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;
@@ -45,16 +46,16 @@
 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.swiperefresh.SwipeRefreshLayoutManager;
 import com.facebook.react.views.webview.ReactWebViewManager;
 
 /**
@@ -81,6 +82,7 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
       new StatusBarModule(reactContext),
       new TimePickerDialogModule(reactContext),
       new ToastModule(reactContext),
+      new VibrationModule(reactContext),
       new WebSocketModule(reactContext)
     );
   }
@@ -106,7 +108,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
       new ReactRawTextManager(),
       new ReactScrollViewManager(),
       new ReactSwitchManager(),
-      new ReactTextInlineImageViewManager(),
+      new FrescoBasedReactTextInlineImageViewManager(),
       new ReactTextInputManager(),
       new ReactTextViewManager(),
       new ReactToolbarManager(),
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 00000000000000..adda78ab020cfc
--- /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/uimanager/DisplayMetricsHolder.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.java
index 18135146eeecbb..c465073f81a119 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/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
index aada76ee9143eb..b5939823acf242 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
@@ -482,6 +482,28 @@ public void measure(int tag, int[] outputBuffer) {
     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();
+  }
+
   public int findTargetTagForTouch(int reactTag, float touchX, float touchY) {
     View view = mTagsToViews.get(reactTag);
     if (view == null) {
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 155776b71573d9..c2b5c8a6e9d8b9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/OnLayoutEvent.java
@@ -9,11 +9,11 @@
 
 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;
 
@@ -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 bcb24667fc5dd6..6a3ecd0a592936 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/ReactCompoundViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactCompoundViewGroup.java
new file mode 100644
index 00000000000000..345dea9f9184bc
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactCompoundViewGroup.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;
+
+/**
+ * This interface should be implemented be native ViewGroup subclasses that can represent more
+ * than a single react node. In that case, virtual and non-virtual (mapping to a View) elements
+ * can overlap, and TouchTargetHelper may incorrectly dispatch touch event to a wrong element
+ * because it priorities children over parents.
+ */
+public interface ReactCompoundViewGroup extends ReactCompoundView {
+  /**
+   * Returns true if react node responsible for the touch even is flattened into this ViewGroup.
+   * Use reactTagForTouch() to get its tag.
+   */
+  boolean interceptsTouchEvent(float touchX, float touchY);
+}
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 eb287bef45a130..df8ad675d294e1 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java
@@ -105,6 +105,10 @@ protected void markUpdated() {
     }
   }
 
+  public boolean hasUnseenUpdates() {
+    return mNodeUpdated;
+  }
+
   @Override
   protected void dirty() {
     if (!isVirtual()) {
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 9635d5bf6f1f10..8c74639d557136 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java
@@ -13,12 +13,14 @@
 
 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}.
@@ -118,7 +120,7 @@ private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup
       }
     }
     return viewGroup;
-}
+  }
 
   /**
    * Returns whether the touch point is within the child View
@@ -144,12 +146,24 @@ private static boolean isTransformedTouchPointInView(
       localX = localXY[0];
       localY = localXY[1];
     }
-    if ((localX >= 0 && localX < (child.getRight() - child.getLeft()))
-        && (localY >= 0 && localY < (child.getBottom() - child.getTop()))) {
-      outLocalPoint.set(localX, localY);
-      return true;
+    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;
     }
-    return false;
   }
 
 
@@ -195,6 +209,11 @@ private static boolean isTransformedTouchPointInView(
 
     } else if (pointerEvents == PointerEvents.AUTO) {
       // Either this view or one of its children is the target
+      if (view instanceof ReactCompoundViewGroup) {
+        if (((ReactCompoundViewGroup) view).interceptsTouchEvent(eventCoords[0], eventCoords[1])) {
+          return view;
+        }
+      }
       if (view instanceof ViewGroup) {
         return findTouchTargetView(eventCoords, (ViewGroup) view);
       }
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
index 95cd4756e2d98b..b407c416a5a9f7 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
@@ -380,8 +380,8 @@ public void findSubviewIn(int reactTag, float targetX, float targetY, Callback c
   }
 
   /**
-   * Determines the location on screen, width, and height of the given view and returns the values
-   * via an async 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
@@ -391,6 +391,15 @@ public void measure(int reactTag, Callback callback) {
     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
@@ -437,6 +446,12 @@ public void measureLayoutRelativeToParent(
    * Invoked at the end of the transaction to commit any updates to the node hierarchy.
    */
   public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) {
+    updateViewHierarchy(eventDispatcher);
+    mNativeViewHierarchyOptimizer.onBatchComplete();
+    mOperationsQueue.dispatchViewUpdates(batchId);
+  }
+
+  protected void updateViewHierarchy(EventDispatcher eventDispatcher) {
     for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) {
       int tag = mShadowNodeRegistry.getRootTag(i);
       ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag);
@@ -445,9 +460,6 @@ public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) {
       calculateRootLayout(cssRoot);
       applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher);
     }
-
-    mNativeViewHierarchyOptimizer.onBatchComplete();
-    mOperationsQueue.dispatchViewUpdates(batchId);
   }
 
   /**
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 9742fc041c27b7..ddeb94e6db2ef3 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
@@ -253,6 +253,16 @@ 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);
+  }
+
   /**
    * 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
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 8981d4354d2a6c..edb62be304e84a 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModuleConstants.java
@@ -72,6 +72,7 @@
   /* package */ static Map getDirectEventTypeConstants() {
     return MapBuilder.builder()
         .put("topSelectionChange", MapBuilder.of("registrationName", "onSelectionChange"))
+        .put("topTextContentSizeChange", MapBuilder.of("registrationName", "onChangeContentSize"))
         .put("topLoadingStart", MapBuilder.of("registrationName", "onLoadingStart"))
         .put("topLoadingFinish", MapBuilder.of("registrationName", "onLoadingFinish"))
         .put("topLoadingError", MapBuilder.of("registrationName", "onLoadingError"))
@@ -87,11 +88,12 @@ public static Map<String, Object> getConstants() {
             "ContentMode",
             MapBuilder.of(
                 "ScaleAspectFit",
-                ImageView.ScaleType.CENTER_INSIDE.ordinal(),
+                ImageView.ScaleType.FIT_CENTER.ordinal(),
                 "ScaleAspectFill",
                 ImageView.ScaleType.CENTER_CROP.ordinal())));
 
-    DisplayMetrics displayMetrics = DisplayMetricsHolder.getDisplayMetrics();
+    DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics();
+    DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
     constants.put(
         "Dimensions",
         MapBuilder.of(
@@ -106,7 +108,19 @@ public static Map<String, Object> getConstants() {
                 "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/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
index 104e247b5e6c70..b3b931e0f8ef52 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
@@ -385,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 {
@@ -634,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,
@@ -673,6 +713,10 @@ public void run() {
                      operations.get(i).execute();
                    }
                  }
+
+                 // Clear layout animation, as animation only apply to current UI operations batch.
+                 mNativeViewHierarchyManager.clearLayoutAnimation();
+
                  if (mViewHierarchyUpdateDebugListener != null) {
                    mViewHierarchyUpdateDebugListener.onViewHierarchyUpdateFinished();
                  }
@@ -723,9 +767,6 @@ public void doFrameGuarded(long frameTimeNanos) {
           mDispatchUIRunnables.get(i).run();
         }
         mDispatchUIRunnables.clear();
-
-        // Clear layout animation, as animation only apply to current UI operations batch.
-        mNativeViewHierarchyManager.clearLayoutAnimation();
       }
 
       ReactChoreographer.getInstance().postFrameCallback(
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 c8635d0515880a..537f1bfb46ba46 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
@@ -13,7 +13,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.Map;
 
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
index 9d3d0f45aa797f..f3c846864d1eb8 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/layoutanimation/BaseLayoutAnimation.java
@@ -34,9 +34,9 @@ Animation createAnimationImpl(View view, int x, int y, int width, int height) {
               toValue,
               fromValue,
               toValue,
-              Animation.RELATIVE_TO_PARENT,
+              Animation.RELATIVE_TO_SELF,
               .5f,
-              Animation.RELATIVE_TO_PARENT,
+              Animation.RELATIVE_TO_SELF,
               .5f);
         default:
           throw new IllegalViewOperationException(
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
index 9586a7b0f88b7d..f01ca1ccf97d9b 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTGroupShadowNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTGroupShadowNode.java
@@ -32,6 +32,8 @@ public void draw(Canvas canvas, Paint paint, float opacity) {
         child.draw(canvas, paint, opacity);
         child.markUpdateSeen();
       }
+
+      restoreCanvas(canvas);
     }
   }
 }
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
index 289ad0b9851553..70dc6faaf2af14 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/art/ARTVirtualNode.java
@@ -17,7 +17,6 @@
 
 import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
 import com.facebook.react.bridge.ReadableArray;
-import com.facebook.react.uimanager.ReactStylesDiffMap;
 import com.facebook.react.uimanager.DisplayMetricsHolder;
 import com.facebook.react.uimanager.annotations.ReactProp;
 import com.facebook.react.uimanager.ReactShadowNode;
@@ -39,7 +38,7 @@ public abstract class ARTVirtualNode extends ReactShadowNode {
   protected final float mScale;
 
   public ARTVirtualNode() {
-    mScale = DisplayMetricsHolder.getDisplayMetrics().density;
+    mScale = DisplayMetricsHolder.getWindowDisplayMetrics().density;
   }
 
   @Override
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 37062918158e85..1926c110abcd5b 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.annotations.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/ImageResizeMode.java b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.java
index fd7a6f67f2b789..30648f18306577 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/image/ImageResizeMode.java
@@ -25,7 +25,7 @@ public class ImageResizeMode {
    */
   public static ScalingUtils.ScaleType toScaleType(@Nullable String resizeModeValue) {
     if ("contain".equals(resizeModeValue)) {
-      return ScalingUtils.ScaleType.CENTER_INSIDE;
+      return ScalingUtils.ScaleType.FIT_CENTER;
     }
     if ("cover".equals(resizeModeValue)) {
       return ScalingUtils.ScaleType.CENTER_CROP;
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 4231d207d9aa64..6ebd2e8c8cdab7 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
@@ -14,6 +14,7 @@
 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;
@@ -120,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);
     }
   }
 
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 1c020acdcc8a45..c45c0403b23713 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
@@ -24,7 +24,6 @@
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.SystemClock;
 
 import com.facebook.common.util.UriUtil;
 import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
@@ -44,6 +43,7 @@
 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;
@@ -151,7 +151,7 @@ public void setShouldNotifyLoadEvents(boolean shouldNotify) {
         @Override
         public void onSubmit(String id, Object callerContext) {
           mEventDispatcher.dispatchEvent(
-              new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_START)
+              new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_START)
           );
         }
 
@@ -162,10 +162,10 @@ public void onFinalImageSet(
             @Nullable Animatable animatable) {
           if (imageInfo != null) {
             mEventDispatcher.dispatchEvent(
-                new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_END)
+                new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)
             );
             mEventDispatcher.dispatchEvent(
-                new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD)
+                new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD)
             );
           }
         }
@@ -173,7 +173,7 @@ public void onFinalImageSet(
         @Override
         public void onFailure(String id, Throwable throwable) {
           mEventDispatcher.dispatchEvent(
-              new ImageLoadEvent(getId(), SystemClock.uptimeMillis(), ImageLoadEvent.ON_LOAD_END)
+              new ImageLoadEvent(getId(), SystemClock.nanoTime(), ImageLoadEvent.ON_LOAD_END)
           );
         }
       };
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
index 2c8c2403890752..11ea4c839fc974 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/picker/ReactPickerManager.java
@@ -12,7 +12,6 @@
 import javax.annotation.Nullable;
 
 import android.content.Context;
-import android.os.SystemClock;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -23,11 +22,12 @@
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
-import com.facebook.react.uimanager.annotations.ReactProp;
+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;
 
@@ -157,7 +157,7 @@ public PickerEventEmitter(ReactPicker reactPicker, EventDispatcher eventDispatch
     @Override
     public void onItemSelected(int position) {
       mEventDispatcher.dispatchEvent( new PickerItemSelectEvent(
-              mReactPicker.getId(), SystemClock.uptimeMillis(), position));
+              mReactPicker.getId(), SystemClock.nanoTime(), position));
     }
   }
 }
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 f1729c55ca32e3..337fd5cfb393ae 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,6 +14,7 @@
 
 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;
@@ -344,7 +344,7 @@ 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(),
@@ -359,7 +359,7 @@ private void onTotalChildrenHeightChange(int newTotalChildrenHeight) {
       ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher()
           .dispatchEvent(new ContentSizeChangeEvent(
                   getId(),
-                  SystemClock.uptimeMillis(),
+                  SystemClock.nanoTime(),
                   getWidth(),
                   newTotalChildrenHeight));
     }
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 3ee46350dbe1e8..fd46f677b7954b 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;
 
 /**
@@ -57,7 +57,7 @@ private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scroll
     reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(
         ScrollEvent.obtain(
             scrollView.getId(),
-            SystemClock.uptimeMillis(),
+            SystemClock.nanoTime(),
             scrollEventType,
             scrollView.getScrollX(),
             scrollView.getScrollY(),
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
index 07a42e0494557f..009f5a12650994 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/SwipeRefreshLayoutManager.java
@@ -14,17 +14,17 @@
 import java.util.Map;
 
 import android.graphics.Color;
-import android.os.SystemClock;
 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.uimanager.annotations.ReactProp;
+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
@@ -91,7 +91,7 @@ protected void addEventEmitters(
           @Override
           public void onRefresh() {
             reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
-                .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.uptimeMillis()));
+                .dispatchEvent(new RefreshEvent(view.getId(), SystemClock.nanoTime()));
           }
         });
   }
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 1ac7f3c79e6b49..f7ff35e9d8227b 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,11 @@ 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'),
     react_native_target('java/com/facebook/react/uimanager/annotations:annotations'),
-    react_native_dep('third-party/android-support-for-standalone-apps/v7/appcompat:appcompat-23.1'),
-    react_native_dep('third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native'),
+    react_native_dep('third-party/android/support/v7/appcompat-orig:appcompat'),
     react_native_dep('third-party/java/jsr-305:jsr-305'),
   ],
   visibility = [
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 14d9fc0d6766f1..f99b3c7a7a4c37 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,12 +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.annotations.ReactProp;
 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.
@@ -71,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 df5bd7dee3c097..8d1e02e6c3d883 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/BUCK
@@ -9,10 +9,6 @@ android_library(
     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/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/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/ReactRawTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactRawTextManager.java
index aa71c74b9ff086..98e02829ae6dc4 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.ReactStylesDiffMap;
 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 3a4eed1319061c..4e986644e16b5f 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,90 +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.drawee.controller.AbstractDraweeControllerBuilder;
 import com.facebook.react.uimanager.LayoutShadowNode;
-import com.facebook.react.uimanager.annotations.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;
-  private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
-  private final @Nullable Object mCallerContext;
-
-  public ReactTextInlineImageShadowNode(
-      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;
-  }
-
-  public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
-    return mDraweeControllerBuilder;
-  }
+public abstract class ReactTextInlineImageShadowNode extends LayoutShadowNode {
 
-  public @Nullable Object getCallerContext() {
-    return mCallerContext;
-  }
+  /**
+   * 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/ReactTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextShadowNode.java
index 314df23ef3d551..2d98703c2f6756 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;
@@ -37,11 +36,11 @@
 import com.facebook.react.uimanager.IllegalViewOperationException;
 import com.facebook.react.uimanager.LayoutShadowNode;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.annotations.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.
@@ -94,7 +93,7 @@ public void execute(SpannableStringBuilder sb) {
     }
   }
 
-  private static final void buildSpannedFromTextCSSNode(
+  private static void buildSpannedFromTextCSSNode(
       ReactTextShadowNode textCSSNode,
       SpannableStringBuilder sb,
       List<SetSpanOperation> ops) {
@@ -107,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());
@@ -154,36 +160,14 @@ private static final void buildSpannedFromTextCSSNode(
     }
   }
 
-  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) Math.ceil(node.getStyleHeight());
-    int width = (int) Math.ceil(node.getStyleWidth());
-    TextInlineImageSpan imageSpan = new TextInlineImageSpan(
-        resources,
-        height,
-        width,
-        node.getUri(),
-        node.getDraweeControllerBuilder(),
-        node.getCallerContext());
-    // 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(
@@ -330,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) {
@@ -483,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 8c7e7f948d677c..5a4cebbecfcea3 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
@@ -13,6 +13,7 @@
 import android.graphics.drawable.Drawable;
 import android.text.Layout;
 import android.text.Spanned;
+import android.view.Gravity;
 import android.widget.TextView;
 
 import com.facebook.react.uimanager.ReactCompoundView;
@@ -20,9 +21,14 @@
 public class ReactTextView extends TextView implements ReactCompoundView {
 
   private boolean mContainsImages;
+  private int mDefaultGravityHorizontal;
+  private int mDefaultGravityVertical;
 
   public ReactTextView(Context context) {
     super(context);
+    mDefaultGravityHorizontal =
+      getGravity() & (Gravity.HORIZONTAL_GRAVITY_MASK | Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK);
+    mDefaultGravityVertical = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
   }
 
   public void setText(ReactTextUpdate update) {
@@ -150,4 +156,20 @@ public void onFinishTemporaryDetach() {
       }
     }
   }
+
+  /* package */ void setGravityHorizontal(int gravityHorizontal) {
+    if (gravityHorizontal == 0) {
+      gravityHorizontal = mDefaultGravityHorizontal;
+    }
+    setGravity(
+      (getGravity() & ~Gravity.HORIZONTAL_GRAVITY_MASK &
+        ~Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravityHorizontal);
+  }
+
+  /* package */ void setGravityVertical(int gravityVertical) {
+    if (gravityVertical == 0) {
+      gravityVertical = mDefaultGravityVertical;
+    }
+    setGravity((getGravity() & ~Gravity.VERTICAL_GRAVITY_MASK) | gravityVertical);
+  }
 }
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 2bffd39695fac1..a1813a32afab26 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
@@ -58,18 +58,33 @@ public void setNumberOfLines(ReactTextView view, int numberOfLines) {
   @ReactProp(name = ViewProps.TEXT_ALIGN)
   public void setTextAlign(ReactTextView view, @Nullable String textAlign) {
     if (textAlign == null || "auto".equals(textAlign)) {
-      view.setGravity(Gravity.NO_GRAVITY);
+      view.setGravityHorizontal(Gravity.NO_GRAVITY);
     } else if ("left".equals(textAlign)) {
-      view.setGravity(Gravity.LEFT);
+      view.setGravityHorizontal(Gravity.LEFT);
     } else if ("right".equals(textAlign)) {
-      view.setGravity(Gravity.RIGHT);
+      view.setGravityHorizontal(Gravity.RIGHT);
     } else if ("center".equals(textAlign)) {
-      view.setGravity(Gravity.CENTER_HORIZONTAL);
+      view.setGravityHorizontal(Gravity.CENTER_HORIZONTAL);
     } else {
       throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
     }
   }
 
+  @ReactProp(name = ViewProps.TEXT_ALIGN_VERTICAL)
+  public void setTextAlignVertical(ReactTextView 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 = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN)
   public void setLineHeight(ReactTextView view, float lineHeight) {
     if (Float.isNaN(lineHeight)) { // NaN will be used if property gets reset
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 6079ce00b552c7..0308ee20c28c87 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,163 +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.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;
-
-/**
- * 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 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 TextInlineImageSpan(
-      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;
-  }
-
-  @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();
-  }
-
-  /**
-   * 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 00000000000000..e8ddbceb6d9568
--- /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 00000000000000..643a2c136fe577
--- /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 00000000000000..d62ecf0ce4c455
--- /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/ReactTextInlineImageViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java
similarity index 56%
rename from ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java
rename to ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java
index b16b42deaf943a..c272bf39d89a6c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextInlineImageViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageViewManager.java
@@ -7,7 +7,7 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
-package com.facebook.react.views.text;
+package com.facebook.react.views.textfrescosupport;
 
 import javax.annotation.Nullable;
 
@@ -19,24 +19,24 @@
 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}
+ * 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 ReactTextInlineImageViewManager
-    extends ViewManager<View, ReactTextInlineImageShadowNode> {
+public class FrescoBasedReactTextInlineImageViewManager
+  extends ViewManager<View, FrescoBasedReactTextInlineImageShadowNode> {
 
   static final String REACT_CLASS = "RCTTextInlineImage";
 
   private final @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
   private final @Nullable Object mCallerContext;
 
-  public ReactTextInlineImageViewManager() {
+  public FrescoBasedReactTextInlineImageViewManager() {
     this(null, null);
   }
 
-  public ReactTextInlineImageViewManager(
-      @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder,
-      @Nullable Object callerContext) {
+  public FrescoBasedReactTextInlineImageViewManager(
+    @Nullable AbstractDraweeControllerBuilder draweeControllerBuilder,
+    @Nullable Object callerContext) {
     mDraweeControllerBuilder = draweeControllerBuilder;
     mCallerContext = callerContext;
   }
@@ -52,18 +52,18 @@ public View createViewInstance(ThemedReactContext context) {
   }
 
   @Override
-  public ReactTextInlineImageShadowNode createShadowNodeInstance() {
-    return new ReactTextInlineImageShadowNode(
-        (mDraweeControllerBuilder != null) ?
-            mDraweeControllerBuilder :
-            Fresco.newDraweeControllerBuilder(),
-        mCallerContext
+  public FrescoBasedReactTextInlineImageShadowNode createShadowNodeInstance() {
+    return new FrescoBasedReactTextInlineImageShadowNode(
+      (mDraweeControllerBuilder != null) ?
+        mDraweeControllerBuilder :
+        Fresco.newDraweeControllerBuilder(),
+      mCallerContext
     );
   }
 
   @Override
-  public Class<ReactTextInlineImageShadowNode> getShadowNodeClass() {
-    return ReactTextInlineImageShadowNode.class;
+  public Class<FrescoBasedReactTextInlineImageShadowNode> getShadowNodeClass() {
+    return FrescoBasedReactTextInlineImageShadowNode.class;
   }
 
   @Override
diff --git a/packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java
similarity index 73%
rename from packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js
rename to ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java
index 2a36bb39dc3bda..fab6bbc1e34d1a 100644
--- a/packager/react-packager/src/DependencyResolver/FileWatcher/__mocks__/sane.js
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ContentSizeWatcher.java
@@ -6,8 +6,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.
  */
-'use strict';
 
-module.exports = {
-  WatchmanWatcher: jest.genMockFromModule('sane/src/watchman_watcher'),
-};
+package com.facebook.react.views.textinput;
+
+public interface ContentSizeWatcher {
+  public void onLayout();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.java
new file mode 100644
index 00000000000000..d85efe207b8692
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactContentSizeChangedEvent.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 content size changes.
+ */
+public class ReactContentSizeChangedEvent extends Event<ReactTextChangedEvent> {
+
+  public static final String EVENT_NAME = "topTextContentSizeChange";
+
+  private int mContentWidth;
+  private int mContentHeight;
+
+  public ReactContentSizeChangedEvent(
+    int viewId,
+    long timestampMs,
+    int contentSizeWidth,
+    int contentSizeHeight) {
+    super(viewId, timestampMs);
+    mContentWidth = contentSizeWidth;
+    mContentHeight = contentSizeHeight;
+  }
+
+  @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 contentSize = Arguments.createMap();
+    contentSize.putDouble("width", mContentWidth);
+    contentSize.putDouble("height", mContentHeight);
+    eventData.putMap("contentSize", contentSize);
+
+    eventData.putInt("target", getViewTag());
+    return eventData;
+  }
+}
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 58e3e5a560a01a..80e5a56bd8255a 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
@@ -67,7 +67,9 @@ public class ReactEditText extends EditText {
   private @Nullable TextWatcherDelegator mTextWatcherDelegator;
   private int mStagedInputType;
   private boolean mContainsImages;
+  private boolean mBlurOnSubmit;
   private @Nullable SelectionWatcher mSelectionWatcher;
+  private @Nullable ContentSizeWatcher mContentSizeWatcher;
   private final InternalKeyListener mKeyListener;
 
   private static final KeyListener sKeyListener = QwertyKeyListener.getInstanceForFullKeyboard();
@@ -84,6 +86,7 @@ public ReactEditText(Context context) {
     mNativeEventCount = 0;
     mIsSettingTextFromJS = false;
     mIsJSSettingFocus = false;
+    mBlurOnSubmit = true;
     mListeners = null;
     mTextWatcherDelegator = null;
     mStagedInputType = getInputType();
@@ -98,7 +101,23 @@ public ReactEditText(Context context) {
   // TODO: t6408636 verify if we should schedule a layout after a View does a requestLayout()
   @Override
   public boolean isLayoutRequested() {
-    return false;
+    // If we are watching and updating container height based on content size
+    // then we don't want to scroll right away. This isn't perfect -- you might
+    // want to limit the height the text input can grow to. Possible solution
+    // is to add another prop that determines whether we should scroll to end
+    // of text.
+    if (mContentSizeWatcher != null) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+    if (mContentSizeWatcher != null) {
+      mContentSizeWatcher.onLayout();
+    }
   }
 
   // Consume 'Enter' key events: TextView tries to give focus to the next TextInput, but it can't
@@ -158,6 +177,10 @@ public void removeTextChangedListener(TextWatcher watcher) {
     }
   }
 
+  public void setContentSizeWatcher(ContentSizeWatcher contentSizeWatcher) {
+    mContentSizeWatcher = contentSizeWatcher;
+  }
+
   @Override
   protected void onSelectionChanged(int selStart, int selEnd) {
     super.onSelectionChanged(selStart, selEnd);
@@ -179,6 +202,14 @@ public void setSelectionWatcher(SelectionWatcher selectionWatcher) {
     mSelectionWatcher = selectionWatcher;
   }
 
+  public void setBlurOnSubmit(boolean blurOnSubmit) {
+    mBlurOnSubmit = blurOnSubmit;
+  }
+
+  public boolean getBlurOnSubmit() {
+    return mBlurOnSubmit;
+  }
+
   /*protected*/ int getStagedInputType() {
     return mStagedInputType;
   }
@@ -426,7 +457,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
     @Override
     public void afterTextChanged(Editable s) {
       if (!mIsSettingTextFromJS && mListeners != null) {
-        for (android.text.TextWatcher listener : mListeners) {
+        for (TextWatcher listener : mListeners) {
           listener.afterTextChanged(s);
         }
       }
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 6cbb5894e1c2bc..b95f3292c2cfd1 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
@@ -15,7 +15,6 @@
 import java.util.Map;
 
 import android.graphics.PorterDuff;
-import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputFilter;
 import android.text.InputType;
@@ -33,7 +32,9 @@
 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.ThemedReactContext;
 import com.facebook.react.uimanager.UIManagerModule;
@@ -42,14 +43,13 @@
 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";
 
@@ -83,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;
   }
 
@@ -181,6 +181,20 @@ public void setOnSelectionChange(final ReactEditText view, boolean onSelectionCh
     }
   }
 
+  @ReactProp(name = "blurOnSubmit", defaultBoolean = true)
+  public void setBlurOnSubmit(ReactEditText view, boolean blurOnSubmit) {
+    view.setBlurOnSubmit(blurOnSubmit);
+  }
+
+  @ReactProp(name = "onChangeContentSize", defaultBoolean = false)
+  public void setOnChangecontentSize(final ReactEditText view, boolean onChangeContentSize) {
+    if (onChangeContentSize) {
+      view.setContentSizeWatcher(new ReactContentSizeWatcher(view));
+    } else {
+      view.setContentSizeWatcher(null);
+    }
+  }
+
   @ReactProp(name = "placeholder")
   public void setPlaceholder(ReactEditText view, @Nullable String placeholder) {
     view.setHint(placeholder);
@@ -411,15 +425,17 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
       if (count == before && newText.equals(oldText)) {
         return;
       }
+
+      // TODO: remove contentSize from onTextChanged entirely now that onChangeContentSize exists?
       int contentWidth = mEditText.getWidth();
       int contentHeight = mEditText.getHeight();
 
       // Use instead size of text content within EditText when available
       if (mEditText.getLayout() != null) {
         contentWidth = mEditText.getCompoundPaddingLeft() + mEditText.getLayout().getWidth() +
-            mEditText.getCompoundPaddingRight();
+          mEditText.getCompoundPaddingRight();
         contentHeight = mEditText.getCompoundPaddingTop() + mEditText.getLayout().getHeight() +
-            mEditText.getCompoundPaddingTop();
+          mEditText.getCompoundPaddingTop();
       }
 
       // The event that contains the event counter and updates it must be sent first.
@@ -427,7 +443,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),
@@ -436,7 +452,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
       mEventDispatcher.dispatchEvent(
           new ReactTextInputEvent(
               mEditText.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               newText,
               oldText,
               start,
@@ -462,17 +478,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()));
             }
           }
@@ -490,14 +506,53 @@ 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 ReactContentSizeWatcher implements ContentSizeWatcher {
+    private ReactEditText mEditText;
+    private EventDispatcher mEventDispatcher;
+    private int mPreviousContentWidth = 0;
+    private int mPreviousContentHeight = 0;
+
+    public ReactContentSizeWatcher(ReactEditText editText) {
+      mEditText = editText;
+      ReactContext reactContext = (ReactContext) editText.getContext();
+      mEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
+    }
+
+    @Override
+    public void onLayout() {
+      int contentWidth = mEditText.getWidth();
+      int contentHeight = mEditText.getHeight();
+
+      // Use instead size of text content within EditText when available
+      if (mEditText.getLayout() != null) {
+        contentWidth = mEditText.getCompoundPaddingLeft() + mEditText.getLayout().getWidth() +
+          mEditText.getCompoundPaddingRight();
+        contentHeight = mEditText.getCompoundPaddingTop() + mEditText.getLayout().getHeight() +
+          mEditText.getCompoundPaddingTop();
+      }
+
+      if (contentWidth != mPreviousContentWidth || contentHeight != mPreviousContentHeight) {
+        mPreviousContentHeight = contentHeight;
+        mPreviousContentWidth = contentWidth;
+
+        mEventDispatcher.dispatchEvent(
+          new ReactContentSizeChangedEvent(
+            mEditText.getId(),
+            SystemClock.uptimeMillis(),
+            (int) PixelUtil.toDIPFromPixel(contentWidth),
+            (int) PixelUtil.toDIPFromPixel(contentHeight)));
+      }
+    }
+  }
+
   private class ReactSelectionWatcher implements SelectionWatcher {
 
     private ReactEditText mReactEditText;
@@ -520,7 +575,7 @@ public void onSelectionChanged(int start, int end) {
         mEventDispatcher.dispatchEvent(
             new ReactTextInputSelectionEvent(
                 mReactEditText.getId(),
-                SystemClock.uptimeMillis(),
+                SystemClock.nanoTime(),
                 start,
                 end
             )
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 6172770dd3e26a..69bb727740cd8b 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.annotations.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;
@@ -88,7 +83,7 @@ public void measure(CSSNode node, float width, float height, MeasureOutput measu
       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/views/toolbar/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
index 253f3576a0f174..df52d70d075366 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/toolbar/BUCK
@@ -9,11 +9,11 @@ android_library(
     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-for-standalone-apps/v7/appcompat:appcompat-23.1'),
-    react_native_dep('third-party/android-support-for-standalone-apps/v7/appcompat:res-for-react-native'),
+    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'),
 ],
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 49a9a4b390b6b3..8b92242914c90b 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 5fe30921a69593..92c585d71bd384 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 321d872c82ebc0..9f162f9f0b4145 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,6 @@
 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;
@@ -26,11 +25,12 @@
 import com.facebook.react.bridge.ReadableArray;
 import com.facebook.react.bridge.ReadableMap;
 import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SystemClock;
 import com.facebook.react.uimanager.PixelUtil;
-import com.facebook.react.uimanager.annotations.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.toolbar.events.ToolbarClickEvent;
 
@@ -131,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));
           }
         });
 
@@ -142,7 +142,7 @@ public boolean onMenuItemClick(MenuItem menuItem) {
             mEventDispatcher.dispatchEvent(
                 new ToolbarClickEvent(
                     view.getId(),
-                    SystemClock.uptimeMillis(),
+                    SystemClock.nanoTime(),
                     menuItem.getOrder()));
             return true;
           }
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 742adbd84b1ba9..fd8961cb8628b6 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
@@ -80,6 +80,7 @@ private static enum BorderStyle {
   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;
@@ -343,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);
     }
   }
 
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 bf57e47a65543d..fcd2fd5eb6f72c 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
@@ -23,6 +23,7 @@
 
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.touch.ReactHitSlopView;
 import com.facebook.react.touch.ReactInterceptingViewGroup;
 import com.facebook.react.touch.OnInterceptTouchEventListener;
 import com.facebook.react.uimanager.MeasureSpecAssertions;
@@ -34,7 +35,7 @@
  * initializes most of the storage needed for them.
  */
 public class ReactViewGroup extends ViewGroup implements
-    ReactInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView {
+    ReactInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView {
 
   private static final int ARRAY_CAPACITY_INCREMENT = 12;
   private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
@@ -87,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;
@@ -513,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 789b294e1f9425..6a4bd5d264914b 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,6 +14,7 @@
 import java.util.Locale;
 import java.util.Map;
 
+import android.graphics.Rect;
 import android.os.Build;
 import android.view.View;
 
@@ -75,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) {
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 3ad5c55e0cd5c1..c522e039156030 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,14 +91,14 @@ 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));
       }
     }
 
@@ -119,7 +119,7 @@ public void onPageScrollStateChanged(int state) {
           throw new IllegalStateException("Unsupported pageScrollState");
       }
       mEventDispatcher.dispatchEvent(
-        new PageScrollStateChangedEvent(getId(), SystemClock.uptimeMillis(), pageScrollState));
+        new PageScrollStateChangedEvent(getId(), SystemClock.nanoTime(), pageScrollState));
     }
   }
 
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
index aac2460c592988..616418bba35b37 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
@@ -9,9 +9,14 @@
 
 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.os.SystemClock;
 import android.text.TextUtils;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
@@ -27,6 +32,7 @@
 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;
@@ -35,13 +41,6 @@
 import com.facebook.react.uimanager.events.Event;
 import com.facebook.react.uimanager.events.EventDispatcher;
 
-import java.io.UnsupportedEncodingException;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.annotation.Nullable;
-
 /**
  * Manages instances of {@link WebView}
  *
@@ -106,7 +105,7 @@ public void onPageStarted(WebView webView, String url, Bitmap favicon) {
           webView,
           new TopLoadingStartEvent(
               webView.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               createWebViewEvent(webView, url)));
     }
 
@@ -129,7 +128,7 @@ public void onReceivedError(
 
       dispatchEvent(
           webView,
-          new TopLoadingErrorEvent(webView.getId(), SystemClock.uptimeMillis(), eventData));
+          new TopLoadingErrorEvent(webView.getId(), SystemClock.nanoTime(), eventData));
     }
 
     @Override
@@ -140,7 +139,7 @@ public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload
           webView,
           new TopLoadingStartEvent(
               webView.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               createWebViewEvent(webView, url)));
     }
 
@@ -149,7 +148,7 @@ private void emitFinishEvent(WebView webView, String url) {
           webView,
           new TopLoadingFinishEvent(
               webView.getId(),
-              SystemClock.uptimeMillis(),
+              SystemClock.nanoTime(),
               createWebViewEvent(webView, url)));
     }
 
@@ -259,6 +258,11 @@ 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);
diff --git a/ReactAndroid/src/main/jni/first-party/fbgloginit/Android.mk b/ReactAndroid/src/main/jni/first-party/fbgloginit/Android.mk
new file mode 100644
index 00000000000000..6d08079a7ebc7b
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/fbgloginit/Android.mk
@@ -0,0 +1,24 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+       glog_init.cpp
+
+LOCAL_C_INCLUDES := $(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
+
+LOCAL_CFLAGS := -fexceptions -fno-omit-frame-pointer
+LOCAL_CFLAGS += -Wall -Werror
+
+CXX11_FLAGS := -std=gnu++11
+LOCAL_CFLAGS += $(CXX11_FLAGS)
+
+LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS)
+
+LOCAL_LDLIBS := -llog
+
+LOCAL_SHARED_LIBRARIES := libglog
+
+LOCAL_MODULE := libglog_init
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/ReactAndroid/src/main/jni/first-party/fbgloginit/BUCK b/ReactAndroid/src/main/jni/first-party/fbgloginit/BUCK
new file mode 100644
index 00000000000000..e7188bd0ef0774
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/fbgloginit/BUCK
@@ -0,0 +1,19 @@
+cxx_library(
+  name = 'fbgloginit',
+  srcs = [
+    'glog_init.cpp',
+  ],
+  exported_headers = ['fb/glog_init.h'],
+  compiler_flags = [
+    '-fexceptions',
+    '-fno-omit-frame-pointer',
+  ],
+  linker_flags = [
+    '-llog',
+  ],
+  deps=[
+    '//xplat/third-party/glog:glog',
+  ],
+  visibility=['PUBLIC'],
+)
+
diff --git a/ReactAndroid/src/main/jni/first-party/fbgloginit/fb/glog_init.h b/ReactAndroid/src/main/jni/first-party/fbgloginit/fb/glog_init.h
new file mode 100644
index 00000000000000..311f703500ae10
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/fbgloginit/fb/glog_init.h
@@ -0,0 +1,11 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <string>
+
+namespace facebook { namespace gloginit {
+
+void initialize(const char* tag = "ReactNativeJNI");
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp b/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp
new file mode 100644
index 00000000000000..771516a2dcdf97
--- /dev/null
+++ b/ReactAndroid/src/main/jni/first-party/fbgloginit/glog_init.cpp
@@ -0,0 +1,142 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "fb/glog_init.h"
+
+#include <iostream>
+#include <mutex>
+#include <stdexcept>
+
+#include <glog/logging.h>
+
+#ifdef __ANDROID__
+
+#include <android/log.h>
+
+static int toAndroidLevel(google::LogSeverity severity) {
+  switch (severity) {
+  case google::GLOG_INFO:
+    return ANDROID_LOG_INFO;
+  case google::GLOG_WARNING:
+    return ANDROID_LOG_WARN;
+  case google::GLOG_ERROR:
+    return ANDROID_LOG_ERROR;
+  case google::GLOG_FATAL:
+    return ANDROID_LOG_FATAL;
+  default:
+    return ANDROID_LOG_FATAL;
+  }
+}
+
+/**
+ * Sends GLog output to adb logcat.
+ */
+class LogcatSink : public google::LogSink {
+ public:
+  void send(
+      google::LogSeverity severity,
+      const char* full_filename,
+      const char* base_filename,
+      int line,
+      const struct ::tm* tm_time,
+      const char* message,
+      size_t message_len) override {
+    auto level = toAndroidLevel(severity);
+    __android_log_print(
+        level,
+        base_filename,
+        "%.*s",
+        (int)message_len,
+        message);
+  }
+};
+
+/**
+ * Sends GLog output to adb logcat.
+ */
+class TaggedLogcatSink : public google::LogSink {
+  const std::string tag_;
+
+ public:
+  TaggedLogcatSink(const std::string &tag) : tag_{tag} {}
+
+  void send(
+      google::LogSeverity severity,
+      const char* full_filename,
+      const char* base_filename,
+      int line,
+      const struct ::tm* tm_time,
+      const char* message,
+      size_t message_len) override {
+    auto level = toAndroidLevel(severity);
+    __android_log_print(
+      level,
+      tag_.c_str(),
+      "%.*s",
+      (int)message_len,
+      message);
+  }
+};
+
+static google::LogSink* make_sink(const std::string& tag) {
+  if (tag.empty()) {
+    return new LogcatSink{};
+  } else {
+    return new TaggedLogcatSink{tag};
+  }
+}
+
+static void sendGlogOutputToLogcat(const char* tag) {
+  google::AddLogSink(make_sink(tag));
+
+  // Disable logging to files
+  for (auto i = 0; i < google::NUM_SEVERITIES; ++i) {
+    google::SetLogDestination(i, "");
+  }
+}
+
+#endif // __ANDROID__
+
+static void lastResort(const char* tag, const char* msg, const char* arg = nullptr) {
+#ifdef __ANDROID__
+  if (!arg) {
+    __android_log_write(ANDROID_LOG_ERROR, tag, msg);
+  } else {
+    __android_log_print(ANDROID_LOG_ERROR, tag, "%s: %s", msg, arg);
+  }
+#else 
+  std::cerr << msg;
+  if (arg) {
+    std::cerr << ": " << arg;
+  }
+  std::cerr << std::endl;
+#endif
+}
+
+namespace facebook { namespace gloginit {
+
+void initialize(const char* tag) {
+  static std::once_flag flag{};
+  static auto failed = false;
+
+  std::call_once(flag, [tag] {
+    try {
+      google::InitGoogleLogging(tag);
+
+#ifdef __ANDROID__
+      sendGlogOutputToLogcat(tag);
+#endif
+    } catch (std::exception& ex) {
+      lastResort(tag, "Failed to initialize glog", ex.what());
+      failed = true;
+    } catch (...) {
+      lastResort(tag, "Failed to initialize glog");
+      failed = true;
+    }
+  });
+
+  if (failed) {
+    throw std::runtime_error{"Failed to initialize glog"};
+  }
+}
+
+}}
diff --git a/ReactAndroid/src/main/jni/first-party/jni/Android.mk b/ReactAndroid/src/main/jni/first-party/jni/Android.mk
index e77eaf1a0aadb6..1ee4d08986ca96 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
index 56d89123450ae1..36574b0b39cb16 100644
--- a/ReactAndroid/src/main/jni/first-party/jni/BUCK
+++ b/ReactAndroid/src/main/jni/first-party/jni/BUCK
@@ -10,6 +10,7 @@ cxx_library(
     '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/Countable.cpp b/ReactAndroid/src/main/jni/first-party/jni/Countable.cpp
index 6ff7efe9068b0f..3ed52f78248487 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 b5d9c54a13b0b6..09aa641fd2900c 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 5fc8d0c84828d4..a268072677d02d 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 a85efa48adaf47..2290af10dc9766 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 a083371b269588..22d4dc2a4d199f 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 7ea816b60e581e..7728153cd62931 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 00000000000000..a41f9106ed92cf
--- /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 00000000000000..b10573752b030d
--- /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 479f43713a3b0b..9fa80c7a4df0e9 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 00000000000000..136ca82f1917f4
--- /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 134acc87e27e84..f5f861ba3c6d11 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 457b79b55d675c..ed129af1bdced3 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 710a6836f1ca1a..6a3516cd3f7da6 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>
 
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 00000000000000..29fc9850a95ce2
--- /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 ebcb778de9e148..c779cde4ef791f 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 8b62ccdedec449..70fc670f5c8ebf 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 00000000000000..206d2b4b17a577
--- /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 00000000000000..aa3652666e7da2
--- /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 00000000000000..2f524ad062af6a
--- /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 d51157b1607269..38fabc500e35f7 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 de1bde0d542b81..23c7450506bba3 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 00000000000000..33027c7e96b395
--- /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 d60c90022723d4..5f69a3b41503f6 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 00000000000000..8dabf67cb62e60
--- /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 bae3d5d636d1d6..2176d1e022ea9c 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 575f2cc6e55917..b578c5c9150742 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 29414d195fee17..2ac6abaf4de81b 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 b4bdd15ea2f14d..26472669dc7184 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/prebuilt/BUCK b/ReactAndroid/src/main/jni/prebuilt/BUCK
new file mode 100644
index 00000000000000..69e71a637360d8
--- /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 00000000000000..dc29e4d637e6d7
--- /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 073d091577cc08..f72729bdce2614 100644
--- a/ReactAndroid/src/main/jni/react/Android.mk
+++ b/ReactAndroid/src/main/jni/react/Android.mk
@@ -8,7 +8,6 @@ LOCAL_SRC_FILES := \
   Bridge.cpp \
   JSCExecutor.cpp \
   JSCHelpers.cpp \
-  JSCWebWorker.cpp \
   MethodCall.cpp \
   Platform.cpp \
   Value.cpp \
@@ -19,6 +18,7 @@ LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
 LOCAL_CFLAGS := \
   -DLOG_TAG=\"ReactNative\"
 
+LOCAL_LDLIBS += -landroid
 LOCAL_CFLAGS += -Wall -Werror -fexceptions -frtti
 CXX11_FLAGS := -std=c++11
 LOCAL_CFLAGS += $(CXX11_FLAGS)
diff --git a/ReactAndroid/src/main/jni/react/BUCK b/ReactAndroid/src/main/jni/react/BUCK
index ffa06d775025bc..809905b50946b2 100644
--- a/ReactAndroid/src/main/jni/react/BUCK
+++ b/ReactAndroid/src/main/jni/react/BUCK
@@ -4,6 +4,7 @@ include_defs('//ReactAndroid/DEFS')
 SUPPORTED_PLATFORMS = '^android-(armv7|x86)$'
 
 DEPS = [
+  '//native/third-party/android-ndk:android',
   '//xplat/fbsystrace:fbsystrace',
   '//xplat/folly:molly',
   '//xplat/third-party/glog:glog',
@@ -21,14 +22,27 @@ def react_library(**kwargs):
     visibility = [
       react_native_target('jni/react/jni:jni'),
     ],
-    deps = DEPS + [
-      '//native/third-party/jsc:jsc',
-      '//native/third-party/jsc:jsc_legacy_profiler',
-    ],
+    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',
@@ -40,23 +54,25 @@ react_library(
     'MethodCall.cpp',
     'JSCHelpers.cpp',
     'JSCExecutor.cpp',
+    'JSCPerfStats.cpp',
     'JSCTracing.cpp',
     'JSCMemory.cpp',
     'JSCLegacyProfiler.cpp',
-    'JSCWebWorker.cpp',
     'Platform.cpp',
   ],
   headers = [
     'JSCTracing.h',
     'JSCLegacyProfiler.h',
     'JSCMemory.h',
+    'JSCPerfStats.h',
   ],
   exported_headers = [
     'Bridge.h',
+    'ExecutorToken.h',
+    'ExecutorTokenFactory.h',
     'Executor.h',
     'JSCExecutor.h',
     'JSCHelpers.h',
-    'JSCWebWorker.h',
     'MessageQueueThread.h',
     'MethodCall.h',
     'JSModulesUnbundle.h',
diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp
index a4bb6887eb2afc..6654e8bd45d8b5 100644
--- a/ReactAndroid/src/main/jni/react/Bridge.cpp
+++ b/ReactAndroid/src/main/jni/react/Bridge.cpp
@@ -5,93 +5,248 @@
 #ifdef WITH_FBSYSTRACE
 #include <fbsystrace.h>
 using fbsystrace::FbSystraceSection;
+using fbsystrace::FbSystraceAsyncFlow;
 #endif
+#include <folly/Memory.h>
+
+#include "Platform.h"
 
 namespace facebook {
 namespace react {
 
-Bridge::Bridge(JSExecutorFactory* jsExecutorFactory, Callback callback) :
+Bridge::Bridge(
+    JSExecutorFactory* jsExecutorFactory,
+    std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
+    std::unique_ptr<BridgeCallback> callback) :
   m_callback(std::move(callback)),
-  m_destroyed(std::shared_ptr<bool>(new bool(false)))
-{
-  auto destroyed = m_destroyed;
-  m_jsExecutor = jsExecutorFactory->createJSExecutor([this, destroyed] (std::string queueJSON, bool isEndOfBatch) {
-    if (*destroyed) {
-      return;
-    }
-    m_callback(parseMethodCalls(queueJSON), isEndOfBatch);
-  });
+  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()));
 }
 
 // This must be called on the same thread on which the constructor was called.
 Bridge::~Bridge() {
-  *m_destroyed = true;
-  m_jsExecutor.reset();
+  CHECK(*m_destroyed) << "Bridge::destroy() must be called before deallocating the Bridge!";
 }
 
-void Bridge::executeApplicationScript(const std::string& script, const std::string& sourceURL) {
-  m_jsExecutor->executeApplicationScript(script, sourceURL);
+void Bridge::loadApplicationScript(const std::string& script, const std::string& sourceURL) {
+  m_mainExecutor->loadApplicationScript(script, sourceURL);
 }
 
 void Bridge::loadApplicationUnbundle(
     std::unique_ptr<JSModulesUnbundle> unbundle,
     const std::string& startupCode,
     const std::string& sourceURL) {
-  m_jsExecutor->loadApplicationUnbundle(std::move(unbundle), startupCode, sourceURL);
+  m_mainExecutor->loadApplicationUnbundle(std::move(unbundle), startupCode, sourceURL);
 }
 
-void Bridge::flush() {
+void Bridge::callFunction(
+    ExecutorToken executorToken,
+    const double moduleId,
+    const double methodId,
+    const folly::dynamic& arguments,
+    const std::string& tracingName) {
   if (*m_destroyed) {
     return;
   }
-  auto returnedJSON = m_jsExecutor->flush();
-  m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
-}
 
-void Bridge::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
-  if (*m_destroyed) {
-    return;
-  }
   #ifdef WITH_FBSYSTRACE
-  FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, "Bridge.callFunction");
+  int systraceCookie = m_systraceCookie++;
+  FbSystraceAsyncFlow::begin(
+      TRACE_TAG_REACT_CXX_BRIDGE,
+      tracingName.c_str(),
+      systraceCookie);
   #endif
-  auto returnedJSON = m_jsExecutor->callFunction(moduleId, methodId, arguments);
-  m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
+
+  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,
+        tracingName.c_str(),
+        systraceCookie);
+    FbSystraceSection s(TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str());
+    #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->callFunction(moduleId, methodId, arguments);
+  });
 }
 
-void Bridge::invokeCallback(const double callbackId, const 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.invokeCallback");
+  int systraceCookie = m_systraceCookie++;
+  FbSystraceAsyncFlow::begin(
+      TRACE_TAG_REACT_CXX_BRIDGE,
+      "<callback>",
+      systraceCookie);
   #endif
-  auto returnedJSON = m_jsExecutor->invokeCallback(callbackId, arguments);
-  m_callback(parseMethodCalls(returnedJSON), true /* = isEndOfBatch */);
+
+  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_jsExecutor->setGlobalVariable(propName, jsonValue);
+  m_mainExecutor->setGlobalVariable(propName, jsonValue);
+}
+
+void* Bridge::getJavaScriptContext() {
+  return m_mainExecutor->getJavaScriptContext();
 }
 
 bool Bridge::supportsProfiling() {
-  return m_jsExecutor->supportsProfiling();
+  return m_mainExecutor->supportsProfiling();
 }
 
 void Bridge::startProfiler(const std::string& title) {
-  m_jsExecutor->startProfiler(title);
+  m_mainExecutor->startProfiler(title);
 }
 
 void Bridge::stopProfiler(const std::string& title, const std::string& filename) {
-  m_jsExecutor->stopProfiler(title, filename);
+  m_mainExecutor->stopProfiler(title, filename);
 }
 
 void Bridge::handleMemoryPressureModerate() {
-  m_jsExecutor->handleMemoryPressureModerate();
+  m_mainExecutor->handleMemoryPressureModerate();
 }
 
 void Bridge::handleMemoryPressureCritical() {
-  m_jsExecutor->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 26b1aa8ef9f6fe..3618ef04fe482f 100644
--- a/ReactAndroid/src/main/jni/react/Bridge.h
+++ b/ReactAndroid/src/main/jni/react/Bridge.h
@@ -2,13 +2,18 @@
 
 #pragma once
 
+#include <atomic>
 #include <functional>
 #include <map>
 #include <vector>
-#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 {
 
@@ -19,35 +24,65 @@ struct dynamic;
 namespace facebook {
 namespace react {
 
-class Bridge {
+class BridgeCallback {
 public:
-  typedef std::function<void(std::vector<MethodCall>, bool isEndOfBatch)> Callback;
+  virtual ~BridgeCallback() {};
 
-  Bridge(JSExecutorFactory* jsExecutorFactory, Callback callback);
-  virtual ~Bridge();
+  virtual void onCallNativeModules(
+      ExecutorToken executorToken,
+      std::vector<MethodCall>&& calls,
+      bool isEndOfBatch) = 0;
+
+  virtual void onExecutorUnregistered(ExecutorToken executorToken) = 0;
+};
+
+class Bridge;
+class ExecutorRegistration {
+public:
+  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_;
+};
 
+class Bridge {
+public:
   /**
-   * Flush get the next queue of changes.
+   * This must be called on the main JS thread.
    */
-  void flush();
+  Bridge(
+      JSExecutorFactory* jsExecutorFactory,
+      std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
+      std::unique_ptr<BridgeCallback> callback);
+  virtual ~Bridge();
 
   /**
    * Executes a function with the module ID and method ID and any additional
    * arguments in JS.
    */
-  void callFunction(const double moduleId, const double methodId, const folly::dynamic& args);
+  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(const double callbackId, const folly::dynamic& args);
+  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 executeApplicationScript(const std::string& script, const std::string& sourceURL);
+  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.
@@ -57,18 +92,70 @@ class Bridge {
     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<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;
-  std::unique_ptr<JSExecutor> m_jsExecutor;
+  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 fb740d9f9d41ea..6f392a3cd8faf0 100644
--- a/ReactAndroid/src/main/jni/react/Executor.h
+++ b/ReactAndroid/src/main/jni/react/Executor.h
@@ -5,6 +5,7 @@
 #include <string>
 #include <vector>
 #include <memory>
+
 #include "JSModulesUnbundle.h"
 
 namespace folly {
@@ -16,13 +17,11 @@ struct dynamic;
 namespace facebook {
 namespace react {
 
+class Bridge;
 class JSExecutor;
-
-typedef std::function<void(std::string, bool)> FlushImmediateCallback;
-
 class JSExecutorFactory {
 public:
-  virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) = 0;
+  virtual std::unique_ptr<JSExecutor> createJSExecutor(Bridge *bridge) = 0;
   virtual ~JSExecutorFactory() {};
 };
 
@@ -31,7 +30,7 @@ class JSExecutor {
   /**
    * Execute an application script bundle in the JS context.
    */
-  virtual void executeApplicationScript(
+  virtual void loadApplicationScript(
     const std::string& script,
     const std::string& sourceURL) = 0;
 
@@ -43,27 +42,27 @@ class JSExecutor {
     const std::string& startupCode,
     const std::string& sourceURL) = 0;
 
-  /**
-   * Executes BatchedBridge.flushedQueue in JS to get the next queue of changes.
-   */
-  virtual std::string flush() = 0;
-
   /**
    * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID,
-   * method ID and optional additional arguments in JS, and returns the next
-   * queue.
+   * method ID and optional additional arguments in JS. The executor is responsible
+   * for using Bridge->callNativeModules to invoke any necessary native modules methods.
    */
-  virtual std::string callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) = 0;
+  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.
+   * 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 std::string invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0;
+  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;
   };
@@ -73,6 +72,7 @@ class JSExecutor {
   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 00000000000000..a630e90d34a16c
--- /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 00000000000000..5d2d42179073c9
--- /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 a951c5d8f4dfd2..feb9c63f9b3f6e 100644
--- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
@@ -3,16 +3,19 @@
 #include "JSCExecutor.h"
 
 #include <algorithm>
-#include <atomic>
+#include <condition_variable>
+#include <mutex>
 #include <sstream>
 #include <string>
+#include <glog/logging.h>
 #include <folly/json.h>
 #include <folly/String.h>
 #include <sys/time.h>
 
+#include "Bridge.h"
 #include "JSCHelpers.h"
-#include "Value.h"
 #include "Platform.h"
+#include "Value.h"
 
 #ifdef WITH_JSC_EXTRA_TRACING
 #include "JSCTracing.h"
@@ -37,6 +40,10 @@ using fbsystrace::FbSystraceSection;
 #include <jsc_config_android.h>
 #endif
 
+#ifdef JSC_HAS_PERF_STATS_API
+#include "JSCPerfStats.h"
+#endif
+
 static const int64_t NANOSECONDS_IN_SECOND = 1000000000LL;
 static const int64_t NANOSECONDS_IN_MILLISECOND = 1000000LL;
 
@@ -45,14 +52,14 @@ 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 nativePerformanceNow(
+static JSValueRef nativeInjectHMRUpdate(
     JSContextRef ctx,
     JSObjectRef function,
     JSObjectRef thisObject,
@@ -79,24 +86,91 @@ static std::string executeJSCallWithJSC(
   return Value(ctx, result).toJSONString();
 }
 
-std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateCallback cb) {
-  return std::unique_ptr<JSExecutor>(new JSCExecutor(cb, cacheDir_));
+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);
+    }
+
+    // Try to load the script from the network if script is a URL
+    // NB: For security, this will only work in debug builds
+    std::string scriptSrc;
+    if (script.find("http://") == 0 || script.find("https://") == 0) {
+      std::stringstream outfileBuilder;
+      outfileBuilder << m_deviceCacheDir << "/workerScript" << m_workerId << ".js";
+      scriptSrc = WebWorkerUtil::loadScriptFromNetworkSync(script, outfileBuilder.str());
+    } else {
+      // TODO(9604438): Protect against script does not exist
+      scriptSrc = WebWorkerUtil::loadScriptFromAssets(script);
+    }
+
+    // TODO(9994180): Throw on error
+    loadApplicationScript(scriptSrc, script);
+  });
+}
+
+JSCExecutor::~JSCExecutor() {
+  CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
+}
+
+void JSCExecutor::destroy() {
+  *m_isDestroyed = true;
+  if (m_messageQueueThread->isOnThread()) {
+    terminateOnJSVMThread();
+  } else {
+    m_messageQueueThread->runOnQueueSync([this] () {
+      terminateOnJSVMThread();
+    });
+  }
 }
 
-JSCExecutor::JSCExecutor(FlushImmediateCallback cb, const std::string& cacheDir) :
-    m_flushImmediateCallback(cb), m_deviceCacheDir(cacheDir) {
+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);
-  m_messageQueueThread = MessageQueues::getCurrentMessageQueueThread();
   s_globalContextRefToJSCExecutor[m_context] = this;
   installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
   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);
 
-  #ifdef WITH_FB_JSC_TUNING
+  #if defined(WITH_JSC_INTERNAL) && defined(WITH_FB_JSC_TUNING)
   configureJSCForAndroid();
   #endif
 
@@ -109,41 +183,48 @@ JSCExecutor::JSCExecutor(FlushImmediateCallback cb, const std::string& cacheDir)
   #ifdef WITH_FB_MEMORY_PROFILING
   addNativeMemoryHooks(m_context);
   #endif
+
+  #ifdef JSC_HAS_PERF_STATS_API
+  addJSCPerfStatsHooks(m_context);
+  #endif
 }
 
-JSCExecutor::~JSCExecutor() {
-  // terminateWebWorker mutates m_webWorkers so collect all the workers to terminate first
+void JSCExecutor::terminateOnJSVMThread() {
+  // terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers
+  // to terminate first
   std::vector<int> workerIds;
-  for (auto it = m_webWorkers.begin(); it != m_webWorkers.end(); it++) {
-    workerIds.push_back(it->first);
+  for (auto& it : m_ownedWorkers) {
+    workerIds.push_back(it.first);
   }
   for (int workerId : workerIds) {
-    terminateWebWorker(workerId);
+    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) {
-  ReactMarker::logMarker("executeApplicationScript_startStringConvert");
+  ReactMarker::logMarker("loadApplicationScript_startStringConvert");
   String jsScript = String::createExpectingAscii(script);
-  ReactMarker::logMarker("executeApplicationScript_endStringConvert");
+  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
   if (!jsSourceURL) {
     evaluateScript(m_context, jsScript, jsSourceURL);
   } else {
-    // If we're evaluating a script, get the device's cache dir 
+    // 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();
 }
 
 void JSCExecutor::loadApplicationUnbundle(
@@ -154,31 +235,34 @@ void JSCExecutor::loadApplicationUnbundle(
     installGlobalFunction(m_context, "nativeRequire", nativeRequire);
   }
   m_unbundle = std::move(unbundle);
-  executeApplicationScript(startupCode, sourceURL);
+  loadApplicationScript(startupCode, sourceURL);
 }
 
-std::string JSCExecutor::flush() {
+void JSCExecutor::flush() {
   // TODO: Make this a first class function instead of evaling. #9317773
-  return executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>());
+  std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>());
+  m_bridge->callNativeModules(*this, calls, true);
 }
 
-std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
+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),
   };
-  return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
+  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, calls, true);
 }
 
-std::string JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
+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)
   };
-  return executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
+  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) {
@@ -191,6 +275,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;
@@ -232,7 +320,7 @@ void JSCExecutor::handleMemoryPressureCritical() {
 }
 
 void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
-  m_flushImmediateCallback(queueJSON, false);
+  m_bridge->callNativeModules(*this, queueJSON, false);
 }
 
 void JSCExecutor::loadModule(uint32_t moduleId) {
@@ -242,56 +330,128 @@ void JSCExecutor::loadModule(uint32_t moduleId) {
   evaluateScript(m_context, source, sourceUrl);
 }
 
-// WebWorker impl
+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();
 
-JSGlobalContextRef JSCExecutor::getContext() {
-  return m_context;
+  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;
 }
 
-std::shared_ptr<MessageQueueThread> JSCExecutor::getMessageQueueThread() {
-  return m_messageQueueThread;
+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::onMessageReceived(int workerId, const std::string& json) {
-  Object& worker = m_webWorkerJSObjs.at(workerId);
+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 = worker.getProperty("onmessage");
+  Value onmessageValue = workerObj->getProperty("onmessage");
   if (onmessageValue.isUndefined()) {
     return;
   }
 
-  JSValueRef args[] = { JSCWebWorker::createMessageObject(m_context, json) };
+  JSValueRef args[] = { createMessageObject(json) };
   onmessageValue.asObject().callAsFunction(1, args);
 
-  m_flushImmediateCallback(flush(), true);
+  flush();
 }
 
-int JSCExecutor::addWebWorker(const std::string& script, JSValueRef workerRef) {
-  static std::atomic_int nextWorkerId(0);
-  int workerId = nextWorkerId++;
-
-  m_webWorkers.emplace(std::piecewise_construct, std::forward_as_tuple(workerId), std::forward_as_tuple(workerId, this, script));
-  Object workerObj = Value(m_context, workerRef).asObject();
-  workerObj.makeProtected();
-  m_webWorkerJSObjs.emplace(workerId, std::move(workerObj));
-  return workerId;
-}
+void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
+  CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!";
 
-void JSCExecutor::postMessageToWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
-  JSCWebWorker& worker = m_webWorkers.at(workerId);
-  worker.postMessage(message);
+  JSValueRef args[] = { createMessageObject(msgString) };
+  Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage");
+  onmessageValue.asObject().callAsFunction(1, args);
 }
 
-void JSCExecutor::terminateWebWorker(int workerId) {
-  JSCWebWorker& worker = m_webWorkers.at(workerId);
+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);
 
-  worker.terminate();
+  std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken);
+  worker->destroy();
+  worker.reset();
+  workerMQT->quitSynchronous();
+}
 
-  m_webWorkers.erase(workerId);
-  m_webWorkerJSObjs.erase(workerId);
+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,
@@ -340,7 +500,7 @@ 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,
@@ -374,7 +534,7 @@ JSValueRef JSCExecutor::nativeStartWorker(
     size_t argumentCount,
     const JSValueRef arguments[],
     JSValueRef *exception) {
-  if (argumentCount != 2) {
+  if (argumentCount != 3) {
     *exception = createErrorString(ctx, "Got wrong number of args");
     return JSValueMakeUndefined(ctx);
   }
@@ -382,6 +542,7 @@ JSValueRef JSCExecutor::nativeStartWorker(
   std::string scriptFile = Value(ctx, arguments[0]).toString().str();
 
   JSValueRef worker = arguments[1];
+  JSValueRef globalObj = arguments[2];
 
   JSCExecutor *executor;
   try {
@@ -391,7 +552,7 @@ JSValueRef JSCExecutor::nativeStartWorker(
     return JSValueMakeUndefined(ctx);
   }
 
-  int workerId = executor->addWebWorker(scriptFile, worker);
+  int workerId = executor->addWebWorker(scriptFile, worker, globalObj);
 
   return JSValueMakeNumber(ctx, workerId);
 }
@@ -422,7 +583,7 @@ JSValueRef JSCExecutor::nativePostMessageToWorker(
     return JSValueMakeUndefined(ctx);
   }
 
-  executor->postMessageToWebWorker((int) workerDouble, arguments[1], exception);
+  executor->postMessageToOwnedWebWorker((int) workerDouble, arguments[1], exception);
 
   return JSValueMakeUndefined(ctx);
 }
@@ -453,7 +614,7 @@ JSValueRef JSCExecutor::nativeTerminateWorker(
     return JSValueMakeUndefined(ctx);
   }
 
-  executor->terminateWebWorker((int) workerDouble);
+  executor->terminateOwnedWebWorker((int) workerDouble);
 
   return JSValueMakeUndefined(ctx);
 }
@@ -471,4 +632,16 @@ static JSValueRef nativePerformanceNow(
   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 d06071a2a7a87b..7f41170c8c44aa 100644
--- a/ReactAndroid/src/main/jni/react/JSCExecutor.h
+++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h
@@ -5,10 +5,13 @@
 #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 "JSCWebWorker.h"
+#include "Value.h"
 
 namespace facebook {
 namespace react {
@@ -17,64 +20,100 @@ class MessageQueueThread;
 
 class JSCExecutorFactory : public JSExecutorFactory {
 public:
-  JSCExecutorFactory(const std::string& cacheDir) : cacheDir_(cacheDir) {}
-  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 JSCWebWorkerOwner {
+class JSCExecutor : public JSExecutor {
 public:
   /**
-   * Should be invoked from the JS thread.
+   * Must be invoked from thread this Executor will run on.
    */
-  explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback, const std::string& cacheDir);
+  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 void loadApplicationUnbundle(
     std::unique_ptr<JSModulesUnbundle> unbundle,
     const std::string& startupCode,
     const std::string& sourceURL) override;
-  virtual std::string flush() override;
-  virtual std::string callFunction(
+  virtual void callFunction(
     const double moduleId,
     const double methodId,
     const folly::dynamic& arguments) override;
-  virtual std::string invokeCallback(
+  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;
   virtual void handleMemoryPressureModerate() override;
   virtual void handleMemoryPressureCritical() override;
+  virtual void destroy() override;
 
-  void flushQueueImmediate(std::string queueJSON);
   void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
-  virtual void onMessageReceived(int workerId, const std::string& message) override;
-  virtual JSGlobalContextRef getContext() override;
-  virtual std::shared_ptr<MessageQueueThread> getMessageQueueThread() override;
 
 private:
   JSGlobalContextRef m_context;
-  FlushImmediateCallback m_flushImmediateCallback;
-  std::unordered_map<int, JSCWebWorker> m_webWorkers;
-  std::unordered_map<int, Object> m_webWorkerJSObjs;
-  std::shared_ptr<MessageQueueThread> m_messageQueueThread;
+  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;
 
-  int addWebWorker(const std::string& script, JSValueRef workerRef);
-  void postMessageToWebWorker(int worker, JSValueRef message, JSValueRef *exn);
-  void terminateWebWorker(int worker);
+  /**
+   * 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,
@@ -96,13 +135,27 @@ class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
       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);
+      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 0711c273f140b8..e961a4051c4b77 100644
--- a/ReactAndroid/src/main/jni/react/JSCHelpers.cpp
+++ b/ReactAndroid/src/main/jni/react/JSCHelpers.cpp
@@ -8,7 +8,7 @@
 #include "Value.h"
 
 #if WITH_FBJSCEXTENSIONS
-#include <jsc_function_info_cache.h>
+#include <jsc_preparsing_cache.h>
 #endif
 
 namespace facebook {
@@ -55,7 +55,7 @@ JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef
     auto line = exception.asObject().getProperty("line");
 
     std::ostringstream locationInfo;
-    std::string file = source != nullptr ? String::adopt(source).str() : "";
+    std::string file = source != nullptr ? String::ref(source).str() : "";
     locationInfo << "(" << (file.length() ? file : "<unknown file>");
     if (line != nullptr && line.isNumber()) {
       locationInfo << ":" << line.asInteger();
diff --git a/ReactAndroid/src/main/jni/react/JSCHelpers.h b/ReactAndroid/src/main/jni/react/JSCHelpers.h
index c765421c4ca5d8..9b61a566b97065 100644
--- a/ReactAndroid/src/main/jni/react/JSCHelpers.h
+++ b/ReactAndroid/src/main/jni/react/JSCHelpers.h
@@ -38,25 +38,10 @@ JSValueRef makeJSCException(
     JSContextRef ctx,
     const char* exception_text);
 
-#ifdef __i386__
-// The Android x86 ABI states that the NDK toolchain assumes 16 byte stack
-// alignment: http://developer.android.com/ndk/guides/x86.html JSC checks for
-// stack alignment, and fails with SIGTRAP if it is not.  Empirically, the
-// google android x86 emulator does not provide this alignment, and so JSC
-// calls may crash.  All checked calls go through here, so the attribute here
-// is added to force alignment and prevent crashes.
-
-JSValueRef evaluateScript(
-    JSContextRef ctx,
-    JSStringRef script,
-    JSStringRef sourceURL,
-    const char* cachePath = nullptr) __attribute__((force_align_arg_pointer));
-#else
 JSValueRef evaluateScript(
     JSContextRef ctx,
     JSStringRef script,
     JSStringRef sourceURL,
     const char* cachePath = nullptr);
-#endif
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/JSCPerfStats.cpp b/ReactAndroid/src/main/jni/react/JSCPerfStats.cpp
new file mode 100644
index 00000000000000..9ef341c9e11dd4
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/JSCPerfStats.cpp
@@ -0,0 +1,65 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "JSCPerfStats.h"
+
+#ifdef JSC_HAS_PERF_STATS_API
+
+#include <JavaScriptCore/JSPerfStats.h>
+#include <JavaScriptCore/JSValueRef.h>
+
+#include "JSCHelpers.h"
+#include "Value.h"
+
+static JSValueRef nativeGetHeapStats(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  JSHeapStats heapStats = {0};
+  JSGetHeapStats(ctx, &heapStats);
+
+  auto result = facebook::react::Object::create(ctx);
+  result.setProperty("size", {ctx, JSValueMakeNumber(ctx, heapStats.size)});
+  result.setProperty("extra_size", {ctx, JSValueMakeNumber(ctx, heapStats.extraSize)});
+  result.setProperty("capacity", {ctx, JSValueMakeNumber(ctx, heapStats.capacity)});
+  result.setProperty("object_count", {ctx, JSValueMakeNumber(ctx, heapStats.objectCount)});
+
+  return (JSObjectRef) result;
+}
+
+static JSValueRef nativeGetGCStats(
+    JSContextRef ctx,
+    JSObjectRef function,
+    JSObjectRef thisObject,
+    size_t argumentCount,
+    const JSValueRef arguments[],
+    JSValueRef* exception) {
+  JSGCStats gcStats = {0};
+  JSGetGCStats(ctx, &gcStats);
+
+  auto result = facebook::react::Object::create(ctx);
+  result.setProperty(
+      "last_full_gc_length",
+      {ctx, JSValueMakeNumber(ctx, gcStats.lastFullGCLength)});
+  result.setProperty(
+      "last_eden_gc_length",
+      {ctx, JSValueMakeNumber(ctx, gcStats.lastEdenGCLength)});
+
+  return (JSObjectRef) result;
+}
+
+#endif
+
+namespace facebook {
+namespace react {
+
+void addJSCPerfStatsHooks(JSGlobalContextRef ctx) {
+#ifdef JSC_HAS_PERF_STATS_API
+  installGlobalFunction(ctx, "nativeGetHeapStats", nativeGetHeapStats);
+  installGlobalFunction(ctx, "nativeGetGCStats", nativeGetGCStats);
+#endif
+}
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCPerfStats.h b/ReactAndroid/src/main/jni/react/JSCPerfStats.h
new file mode 100644
index 00000000000000..73ca29e8b0ee88
--- /dev/null
+++ b/ReactAndroid/src/main/jni/react/JSCPerfStats.h
@@ -0,0 +1,12 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+
+#include <JavaScriptCore/JSContextRef.h>
+
+namespace facebook {
+namespace react {
+
+void addJSCPerfStatsHooks(JSGlobalContextRef ctx);
+
+} }
diff --git a/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp b/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp
deleted file mode 100644
index e8e710bdf39332..00000000000000
--- a/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
-
-#include "JSCWebWorker.h"
-
-#include <unistd.h>
-#include <condition_variable>
-#include <mutex>
-#include <unordered_map>
-
-#include <glog/logging.h>
-#include <folly/Memory.h>
-
-#include "JSCHelpers.h"
-#include "MessageQueueThread.h"
-#include "Platform.h"
-#include "Value.h"
-
-#include <JavaScriptCore/JSValueRef.h>
-
-namespace facebook {
-namespace react {
-
-// TODO(9604425): thread safety
-static std::unordered_map<JSContextRef, JSCWebWorker*> s_globalContextRefToJSCWebWorker;
-
-JSCWebWorker::JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string scriptSrc) :
-    id_(id),
-    scriptName_(std::move(scriptSrc)),
-    owner_(owner) {
-  ownerMessageQueueThread_ = owner->getMessageQueueThread();
-  CHECK(ownerMessageQueueThread_) << "Owner MessageQueue must not be null";
-  workerMessageQueueThread_ = WebWorkerUtil::createWebWorkerThread(id, ownerMessageQueueThread_.get());
-  CHECK(workerMessageQueueThread_) << "Failed to create worker thread";
-
-  workerMessageQueueThread_->runOnQueue([this] () {
-    initJSVMAndLoadScript();
-  });
-}
-
-
-JSCWebWorker::~JSCWebWorker() {
-  CHECK(isTerminated()) << "Didn't terminate the web worker before releasing it!";;
-}
-
-void JSCWebWorker::postMessage(JSValueRef msg) {
-  std::string msgString = Value(owner_->getContext(), msg).toJSONString();
-
-  workerMessageQueueThread_->runOnQueue([this, msgString] () {
-    if (isTerminated()) {
-      return;
-    }
-
-    JSValueRef args[] = { createMessageObject(context_, msgString) };
-    Value onmessageValue = Object::getGlobalObject(context_).getProperty("onmessage");
-    onmessageValue.asObject().callAsFunction(1, args);
-  });
-}
-
-void JSCWebWorker::terminate() {
-  if (isTerminated()) {
-    return;
-  }
-  isTerminated_.store(true, std::memory_order_release);
-
-  if (workerMessageQueueThread_->isOnThread()) {
-    terminateOnWorkerThread();
-  } else {
-    std::mutex signalMutex;
-    std::condition_variable signalCv;
-    bool terminationComplete = false;
-
-    workerMessageQueueThread_->runOnQueue([&] () mutable {
-      std::lock_guard<std::mutex> lock(signalMutex);
-
-      terminateOnWorkerThread();
-      terminationComplete = true;
-
-      signalCv.notify_one();
-    });
-
-    std::unique_lock<std::mutex> lock(signalMutex);
-    signalCv.wait(lock, [&terminationComplete] { return terminationComplete; });
-  }
-}
-
-void JSCWebWorker::terminateOnWorkerThread() {
-  s_globalContextRefToJSCWebWorker.erase(context_);
-  JSGlobalContextRelease(context_);
-  context_ = nullptr;
-  workerMessageQueueThread_->quitSynchronous();
-}
-
-bool JSCWebWorker::isTerminated() {
-  return isTerminated_.load(std::memory_order_acquire);
-}
-
-void JSCWebWorker::initJSVMAndLoadScript() {
-  CHECK(!isTerminated()) << "Worker was already finished!";
-  CHECK(!context_) << "Worker JS VM was already created!";
-
-  context_ = JSGlobalContextCreateInGroup(
-      NULL, // use default JS 'global' object
-      NULL // create new group (i.e. new VM)
-  );
-  s_globalContextRefToJSCWebWorker[context_] = this;
-
-  // TODO(9604438): Protect against script does not exist
-  std::string script = WebWorkerUtil::loadScriptFromAssets(scriptName_);
-  evaluateScript(context_, String(script.c_str()), String(scriptName_.c_str()));
-
-  installGlobalFunction(context_, "postMessage", nativePostMessage);
-}
-
-void JSCWebWorker::postMessageToOwner(JSValueRef msg) {
-  std::string msgString = Value(context_, msg).toJSONString();
-  ownerMessageQueueThread_->runOnQueue([this, msgString] () {
-      owner_->onMessageReceived(id_, msgString);
-  });
-}
-
-JSValueRef JSCWebWorker::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];
-  JSCWebWorker *webWorker = s_globalContextRefToJSCWebWorker.at(JSContextGetGlobalContext(ctx));
-
-  if (webWorker->isTerminated()) {
-    return JSValueMakeUndefined(ctx);
-  }
-
-  webWorker->postMessageToOwner(msg);
-  
-  return JSValueMakeUndefined(ctx);
-}
-
-/*static*/
-Object JSCWebWorker::createMessageObject(JSContextRef context, const std::string& msgJson) {
-  Value rebornJSMsg = Value::fromJSON(context, String(msgJson.c_str()));
-  Object messageObject = Object::create(context);
-  messageObject.setProperty("data", rebornJSMsg);
-  return std::move(messageObject);
-}
-
-}
-}
diff --git a/ReactAndroid/src/main/jni/react/JSCWebWorker.h b/ReactAndroid/src/main/jni/react/JSCWebWorker.h
deleted file mode 100644
index 73d983cdfe7b74..00000000000000
--- a/ReactAndroid/src/main/jni/react/JSCWebWorker.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2004-present Facebook. All Rights Reserved.
-
-#include <atomic>
-#include <functional>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <queue>
-
-#include <JavaScriptCore/JSValueRef.h>
-
-#include "Value.h"
-
-namespace facebook {
-namespace react {
-
-class MessageQueueThread;
-
-/**
- * A class that can own the lifecycle, receive messages from, and dispatch messages
- * to JSCWebWorkers.
- */
-class JSCWebWorkerOwner {
-public:
-  /**
-   * Called when a worker has posted a message with `postMessage`.
-   */
-  virtual void onMessageReceived(int workerId, const std::string& message) = 0;
-  virtual JSGlobalContextRef getContext() = 0;
-
-  /**
-   * Should return the owner's MessageQueueThread. Calls to onMessageReceived will be enqueued
-   * on this thread.
-   */
-  virtual std::shared_ptr<MessageQueueThread> getMessageQueueThread() = 0;
-};
-
-/**
- * Implementation of a web worker for JSC. The web worker should be created from the owner's
- * (e.g., owning JSCExecutor instance) JS MessageQueueThread. The worker is responsible for
- * creating its own MessageQueueThread.
- *
- * During operation, the JSCExecutor should call postMessage **from its own MessageQueueThread**
- * to send messages to the worker. The worker will handle enqueueing those messages on its own
- * MessageQueueThread as appropriate. When the worker has a message to post to the owner, it will
- * enqueue a call to owner->onMessageReceived on the owner's MessageQueueThread.
- */
-class JSCWebWorker {
-public:
-  explicit JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string script);
-  ~JSCWebWorker();
-
-  /**
-   * Post a message to be received by the worker on its thread. This must be called from 
-   * ownerMessageQueueThread_.
-   */
-  void postMessage(JSValueRef msg);
-
-  /**
-   * Synchronously quits the current worker and cleans up its VM.
-   */
-  void terminate();
-
-  /**
-   * Whether terminate() has been called on this worker.
-   */
-  bool isTerminated();
-
-  static Object createMessageObject(JSContextRef context, const std::string& msgData);
-private:
-  void initJSVMAndLoadScript();
-  void postRunnableToEventLoop(std::function<void()>&& runnable);
-  void postMessageToOwner(JSValueRef result);
-  void terminateOnWorkerThread();
-
-  int id_;
-  std::atomic_bool isTerminated_ = ATOMIC_VAR_INIT(false);
-  std::string scriptName_;
-  JSCWebWorkerOwner *owner_ = nullptr;
-  std::shared_ptr<MessageQueueThread> ownerMessageQueueThread_;
-  std::unique_ptr<MessageQueueThread> workerMessageQueueThread_;
-  JSGlobalContextRef context_ = nullptr;
-
-  static JSValueRef nativePostMessage(
-      JSContextRef ctx,
-      JSObjectRef function,
-      JSObjectRef thisObject,
-      size_t argumentCount,
-      const JSValueRef arguments[],
-      JSValueRef *exception);
-};
-
-}
-}
diff --git a/ReactAndroid/src/main/jni/react/MessageQueueThread.h b/ReactAndroid/src/main/jni/react/MessageQueueThread.h
index 544b91142be980..f6352ab0d90abb 100644
--- a/ReactAndroid/src/main/jni/react/MessageQueueThread.h
+++ b/ReactAndroid/src/main/jni/react/MessageQueueThread.h
@@ -2,7 +2,9 @@
 
 #pragma once
 
+#include <condition_variable>
 #include <functional>
+#include <mutex>
 
 namespace facebook {
 namespace react {
@@ -14,6 +16,24 @@ class MessageQueueThread {
   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/Platform.cpp b/ReactAndroid/src/main/jni/react/Platform.cpp
index 03edca72c635b8..270764440dd29c 100644
--- a/ReactAndroid/src/main/jni/react/Platform.cpp
+++ b/ReactAndroid/src/main/jni/react/Platform.cpp
@@ -16,14 +16,15 @@ GetCurrentMessageQueueThread getCurrentMessageQueueThread;
 namespace WebWorkerUtil {
 WebWorkerQueueFactory createWebWorkerThread;
 LoadScriptFromAssets loadScriptFromAssets;
+LoadScriptFromNetworkSync loadScriptFromNetworkSync;
 };
 
 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
index c5c1c5043b04c0..da214ee851e888 100644
--- a/ReactAndroid/src/main/jni/react/Platform.h
+++ b/ReactAndroid/src/main/jni/react/Platform.h
@@ -29,12 +29,15 @@ extern WebWorkerQueueFactory createWebWorkerThread;
 
 using LoadScriptFromAssets = std::function<std::string(const std::string& assetName)>;
 extern LoadScriptFromAssets loadScriptFromAssets;
+
+using LoadScriptFromNetworkSync = std::function<std::string(const std::string& url, const std::string& tempfileName)>;
+extern LoadScriptFromNetworkSync loadScriptFromNetworkSync;
 };
 
 namespace PerfLogging {
 using InstallNativeHooks = std::function<void(JSGlobalContextRef)>;
 extern InstallNativeHooks installNativeHooks;
-}
+};
 
 namespace JSLogging {
   using JSCNativeHook = JSValueRef (*) (
@@ -44,6 +47,6 @@ namespace JSLogging {
       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 ac07c486fd5cd4..6c1c393ded7828 100644
--- a/ReactAndroid/src/main/jni/react/Value.cpp
+++ b/ReactAndroid/src/main/jni/react/Value.cpp
@@ -79,6 +79,16 @@ Value Object::getProperty(const String& propName) const {
   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));
 }
@@ -96,6 +106,30 @@ 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(
diff --git a/ReactAndroid/src/main/jni/react/Value.h b/ReactAndroid/src/main/jni/react/Value.h
index 1cee8cc9fd1587..5fcb89e3812f94 100644
--- a/ReactAndroid/src/main/jni/react/Value.h
+++ b/ReactAndroid/src/main/jni/react/Value.h
@@ -4,6 +4,9 @@
 
 #include <memory>
 #include <sstream>
+#include <unordered_map>
+#include <vector>
+
 #include <JavaScriptCore/JSObjectRef.h>
 #include <JavaScriptCore/JSRetainPtr.h>
 #include <JavaScriptCore/JSStringRef.h>
@@ -26,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)
   {}
@@ -123,8 +128,11 @@ class Object : public noncopyable {
 
   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) {
diff --git a/ReactAndroid/src/main/jni/react/jni/Android.mk b/ReactAndroid/src/main/jni/react/jni/Android.mk
index 9d4d9b2b5fffad..46bf1d5251a99a 100644
--- a/ReactAndroid/src/main/jni/react/jni/Android.mk
+++ b/ReactAndroid/src/main/jni/react/jni/Android.mk
@@ -5,6 +5,7 @@ include $(CLEAR_VARS)
 LOCAL_MODULE := reactnativejni
 
 LOCAL_SRC_FILES := \
+	JExecutorToken.cpp \
   JMessageQueueThread.cpp \
   JSCPerfLogging.cpp \
   JSLoader.cpp \
@@ -22,7 +23,7 @@ LOCAL_CFLAGS += $(CXX11_FLAGS)
 LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS)
 
 LOCAL_LDLIBS += -landroid
-LOCAL_SHARED_LIBRARIES := libfolly_json libfbjni libjsc
+LOCAL_SHARED_LIBRARIES := libfolly_json libfbjni libjsc libglog_init
 LOCAL_STATIC_LIBRARIES := libreactnative
 
 include $(BUILD_SHARED_LIBRARY)
@@ -30,5 +31,6 @@ include $(BUILD_SHARED_LIBRARY)
 $(call import-module,react)
 $(call import-module,jsc)
 $(call import-module,folly)
+$(call import-module,fbgloginit)
 $(call import-module,jni)
 $(call import-module,jsc)
diff --git a/ReactAndroid/src/main/jni/react/jni/BUCK b/ReactAndroid/src/main/jni/react/jni/BUCK
index bebdf54ce2a38f..be23d64cfe0913 100644
--- a/ReactAndroid/src/main/jni/react/jni/BUCK
+++ b/ReactAndroid/src/main/jni/react/jni/BUCK
@@ -4,6 +4,7 @@ include_defs('//ReactAndroid/DEFS')
 SUPPORTED_PLATFORMS = '^android-(armv7|x86)$'
 
 DEPS = [
+  FBGLOGINIT_TARGET,
   '//native/jni:jni',
   '//native/third-party/android-ndk:android',
   '//xplat/folly:molly',
@@ -15,10 +16,17 @@ def jni_library(**kwargs):
     visibility = [
       'PUBLIC',
     ],
-    deps = DEPS + [
+    deps = DEPS + JSC_DEPS + [
       react_native_target('jni/react:react'),
-      '//native/third-party/jsc:jsc',
-      '//native/third-party/jsc:jsc_legacy_profiler',
+    ],
+    **kwargs
+  )
+
+  cxx_library(
+    name='jni-internal',
+    visibility = [INTERNAL_APP],
+    deps = DEPS + JSC_INTERNAL_DEPS + [
+      react_native_target('jni/react:react-internal'),
     ],
     **kwargs
   )
@@ -28,6 +36,7 @@ jni_library(
   header_namespace = 'react/jni',
   supported_platforms_regex = SUPPORTED_PLATFORMS,
   srcs = [
+    'JExecutorToken.cpp',
     'JMessageQueueThread.cpp',
     'JSCPerfLogging.cpp',
     'JSLoader.cpp',
@@ -39,12 +48,14 @@ jni_library(
   ],
   headers = [
     'JSLoader.h',
-    'ProxyExecutor.h',
+    'JExecutorToken.h',
+    'JExecutorTokenFactory.h',
     'JMessageQueueThread.h',
     'JNativeRunnable.h',
     'JniJSModulesUnbundle.h',
     'JSCPerfLogging.h',
     'JSLogging.h',
+    'ProxyExecutor.h',
     'WebWorkers.h',
   ],
   exported_headers = [
@@ -59,6 +70,7 @@ jni_library(
   compiler_flags = [
     '-Wall',
     '-Werror',
+    '-Wno-deprecated-declarations',
     '-fexceptions',
     '-std=c++11',
     '-fvisibility=hidden',
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 00000000000000..97e80948b196a5
--- /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 00000000000000..ac348f0ecd731f
--- /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 00000000000000..d7d923111395b0
--- /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/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
index 128ef833895441..2ba65553136f40 100644
--- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
@@ -3,6 +3,7 @@
 #include <android/asset_manager_jni.h>
 #include <android/input.h>
 #include <fb/log.h>
+#include <fb/glog_init.h>
 #include <folly/json.h>
 #include <jni/Countable.h>
 #include <jni/Environment.h>
@@ -16,6 +17,8 @@
 #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"
@@ -565,6 +568,7 @@ namespace bridge {
 
 static jmethodID gCallbackMethod;
 static jmethodID gOnBatchCompleteMethod;
+static jmethodID gOnExecutorUnregisteredMethod;
 static jmethodID gLogMarkerMethod;
 
 struct CountableBridge : Bridge, Countable {
@@ -580,7 +584,7 @@ static void logMarker(const std::string& marker) {
   env->DeleteLocalRef(jmarker);
 }
 
-static void makeJavaCall(JNIEnv* env, jobject callback, MethodCall&& call) {
+static void makeJavaCall(JNIEnv* env, ExecutorToken executorToken, jobject callback, const MethodCall& call) {
   if (call.arguments.isNull()) {
     return;
   }
@@ -592,39 +596,64 @@ static void makeJavaCall(JNIEnv* env, jobject callback, MethodCall&& call) {
   #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 callback(weakCallback);
-    if (callback) {
-      for (auto&& call : calls) {
-        makeJavaCall(env, callback, std::move(call));
+
+    ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread_);
+    if (!callbackQueueThread) {
+      FBLOGW("Dropped callback because callback queue thread went away");
+      return;
+    }
+
+    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;
         }
@@ -632,33 +661,49 @@ 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 bridgeCallback = folly::make_unique<PlatformBridgeCallback>(weakCallback, weakCallbackQueueThread);
   auto nativeExecutorFactory = extractRefPtr<CountableJSExecutorFactory>(env, executor);
-  auto bridge = createNew<CountableBridge>(nativeExecutorFactory.get(), bridgeCallback);
+  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(
+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->flush();
+    bridge->loadApplicationScript(script, sourceUri);
   } catch (...) {
     translatePendingCppExceptionToJavaException();
   }
@@ -669,15 +714,12 @@ static void loadApplicationUnbundle(
     AAssetManager *assetManager,
     const std::string& startupCode,
     const std::string& startupFileName) {
-
   try {
-    // Load the application unbundle and collect/dispatch any native calls that might have occured
     bridge->loadApplicationUnbundle(
       std::unique_ptr<JSModulesUnbundle>(
         new JniJSModulesUnbundle(assetManager, startupFileName)),
       startupCode,
       startupFileName);
-    bridge->flush();
   } catch (...) {
     translatePendingCppExceptionToJavaException();
   }
@@ -694,15 +736,15 @@ static void loadScriptFromAssets(JNIEnv* env, jobject obj, jobject assetManager,
   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"));
   if (JniJSModulesUnbundle::isUnbundle(manager, assetNameStr)) {
-    loadApplicationUnbundle(bridge, manager, script, assetNameStr);
+    loadApplicationUnbundle(bridge, manager, script, "file://" + assetNameStr);
   } else {
-    executeApplicationScript(bridge, script, assetNameStr);
+    loadApplicationScript(bridge, script, "file://" + assetNameStr);
   }
   if (env->ExceptionCheck()) {
     return;
@@ -720,38 +762,41 @@ static void loadScriptFromFile(JNIEnv* env, jobject obj, jstring fileName, jstri
   #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) {
+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));
   try {
     bridge->callFunction(
+      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
       (double) moduleId,
       (double) methodId,
-      std::move(arguments->array)
+      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<CountableBridge>(env, obj);
   auto arguments = cthis(wrap_alias(args));
   try {
     bridge->invokeCallback(
+      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
       (double) callbackId,
       std::move(arguments->array)
     );
@@ -765,6 +810,17 @@ static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstrin
   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<CountableBridge>(env, obj);
   return bridge->supportsProfiling() ? JNI_TRUE : JNI_FALSE;
@@ -818,13 +874,21 @@ std::string getDeviceCacheDir() {
 }
 
 struct CountableJSCExecutorFactory : CountableJSExecutorFactory  {
-  virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) override {
-    return JSCExecutorFactory(getDeviceCacheDir()).createJSExecutor(cb);
+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) {
-  auto executor = createNew<CountableJSCExecutorFactory>();
+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));
 }
 
@@ -844,6 +908,7 @@ jmethodID getLogMarkerMethod() {
 
 extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
   return initialize(vm, [] {
+    facebook::gloginit::initialize();
     // Inject some behavior into react/
     ReactMarker::logMarker = bridge::logMarker;
     WebWorkerUtil::createWebWorkerThread = WebWorkers::createWebWorkerThread;
@@ -851,6 +916,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
       [] (const std::string& assetName) {
         return loadScriptFromAssets(assetName);
       };
+    WebWorkerUtil::loadScriptFromNetworkSync = WebWorkers::loadScriptFromNetworkSync;
     MessageQueues::getCurrentMessageQueueThread =
       [] {
         return std::unique_ptr<MessageQueueThread>(
@@ -919,7 +985,8 @@ 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", {
@@ -929,14 +996,16 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
     });
 
     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),
@@ -944,12 +1013,13 @@ 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/NativeRunnableDeprecated");
diff --git a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
index e1025b06bba5b6..07743bed896a57 100644
--- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
@@ -8,6 +8,7 @@
 #include <jni/LocalString.h>
 #include <folly/json.h>
 
+#include <react/Bridge.h>
 namespace facebook {
 namespace react {
 
@@ -27,25 +28,25 @@ static std::string executeJSCallWithProxy(
   return result->toString();
 }
 
-std::unique_ptr<JSExecutor> ProxyExecutorOneTimeFactory::createJSExecutor(FlushImmediateCallback ignoredCallback) {
+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());
@@ -57,25 +58,23 @@ void ProxyExecutor::loadApplicationUnbundle(std::unique_ptr<JSModulesUnbundle>,
     "Loading application unbundles is not supported for proxy executors");
 }
 
-std::string ProxyExecutor::flush() {
-  return executeJSCallWithProxy(m_executor.get(), "flushedQueue", std::vector<folly::dynamic>());
-}
-
-std::string ProxyExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
+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),
   };
-  return executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call));
+  std::string result = executeJSCallWithProxy(m_executor.get(), "callFunctionReturnFlushedQueue", std::move(call));
+  m_bridge->callNativeModules(*this, result, true);
 }
 
-std::string ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
+void ProxyExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
   std::vector<folly::dynamic> call{
     (double) callbackId,
     std::move(arguments)
   };
-  return executeJSCallWithProxy(m_executor.get(), "invokeCallbackAndReturnFlushedQueue", std::move(call));
+  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 09e7778b097e06..c66346424920cd 100644
--- a/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h
+++ b/ReactAndroid/src/main/jni/react/jni/ProxyExecutor.h
@@ -19,7 +19,7 @@ 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;
@@ -27,22 +27,22 @@ class ProxyExecutorOneTimeFactory : public CountableJSExecutorFactory {
 
 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 void loadApplicationUnbundle(
     std::unique_ptr<JSModulesUnbundle> bundle,
     const std::string& startupCode,
     const std::string& sourceURL) override;
-  virtual std::string flush() override;
-  virtual std::string callFunction(
+  virtual void callFunction(
     const double moduleId,
     const double methodId,
     const folly::dynamic& arguments) override;
-  virtual std::string invokeCallback(
+  virtual void invokeCallback(
     const double callbackId,
     const folly::dynamic& arguments) override;
   virtual void setGlobalVariable(
@@ -51,6 +51,7 @@ class ProxyExecutor : public JSExecutor {
 
 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
index e243fa8bfb3227..a37df26c6a12d0 100644
--- a/ReactAndroid/src/main/jni/react/jni/WebWorkers.h
+++ b/ReactAndroid/src/main/jni/react/jni/WebWorkers.h
@@ -2,7 +2,10 @@
 
 #pragma once
 
+#include <fstream>
 #include <memory>
+#include <string>
+#include <sstream>
 
 #include <jni.h>
 #include <folly/Memory.h>
@@ -24,6 +27,24 @@ class WebWorkers : public JavaClass<WebWorkers> {
     auto res = method(WebWorkers::javaClassStatic(), id, static_cast<JMessageQueueThread*>(ownerMessageQueueThread)->jobj());
     return folly::make_unique<JMessageQueueThread>(res);
   }
+
+  static std::string loadScriptFromNetworkSync(const std::string& url, const std::string& tempfileName) {
+    static auto method = WebWorkers::javaClassStatic()->
+        getStaticMethod<void(jstring, jstring)>("downloadScriptToFileSync");
+    method(
+        WebWorkers::javaClassStatic(),
+        jni::make_jstring(url).get(),
+        jni::make_jstring(tempfileName).get());
+
+    std::ifstream tempFile(tempfileName);
+    if (!tempFile.good()) {
+      throw std::runtime_error("Didn't find worker script file at " + tempfileName);
+    }
+    std::stringstream buffer;
+    buffer << tempFile.rdbuf();
+    std::remove(tempfileName.c_str());
+    return buffer.str();
+  }
 };
 
 } }
diff --git a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
index 9091c1125055fd..1de276f2093e3e 100644
--- a/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
+++ b/ReactAndroid/src/main/jni/react/test/jscexecutor.cpp
@@ -33,7 +33,7 @@ TEST(JSCExecutor, CallFunction) {
   "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);
@@ -62,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") },
@@ -89,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];
@@ -115,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"),
@@ -142,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];
@@ -165,7 +165,7 @@ TEST(JSCExecutor, SetSimpleGlobalVariable) {
   "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());
@@ -185,7 +185,7 @@ TEST(JSCExecutor, SetObjectGlobalVariable) {
   "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 0bf49e33d6fa92..56ad010bc2e831 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/libraries/fbcore/src/main/java/com/facebook/common/logging/BUCK b/ReactAndroid/src/main/libraries/fbcore/src/main/java/com/facebook/common/logging/BUCK
index cf221085156f5e..93661768e1bdb2 100644
--- 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
@@ -1,11 +1,10 @@
-android_prebuilt_aar(
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
     name = 'logging',
-    aar = ':fbcore-binary-aar',
-    visibility = ['//ReactAndroid/...'],
+    exported_deps = [
+        react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
+    ],
+    visibility = ['//ReactAndroid/...',],
 )
 
-remote_file(
-    name = 'fbcore-binary-aar',
-    url = 'mvn:com.facebook.fresco:fbcore:aar:0.8.1',
-    sha1 = 'cc46b3d564139bf63bb41534c7a723ee8119ae5f',
-)
diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml
index d56ca71d1cec21..647a4c37359486 100644
--- a/ReactAndroid/src/main/res/devsupport/values/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml
@@ -3,8 +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_hot_module_replacement" project="catalyst" translatable="false">Enable Hot Reloading</string>
+  <string name="catalyst_hot_module_replacement_off" project="catalyst" translatable="false">Disable Hot Reloading</string>
   <string name="catalyst_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>
diff --git a/ReactAndroid/src/main/third-party/android-support-for-standalone-apps/v7/appcompat/BUCK b/ReactAndroid/src/main/third-party/android-support-for-standalone-apps/v7/appcompat/BUCK
deleted file mode 100644
index 626b8dcdac7ad9..00000000000000
--- a/ReactAndroid/src/main/third-party/android-support-for-standalone-apps/v7/appcompat/BUCK
+++ /dev/null
@@ -1,36 +0,0 @@
-android_prebuilt_aar(
-    name = 'appcompat-23.1',
-    aar = ':appcompat-binary-aar',
-    visibility = ['//ReactAndroid/...',],
-)
-
-# Unpack resources from the appcompat aar and merge their ids into the
-# generated com.facebook.react.R class.
-#
-# We do this for compatibility with Gradle: we build the open source
-# version React Native with both Buck and Gradle. See for example
-# ReactToolbarManager.java where we access the appcompat resources
-# via com.facebook.react.R.
-android_resource(
-    name = 'res-for-react-native',
-    res = ':res-unpacker-cmd',
-    package = 'com.facebook.react',
-    visibility = ['//ReactAndroid/...',],
-)
-
-genrule(
-    name = 'res-unpacker-cmd',
-    cmd = '$(exe :res-unpacker) $(location :appcompat-binary-aar) $OUT',
-    out = 'res',
-)
-
-python_binary(
-    name = 'res-unpacker',
-    main = 'res-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/v4/BUCK b/ReactAndroid/src/main/third-party/android/support/v4/BUCK
index b0a85e26b28652..3f34e9424b661c 100644
--- a/ReactAndroid/src/main/third-party/android/support/v4/BUCK
+++ b/ReactAndroid/src/main/third-party/android/support/v4/BUCK
@@ -1,7 +1,7 @@
 android_prebuilt_aar(
     name = 'lib-support-v4',
     aar = ':lib-support-v4-binary-aar',
-    visibility = ['//ReactAndroid/...',],
+    visibility = ['PUBLIC',],
 )
 
 remote_file(
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 00000000000000..9afb2e02fd6a25
--- /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-for-standalone-apps/v7/appcompat/res-unpacker.py b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py
similarity index 70%
rename from ReactAndroid/src/main/third-party/android-support-for-standalone-apps/v7/appcompat/res-unpacker.py
rename to ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py
index 145d09b02a7384..ddb65efb21927e 100644
--- a/ReactAndroid/src/main/third-party/android-support-for-standalone-apps/v7/appcompat/res-unpacker.py
+++ b/ReactAndroid/src/main/third-party/android/support/v7/appcompat-orig/aar-unpacker.py
@@ -5,7 +5,7 @@
 import tempfile
 import zipfile
 
-# Helper that unpacks the contents of the res folder of an .aar file
+# Helper that unpacks the contents of an .aar file
 # into given destination.
 
 @contextlib.contextmanager
@@ -16,5 +16,5 @@ def cleanup(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('res/'), z.namelist()))
-            shutil.move(os.path.join(temp_path, 'res'), sys.argv[2])
+            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/java/jsr-305/BUCK b/ReactAndroid/src/main/third-party/java/jsr-305/BUCK
index 3b5bb0b0e0b84f..0c02a2716a7a33 100644
--- a/ReactAndroid/src/main/third-party/java/jsr-305/BUCK
+++ b/ReactAndroid/src/main/third-party/java/jsr-305/BUCK
@@ -1,7 +1,7 @@
 prebuilt_jar(
     name = 'jsr-305',
     binary_jar = ':jsr305-binary-jar',
-    visibility = ['//ReactAndroid/...',],
+    visibility = ['PUBLIC'],
 )
 
 remote_file(
diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java
index 7f3d96f30b1a7c..97e9d6e5392a2c 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java
@@ -82,7 +82,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
     mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
     mReactContext.initializeWithInstance(mCatalystInstanceMock);
     DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
 
     UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
     when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
diff --git a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
index 8a681ccb51bfb4..dec87ebe3c6f0b 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
@@ -52,21 +52,21 @@ public void setup() {
   public void testCallMethodWithoutEnoughArgs() throws Exception {
     BaseJavaModule.NativeMethod regularMethod = mMethods.get("regularMethod");
     Mockito.stub(mArguments.size()).toReturn(1);
-    regularMethod.invoke(null, mArguments);
+    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, mArguments);
+    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, mArguments);
+    asyncMethod.invoke(null, null, mArguments);
   }
 
   private static class MethodsModule extends BaseJavaModule {
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
index 63707bae0ebfca..ce38961233cbca 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/network/NetworkingModuleTest.java
@@ -14,6 +14,7 @@
 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;
@@ -88,13 +89,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
     NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
 
     networkingModule.sendRequest(
-        "GET",
-        "http://somedomain/foo",
-        0,
-        JavaOnlyArray.of(),
-        null,
-        true,
-        0);
+      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());
@@ -108,7 +110,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
   public void testFailGetWithInvalidHeadersStruct() throws Exception {
     RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class);
     ReactApplicationContext context = mock(ReactApplicationContext.class);
-    when(context.getJSModule(any(Class.class))).thenReturn(emitter);
+    when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter);
 
     OkHttpClient httpClient = mock(OkHttpClient.class);
     NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient);
@@ -118,13 +120,14 @@ public void testFailGetWithInvalidHeadersStruct() throws Exception {
     mockEvents();
 
     networkingModule.sendRequest(
-        "GET",
-        "http://somedoman/foo",
-        0,
-        JavaOnlyArray.from(invalidHeaders),
-        null,
-        true,
-        0);
+      mock(ExecutorToken.class),
+      "GET",
+      "http://somedoman/foo",
+      0,
+      JavaOnlyArray.from(invalidHeaders),
+      null,
+      true,
+      0);
 
     verifyErrorEmit(emitter, 0);
   }
@@ -133,7 +136,7 @@ public void testFailGetWithInvalidHeadersStruct() throws Exception {
   public void testFailPostWithoutContentType() throws Exception {
     RCTDeviceEventEmitter emitter = mock(RCTDeviceEventEmitter.class);
     ReactApplicationContext context = mock(ReactApplicationContext.class);
-    when(context.getJSModule(any(Class.class))).thenReturn(emitter);
+    when(context.getJSModule(any(ExecutorToken.class), any(Class.class))).thenReturn(emitter);
 
     OkHttpClient httpClient = mock(OkHttpClient.class);
     NetworkingModule networkingModule = new NetworkingModule(context, "", httpClient);
@@ -144,13 +147,14 @@ public void testFailPostWithoutContentType() throws Exception {
     mockEvents();
 
     networkingModule.sendRequest(
-        "POST",
-        "http://somedomain/bar",
-        0,
-        JavaOnlyArray.of(),
-        body,
-        true,
-        0);
+      mock(ExecutorToken.class),
+      "POST",
+      "http://somedomain/bar",
+      0,
+      JavaOnlyArray.of(),
+      body,
+      true,
+      0);
 
     verifyErrorEmit(emitter, 0);
   }
@@ -184,7 +188,7 @@ public WritableMap answer(InvocationOnMock invocation) throws Throwable {
   }
 
   @Test
-  public void testSuccessfullPostRequest() throws Exception {
+  public void testSuccessfulPostRequest() throws Exception {
     OkHttpClient httpClient = mock(OkHttpClient.class);
     when(httpClient.newCall(any(Request.class))).thenAnswer(new Answer<Object>() {
           @Override
@@ -200,13 +204,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
     body.putString("string", "This is request body");
 
     networkingModule.sendRequest(
-        "POST",
-        "http://somedomain/bar",
-        0,
-        JavaOnlyArray.of(JavaOnlyArray.of("Content-Type", "text/plain")),
-        body,
-        true,
-        0);
+      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());
@@ -237,13 +242,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
         JavaOnlyArray.of("User-Agent", "React test agent/1.0"));
 
     networkingModule.sendRequest(
-        "GET",
-        "http://someurl/baz",
-        0,
-        JavaOnlyArray.from(headers),
-        null,
-        true,
-        0);
+      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();
@@ -284,13 +290,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
 
     NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
     networkingModule.sendRequest(
-        "POST",
-        "http://someurl/uploadFoo",
-        0,
-        new JavaOnlyArray(),
-        body,
-        true,
-        0);
+      mock(ExecutorToken.class),
+      "POST",
+      "http://someurl/uploadFoo",
+      0,
+      new JavaOnlyArray(),
+      body,
+      true,
+      0);
 
     // verify url, method, headers
     ArgumentCaptor<Request> argumentCaptor = ArgumentCaptor.forClass(Request.class);
@@ -342,13 +349,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
 
     NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
     networkingModule.sendRequest(
-        "POST",
-        "http://someurl/uploadFoo",
-        0,
-        JavaOnlyArray.from(headers),
-        body,
-        true,
-        0);
+      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);
@@ -437,13 +445,14 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
 
     NetworkingModule networkingModule = new NetworkingModule(null, "", httpClient);
     networkingModule.sendRequest(
-        "POST",
-        "http://someurl/uploadFoo",
-        0,
-        JavaOnlyArray.from(headers),
-        body,
-        true,
-        0);
+      mock(ExecutorToken.class),
+      "POST",
+      "http://someurl/uploadFoo",
+      0,
+      JavaOnlyArray.from(headers),
+      body,
+      true,
+      0);
 
     // verify RequestBodyPart for image
     PowerMockito.verifyStatic(times(1));
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
index 423480cda27524..e6185a8f02e863 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java
@@ -12,6 +12,7 @@
 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;
@@ -54,6 +55,7 @@ public class TimingModuleTest {
   private PostFrameCallbackHandler mPostFrameCallbackHandler;
   private long mCurrentTimeNs;
   private JSTimersExecution mJSTimersMock;
+  private ExecutorToken mExecutorTokenMock;
 
   @Rule
   public PowerMockRule rule = new PowerMockRule();
@@ -92,7 +94,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
 
     mTiming = new Timing(reactContext);
     mJSTimersMock = mock(JSTimersExecution.class);
-    when(reactInstance.getJSModule(JSTimersExecution.class)).thenReturn(mJSTimersMock);
+    mExecutorTokenMock = mock(ExecutorToken.class);
+    when(reactContext.getJSModule(mExecutorTokenMock, JSTimersExecution.class)).thenReturn(mJSTimersMock);
     mTiming.initialize();
   }
 
@@ -107,7 +110,7 @@ private void stepChoreographerFrame() {
   @Test
   public void testSimpleTimer() {
     mTiming.onHostResume();
-    mTiming.createTimer(1, 0, 0, false);
+    mTiming.createTimer(mExecutorTokenMock, 1, 1, 0, false);
     stepChoreographerFrame();
     verify(mJSTimersMock).callTimers(JavaOnlyArray.of(1));
     reset(mJSTimersMock);
@@ -117,7 +120,7 @@ public void testSimpleTimer() {
 
   @Test
   public void testSimpleRecurringTimer() {
-    mTiming.createTimer(100, 0, 0, true);
+    mTiming.createTimer(mExecutorTokenMock, 100, 1, 0, true);
     mTiming.onHostResume();
     stepChoreographerFrame();
     verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
@@ -130,13 +133,13 @@ public void testSimpleRecurringTimer() {
   @Test
   public void testCancelRecurringTimer() {
     mTiming.onHostResume();
-    mTiming.createTimer(105, 0, 0, true);
+    mTiming.createTimer(mExecutorTokenMock, 105, 1, 0, true);
 
     stepChoreographerFrame();
     verify(mJSTimersMock).callTimers(JavaOnlyArray.of(105));
 
     reset(mJSTimersMock);
-    mTiming.deleteTimer(105);
+    mTiming.deleteTimer(mExecutorTokenMock, 105);
     stepChoreographerFrame();
     verifyNoMoreInteractions(mJSTimersMock);
   }
@@ -144,7 +147,7 @@ public void testCancelRecurringTimer() {
   @Test
   public void testPausingAndResuming() {
     mTiming.onHostResume();
-    mTiming.createTimer(41, 0, 0, true);
+    mTiming.createTimer(mExecutorTokenMock, 41, 1, 0, true);
 
     stepChoreographerFrame();
     verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
@@ -160,6 +163,12 @@ public void testPausingAndResuming() {
     verify(mJSTimersMock).callTimers(JavaOnlyArray.of(41));
   }
 
+  @Test
+  public void testSetTimeoutZero() {
+    mTiming.createTimer(mExecutorTokenMock, 100, 0, 0, false);
+    verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
+  }
+
   private static class PostFrameCallbackHandler implements Answer<Void> {
 
     private Choreographer.FrameCallback mFrameCallback;
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java
index 5da10ac938e7df..f1944ec1225898 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/LayoutPropertyApplicatorTest.java
@@ -54,12 +54,14 @@ public class LayoutPropertyApplicatorTest {
 
   @Before
   public void setup() {
-    DisplayMetricsHolder.setDisplayMetrics(new DisplayMetrics());
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
+    DisplayMetricsHolder.setScreenDisplayMetrics(new DisplayMetrics());
   }
 
   @After
   public void teardown() {
-    DisplayMetricsHolder.setDisplayMetrics(null);
+    DisplayMetricsHolder.setWindowDisplayMetrics(null);
+    DisplayMetricsHolder.setScreenDisplayMetrics(null);
   }
 
   public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
@@ -309,7 +311,7 @@ public void testEnumerations() {
   public void testPropertiesResetToDefault() {
     DisplayMetrics displayMetrics = new DisplayMetrics();
     displayMetrics.density = 1.0f;
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
 
     LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode());
     ReactStylesDiffMap map = buildStyles(
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java
index 641e299920cb0c..bcc930fa63a801 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.java
@@ -147,7 +147,8 @@ public void testNativePropsIncludeCorrectTypes() {
     List<ViewManager> viewManagers = Arrays.<ViewManager>asList(new ViewManagerUnderTest());
     ReactApplicationContext reactContext = new ReactApplicationContext(RuntimeEnvironment.application);
     DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
     UIManagerModule uiManagerModule = new UIManagerModule(
         reactContext,
         viewManagers,
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java
index 2652422e9834c6..0f8b7ec9195e79 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.java
@@ -60,7 +60,8 @@ public void setUp() {
     mUIImplementation = mock(UIImplementation.class);
 
     DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
   }
 
   @Test
diff --git a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java
index d409752caf8a88..3b9cd770647bd1 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleTest.java
@@ -109,7 +109,8 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
     mReactContext.initializeWithInstance(mCatalystInstanceMock);
 
     DisplayMetrics displayMetrics = mReactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
 
     UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
     when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
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
index f74b4528e7ec62..a247f184753641 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ImageResizeModeTest.java
@@ -34,7 +34,7 @@ public void testImageResizeMode() {
         .isEqualTo(ScalingUtils.ScaleType.CENTER_CROP);
 
     assertThat(ImageResizeMode.toScaleType("contain"))
-        .isEqualTo(ScalingUtils.ScaleType.CENTER_INSIDE);
+        .isEqualTo(ScalingUtils.ScaleType.FIT_CENTER);
 
     assertThat(ImageResizeMode.toScaleType("cover"))
         .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
index c029d4653e32e4..186e4dca4fe9b8 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.java
@@ -59,12 +59,12 @@ public void setup() {
     mContext.initializeWithInstance(mCatalystInstanceMock);
     mThemeContext = new ThemedReactContext(mContext, mContext);
     Fresco.initialize(mContext);
-    DisplayMetricsHolder.setDisplayMetrics(new DisplayMetrics());
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
   }
 
   @After
   public void teardown() {
-    DisplayMetricsHolder.setDisplayMetrics(null);
+    DisplayMetricsHolder.setWindowDisplayMetrics(null);
   }
 
   public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
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
index f23128ab3dd55e..68734c4a502aec 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/text/ReactTextTest.java
@@ -373,7 +373,8 @@ private void executePendingChoreographerCallbacks() {
   public UIManagerModule getUIManagerModule() {
     ReactApplicationContext reactContext = ReactTestHelper.createCatalystContextForTest();
     DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
     List<ViewManager> viewManagers = Arrays.asList(
         new ViewManager[] {
             new ReactTextViewManager(),
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
index 08489bbc025727..202eabd46c40f3 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.java
@@ -60,7 +60,7 @@ public void setup() {
     mContext.initializeWithInstance(mCatalystInstanceMock);
     mThemedContext = new ThemedReactContext(mContext, mContext);
     mManager = new ReactTextInputManager();
-    DisplayMetricsHolder.setDisplayMetrics(new DisplayMetrics());
+    DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics());
   }
 
   public ReactStylesDiffMap buildStyles(Object... keysAndValues) {
@@ -289,26 +289,45 @@ public void testIncrementalInputTypeUpdates() {
   @Test
   public void testTextAlign() {
     ReactEditText view = mManager.createViewInstance(mThemedContext);
-    int gravity = view.getGravity();
-    assertThat(view.getGravity() & Gravity.BOTTOM).isNotEqualTo(Gravity.BOTTOM);
+    int defaultGravity = view.getGravity();
+    int defaultHorizontalGravity = defaultGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+    int defaultVerticalGravity = defaultGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+    // Theme
+    assertThat(view.getGravity()).isNotEqualTo(Gravity.NO_GRAVITY);
+
+    // TextAlign
+    mManager.updateProperties(view, buildStyles("textAlign", "left"));
+    assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.LEFT);
+    mManager.updateProperties(view, buildStyles("textAlign", "right"));
+    assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.RIGHT);
+    mManager.updateProperties(view, buildStyles("textAlign", "center"));
+    assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_HORIZONTAL);
+    mManager.updateProperties(view, buildStyles("textAlign", null));
+    assertThat(view.getGravity() & Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity);
 
+    // TextAlignVertical
+    mManager.updateProperties(view, buildStyles("textAlignVertical", "top"));
+    assertThat(view.getGravity() & Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP);
     mManager.updateProperties(view, buildStyles("textAlignVertical", "bottom"));
-    assertThat(view.getGravity() & Gravity.BOTTOM).isEqualTo(Gravity.BOTTOM);
+    assertThat(view.getGravity() & Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.BOTTOM);
+    mManager.updateProperties(view, buildStyles("textAlignVertical", "center"));
+    assertThat(view.getGravity() & Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_VERTICAL);
+    mManager.updateProperties(view, buildStyles("textAlignVertical", null));
+    assertThat(view.getGravity() & Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(defaultVerticalGravity);
 
+    // TextAlign + TextAlignVertical
     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);
-
+      view,
+      buildStyles("textAlign", "center", "textAlignVertical", "center"));
+    assertThat(view.getGravity()).isEqualTo(Gravity.CENTER);
     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);
+      view,
+      buildStyles("textAlign", "right", "textAlignVertical", "bottom"));
+    assertThat(view.getGravity()).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM);
+    mManager.updateProperties(
+      view,
+      buildStyles("textAlign", null, "textAlignVertical", null));
+    assertThat(view.getGravity()).isEqualTo(defaultGravity);
   }
 }
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
index ef89b7df002954..630c80a723a9b3 100644
--- a/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java
+++ b/ReactAndroid/src/test/java/com/facebook/react/views/textinput/TextInputTest.java
@@ -183,7 +183,8 @@ public UIManagerModule getUIManagerModule() {
             new ReactTextInputManager(),
         });
     DisplayMetrics displayMetrics = reactContext.getResources().getDisplayMetrics();
-    DisplayMetricsHolder.setDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics);
+    DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics);
     UIManagerModule uiManagerModule = new UIManagerModule(
         reactContext,
         viewManagers,
diff --git a/Releases.md b/Releases.md
index 06933c78924eba..649b5f177e60ec 100644
--- a/Releases.md
+++ b/Releases.md
@@ -1,16 +1,16 @@
 The list of releases with notes can be found at:
 https://github.com/facebook/react-native/releases
 
-Future releases:
+Release schedule:
 
-- **0.20 branch cut**, 0.20.0-rc - **week of Feb 1**
-- 0.20.0 - Feb 15
 - **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 7
-- **0.23 branch cut**, 0.23.0-rc - **week of Mar 7**
-- 0.23.0 - Mar 21
+- 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
@@ -24,7 +24,7 @@ To cut a release branch and check that everything works, you'll need Mac OS with
 Run:
 
     cd react-native
-    ./scripts/release.sh 0.19   # Replace 0.19 with the version you're cutting the branch for :)
+    ./scripts/release.sh version_you_are_releasing # e.g. ./scripts/release.sh 0.22
 
 #### Check that everything works
 
@@ -32,20 +32,99 @@ Make absolutely sure a basic iOS and Android workflow works on the release branc
   
 #### Push to github
 
-  - Check git history, the last commit should be "[0.19-rc] Bump version numbers" (with the correct version)
-  - `git push origin 0.version_you_are_releasing-stable`
-  
+  - 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 voluteers can write 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
+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.
 
-## Do a release
+```
+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
+```
 
-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.
+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
 
-## Track bug reports from the community during the following two weeks and make sure they get fixed
+Uncheck the box "This is a pre-release" and publish the notes.
 
-A good way to do this is to create a github issue and post about it so people can report bugs: https://github.com/facebook/react-native/issues/5201
+Tweet about it! :) ([example tweet](https://twitter.com/grabbou/status/701510554758856704))
diff --git a/babel-preset/configs/hmr.js b/babel-preset/configs/hmr.js
index 2a4bb9a93703ef..e88e2c67d5be77 100644
--- a/babel-preset/configs/hmr.js
+++ b/babel-preset/configs/hmr.js
@@ -8,16 +8,23 @@
  */
 'use strict';
 
+var path = require('path');
 var resolvePlugins = require('../lib/resolvePlugins');
 
-module.exports = function(options) {
+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: 'react-transform-hmr/lib/index.js',
+            transform: transform,
             imports: ['React'],
             locals: ['module'],
           }]
diff --git a/babel-preset/package.json b/babel-preset/package.json
index 0ab0579b1532e7..c664d898895bc9 100644
--- a/babel-preset/package.json
+++ b/babel-preset/package.json
@@ -1,6 +1,6 @@
 {
   "name": "babel-preset-react-native",
-  "version": "1.4.0",
+  "version": "1.5.1",
   "description": "Babel preset for React Native applications",
   "main": "index.js",
   "repository": "https://github.com/facebook/react-native/tree/master/babel-preset",
@@ -9,37 +9,37 @@
     "preset",
     "react-native"
   ],
-  "license" : "BSD-3-Clause",
+  "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-syntax-async-functions": "^6.5.0",
+    "babel-plugin-syntax-class-properties": "^6.5.0",
+    "babel-plugin-syntax-flow": "^6.5.0",
+    "babel-plugin-syntax-jsx": "^6.5.0",
+    "babel-plugin-syntax-trailing-function-commas": "^6.5.0",
+    "babel-plugin-transform-class-properties": "^6.5.0",
+    "babel-plugin-transform-es2015-arrow-functions": "^6.5.0",
+    "babel-plugin-transform-es2015-block-scoping": "^6.5.0",
+    "babel-plugin-transform-es2015-classes": "^6.5.0",
+    "babel-plugin-transform-es2015-computed-properties": "^6.5.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",
+    "babel-plugin-transform-es2015-destructuring": "^6.5.0",
+    "babel-plugin-transform-es2015-for-of": "^6.5.0",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.5.0",
+    "babel-plugin-transform-es2015-parameters": "^6.5.0",
+    "babel-plugin-transform-es2015-shorthand-properties": "^6.5.0",
+    "babel-plugin-transform-es2015-spread": "^6.5.0",
+    "babel-plugin-transform-es2015-template-literals": "^6.5.0",
+    "babel-plugin-transform-flow-strip-types": "^6.5.0",
+    "babel-plugin-transform-object-assign": "^6.5.0",
+    "babel-plugin-transform-object-rest-spread": "^6.5.0",
+    "babel-plugin-transform-react-display-name": "^6.5.0",
+    "babel-plugin-transform-react-jsx": "^6.5.0",
+    "babel-plugin-transform-regenerator": "^6.5.0",
     "react-transform-hmr": "^1.0.2"
   }
 }
diff --git a/bots/IssueCommands.txt b/bots/IssueCommands.txt
new file mode 100644
index 00000000000000..9a854da6c5e67c
--- /dev/null
+++ b/bots/IssueCommands.txt
@@ -0,0 +1,33 @@
+React Native GitHub Issue Task Force: astreet, bestander, brentvatne, browniefed, cancan101, chirag04, christopherdro, corbt, cosmith, davidaurelio, dmmiller, dsibiski, foghina, frantic, grabbou, gre, ide, janicduplessis, javache, jaygarcia, jsierles, kmagiera, kmagiera, Kureev, lelandrichardson, martinbigio, mkonicek, satya164, skevy, vjeux
+
+@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 close
+comment {author} tells me to close this issue. If you think it should still be opened let us know why.
+close
+
+@facebook-github-bot reopen
+comment Okay, reopening this issue.
+reopen
diff --git a/circle.yml b/circle.yml
index b0ea6e9902b3d3..7f95f04a1b7ed9 100644
--- a/circle.yml
+++ b/circle.yml
@@ -4,7 +4,7 @@ general:
       - gh-pages # list of branches to ignore
 machine:
   node:
-    version: 5.1.0
+    version: 5.6.0
   environment:
     PATH: "~/$CIRCLE_PROJECT_REPONAME/gradle-2.9/bin:$PATH"
     TERM: "dumb"
@@ -13,51 +13,78 @@ machine:
 
 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/...
-  # using npm@3 because of problems with shrink-wrapped optional deps installs on linux
-  - npm install -g npm@3.2
-  - source scripts/circle-ci-android-setup.sh && getAndroidSDK
-  - ./gradlew :ReactAndroid:downloadBoost :ReactAndroid:downloadDoubleConversion :ReactAndroid:downloadFolly :ReactAndroid:downloadGlog
+    # 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"
+    - "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
-    # assemble done separately because it requires quite a lot of memory and also gives time for emulator to load
-    - ./gradlew :ReactAndroid:assembleDebug -PdisablePreDex -Pjobs=1:
-            timeout: 360
     - source scripts/circle-ci-android-setup.sh && waitForAVD
+
   override:
-    # buck tests
-    - buck/bin/buck test ReactAndroid/src/test/... --config build.threads=1
+    # 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
-    # temp, we can't run instrumentation tests yet
-    - buck/bin/buck build ReactAndroid/src/androidTest/java/com/facebook/react/tests
 
     # unit tests
-    - ./gradlew :ReactAndroid:testDebugUnitTest -PdisablePreDex
+    - 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
-    # run tests on the emulator
+    # 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
+
+    # Publish to Sinopia, create a new app using 'react-native init' and check the packager starts
+    - ./scripts/e2e-test.sh --packager
+
+    # 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/AndroidBuildingFromSource.md b/docs/AndroidBuildingFromSource.md
index 6a7cd37f1b0a44..5c31274640f702 100644
--- a/docs/AndroidBuildingFromSource.md
+++ b/docs/AndroidBuildingFromSource.md
@@ -76,7 +76,6 @@ project(':ReactAndroid').projectDir = new File(
 ...
 ```
 
-
 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
@@ -92,7 +91,6 @@ dependencies {
 ...
 ```
 
-
 #### 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'`.
@@ -105,6 +103,11 @@ compile(project(':react-native-custom-module')) {
 }
 ```
 
+## 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
 
@@ -117,3 +120,7 @@ gradle.projectsLoaded {
     }
 }
 ```
+
+## 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 321bb0c46bfe7a..3dc51532dc38eb 100644
--- a/docs/AndroidUIPerformance.md
+++ b/docs/AndroidUIPerformance.md
@@ -43,7 +43,7 @@ Once the trace starts collecting, perform the animation or interaction you care
 
 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 (preferably Chrome), you should see some
 
 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 af8d0031ca1ff9..1c570a952e41e4 100644
--- a/docs/Animations.md
+++ b/docs/Animations.md
@@ -267,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({
@@ -344,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],
@@ -376,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
@@ -395,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
@@ -440,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}>
@@ -461,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
@@ -497,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>
@@ -516,24 +516,23 @@ 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 React = require('react-native');
-var { Dimensions } = React;
+import { Dimensions } from 'react-native';
 var SCREEN_WIDTH = Dimensions.get('window').width;
 var BaseConfig = Navigator.SceneConfigs.FloatFromRight;
 
diff --git a/docs/CommunicationIOS.md b/docs/CommunicationIOS.md
index 3e430636f05b2c..ebc08328dd2754 100644
--- a/docs/CommunicationIOS.md
+++ b/docs/CommunicationIOS.md
@@ -7,7 +7,7 @@ permalink: docs/communication-ios.html
 next: native-modules-android
 ---
 
-In [Integrating with Existing Apps guide](http://facebook.github.io/react-native/docs/embedded-app-ios.html) and [Native UI Components guide](https://facebook.github.io/react-native/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.
+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
 
@@ -39,18 +39,17 @@ RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
 ```
 'use strict';
 
-var React = require('react-native');
-  var {
+import React, {
   View,
   Image
-} = React;
+} from 'react-native';
 
 class ImageBrowserApp extends React.Component {
-  renderImage: function(imgURI) {
+  renderImage(imgURI) {
     return (
       <Image source={{uri: imgURI}} />
     );
-  },
+  }
   render() {
     return (
       <View>
@@ -80,13 +79,13 @@ There is no way to update only a few properties at a time. We suggest that you b
 > 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](https://facebook.github.io/react-native/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.
+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](https://facebook.github.io/react-native/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.
+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)
 
@@ -96,19 +95,19 @@ React Native enables you to perform cross-language function calls. You can execu
 
 ### Calling React Native functions from native (events)
 
-Events are described in detail in [this article](http://facebook.github.io/react-native/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 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. 
+* 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](https://facebook.github.io/react-native/docs/native-modules-ios.html#content).
+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.
 
@@ -125,7 +124,7 @@ When integrating native and React Native, we also need a way to consolidate two
 
 ### Layout of a native component embedded in React Native
 
-This case is covered in [this article](https://facebook.github.io/react-native/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.
+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
 
diff --git a/docs/Debugging.md b/docs/Debugging.md
index 3e7793aabdb3ae..6c70ff22ce1ab2 100644
--- a/docs/Debugging.md
+++ b/docs/Debugging.md
@@ -43,9 +43,6 @@ 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 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.
 
-#### 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).
-
 ### 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 eab90e85a87541..802c86848f6787 100644
--- a/docs/DevelopmentSetupAndroid.md
+++ b/docs/DevelopmentSetupAndroid.md
@@ -50,12 +50,12 @@ React Native Android use [gradle](https://docs.gradle.org) as a build system. We
 ### 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:
-  * Android SDK Build-tools version 23.0.1
+  * Android SDK Build-tools version 23.0.**1**
   * Android 6.0 (API 23)
-  * Android Support Repository
+  * Local Maven repository for Support Libraries (this is called Android Support Repository in older versions)
 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
 
@@ -72,10 +72,11 @@ 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 (or install [Frappé](http://getfrappe.com))
 
@@ -99,3 +100,14 @@ Then restart the emulator and when it runs you can just do `react-native run-and
 ### 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.
+
+### Troubleshooting
+
+In case you encounter
+
+```
+Execution failed for task ':app:installDebug'.
+  com.android.builder.testing.api.DeviceException: com.android.ddmlib.ShellCommandUnresponsiveException
+```
+
+try downgrading your Gradle version to 1.2.3 in `<project-name>/android/build.gradle` (https://github.com/facebook/react-native/issues/2720)
diff --git a/docs/DirectManipulation.md b/docs/DirectManipulation.md
index a47a39fe2cbdb1..3c6fd46ed930d6 100644
--- a/docs/DirectManipulation.md
+++ b/docs/DirectManipulation.md
@@ -156,7 +156,7 @@ 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`.
diff --git a/docs/EmbeddedAppAndroid.md b/docs/EmbeddedAppAndroid.md
index 88122d52ac86c0..13d573d1f38a6a 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.17.+'
+    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`:
 
@@ -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 2082070ba16f1e..dbdb5e31d9c2c9 100644
--- a/docs/EmbeddedAppIOS.md
+++ b/docs/EmbeddedAppIOS.md
@@ -66,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: {
@@ -98,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 :)).
 
@@ -186,7 +185,7 @@ If you don't do this, you will see the error - `Could not connect to development
 
 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 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.
 
diff --git a/docs/GestureResponderSystem.md b/docs/GestureResponderSystem.md
index 2c83274350c755..b7c8dbc40e19ee 100644
--- a/docs/GestureResponderSystem.md
+++ b/docs/GestureResponderSystem.md
@@ -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 cc83191d1140bc..245ecc4c5a34ec 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
@@ -25,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`
@@ -41,7 +49,7 @@ _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](http://facebook.github.io/react-native/docs/running-on-device-ios.html#content)._
+_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:**
 
@@ -51,11 +59,11 @@ _Note: If you are using an iOS device, see the [Running on iOS Device page](http
 - 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 an Android device, see the [Running on Android 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 00000000000000..bc9ee326df6735
--- /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-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 12ab24fe2fdf79..89c8d44638b5a4 100644
--- a/docs/Images.md
+++ b/docs/Images.md
@@ -35,6 +35,8 @@ And `button.js` code contains
 
 Packager will bundle and serve the image corresponding to device's screen density, e.g. on iPhone 5s `check@2x.png` will be used, on Nexus 5 – `check@3x.png`. If there is no image matching the screen density, the closest best option will be selected.
 
+On Windows, you might need to restart the packager if you add new images to your project.
+
 Here are some benefits that you get:
 
 1. Same system on iOS and Android.
@@ -90,7 +92,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/IssueGuidelines.md b/docs/IssueGuidelines.md
new file mode 100644
index 00000000000000..3745048c1be8b1
--- /dev/null
+++ b/docs/IssueGuidelines.md
@@ -0,0 +1,46 @@
+Here are some tips on how to manage GitHub issues efficiently:
+
+### 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: [#6378](https://github.com/facebook/react-native/issues/6378), [#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/docs/KnownIssues.md b/docs/KnownIssues.md
index a3deb731978395..61db2d86553d38 100644
--- a/docs/KnownIssues.md
+++ b/docs/KnownIssues.md
@@ -15,30 +15,22 @@ 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
 
-```
-Maps
-Modal
-Spinner (http://developer.android.com/guide/topics/ui/controls/spinner.html)
-Slider (known as SeekBar)
-```
+- 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
 
-```
-Camera Roll
-Media
-PushNotificationIOS
-```
+- 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
 
@@ -62,7 +54,7 @@ Another issue with `overflow: 'hidden'` on Android: a view is not clipped by the
 
 ### View shadows
 
-The `shadow*` [view styles](/react-native/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.
+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
 
@@ -87,4 +79,4 @@ Try running `react-native init` with `--verbose` and see [#2797](https://github.
 
 ### Text Input Border
 
-The text input has by default a border at the bottom of its view. This border has its padding set by the background image provided by the system, and it cannot be changed. Solutions to avoid this is to either not set height explicitly, case in which the system will take care of displaying the border in the correct position, or to not display the border by setting underlineColor to transparent.
+The text input has by default a border at the bottom of its view. This border has its padding set by the background image provided by the system, and it cannot be changed. Solutions to avoid this is to either not set height explicitly, case in which the system will take care of displaying the border in the correct position, or to not display the border by setting underlineColorAndroid to transparent.
diff --git a/docs/LinkingLibraries.md b/docs/LinkingLibraries.md
index 8fc6fe6e582b34..0ce0849531a481 100644
--- a/docs/LinkingLibraries.md
+++ b/docs/LinkingLibraries.md
@@ -65,7 +65,7 @@ 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
 
@@ -73,7 +73,7 @@ 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
 
@@ -97,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 65bc9e851eb109..b1cf5f82dbab8a 100644
--- a/docs/LinuxWindowsSupport.md
+++ b/docs/LinuxWindowsSupport.md
@@ -22,6 +22,6 @@ On Windows the packager won't be started automatically when you run `react-nativ
     cd MyAwesomeApp
     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).
+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/5fa33f3d07f8595a188f6fe04d6168a6ede1e721/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 ca38d31af19924..3c32591c4579ea 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 argument 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](native-modules-android.html), 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 fffb06ecadcfe7..e78d8671476697 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() {
@@ -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').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 bd86b8217f8c88..5727c22606ad45 100644
--- a/docs/NativeModulesAndroid.md
+++ b/docs/NativeModulesAndroid.md
@@ -130,14 +130,14 @@ To make it simpler to access your new functionality from JavaScript, it is commo
  * 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 other JavaScript file you can call the method like this:
 
 ```js
-var ToastAndroid = require('./ToastAndroid');
+import ToastAndroid from './ToastAndroid';
 
 ToastAndroid.show('Awesome', ToastAndroid.SHORT);
 ```
@@ -275,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 = {
@@ -401,7 +401,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule implements Act
 
 ### 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 `ActivityEventListener`. Then, you need to register a listener in the module's constructor,
+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);
diff --git a/docs/NativeModulesIOS.md b/docs/NativeModulesIOS.md
index 41266aad7b34e6..00c03ce5938f62 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');
 ```
 
@@ -58,7 +59,7 @@ CalendarManager.addEvent('Birthday Party', '4 Privet Drive, Surrey');
 >
 > The name of the method exported to JavaScript is the native method's name up to the first colon. React Native also defines a macro called `RCT_REMAP_METHOD()` to specify the JavaScript method's name. This is useful when multiple native methods are the same up to the first colon and would have conflicting JavaScript names.
 
-The return type of bridge methods is always `void`. React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events (see below).
+The CalendarManager module is instantiated on the Objective-C side using a [CalendarManager new] call. The return type of bridge methods is always `void`. React Native bridge is asynchronous, so the only way to pass a result to JavaScript is by using callbacks or emitting events (see below).
 
 ## Argument Types
 
@@ -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: '...'
 })
 ```
@@ -188,7 +189,8 @@ RCT_REMAP_METHOD(findEvents,
   if (events) {
     resolve(events);
   } else {
-    reject(error);
+    NSError *error = ...
+    reject(@"no_events", @"There were no events", error);
   }
 }
 ```
@@ -286,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
 ```
@@ -333,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',
diff --git a/docs/NavigatorComparison.md b/docs/NavigatorComparison.md
index cc6c5c6c8bbdcf..80262d1a358d86 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
diff --git a/docs/Network.md b/docs/Network.md
index 1997e47f326504..498a0df21cfec9 100644
--- a/docs/Network.md
+++ b/docs/Network.md
@@ -41,29 +41,34 @@ fetch('https://mywebsite.com/endpoint/', {
 
 1.  Using `then` and `catch` in synchronous code:
 
-```js
-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:
 
-```js
-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
 
@@ -119,4 +124,4 @@ 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.
+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 [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 1dfbb544445cc4..89c02fd9fe833f 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
@@ -257,7 +257,6 @@ where the modal was opened from. See the Animations guide for more
 information about how to use LayoutAnimation.
 
 Caveats:
-- LayoutAnimation only exists on iOS.
 - LayoutAnimation only works for fire-and-forget animations ("static"
   animations) -- if it must be be interruptible, you will need to use
 Animated.
diff --git a/docs/PlatformSpecificInformation.md b/docs/PlatformSpecificInformation.md
index 38345e541ca883..03aa48bf1acc2c 100644
--- a/docs/PlatformSpecificInformation.md
+++ b/docs/PlatformSpecificInformation.md
@@ -7,11 +7,11 @@ 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: 
+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/   
+/common/components/
+/android/components/
 /ios/components/
 ```
 
@@ -25,7 +25,7 @@ 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. 
+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:
 
@@ -37,7 +37,7 @@ 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
-var BigButton = require('./components/BigButton');
+import BigButton from './components/BigButton';
 ```
 
 React Native will import the correct component for the running platform.
@@ -46,7 +46,7 @@ React Native will import the correct component for the running platform.
 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 { Platform } = React;
 
 var styles = StyleSheet.create({
   height: (Platform.OS === 'ios') ? 200 : 100,
@@ -61,7 +61,7 @@ On Android, the Platform module can be also used to detect which is the version
 ```javascript
 var {Platform} = React;
 
-if(Platform.Version === '5.0'){
+if(Platform.Version === 21){
   console.log('Running on Lollipop!');
 }
-```
\ No newline at end of file
+```
diff --git a/docs/PullRequestGuidelines.md b/docs/PullRequestGuidelines.md
new file mode 100644
index 00000000000000..3e8b420cac01a5
--- /dev/null
+++ b/docs/PullRequestGuidelines.md
@@ -0,0 +1,4 @@
+Here are some tips on reviewing pull requests:
+
+- Does the PR miss info required in the [Pull request template](https://github.com/facebook/react-native/blob/master/PULL_REQUEST_TEMPLATE.md)? Ask for it and link to the template. Add labels 'Needs revision' and 'Needs response from author'. Examples: [#6395](https://github.com/facebook/react-native/pull/6395), [#5637](https://github.com/facebook/react-native/pull/5637).
+- Does the code style match the [Style guide](https://github.com/facebook/react-native/blob/master/CONTRIBUTING.md#style-guide), especially consistency with the rest of the codebase? If not, link to the style guide and add the label 'Needs revision'.
diff --git a/docs/RunningOnDeviceIOS.md b/docs/RunningOnDeviceIOS.md
index c90f3b04ed887a..619b048fcea22d 100644
--- a/docs/RunningOnDeviceIOS.md
+++ b/docs/RunningOnDeviceIOS.md
@@ -31,7 +31,7 @@ When you run your app on device, we pack all the JavaScript code and the images
 
 ## 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
 
diff --git a/docs/SignedAPKAndroid.md b/docs/SignedAPKAndroid.md
index d35e95d82d9da6..e4ecc0d51935d0 100644
--- a/docs/SignedAPKAndroid.md
+++ b/docs/SignedAPKAndroid.md
@@ -35,7 +35,7 @@ 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
 
@@ -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:
+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.
 
-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
-```
-
-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`:
 
 ```gradle
-...
-android {
-    ...
-    buildTypes {
-        release {
-            ...
-            minifyEnabled true
-        }
-    }
-}
-...
+/**
+ * Run Proguard to shrink the Java bytecode in release builds.
+ */
+def enableProguardInReleaseBuilds = true
 ```
diff --git a/docs/Style.md b/docs/Style.md
index 9f7229c94e2eff..fb6cb7a6dc077a 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/Text.md b/docs/Text.md
index a40f2d97bd7bdf..4c48ef1caac375 100644
--- a/docs/Text.md
+++ b/docs/Text.md
@@ -63,7 +63,7 @@ When the browser is trying to render a text node, it's going to go all the way u
 In React Native, we are more strict about it: **you must wrap all the text nodes inside of a `<Text>` component**; you cannot have a text node directly under a `<View>`.
 
 ```javascript
-// BAD: will fatal, can't have a text node as child of a <View>
+// BAD: will raise exception, can't have a text node as child of a <View>
 <View>
   Some text
 </View>
diff --git a/docs/Timers.md b/docs/Timers.md
index d4a9396deceed7..b90180d429e793 100644
--- a/docs/Timers.md
+++ b/docs/Timers.md
@@ -61,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 6db929a2c2782b..875cfa2c85307a 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 b5dc1d16c084aa..ec5a4d72bfc56b 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
 
@@ -111,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>
 
 
@@ -189,8 +197,8 @@ 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
@@ -282,8 +290,8 @@ Now modify the render function to render a loading view if we don't have any mov
 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
@@ -367,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.
diff --git a/docs/Upgrading.md b/docs/Upgrading.md
index 51b46bfe737e54..4034b4f731cfbf 100644
--- a/docs/Upgrading.md
+++ b/docs/Upgrading.md
@@ -50,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 488c16f1adcbf8..aec0c770fe9195 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>
diff --git a/flow/Map.js b/flow/Map.js
new file mode 100644
index 00000000000000..73c4e12caba472
--- /dev/null
+++ b/flow/Map.js
@@ -0,0 +1,31 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+// @nolint
+
+// These annotations are copy/pasted from the built-in Flow definitions for
+// Native Map.
+
+declare module "Map" {
+  // Use the name "MapPolyfill" so that we don't get confusing error
+  // messages about "Using Map instead of Map".
+  declare class MapPolyfill<K, V> {
+    @@iterator(): Iterator<[K, V]>;
+    constructor<Key, Value>(_: void): MapPolyfill<Key, Value>;
+    constructor<Key, Value>(_: null): MapPolyfill<Key, Value>;
+    constructor<Key, Value>(iterable: Array<[Key, Value]>): MapPolyfill<Key, Value>;
+    constructor<Key, Value>(iterable: Iterable<[Key, Value]>): MapPolyfill<Key, Value>;
+    clear(): void;
+    delete(key: K): boolean;
+    entries(): Iterator<[K, V]>;
+    forEach(callbackfn: (value: V, index: K, map: MapPolyfill<K, V>) => mixed, thisArg?: any): void;
+    get(key: K): V | void;
+    has(key: K): boolean;
+    keys(): Iterator<K>;
+    set(key: K, value: V): MapPolyfill<K, V>;
+    size: number;
+    values(): Iterator<V>;
+  }
+
+  // Don't "declare class exports" directly, otherwise in error messages our
+  // show up as "exports" instead of "Map" or "MapPolyfill".
+  declare var exports: typeof MapPolyfill;
+}
diff --git a/flow/Set.js b/flow/Set.js
new file mode 100644
index 00000000000000..7ca7dcd2dc1dd3
--- /dev/null
+++ b/flow/Set.js
@@ -0,0 +1,26 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+// @nolint
+
+// These annotations are copy/pasted from the built-in Flow definitions for
+// Native Set.
+
+declare module "Set" {
+  // Use the name "SetPolyfill" so that we don't get confusing error
+  // messages about "Using Set instead of Set".
+  declare class SetPolyfill<T> {
+    @@iterator(): Iterator<T>;
+    add(value: T): SetPolyfill<T>;
+    clear(): void;
+    delete(value: T): boolean;
+    entries(): Iterator<[T, T]>;
+    forEach(callbackfn: (value: T, index: T, set: SetPolyfill<T>) => mixed, thisArg?: any): void;
+    has(value: T): boolean;
+    keys(): Iterator<T>;
+    size: number;
+    values(): Iterator<T>;
+  }
+
+  // Don't "declare class exports" directly, otherwise in error messages our
+  // show up as "exports" instead of "Set" or "SetPolyfill".
+  declare var exports: typeof SetPolyfill;
+}
diff --git a/flow/react.js b/flow/react.js
new file mode 100644
index 00000000000000..f54cd42509e209
--- /dev/null
+++ b/flow/react.js
@@ -0,0 +1,3 @@
+// temporary patches for React.Component and React.Element
+declare var ReactComponent: typeof React$Component;
+declare var ReactElement: typeof React$Element;
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9881fc8cf8b3f9..0c42144e803565 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-2.11-all.zip
diff --git a/keystores/BUCK b/keystores/BUCK
new file mode 100644
index 00000000000000..15da20e6b92c3f
--- /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 00000000000000..364e105ed39fbf
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 00000000000000..121bfb49f0dfda
--- /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/buildBundle.js b/local-cli/bundle/buildBundle.js
index 5b75ca3fb68706..10130e5a8c5f21 100644
--- a/local-cli/bundle/buildBundle.js
+++ b/local-cli/bundle/buildBundle.js
@@ -28,7 +28,6 @@ function buildBundle(args, config, output = outputBundle) {
       getTransformOptionsModulePath: config.getTransformOptionsModulePath,
       transformModulePath: args.transformer,
       verbose: args.verbose,
-      disableInternalTransforms: true,
     };
 
     const requestOpts = {
diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js
index 6f40e5baccde73..e4247982f5404f 100644
--- a/local-cli/bundle/output/bundle.js
+++ b/local-cli/bundle/output/bundle.js
@@ -17,14 +17,10 @@ function buildBundle(packagerClient, requestOptions) {
 }
 
 function createCodeWithMap(bundle, dev) {
-  if (!dev) {
-    return bundle.getMinifiedSourceAndMap(dev);
-  } else {
-    return {
-      code: bundle.getSource({dev}),
-      map: JSON.stringify(bundle.getSourceMap({dev})),
-    };
-  }
+  return {
+    code: bundle.getSource({dev}),
+    map: JSON.stringify(bundle.getSourceMap({dev})),
+  };
 }
 
 function saveBundleAndMap(bundle, options, log) {
@@ -53,7 +49,6 @@ function saveBundleAndMap(bundle, options, log) {
   }
 }
 
-
 exports.build = buildBundle;
 exports.save = saveBundleAndMap;
 exports.formatName = 'bundle';
diff --git a/local-cli/cli.js b/local-cli/cli.js
index ace3251d764c1d..1e22cb29e6c3f0 100644
--- a/local-cli/cli.js
+++ b/local-cli/cli.js
@@ -71,7 +71,7 @@ function run() {
     printUsage();
   }
 
-  const setupEnvScript = /^win/.test(process.platform)
+  var setupEnvScript = /^win/.test(process.platform)
     ? 'setup_env.bat'
     : 'setup_env.sh';
   childProcess.execFileSync(path.join(__dirname, setupEnvScript));
diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js
index 8ada423cf26287..adc1e889701176 100644
--- a/local-cli/dependencies/dependencies.js
+++ b/local-cli/dependencies/dependencies.js
@@ -63,7 +63,6 @@ function _dependencies(argv, config, resolve, reject) {
     getTransformOptionsModulePath: config.getTransformOptionsModulePath,
     transformModulePath: args.transformer,
     verbose: config.verbose,
-    disableInternalTransforms: true,
   };
 
   const relativePath = packageOpts.projectRoots.map(root =>
diff --git a/local-cli/generator-android/templates/package/MainActivity.java b/local-cli/generator-android/templates/package/MainActivity.java
index 552a4d2a59de9f..283dffc92e17f8 100644
--- a/local-cli/generator-android/templates/package/MainActivity.java
+++ b/local-cli/generator-android/templates/package/MainActivity.java
@@ -28,9 +28,9 @@ protected boolean getUseDeveloperSupport() {
     }
 
     /**
-    * A list of packages used by the app. If the app uses additional views
-    * or modules besides the default ones, add more packages here.
-    */
+     * 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
     protected List<ReactPackage> getPackages() {
         return Arrays.<ReactPackage>asList(
diff --git a/local-cli/generator-android/templates/src/app/react.gradle b/local-cli/generator-android/templates/src/app/react.gradle
index 11a4f8b871de71..348fb127368b9e 100644
--- a/local-cli/generator-android/templates/src/app/react.gradle
+++ b/local-cli/generator-android/templates/src/app/react.gradle
@@ -41,7 +41,7 @@ gradle.projectsEvaluated {
             def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
                     file("$buildDir/intermediates/assets/${targetPath}")
 
-            def resourcesDirConfigName = "jsBundleDir${targetName}"
+            def resourcesDirConfigName = "resourcesDir${targetName}"
             def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
                     file("$buildDir/intermediates/res/merged/${targetPath}")
             def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
diff --git a/local-cli/generator-ios/templates/app/AppDelegate.m b/local-cli/generator-ios/templates/app/AppDelegate.m
index 6cf213bd4cbdde..909092b9cc757a 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/templates/index.android.js b/local-cli/generator/templates/index.android.js
index ce565390a48f5d..e9baac5cc51bbb 100644
--- a/local-cli/generator/templates/index.android.js
+++ b/local-cli/generator/templates/index.android.js
@@ -2,7 +2,7 @@
  * Sample React Native App
  * https://github.com/facebook/react-native
  */
-'use strict';
+
 import React, {
   AppRegistry,
   Component,
diff --git a/local-cli/generator/templates/index.ios.js b/local-cli/generator/templates/index.ios.js
index de44bc2534c119..746044afb4864f 100644
--- a/local-cli/generator/templates/index.ios.js
+++ b/local-cli/generator/templates/index.ios.js
@@ -2,7 +2,7 @@
  * Sample React Native App
  * https://github.com/facebook/react-native
  */
-'use strict';
+
 import React, {
   AppRegistry,
   Component,
diff --git a/local-cli/runAndroid/adb.js b/local-cli/runAndroid/adb.js
new file mode 100644
index 00000000000000..8128c866dd72d1
--- /dev/null
+++ b/local-cli/runAndroid/adb.js
@@ -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.
+ * 
+ * @flow
+ */
+
+const child_process = require('child_process');
+
+/**
+ * Parses the output of the 'adb devices' command
+ */
+function parseDevicesResult(result: string): Array<string> {
+  if (!result) {
+    return [];
+  }
+  
+  const devices = [];
+  const lines = result.trim().split(/\r?\n/);
+ 
+  for (let i=0; i < lines.length; i++) {
+    let words = lines[i].split(/[ ,\t]+/).filter((w) => w !== '');
+    
+    if (words[1] === 'device') {
+      devices.push(words[0]);
+    }
+  }
+  return devices;
+}
+
+/**
+ * Executes the commands needed to get a list of devices from ADB
+ */
+function getDevices(): Array<string> {
+  try {
+    const devicesResult = child_process.execSync('adb devices');
+    return parseDevicesResult(devicesResult.toString());
+  } catch (e) {
+    return [];
+  }
+  
+  
+}
+
+module.exports = {
+  parseDevicesResult: parseDevicesResult,
+  getDevices: getDevices
+};
\ No newline at end of file
diff --git a/local-cli/runAndroid/runAndroid.js b/local-cli/runAndroid/runAndroid.js
index fa2836998c1523..e2552788b755cb 100644
--- a/local-cli/runAndroid/runAndroid.js
+++ b/local-cli/runAndroid/runAndroid.js
@@ -15,6 +15,7 @@ const path = require('path');
 const parseCommandLine = require('../util/parseCommandLine');
 const isPackagerRunning = require('../util/isPackagerRunning');
 const Promise = require('promise');
+const adb = require('./adb');
 
 /**
  * Starts the app on a connected Android emulator or device.
@@ -30,21 +31,31 @@ 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)',
+  }, {
+    command: 'flavor',
+    type: 'string',
+    required: false,
   }], 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;
   }
 
   resolve(isPackagerRunning().then(result => {
     if (result === 'running') {
-      console.log(chalk.bold('JS server already running.'));
+      console.log(chalk.bold(`JS server already running.`));
     } else if (result === 'unrecognized') {
-      console.warn(chalk.yellow('JS server not recognized, continuing with build...'));
+      console.warn(chalk.yellow(`JS server not recognized, continuing with build...`));
     } else {
       // result == 'not_running'
-      console.log(chalk.bold('Starting JS server...'));
+      console.log(chalk.bold(`Starting JS server...`));
       startServerInNewWindow();
     }
     buildAndRun(args, reject);
@@ -52,26 +63,33 @@ 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'
       : './gradlew';
 
-    const gradleArgs = ['installDebug'];
+    const gradleArgs = [];
+    if (args['flavor']) {
+        gradleArgs.push('install' +
+          args['flavor'][0].toUpperCase() + args['flavor'].slice(1)
+        );
+    } else {
+        gradleArgs.push('installDebug');
+    }
+
     if (args['install-debug']) {
       gradleArgs.push(args['install-debug']);
     }
 
     console.log(chalk.bold(
-      'Building and installing the app on the device (cd android && ' + cmd +
-      ' ' + gradleArgs.join(' ') + ')...'
+      `Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')}...`
     ));
 
     child_process.execFileSync(cmd, gradleArgs, {
@@ -101,18 +119,34 @@ function buildAndRun(args, reject) {
       ? process.env.ANDROID_HOME + '/platform-tools/adb'
       : 'adb';
 
-    const adbArgs = [
-      'shell', 'am', 'start', '-n', packageName + '/.MainActivity'
-    ];
+    const devices = adb.getDevices();
+    
+    if (devices && devices.length > 0) {
+      devices.forEach((device) => {
 
-    console.log(chalk.bold(
-      'Starting the app (' + adbPath + ' ' + adbArgs.join(' ') + ')...'
-    ));
+        const adbArgs = ['-s', device, 'shell', 'am', 'start', '-n', packageName + '/.MainActivity'];
+
+        console.log(chalk.bold(
+          `Starting the app on ${device} (${adbPath} ${adbArgs.join(' ')})...`
+        ));
+
+        child_process.spawnSync(adbPath, adbArgs, {stdio: 'inherit'});  
+      }); 
+    } else {
+      // If we cannot execute based on adb devices output, fall back to
+      // shell am start
+      const fallbackAdbArgs = [
+        'shell', 'am', 'start', '-n', packageName + '/.MainActivity'
+      ];
+      console.log(chalk.bold(
+        `Starting the app (${adbPath} ${fallbackAdbArgs.join(' ')}...`
+      ));
+      child_process.spawnSync(adbPath, fallbackAdbArgs, {stdio: 'inherit'});
+    }
 
-    child_process.spawnSync(adbPath, adbArgs, {stdio: 'inherit'});
   } catch (e) {
     console.log(chalk.red(
-      'adb invocation failed. Do you have adb in your PATH?'
+      `adb invocation failed. Do you have adb in your PATH?`
     ));
     // stderr is automatically piped from the gradle process, so the user
     // should see the error already, there is no need to do
@@ -123,18 +157,24 @@ function buildAndRun(args, reject) {
 }
 
 function startServerInNewWindow() {
+  var yargV = require('yargs').argv;
+
   const launchPackagerScript = path.resolve(
     __dirname, '..', '..', 'packager', 'launchPackager.command'
   );
 
   if (process.platform === 'darwin') {
-    child_process.spawnSync('open', [launchPackagerScript]);
+    if (yargV.open) {
+      return child_process.spawnSync('open', ['-a', yargV.open, launchPackagerScript]);
+    }
+    return child_process.spawnSync('open', [launchPackagerScript]);
+
   } else if (process.platform === 'linux') {
-    child_process.spawn(
-      'xterm',
-      ['-e', 'sh', launchPackagerScript],
-      {detached: true}
-    );
+    if (yargV.open){
+      return child_process.spawn(yargV.open,['-e', 'sh', launchPackagerScript], {detached: true});
+    }
+    return child_process.spawn('xterm',['-e', 'sh', launchPackagerScript],{detached: true});
+
   } else if (/^win/.test(process.platform)) {
     console.log(chalk.yellow('Starting the packager in a new window ' +
       'is not supported on Windows yet.\nPlease start it manually using ' +
@@ -144,8 +184,7 @@ function startServerInNewWindow() {
       'Windows on a daily basis.\n' +
       'Would you be up for sending a pull request?');
   } else {
-    console.log(chalk.red('Cannot start the packager. Unknown platform ' +
-      process.platform));
+    console.log(chalk.red(`Cannot start the packager. Unknown platform ${process.platform}`));
   }
 }
 
diff --git a/local-cli/runIOS/runIOS.js b/local-cli/runIOS/runIOS.js
index 00249820e0d1e7..584d4ada4fee5f 100644
--- a/local-cli/runIOS/runIOS.js
+++ b/local-cli/runIOS/runIOS.js
@@ -33,6 +33,11 @@ function _runIOS(argv, config, resolve, reject) {
     type: 'string',
     required: false,
     default: 'iPhone 6',
+  }, {
+    command: 'scheme',
+    description: 'Explicitly set Xcode scheme to use',
+    type: 'string',
+    required: false,
   }], argv);
 
   process.chdir('ios');
@@ -42,6 +47,7 @@ function _runIOS(argv, config, resolve, reject) {
   }
 
   const inferredSchemeName = path.basename(xcodeProject.name, path.extname(xcodeProject.name));
+  const scheme = args.scheme || inferredSchemeName
   console.log(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} ${xcodeProject.name}`);
 
   const simulators = parseIOSSimulatorsList(
@@ -63,7 +69,7 @@ function _runIOS(argv, config, resolve, reject) {
 
   const xcodebuildArgs = [
     xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name,
-    '-scheme', inferredSchemeName,
+    '-scheme', scheme,
     '-destination', `id=${selectedSimulator.udid}`,
     '-derivedDataPath', 'build',
   ];
diff --git a/local-cli/server/formatBanner.js b/local-cli/server/formatBanner.js
index f54095b0540792..9ae49a65e3622e 100644
--- a/local-cli/server/formatBanner.js
+++ b/local-cli/server/formatBanner.js
@@ -8,7 +8,7 @@
  */
 'use strict';
 
-var _ = require('underscore');
+var _ = require('lodash');
 var wordwrap = require('wordwrap');
 
 var HORIZONTAL_LINE = '\u2500';
@@ -45,7 +45,7 @@ var BOTTOM_RIGHT = '\u2518';
 function formatBanner(message, options) {
   options = options || {};
   _.defaults(options, {
-    chalkFunction: _.identity,
+    chalkFunction: (fn) => fn,
     width: 80,
     marginLeft: 0,
     marginRight: 0,
@@ -71,7 +71,7 @@ function formatBanner(message, options) {
 
   var left = spaces(marginLeft) + VERTICAL_LINE + spaces(paddingLeft);
   var right = spaces(paddingRight) + VERTICAL_LINE + spaces(marginRight);
-  var bodyLines = _.flatten([
+  var bodyLines = _.flattenDeep([
     arrayOf('', paddingTop),
     body.split('\n'),
     arrayOf('', paddingBottom),
@@ -88,7 +88,7 @@ function formatBanner(message, options) {
     spaces(marginRight);
   var bottom = spaces(marginLeft) + BOTTOM_LEFT + horizontalBorderLine +
     BOTTOM_RIGHT + spaces(marginRight);
-  return _.flatten([top, bodyLines, bottom]).join('\n');
+  return _.flattenDeep([top, bodyLines, bottom]).join('\n');
 }
 
 function spaces(number) {
diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js
index 3f5f33aeeddf17..57c0be9d26279a 100644
--- a/local-cli/server/runServer.js
+++ b/local-cli/server/runServer.js
@@ -15,6 +15,7 @@ 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');
@@ -24,11 +25,13 @@ 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)
@@ -51,6 +54,7 @@ function runServer(args, config, readyCallback) {
       });
 
       wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy');
+      ms = messageSocket.attachToServer(serverInstance, '/message');
       webSocketProxy.attachToServer(serverInstance, '/devtools');
       readyCallback();
     }
diff --git a/local-cli/server/util/attachHMRServer.js b/local-cli/server/util/attachHMRServer.js
index 54f510fd71eedc..eef673a8028209 100644
--- a/local-cli/server/util/attachHMRServer.js
+++ b/local-cli/server/util/attachHMRServer.js
@@ -8,9 +8,14 @@
  */
 'use strict';
 
+const getInverseDependencies = require('node-haste').getInverseDependencies;
 const querystring = require('querystring');
 const url = require('url');
 
+const blacklist = [
+  'Libraries/Utilities/HMRClient.js',
+];
+
 /**
  * Attaches a WebSocket based connection to the Packager to expose
  * Hot Module Replacement updates to the simulator.
@@ -23,13 +28,15 @@ function attachHMRServer({httpServer, path, packagerServer}) {
     packagerServer.setHMRFileChangeListener(null);
   }
 
-  // Returns a promise with the full list of dependencies and the shallow
-  // dependencies each file on the dependency list has for the give platform
-  // and entry file.
+  // 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,
+      hot: true,
       entryFile: bundleEntry,
     }).then(response => {
       // for each dependency builds the object:
@@ -70,12 +77,16 @@ function attachHMRServer({httpServer, path, packagerServer}) {
             dependenciesModulesCache[depName] = dep;
           });
         })).then(() => {
-          return {
-            dependenciesCache,
-            dependenciesModulesCache,
-            shallowDependencies,
-            resolutionResponse: response,
-          };
+          return getInverseDependencies(response)
+            .then(inverseDependenciesCache => {
+              return {
+                dependenciesCache,
+                dependenciesModulesCache,
+                shallowDependencies,
+                inverseDependenciesCache,
+                resolutionResponse: response,
+              };
+            });
         });
       });
     });
@@ -97,6 +108,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
         dependenciesCache,
         dependenciesModulesCache,
         shallowDependencies,
+        inverseDependenciesCache,
       }) => {
         client = {
           ws,
@@ -105,12 +117,24 @@ function attachHMRServer({httpServer, path, packagerServer}) {
           dependenciesCache,
           dependenciesModulesCache,
           shallowDependencies,
+          inverseDependenciesCache,
         };
 
         packagerServer.setHMRFileChangeListener((filename, stat) => {
           if (!client) {
             return;
           }
+          console.log(
+            `[Hot Module Replacement] File change detected (${time()})`
+          );
+
+          const blacklisted = blacklist.find(path =>
+            filename.indexOf(path) !== -1
+          );
+
+          if (blacklisted) {
+            return;
+          }
 
           client.ws.send(JSON.stringify({type: 'update-start'}));
           stat.then(() => {
@@ -132,6 +156,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                   return packagerServer.getDependencies({
                     platform: client.platform,
                     dev: true,
+                    hot: true,
                     entryFile: filename,
                     recursive: true,
                   }).then(response => {
@@ -148,6 +173,7 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                     dependenciesCache,
                     dependenciesModulesCache,
                     shallowDependencies,
+                    inverseDependenciesCache,
                     resolutionResponse,
                   }) => {
                     if (!client) {
@@ -162,6 +188,16 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                       }
                     });
 
+                    // 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;
@@ -182,11 +218,23 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                   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()) {
@@ -196,7 +244,8 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                 return JSON.stringify({
                   type: 'update',
                   body: {
-                    modules: bundle.getModulesCode(),
+                    modules: bundle.getModulesNamesAndCode(),
+                    inverseDependencies: inverseDependenciesCache,
                     sourceURLs: bundle.getSourceURLs(),
                     sourceMappingURLs: bundle.getSourceMappingURLs(),
                   },
@@ -230,6 +279,10 @@ function attachHMRServer({httpServer, path, packagerServer}) {
                   return;
                 }
 
+                console.log(
+                  '[Hot Module Replacement] Sending HMR update to client (' +
+                  time() + ')'
+                );
                 client.ws.send(update);
               });
             },
@@ -263,4 +316,9 @@ function arrayEquals(arrayA, arrayB) {
   );
 }
 
+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/launchEditor.js b/local-cli/server/util/launchEditor.js
index ee84452941326b..e65edfa3579678 100644
--- a/local-cli/server/util/launchEditor.js
+++ b/local-cli/server/util/launchEditor.js
@@ -106,6 +106,12 @@ function launchEditor(fileName, lineNumber) {
     return;
   }
 
+  // Sanitize lineNumber to prevent malicious use on win32
+  // via: https://github.com/nodejs/node/blob/c3bb4b1aa5e907d489619fb43d233c3336bfc03d/lib/child_process.js#L333
+  if (lineNumber && isNaN(lineNumber)) {
+    return;
+  }
+
   var editor = guessEditor();
   if (!editor) {
     printInstructions('PRO TIP');
diff --git a/local-cli/server/util/messageSocket.js b/local-cli/server/util/messageSocket.js
new file mode 100644
index 00000000000000..3c56aa0544cdec
--- /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/npm-shrinkwrap.json b/npm-shrinkwrap.json
index dc7c0be87142f9..b04506d1921db9 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -12,11 +12,6 @@
       "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": "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": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz",
@@ -102,6 +97,11 @@
       "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"
     },
+    "async": {
+      "version": "0.2.10",
+      "from": "async@>=0.2.6 <0.3.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz"
+    },
     "async-each": {
       "version": "0.1.6",
       "from": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz",
@@ -155,108 +155,137 @@
       }
     },
     "babel-core": {
-      "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",
+      "version": "6.6.4",
+      "from": "babel-core@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.6.4.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"
+          "version": "6.6.0",
+          "from": "babel-code-frame@>=6.6.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+          "version": "6.6.4",
+          "from": "babel-generator@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.6.4.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"
+          "version": "6.6.0",
+          "from": "babel-helpers@>=6.6.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.6.0.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"
+          "version": "6.6.0",
+          "from": "babel-messages@>=6.6.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.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"
+          "version": "6.6.4",
+          "from": "babel-template@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz"
         },
         "babel-runtime": {
           "version": "5.8.35",
-          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
           "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         },
+        "babel-register": {
+          "version": "6.6.0",
+          "from": "babel-register@>=6.6.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.6.0.tgz",
+          "dependencies": {
+            "core-js": {
+              "version": "2.1.3",
+              "from": "core-js@>=2.1.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.1.3.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",
+          "version": "6.6.4",
+          "from": "babel-traverse@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.tgz",
           "dependencies": {
             "globals": {
               "version": "8.18.0",
-              "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+              "from": "globals@>=8.3.0 <9.0.0",
               "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",
+              "from": "invariant@>=2.2.0 <3.0.0",
               "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
             }
           }
         },
+        "babel-types": {
+          "version": "6.6.4",
+          "from": "babel-types@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz"
+        },
+        "babylon": {
+          "version": "6.6.4",
+          "from": "babylon@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+        },
         "source-map": {
           "version": "0.5.3",
-          "from": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
+          "from": "source-map@>=0.5.0 <0.6.0",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz"
         }
       }
     },
     "babel-eslint": {
-      "version": "4.1.4",
-      "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",
+      "version": "5.0.0",
+      "from": "babel-eslint@latest",
+      "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-5.0.0.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": "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"
-        },
-        "babylon": {
-          "version": "5.8.29",
-          "from": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz",
-          "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.29.tgz"
-        },
-        "globals": {
-          "version": "6.4.1",
-          "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": "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": "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": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz",
-          "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.40.tgz"
+        "acorn-to-esprima": {
+          "version": "2.0.8",
+          "from": "acorn-to-esprima@>=2.0.4 <3.0.0",
+          "resolved": "https://registry.npmjs.org/acorn-to-esprima/-/acorn-to-esprima-2.0.8.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"
+        "babel-traverse": {
+          "version": "6.7.0",
+          "from": "babel-traverse@>=6.0.20 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.7.0.tgz",
+          "dependencies": {
+            "babel-code-frame": {
+              "version": "6.6.5",
+              "from": "babel-code-frame@>=6.6.5 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.5.tgz"
+            },
+            "babel-messages": {
+              "version": "6.6.5",
+              "from": "babel-messages@>=6.6.5 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.5.tgz"
+            },
+            "babel-runtime": {
+              "version": "5.8.35",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
+              "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            },
+            "babel-types": {
+              "version": "6.7.0",
+              "from": "babel-types@>=6.7.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.7.0.tgz"
+            },
+            "babylon": {
+              "version": "6.7.0",
+              "from": "babylon@>=6.7.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.7.0.tgz"
+            },
+            "globals": {
+              "version": "8.18.0",
+              "from": "globals@>=8.18.0 <9.0.0",
+              "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+            },
+            "invariant": {
+              "version": "2.2.1",
+              "from": "invariant@>=2.2.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.1.tgz"
+            }
+          }
         }
       }
     },
@@ -276,13 +305,13 @@
       "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.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",
+      "version": "6.5.0",
+      "from": "babel-plugin-external-helpers@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.5.0.tgz",
       "dependencies": {
         "babel-runtime": {
           "version": "5.8.35",
-          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
           "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         }
       }
@@ -348,404 +377,548 @@
       "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz"
     },
     "babel-polyfill": {
-      "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",
+      "version": "6.6.1",
+      "from": "babel-polyfill@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.6.1.tgz",
       "dependencies": {
+        "core-js": {
+          "version": "2.1.3",
+          "from": "core-js@>=2.1.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.1.3.tgz"
+        },
         "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"
+          "version": "6.5.0",
+          "from": "babel-regenerator-runtime@>=6.3.13 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.5.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"
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "dependencies": {
+            "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"
+            }
+          }
         }
       }
     },
     "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",
+      "version": "1.5.1",
+      "from": "babel-preset-react-native@>=1.5.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/babel-preset-react-native/-/babel-preset-react-native-1.5.1.tgz",
       "dependencies": {
         "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",
+          "from": "babel-plugin-react-transform@2.0.0-beta1",
           "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",
+              "from": "array-find@>=1.0.0 <2.0.0",
               "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-syntax-async-functions@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
             }
           }
         },
         "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",
+          "version": "6.5.0",
+          "from": "babel-plugin-syntax-class-properties@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-syntax-flow@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-syntax-jsx@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-syntax-trailing-function-commas@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.0",
+          "from": "babel-plugin-transform-class-properties@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.6.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.2",
+          "from": "babel-plugin-transform-es2015-arrow-functions@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.5.2.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-block-scoping@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.6.4.tgz",
           "dependencies": {
             "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",
+              "version": "6.6.4",
+              "from": "babel-traverse@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                  "version": "6.6.0",
+                  "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                  "version": "6.6.0",
+                  "from": "babel-messages@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                },
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
                 },
                 "globals": {
                   "version": "8.18.0",
-                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "from": "globals@>=8.3.0 <9.0.0",
                   "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",
+                  "from": "invariant@>=2.2.0 <3.0.0",
                   "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                 }
               }
             },
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.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"
+              "version": "6.6.4",
+              "from": "babel-template@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz",
+              "dependencies": {
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                }
+              }
             },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-classes@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.6.4.tgz",
           "dependencies": {
             "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"
+              "version": "6.6.0",
+              "from": "babel-helper-optimise-call-expression@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.6.0.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",
+              "version": "6.6.0",
+              "from": "babel-helper-function-name@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.6.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"
+                  "version": "6.6.4",
+                  "from": "babel-helper-get-function-arity@>=6.3.13 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.6.4.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"
+              "version": "6.6.4",
+              "from": "babel-helper-replace-supers@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.6.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"
+              "version": "6.6.4",
+              "from": "babel-template@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz",
+              "dependencies": {
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.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",
+              "version": "6.6.4",
+              "from": "babel-traverse@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                  "version": "6.6.0",
+                  "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.tgz"
+                },
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
                 },
                 "globals": {
                   "version": "8.18.0",
-                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "from": "globals@>=8.3.0 <9.0.0",
                   "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",
+                  "from": "invariant@>=2.2.0 <3.0.0",
                   "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.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"
+              "version": "6.6.4",
+              "from": "babel-helper-define-map@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.6.4.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"
+              "version": "6.6.0",
+              "from": "babel-messages@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
             },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
+            },
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-computed-properties@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.6.4.tgz",
           "dependencies": {
             "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",
+              "version": "6.6.4",
+              "from": "babel-helper-define-map@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.6.4.tgz",
               "dependencies": {
+                "babel-types": {
+                  "version": "6.6.4",
+                  "from": "babel-types@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz",
+                  "dependencies": {
+                    "babel-traverse": {
+                      "version": "6.6.4",
+                      "from": "babel-traverse@>=6.6.4 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.tgz",
+                      "dependencies": {
+                        "babel-code-frame": {
+                          "version": "6.6.0",
+                          "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.tgz"
+                        },
+                        "babel-messages": {
+                          "version": "6.6.0",
+                          "from": "babel-messages@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                        },
+                        "babylon": {
+                          "version": "6.6.4",
+                          "from": "babylon@>=6.6.4 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                        },
+                        "globals": {
+                          "version": "8.18.0",
+                          "from": "globals@>=8.3.0 <9.0.0",
+                          "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                        },
+                        "invariant": {
+                          "version": "2.2.0",
+                          "from": "invariant@>=2.2.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.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",
+                  "version": "6.6.0",
+                  "from": "babel-helper-function-name@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.6.0.tgz",
                   "dependencies": {
                     "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",
+                      "version": "6.6.4",
+                      "from": "babel-traverse@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                          "version": "6.6.0",
+                          "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                          "version": "6.6.0",
+                          "from": "babel-messages@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                        },
+                        "babylon": {
+                          "version": "6.6.4",
+                          "from": "babylon@>=6.6.4 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
                         },
                         "globals": {
                           "version": "8.18.0",
-                          "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                          "from": "globals@>=8.3.0 <9.0.0",
                           "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",
+                          "from": "invariant@>=2.2.0 <3.0.0",
                           "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"
+                      "version": "6.6.4",
+                      "from": "babel-helper-get-function-arity@>=6.3.13 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.6.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",
+              "version": "6.6.4",
+              "from": "babel-template@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz",
               "dependencies": {
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.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",
+                  "version": "6.6.4",
+                  "from": "babel-traverse@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                      "version": "6.6.0",
+                      "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                      "version": "6.6.0",
+                      "from": "babel-messages@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
                     },
                     "globals": {
                       "version": "8.18.0",
-                      "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                      "from": "globals@>=8.3.0 <9.0.0",
                       "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",
+                      "from": "invariant@>=2.2.0 <3.0.0",
                       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                     }
                   }
+                },
+                "babel-types": {
+                  "version": "6.6.4",
+                  "from": "babel-types@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz"
                 }
               }
             },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "from": "babel-plugin-transform-es2015-constants@>=6.1.4 <7.0.0",
           "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",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-destructuring@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.6.4.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.0",
+          "from": "babel-plugin-transform-es2015-for-of@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.6.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.6.4.tgz",
           "dependencies": {
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz",
+              "dependencies": {
+                "babel-traverse": {
+                  "version": "6.6.4",
+                  "from": "babel-traverse@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.tgz",
+                  "dependencies": {
+                    "babel-code-frame": {
+                      "version": "6.6.0",
+                      "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.tgz"
+                    },
+                    "babel-messages": {
+                      "version": "6.6.0",
+                      "from": "babel-messages@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                    },
+                    "babylon": {
+                      "version": "6.6.4",
+                      "from": "babylon@>=6.6.4 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                    },
+                    "globals": {
+                      "version": "8.18.0",
+                      "from": "globals@>=8.3.0 <9.0.0",
+                      "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                    },
+                    "invariant": {
+                      "version": "2.2.0",
+                      "from": "invariant@>=2.2.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                    }
+                  }
+                }
+              }
+            },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "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",
+              "version": "6.6.4",
+              "from": "babel-template@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz",
               "dependencies": {
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.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",
+                  "version": "6.6.4",
+                  "from": "babel-traverse@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                      "version": "6.6.0",
+                      "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                      "version": "6.6.0",
+                      "from": "babel-messages@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
                     },
                     "globals": {
                       "version": "8.18.0",
-                      "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                      "from": "globals@>=8.3.0 <9.0.0",
                       "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",
+                      "from": "invariant@>=2.2.0 <3.0.0",
                       "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                     }
                   }
@@ -753,265 +926,389 @@
               }
             },
             "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"
+              "version": "6.6.4",
+              "from": "babel-plugin-transform-strict-mode@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.6.4.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-parameters@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.6.4.tgz",
           "dependencies": {
             "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",
+              "version": "6.6.4",
+              "from": "babel-traverse@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                  "version": "6.6.0",
+                  "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                  "version": "6.6.0",
+                  "from": "babel-messages@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                },
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
                 },
                 "globals": {
                   "version": "8.18.0",
-                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "from": "globals@>=8.3.0 <9.0.0",
                   "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",
+                  "from": "invariant@>=2.2.0 <3.0.0",
                   "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.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",
+              "version": "6.6.4",
+              "from": "babel-helper-call-delegate@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.6.4.tgz",
               "dependencies": {
                 "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"
+                  "version": "6.6.4",
+                  "from": "babel-helper-hoist-variables@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.6.4.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"
+              "version": "6.6.4",
+              "from": "babel-helper-get-function-arity@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.6.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"
+              "version": "6.6.4",
+              "from": "babel-template@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.6.4.tgz",
+              "dependencies": {
+                "babylon": {
+                  "version": "6.6.4",
+                  "from": "babylon@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                }
+              }
+            },
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz"
             },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-transform-es2015-shorthand-properties@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.5.0.tgz",
           "dependencies": {
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz",
+              "dependencies": {
+                "babel-traverse": {
+                  "version": "6.6.4",
+                  "from": "babel-traverse@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.tgz",
+                  "dependencies": {
+                    "babel-code-frame": {
+                      "version": "6.6.0",
+                      "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.tgz"
+                    },
+                    "babel-messages": {
+                      "version": "6.6.0",
+                      "from": "babel-messages@>=6.6.0 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                    },
+                    "babylon": {
+                      "version": "6.6.4",
+                      "from": "babylon@>=6.6.4 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                    },
+                    "globals": {
+                      "version": "8.18.0",
+                      "from": "globals@>=8.3.0 <9.0.0",
+                      "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                    },
+                    "invariant": {
+                      "version": "2.2.0",
+                      "from": "invariant@>=2.2.0 <3.0.0",
+                      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
+                    }
+                  }
+                }
+              }
+            },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-spread@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.6.4.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-es2015-template-literals@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.6.4.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-flow-strip-types@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.6.4.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-transform-object-assign@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-object-rest-spread@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.6.4.tgz",
           "dependencies": {
             "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"
+              "version": "6.5.0",
+              "from": "babel-plugin-syntax-object-rest-spread@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.5.0.tgz"
             },
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.5.0",
+          "from": "babel-plugin-transform-react-display-name@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.5.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.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",
+          "version": "6.6.4",
+          "from": "babel-plugin-transform-react-jsx@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.6.4.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "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"
+              "version": "6.6.4",
+              "from": "babel-helper-builder-react-jsx@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.6.4.tgz",
+              "dependencies": {
+                "babel-types": {
+                  "version": "6.6.4",
+                  "from": "babel-types@>=6.6.4 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz",
+                  "dependencies": {
+                    "babel-traverse": {
+                      "version": "6.6.4",
+                      "from": "babel-traverse@>=6.6.4 <7.0.0",
+                      "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.tgz",
+                      "dependencies": {
+                        "babel-code-frame": {
+                          "version": "6.6.0",
+                          "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.tgz"
+                        },
+                        "babel-messages": {
+                          "version": "6.6.0",
+                          "from": "babel-messages@>=6.6.0 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+                        },
+                        "babylon": {
+                          "version": "6.6.4",
+                          "from": "babylon@>=6.6.4 <7.0.0",
+                          "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
+                        },
+                        "globals": {
+                          "version": "8.18.0",
+                          "from": "globals@>=8.3.0 <9.0.0",
+                          "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+                        },
+                        "invariant": {
+                          "version": "2.2.0",
+                          "from": "invariant@>=2.2.0 <3.0.0",
+                          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.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",
+          "version": "6.6.0",
+          "from": "babel-plugin-transform-regenerator@>=6.5.0 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.6.0.tgz",
           "dependencies": {
             "babel-runtime": {
               "version": "5.8.35",
-              "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+              "from": "babel-runtime@>=5.0.0 <6.0.0",
               "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",
+              "version": "6.6.4",
+              "from": "babel-traverse@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+                  "version": "6.6.0",
+                  "from": "babel-code-frame@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+                  "version": "6.6.0",
+                  "from": "babel-messages@>=6.6.0 <7.0.0",
+                  "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
                 },
                 "globals": {
                   "version": "8.18.0",
-                  "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+                  "from": "globals@>=8.3.0 <9.0.0",
                   "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",
+                  "from": "invariant@>=2.2.0 <3.0.0",
                   "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
                 }
               }
+            },
+            "babel-types": {
+              "version": "6.6.4",
+              "from": "babel-types@>=6.3.13 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz"
+            },
+            "babylon": {
+              "version": "6.6.4",
+              "from": "babylon@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.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",
+      "version": "6.6.0",
+      "from": "babel-register@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.6.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"
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "dependencies": {
+            "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-js": {
+          "version": "2.1.3",
+          "from": "core-js@>=2.1.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.1.3.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",
+      "version": "6.6.4",
+      "from": "babel-types@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.6.4.tgz",
       "dependencies": {
         "babel-runtime": {
           "version": "5.8.35",
-          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
           "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",
+          "version": "6.6.4",
+          "from": "babel-traverse@>=6.6.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.6.4.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"
+              "version": "6.6.0",
+              "from": "babel-code-frame@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.6.0.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"
+              "version": "6.6.0",
+              "from": "babel-messages@>=6.6.0 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.6.0.tgz"
+            },
+            "babylon": {
+              "version": "6.6.4",
+              "from": "babylon@>=6.6.4 <7.0.0",
+              "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz"
             },
             "globals": {
               "version": "8.18.0",
-              "from": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz",
+              "from": "globals@>=8.3.0 <9.0.0",
               "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",
+              "from": "invariant@>=2.2.0 <3.0.0",
               "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz"
             }
           }
@@ -1019,13 +1316,13 @@
       }
     },
     "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",
+      "version": "6.6.4",
+      "from": "babylon@>=6.5.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.6.4.tgz",
       "dependencies": {
         "babel-runtime": {
           "version": "5.8.35",
-          "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz",
+          "from": "babel-runtime@>=5.0.0 <6.0.0",
           "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.35.tgz"
         }
       }
@@ -1303,481 +1600,561 @@
       "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",
+      "version": "2.2.0",
+      "from": "eslint@2.2.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.2.0.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",
+          "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": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+              "from": "typedarray@>=0.0.5 <0.1.0",
               "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.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",
-              "dependencies": {
-                "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": "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",
+          "version": "1.2.0",
+          "from": "doctrine@>=1.1.0 <2.0.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.2.0.tgz",
           "dependencies": {
             "esutils": {
               "version": "1.1.6",
-              "from": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz",
+              "from": "esutils@>=1.1.6 <2.0.0",
               "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
+            },
+            "isarray": {
+              "version": "1.0.0",
+              "from": "isarray@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.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",
+        "es6-map": {
+          "version": "0.1.3",
+          "from": "es6-map@>=0.1.3 <0.2.0",
+          "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.3.tgz",
           "dependencies": {
-            "es6-map": {
+            "d": {
               "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"
-                },
-                "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"
-                    }
-                  }
-                },
-                "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"
-                    }
-                  }
-                },
-                "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"
-                },
-                "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"
-                },
-                "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"
-                }
-              }
+              "from": "d@>=0.1.1 <0.2.0",
+              "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
             },
-            "es6-weak-map": {
+            "es5-ext": {
+              "version": "0.10.11",
+              "from": "es5-ext@>=0.10.8 <0.11.0",
+              "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz"
+            },
+            "es6-iterator": {
+              "version": "2.0.0",
+              "from": "es6-iterator@>=2.0.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz"
+            },
+            "es6-set": {
               "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",
+              "from": "es6-set@>=0.1.3 <0.2.0",
+              "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz"
+            },
+            "es6-symbol": {
+              "version": "3.0.2",
+              "from": "es6-symbol@>=3.0.1 <3.1.0",
+              "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz"
+            },
+            "event-emitter": {
+              "version": "0.3.4",
+              "from": "event-emitter@>=0.3.4 <0.4.0",
+              "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz"
+            }
+          }
+        },
+        "escope": {
+          "version": "3.5.0",
+          "from": "escope@>=3.4.0 <4.0.0",
+          "resolved": "https://registry.npmjs.org/escope/-/escope-3.5.0.tgz",
+          "dependencies": {
+            "es6-weak-map": {
+              "version": "2.0.1",
+              "from": "es6-weak-map@>=2.0.1 <3.0.0",
+              "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz",
               "dependencies": {
                 "d": {
                   "version": "0.1.1",
-                  "from": "https://registry.npmjs.org/d/-/d-0.1.1.tgz",
+                  "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": "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"
+                  "version": "0.10.11",
+                  "from": "es5-ext@>=0.10.8 <0.11.0",
+                  "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.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"
+                  "version": "2.0.0",
+                  "from": "es6-iterator@>=2.0.0 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.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"
+                  "version": "3.0.2",
+                  "from": "es6-symbol@>=3.0.0 <4.0.0",
+                  "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.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"
+              "version": "4.0.0",
+              "from": "esrecurse@>=4.0.0 <5.0.0",
+              "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.0.0.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"
-        },
-        "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"
+          "version": "3.1.1",
+          "from": "espree@>=3.0.0 <4.0.0",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-3.1.1.tgz",
+          "dependencies": {
+            "acorn": {
+              "version": "3.0.4",
+              "from": "acorn@>=3.0.4 <4.0.0",
+              "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.0.4.tgz"
+            },
+            "acorn-jsx": {
+              "version": "2.0.1",
+              "from": "acorn-jsx@>=2.0.1 <3.0.0",
+              "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-2.0.1.tgz",
+              "dependencies": {
+                "acorn": {
+                  "version": "2.7.0",
+                  "from": "acorn@>=2.0.1 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz"
+                }
+              }
+            }
+          }
+        },
+        "estraverse": {
+          "version": "4.1.1",
+          "from": "estraverse@>=4.1.1 <5.0.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz"
         },
         "estraverse-fb": {
           "version": "1.3.1",
-          "from": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.1.tgz",
+          "from": "estraverse-fb@>=1.3.1 <2.0.0",
           "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.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"
-        },
-        "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",
+        "file-entry-cache": {
+          "version": "1.2.4",
+          "from": "file-entry-cache@>=1.1.1 <2.0.0",
+          "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.2.4.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",
+            "flat-cache": {
+              "version": "1.0.10",
+              "from": "flat-cache@>=1.0.9 <2.0.0",
+              "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.0.10.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",
+                "del": {
+                  "version": "2.2.0",
+                  "from": "del@>=2.0.2 <3.0.0",
+                  "resolved": "https://registry.npmjs.org/del/-/del-2.2.0.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"
+                    "globby": {
+                      "version": "4.0.0",
+                      "from": "globby@>=4.0.0 <5.0.0",
+                      "resolved": "https://registry.npmjs.org/globby/-/globby-4.0.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"
+                        }
+                      }
+                    },
+                    "is-path-cwd": {
+                      "version": "1.0.0",
+                      "from": "is-path-cwd@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz"
+                    },
+                    "is-path-in-cwd": {
+                      "version": "1.0.0",
+                      "from": "is-path-in-cwd@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz",
+                      "dependencies": {
+                        "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"
+                        }
+                      }
+                    },
+                    "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"
+                        }
+                      }
+                    },
+                    "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.3",
+                          "from": "glob@>=7.0.0 <8.0.0",
+                          "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz"
+                        }
+                      }
                     }
                   }
+                },
+                "read-json-sync": {
+                  "version": "1.1.1",
+                  "from": "read-json-sync@>=1.1.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/read-json-sync/-/read-json-sync-1.1.1.tgz"
+                },
+                "write": {
+                  "version": "0.2.1",
+                  "from": "write@>=0.2.1 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz"
                 }
               }
             }
           }
         },
+        "glob": {
+          "version": "6.0.4",
+          "from": "glob@>=6.0.4 <7.0.0",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz"
+        },
+        "globals": {
+          "version": "8.18.0",
+          "from": "globals@>=8.18.0 <9.0.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-8.18.0.tgz"
+        },
+        "ignore": {
+          "version": "2.2.19",
+          "from": "ignore@>=2.2.19 <3.0.0",
+          "resolved": "https://registry.npmjs.org/ignore/-/ignore-2.2.19.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",
+          "version": "0.12.0",
+          "from": "inquirer@>=0.12.0 <0.13.0",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
           "dependencies": {
+            "ansi-escapes": {
+              "version": "1.3.0",
+              "from": "ansi-escapes@>=1.1.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.3.0.tgz"
+            },
+            "cli-cursor": {
+              "version": "1.0.2",
+              "from": "cli-cursor@>=1.0.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+              "dependencies": {
+                "restore-cursor": {
+                  "version": "1.0.1",
+                  "from": "restore-cursor@>=1.0.1 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+                  "dependencies": {
+                    "exit-hook": {
+                      "version": "1.1.1",
+                      "from": "exit-hook@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.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"
+                    }
+                  }
+                }
+              }
+            },
             "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"
+              "version": "2.1.0",
+              "from": "cli-width@>=2.0.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.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"
+              "version": "1.4.0",
+              "from": "figures@>=1.3.5 <2.0.0",
+              "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.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",
+              "version": "1.0.1",
+              "from": "readline2@>=1.0.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.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"
+                "code-point-at": {
+                  "version": "1.0.0",
+                  "from": "code-point-at@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.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"
-                    }
-                  }
+                "is-fullwidth-code-point": {
+                  "version": "1.0.0",
+                  "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
+                },
+                "mute-stream": {
+                  "version": "0.0.5",
+                  "from": "mute-stream@0.0.5",
+                  "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz"
                 }
               }
             },
             "run-async": {
               "version": "0.1.0",
-              "from": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
+              "from": "run-async@>=0.1.0 <0.2.0",
               "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"
+              "version": "3.1.2",
+              "from": "rx-lite@>=3.1.2 <4.0.0",
+              "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz"
+            },
+            "string-width": {
+              "version": "1.0.1",
+              "from": "string-width@>=1.0.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz",
+              "dependencies": {
+                "code-point-at": {
+                  "version": "1.0.0",
+                  "from": "code-point-at@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz"
+                },
+                "is-fullwidth-code-point": {
+                  "version": "1.0.0",
+                  "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.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",
+          "version": "2.13.1",
+          "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.13.1.tgz",
           "dependencies": {
             "generate-function": {
               "version": "2.0.0",
-              "from": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz",
+              "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": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+              "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": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+                  "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": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz",
+              "from": "jsonpointer@2.0.0",
               "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"
             }
           }
         },
         "is-resolvable": {
           "version": "1.0.0",
-          "from": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
+          "from": "is-resolvable@>=1.0.0 <2.0.0",
           "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"
+              "version": "1.0.2",
+              "from": "tryit@>=1.0.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.2.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",
+          "version": "3.5.4",
+          "from": "js-yaml@>=3.5.1 <4.0.0",
+          "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.5.4.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",
+              "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": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+                  "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": "https://registry.npmjs.org/esprima/-/esprima-2.2.0.tgz",
-              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.2.0.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"
-                }
-              }
-            }
-          }
-        },
-        "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"
-            }
-          }
-        },
-        "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": {
-            "lodash._arraymap": {
-              "version": "3.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"
-                }
-              }
+              "version": "2.7.2",
+              "from": "esprima@>=2.6.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.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"
+        "lodash": {
+          "version": "4.6.1",
+          "from": "lodash@>=4.0.0 <5.0.0",
+          "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.6.1.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",
+          "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": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+              "from": "prelude-ls@>=1.1.2 <1.2.0",
               "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",
+              "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": "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"
+              "version": "0.3.2",
+              "from": "type-check@>=0.3.2 <0.4.0",
+              "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.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"
+              "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.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"
+              "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"
             }
           }
         },
         "path-is-inside": {
           "version": "1.0.1",
-          "from": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz",
+          "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"
         },
+        "pluralize": {
+          "version": "1.2.1",
+          "from": "pluralize@>=1.2.1 <2.0.0",
+          "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz"
+        },
+        "require-uncached": {
+          "version": "1.0.2",
+          "from": "require-uncached@>=1.0.2 <2.0.0",
+          "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.2.tgz",
+          "dependencies": {
+            "caller-path": {
+              "version": "0.1.0",
+              "from": "caller-path@>=0.1.0 <0.2.0",
+              "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
+              "dependencies": {
+                "callsites": {
+                  "version": "0.2.0",
+                  "from": "callsites@>=0.2.0 <0.3.0",
+                  "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz"
+                }
+              }
+            },
+            "resolve-from": {
+              "version": "1.0.1",
+              "from": "resolve-from@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz"
+            }
+          }
+        },
+        "shelljs": {
+          "version": "0.5.3",
+          "from": "shelljs@>=0.5.3 <0.6.0",
+          "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz"
+        },
         "strip-json-comments": {
           "version": "1.0.4",
-          "from": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+          "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"
         },
+        "table": {
+          "version": "3.7.8",
+          "from": "table@>=3.7.8 <4.0.0",
+          "resolved": "https://registry.npmjs.org/table/-/table-3.7.8.tgz",
+          "dependencies": {
+            "bluebird": {
+              "version": "3.3.4",
+              "from": "bluebird@>=3.1.1 <4.0.0",
+              "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.3.4.tgz"
+            },
+            "slice-ansi": {
+              "version": "0.0.4",
+              "from": "slice-ansi@0.0.4",
+              "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz"
+            },
+            "string-width": {
+              "version": "1.0.1",
+              "from": "string-width@>=1.0.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz",
+              "dependencies": {
+                "code-point-at": {
+                  "version": "1.0.0",
+                  "from": "code-point-at@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz"
+                },
+                "is-fullwidth-code-point": {
+                  "version": "1.0.0",
+                  "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
+                  "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
+                }
+              }
+            },
+            "tv4": {
+              "version": "1.2.7",
+              "from": "tv4@>=1.2.7 <2.0.0",
+              "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.7.tgz"
+            },
+            "xregexp": {
+              "version": "3.1.0",
+              "from": "xregexp@>=3.0.0 <4.0.0",
+              "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-3.1.0.tgz"
+            }
+          }
+        },
         "text-table": {
           "version": "0.2.0",
-          "from": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+          "from": "text-table@>=0.2.0 <0.3.0",
           "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.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"
+        "user-home": {
+          "version": "2.0.0",
+          "from": "user-home@>=2.0.0 <3.0.0",
+          "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",
+              "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
+            }
+          }
         }
       }
     },
+    "eslint-plugin-flow-vars": {
+      "version": "0.2.1",
+      "from": "eslint-plugin-flow-vars@latest",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-flow-vars/-/eslint-plugin-flow-vars-0.2.1.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"
+      "version": "4.2.1",
+      "from": "eslint-plugin-react@latest",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-4.2.1.tgz"
     },
     "esprima-fb": {
       "version": "15001.1001.0-dev-harmony-fb",
@@ -1814,47 +2191,1335 @@
       "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.6.0",
-      "from": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.0.tgz",
-      "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.0.tgz",
+      "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": {
+            "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"
+            }
+          }
+        }
+      }
+    },
+    "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"
+    },
+    "filename-regex": {
+      "version": "2.0.0",
+      "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"
+    },
+    "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"
+    },
+    "find-up": {
+      "version": "1.0.0",
+      "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": {
+        "path-exists": {
+          "version": "2.0.0",
+          "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"
+        }
+      }
+    },
+    "flow-bin": {
+      "version": "0.22.0",
+      "from": "flow-bin@0.22.0",
+      "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.22.0.tgz",
       "dependencies": {
-        "ua-parser-js": {
-          "version": "0.7.10",
-          "from": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.10.tgz",
-          "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.10.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": {
+            "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": {
+                "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.7 <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"
+                    }
+                  }
+                }
+              }
+            },
+            "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": {
+                "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": {
+                    "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"
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "semver": {
+                      "version": "5.1.0",
+                      "from": "semver@>=5.0.3 <6.0.0",
+                      "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.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": {
+                "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"
+                    }
+                  }
+                },
+                "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": {
+                        "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"
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "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": {
+                        "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"
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "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"
+                    },
+                    "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"
+                        }
+                      }
+                    },
+                    "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"
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "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"
+                    },
+                    "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"
+                        }
+                      }
+                    },
+                    "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"
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "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"
+                    }
+                  }
+                },
+                "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": {
+                    "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": {
+                        "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"
+                    }
+                  }
+                },
+                "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"
+                    }
+                  }
+                },
+                "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": {
+                    "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": {
+                        "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"
+                            }
+                          }
+                        },
+                        "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.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"
+                    }
+                  }
+                },
+                "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": {
+                    "duplexer": {
+                      "version": "0.1.1",
+                      "from": "duplexer@>=0.1.1 <0.2.0",
+                      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.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"
+                    }
+                  }
+                },
+                "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"
+                    }
+                  }
+                },
+                "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"
+                    }
+                  }
+                },
+                "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": {
+                    "defaults": {
+                      "version": "1.0.3",
+                      "from": "defaults@>=1.0.0 <2.0.0",
+                      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.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": {
+                        "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"
+                        }
+                      }
+                    },
+                    "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": {
+                        "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": {
+                            "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"
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    },
+                    "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": "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"
+                        }
+                      }
+                    }
+                  }
+                },
+                "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": {
+                    "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"
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            },
+            "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"
+                    },
+                    "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"
+                    }
+                  }
+                },
+                "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"
+                    }
+                  }
+                },
+                "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"
+                },
+                "async": {
+                  "version": "0.9.2",
+                  "from": "async@>=0.9.0 <0.10.0",
+                  "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.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"
+                },
+                "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"
+                }
+              }
+            },
+            "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": {
+                "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"
+                    }
+                  }
+                }
+              }
+            },
+            "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": {
+                "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.3",
+                      "from": "glob@>=7.0.0 <8.0.0",
+                      "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.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"
+                    }
+                  }
+                }
+              }
+            },
+            "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"
+            }
+          }
         },
-        "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"
-        }
-      }
-    },
-    "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"
-    },
-    "filename-regex": {
-      "version": "2.0.0",
-      "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"
-    },
-    "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"
-    },
-    "find-up": {
-      "version": "1.0.0",
-      "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": {
-        "path-exists": {
-          "version": "2.0.0",
-          "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"
+        "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": {
+            "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": {
+                "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"
+                    }
+                  }
+                }
+              }
+            }
+          }
         }
       }
     },
@@ -1940,31 +3605,31 @@
           "from": "balanced-match@>=0.2.0 <0.3.0",
           "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.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"
         },
+        "block-stream": {
+          "version": "0.0.8",
+          "from": "block-stream@*",
+          "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.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"
         },
-        "chalk": {
-          "version": "1.1.1",
-          "from": "chalk@^1.1.1",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
-        },
         "caseless": {
           "version": "0.11.0",
           "from": "caseless@~0.11.0",
           "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz"
         },
+        "chalk": {
+          "version": "1.1.1",
+          "from": "chalk@^1.1.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz"
+        },
         "combined-stream": {
           "version": "1.0.5",
           "from": "combined-stream@~1.0.5",
@@ -1995,26 +3660,26 @@
           "from": "ctype@0.5.3",
           "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz"
         },
-        "debug": {
-          "version": "0.7.4",
-          "from": "debug@~0.7.2",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.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"
         },
         "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"
         },
+        "debug": {
+          "version": "0.7.4",
+          "from": "debug@~0.7.2",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz"
+        },
         "escape-string-regexp": {
           "version": "1.0.3",
           "from": "escape-string-regexp@^1.0.2",
@@ -2045,11 +3710,6 @@
           "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",
@@ -2060,36 +3720,41 @@
           "from": "graceful-fs@4.1",
           "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.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"
+        },
         "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"
         },
-        "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"
-        },
         "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"
         },
+        "hawk": {
+          "version": "3.1.0",
+          "from": "hawk@~3.1.0",
+          "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz"
+        },
         "http-signature": {
           "version": "0.11.0",
           "from": "http-signature@~0.11.0",
@@ -2225,26 +3890,26 @@
           "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"
         },
-        "sntp": {
-          "version": "1.0.9",
-          "from": "sntp@1.x.x",
-          "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.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"
         },
         "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",
@@ -2260,16 +3925,16 @@
           "from": "strip-ansi@^3.0.0",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.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"
-        },
         "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",
@@ -2636,166 +4301,176 @@
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz"
     },
     "jest-cli": {
-      "version": "0.8.2",
-      "from": "jest-cli@0.8.2",
-      "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-0.8.2.tgz",
+      "version": "0.9.0-fb2",
+      "from": "jest-cli@next",
       "dependencies": {
         "cover": {
           "version": "0.2.9",
-          "from": "https://registry.npmjs.org/cover/-/cover-0.2.9.tgz",
+          "from": "cover@>=0.2.9 <0.3.0",
           "resolved": "https://registry.npmjs.org/cover/-/cover-0.2.9.tgz",
           "dependencies": {
             "cli-table": {
               "version": "0.0.2",
-              "from": "https://registry.npmjs.org/cli-table/-/cli-table-0.0.2.tgz",
+              "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": "https://registry.npmjs.org/colors/-/colors-0.3.0.tgz",
+                  "from": "colors@0.3.0",
                   "resolved": "https://registry.npmjs.org/colors/-/colors-0.3.0.tgz"
                 }
               }
             },
             "underscore": {
               "version": "1.2.4",
-              "from": "https://registry.npmjs.org/underscore/-/underscore-1.2.4.tgz",
+              "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": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.0.0.tgz",
+              "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": "https://registry.npmjs.org/which/-/which-1.0.9.tgz",
+              "from": "which@>=1.0.0 <1.1.0",
               "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz"
             }
           }
         },
         "diff": {
           "version": "2.2.1",
-          "from": "https://registry.npmjs.org/diff/-/diff-2.2.1.tgz",
+          "from": "diff@>=2.1.1 <3.0.0",
           "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.1.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"
+        },
         "istanbul": {
-          "version": "0.3.22",
-          "from": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz",
-          "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.3.22.tgz",
+          "version": "0.4.2",
+          "from": "istanbul@>=0.4.2 <0.5.0",
+          "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.2.tgz",
           "dependencies": {
             "abbrev": {
               "version": "1.0.7",
-              "from": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz",
+              "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": "https://registry.npmjs.org/async/-/async-1.5.0.tgz",
-              "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
+              "version": "1.5.2",
+              "from": "async@>=1.0.0 <2.0.0",
+              "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
             },
             "escodegen": {
               "version": "1.7.1",
-              "from": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz",
+              "from": "escodegen@>=1.7.0 <1.8.0",
               "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz",
               "dependencies": {
                 "estraverse": {
                   "version": "1.9.3",
-                  "from": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+                  "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": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
+                  "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": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+                  "from": "optionator@>=0.5.0 <0.6.0",
                   "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",
+                      "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": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+                      "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": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+                      "from": "wordwrap@>=0.0.2 <0.1.0",
                       "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"
+                      "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": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz",
+                      "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": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz",
+                      "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": "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"
+                  "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"
+                    }
+                  }
                 }
               }
             },
             "esprima": {
-              "version": "2.5.0",
-              "from": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz",
-              "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.5.0.tgz"
+              "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": "https://registry.npmjs.org/fileset/-/fileset-0.2.1.tgz",
+              "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": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.5.tgz",
+              "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": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.1.tgz",
+                  "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": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+                      "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": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz",
+                      "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": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+                      "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": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+                      "from": "yargs@>=3.10.0 <3.11.0",
                       "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
                       "dependencies": {
                         "window-size": {
                           "version": "0.1.0",
-                          "from": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+                          "from": "window-size@0.1.0",
                           "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz"
                         }
                       }
@@ -2805,47 +4480,37 @@
               }
             },
             "js-yaml": {
-              "version": "3.4.6",
-              "from": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.6.tgz",
-              "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.6.tgz",
+              "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.3",
-                  "from": "https://registry.npmjs.org/argparse/-/argparse-1.0.3.tgz",
-                  "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.3.tgz",
+                  "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": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+                      "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.7.1",
-                  "from": "https://registry.npmjs.org/esprima/-/esprima-2.7.1.tgz",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.1.tgz"
-                },
-                "inherit": {
-                  "version": "2.2.2",
-                  "from": "https://registry.npmjs.org/inherit/-/inherit-2.2.2.tgz",
-                  "resolved": "https://registry.npmjs.org/inherit/-/inherit-2.2.2.tgz"
                 }
               }
             },
             "nopt": {
               "version": "3.0.6",
-              "from": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+              "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": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
+              "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": "https://registry.npmjs.org/has-flag/-/has-flag-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"
                 }
               }
@@ -2853,449 +4518,522 @@
           }
         },
         "jsdom": {
-          "version": "7.2.1",
-          "from": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.1.tgz",
-          "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.1.tgz",
+          "version": "7.2.2",
+          "from": "jsdom@>=7.2.0 <8.0.0",
+          "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
           "dependencies": {
             "abab": {
-              "version": "1.0.1",
-              "from": "https://registry.npmjs.org/abab/-/abab-1.0.1.tgz",
-              "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.1.tgz"
+              "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.6.4",
-              "from": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz",
-              "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz"
+              "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": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz",
+              "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.0",
-              "from": "https://registry.npmjs.org/cssom/-/cssom-0.3.0.tgz",
-              "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.0.tgz"
+              "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.30",
-              "from": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.30.tgz",
-              "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.30.tgz"
+              "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.7.1",
-              "from": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz",
-              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.7.1.tgz",
+              "version": "1.8.0",
+              "from": "escodegen@>=1.6.1 <2.0.0",
+              "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.0.tgz",
               "dependencies": {
                 "estraverse": {
                   "version": "1.9.3",
-                  "from": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+                  "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": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz",
-                  "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz"
+                  "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.5.0",
-                  "from": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
-                  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz",
+                  "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": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+                      "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": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+                      "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": "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"
+                      "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": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz",
-                      "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz"
+                      "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.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"
+                      "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": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+                  "from": "source-map@>=0.2.0 <0.3.0",
                   "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz"
                 }
               }
             },
             "nwmatcher": {
               "version": "1.3.7",
-              "from": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.7.tgz",
+              "from": "nwmatcher@>=1.3.7 <2.0.0",
               "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.3.7.tgz"
             },
             "parse5": {
               "version": "1.5.1",
-              "from": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
+              "from": "parse5@>=1.5.1 <2.0.0",
               "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz"
             },
             "request": {
-              "version": "2.67.0",
-              "from": "https://registry.npmjs.org/request/-/request-2.67.0.tgz",
-              "resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz",
+              "version": "2.69.0",
+              "from": "request@>=2.55.0 <3.0.0",
+              "resolved": "https://registry.npmjs.org/request/-/request-2.69.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"
+                },
+                "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": {
+                    "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"
+                    }
+                  }
+                },
                 "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"
+                  "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": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+                  "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": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
+                  "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": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+                  "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": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz",
+                  "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.0",
-                      "from": "https://registry.npmjs.org/async/-/async-1.5.0.tgz",
-                      "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz"
+                      "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"
+                        }
+                      }
                     }
                   }
                 },
-                "json-stringify-safe": {
-                  "version": "5.0.1",
-                  "from": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-                  "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
-                },
-                "mime-types": {
-                  "version": "2.1.8",
-                  "from": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.tgz",
-                  "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.8.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": {
-                    "mime-db": {
-                      "version": "1.20.0",
-                      "from": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.tgz",
-                      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.20.0.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"
+                    },
+                    "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"
                     }
                   }
                 },
-                "node-uuid": {
-                  "version": "1.4.7",
-                  "from": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz",
-                  "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz"
-                },
-                "qs": {
-                  "version": "5.2.0",
-                  "from": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz",
-                  "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
-                },
-                "tunnel-agent": {
-                  "version": "0.4.2",
-                  "from": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz",
-                  "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz"
-                },
                 "http-signature": {
-                  "version": "1.1.0",
-                  "from": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.0.tgz",
-                  "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.0.tgz",
+                  "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.1.5",
-                      "from": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
-                      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
+                      "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": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz",
+                      "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": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
+                          "from": "extsprintf@1.0.2",
                           "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
                         },
                         "json-schema": {
                           "version": "0.2.2",
-                          "from": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz",
+                          "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": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
+                          "from": "verror@1.3.6",
                           "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
                         }
                       }
                     },
                     "sshpk": {
-                      "version": "1.7.1",
-                      "from": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.1.tgz",
-                      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.1.tgz",
+                      "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": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
+                          "from": "asn1@>=0.2.3 <0.3.0",
                           "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz"
                         },
-                        "assert-plus": {
-                          "version": "0.2.0",
-                          "from": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
-                          "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz"
-                        },
                         "dashdash": {
-                          "version": "1.10.1",
-                          "from": "https://registry.npmjs.org/dashdash/-/dashdash-1.10.1.tgz",
-                          "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.10.1.tgz",
+                          "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": "0.1.5",
-                              "from": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz",
-                              "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz"
+                              "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": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz",
+                          "from": "jsbn@>=0.1.0 <0.2.0",
                           "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz"
                         },
                         "tweetnacl": {
-                          "version": "0.13.2",
-                          "from": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.2.tgz",
-                          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.2.tgz"
+                          "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": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz",
+                          "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": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
+                          "from": "ecc-jsbn@>=0.0.1 <1.0.0",
                           "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz"
                         }
                       }
                     }
                   }
                 },
-                "oauth-sign": {
-                  "version": "0.8.0",
-                  "from": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz",
-                  "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.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"
                 },
-                "hawk": {
-                  "version": "3.1.2",
-                  "from": "https://registry.npmjs.org/hawk/-/hawk-3.1.2.tgz",
-                  "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.2.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": {
-                    "hoek": {
-                      "version": "2.16.3",
-                      "from": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
-                      "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz"
-                    },
-                    "boom": {
-                      "version": "2.10.1",
-                      "from": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
-                      "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz"
-                    },
-                    "cryptiles": {
-                      "version": "2.0.5",
-                      "from": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
-                      "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz"
-                    },
-                    "sntp": {
-                      "version": "1.0.9",
-                      "from": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
-                      "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz"
+                    "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"
                     }
                   }
                 },
-                "aws-sign2": {
-                  "version": "0.6.0",
-                  "from": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
-                  "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.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": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
+                  "from": "stringstream@>=0.0.4 <0.1.0",
                   "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
                 },
-                "combined-stream": {
-                  "version": "1.0.5",
-                  "from": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-                  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
-                  "dependencies": {
-                    "delayed-stream": {
-                      "version": "1.0.0",
-                      "from": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-                      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
-                    }
-                  }
-                },
-                "isstream": {
-                  "version": "0.1.2",
-                  "from": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-                  "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
-                },
-                "is-typedarray": {
-                  "version": "1.0.0",
-                  "from": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-                  "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
-                },
-                "har-validator": {
-                  "version": "2.0.3",
-                  "from": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.3.tgz",
-                  "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.3.tgz",
-                  "dependencies": {
-                    "is-my-json-valid": {
-                      "version": "2.12.3",
-                      "from": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.3.tgz",
-                      "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.3.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"
-                        }
-                      }
-                    },
-                    "pinkie-promise": {
-                      "version": "2.0.0",
-                      "from": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
-                      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz",
-                      "dependencies": {
-                        "pinkie": {
-                          "version": "2.0.1",
-                          "from": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.1.tgz",
-                          "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.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"
                 }
               }
             },
             "sax": {
-              "version": "1.1.4",
-              "from": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
-              "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz"
+              "version": "1.1.5",
+              "from": "sax@>=1.1.4 <2.0.0",
+              "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.5.tgz"
             },
             "symbol-tree": {
               "version": "3.1.4",
-              "from": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.4.tgz",
+              "from": "symbol-tree@>=3.1.0 <4.0.0",
               "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.1.4.tgz"
             },
             "tough-cookie": {
               "version": "2.2.1",
-              "from": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.1.tgz",
+              "from": "tough-cookie@>=2.2.0 <3.0.0",
               "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.1.tgz"
             },
             "webidl-conversions": {
               "version": "2.0.1",
-              "from": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
+              "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": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz",
+              "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": {
                 "tr46": {
-                  "version": "0.0.2",
-                  "from": "https://registry.npmjs.org/tr46/-/tr46-0.0.2.tgz",
-                  "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.2.tgz"
+                  "version": "0.0.3",
+                  "from": "tr46@>=0.0.1 <0.1.0",
+                  "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
                 }
               }
             },
             "xml-name-validator": {
               "version": "2.0.1",
-              "from": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
+              "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": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz",
-          "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz",
-          "dependencies": {
-            "jsonify": {
-              "version": "0.0.0",
-              "from": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
-              "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
-            }
-          }
-        },
         "node-haste": {
-          "version": "1.2.8",
-          "from": "https://registry.npmjs.org/node-haste/-/node-haste-1.2.8.tgz",
-          "resolved": "https://registry.npmjs.org/node-haste/-/node-haste-1.2.8.tgz",
+          "version": "2.2.0",
+          "from": "node-haste@>=2.2.0 <3.0.0",
           "dependencies": {
-            "esprima-fb": {
-              "version": "4001.1001.0-dev-harmony-fb",
-              "from": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-4001.1001.0-dev-harmony-fb.tgz",
-              "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-4001.1001.0-dev-harmony-fb.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": {
+                "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"
+                    }
+                  }
+                },
+                "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"
+                    },
+                    "sigmund": {
+                      "version": "1.0.1",
+                      "from": "sigmund@>=1.0.0 <1.1.0",
+                      "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.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"
+                }
+              }
+            },
+            "throat": {
+              "version": "2.0.2",
+              "from": "throat@>=2.0.2 <3.0.0",
+              "resolved": "https://registry.npmjs.org/throat/-/throat-2.0.2.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",
+          "version": "1.2.4",
+          "from": "which@>=1.1.1 <2.0.0",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz",
           "dependencies": {
             "is-absolute": {
               "version": "0.1.7",
-              "from": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
+              "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": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
+                  "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"
             }
           }
         }
@@ -3338,11 +5076,21 @@
       "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",
@@ -3475,7 +5223,7 @@
     },
     "lodash": {
       "version": "3.10.1",
-      "from": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+      "from": "lodash@>=3.10.1 <4.0.0",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
     },
     "lodash._baseassign": {
@@ -3850,6 +5598,88 @@
         }
       }
     },
+    "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": {
+                "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"
+                },
+                "sigmund": {
+                  "version": "1.0.1",
+                  "from": "sigmund@>=1.0.0 <1.1.0",
+                  "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.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"
+            }
+          }
+        },
+        "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",
@@ -4058,6 +5888,18 @@
               }
             }
           }
+        },
+        "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"
+            }
+          }
         }
       }
     },
@@ -4418,7 +6260,14 @@
         "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"
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz",
+          "dependencies": {
+            "amdefine": {
+              "version": "1.0.0",
+              "from": "amdefine@>=0.0.4",
+              "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+            }
+          }
         }
       }
     },
@@ -4544,54 +6393,37 @@
       "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",
+      "version": "2.6.2",
+      "from": "uglify-js@2.6.2",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.2.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"
+          "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"
         },
         "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"
-            }
-          }
+          "version": "3.10.0",
+          "from": "yargs@>=3.10.0 <3.11.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.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"
         }
       }
     },
-    "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"
+    "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"
     },
     "user-home": {
       "version": "1.1.1",
diff --git a/package.json b/package.json
index 8e606d2fc6b544..b1b8080c7ce468 100644
--- a/package.json
+++ b/package.json
@@ -21,8 +21,23 @@
     "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",
@@ -68,20 +83,25 @@
       "downstream/core/nativeRequestAnimationFrame.js",
       "downstream/core/toArray.js",
       "node_modules/jest-cli",
-      "node_modules/react/dist"
+      "node_modules/react/dist",
+      "/node_modules/fbjs/.*/__mocks__/",
+      "<rootDir>/website/"
     ],
     "testFileExtensions": [
       "js"
     ],
     "unmockedModulePathPatterns": [
       "promise",
-      "source-map"
+      "source-map",
+      "fast-path",
+      "fbjs"
     ]
   },
   "main": "Libraries/react-native/react-native.js",
   "files": [
     "React",
     "React.podspec",
+    "ReactAndroid",
     "android",
     "Libraries",
     "packager",
@@ -96,6 +116,7 @@
   ],
   "scripts": {
     "test": "NODE_ENV=test jest",
+    "flow": "flow",
     "lint": "eslint Examples/ Libraries/",
     "start": "/usr/bin/env bash -c './packager/packager.sh \"$@\" || true' --"
   },
@@ -108,30 +129,34 @@
   "dependencies": {
     "absolute-path": "^0.0.0",
     "art": "^0.10.0",
-    "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",
+    "babel-core": "^6.6.4",
+    "babel-plugin-external-helpers": "^6.5.0",
+    "babel-polyfill": "^6.6.1",
+    "babel-preset-react-native": "^1.5.1",
+    "babel-register": "^6.6.0",
+    "babel-types": "^6.6.4",
+    "babylon": "^6.6.4",
     "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": "^0.6.0",
+    "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",
+    "lodash": "^3.10.1",
     "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",
@@ -145,8 +170,7 @@
     "source-map": "^0.4.4",
     "stacktrace-parser": "^0.1.3",
     "temp": "0.8.3",
-    "uglify-js": "^2.4.24",
-    "underscore": "^1.8.3",
+    "uglify-js": "^2.6.2",
     "wordwrap": "^1.0.0",
     "worker-farm": "^1.3.1",
     "ws": "^0.8.0",
@@ -155,10 +179,13 @@
     "yeoman-generator": "^0.20.3"
   },
   "devDependencies": {
-    "jest-cli": "0.8.2",
-    "babel-eslint": "4.1.4",
-    "eslint": "1.3.1",
-    "eslint-plugin-react": "3.3.1",
-    "portfinder": "0.4.0"
+    "babel-eslint": "^5.0.0",
+    "eslint": "~2.2.0",
+    "eslint-plugin-flow-vars": "^0.2.1",
+    "eslint-plugin-react": "^4.2.1",
+    "flow-bin": "0.22.0",
+    "jest-cli": "0.9.0-fb2",
+    "portfinder": "0.4.0",
+    "react": "^0.14.5"
   }
 }
diff --git a/packager/README.md b/packager/README.md
index 039865ba6e1402..8010cc38365048 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
diff --git a/packager/blacklist.js b/packager/blacklist.js
index ee369e1af7266d..823ab156bba850 100644
--- a/packager/blacklist.js
+++ b/packager/blacklist.js
@@ -17,57 +17,7 @@ var sharedBlacklist = [
   '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.
-  //
-  // fbjs forks:
-  'node_modules/fbjs/lib/Map.js',
-  'node_modules/fbjs/lib/Promise.js',
-  'node_modules/fbjs/lib/fetch.js',
-  // fbjs stubs:
-  'node_modules/fbjs/lib/ErrorUtils.js',
-  'node_modules/fbjs/lib/URI.js',
-  // fbjs modules:
-  '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',
-
-  // Those conflicts with the ones in fbjs/. 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',
 
   /website\/node_modules\/.*/,
 
diff --git a/packager/package.json b/packager/package.json
index 117369a73bf130..ab25c5cf2e406b 100644
--- a/packager/package.json
+++ b/packager/package.json
@@ -1,5 +1,5 @@
 {
-  "version": "0.1.3",
+  "version": "0.2.0",
   "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 66ecea85464ca9..9e6ffbe438c997 100755
--- a/packager/react-native-xcode.sh
+++ b/packager/react-native-xcode.sh
@@ -10,15 +10,15 @@
 # This script is supposed to be invoked as part of Xcode build process
 # and relies on environment variables (including PWD) set by Xcode
 
-# There is no point in creating an offline package for simulator builds
-# because the packager is supposed to be running during development anyways
-if [[ "$PLATFORM_NAME" = "iphonesimulator" ]]; then
-  echo "Skipping bundling for Simulator platform"
-  exit 0;
-fi
-
 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
     ;;
   "")
diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js
index 5b28e7741a2b93..dce805c8b95efa 100644
--- a/packager/react-packager/index.js
+++ b/packager/react-packager/index.js
@@ -10,10 +10,10 @@
 
 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;
@@ -117,6 +117,16 @@ function createNonPersistentServer(options) {
   return createServer(options);
 }
 
+function omit(obj, blacklistedKeys) {
+  return Object.keys(obj).reduce((clone, key) => {
+    if (blacklistedKeys.indexOf(key) === -1) {
+      clone[key] = obj[key];
+    }
+
+    return clone;
+  }, {});
+}
+
 // we need to listen on a socket as soon as a server is created, but only once.
 // This file also serves as entry point when spawning a socket server; in that
 // case we need to start the server immediately.
diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js
index 1cb90b334bcc8b..15bfc0ce05fb2c 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({
diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js
index bb411ce6ff4f74..71356acf38f6ab 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 9700af0886311f..2a947f2914708b 100644
--- a/packager/react-packager/src/Bundler/Bundle.js
+++ b/packager/react-packager/src/Bundler/Bundle.js
@@ -8,56 +8,47 @@
  */
 'use strict';
 
-const _ = require('underscore');
+const _ = require('lodash');
 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 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) {
+  constructor({sourceMapUrl, minify} = {}) {
     super();
     this._sourceMap = false;
     this._sourceMapUrl = sourceMapUrl;
     this._shouldCombineSourceMaps = false;
     this._numPrependedModules = 0;
     this._numRequireCalls = 0;
+    this._minify = minify;
   }
 
-  addModule(resolver, response, module, transformed) {
-    return resolver.wrapModule(
-      response,
+  addModule(resolver, resolutionResponse, module, moduleTransport) {
+    const index = super.addModule(moduleTransport);
+    return resolver.wrapModule({
+      resolutionResponse,
       module,
-      transformed.code
-    ).then(({code, name}) => {
-      const moduleTransport = new ModuleTransport({
-        code,
-        name,
-        map: transformed.map,
-        sourceCode: transformed.sourceCode,
-        sourcePath: transformed.sourcePath,
-        virtual: transformed.virtual,
-      });
-
+      name: moduleTransport.name,
+      code: moduleTransport.code,
+      map: moduleTransport.map,
+      meta: moduleTransport.meta,
+      minify: this._minify,
+    }).then(({code, map}) => {
       // 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) {
+      if (!this._shouldCombineSourceMaps && map != null) {
         this._shouldCombineSourceMaps = true;
       }
 
-      super.addModule(moduleTransport);
+      this.replaceModuleAt(
+        index, new ModuleTransport({...moduleTransport, code, map}));
     });
   }
 
@@ -103,10 +94,6 @@ class Bundle extends BundleBase {
 
     options = options || {};
 
-    if (options.minify) {
-      return this.getMinifiedSourceAndMap(options.dev).code;
-    }
-
     let source = super.getSource();
 
     if (options.inlineSourceMap) {
@@ -118,84 +105,22 @@ class Bundle extends BundleBase {
     return source;
   }
 
-  getUnbundle({minify}) {
-    const allModules = super.getModules().slice();
+  getUnbundle() {
+    const allModules = this.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');
+    const startupCode = allModules.map(getCode).join('\n');
 
     return {
       startupCode,
-      modules:
-        modules.map(minify ? getNameAndMinifiedCode : getNameAndCode)
+      modules: modules.map(getNameAndCode)
     };
   }
 
-  getMinifiedSourceAndMap(dev) {
-    super.assertFinalized();
-
-    if (this._minifiedSourceAndMap) {
-      return this._minifiedSourceAndMap;
-    }
-
-    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: this._sourceMapUrl,
-        inSourceMap: map,
-        output: {ascii_only: true},
-      });
-      Activity.endEvent(minifyActivity);
-      return this._minifiedSourceAndMap;
-    } catch(e) {
-      // Sometimes, when somebody is using a new syntax feature that we
-      // don't yet have transform for, the untransformed line is sent to
-      // uglify, and it chokes on it. This code tries to print the line
-      // and the module for easier debugging
-      let errorMessage = 'Error while minifying JS\n';
-      if (e.line) {
-        errorMessage += 'Transformed code line: "' +
-                        source.split('\n')[e.line - 1] + '"\n';
-      }
-      if (e.pos) {
-        let fromIndex = source.lastIndexOf('__d(\'', e.pos);
-        if (fromIndex > -1) {
-          fromIndex += '__d(\''.length;
-          const toIndex = source.indexOf('\'', fromIndex);
-          errorMessage += 'Module name (best guess): ' +
-                          source.substring(fromIndex, toIndex) + '\n';
-        }
-      }
-      errorMessage += e.toString();
-      throw new Error(errorMessage);
-    }
-  }
-
   /**
    * I found a neat trick in the sourcemap spec that makes it easy
    * to concat sourcemaps. The `sections` field allows us to combine
@@ -211,14 +136,17 @@ class Bundle extends BundleBase {
     };
 
     let line = 0;
-    super.getModules().forEach(function(module) {
+    this.getModules().forEach(module => {
       let map = module.map;
+
       if (module.virtual) {
         map = generateSourceMapForVirtualModule(module);
       }
 
       if (options.excludeSource) {
-        map = _.extend({}, map, {sourcesContent: []});
+        if (map.sourcesContent && map.sourcesContent.length) {
+          map = Object.assign({}, map, {sourcesContent: []});
+        }
       }
 
       result.sections.push({
@@ -234,25 +162,20 @@ class Bundle extends BundleBase {
   getSourceMap(options) {
     super.assertFinalized();
 
-    options = options || {};
-
-    if (options.minify) {
-      return this.getMinifiedSourceAndMap(options.dev).map;
-    }
-
     if (this._shouldCombineSourceMaps) {
       return this._getCombinedSourceMaps(options);
     }
 
     const mappings = this._getMappings();
+    const modules = this.getModules();
     const map = {
       file: this._getSourceMapFile(),
-      sources: _.pluck(super.getModules(), 'sourcePath'),
+      sources: modules.map(module => module.sourcePath),
       version: 3,
       names: [],
       mappings: mappings,
       sourcesContent: options.excludeSource
-    ? [] : _.pluck(super.getModules(), 'sourceCode')
+        ? [] : modules.map(module => module.sourceCode)
     };
     return map;
   }
@@ -316,12 +239,10 @@ class Bundle extends BundleBase {
   }
 
   getJSModulePaths() {
-    return super.getModules().filter(function(module) {
+    return this.getModules()
       // Filter out non-js files. Like images etc.
-      return !module.virtual;
-    }).map(function(module) {
-      return module.sourcePath;
-    });
+      .filter(module => !module.virtual)
+      .map(module => module.sourcePath);
   }
 
   getDebugInfo() {
@@ -338,7 +259,7 @@ class Bundle extends BundleBase {
       '}',
       '</style>',
       '<h3> Module paths and transformed code: </h3>',
-      super.getModules().map(function(m) {
+      this.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>';
@@ -354,15 +275,17 @@ class Bundle extends BundleBase {
       sourceMapUrl: this._sourceMapUrl,
       numPrependedModules: this._numPrependedModules,
       numRequireCalls: this._numRequireCalls,
+      shouldCombineSourceMaps: this._shouldCombineSourceMaps,
     };
   }
 
   static fromJSON(json) {
-    const bundle = new Bundle(json.sourceMapUrl);
+    const bundle = new Bundle({sourceMapUrl: json.sourceMapUrl});
 
     bundle._sourceMapUrl = json.sourceMapUrl;
     bundle._numPrependedModules = json.numPrependedModules;
     bundle._numRequireCalls = json.numRequireCalls;
+    bundle._shouldCombineSourceMaps = json.shouldCombineSourceMaps;
 
     BundleBase.fromJSON(bundle, json);
 
diff --git a/packager/react-packager/src/Bundler/BundleBase.js b/packager/react-packager/src/Bundler/BundleBase.js
index d3794a433c2f2c..472db21f13cc9e 100644
--- a/packager/react-packager/src/Bundler/BundleBase.js
+++ b/packager/react-packager/src/Bundler/BundleBase.js
@@ -8,7 +8,6 @@
  */
 'use strict';
 
-const _ = require('underscore');
 const ModuleTransport = require('../lib/ModuleTransport');
 
 class BundleBase {
@@ -32,11 +31,19 @@ class BundleBase {
   }
 
   addModule(module) {
-    if (!module instanceof ModuleTransport) {
+    if (!(module instanceof ModuleTransport)) {
       throw new Error('Expeceted a ModuleTransport object');
     }
 
-    this._modules.push(module);
+    return this._modules.push(module) - 1;
+  }
+
+  replaceModuleAt(index, module) {
+    if (!(module instanceof ModuleTransport)) {
+      throw new Error('Expeceted a ModuleTransport object');
+    }
+
+    this._modules[index] = module;
   }
 
   getModules() {
@@ -66,7 +73,7 @@ class BundleBase {
       return this._source;
     }
 
-    this._source = _.pluck(this._modules, 'code').join('\n');
+    this._source = this._modules.map((module) => module.code).join('\n');
     return this._source;
   }
 
diff --git a/packager/react-packager/src/Bundler/HMRBundle.js b/packager/react-packager/src/Bundler/HMRBundle.js
index 49a21dfd8bc7a7..90d6e3b3d570fa 100644
--- a/packager/react-packager/src/Bundler/HMRBundle.js
+++ b/packager/react-packager/src/Bundler/HMRBundle.js
@@ -8,7 +8,6 @@
  */
 'use strict';
 
-const _ = require('underscore');
 const BundleBase = require('./BundleBase');
 const ModuleTransport = require('../lib/ModuleTransport');
 
@@ -21,31 +20,26 @@ class HMRBundle extends BundleBase {
     this._sourceMappingURLs = [];
   }
 
-  addModule(resolver, response, module, transformed) {
-    return resolver.resolveRequires(response,
+  addModule(resolver, response, module, moduleTransport) {
+    return resolver.resolveRequires(
+      response,
       module,
-      transformed.code,
-    ).then(({name, code}) => {
-      // need to be in single line so that lines match on sourcemaps
-      code = `__accept(${JSON.stringify(name)}, function(global, require, module, exports) { ${code} });`;
-
-      const moduleTransport = new ModuleTransport({
-        code,
-        name,
-        map: transformed.map,
-        sourceCode: transformed.sourceCode,
-        sourcePath: transformed.sourcePath,
-        virtual: transformed.virtual,
-      });
-
-      super.addModule(moduleTransport);
+      moduleTransport.code,
+      moduleTransport.meta.dependencyOffsets,
+    ).then(code => {
+      super.addModule(new ModuleTransport({...moduleTransport, code}));
       this._sourceMappingURLs.push(this._sourceMappingURLFn(moduleTransport.sourcePath));
       this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath));
     });
   }
 
-  getModulesCode() {
-    return this._modules.map(module => module.code);
+  getModulesNamesAndCode() {
+    return this._modules.map(module => {
+      return {
+        name: JSON.stringify(module.name),
+        code: module.code,
+      };
+    });
   }
 
   getSourceURLs() {
diff --git a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
index 23fe5762dda7f8..173c9a3229e80a 100644
--- a/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
+++ b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js
@@ -14,14 +14,13 @@ 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');
 
 describe('Bundle', () => {
   var bundle;
 
   beforeEach(() => {
-    bundle = new Bundle('test_url');
+    bundle = new Bundle({sourceMapUrl: 'test_url'});
     bundle.getSourceMap = jest.genMockFn().mockImpl(() => {
       return 'test-source-map';
     });
@@ -109,33 +108,39 @@ describe('Bundle', () => {
       });
     });
 
-    pit('should get minified source', () => {
-      const minified = {
-        code: 'minified',
-        map: 'map',
-      };
+    fpit('should insert modules in a deterministic order, independent from timing of the wrapping process', () => {
+      const moduleTransports = [
+        createModuleTransport({name: 'module1'}),
+        createModuleTransport({name: 'module2'}),
+        createModuleTransport({name: 'module3'}),
+      ];
 
-      UglifyJS.minify = function() {
-        return minified;
+      const resolves = {};
+      const resolver = {
+        wrapModule({name}) {
+          return new Promise(resolve => resolves[name] = resolve);
+        }
       };
 
-      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);
+      console.log(bundle.addModule+'')
+      const promise = Promise.all(
+        moduleTransports.map(m => bundle.addModule(resolver, null, null, m)))
+      .then(() => {
+        expect(bundle.getModules())
+          .toEqual(moduleTransports);
       });
+
+      resolves.module2({code: ''});
+      resolves.module3({code: ''});
+      resolves.module1({code: ''});
+
+      return promise;
     });
   });
 
   describe('sourcemap bundle', () => {
     pit('should create sourcemap', () => {
-      const otherBundle = new Bundle('test_url');
+      const otherBundle = new Bundle({sourceMapUrl: 'test_url'});
 
       return Promise.resolve().then(() => {
         return addModule({
@@ -179,7 +184,7 @@ describe('Bundle', () => {
     });
 
     pit('should combine sourcemaps', () => {
-      const otherBundle = new Bundle('test_url');
+      const otherBundle = new Bundle({sourceMapUrl: 'test_url'});
 
       return Promise.resolve().then(() => {
         return addModule({
@@ -269,7 +274,7 @@ describe('Bundle', () => {
 
   describe('getAssets()', () => {
     it('should save and return asset objects', () => {
-      var p = new Bundle('test_url');
+      var p = new Bundle({sourceMapUrl: 'test_url'});
       var asset1 = {};
       var asset2 = {};
       p.addAsset(asset1);
@@ -281,7 +286,7 @@ describe('Bundle', () => {
 
   describe('getJSModulePaths()', () => {
     pit('should return module paths', () => {
-      var otherBundle = new Bundle('test_url');
+      var otherBundle = new Bundle({sourceMapUrl: 'test_url'});
       return Promise.resolve().then(() => {
         return addModule({
           bundle: otherBundle,
@@ -305,7 +310,7 @@ describe('Bundle', () => {
 
   describe('getEtag()', function() {
     it('should return an etag', function() {
-      var bundle = new Bundle('test_url');
+      var bundle = new Bundle({sourceMapUrl: 'test_url'});
       bundle.finalize({});
       var eTag = crypto.createHash('md5').update(bundle.getSource()).digest('hex');
       expect(bundle.getEtag()).toEqual(eTag);
@@ -365,19 +370,26 @@ function genSourceMap(modules) {
   return sourceMapGen.toJSON();
 }
 
-function resolverFor(code) {
+function resolverFor(code, map) {
   return {
-    wrapModule: (response, module, sourceCode) => Promise.resolve(
-      {name: 'name', code}
-    ),
+    wrapModule: () => Promise.resolve({code, map}),
   };
 }
 
 function addModule({bundle, code, sourceCode, sourcePath, map, virtual}) {
   return bundle.addModule(
-    resolverFor(code),
+    resolverFor(code, map),
     null,
     null,
-    {sourceCode, sourcePath, map, virtual}
+    createModuleTransport({code, sourceCode, sourcePath, map, virtual})
   );
 }
+
+function createModuleTransport(data) {
+  return new ModuleTransport({
+    code: '',
+    sourceCode: '',
+    sourcePath: '',
+    ...data,
+  });
+}
diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
index 5ed7d65877d88f..ac37db8968944d 100644
--- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
+++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js
@@ -10,7 +10,8 @@
 
 jest
   .setMock('worker-farm', () => () => undefined)
-  .dontMock('underscore')
+  .dontMock('node-haste/node_modules/throat')
+  .dontMock('lodash')
   .dontMock('../../lib/ModuleTransport')
   .setMock('uglify-js')
   .dontMock('../');
@@ -18,7 +19,6 @@ jest
 jest.mock('fs');
 
 var Bundler = require('../');
-var JSTransformer = require('../../JSTransformer');
 var Resolver = require('../../Resolver');
 var sizeOf = require('image-size');
 var fs = require('fs');
@@ -42,25 +42,29 @@ describe('Bundler', function() {
       isJSON() { return isJSON; },
       isAsset() { return isAsset; },
       isAsset_DEPRECATED() { return isAsset_DEPRECATED; },
+      read: () => ({
+        code: 'arbitrary',
+        source: 'arbitrary',
+      }),
     };
   }
 
   var getDependencies;
   var getModuleSystemDependencies;
-  var wrapModule;
   var bundler;
   var assetServer;
   var modules;
+  var projectRoots;
 
   beforeEach(function() {
     getDependencies = jest.genMockFn();
     getModuleSystemDependencies = jest.genMockFn();
-    wrapModule = jest.genMockFn();
+    projectRoots = ['/root'];
+
     Resolver.mockImpl(function() {
       return {
         getDependencies: getDependencies,
         getModuleSystemDependencies: getModuleSystemDependencies,
-        wrapModule: wrapModule,
       };
     });
 
@@ -79,7 +83,7 @@ describe('Bundler', function() {
     };
 
     bundler = new Bundler({
-      projectRoots: ['/root'],
+      projectRoots,
       assetServer: assetServer,
     });
 
@@ -108,34 +112,18 @@ describe('Bundler', function() {
       }),
     ];
 
-    getDependencies.mockImpl(function() {
-      return Promise.resolve({
+    getDependencies.mockImpl((main, options, transformOptions) =>
+      Promise.resolve({
         mainModuleId: 'foo',
-        dependencies: modules
-      });
-    });
+        dependencies: modules,
+        transformOptions,
+      })
+    );
 
     getModuleSystemDependencies.mockImpl(function() {
       return [];
     });
 
-    JSTransformer.prototype.loadFileAndTransform
-      .mockImpl(function(path) {
-        return Promise.resolve({
-          code: 'transformed ' + path,
-          map: 'sourcemap ' + path,
-          sourceCode: 'source ' + path,
-          sourcePath: path
-        });
-      });
-
-    wrapModule.mockImpl(function(response, module, code) {
-      return module.getName().then(name => ({
-        name,
-        code: 'lol ' + code + ' lol'
-      }));
-    });
-
     sizeOf.mockImpl(function(path, cb) {
       cb(null, { width: 50, height: 100 });
     });
@@ -206,10 +194,20 @@ describe('Bundler', function() {
   });
 
   pit('gets the list of dependencies from the resolver', function() {
-    return bundler.getDependencies('/root/foo.js', true).then(() =>
+    const entryFile = '/root/foo.js';
+    return bundler.getDependencies({entryFile, recursive: true}).then(() =>
       expect(getDependencies).toBeCalledWith(
         '/root/foo.js',
         { dev: true, recursive: true },
+        { minify: false,
+          dev: true,
+          transform: {
+            dev: true,
+            hot: false,
+            projectRoots,
+          }
+        },
+        undefined,
       )
     );
   });
diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js
index a07d73f7ac5fc2..3c1c426dfabf64 100644
--- a/packager/react-packager/src/Bundler/index.js
+++ b/packager/react-packager/src/Bundler/index.js
@@ -13,8 +13,7 @@ const fs = require('fs');
 const path = require('path');
 const Promise = require('promise');
 const ProgressBar = require('progress');
-const BundlesLayout = require('../BundlesLayout');
-const Cache = require('../DependencyResolver/Cache');
+const Cache = require('node-haste').Cache;
 const Transformer = require('../JSTransformer');
 const Resolver = require('../Resolver');
 const Bundle = require('./Bundle');
@@ -27,7 +26,6 @@ const imageSize = require('image-size');
 const version = require('../../../../package.json').version;
 
 const sizeOf = Promise.denodeify(imageSize);
-const readFile = Promise.denodeify(fs.readFile);
 
 const noop = () => {};
 
@@ -83,10 +81,6 @@ const validateOpts = declareOpts({
     type: 'number',
     required: false,
   },
-  disableInternalTransforms: {
-    type: 'boolean',
-    default: false,
-  },
 });
 
 class Bundler {
@@ -104,15 +98,28 @@ class Bundler {
       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,
-      cacheKey: [
-        'react-packager-cache',
-        version,
-        opts.cacheVersion,
-        opts.projectRoots.join(',').split(path.sep).join('-'),
-        mtime
-      ].join('$'),
+      cacheKey: cacheKeyParts.join('$'),
+    });
+
+    this._transformer = new Transformer({
+      transformModulePath: opts.transformModulePath,
     });
 
     this._resolver = new Resolver({
@@ -124,21 +131,10 @@ class Bundler {
       fileWatcher: opts.fileWatcher,
       assetExts: opts.assetExts,
       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,
+      transformCode:
+        (module, code, options) =>
+          this._transformer.transformFile(module.path, code, options),
+      minifyCode: this._transformer.minify,
     });
 
     this._projectRoots = opts.projectRoots;
@@ -156,24 +152,20 @@ class Bundler {
     return this._cache.end();
   }
 
-  getLayout(main, isDev) {
-    return this._bundlesLayout.generateLayout(main, isDev);
-  }
-
   bundle(options) {
-    const {dev, isUnbundle, platform} = options;
+    const {dev, minify, unbundle} = options;
     const moduleSystemDeps =
-      this._resolver.getModuleSystemDependencies({dev, isUnbundle, platform});
+      this._resolver.getModuleSystemDependencies({dev, unbundle});
     return this._bundle({
-      bundle: new Bundle(options.sourceMapUrl),
+      bundle: new Bundle({minify, sourceMapUrl: options.sourceMapUrl}),
       moduleSystemDeps,
       ...options,
     });
   }
 
-  _sourceHMRURL(platform, path) {
+  _sourceHMRURL(platform, host, port, path) {
     return this._hmrURL(
-      'http://localhost:8081', // TODO: (martinb) avoid hardcoding
+      `http://${host}:${port}`,
       platform,
       'bundle',
       path,
@@ -214,10 +206,10 @@ class Bundler {
     );
   }
 
-  hmrBundle(options) {
+  hmrBundle(options, host, port) {
     return this._bundle({
       bundle: new HMRBundle({
-        sourceURLFn: this._sourceHMRURL.bind(this, options.platform),
+        sourceURLFn: this._sourceHMRURL.bind(this, options.platform, host, port),
         sourceMappingURLFn: this._sourceMappingHMRURL.bind(
           this,
           options.platform,
@@ -233,7 +225,8 @@ class Bundler {
     entryFile,
     runModule: runMainModule,
     runBeforeMainModule,
-    dev: isDev,
+    dev,
+    minify,
     platform,
     moduleSystemDeps = [],
     hot,
@@ -267,7 +260,8 @@ class Bundler {
 
     return this._buildBundle({
       entryFile,
-      isDev,
+      dev,
+      minify,
       platform,
       bundle,
       hot,
@@ -282,7 +276,7 @@ class Bundler {
     runModule: runMainModule,
     runBeforeMainModule,
     sourceMapUrl,
-    dev: isDev,
+    dev,
     platform,
   }) {
     const onModuleTransformed = ({module, transformed, response, bundle}) => {
@@ -306,17 +300,19 @@ class Bundler {
 
     return this._buildBundle({
       entryFile,
-      isDev,
+      dev,
       platform,
       onModuleTransformed,
       finalizeBundle,
+      minify: false,
       bundle: new PrepackBundle(sourceMapUrl),
     });
   }
 
   _buildBundle({
     entryFile,
-    isDev,
+    dev,
+    minify,
     platform,
     bundle,
     hot,
@@ -326,54 +322,54 @@ class Bundler {
     finalizeBundle = noop,
   }) {
     const findEventId = Activity.startEvent('find dependencies');
+
     if (!resolutionResponse) {
-      resolutionResponse = this.getDependencies(entryFile, isDev, platform);
+      let onProgess;
+      if (process.stdout.isTTY) {
+        const bar = new ProgressBar(
+          'transformed :current/:total (:percent)',
+          {complete: '=', incomplete: ' ', width: 40, total: 1},
+        );
+        onProgess = (_, total) => {
+          bar.total = total;
+          bar.tick();
+        };
+      }
+
+      resolutionResponse = this.getDependencies(
+        {entryFile, dev, platform, hot, onProgess, minify});
     }
 
     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,
+      const toModuleTransport = module =>
+        this._toModuleTransport({
+          module,
+          bundle,
+          transformOptions: response.transformOptions,
+        }).then(transformed => {
+          onModuleTransformed({
             module,
-            platform,
-            dev: isDev,
-            hot
-          }).then(transformed => {
-            bar.tick();
-            onModuleTransformed({module, transformed, response, bundle});
-            return {module, transformed};
-          })
+            response,
+            bundle,
+            transformed,
+          });
+          return {module, transformed};
+        });
+
+      return Promise.all(response.dependencies.map(toModuleTransport))
+        .then(transformedModules =>
+          Promise
+            .resolve(finalizeBundle({bundle, transformedModules, response}))
+            .then(() => bundle)
         );
-      return Promise.all(transformPromises).then(transformedModules => {
-        Activity.endEvent(transformEventId);
-        return Promise
-          .resolve(finalizeBundle({bundle, transformedModules, response}))
-          .then(() => bundle);
-      });
     });
   }
 
   invalidateFile(filePath) {
-    if (this._transformOptionsModule) {
-      this._transformOptionsModule.onFileChange &&
-        this._transformOptionsModule.onFileChange();
-    }
-
-    this._transformer.invalidateFile(filePath);
+    this._cache.invalidate(filePath);
   }
 
   getShallowDependencies(entryFile) {
@@ -388,19 +384,35 @@ class Bundler {
     return this._resolver.getModuleForPath(entryFile);
   }
 
-  getDependencies(main, isDev, platform, recursive = true) {
-    return this._resolver.getDependencies(
-      main,
-      {
-        dev: isDev,
+  getDependencies({
+    entryFile,
+    platform,
+    dev = true,
+    minify = !dev,
+    hot = false,
+    recursive = true,
+    onProgess,
+  }) {
+    return this.getTransformOptions(
+      entryFile, {dev, platform, hot, projectRoots: this._projectRoots}
+    ).then(transformSpecificOptions => {
+      const transformOptions = {
+        minify,
+        dev,
         platform,
-        recursive,
-      },
-    );
+        transform: transformSpecificOptions,
+      };
+      return this._resolver.getDependencies(
+        entryFile,
+        {dev, platform, recursive},
+        transformOptions,
+        onProgess,
+      );
+    });
   }
 
   getOrderedDependencyPaths({ entryFile, dev, platform }) {
-    return this.getDependencies(entryFile, dev, platform).then(
+    return this.getDependencies({entryFile, dev, platform}).then(
       ({ dependencies }) => {
         const ret = [];
         const promises = [];
@@ -431,36 +443,32 @@ class Bundler {
     );
   }
 
-  _transformModule({
-    bundle,
-    module,
-    mainModuleName,
-    platform = null,
-    dev = true,
-    hot = false,
-  }) {
+  _toModuleTransport({module, bundle, transformOptions}) {
+    let moduleTransport;
     if (module.isAsset_DEPRECATED()) {
-      return this._generateAssetModule_DEPRECATED(bundle, module);
+      moduleTransport = this._generateAssetModule_DEPRECATED(bundle, module);
     } else if (module.isAsset()) {
-      return this._generateAssetModule(bundle, module, platform);
-    } else if (module.isJSON()) {
-      return generateJSONModule(module);
-    } else {
-      return this._getTransformOptions(
-        {
-          bundleEntry: mainModuleName,
-          platform: platform,
-          dev: dev,
-          modulePath: module.path,
-        },
-        {hot},
-      ).then(options => {
-        return this._transformer.loadFileAndTransform(
-          path.resolve(module.path),
-          options,
-        );
-      });
+      moduleTransport = this._generateAssetModule(
+        bundle, module, transformOptions.platform);
+    }
+
+    if (moduleTransport) {
+      return Promise.resolve(moduleTransport);
     }
+
+    return Promise.all([
+      module.getName(),
+      module.read(transformOptions),
+    ]).then((
+      [name, {code, dependencies, dependencyOffsets, map, source}]
+    ) => new ModuleTransport({
+      name,
+      code,
+      map,
+      meta: {dependencies, dependencyOffsets},
+      sourceCode: source,
+      sourcePath: module.path
+    }));
   }
 
   getGraphDebugInfo() {
@@ -483,9 +491,10 @@ class Bundler {
 
       bundle.addAsset(img);
 
-      const code = 'module.exports = ' + JSON.stringify(img) + ';';
+      const code = 'module.exports=' + JSON.stringify(img) + ';';
 
       return new ModuleTransport({
+        name: id,
         code: code,
         sourceCode: code,
         sourcePath: module.path,
@@ -528,19 +537,31 @@ class Bundler {
         type: assetData.type,
       };
 
-      const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
-      const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(asset));
+      const json = JSON.stringify(asset);
+      const code =
+        `module.exports = require('AssetRegistry').registerAsset(${json});`;
+      const dependencies = ['AssetRegistry'];
+      const dependencyOffsets = [code.indexOf('AssetRegistry') - 1];
 
-      return {asset, code};
+      return {
+        asset,
+        code,
+        meta: {dependencies, dependencyOffsets}
+      };
     });
   }
 
 
   _generateAssetModule(bundle, module, platform = null) {
-    return this._generateAssetObjAndCode(module, platform).then(({asset, code}) => {
+    return Promise.all([
+      module.getName(),
+      this._generateAssetObjAndCode(module, platform),
+    ]).then(([name, {asset, code, meta}]) => {
       bundle.addAsset(asset);
       return new ModuleTransport({
-        code: code,
+        name,
+        code,
+        meta,
         sourceCode: code,
         sourcePath: module.path,
         virtual: true,
@@ -548,37 +569,15 @@ 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};
-    });
+  getTransformOptions(mainModuleName, options) {
+    const extraOptions = this._transformOptionsModule
+      ? this._transformOptionsModule(mainModuleName, options, this)
+      : null;
+    return Promise.resolve(extraOptions)
+      .then(extraOptions => Object.assign(options, extraOptions));
   }
 }
 
-function generateJSONModule(module) {
-  return readFile(module.path).then(function(data) {
-    const code = 'module.exports = ' + data.toString('utf8') + ';';
-
-    return new ModuleTransport({
-      code: code,
-      sourceCode: code,
-      sourcePath: module.path,
-      virtual: true,
-    });
-  });
-}
-
 function getPathRelativeToRoot(roots, absPath) {
   for (let i = 0; i < roots.length; i++) {
     const relPath = path.relative(roots[i], absPath);
@@ -597,12 +596,4 @@ function verifyRootExists(root) {
   assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
 }
 
-class DummyCache {
-  get(filepath, field, loaderCb) {
-    return loaderCb();
-  }
-
-  end(){}
-  invalidate(filepath){}
-}
 module.exports = Bundler;
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 dafdb3680ae1b1..00000000000000
--- 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('../../DependencyResolver/Cache/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 00868b22908df6..00000000000000
--- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js
+++ /dev/null
@@ -1,599 +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('../../DependencyResolver/Cache')
-  .mock('../../Activity');
-
-const Promise = require('promise');
-const path = require('path');
-
-jest.mock('fs');
-
-var BundlesLayout = require('../index');
-var Cache = require('../../DependencyResolver/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/Object.es7.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 xindex
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            require("xa");`,
-          'a.js': `
-            /**
-             * @providesModule xa
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");
-            ${'System.import'}("xb");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */`,
-          'b.js': `
-            /**
-             * @providesModule xb
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            require("xa");
-            ${'System.import'}("xb");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */`,
-          'b.js': `
-            /**
-             * @providesModule xb
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */,
-            require("xb");`,
-          'b.js': `
-            /**
-             * @providesModule xb
-             */
-            require("xc");`,
-          'c.js': `
-            /**
-             * @providesModule xc
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");
-            ${'System.import'}("xb");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */,
-            require("xc");`,
-          'b.js': `
-            /**
-             * @providesModule xb
-             */
-            require("xc");`,
-          'c.js': `
-            /**
-             * @providesModule xc
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");`,
-          'a.js': `
-            /**,
-             * @providesModule xa
-             */,
-            ${'System.import'}("xb");`,
-          'b.js': `
-            /**
-             * @providesModule xb
-             */
-            require("xc");`,
-          'c.js': `
-            /**
-             * @providesModule xc
-             */`,
-        }
-      });
-
-      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 xindex
-             */
-            ${'System.import'}("xa");`,
-          'a.js':`
-            /**,
-             * @providesModule xa
-             */,
-            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 xindex
-             */
-            ${'System.import'}("xa");
-            ${'System.import'}("xb");`,
-          'a.js':`
-            /**,
-             * @providesModule xa
-             */,
-            require("./img.png");`,
-          'b.js':`
-            /**,
-             * @providesModule xb
-             */,
-            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 xindex
-             */
-            ${'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 xindex
-             */
-            ${'System.import'}("xa");`,
-          'a.js':`
-            /**,
-             * @providesModule xa
-             */,
-            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 xindex
-             */
-            ${'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 xindex
-             */
-            ${'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 2fd906232af502..00000000000000
--- a/packager/react-packager/src/BundlesLayout/index.js
+++ /dev/null
@@ -1,219 +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('../DependencyResolver/Cache/lib/getCacheFilePath');
-const loadCacheSync = require('../DependencyResolver/Cache/lib/loadCacheSync');
-const version = require('../../../../package.json').version;
-const path = require('path');
-const tmpdir = require('os').tmpDir();
-
-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(
-      tmpdir,
-      '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/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js
deleted file mode 100644
index f5555facd74451..00000000000000
--- 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 a77d0aef5e932c..00000000000000
--- 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/Cache/__tests__/Cache-test.js b/packager/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js
deleted file mode 100644
index 2de2318e454c2e..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Cache/__tests__/Cache-test.js
+++ /dev/null
@@ -1,335 +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('../')
-  .dontMock('../lib/loadCacheSync')
-  .dontMock('../lib/getCacheFilePath');
-
-jest
-  .mock('fs')
-  .setMock('os', {
-    tmpDir() { return 'tmpDir'; },
-  });
-
-var Promise = require('promise');
-var fs = require('graceful-fs');
-
-var Cache = require('../');
-
-describe('Cache', () => {
-  describe('getting/setting', () => {
-    pit('calls loader callback for uncached file', () => {
-      fs.stat.mockImpl((file, callback) => {
-        callback(null, {
-          mtime: {
-            getTime: () => {},
-          },
-        });
-      });
-
-      var cache = new Cache({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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({
-        cacheKey: 'cache',
-      });
-      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];
-
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => mtimes[index++],
-          },
-        })
-      );
-
-      var cache = new Cache({
-        cacheKey: 'cache',
-      });
-
-      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 has some trouble with promises and timeouts within promises :(
-      jest.runAllTimers();
-      jest.runAllTimers();
-
-      expect(fs.writeFile).toBeCalled();
-    });
-  });
-
-  describe('check for cache presence', () => {
-    it('synchronously resolves cache presence', () => {
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => {},
-          },
-        })
-      );
-
-      var cache = new Cache({
-        cacheKey: 'cache',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('banana')
-      );
-      var file = '/rootDir/someFile';
-
-      return cache
-        .get(file, 'field', loaderCb)
-        .then(() => {
-          expect(cache.has(file)).toBe(true);
-          expect(cache.has(file, 'field')).toBe(true);
-          expect(cache.has(file, 'foo')).toBe(false);
-        });
-    });
-  });
-
-  describe('invalidate', () => {
-    it('invalidates the cache per file or per-field', () => {
-      fs.stat.mockImpl((file, callback) =>
-        callback(null, {
-          mtime: {
-            getTime: () => {},
-          },
-        })
-      );
-
-      var cache = new Cache({
-        cacheKey: 'cache',
-      });
-      var loaderCb = jest.genMockFn().mockImpl(() =>
-        Promise.resolve('banana')
-      );
-      var file = '/rootDir/someFile';
-
-      return cache.get(file, 'field', loaderCb).then(() => {
-        expect(cache.has(file)).toBe(true);
-        cache.invalidate(file, 'field');
-        expect(cache.has(file)).toBe(true);
-        expect(cache.has(file, 'field')).toBe(false);
-        cache.invalidate(file);
-        expect(cache.has(file)).toBe(false);
-      });
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/Cache/index.js b/packager/react-packager/src/DependencyResolver/Cache/index.js
deleted file mode 100644
index 4e1698cb3fda7e..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Cache/index.js
+++ /dev/null
@@ -1,192 +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 fs = require('graceful-fs');
-const getCacheFilePath = require('./lib/getCacheFilePath');
-const isAbsolutePath = require('absolute-path');
-const loadCacheSync = require('./lib/loadCacheSync');
-const tmpDir = require('os').tmpDir();
-
-function getObjectValues(object) {
-  return Object.keys(object).map(key => object[key]);
-}
-
-function debounce(fn, delay) {
-  var timeout;
-  return () => {
-    clearTimeout(timeout);
-    timeout = setTimeout(fn, delay);
-  };
-}
-
-class Cache {
-  constructor({
-    resetCache,
-    cacheKey,
-    cacheDirectory = tmpDir,
-  }) {
-    this._cacheFilePath = getCacheFilePath(cacheDirectory, cacheKey);
-    if (!resetCache) {
-      this._data = this._loadCacheSync(this._cacheFilePath);
-    } else {
-      this._data = Object.create(null);
-    }
-
-    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, field) {
-    if (this.has(filepath, field)) {
-      if (field == null) {
-        delete this._data[filepath];
-      } else {
-        delete this._data[filepath].data[field];
-      }
-    }
-  }
-
-  end() {
-    return this._persistCache();
-  }
-
-  has(filepath, field) {
-    return Object.prototype.hasOwnProperty.call(this._data, filepath) &&
-      (field == null || 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;
-    }
-
-    const data = this._data;
-    const cacheFilepath = this._cacheFilePath;
-
-    const allPromises = getObjectValues(data)
-      .map(record => {
-        const fieldNames = Object.keys(record.data);
-        const fieldValues = getObjectValues(record.data);
-
-        return Promise
-          .all(fieldValues)
-          .then(ref => {
-            const 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 => {
-        const json = Object.create(null);
-        Object.keys(data).forEach((key, i) => {
-          // make sure the key wasn't added nor removed after we started
-          // persisting the cache
-          const value = values[i];
-          if (!value) {
-            return;
-          }
-
-          json[key] = Object.create(null);
-          json[key].metadata = data[key].metadata;
-          json[key].data = value.data;
-        });
-        return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json));
-      })
-      .catch(e => console.error('Error while persisting cache:', e.message))
-      .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;
-  }
-}
-
-module.exports = Cache;
diff --git a/packager/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js b/packager/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js
deleted file mode 100644
index 3975b65a34b2a2..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Cache/lib/getCacheFilePath.js
+++ /dev/null
@@ -1,20 +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');
-
-function getCacheFilePath(tmpdir, ...args) {
-  const hash = crypto.createHash('md5');
-  args.forEach(arg => hash.update(arg));
-  return path.join(tmpdir, hash.digest('hex'));
-}
-
-module.exports = getCacheFilePath;
diff --git a/packager/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js b/packager/react-packager/src/DependencyResolver/Cache/lib/loadCacheSync.js
deleted file mode 100644
index 87d6944c12673f..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Cache/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('graceful-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/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.js
deleted file mode 100644
index d7d9f20e578e6f..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/DependencyGraphHelpers.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 DependencyGraphHelpers {
-  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 = DependencyGraphHelpers;
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 5f18875ae1fdfe..00000000000000
--- 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 2f0b26af4773d8..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/HasteMap.js
+++ /dev/null
@@ -1,142 +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';
-const NATIVE_PLATFORM = 'native';
-
-class HasteMap {
-  constructor({
-    extensions,
-    fastfs,
-    moduleCache,
-    preferNativePlatform,
-    helpers,
-  }) {
-    this._extensions = extensions;
-    this._fastfs = fastfs;
-    this._moduleCache = moduleCache;
-    this._preferNativePlatform = preferNativePlatform;
-    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 platform is 'ios', we prefer .ios.js to .native.js which we prefer to
-    // a plain .js file.
-    let module = undefined;
-    if (module == null && platform != null) {
-      module = modulesMap[platform];
-    }
-    if (module == null && this._preferNativePlatform) {
-      module = modulesMap[NATIVE_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;
-    const existingModule = moduleMap[modulePlatform];
-
-    if (existingModule && existingModule.path !== mod.path) {
-      throw new Error(
-        `Naming collision detected: ${mod.path} ` +
-        `collides with ${existingModule.path}`
-      );
-    }
-
-    moduleMap[modulePlatform] = mod;
-  }
-}
-
-module.exports = HasteMap;
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 879c53efa59f55..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionRequest.js
+++ /dev/null
@@ -1,462 +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,
-    preferNativePlatform,
-    entryPath,
-    hasteMap,
-    deprecatedAssetMap,
-    helpers,
-    moduleCache,
-    fastfs,
-    shouldThrowOnUnresolvedErrors,
-  }) {
-    this._platform = platform;
-    this._preferNativePlatform = preferNativePlatform;
-    this._entryPath = entryPath;
-    this._hasteMap = hasteMap;
-    this._deprecatedAssetMap = deprecatedAssetMap;
-    this._helpers = helpers;
-    this._moduleCache = moduleCache;
-    this._fastfs = fastfs;
-    this._shouldThrowOnUnresolvedErrors = shouldThrowOnUnresolvedErrors;
-    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._shouldThrowOnUnresolvedErrors(this._entryPath, this._platform)
-      ) {
-        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, recursive = true) {
-    return this._getAllMocks(mocksPattern).then(allMocks => {
-      const entry = this._moduleCache.getModule(this._entryPath);
-      const mocks = Object.create(null);
-      const visited = Object.create(null);
-      visited[entry.hash()] = true;
-
-      response.pushDependency(entry);
-      const collect = (mod) => {
-        return mod.getDependencies().then(
-          depNames => Promise.all(
-            depNames.map(name => this.resolveDependency(mod, name))
-          ).then((dependencies) => [depNames, dependencies])
-        ).then(([depNames, dependencies]) => {
-          if (allMocks) {
-            const list = [mod.getName()];
-            const pkg = mod.getPackage();
-            if (pkg) {
-              list.push(pkg.getName());
-            }
-            return Promise.all(list).then(names => {
-              names.forEach(name => {
-                if (allMocks[name] && !mocks[name]) {
-                  const mockModule =
-                    this._moduleCache.getModule(allMocks[name]);
-                  depNames.push(name);
-                  dependencies.push(mockModule);
-                  mocks[name] = allMocks[name];
-                }
-              });
-              return [depNames, dependencies];
-            });
-          }
-          return Promise.resolve([depNames, dependencies]);
-        }).then(([depNames, dependencies]) => {
-          let p = Promise.resolve();
-          const filteredPairs = [];
-
-          dependencies.forEach((modDep, i) => {
-            const name = depNames[i];
-            if (modDep == null) {
-              // It is possible to require mocks that don't have a real
-              // module backing them. If a dependency cannot be found but there
-              // exists a mock with the desired ID, resolve it and add it as
-              // a dependency.
-              if (allMocks && allMocks[name] && !mocks[name]) {
-                const mockModule = this._moduleCache.getModule(allMocks[name]);
-                mocks[name] = allMocks[name];
-                return filteredPairs.push([name, mockModule]);
-              }
-
-              debug(
-                'WARNING: Cannot find required module `%s` from module `%s`',
-                name,
-                mod.path
-              );
-              return false;
-            }
-            return filteredPairs.push([name, modDep]);
-          });
-
-          response.setResolvedDependencyPairs(mod, filteredPairs);
-
-          filteredPairs.forEach(([depName, modDep]) => {
-            p = p.then(() => {
-              if (!visited[modDep.hash()]) {
-                visited[modDep.hash()] = true;
-                response.pushDependency(modDep);
-                if (recursive) {
-                  return collect(modDep);
-                }
-              }
-              return null;
-            });
-          });
-
-          return p;
-        });
-      };
-
-      return collect(entry).then(() => response.setMocks(mocks));
-    });
-  }
-
-  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;
-    });
-  }
-
-  _resolveFileOrDir(fromModule, toModuleName) {
-    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)
-      )
-    );
-  }
-
-  _resolveNodeDependency(fromModule, toModuleName) {
-    if (toModuleName[0] === '.' || toModuleName[1] === '/') {
-      return this._resolveFileOrDir(fromModule, toModuleName);
-    } else {
-      return this._redirectRequire(fromModule, toModuleName).then(
-        realModuleName => {
-          if (realModuleName[0] === '.' || realModuleName[1] === '/') {
-            // derive absolute path /.../node_modules/fromModuleDir/realModuleName
-            const fromModuleParentIdx = fromModule.path.lastIndexOf('node_modules/') + 13;
-            const fromModuleDir = fromModule.path.slice(0, fromModule.path.indexOf('/', fromModuleParentIdx));
-            const absPath = path.join(fromModuleDir, realModuleName);
-            return this._resolveFileOrDir(fromModule, absPath);
-          }
-
-          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._preferNativePlatform &&
-                 this._fastfs.fileExists(potentialModulePath + '.native.js')) {
-        file = potentialModulePath + '.native.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,
-`Unable to find this module in its module map or any of the node_modules directories under ${potentialDirPath} and its parent directories
-
-This might be related to https://github.com/facebook/react-native/issues/4968
-To resolve try the following:
-  1. Clear watchman watches: \`watchman watch-del-all\`.
-  2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`.
-  3. Reset packager cache: \`rm -fr $TMPDIR/react-*\` or \`npm start -- --reset-cache\`.`,
-        );
-      }
-
-      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 c13895fbe1e91d..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/ResolutionResponse.js
+++ /dev/null
@@ -1,95 +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.numPrependedDependencies = 0;
-    this._mappings = Object.create(null);
-    this._finalized = false;
-  }
-
-  copy(properties) {
-    const {
-      dependencies = this.dependencies,
-      asyncDependencies = this.asyncDependencies,
-      mainModuleId = this.mainModuleId,
-      mocks = this.mocks,
-    } = properties;
-    return Object.assign(new this.constructor(), this, {
-      dependencies,
-      asyncDependencies,
-      mainModuleId,
-      mocks,
-    });
-  }
-
-  _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);
-    this.numPrependedDependencies += 1;
-  }
-
-  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 09a4454373e21e..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js
+++ /dev/null
@@ -1,4353 +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');
-
-const Promise = require('promise');
-const DependencyGraph = require('../index');
-const fs = require('graceful-fs');
-
-const mocksPattern = /(?:[\\/]|^)__mocks__[\\/]([^\/]+)\.js$/;
-
-describe('DependencyGraph', function() {
-  let defaults;
-
-  function getOrderedDependenciesAsJSON(dgraph, entry, platform, recursive = true) {
-    return dgraph.getDependencies(entry, platform, recursive)
-      .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'],
-      shouldThrowOnUnresolvedErrors: () => false,
-    };
-  });
-
-  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',
-            ' */',
-            'require("b")',
-          ].join('\n'),
-          'b.js': [
-            '/**',
-            ' * @providesModule b',
-            ' */',
-          ].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: ['b'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-            {
-              id: 'b',
-              path: '/root/b.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-              resolveDependency: undefined,
-            },
-          ]);
-      });
-    });
-
-    pit('should get shallow dependencies', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("a")',
-          ].join('\n'),
-          'a.js': [
-            '/**',
-            ' * @providesModule a',
-            ' */',
-            'require("b")',
-          ].join('\n'),
-          'b.js': [
-            '/**',
-            ' * @providesModule b',
-            ' */',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js', null, false).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: ['b'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: 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'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return dgraph.load().catch(err => {
-        expect(err.message).toEqual('Failed to build DependencyGraph: Naming collision detected: /root/b.js collides with /root/index.js');
-        expect(err.type).toEqual('DependencyGraphError');
-      });
-    });
-
-    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,
-            },
-          ]);
-      });
-    });
-
-    testBrowserField('browser');
-    testBrowserField('react-native');
-
-    function replaceBrowserField(json, fieldName) {
-      if (fieldName !== 'browser') {
-        json[fieldName] = json.browser;
-        delete json.browser;
-      }
-
-      return json;
-    }
-
-    function testBrowserField(fieldName) {
-      pit('should support simple browser field in packages ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                main: 'main.js',
-                browser: 'client.js',
-              }, fieldName)),
-              '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 ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                main: 'main.js',
-                browser: 'client',
-              }, fieldName)),
-              '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 ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                main: './main.js',
-                browser: {
-                  './main.js': './client.js',
-                },
-              }, fieldName)),
-              'main.js': 'some other code',
-              'client.js': 'some code',
-            },
-          },
-        });
-
-        var dgraph = new DependencyGraph({
-          ...defaults,
-          roots: [root],
-          assetExts: ['png', 'jpg'],
-        });
-        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 ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                main: './main.js',
-                browser: {
-                  './main': './client.js',
-                },
-              }, fieldName)),
-              'main.js': 'some other code',
-              'client.js': 'some code',
-            },
-          },
-        });
-
-        var dgraph = new DependencyGraph({
-          ...defaults,
-          roots: [root],
-          assetExts: ['png', 'jpg'],
-        });
-        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 ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                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',
-                },
-              }, fieldName)),
-              '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',
-            },
-          },
-        });
-
-        const 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 ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                browser: {
-                  'node-package': 'browser-package',
-                },
-              }, fieldName)),
-              '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,
-              },
-            ]);
-        });
-      });
-
-      pit('should support browser mapping of a package to a file ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                browser: {
-                  'node-package': './dir/browser.js',
-                },
-              }, fieldName)),
-              'index.js': 'require("./dir/ooga")',
-              'dir': {
-                'ooga.js': 'require("node-package")',
-                'browser.js': 'some browser code',
-              },
-              'node-package': {
-                'package.json': JSON.stringify({
-                  'name': 'node-package',
-                }),
-                'index.js': 'some node code',
-              },
-            },
-          },
-        });
-
-        const 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: ['./dir/ooga'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              { id: 'aPackage/dir/ooga.js',
-                path: '/root/aPackage/dir/ooga.js',
-                dependencies: ['node-package'],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-              { id: 'aPackage/dir/browser.js',
-                path: '/root/aPackage/dir/browser.js',
-                dependencies: [],
-                isAsset: false,
-                isAsset_DEPRECATED: false,
-                isJSON: false,
-                isPolyfill: false,
-                resolution: undefined,
-              },
-            ]);
-        });
-      });
-
-      pit('should support browser mapping for packages ("' + fieldName + '")', function() {
-        var root = '/root';
-        fs.__setMockFilesystem({
-          'root': {
-            'index.js': [
-              '/**',
-              ' * @providesModule index',
-              ' */',
-              'require("aPackage")',
-            ].join('\n'),
-            'aPackage': {
-              'package.json': JSON.stringify(replaceBrowserField({
-                name: 'aPackage',
-                browser: {
-                  'node-package': 'browser-package',
-                },
-              }, fieldName)),
-              '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,
-              },
-            ]);
-        });
-      });
-    }
-
-    pit('should fall back to browser mapping from react-native mapping', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("aPackage")',
-          ].join('\n'),
-          'aPackage': {
-            'package.json': JSON.stringify({
-              name: 'aPackage',
-              'react-native': {
-                'node-package': 'rn-package',
-              },
-            }),
-            'index.js': 'require("node-package")',
-            'node_modules': {
-              'node-package': {
-                'package.json': JSON.stringify({
-                  'name': 'node-package',
-                }),
-                'index.js': 'some node code',
-              },
-              'rn-package': {
-                'package.json': JSON.stringify({
-                  'name': 'rn-package',
-                  browser: {
-                    'nested-package': 'nested-browser-package',
-                  },
-                }),
-                'index.js': 'require("nested-package")',
-              },
-              'nested-browser-package': {
-                'package.json': JSON.stringify({
-                  'name': 'nested-browser-package',
-                }),
-                'index.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/index.js',
-              path: '/root/aPackage/index.js',
-              dependencies: ['node-package'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'rn-package/index.js',
-              path: '/root/aPackage/node_modules/rn-package/index.js',
-              dependencies: ['nested-package'],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            { id: 'nested-browser-package/index.js',
-              path: '/root/aPackage/node_modules/nested-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';
-      var otherRoot = '/anotherRoot';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'require("shouldWork");',
-            'require("dontWork");',
-            'require("wontWork");',
-            'require("ember");',
-            'require("internalVendoredPackage");',
-            'require("anotherIndex");',
-          ].join('\n'),
-          'node_modules': {
-            'react-haste': {
-              'package.json': JSON.stringify({
-                name: 'react-haste',
-                main: 'main.js',
-              }),
-              // @providesModule should not be ignored here, because react-haste is whitelisted
-              'main.js': [
-                '/**',
-                ' * @providesModule shouldWork',
-                ' */',
-                'require("submodule");',
-              ].join('\n'),
-              'node_modules': {
-                'bar': {
-                  'package.json': JSON.stringify({
-                    name: 'bar',
-                    main: 'main.js',
-                  }),
-                  // @providesModule should be ignored here, because it's not whitelisted
-                  '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',
-              }),
-              // @providesModule should be ignored here, because it's not whitelisted,
-              // and also, the modules "id" should be ember/main.js, not it's haste name
-              'main.js':[
-                '/**',
-                ' * @providesModule wontWork',
-                ' */',
-                'hi();',
-              ].join('\n'),
-            },
-          },
-          // This part of the dep graph is meant to emulate internal facebook infra.
-          // By whitelisting `vendored_modules`, haste should still work.
-          'vendored_modules': {
-            'a-vendored-package': {
-              'package.json': JSON.stringify({
-                name: 'a-vendored-package',
-                main: 'main.js',
-              }),
-              // @providesModule should _not_ be ignored here, because it's whitelisted.
-              'main.js':[
-                '/**',
-                ' * @providesModule internalVendoredPackage',
-                ' */',
-                'hiFromInternalPackage();',
-              ].join('\n'),
-            },
-          },
-        },
-        // we need to support multiple roots and using haste between them
-        'anotherRoot': {
-          'index.js': [
-            '/**',
-            ' * @providesModule anotherIndex',
-            ' */',
-            'wazup()',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root, otherRoot],
-      });
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
-        expect(deps)
-          .toEqual([
-            {
-              id: 'index',
-              path: '/root/index.js',
-              dependencies: [
-                'shouldWork',
-                'dontWork',
-                'wontWork',
-                'ember',
-                'internalVendoredPackage',
-                'anotherIndex',
-              ],
-              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,
-            },
-            {
-              id: 'ember/main.js',
-              path: '/root/node_modules/ember/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'internalVendoredPackage',
-              path: '/root/vendored_modules/a-vendored-package/main.js',
-              dependencies: [],
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isJSON: false,
-              isPolyfill: false,
-              resolution: undefined,
-            },
-            {
-              id: 'anotherIndex',
-              path: '/anotherRoot/index.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,
-              },
-            ]);
-        });
-      });
-    });
-
-    pit('should not error when the watcher reports a known file as added', function() {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          'index.js': [
-            '/**',
-            ' * @providesModule index',
-            ' */',
-            'var b = require("b");',
-          ].join('\n'),
-          'b.js': [
-            '/**',
-            ' * @providesModule b',
-            ' */',
-            'module.exports = function() {};',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-      });
-
-      return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
-        triggerFileChange('add', 'index.js', root, mockStat);
-        return getOrderedDependenciesAsJSON(dgraph, '/root/index.js');
-      });
-    });
-  });
-
-  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).toEqual({});
-        });
-    });
-
-    pit('retrieves a list of all required mocks', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          '__mocks__': {
-            'A.js': '',
-            'b.js': '',
-          },
-          'b.js': [
-            '/**',
-            ' * @providesModule b',
-            ' */',
-            'require("A");',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        mocksPattern,
-      });
-
-      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,
-      });
-
-      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'],
-            },
-            {
-              path: '/root/__mocks__/b.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: '/root/__mocks__/b.js',
-              dependencies: [],
-            },
-          ]);
-        });
-    });
-
-    pit('resolves mocks that do not have a real module associated with them', () => {
-      var root = '/root';
-      fs.__setMockFilesystem({
-        'root': {
-          '__mocks__': {
-            'foo.js': [
-              'require("b");',
-            ].join('\n'),
-            'b.js': '',
-          },
-          'A.js': [
-            '/**',
-            ' * @providesModule A',
-            ' */',
-            'require("foo");',
-          ].join('\n'),
-        },
-      });
-
-      var dgraph = new DependencyGraph({
-        ...defaults,
-        roots: [root],
-        mocksPattern,
-      });
-
-      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'],
-            },
-            {
-              path: '/root/__mocks__/foo.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: '/root/__mocks__/foo.js',
-              dependencies: ['b'],
-            },
-            {
-              path: '/root/__mocks__/b.js',
-              isJSON: false,
-              isAsset: false,
-              isAsset_DEPRECATED: false,
-              isPolyfill: false,
-              id: '/root/__mocks__/b.js',
-              dependencies: [],
-            },
-          ]);
-        });
-    });
-  });
-});
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 d710112a9587e9..00000000000000
--- 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 3c5ade661eb512..00000000000000
--- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js
+++ /dev/null
@@ -1,280 +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 DependencyGraphHelpers = require('./DependencyGraphHelpers');
-const ResolutionRequest = require('./ResolutionRequest');
-const ResolutionResponse = require('./ResolutionResponse');
-const HasteMap = require('./HasteMap');
-const DeprecatedAssetMap = require('./DeprecatedAssetMap');
-
-const ERROR_BUILDING_DEP_GRAPH = 'DependencyGraphError';
-
-const defaultActivity = {
-  startEvent: () => {},
-  endEvent: () => {},
-};
-
-class DependencyGraph {
-  constructor({
-    activity,
-    roots,
-    ignoreFilePath,
-    fileWatcher,
-    assetRoots_DEPRECATED,
-    assetExts,
-    providesModuleNodeModules,
-    platforms,
-    preferNativePlatform,
-    cache,
-    extensions,
-    mocksPattern,
-    extractRequires,
-    transformCode,
-    shouldThrowOnUnresolvedErrors = () => true,
-  }) {
-    this._opts = {
-      activity: activity || defaultActivity,
-      roots,
-      ignoreFilePath: ignoreFilePath || (() => {}),
-      fileWatcher,
-      assetRoots_DEPRECATED: assetRoots_DEPRECATED || [],
-      assetExts: assetExts || [],
-      providesModuleNodeModules,
-      platforms: platforms || [],
-      preferNativePlatform: preferNativePlatform || false,
-      extensions: extensions || ['js', 'json'],
-      mocksPattern,
-      extractRequires,
-      shouldThrowOnUnresolvedErrors,
-      transformCode,
-    };
-    this._cache = cache;
-    this._helpers = new DependencyGraphHelpers(this._opts);
-    this.load();
-  }
-
-  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({
-      fastfs: this._fastfs,
-      cache: this._cache,
-      extractRequires: this._opts.extractRequires,
-      transformCode: this._opts.transformCode,
-      depGraphHelpers: this._helpers,
-    });
-
-    this._hasteMap = new HasteMap({
-      fastfs: this._fastfs,
-      extensions: this._opts.extensions,
-      moduleCache: this._moduleCache,
-      preferNativePlatform: this._opts.preferNativePlatform,
-      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)
-    ).catch(err => {
-      const error = new Error(
-        `Failed to build DependencyGraph: ${err.message}`
-      );
-      error.type = ERROR_BUILDING_DEP_GRAPH;
-      error.stack = err.stack;
-      throw error;
-    });
-
-    return this._loading;
-  }
-
-  /**
-   * Returns a promise with the direct dependencies the module associated to
-   * the given entryPath has.
-   */
-  getShallowDependencies(entryPath) {
-    return this._moduleCache.getModule(entryPath).getDependencies();
-  }
-
-  getFS() {
-    return this._fastfs;
-  }
-
-  /**
-   * Returns the module object for the given path.
-   */
-  getModuleForPath(entryFile) {
-    return this._moduleCache.getModule(entryFile);
-  }
-
-  getAllModules() {
-    return this.load().then(() => this._moduleCache.getAllModules());
-  }
-
-  getDependencies(entryPath, platform, recursive = true) {
-    return this.load().then(() => {
-      platform = this._getRequestPlatform(entryPath, platform);
-      const absPath = this._getAbsolutePath(entryPath);
-      const req = new ResolutionRequest({
-        platform,
-        preferNativePlatform: this._opts.preferNativePlatform,
-        entryPath: absPath,
-        deprecatedAssetMap: this._deprecatedAssetMap,
-        hasteMap: this._hasteMap,
-        helpers: this._helpers,
-        moduleCache: this._moduleCache,
-        fastfs: this._fastfs,
-        shouldThrowOnUnresolvedErrors: this._opts.shouldThrowOnUnresolvedErrors,
-      });
-
-      const response = new ResolutionResponse();
-
-      return Promise.all([
-        req.getOrderedDependencies(
-          response,
-          this._opts.mocksPattern,
-          recursive,
-        ),
-        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/FileWatcher/__tests__/FileWatcher-test.js b/packager/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.js
deleted file mode 100644
index b0d2fd090bb7d7..00000000000000
--- a/packager/react-packager/src/DependencyResolver/FileWatcher/__tests__/FileWatcher-test.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';
-
-jest
-  .dontMock('util')
-  .dontMock('events')
-  .dontMock('../')
-  .setMock('child_process', {
-    exec: function(cmd, cb) {
-      cb(null, '/usr/bin/watchman');
-    },
-  });
-
-var sane = require('sane');
-
-describe('FileWatcher', function() {
-  var Watcher;
-  var FileWatcher;
-  var config;
-
-  beforeEach(function() {
-    Watcher = sane.WatchmanWatcher;
-    Watcher.prototype.once.mockImplementation(function(type, callback) {
-      callback();
-    });
-    FileWatcher = require('../');
-
-    config = [{
-      dir: 'rootDir',
-      globs: [
-        '**/*.js',
-        '**/*.json',
-      ],
-    }];
-  });
-
-  pit('it should get the watcher instance when ready', function() {
-    var fileWatcher = new FileWatcher(config);
-    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(config);
-    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(config);
-    Watcher.prototype.close.mockImplementation(function(callback) {
-      callback();
-    });
-
-    return fileWatcher.end().then(function() {
-      expect(Watcher.prototype.close).toBeCalled();
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/FileWatcher/index.js b/packager/react-packager/src/DependencyResolver/FileWatcher/index.js
deleted file mode 100644
index d75e02119c80f7..00000000000000
--- a/packager/react-packager/src/DependencyResolver/FileWatcher/index.js
+++ /dev/null
@@ -1,123 +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 MAX_WAIT_TIME = 120000;
-
-// TODO(amasad): can we use watchman version command instead?
-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() {
-    inited = false;
-    return this._loading.then(
-      (watchers) => watchers.map(
-        watcher => Promise.denodeify(watcher.close).call(watcher)
-      )
-    );
-  }
-
-  static createDummyWatcher() {
-    return Object.assign(new EventEmitter(), {
-      isWatchman: () => Promise.resolve(false),
-      end: () => Promise.resolve(),
-    });
-  }
-}
-
-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(timeoutMessage(Watcher)));
-      }, MAX_WAIT_TIME);
-
-      watcher.once('ready', function() {
-        clearTimeout(rejectTimeout);
-        resolve(watcher);
-      });
-    });
-  });
-}
-
-function timeoutMessage(Watcher) {
-  const lines = [
-    'Watcher took too long to load (' + Watcher.name + ')',
-  ];
-  if (Watcher === sane.WatchmanWatcher) {
-    lines.push(
-      'Try running `watchman version` from your terminal',
-      'https://facebook.github.io/watchman/docs/troubleshooting.html',
-    );
-  }
-  return lines.join('\n');
-}
-
-module.exports = FileWatcher;
diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js
deleted file mode 100644
index f9142611fed3e7..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Module.js
+++ /dev/null
@@ -1,220 +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 = extractRequires,
-    transformCode,
-    depGraphHelpers,
-  }) {
-    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;
-    this._transformCode = transformCode;
-    this._depGraphHelpers = depGraphHelpers;
-  }
-
-  isHaste() {
-    return this._cache.get(
-      this.path,
-      'isHaste',
-      () => this._readDocBlock().then(data => !!data.id)
-    );
-  }
-
-  getCode() {
-    return this.read().then(({code}) => code);
-  }
-
-  getName() {
-    return this._cache.get(
-      this.path,
-      'name',
-      () => this._readDocBlock().then(({id}) => {
-        if (id) {
-          return 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._cache.get(
-      this.path,
-      'dependencies',
-      () => this.read().then(data => data.dependencies)
-    );
-  }
-
-  getAsyncDependencies() {
-    return this._cache.get(
-      this.path,
-      'asyncDependencies',
-      () => this.read().then(data => data.asyncDependencies)
-    );
-  }
-
-  invalidate() {
-    this._cache.invalidate(this.path);
-  }
-
-  _parseDocBlock(docBlock) {
-    // Extract an id for the module if it's using @providesModule syntax
-    // and if it's NOT in node_modules (and not a whitelisted node_module).
-    // This handles the case where a project may have a dep that has @providesModule
-    // docblock comments, but doesn't want it to conflict with whitelisted @providesModule
-    // modules, such as react-haste, fbjs-haste, or react-native or with non-dependency,
-    // project-specific code that is using @providesModule.
-    const moduleDocBlock = docblock.parseAsObject(docBlock);
-    const provides = moduleDocBlock.providesModule || moduleDocBlock.provides;
-
-    const id = provides && !this._depGraphHelpers.isNodeModulesDir(this.path)
-        ? /^\S+/.exec(provides)[0]
-        : undefined;
-    return [id, moduleDocBlock];
-  }
-
-  _readDocBlock() {
-    const reading = this._reading || this._docBlock;
-    if (reading) {
-      return reading;
-    }
-    this._docBlock = this._fastfs.readWhile(this.path, whileInDocBlock)
-      .then(docBlock => {
-        const [id] = this._parseDocBlock(docBlock);
-        return {id};
-      });
-    return this._docBlock;
-  }
-
-  read() {
-    if (this._reading) {
-      return this._reading;
-    }
-
-    this._reading = this._fastfs.readFile(this.path).then(content => {
-      const [id, moduleDocBlock] = this._parseDocBlock(content);
-
-      // Ignore requires in JSON files or generated code. An example of this
-      // is prebuilt files like the SourceMap library.
-      if (this.isJSON() || 'extern' in moduleDocBlock) {
-        return {
-          id,
-          dependencies: [],
-          asyncDependencies: [],
-          code: content,
-        };
-      } else {
-        const transformCode = this._transformCode;
-        const codePromise = transformCode
-            ? transformCode(this, content)
-            : Promise.resolve({code: content});
-
-        return codePromise.then(({code, dependencies, asyncDependencies}) => {
-          const {deps} = this._extractor(code);
-          return {
-            id,
-            code,
-            dependencies: dependencies || deps.sync,
-            asyncDependencies: asyncDependencies || deps.async,
-          };
-        });
-      }
-    });
-
-    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,
-    };
-  }
-}
-
-function whileInDocBlock(chunk, i, result) {
-  // consume leading whitespace
-  if (!/\S/.test(result)) {
-    return true;
-  }
-
-  // check for start of doc block
-  if (!/^\s*\/(\*{2}|\*?$)/.test(result)) {
-    return false;
-  }
-
-  // check for end of doc block
-  return !/\*\//.test(result);
-}
-
-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 3fcfb173ad971a..00000000000000
--- a/packager/react-packager/src/DependencyResolver/ModuleCache.js
+++ /dev/null
@@ -1,107 +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,
-    transformCode,
-    depGraphHelpers,
-  }) {
-    this._moduleCache = Object.create(null);
-    this._packageCache = Object.create(null);
-    this._fastfs = fastfs;
-    this._cache = cache;
-    this._extractRequires = extractRequires;
-    this._transformCode = transformCode;
-    this._depGraphHelpers = depGraphHelpers;
-
-    fastfs.on('change', this._processFileChange.bind(this));
-  }
-
-  getModule(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._moduleCache[filePath]) {
-      this._moduleCache[filePath] = new Module({
-        file: filePath,
-        fastfs: this._fastfs,
-        moduleCache: this,
-        cache: this._cache,
-        extractor: this._extractRequires,
-        transformCode: this._transformCode,
-        depGraphHelpers: this._depGraphHelpers,
-      });
-    }
-    return this._moduleCache[filePath];
-  }
-
-  getAllModules() {
-    return this._moduleCache;
-  }
-
-  getAssetModule(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._moduleCache[filePath]) {
-      this._moduleCache[filePath] = new AssetModule({
-        file: filePath,
-        fastfs: this._fastfs,
-        moduleCache: this,
-        cache: this._cache,
-      });
-    }
-    return this._moduleCache[filePath];
-  }
-
-  getPackage(filePath) {
-    filePath = path.resolve(filePath);
-    if (!this._packageCache[filePath]) {
-      this._packageCache[filePath] = new Package({
-        file: filePath,
-        fastfs: this._fastfs,
-        cache: 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 e88b0640ccbcb2..00000000000000
--- a/packager/react-packager/src/DependencyResolver/Package.js
+++ /dev/null
@@ -1,100 +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 => {
-      var replacements = getReplacements(json);
-      if (typeof replacements === 'string') {
-        return path.join(this.root, replacements);
-      }
-
-      let main = json.main || 'index';
-
-      if (replacements && typeof replacements === 'object') {
-        main = replacements[main] ||
-          replacements[main + '.js'] ||
-          replacements[main + '.json'] ||
-          replacements[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 => {
-      var replacements = getReplacements(json);
-
-      if (!replacements || typeof replacements !== 'object') {
-        return name;
-      }
-
-      if (name[0] !== '/') {
-        return replacements[name] || name;
-      }
-
-      if (!isAbsolutePath(name)) {
-        throw new Error(`Expected ${name} to be absolute path`);
-      }
-
-      const relPath = './' + path.relative(this.root, name);
-      const redirect = replacements[relPath] ||
-              replacements[relPath + '.js'] ||
-              replacements[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;
-  }
-}
-
-function getReplacements(pkg) {
-  return pkg['react-native'] == null
-    ? pkg.browser
-    : pkg['react-native'];
-}
-
-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 97c57adb7e9c4d..00000000000000
--- 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({ file: 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 4ed5868b1860b6..00000000000000
--- a/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js
+++ /dev/null
@@ -1,324 +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 DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers');
-const Promise = require('promise');
-const fs = require('graceful-fs');
-
-function mockIndexFile(indexJs) {
-  fs.__setMockFilesystem({'root': {'index.js': indexJs}});
-}
-
-describe('Module', () => {
-  const fileWatcher = {
-    on: () =>  this,
-    isWatchman: () => Promise.resolve(false),
-  };
-  const fileName = '/root/index.js';
-
-  let cache, fastfs;
-
-  const createCache = () => ({
-    get: jest.genMockFn().mockImplementation(
-      (filepath, field, cb) => cb(filepath)
-    ),
-    invalidate: jest.genMockFn(),
-    end: jest.genMockFn(),
-  });
-
-  const createModule = (options) =>
-    new Module({
-      cache,
-      fastfs,
-      file: fileName,
-      depGraphHelpers: new DependencyGraphHelpers(),
-      moduleCache: new ModuleCache({fastfs, cache}),
-      ...options,
-    });
-
-  beforeEach(function(done) {
-    cache = createCache();
-    fastfs = new Fastfs(
-      'test',
-      ['/root'],
-      fileWatcher,
-      {crawling: Promise.resolve([fileName]), ignore: []},
-    );
-
-    fastfs.build().then(done);
-  });
-
-  describe('Module ID', () => {
-    const moduleId = 'arbitraryModule';
-    const source =
-    `/**
-       * @providesModule ${moduleId}
-       */
-    `;
-
-    let module;
-    beforeEach(() => {
-      module = createModule();
-    });
-
-    describe('@providesModule annotations', () => {
-      beforeEach(() => {
-        mockIndexFile(source);
-      });
-
-      pit('extracts the module name from the header', () =>
-        module.getName().then(name => expect(name).toEqual(moduleId))
-      );
-
-      pit('identifies the module as haste module', () =>
-        module.isHaste().then(isHaste => expect(isHaste).toBe(true))
-      );
-
-      pit('does not transform the file in order to access the name', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).getName()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-
-      pit('does not transform the file in order to access the haste status', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).isHaste()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-    });
-
-    describe('@provides annotations', () => {
-      beforeEach(() => {
-        mockIndexFile(source.replace(/@providesModule/, '@provides'));
-      });
-
-      pit('extracts the module name from the header if it has a @provides annotation', () =>
-        module.getName().then(name => expect(name).toEqual(moduleId))
-      );
-
-      pit('identifies the module as haste module', () =>
-        module.isHaste().then(isHaste => expect(isHaste).toBe(true))
-      );
-
-      pit('does not transform the file in order to access the name', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).getName()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-
-      pit('does not transform the file in order to access the haste status', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).isHaste()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-    });
-
-    describe('no annotation', () => {
-      beforeEach(() => {
-        mockIndexFile('arbitrary(code);');
-      });
-
-      pit('uses the file name as module name', () =>
-        module.getName().then(name => expect(name).toEqual(fileName))
-      );
-
-      pit('does not identify the module as haste module', () =>
-        module.isHaste().then(isHaste => expect(isHaste).toBe(false))
-      );
-
-      pit('does not transform the file in order to access the name', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).getName()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-
-      pit('does not transform the file in order to access the haste status', () => {
-        const transformCode =
-          jest.genMockFn().mockReturnValue(Promise.resolve());
-        return createModule({transformCode}).isHaste()
-          .then(() => expect(transformCode).not.toBeCalled());
-      });
-    });
-  });
-
-  describe('Async Dependencies', () => {
-    function expectAsyncDependenciesToEqual(expected) {
-      const module = createModule();
-      return module.getAsyncDependencies().then(actual =>
-        expect(actual).toEqual(expected)
-      );
-    }
-
-    pit('should recognize single dependency', () => {
-      mockIndexFile('System.' + 'import("dep1")');
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-
-    pit('should parse single quoted dependencies', () => {
-      mockIndexFile('System.' + 'import(\'dep1\')');
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-
-    pit('should parse multiple async dependencies on the same module', () => {
-      mockIndexFile([
-        'System.' + 'import("dep1")',
-        'System.' + 'import("dep2")',
-      ].join('\n'));
-
-      return expectAsyncDependenciesToEqual([
-        ['dep1'],
-        ['dep2'],
-      ]);
-    });
-
-    pit('parse fine new lines', () => {
-      mockIndexFile('System.' + 'import(\n"dep1"\n)');
-
-      return expectAsyncDependenciesToEqual([['dep1']]);
-    });
-  });
-
-  describe('Code', () => {
-    const fileContents = 'arbitrary(code)';
-    beforeEach(function() {
-      mockIndexFile(fileContents);
-    });
-
-    pit('exposes file contents as `code` property on the data exposed by `read()`', () =>
-      createModule().read().then(({code}) =>
-        expect(code).toBe(fileContents))
-    );
-
-    pit('exposes file contes via the `getCode()` method', () =>
-      createModule().getCode().then(code =>
-        expect(code).toBe(fileContents))
-    );
-
-    pit('does not save the code in the cache', () =>
-      createModule().getCode().then(() =>
-        expect(cache.get).not.toBeCalled()
-      )
-    );
-  });
-
-  describe('Extrators', () => {
-
-    pit('uses custom require extractors if specified', () => {
-      mockIndexFile('');
-      const module = createModule({
-        extractor: code => ({deps: {sync: ['foo', 'bar']}}),
-      });
-
-      return module.getDependencies().then(actual =>
-        expect(actual).toEqual(['foo', 'bar']));
-    });
-  });
-
-  describe('Custom Code Transform', () => {
-    let transformCode;
-    const fileContents = 'arbitrary(code);';
-    const exampleCode = `
-      require('a');
-      System.import('b');
-      require('c');`;
-
-    beforeEach(function() {
-      transformCode = jest.genMockFn();
-      mockIndexFile(fileContents);
-      transformCode.mockReturnValue(Promise.resolve({code: ''}));
-    });
-
-    pit('passes the module and file contents to the transform function when reading', () => {
-      const module = createModule({transformCode});
-      return module.read()
-        .then(() => {
-          expect(transformCode).toBeCalledWith(module, fileContents);
-        });
-    });
-
-    pit('uses the code that `transformCode` resolves to to extract dependencies', () => {
-      transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
-      const module = createModule({transformCode});
-
-      return Promise.all([
-        module.getDependencies(),
-        module.getAsyncDependencies(),
-      ]).then(([dependencies, asyncDependencies]) => {
-        expect(dependencies).toEqual(['a', 'c']);
-        expect(asyncDependencies).toEqual([['b']]);
-      });
-    });
-
-    pit('uses dependencies that `transformCode` resolves to, instead of extracting them', () => {
-      const mockedDependencies = ['foo', 'bar'];
-      transformCode.mockReturnValue(Promise.resolve({
-        code: exampleCode,
-        dependencies: mockedDependencies,
-      }));
-      const module = createModule({transformCode});
-
-      return Promise.all([
-        module.getDependencies(),
-        module.getAsyncDependencies(),
-      ]).then(([dependencies, asyncDependencies]) => {
-        expect(dependencies).toEqual(mockedDependencies);
-        expect(asyncDependencies).toEqual([['b']]);
-      });
-    });
-
-    pit('uses async dependencies that `transformCode` resolves to, instead of extracting them', () => {
-      const mockedAsyncDependencies = [['foo', 'bar'], ['baz']];
-      transformCode.mockReturnValue(Promise.resolve({
-        code: exampleCode,
-        asyncDependencies: mockedAsyncDependencies,
-      }));
-      const module = createModule({transformCode});
-
-      return Promise.all([
-        module.getDependencies(),
-        module.getAsyncDependencies(),
-      ]).then(([dependencies, asyncDependencies]) => {
-        expect(dependencies).toEqual(['a', 'c']);
-        expect(asyncDependencies).toEqual(mockedAsyncDependencies);
-      });
-    });
-
-    pit('exposes the transformed code rather than the raw file contents', () => {
-      transformCode.mockReturnValue(Promise.resolve({code: exampleCode}));
-      const module = createModule({transformCode});
-      return Promise.all([module.read(), module.getCode()])
-        .then(([data, code]) => {
-          expect(data.code).toBe(exampleCode);
-          expect(code).toBe(exampleCode);
-        });
-    });
-  });
-});
diff --git a/packager/react-packager/src/DependencyResolver/__tests__/fastfs-data b/packager/react-packager/src/DependencyResolver/__tests__/fastfs-data
deleted file mode 100644
index fe2c6388532b30..00000000000000
--- a/packager/react-packager/src/DependencyResolver/__tests__/fastfs-data
+++ /dev/null
@@ -1,39 +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.
- * An arbitrary module header
- * @providesModule
- */
-
- const some: string = 'arbitrary code';
- const containing: string = 'ūñïčødę';
-
-/**
- * An arbitrary class that extends some thing
- * It exposes a random number, which may be reset at the callers discretion
- */
-class Arbitrary extends Something {
-  constructor() {
-    this.reset();
-  }
-
-  /**
-   * Returns the random number
-   * @returns number
-   */
-  get random(): number {
-    return this._random;
-  }
-
-  /**
-   * Re-creates the internal random number
-   * @returns void
-   */
-  reset(): void {
-    this._random = Math.random();
-  }
-}
diff --git a/packager/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js b/packager/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js
deleted file mode 100644
index cbff314af1a03c..00000000000000
--- a/packager/react-packager/src/DependencyResolver/__tests__/fastfs-integrated-test.js
+++ /dev/null
@@ -1,87 +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()
-  .dontMock('graceful-fs');
-
-const Fastfs = require('../fastfs');
-
-const {EventEmitter} = require('events');
-const fs = require('fs');
-const path = require('path');
-
-const fileName = path.resolve(__dirname, 'fastfs-data');
-const contents = fs.readFileSync(fileName, 'utf-8');
-
-describe('fastfs:', function() {
-  let fastfs;
-  const crawling = Promise.resolve([fileName]);
-  const roots = [__dirname];
-  const watcher = new EventEmitter();
-
-  beforeEach(function(done) {
-    fastfs = new Fastfs('arbitrary', roots, watcher, {crawling});
-    fastfs.build().then(done);
-  });
-
-  describe('partial reading', () => {
-    // these are integrated tests that read real files from disk
-
-    pit('reads a file while a predicate returns true', function() {
-      return fastfs.readWhile(fileName, () => true).then(readContent =>
-        expect(readContent).toEqual(contents)
-      );
-    });
-
-    pit('invokes the predicate with the new chunk, the invocation index, and the result collected so far', () => {
-      const predicate = jest.genMockFn().mockReturnValue(true);
-      return fastfs.readWhile(fileName, predicate).then(() => {
-        let aggregated = '';
-        const {calls} = predicate.mock;
-        expect(calls).not.toEqual([]);
-
-        calls.forEach((call, i) => {
-          const [chunk] = call;
-          aggregated += chunk;
-          expect(chunk).not.toBe('');
-          expect(call).toEqual([chunk, i, aggregated]);
-        });
-
-        expect(aggregated).toEqual(contents);
-      });
-    });
-
-    pit('stops reading when the predicate returns false', () => {
-      const predicate = jest.genMockFn().mockImpl((_, i) => i !== 0);
-      return fastfs.readWhile(fileName, predicate).then((readContent) => {
-        const {calls} = predicate.mock;
-        expect(calls.length).toBe(1);
-        expect(readContent).toBe(calls[0][2]);
-      });
-    });
-
-    pit('after reading the whole file with `readWhile`, `read()` still works', () => {
-      // this test allows to reuse the results of `readWhile` for `readFile`
-      return fastfs.readWhile(fileName, () => true).then(() => {
-        fastfs.readFile(fileName).then(readContent =>
-          expect(readContent).toEqual(contents)
-        );
-      });
-    });
-
-    pit('after reading parts of the file with `readWhile`, `read()` still works', () => {
-      return fastfs.readWhile(fileName, () => false).then(() => {
-        fastfs.readFile(fileName).then(readContent =>
-          expect(readContent).toEqual(contents)
-        );
-      });
-    });
-  });
-});
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 fe755bcb621e13..00000000000000
--- 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 88e32d8843c244..00000000000000
--- 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('graceful-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 1871e3ead8b554..00000000000000
--- 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 325604ffe08fa7..00000000000000
--- a/packager/react-packager/src/DependencyResolver/fastfs.js
+++ /dev/null
@@ -1,402 +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('graceful-fs');
-const path = require('path');
-
-// workaround for https://github.com/isaacs/node-graceful-fs/issues/56
-// fs.close is patched, whereas graceful-fs.close is not.
-const fsClose = require('fs').close;
-
-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: ${filePath}`);
-    }
-    return file.read();
-  }
-
-  readWhile(filePath, predicate) {
-    const file = this._getFile(filePath);
-    if (!file) {
-      throw new Error(`Unable to find file with path: ${filePath}`);
-    }
-    return file.readWhile(predicate);
-  }
-
-  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;
-  }
-
-  readWhile(predicate) {
-    return readWhile(this.path, predicate).then(({result, completed}) => {
-      if (completed && !this._read) {
-        this._read = Promise.resolve(result);
-      }
-      return result;
-    });
-  }
-
-  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() {
-    let files = [];
-    Object.keys(this.children).forEach(key => {
-      const file = this.children[key];
-      if (file.isDir) {
-        files = files.concat(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 readWhile(filePath, predicate) {
-  return new Promise((resolve, reject) => {
-    fs.open(filePath, 'r', (openError, fd) => {
-      if (openError) {
-        reject(openError);
-        return;
-      }
-
-      read(
-        fd,
-        /*global Buffer: true*/
-        new Buffer(512),
-        makeReadCallback(fd, predicate, (readError, result, completed) => {
-          if (readError) {
-            reject(readError);
-          } else {
-            resolve({result, completed});
-          }
-        })
-      );
-    });
-  });
-}
-
-function read(fd, buffer, callback) {
-  fs.read(fd, buffer, 0, buffer.length, -1, callback);
-}
-
-function close(fd, error, result, complete, callback) {
-  fsClose(fd, closeError => callback(error || closeError, result, complete));
-}
-
-function makeReadCallback(fd, predicate, callback) {
-  let result = '';
-  let index = 0;
-  return function readCallback(error, bytesRead, buffer) {
-    if (error) {
-      close(fd, error, undefined, false, callback);
-      return;
-    }
-
-    const completed = bytesRead === 0;
-    const chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead);
-    result += chunk;
-    if (completed || !predicate(chunk, index++, result)) {
-      close(fd, null, result, completed, callback);
-    } else {
-      read(fd, buffer, readCallback);
-    }
-  };
-}
-
-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 ff61c40537d419..00000000000000
--- 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 f5ccdc3ab9b4f2..00000000000000
--- 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 107cc6ba9e36b6..00000000000000
--- 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 33fa13cea18ded..00000000000000
--- 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 cfcb65016452ac..00000000000000
--- 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/DependencyResolver/lib/replacePatterns.js b/packager/react-packager/src/DependencyResolver/lib/replacePatterns.js
deleted file mode 100644
index a4e563d2c67cce..00000000000000
--- a/packager/react-packager/src/DependencyResolver/lib/replacePatterns.js
+++ /dev/null
@@ -1,15 +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';
-
-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;
diff --git a/packager/react-packager/src/JSTransformer/__mocks__/underscore.js b/packager/react-packager/src/JSTransformer/__mocks__/lodash.js
similarity index 89%
rename from packager/react-packager/src/JSTransformer/__mocks__/underscore.js
rename to packager/react-packager/src/JSTransformer/__mocks__/lodash.js
index 45754e1a547c0e..ac8224e6042b92 100644
--- a/packager/react-packager/src/JSTransformer/__mocks__/underscore.js
+++ b/packager/react-packager/src/JSTransformer/__mocks__/lodash.js
@@ -10,4 +10,4 @@
 
 // Bug with Jest because we're going to the node_modules that is a sibling
 // of what jest thinks our root (the dir with the package.json) should be.
-module.exports = require.requireActual('underscore');
+module.exports = require.requireActual('lodash');
diff --git a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
index 7e12976895df84..dc93a872f370ae 100644
--- a/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
+++ b/packager/react-packager/src/JSTransformer/__tests__/Transformer-test.js
@@ -12,58 +12,66 @@ jest
   .dontMock('../../lib/ModuleTransport')
   .dontMock('../');
 
-jest.mock('fs');
-jest.setMock('temp', {path: () => '/arbitrary/path'});
+const fs = {writeFileSync: jest.genMockFn()};
+const temp = {path: () => '/arbitrary/path'};
+const workerFarm = jest.genMockFn();
+jest.setMock('fs', fs);
+jest.setMock('temp', temp);
+jest.setMock('worker-farm', workerFarm);
 
-var Cache = require('../../DependencyResolver/Cache');
 var Transformer = require('../');
-var fs = require('fs');
 
-var options;
+const {any} = jasmine;
 
 describe('Transformer', function() {
-  var workers;
+  let options, workers, Cache;
+  const fileName = '/an/arbitrary/file.js';
+  const transformModulePath = __filename;
 
   beforeEach(function() {
-    workers = jest.genMockFn();
-    jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() {
-      return workers;
-    }));
+    Cache = jest.genMockFn();
+    Cache.prototype.get = jest.genMockFn().mockImpl((a, b, c) => c());
 
-    options = {
-      transformModulePath: '/foo/bar',
-      cache: new Cache({}),
-    };
+    fs.writeFileSync.mockClear();
+    options = {transformModulePath};
+    workerFarm.mockClear();
+    workerFarm.mockImpl((opts, path, methods) => {
+      const api = workers = {};
+      methods.forEach(method => api[method] = jest.genMockFn());
+      return api;
+    });
   });
 
-  pit('should loadFileAndTransform', function() {
-    workers.mockImpl(function(data, callback) {
-      callback(null, { code: 'transformed', map: 'sourceMap' });
-    });
-    fs.readFile.mockImpl(function(file, callback) {
-      callback(null, 'content');
+  it('passes transform module path, file path, source code, and options to the worker farm when transforming', () => {
+    const transformOptions = {arbitrary: 'options'};
+    const code = 'arbitrary(code)';
+    new Transformer(options).transformFile(fileName, code, transformOptions);
+    expect(workers.transformAndExtractDependencies).toBeCalledWith(
+      transformModulePath,
+      fileName,
+      code,
+      transformOptions,
+      any(Function),
+    );
+  });
+
+  pit('passes the data produced by the worker back', () => {
+    const transformer = new Transformer(options);
+    const result = { code: 'transformed', map: 'sourceMap' };
+    workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) {
+      callback(null, result);
     });
 
-    return new Transformer(options).loadFileAndTransform('file')
-      .then(function(data) {
-        expect(data).toEqual({
-          code: 'transformed',
-          map: 'sourceMap',
-          sourcePath: 'file',
-          sourceCode: 'content'
-        });
-      });
+    return transformer.transformFile(fileName, '', {})
+      .then(data => expect(data).toBe(result));
   });
 
   pit('should add file info to parse errors', function() {
+    const transformer = new Transformer(options);
     var message = 'message';
     var snippet = 'snippet';
 
-    fs.readFile.mockImpl(function(file, callback) {
-      callback(null, 'var x;\nvar answer = 1 = x;');
-    });
-
-    workers.mockImpl(function(data, callback) {
+    workers.transformAndExtractDependencies.mockImpl(function(transformPath, filename, code, options, callback) {
       var babelError = new SyntaxError(message);
       babelError.type = 'SyntaxError';
       babelError.description = message;
@@ -75,13 +83,13 @@ describe('Transformer', function() {
       callback(babelError);
     });
 
-    return new Transformer(options).loadFileAndTransform('foo-file.js')
+    return transformer.transformFile(fileName, '', {})
       .catch(function(error) {
         expect(error.type).toEqual('TransformError');
         expect(error.message).toBe('SyntaxError ' + message);
         expect(error.lineNumber).toBe(2);
         expect(error.column).toBe(15);
-        expect(error.filename).toBe('foo-file.js');
+        expect(error.filename).toBe(fileName);
         expect(error.description).toBe(message);
         expect(error.snippet).toBe(snippet);
       });
diff --git a/packager/react-packager/src/JSTransformer/__tests__/worker-test.js b/packager/react-packager/src/JSTransformer/__tests__/worker-test.js
deleted file mode 100644
index f81907c2c8e0de..00000000000000
--- a/packager/react-packager/src/JSTransformer/__tests__/worker-test.js
+++ /dev/null
@@ -1,109 +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('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 9767985926b580..3ddd95bdf0adb8 100644
--- a/packager/react-packager/src/JSTransformer/index.js
+++ b/packager/react-packager/src/JSTransformer/index.js
@@ -8,17 +8,13 @@
  */
 'use strict';
 
-const ModuleTransport = require('../lib/ModuleTransport');
 const Promise = require('promise');
 const declareOpts = require('../lib/declareOpts');
-const fs = require('fs');
-const temp = require('temp');
+const os = require('os');
 const util = require('util');
 const workerFarm = require('worker-farm');
 const debug = require('debug')('ReactNativePackager:JStransformer');
 
-const readFile = Promise.denodeify(fs.readFile);
-
 // Avoid memory leaks caused in workers. This number seems to be a good enough number
 // to avoid any memory leak while not slowing down initial builds.
 // TODO(amasad): Once we get bundle splitting, we can drive this down a bit more.
@@ -31,152 +27,98 @@ const DEFAULT_MAX_CALL_TIME = 301000;
 const MAX_RETRIES = 2;
 
 const validateOpts = declareOpts({
-  projectRoots: {
-    type: 'array',
-    required: true,
-  },
-  blacklistRE: {
-    type: 'object', // typeof regex is object
-  },
-  polyfillModuleNames: {
-    type: 'array',
-    default: [],
-  },
   transformModulePath: {
     type:'string',
     required: false,
   },
-  cache: {
-    type: 'object',
-    required: true,
-  },
   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,
-        maxCallsPerWorker: MAX_CALLS_PER_WORKER,
-        maxCallTime: opts.transformTimeoutInterval,
-        maxRetries: MAX_RETRIES,
-      }, transformer);
-
-      this._transform = Promise.denodeify(this._workers);
+    const {transformModulePath} = opts;
+
+    if (transformModulePath) {
+      this._transformModulePath = require.resolve(transformModulePath);
+
+      this._workers = workerFarm(
+        {
+          autoStart: true,
+          maxConcurrentCallsPerWorker: 1,
+          maxConcurrentWorkers: maxConcurrentWorkers,
+          maxCallsPerWorker: MAX_CALLS_PER_WORKER,
+          maxCallTime: opts.transformTimeoutInterval,
+          maxRetries: MAX_RETRIES,
+        },
+        require.resolve('./worker'),
+        ['minify', 'transformAndExtractDependencies']
+      );
+
+      this._transform = Promise.denodeify(this._workers.transformAndExtractDependencies);
+      this.minify = Promise.denodeify(this._workers.minify);
     }
   }
 
   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, options) {
-    if (this._transform == null) {
+  transformFile(fileName, code, options) {
+    if (!this._transform) {
       return Promise.reject(new Error('No transfrom module'));
     }
-
-    debug('transforming file', filePath);
-
-    const optionsJSON = JSON.stringify(options);
-
-    return this._cache.get(
-      filePath,
-      'transformedSource-' + optionsJSON,
-      // TODO: use fastfs to avoid reading file from disk again
-      () => readFile(filePath).then(
-        buffer => {
-          const sourceCode = buffer.toString('utf8');
-
-          return this._transform({
-            sourceCode,
-            filename: filePath,
-            options: {
-              ...options,
-              projectRoots: this._projectRoots,
-              externalTransformModulePath: this._transformModulePath,
-            },
-          }).then(res => {
-            if (res.error) {
-              console.warn(
-                'Error property on the result value from the transformer',
-                'module is deprecated and will be removed in future versions.',
-                'Please pass an error object as the first argument to the callback'
-              );
-              throw formatError(res.error, filePath);
-            }
-
-            debug('done transforming file', filePath);
-
-            return new ModuleTransport({
-              code: res.code,
-              map: res.map,
-              sourcePath: filePath,
-              sourceCode: sourceCode,
-            });
-          }).catch(err => {
-            if (err.type === 'TimeoutError') {
-              const timeoutErr = new Error(
-                `TimeoutError: transforming ${filePath} took longer than ` +
-                `${this._opts.transformTimeoutInterval / 1000} seconds.\n` +
-                `You can adjust timeout via the 'transformTimeoutInterval' option`
-              );
-              timeoutErr.type = 'TimeoutError';
-              throw timeoutErr;
-            } else if (err.type === 'ProcessTerminatedError') {
-              const uncaughtError = new Error(
-                'Uncaught error in the transformer worker: ' +
-                this._opts.transformModulePath
-              );
-              uncaughtError.type = 'ProcessTerminatedError';
-              throw uncaughtError;
-            }
-
-            throw formatError(err, filePath);
-          });
-        })
-    );
+    debug('transforming file', fileName);
+    return this
+      ._transform(this._transformModulePath, fileName, code, options)
+      .then(result => {
+        debug('done transforming file', fileName);
+        return result;
+      })
+      .catch(error => {
+        if (error.type === 'TimeoutError') {
+          const timeoutErr = new Error(
+            `TimeoutError: transforming ${fileName} took longer than ` +
+            `${this._opts.transformTimeoutInterval / 1000} seconds.\n` +
+            `You can adjust timeout via the 'transformTimeoutInterval' option`
+          );
+          timeoutErr.type = 'TimeoutError';
+          throw timeoutErr;
+        } else if (error.type === 'ProcessTerminatedError') {
+          const uncaughtError = new Error(
+            'Uncaught error in the transformer worker: ' +
+            this._opts.transformModulePath
+          );
+          uncaughtError.type = 'ProcessTerminatedError';
+          throw uncaughtError;
+        }
+
+        throw formatError(error, fileName);
+      });
   }
 }
 
-
 module.exports = Transformer;
 
 Transformer.TransformError = TransformError;
diff --git a/packager/react-packager/src/JSTransformer/worker.js b/packager/react-packager/src/JSTransformer/worker.js
deleted file mode 100644
index c8d388a41e274d..00000000000000
--- a/packager/react-packager/src/JSTransformer/worker.js
+++ /dev/null
@@ -1,77 +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 babel = require('babel-core');
-var makeInternalConfig = require('babel-preset-react-native/configs/internal');
-
-// 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 00000000000000..64ad751fe0448d
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in 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});
+}
+
+const babelOptions = {
+  babelrc: false,
+  compact: true,
+  retainLines: false,
+};
+
+function normalize({code}) {
+  return babel.transform(code, babelOptions).code;
+}
+
+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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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(normalize(constantFolding('arbitrary.js', parse(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 00000000000000..fba330eeccc7a8
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-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 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, 98]);
+  });
+
+  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 extract calls to require with non-static arguments', () => {
+    const code = `require('foo/' + bar)`;
+
+    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]);
+  });
+
+  it('can handle regular expressions', () => {
+    const code = `require('a'); /["']/.test('foo'); require("b");`;
+
+    const {dependencies, dependencyOffsets} = extractDependencies(code);
+    expect(dependencies).toEqual(['a', 'b']);
+    expect(dependencyOffsets).toEqual([8, 42]);
+  });
+});
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 00000000000000..53fe1ed8f3623e
--- /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 00000000000000..184a4aef584ef9
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/minify-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.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 {objectContaining} = jasmine;
+
+describe('Minification:', () => {
+  const filename = '/arbitrary/file.js';
+  const code = 'arbitrary(code)';
+  let map;
+
+  beforeEach(() => {
+    uglify.minify.mockClear();
+    uglify.minify.mockReturnValue({code: '', map: '{}'});
+    map = {version: 3, sources: ['?'], mappings: ''};
+  });
+
+  it('passes file name, code, and source map to `uglify`', () => {
+    minify(filename, code, map);
+    expect(uglify.minify).toBeCalledWith(code, objectContaining({
+      fromString: true,
+      inSourceMap: map,
+      outSourceMap: true,
+    }));
+  });
+
+  it('returns the code provided by uglify', () => {
+    uglify.minify.mockReturnValue({code, map: '{}'});
+    const result = minify('', '', {});
+    expect(result.code).toBe(code);
+  });
+
+  it('parses the source map object provided by uglify and sets the sources property', () => {
+    uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''});
+    const result = minify(filename, '', {});
+    expect(result.map).toEqual({...map, sources: [filename]});
+  });
+});
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 00000000000000..b08035098f4904
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/__tests__/worker-test.js
@@ -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.
+ */
+'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();
+    });
+  });
+
+  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, inline, options;
+    let transformResult, dependencyData;
+    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');
+
+      options = {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('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 `constant-folding` for the result', done => {
+      transformCode(transform, 'filename', 'code', options, (_, result) => {
+        expect(result)
+          .toEqual(objectContaining({code: foldedCode, map: foldedMap}));
+        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 00000000000000..f896430f468bd3
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/constant-folding.js
@@ -0,0 +1,85 @@
+/**
+ * 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,
+    sourceMaps: true,
+    sourceFileName: filename,
+    babelrc: false,
+    compact: true,
+    retainLines: 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 00000000000000..4e1bfb396a6890
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/extract-dependencies.js
@@ -0,0 +1,49 @@
+/**
+ * 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 babylon = require('babylon');
+
+/**
+ * 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 ast = babylon.parse(code);
+  const dependencies = new Set();
+  const dependencyOffsets = [];
+
+  babel.traverse(ast, {
+    CallExpression(path) {
+      const node = path.node;
+      const callee = node.callee;
+      const arg = node.arguments[0];
+      if (callee.type !== 'Identifier' || callee.name !== 'require' || !arg || arg.type !== 'StringLiteral') {
+        return;
+      }
+      dependencyOffsets.push(arg.start);
+      dependencies.add(arg.value);
+    }
+  });
+
+  return {dependencyOffsets, dependencies: Array.from(dependencies)};
+}
+
+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 00000000000000..1b68feaed85c8a
--- /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 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 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 result = isJson || options.extern
+      ? {dependencies: [], dependencyOffsets: []}
+      : extractDependencies(code);
+
+    result.code = code;
+    result.map = map;
+
+    callback(null, result);
+  });
+}
+
+exports.transformAndExtractDependencies = (
+  transform,
+  filename,
+  sourceCode,
+  options,
+  callback
+) => {
+  transformCode(require(transform), filename, sourceCode, options || {}, callback);
+};
+
+exports.minify = (filename, code, sourceMap, callback) => {
+  var result;
+  try {
+    result = minify(filename, code, sourceMap);
+  } catch (error) {
+    callback(error);
+  }
+  callback(null, result);
+};
+
+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 00000000000000..a294bcefc8639a
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/inline.js
@@ -0,0 +1,104 @@
+/**
+ * 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,
+    sourceMaps: true,
+    sourceFileName: filename,
+    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 00000000000000..680cb8859db349
--- /dev/null
+++ b/packager/react-packager/src/JSTransformer/worker/minify.js
@@ -0,0 +1,29 @@
+/**
+ * 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');
+
+function minify(filename, code, sourceMap) {
+  const minifyResult = uglify.minify(code, {
+    fromString: true,
+    inSourceMap: sourceMap,
+    outSourceMap: true,
+    output: {
+      ascii_only: true,
+      screw_ie8: true,
+    },
+  });
+
+  minifyResult.map = JSON.parse(minifyResult.map);
+  minifyResult.map.sources = [filename];
+  return minifyResult;
+}
+
+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 7ef335d9b3d2d4..286dc9ebd50fd4 100644
--- a/packager/react-packager/src/Resolver/__tests__/Resolver-test.js
+++ b/packager/react-packager/src/Resolver/__tests__/Resolver-test.js
@@ -8,39 +8,48 @@
  */
 'use strict';
 
-jest.dontMock('../')
-  .dontMock('underscore')
-  .dontMock('../../DependencyResolver/lib/extractRequires')
-  .dontMock('../../DependencyResolver/lib/replacePatterns');
-
+jest.dontMock('../');
 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('../');
+
+const path = require('path');
 
-var path = require('path');
-var _ = 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);
+      this.isJSON = 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.mockImpl(() => Promise.resolve());
+    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) {
@@ -50,6 +59,10 @@ describe('Resolver', function() {
     finalize() {
       return Promise.resolve(this);
     }
+
+    getResolvedDependencyPairs() {
+      return [];
+    }
   }
 
   function createModule(id, dependencies) {
@@ -59,15 +72,38 @@ describe('Resolver', function() {
     return module;
   }
 
+  function createJsonModule(id) {
+    const module = createModule(id, []);
+    module.isJSON.mockReturnValue(true);
+    return module;
+  }
+
   function createPolyfill(id, dependencies) {
     var polyfill = new Polyfill({});
-    polyfill.getName.mockImpl(() => Promise.resolve(id));
-    polyfill.getDependencies.mockImpl(() => Promise.resolve(dependencies));
-    polyfill.isPolyfill.mockReturnValue(true);
+    polyfill.getName = jest.genMockFn().mockImpl(() => Promise.resolve(id));
+    polyfill.getDependencies =
+      jest.genMockFn().mockImpl(() => Promise.resolve(dependencies));
     return polyfill;
   }
 
   describe('getDependencies', function() {
+    it('forwards transform options to the dependency graph', function() {
+      const transformOptions = {arbitrary: 'options'};
+      const platform = 'ios';
+      const entry = '/root/index.js';
+
+      DependencyGraph.prototype.getDependencies.mockImplementation(
+        () => Promise.reject());
+      new Resolver({projectRoot: '/root', })
+        .getDependencies(entry, {platform}, transformOptions);
+      expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({
+        entryPath: entry,
+        platform: platform,
+        transformOptions: transformOptions,
+        recursive: true,
+      });
+    });
+
     pit('should get dependencies with polyfills', function() {
       var module = createModule('index');
       var deps = [module];
@@ -80,7 +116,6 @@ describe('Resolver', function() {
         return Promise.resolve(new ResolutionResponseMock({
           dependencies: deps,
           mainModuleId: 'index',
-          asyncDependencies: [],
         }));
       });
 
@@ -88,30 +123,26 @@ 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/polyfills.js',
-              id: 'polyfills/polyfills.js',
-              isPolyfill: true,
+          expect(DependencyGraph.prototype.createPolyfill.mock.calls.map((call) => call[0])).toEqual([
+            { id: 'polyfills/polyfills.js',
+              file: 'polyfills/polyfills.js',
               dependencies: []
             },
             { id: 'polyfills/console.js',
-              isPolyfill: true,
-              path: 'polyfills/console.js',
+              file: 'polyfills/console.js',
               dependencies: [
                 'polyfills/polyfills.js'
               ],
             },
             { id: 'polyfills/error-guard.js',
-              isPolyfill: true,
-              path: 'polyfills/error-guard.js',
+              file: 'polyfills/error-guard.js',
               dependencies: [
                 '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/polyfills.js',
                 'polyfills/console.js',
@@ -119,8 +150,7 @@ describe('Resolver', function() {
               ],
             },
             { id: 'polyfills/Array.prototype.es6.js',
-              isPolyfill: true,
-              path: 'polyfills/Array.prototype.es6.js',
+              file: 'polyfills/Array.prototype.es6.js',
               dependencies: [
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
@@ -129,8 +159,7 @@ describe('Resolver', function() {
               ],
             },
             { id: 'polyfills/Array.es6.js',
-              isPolyfill: true,
-              path: 'polyfills/Array.es6.js',
+              file: 'polyfills/Array.es6.js',
               dependencies: [
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
@@ -140,8 +169,7 @@ describe('Resolver', function() {
               ],
             },
             { id: 'polyfills/Object.es7.js',
-              isPolyfill: true,
-              path: 'polyfills/Object.es7.js',
+              file: 'polyfills/Object.es7.js',
               dependencies: [
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
@@ -152,8 +180,7 @@ describe('Resolver', function() {
               ],
             },
             { id: 'polyfills/babelHelpers.js',
-              isPolyfill: true,
-              path: 'polyfills/babelHelpers.js',
+              file: 'polyfills/babelHelpers.js',
               dependencies: [
                 'polyfills/polyfills.js',
                 'polyfills/console.js',
@@ -180,16 +207,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, true);
-          expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
+              .toBeCalledWith({entryPath: '/root/index.js', recursive: true});
+          expect(result.dependencies[0]).toBe(polyfill);
           expect(result.dependencies[result.dependencies.length - 1])
               .toBe(module);
         });
@@ -208,17 +236,15 @@ 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/polyfills.js',
                 'polyfills/console.js',
@@ -241,388 +267,8 @@ describe('Resolver', function() {
         projectRoot: '/root',
       });
 
-      var dependencies = ['x', 'y', 'z', 'a', 'b'];
-
       /*eslint-disable */
       var code = [
-        // single line import
-        "import'x';",
-        "import 'x';",
-        "import 'x' ;",
-        "import Default from 'x';",
-        "import * as All from 'x';",
-        "import {} from 'x';",
-        "import { } from 'x';",
-        "import {Foo} from 'x';",
-        "import { Foo } from 'x';",
-        "import { Foo, } from 'x';",
-        "import {Foo as Bar} from 'x';",
-        "import { Foo as Bar } from 'x';",
-        "import { Foo as Bar, } from 'x';",
-        "import { Foo, Bar } from 'x';",
-        "import { Foo, Bar, } from 'x';",
-        "import { Foo as Bar, Baz } from 'x';",
-        "import { Foo as Bar, Baz, } from 'x';",
-        "import { Foo, Bar as Baz } from 'x';",
-        "import { Foo, Bar as Baz, } from 'x';",
-        "import { Foo as Bar, Baz as Qux } from 'x';",
-        "import { Foo as Bar, Baz as Qux, } from 'x';",
-        "import { Foo, Bar, Baz } from 'x';",
-        "import { Foo, Bar, Baz, } from 'x';",
-        "import { Foo as Bar, Baz, Qux } from 'x';",
-        "import { Foo as Bar, Baz, Qux, } from 'x';",
-        "import { Foo, Bar as Baz, Qux } from 'x';",
-        "import { Foo, Bar as Baz, Qux, } from 'x';",
-        "import { Foo, Bar, Baz as Qux } from 'x';",
-        "import { Foo, Bar, Baz as Qux, } from 'x';",
-        "import { Foo as Bar, Baz as Qux, Norf } from 'x';",
-        "import { Foo as Bar, Baz as Qux, Norf, } from 'x';",
-        "import { Foo as Bar, Baz, Qux as Norf } from 'x';",
-        "import { Foo as Bar, Baz, Qux as Norf, } from 'x';",
-        "import { Foo, Bar as Baz, Qux as Norf } from 'x';",
-        "import { Foo, Bar as Baz, Qux as Norf, } from 'x';",
-        "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';",
-        "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';",
-        "import Default, * as All from 'x';",
-        "import Default, { } from 'x';",
-        "import Default, { Foo } from 'x';",
-        "import Default, { Foo, } from 'x';",
-        "import Default, { Foo as Bar } from 'x';",
-        "import Default, { Foo as Bar, } from 'x';",
-        "import Default, { Foo, Bar } from 'x';",
-        "import Default, { Foo, Bar, } from 'x';",
-        "import Default, { Foo as Bar, Baz } from 'x';",
-        "import Default, { Foo as Bar, Baz, } from 'x';",
-        "import Default, { Foo, Bar as Baz } from 'x';",
-        "import Default, { Foo, Bar as Baz, } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux, } from 'x';",
-        "import Default, { Foo, Bar, Baz } from 'x';",
-        "import Default, { Foo, Bar, Baz, } from 'x';",
-        "import Default, { Foo as Bar, Baz, Qux } from 'x';",
-        "import Default, { Foo as Bar, Baz, Qux, } from 'x';",
-        "import Default, { Foo, Bar as Baz, Qux } from 'x';",
-        "import Default, { Foo, Bar as Baz, Qux, } from 'x';",
-        "import Default, { Foo, Bar, Baz as Qux } from 'x';",
-        "import Default, { Foo, Bar, Baz as Qux, } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux, Norf } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';",
-        "import Default, { Foo as Bar, Baz, Qux as Norf } from 'x';",
-        "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';",
-        "import Default, { Foo, Bar as Baz, Qux as Norf } from 'x';",
-        "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';",
-        "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';",
-        "import Default , { } from 'x';",
-        'import "x";',
-        'import Default from "x";',
-        'import * as All from "x";',
-        'import { } from "x";',
-        'import { Foo } from "x";',
-        'import { Foo, } from "x";',
-        'import { Foo as Bar } from "x";',
-        'import { Foo as Bar, } from "x";',
-        'import { Foo, Bar } from "x";',
-        'import { Foo, Bar, } from "x";',
-        'import { Foo as Bar, Baz } from "x";',
-        'import { Foo as Bar, Baz, } from "x";',
-        'import { Foo, Bar as Baz } from "x";',
-        'import { Foo, Bar as Baz, } from "x";',
-        'import { Foo as Bar, Baz as Qux } from "x";',
-        'import { Foo as Bar, Baz as Qux, } from "x";',
-        'import { Foo, Bar, Baz } from "x";',
-        'import { Foo, Bar, Baz, } from "x";',
-        'import { Foo as Bar, Baz, Qux } from "x";',
-        'import { Foo as Bar, Baz, Qux, } from "x";',
-        'import { Foo, Bar as Baz, Qux } from "x";',
-        'import { Foo, Bar as Baz, Qux, } from "x";',
-        'import { Foo, Bar, Baz as Qux } from "x";',
-        'import { Foo, Bar, Baz as Qux, } from "x";',
-        'import { Foo as Bar, Baz as Qux, Norf } from "x";',
-        'import { Foo as Bar, Baz as Qux, Norf, } from "x";',
-        'import { Foo as Bar, Baz, Qux as Norf } from "x";',
-        'import { Foo as Bar, Baz, Qux as Norf, } from "x";',
-        'import { Foo, Bar as Baz, Qux as Norf } from "x";',
-        'import { Foo, Bar as Baz, Qux as Norf, } from "x";',
-        'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";',
-        'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";',
-        'import Default, * as All from "x";',
-        'import Default, { } from "x";',
-        'import Default, { Foo } from "x";',
-        'import Default, { Foo, } from "x";',
-        'import Default, { Foo as Bar } from "x";',
-        'import Default, { Foo as Bar, } from "x";',
-        'import Default, { Foo, Bar } from "x";',
-        'import Default, { Foo, Bar, } from "x";',
-        'import Default, { Foo as Bar, Baz } from "x";',
-        'import Default, { Foo as Bar, Baz, } from "x";',
-        'import Default, { Foo, Bar as Baz } from "x";',
-        'import Default, { Foo, Bar as Baz, } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux, } from "x";',
-        'import Default, { Foo, Bar, Baz } from "x";',
-        'import Default, { Foo, Bar, Baz, } from "x";',
-        'import Default, { Foo as Bar, Baz, Qux } from "x";',
-        'import Default, { Foo as Bar, Baz, Qux, } from "x";',
-        'import Default, { Foo, Bar as Baz, Qux } from "x";',
-        'import Default, { Foo, Bar as Baz, Qux, } from "x";',
-        'import Default, { Foo, Bar, Baz as Qux } from "x";',
-        'import Default, { Foo, Bar, Baz as Qux, } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux, Norf } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux, Norf, } from "x";',
-        'import Default, { Foo as Bar, Baz, Qux as Norf } from "x";',
-        'import Default, { Foo as Bar, Baz, Qux as Norf, } from "x";',
-        'import Default, { Foo, Bar as Baz, Qux as Norf } from "x";',
-        'import Default, { Foo, Bar as Baz, Qux as Norf, } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";',
-        'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";',
-        'import Default from "y";',
-        'import * as All from \'z\';',
-        // import with support for new lines
-        "import { Foo,\n Bar }\n from 'x';",
-        "import { \nFoo,\nBar,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz\n }\n from 'x';",
-        "import { \nFoo as Bar,\n Baz\n, }\n from 'x';",
-        "import { Foo,\n Bar as Baz\n }\n from 'x';",
-        "import { Foo,\n Bar as Baz,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux,\n }\n from 'x';",
-        "import { Foo,\n Bar,\n Baz }\n from 'x';",
-        "import { Foo,\n Bar,\n Baz,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';",
-        "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';",
-        "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';",
-        "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';",
-        "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';",
-        "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';",
-        "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';",
-        "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';",
-        "import Default,\n * as All from 'x';",
-        "import Default,\n { } from 'x';",
-        "import Default,\n { Foo\n }\n from 'x';",
-        "import Default,\n { Foo,\n }\n from 'x';",
-        "import Default,\n { Foo as Bar\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar\n } from\n 'x';",
-        "import Default,\n { Foo,\n Bar,\n } from\n 'x';",
-        "import Default,\n { Foo as Bar,\n Baz\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar as Baz\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';",
-        "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';",
-        "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';",
-        "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';",
-        "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';",
-        "import Default\n , { } from 'x';",
-        // single line export
-        "export'x';",
-        "export 'x';",
-        "export 'x' ;",
-        "export Default from 'x';",
-        "export * as All from 'x';",
-        "export {} from 'x';",
-        "export { } from 'x';",
-        "export {Foo} from 'x';",
-        "export { Foo } from 'x';",
-        "export { Foo, } from 'x';",
-        "export {Foo as Bar} from 'x';",
-        "export { Foo as Bar } from 'x';",
-        "export { Foo as Bar, } from 'x';",
-        "export { Foo, Bar } from 'x';",
-        "export { Foo, Bar, } from 'x';",
-        "export { Foo as Bar, Baz } from 'x';",
-        "export { Foo as Bar, Baz, } from 'x';",
-        "export { Foo, Bar as Baz } from 'x';",
-        "export { Foo, Bar as Baz, } from 'x';",
-        "export { Foo as Bar, Baz as Qux } from 'x';",
-        "export { Foo as Bar, Baz as Qux, } from 'x';",
-        "export { Foo, Bar, Baz } from 'x';",
-        "export { Foo, Bar, Baz, } from 'x';",
-        "export { Foo as Bar, Baz, Qux } from 'x';",
-        "export { Foo as Bar, Baz, Qux, } from 'x';",
-        "export { Foo, Bar as Baz, Qux } from 'x';",
-        "export { Foo, Bar as Baz, Qux, } from 'x';",
-        "export { Foo, Bar, Baz as Qux } from 'x';",
-        "export { Foo, Bar, Baz as Qux, } from 'x';",
-        "export { Foo as Bar, Baz as Qux, Norf } from 'x';",
-        "export { Foo as Bar, Baz as Qux, Norf, } from 'x';",
-        "export { Foo as Bar, Baz, Qux as Norf } from 'x';",
-        "export { Foo as Bar, Baz, Qux as Norf, } from 'x';",
-        "export { Foo, Bar as Baz, Qux as Norf } from 'x';",
-        "export { Foo, Bar as Baz, Qux as Norf, } from 'x';",
-        "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'x';",
-        "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'x';",
-        "export Default, * as All from 'x';",
-        "export Default, { } from 'x';",
-        "export Default, { Foo } from 'x';",
-        "export Default, { Foo, } from 'x';",
-        "export Default, { Foo as Bar } from 'x';",
-        "export Default, { Foo as Bar, } from 'x';",
-        "export Default, { Foo, Bar } from 'x';",
-        "export Default, { Foo, Bar, } from 'x';",
-        "export Default, { Foo as Bar, Baz } from 'x';",
-        "export Default, { Foo as Bar, Baz, } from 'x';",
-        "export Default, { Foo, Bar as Baz } from 'x';",
-        "export Default, { Foo, Bar as Baz, } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux, } from 'x';",
-        "export Default, { Foo, Bar, Baz } from 'x';",
-        "export Default, { Foo, Bar, Baz, } from 'x';",
-        "export Default, { Foo as Bar, Baz, Qux } from 'x';",
-        "export Default, { Foo as Bar, Baz, Qux, } from 'x';",
-        "export Default, { Foo, Bar as Baz, Qux } from 'x';",
-        "export Default, { Foo, Bar as Baz, Qux, } from 'x';",
-        "export Default, { Foo, Bar, Baz as Qux } from 'x';",
-        "export Default, { Foo, Bar, Baz as Qux, } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux, Norf } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'x';",
-        "export Default, { Foo as Bar, Baz, Qux as Norf } from 'x';",
-        "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'x';",
-        "export Default, { Foo, Bar as Baz, Qux as Norf } from 'x';",
-        "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'x';",
-        "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'x';",
-        "export Default , { } from 'x';",
-        'export "x";',
-        'export Default from "x";',
-        'export * as All from "x";',
-        'export { } from "x";',
-        'export { Foo } from "x";',
-        'export { Foo, } from "x";',
-        'export { Foo as Bar } from "x";',
-        'export { Foo as Bar, } from "x";',
-        'export { Foo, Bar } from "x";',
-        'export { Foo, Bar, } from "x";',
-        'export { Foo as Bar, Baz } from "x";',
-        'export { Foo as Bar, Baz, } from "x";',
-        'export { Foo, Bar as Baz } from "x";',
-        'export { Foo, Bar as Baz, } from "x";',
-        'export { Foo as Bar, Baz as Qux } from "x";',
-        'export { Foo as Bar, Baz as Qux, } from "x";',
-        'export { Foo, Bar, Baz } from "x";',
-        'export { Foo, Bar, Baz, } from "x";',
-        'export { Foo as Bar, Baz, Qux } from "x";',
-        'export { Foo as Bar, Baz, Qux, } from "x";',
-        'export { Foo, Bar as Baz, Qux } from "x";',
-        'export { Foo, Bar as Baz, Qux, } from "x";',
-        'export { Foo, Bar, Baz as Qux } from "x";',
-        'export { Foo, Bar, Baz as Qux, } from "x";',
-        'export { Foo as Bar, Baz as Qux, Norf } from "x";',
-        'export { Foo as Bar, Baz as Qux, Norf, } from "x";',
-        'export { Foo as Bar, Baz, Qux as Norf } from "x";',
-        'export { Foo as Bar, Baz, Qux as Norf, } from "x";',
-        'export { Foo, Bar as Baz, Qux as Norf } from "x";',
-        'export { Foo, Bar as Baz, Qux as Norf, } from "x";',
-        'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "x";',
-        'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "x";',
-        'export Default, * as All from "x";',
-        'export Default, { } from "x";',
-        'export Default, { Foo } from "x";',
-        'export Default, { Foo, } from "x";',
-        'export Default, { Foo as Bar } from "x";',
-        'export Default, { Foo as Bar, } from "x";',
-        'export Default, { Foo, Bar } from "x";',
-        'export Default, { Foo, Bar, } from "x";',
-        'export Default, { Foo as Bar, Baz } from "x";',
-        'export Default, { Foo as Bar, Baz, } from "x";',
-        'export Default, { Foo, Bar as Baz } from "x";',
-        'export Default, { Foo, Bar as Baz, } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux, } from "x";',
-        'export Default, { Foo, Bar, Baz } from "x";',
-        'export Default, { Foo, Bar, Baz, } from "x";',
-        'export Default, { Foo as Bar, Baz, Qux } from "x";',
-        'export Default, { Foo as Bar, Baz, Qux, } from "x";',
-        'export Default, { Foo, Bar as Baz, Qux } from "x";',
-        'export Default, { Foo, Bar as Baz, Qux, } from "x";',
-        'export Default, { Foo, Bar, Baz as Qux } from "x";',
-        'export Default, { Foo, Bar, Baz as Qux, } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux, Norf } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux, Norf, } from "x";',
-        'export Default, { Foo as Bar, Baz, Qux as Norf } from "x";',
-        'export Default, { Foo as Bar, Baz, Qux as Norf, } from "x";',
-        'export Default, { Foo, Bar as Baz, Qux as Norf } from "x";',
-        'export Default, { Foo, Bar as Baz, Qux as Norf, } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "x";',
-        'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "x";',
-        'export Default from "y";',
-        'export * as All from \'z\';',
-        // export with support for new lines
-        "export { Foo,\n Bar }\n from 'x';",
-        "export { \nFoo,\nBar,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz\n }\n from 'x';",
-        "export { \nFoo as Bar,\n Baz\n, }\n from 'x';",
-        "export { Foo,\n Bar as Baz\n }\n from 'x';",
-        "export { Foo,\n Bar as Baz,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux,\n }\n from 'x';",
-        "export { Foo,\n Bar,\n Baz }\n from 'x';",
-        "export { Foo,\n Bar,\n Baz,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';",
-        "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';",
-        "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';",
-        "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';",
-        "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'x';",
-        "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'x';",
-        "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'x';",
-        "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'x';",
-        "export Default,\n * as All from 'x';",
-        "export Default,\n { } from 'x';",
-        "export Default,\n { Foo\n }\n from 'x';",
-        "export Default,\n { Foo,\n }\n from 'x';",
-        "export Default,\n { Foo as Bar\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar\n } from\n 'x';",
-        "export Default,\n { Foo,\n Bar,\n } from\n 'x';",
-        "export Default,\n { Foo as Bar,\n Baz\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar as Baz\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'x';",
-        "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'x';",
-        "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'x';",
-        "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'x';",
-        "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'x';",
-        "export Default\n , { } from 'x';",
         // require
         'require("x")',
         'require("y")',
@@ -632,12 +278,19 @@ describe('Resolver', function() {
       ].join('\n');
       /*eslint-disable */
 
-      const module = createModule('test module', ['x', 'y']);
+      function *findDependencyOffsets() {
+        const re = /(['"']).*?\1/g;
+        let match;
+        while ((match = re.exec(code))) {
+          yield match.index;
+        }
+      }
 
+      const dependencyOffsets = Array.from(findDependencyOffsets());
+      const module = createModule('test module', ['x', 'y']);
       const resolutionResponse = new ResolutionResponseMock({
         dependencies: [module],
         mainModuleId: 'test module',
-        asyncDependencies: [],
       });
 
       resolutionResponse.getResolvedDependencyPairs = (module) => {
@@ -647,393 +300,15 @@ describe('Resolver', function() {
         ];
       }
 
-      return depResolver.wrapModule(
+      return depResolver.wrapModule({
         resolutionResponse,
-        createModule('test module', ['x', 'y']),
-        code
-      ).then(processedCode => {
-        expect(processedCode.name).toEqual('test module');
-        expect(processedCode.code).toEqual([
-          '__d(\'test module\',function(global, require,' +
-            ' module, exports) {  ' +
-            // single line import
-            "import'x';",
-          "import 'changed';",
-          "import 'changed' ;",
-          "import Default from 'changed';",
-          "import * as All from 'changed';",
-          "import {} from 'changed';",
-          "import { } from 'changed';",
-          "import {Foo} from 'changed';",
-          "import { Foo } from 'changed';",
-          "import { Foo, } from 'changed';",
-          "import {Foo as Bar} from 'changed';",
-          "import { Foo as Bar } from 'changed';",
-          "import { Foo as Bar, } from 'changed';",
-          "import { Foo, Bar } from 'changed';",
-          "import { Foo, Bar, } from 'changed';",
-          "import { Foo as Bar, Baz } from 'changed';",
-          "import { Foo as Bar, Baz, } from 'changed';",
-          "import { Foo, Bar as Baz } from 'changed';",
-          "import { Foo, Bar as Baz, } from 'changed';",
-          "import { Foo as Bar, Baz as Qux } from 'changed';",
-          "import { Foo as Bar, Baz as Qux, } from 'changed';",
-          "import { Foo, Bar, Baz } from 'changed';",
-          "import { Foo, Bar, Baz, } from 'changed';",
-          "import { Foo as Bar, Baz, Qux } from 'changed';",
-          "import { Foo as Bar, Baz, Qux, } from 'changed';",
-          "import { Foo, Bar as Baz, Qux } from 'changed';",
-          "import { Foo, Bar as Baz, Qux, } from 'changed';",
-          "import { Foo, Bar, Baz as Qux } from 'changed';",
-          "import { Foo, Bar, Baz as Qux, } from 'changed';",
-          "import { Foo as Bar, Baz as Qux, Norf } from 'changed';",
-          "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
-          "import { Foo as Bar, Baz, Qux as Norf } from 'changed';",
-          "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
-          "import { Foo, Bar as Baz, Qux as Norf } from 'changed';",
-          "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
-          "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';",
-          "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';",
-          "import Default, * as All from 'changed';",
-          "import Default, { } from 'changed';",
-          "import Default, { Foo } from 'changed';",
-          "import Default, { Foo, } from 'changed';",
-          "import Default, { Foo as Bar } from 'changed';",
-          "import Default, { Foo as Bar, } from 'changed';",
-          "import Default, { Foo, Bar } from 'changed';",
-          "import Default, { Foo, Bar, } from 'changed';",
-          "import Default, { Foo as Bar, Baz } from 'changed';",
-          "import Default, { Foo as Bar, Baz, } from 'changed';",
-          "import Default, { Foo, Bar as Baz } from 'changed';",
-          "import Default, { Foo, Bar as Baz, } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux, } from 'changed';",
-          "import Default, { Foo, Bar, Baz } from 'changed';",
-          "import Default, { Foo, Bar, Baz, } from 'changed';",
-          "import Default, { Foo as Bar, Baz, Qux } from 'changed';",
-          "import Default, { Foo as Bar, Baz, Qux, } from 'changed';",
-          "import Default, { Foo, Bar as Baz, Qux } from 'changed';",
-          "import Default, { Foo, Bar as Baz, Qux, } from 'changed';",
-          "import Default, { Foo, Bar, Baz as Qux } from 'changed';",
-          "import Default, { Foo, Bar, Baz as Qux, } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
-          "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';",
-          "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
-          "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';",
-          "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';",
-          "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';",
-          "import Default , { } from 'changed';",
-          'import "changed";',
-          'import Default from "changed";',
-          'import * as All from "changed";',
-          'import { } from "changed";',
-          'import { Foo } from "changed";',
-          'import { Foo, } from "changed";',
-          'import { Foo as Bar } from "changed";',
-          'import { Foo as Bar, } from "changed";',
-          'import { Foo, Bar } from "changed";',
-          'import { Foo, Bar, } from "changed";',
-          'import { Foo as Bar, Baz } from "changed";',
-          'import { Foo as Bar, Baz, } from "changed";',
-          'import { Foo, Bar as Baz } from "changed";',
-          'import { Foo, Bar as Baz, } from "changed";',
-          'import { Foo as Bar, Baz as Qux } from "changed";',
-          'import { Foo as Bar, Baz as Qux, } from "changed";',
-          'import { Foo, Bar, Baz } from "changed";',
-          'import { Foo, Bar, Baz, } from "changed";',
-          'import { Foo as Bar, Baz, Qux } from "changed";',
-          'import { Foo as Bar, Baz, Qux, } from "changed";',
-          'import { Foo, Bar as Baz, Qux } from "changed";',
-          'import { Foo, Bar as Baz, Qux, } from "changed";',
-          'import { Foo, Bar, Baz as Qux } from "changed";',
-          'import { Foo, Bar, Baz as Qux, } from "changed";',
-          'import { Foo as Bar, Baz as Qux, Norf } from "changed";',
-          'import { Foo as Bar, Baz as Qux, Norf, } from "changed";',
-          'import { Foo as Bar, Baz, Qux as Norf } from "changed";',
-          'import { Foo as Bar, Baz, Qux as Norf, } from "changed";',
-          'import { Foo, Bar as Baz, Qux as Norf } from "changed";',
-          'import { Foo, Bar as Baz, Qux as Norf, } from "changed";',
-          'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";',
-          'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";',
-          'import Default, * as All from "changed";',
-          'import Default, { } from "changed";',
-          'import Default, { Foo } from "changed";',
-          'import Default, { Foo, } from "changed";',
-          'import Default, { Foo as Bar } from "changed";',
-          'import Default, { Foo as Bar, } from "changed";',
-          'import Default, { Foo, Bar } from "changed";',
-          'import Default, { Foo, Bar, } from "changed";',
-          'import Default, { Foo as Bar, Baz } from "changed";',
-          'import Default, { Foo as Bar, Baz, } from "changed";',
-          'import Default, { Foo, Bar as Baz } from "changed";',
-          'import Default, { Foo, Bar as Baz, } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux, } from "changed";',
-          'import Default, { Foo, Bar, Baz } from "changed";',
-          'import Default, { Foo, Bar, Baz, } from "changed";',
-          'import Default, { Foo as Bar, Baz, Qux } from "changed";',
-          'import Default, { Foo as Bar, Baz, Qux, } from "changed";',
-          'import Default, { Foo, Bar as Baz, Qux } from "changed";',
-          'import Default, { Foo, Bar as Baz, Qux, } from "changed";',
-          'import Default, { Foo, Bar, Baz as Qux } from "changed";',
-          'import Default, { Foo, Bar, Baz as Qux, } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";',
-          'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";',
-          'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";',
-          'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";',
-          'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";',
-          'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";',
-          'import Default from "Y";',
-          'import * as All from \'z\';',
-          // import with support for new lines
-          "import { Foo,\n Bar }\n from 'changed';",
-          "import { \nFoo,\nBar,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz\n }\n from 'changed';",
-          "import { \nFoo as Bar,\n Baz\n, }\n from 'changed';",
-          "import { Foo,\n Bar as Baz\n }\n from 'changed';",
-          "import { Foo,\n Bar as Baz,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "import { Foo,\n Bar,\n Baz }\n from 'changed';",
-          "import { Foo,\n Bar,\n Baz,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';",
-          "import { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';",
-          "import { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';",
-          "import { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';",
-          "import { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';",
-          "import { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';",
-          "import { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';",
-          "import { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';",
-          "import Default,\n * as All from 'changed';",
-          "import Default,\n { } from 'changed';",
-          "import Default,\n { Foo\n }\n from 'changed';",
-          "import Default,\n { Foo,\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar\n } from\n 'changed';",
-          "import Default,\n { Foo,\n Bar,\n } from\n 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';",
-          "import Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';",
-          "import Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';",
-          "import Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';",
-          "import Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';",
-          "import Default\n , { } from 'changed';",
-          // single line export
-          "export'x';",
-          "export 'changed';",
-          "export 'changed' ;",
-          "export Default from 'changed';",
-          "export * as All from 'changed';",
-          "export {} from 'changed';",
-          "export { } from 'changed';",
-          "export {Foo} from 'changed';",
-          "export { Foo } from 'changed';",
-          "export { Foo, } from 'changed';",
-          "export {Foo as Bar} from 'changed';",
-          "export { Foo as Bar } from 'changed';",
-          "export { Foo as Bar, } from 'changed';",
-          "export { Foo, Bar } from 'changed';",
-          "export { Foo, Bar, } from 'changed';",
-          "export { Foo as Bar, Baz } from 'changed';",
-          "export { Foo as Bar, Baz, } from 'changed';",
-          "export { Foo, Bar as Baz } from 'changed';",
-          "export { Foo, Bar as Baz, } from 'changed';",
-          "export { Foo as Bar, Baz as Qux } from 'changed';",
-          "export { Foo as Bar, Baz as Qux, } from 'changed';",
-          "export { Foo, Bar, Baz } from 'changed';",
-          "export { Foo, Bar, Baz, } from 'changed';",
-          "export { Foo as Bar, Baz, Qux } from 'changed';",
-          "export { Foo as Bar, Baz, Qux, } from 'changed';",
-          "export { Foo, Bar as Baz, Qux } from 'changed';",
-          "export { Foo, Bar as Baz, Qux, } from 'changed';",
-          "export { Foo, Bar, Baz as Qux } from 'changed';",
-          "export { Foo, Bar, Baz as Qux, } from 'changed';",
-          "export { Foo as Bar, Baz as Qux, Norf } from 'changed';",
-          "export { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
-          "export { Foo as Bar, Baz, Qux as Norf } from 'changed';",
-          "export { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
-          "export { Foo, Bar as Baz, Qux as Norf } from 'changed';",
-          "export { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
-          "export { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';",
-          "export { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';",
-          "export Default, * as All from 'changed';",
-          "export Default, { } from 'changed';",
-          "export Default, { Foo } from 'changed';",
-          "export Default, { Foo, } from 'changed';",
-          "export Default, { Foo as Bar } from 'changed';",
-          "export Default, { Foo as Bar, } from 'changed';",
-          "export Default, { Foo, Bar } from 'changed';",
-          "export Default, { Foo, Bar, } from 'changed';",
-          "export Default, { Foo as Bar, Baz } from 'changed';",
-          "export Default, { Foo as Bar, Baz, } from 'changed';",
-          "export Default, { Foo, Bar as Baz } from 'changed';",
-          "export Default, { Foo, Bar as Baz, } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux, } from 'changed';",
-          "export Default, { Foo, Bar, Baz } from 'changed';",
-          "export Default, { Foo, Bar, Baz, } from 'changed';",
-          "export Default, { Foo as Bar, Baz, Qux } from 'changed';",
-          "export Default, { Foo as Bar, Baz, Qux, } from 'changed';",
-          "export Default, { Foo, Bar as Baz, Qux } from 'changed';",
-          "export Default, { Foo, Bar as Baz, Qux, } from 'changed';",
-          "export Default, { Foo, Bar, Baz as Qux } from 'changed';",
-          "export Default, { Foo, Bar, Baz as Qux, } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';",
-          "export Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';",
-          "export Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';",
-          "export Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';",
-          "export Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';",
-          "export Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';",
-          "export Default , { } from 'changed';",
-          'export "changed";',
-          'export Default from "changed";',
-          'export * as All from "changed";',
-          'export { } from "changed";',
-          'export { Foo } from "changed";',
-          'export { Foo, } from "changed";',
-          'export { Foo as Bar } from "changed";',
-          'export { Foo as Bar, } from "changed";',
-          'export { Foo, Bar } from "changed";',
-          'export { Foo, Bar, } from "changed";',
-          'export { Foo as Bar, Baz } from "changed";',
-          'export { Foo as Bar, Baz, } from "changed";',
-          'export { Foo, Bar as Baz } from "changed";',
-          'export { Foo, Bar as Baz, } from "changed";',
-          'export { Foo as Bar, Baz as Qux } from "changed";',
-          'export { Foo as Bar, Baz as Qux, } from "changed";',
-          'export { Foo, Bar, Baz } from "changed";',
-          'export { Foo, Bar, Baz, } from "changed";',
-          'export { Foo as Bar, Baz, Qux } from "changed";',
-          'export { Foo as Bar, Baz, Qux, } from "changed";',
-          'export { Foo, Bar as Baz, Qux } from "changed";',
-          'export { Foo, Bar as Baz, Qux, } from "changed";',
-          'export { Foo, Bar, Baz as Qux } from "changed";',
-          'export { Foo, Bar, Baz as Qux, } from "changed";',
-          'export { Foo as Bar, Baz as Qux, Norf } from "changed";',
-          'export { Foo as Bar, Baz as Qux, Norf, } from "changed";',
-          'export { Foo as Bar, Baz, Qux as Norf } from "changed";',
-          'export { Foo as Bar, Baz, Qux as Norf, } from "changed";',
-          'export { Foo, Bar as Baz, Qux as Norf } from "changed";',
-          'export { Foo, Bar as Baz, Qux as Norf, } from "changed";',
-          'export { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";',
-          'export { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";',
-          'export Default, * as All from "changed";',
-          'export Default, { } from "changed";',
-          'export Default, { Foo } from "changed";',
-          'export Default, { Foo, } from "changed";',
-          'export Default, { Foo as Bar } from "changed";',
-          'export Default, { Foo as Bar, } from "changed";',
-          'export Default, { Foo, Bar } from "changed";',
-          'export Default, { Foo, Bar, } from "changed";',
-          'export Default, { Foo as Bar, Baz } from "changed";',
-          'export Default, { Foo as Bar, Baz, } from "changed";',
-          'export Default, { Foo, Bar as Baz } from "changed";',
-          'export Default, { Foo, Bar as Baz, } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux, } from "changed";',
-          'export Default, { Foo, Bar, Baz } from "changed";',
-          'export Default, { Foo, Bar, Baz, } from "changed";',
-          'export Default, { Foo as Bar, Baz, Qux } from "changed";',
-          'export Default, { Foo as Bar, Baz, Qux, } from "changed";',
-          'export Default, { Foo, Bar as Baz, Qux } from "changed";',
-          'export Default, { Foo, Bar as Baz, Qux, } from "changed";',
-          'export Default, { Foo, Bar, Baz as Qux } from "changed";',
-          'export Default, { Foo, Bar, Baz as Qux, } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux, Norf } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";',
-          'export Default, { Foo as Bar, Baz, Qux as Norf } from "changed";',
-          'export Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";',
-          'export Default, { Foo, Bar as Baz, Qux as Norf } from "changed";',
-          'export Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";',
-          'export Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";',
-          'export Default from "Y";',
-          'export * as All from \'z\';',
-          // export with support for new lines
-          "export { Foo,\n Bar }\n from 'changed';",
-          "export { \nFoo,\nBar,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz\n }\n from 'changed';",
-          "export { \nFoo as Bar,\n Baz\n, }\n from 'changed';",
-          "export { Foo,\n Bar as Baz\n }\n from 'changed';",
-          "export { Foo,\n Bar as Baz,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "export { Foo,\n Bar,\n Baz }\n from 'changed';",
-          "export { Foo,\n Bar,\n Baz,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';",
-          "export { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';",
-          "export { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';",
-          "export { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';",
-          "export { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz,\n Qux as Norf\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz,\n Qux as Norf,\n }\n from 'changed';",
-          "export { Foo,\n Bar as Baz,\n Qux as Norf\n }\n from 'changed';",
-          "export { Foo,\n Bar as Baz,\n Qux as Norf,\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf\n }\n from 'changed';",
-          "export { Foo as Bar,\n Baz as Qux,\n Norf as Enuf,\n }\n from 'changed';",
-          "export Default,\n * as All from 'changed';",
-          "export Default,\n { } from 'changed';",
-          "export Default,\n { Foo\n }\n from 'changed';",
-          "export Default,\n { Foo,\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar\n } from\n 'changed';",
-          "export Default,\n { Foo,\n Bar,\n } from\n 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz,\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar as Baz\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar as Baz,\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar,\n Baz\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar,\n Baz,\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz,\n Qux\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz,\n Qux,\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar as Baz,\n Qux\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar as Baz,\n Qux,\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar,\n Baz as Qux\n }\n from 'changed';",
-          "export Default,\n { Foo,\n Bar,\n Baz as Qux,\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf,\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz,\n Qux as Norf, }\n from 'changed';",
-          "export Default,\n { Foo, Bar as Baz,\n Qux as Norf }\n from 'changed';",
-          "export Default,\n { Foo, Bar as Baz,\n Qux as Norf, }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore\n }\n from 'changed';",
-          "export Default,\n { Foo as Bar,\n Baz as Qux,\n Norf as NoMore,\n }\n from 'changed';",
-          "export Default\n , { } from 'changed';",
+        module: createModule('test module', ['x', 'y']),
+        name: 'test module',
+        code,
+        meta: {dependencyOffsets}
+      }).then(({code: processedCode}) => {
+        expect(processedCode).toEqual([
+          '__d("test module", function(global, require, module, exports) {' +
           // require
           'require("changed")',
           'require("Y")',
@@ -1045,6 +320,22 @@ describe('Resolver', function() {
       });
     });
 
+    pit('should pass through passed-in source maps', () => {
+      const module = createModule('test module');
+      const resolutionResponse = new ResolutionResponseMock({
+        dependencies: [module],
+        mainModuleId: 'test module',
+      });
+      const inputMap = {version: 3, mappings: 'ARBITRARY'};
+      return new Resolver({projectRoot: '/root'}).wrapModule({
+        resolutionResponse,
+        module,
+        name: 'test module',
+        code: 'arbitrary(code)',
+        map: inputMap,
+      }).then(({map}) => expect(map).toBe(inputMap));
+    });
+
     pit('should resolve polyfills', function () {
       const depResolver = new Resolver({
         projectRoot: '/root',
@@ -1053,17 +344,90 @@ describe('Resolver', function() {
       const code = [
         'global.fetch = () => 1;',
       ].join('');
-      return depResolver.wrapModule(
-        null,
-        polyfill,
+      return depResolver.wrapModule({
+        module: polyfill,
         code
-      ).then(processedCode => {
-        expect(processedCode.code).toEqual([
+      }).then(({code: processedCode}) => {
+        expect(processedCode).toEqual([
           '(function(global) {',
           'global.fetch = () => 1;',
           "\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);",
         ].join(''));
       });
     });
+
+    describe('JSON files:', () => {
+      const code = JSON.stringify({arbitrary: "data"});
+      const id = 'arbitrary.json';
+      let depResolver, module, resolutionResponse;
+
+      beforeEach(() => {
+        depResolver = new Resolver({projectRoot: '/root'});
+        module = createJsonModule(id);
+        resolutionResponse = new ResolutionResponseMock({
+          dependencies: [module],
+          mainModuleId: id,
+        });
+      });
+
+      pit('should prefix JSON files with `module.exports=`', () => {
+        return depResolver
+          .wrapModule({resolutionResponse, module, name: id, code})
+          .then(({code: processedCode}) =>
+            expect(processedCode).toEqual([
+              `__d(${JSON.stringify(id)}, function(global, require, module, exports) {`,
+              `module.exports = ${code}\n});`,
+            ].join('')));
+      });
+    });
+
+    describe('minification:', () => {
+      const code ='arbitrary(code)';
+      const id = 'arbitrary.js';
+      let depResolver, minifyCode, module, resolutionResponse, sourceMap;
+
+      beforeEach(() => {
+        minifyCode = jest.genMockFn().mockImpl((filename, code, map) =>
+          Promise.resolve({code, map}));
+        depResolver = new Resolver({
+          projectRoot: '/root',
+          minifyCode
+        });
+        module = createModule(id);
+        module.path = '/arbitrary/path.js';
+        resolutionResponse = new ResolutionResponseMock({
+          dependencies: [module],
+          mainModuleId: id,
+        });
+        sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'};
+      });
+
+      pit('should invoke the minifier with the wrapped code', () => {
+        const wrappedCode = `__d("${id}", function(global, require, module, exports) {${code}\n});`
+        return depResolver
+          .wrapModule({
+            resolutionResponse,
+            module,
+            name: id,
+            code,
+            map: sourceMap,
+            minify: true
+          }).then(() => {
+            expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap);
+          });
+      });
+
+      pit('should use minified code', () => {
+        const minifiedCode = 'minified(code)';
+        const minifiedMap = {version: 3, file: ['minified']};
+        minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap}));
+        return depResolver
+          .wrapModule({resolutionResponse, module, name: id, code, minify: true})
+          .then(({code, map}) => {
+            expect(code).toEqual(minifiedCode);
+            expect(map).toEqual(minifiedMap);
+          });
+      });
+    });
   });
 });
diff --git a/packager/react-packager/src/Resolver/index.js b/packager/react-packager/src/Resolver/index.js
index a677c2199da867..707402facf3a7d 100644
--- a/packager/react-packager/src/Resolver/index.js
+++ b/packager/react-packager/src/Resolver/index.js
@@ -11,9 +11,7 @@
 
 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 declareOpts = require('../lib/declareOpts');
 const Promise = require('promise');
 
@@ -49,6 +47,12 @@ const validateOpts = declareOpts({
     type: 'object',
     required: true,
   },
+  transformCode: {
+    type: 'function',
+  },
+  minifyCode: {
+    type: 'function',
+  },
 });
 
 const getDependenciesValidateOpts = declareOpts({
@@ -60,7 +64,7 @@ const getDependenciesValidateOpts = declareOpts({
     type: 'string',
     required: false,
   },
-  isUnbundle: {
+  unbundle: {
     type: 'boolean',
     default: false
   },
@@ -85,7 +89,6 @@ class Resolver {
           (opts.blacklistRE && opts.blacklistRE.test(filepath));
       },
       providesModuleNodeModules: [
-        'fbjs',
         'react',
         'react-native',
         // Parse requires AsyncStorage. They will
@@ -99,8 +102,10 @@ class Resolver {
       fileWatcher: opts.fileWatcher,
       cache: opts.cache,
       shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios',
+      transformCode: opts.transformCode,
     });
 
+    this._minifyCode = opts.minifyCode;
     this._polyfillModuleNames = opts.polyfillModuleNames || [];
 
     this._depGraph.load().catch(err => {
@@ -121,14 +126,15 @@ class Resolver {
     return this._depGraph.getModuleForPath(entryFile);
   }
 
-  getDependencies(main, options) {
-    const opts = getDependenciesValidateOpts(options);
-
-    return this._depGraph.getDependencies(
-      main,
-      opts.platform,
-      opts.recursive,
-    ).then(resolutionResponse => {
+  getDependencies(entryPath, options, transformOptions, onProgress) {
+    const {platform, recursive} = getDependenciesValidateOpts(options);
+    return this._depGraph.getDependencies({
+      entryPath,
+      platform,
+      transformOptions,
+      recursive,
+      onProgress,
+    }).then(resolutionResponse => {
       this._getPolyfillDependencies().reverse().forEach(
         polyfill => resolutionResponse.prependDependency(polyfill)
       );
@@ -144,18 +150,17 @@ class Resolver {
         ? path.join(__dirname, 'polyfills/prelude_dev.js')
         : path.join(__dirname, 'polyfills/prelude.js');
 
-    const moduleSystem = opts.isUnbundle
+    const moduleSystem = opts.unbundle
         ? path.join(__dirname, 'polyfills/require-unbundle.js')
         : path.join(__dirname, 'polyfills/require.js');
 
     return [
       prelude,
       moduleSystem
-    ].map(moduleName => new Polyfill({
-      path: moduleName,
+    ].map(moduleName => this._depGraph.createPolyfill({
+      file: moduleName,
       id: moduleName,
       dependencies: [],
-      isPolyfill: true,
     }));
   }
 
@@ -172,25 +177,22 @@ class Resolver {
     ].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,
       })
     );
   }
 
-  resolveRequires(resolutionResponse, module, code) {
+  resolveRequires(resolutionResponse, module, code, dependencyOffsets = []) {
     return Promise.resolve().then(() => {
-      if (module.isPolyfill()) {
-        return Promise.resolve({code});
-      }
-
       const resolvedDeps = Object.create(null);
       const resolvedDepsArr = [];
 
       return Promise.all(
+        // here, we build a map of all require strings (relative and absolute)
+        // to the canonical name of the module they reference
         resolutionResponse.getResolvedDependencyPairs(module).map(
           ([depName, depModule]) => {
             if (depModule) {
@@ -202,59 +204,81 @@ class Resolver {
           }
         )
       ).then(() => {
-        const relativizeCode = (codeMatch, pre, quot, depName, post) => {
+        const relativizeCode = (codeMatch, quot, depName) => {
+          // if we have a canonical name for the module imported here,
+          // we use it, so that require() is always called with the same
+          // id for every module.
+          // Example:
+          // -- in a/b.js:
+          //    require('./c') => require('a/c');
+          // -- in b/index.js:
+          //    require('../a/c') => require('a/c');
           const depId = resolvedDeps[depName];
           if (depId) {
-            return pre + quot + depId + post;
+            return quot + depId + quot;
           } else {
             return codeMatch;
           }
         };
 
-        code = code
-          .replace(replacePatterns.IMPORT_RE, relativizeCode)
-          .replace(replacePatterns.EXPORT_RE, relativizeCode)
-          .replace(replacePatterns.REQUIRE_RE, relativizeCode);
+        code = dependencyOffsets.reduceRight((codeBits, offset) => {
+          const first = codeBits.shift();
+          codeBits.unshift(
+            first.slice(0, offset),
+            first.slice(offset).replace(/(['"])([^'"']*)\1/, relativizeCode),
+          );
+          return codeBits;
+        }, [code]);
 
-        return module.getName().then(name => {
-          return {name, code};
-        });
+        return code.join('');
       });
     });
   }
 
-  wrapModule(resolutionResponse, module, code) {
-    if (module.isPolyfill()) {
-      return Promise.resolve({
-        code: definePolyfillCode(code),
-      });
+  wrapModule({
+    resolutionResponse,
+    module,
+    name,
+    map,
+    code,
+    meta = {},
+    minify = false
+  }) {
+    if (module.isJSON()) {
+      code = `module.exports = ${code}`;
     }
-
-    return this.resolveRequires(resolutionResponse, module, code).then(
-      ({name, code}) => {
-        return {name, code: defineModuleCode(name, code)};
-      });
+    const result = module.isPolyfill()
+      ? Promise.resolve({code: definePolyfillCode(code)})
+      : this.resolveRequires(
+        resolutionResponse,
+        module,
+        code,
+        meta.dependencyOffsets
+      ).then(code => ({code: defineModuleCode(name, code), map}));
+
+    return minify
+      ? result.then(({code, map}) => this._minifyCode(module.path, code, map))
+      : result;
   }
 
   getDebugInfo() {
     return this._depGraph.getDebugInfo();
   }
-
 }
 
 function defineModuleCode(moduleName, code) {
   return [
     `__d(`,
-    `'${moduleName}',`,
-    'function(global, require, module, exports) {',
-    `  ${code}`,
+    `${JSON.stringify(moduleName)}, `,
+    `function(global, require, module, exports) {`,
+      `${code}`,
     '\n});',
   ].join('');
 }
 
-function definePolyfillCode(code) {
+function definePolyfillCode(code,) {
   return [
-    '(function(global) {',
+    `(function(global) {`,
     code,
     `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`,
   ].join('');
diff --git a/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js b/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-test.js
deleted file mode 100644
index 39d687881885ba..00000000000000
--- a/packager/react-packager/src/Resolver/polyfills/__tests__/loadBundles-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.dontMock('../loadBundles');
-jest.mock('NativeModules');
-
-let loadBundles;
-let loadBundlesCalls;
-
-describe('loadBundles', () => {
-  beforeEach(() => {
-    loadBundles = jest.genMockFunction();
-    loadBundlesCalls = loadBundles.mock.calls;
-    require('NativeModules').RCTBundlesLoader = {loadBundles};
-
-    require('../loadBundles');
-  });
-
-  it('should set `global.__loadBundles` function when polyfill is initialized', () => {
-    expect(typeof global.__loadBundles).toBe('function');
-  });
-
-  it('should return a promise', () => {
-    loadBundles.mockImpl((bundles, callback) => callback());
-    expect(global.__loadBundles(['bundle.0']) instanceof Promise).toBeTruthy();
-  });
-
-  pit('shouldn\'t request already loaded bundles', () => {
-    loadBundles.mockImpl((bundles, callback) => callback());
-    return global.__loadBundles(['bundle.0'])
-      .then(() => global.__loadBundles(['bundle.0']))
-      .then(() => expect(loadBundlesCalls.length).toBe(1));
-  });
-
-  pit('shouldn\'n request inflight bundles', () => {
-    loadBundles.mockImpl((bundles, callback) => {
-      if (bundles.length === 1 && bundles[0] === 'bundle.0') {
-        setTimeout(callback, 1000);
-      } else if (bundles.length === 1 && bundles[0] === 'bundle.1') {
-        setTimeout(callback, 500);
-      }
-    });
-
-    const promises = Promise.all([
-      global.__loadBundles(['bundle.0']),
-      global.__loadBundles(['bundle.0', 'bundle.1']),
-    ]).then(() => {
-      expect(loadBundlesCalls.length).toBe(2);
-      expect(loadBundlesCalls[0][0][0]).toBe('bundle.0');
-      expect(loadBundlesCalls[1][0][0]).toBe('bundle.1');
-    });
-
-    jest.runAllTimers();
-    return promises;
-  });
-});
diff --git a/packager/react-packager/src/Resolver/polyfills/error-guard.js b/packager/react-packager/src/Resolver/polyfills/error-guard.js
index 8810a4b12a778d..d013ec1104c829 100644
--- a/packager/react-packager/src/Resolver/polyfills/error-guard.js
+++ b/packager/react-packager/src/Resolver/polyfills/error-guard.js
@@ -20,6 +20,9 @@ var ErrorUtils = {
   setGlobalHandler: function(fun) {
     ErrorUtils._globalHandler = fun;
   },
+  getGlobalHandler: function() {
+    return ErrorUtils._globalHandler;
+  },
   reportError: function(error) {
     ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);
   },
diff --git a/packager/react-packager/src/Resolver/polyfills/loadBundles.js b/packager/react-packager/src/Resolver/polyfills/loadBundles.js
deleted file mode 100644
index 7bb9be104a32c5..00000000000000
--- a/packager/react-packager/src/Resolver/polyfills/loadBundles.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint strict:0 */
-
-let loadBundlesOnNative = (bundles) =>
-  new Promise((resolve) =>
-    require('NativeModules').RCTBundlesLoader.loadBundles(bundles, resolve));
-
-let requestedBundles = Object.create(null);
-
-/**
- * Returns a promise that is fulfilled once all the indicated bundles are
- * loaded into memory and injected into the JS engine.
- * This invokation might need to go through the bridge
- * and run native code to load some, if not all, the requested bundles.
- * If all the bundles have already been loaded, the promise is resolved
- * immediately. Otherwise, we'll determine which bundles still need to get
- * loaded considering both, the ones already loaded, and the ones being
- * currently asynchronously loaded by other invocations to `__loadBundles`,
- * and return a promise that will get fulfilled once all these are finally
- * loaded.
- *
- * Note this function should only be invoked by generated code.
- */
-global.__loadBundles = function(bundles) {
-  // split bundles by whether they've already been requested or not
-  const bundlesToRequest = bundles.filter(b => !requestedBundles[b]);
-
-  // keep a reference to the promise loading each bundle
-  if (bundlesToRequest.length > 0) {
-    const nativePromise = loadBundlesOnNative(bundlesToRequest);
-    bundlesToRequest.forEach(b => requestedBundles[b] = nativePromise);
-  }
-
-  return Promise.all(bundles.map(bundle => requestedBundles[bundle]));
-};
diff --git a/packager/react-packager/src/Resolver/polyfills/require-unbundle.js b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js
index 87e856013e1e13..9c1e36e77ff545 100644
--- a/packager/react-packager/src/Resolver/polyfills/require-unbundle.js
+++ b/packager/react-packager/src/Resolver/polyfills/require-unbundle.js
@@ -80,7 +80,7 @@ function unknownModuleError(id) {
   let message = 'Requiring unknown module "' + id + '".';
   if (__DEV__) {
     message +=
-      'If you are sure the module is there, try restarting the packager.';
+      'If you are sure the module is there, try restarting the packager or running "npm install".';
   }
   return Error(message);
 }
diff --git a/packager/react-packager/src/Resolver/polyfills/require.js b/packager/react-packager/src/Resolver/polyfills/require.js
index a5a7a55f01a546..d5b0cd81a2f57c 100644
--- a/packager/react-packager/src/Resolver/polyfills/require.js
+++ b/packager/react-packager/src/Resolver/polyfills/require.js
@@ -48,7 +48,7 @@ function requireImpl(id) {
   if (!mod) {
     var msg = 'Requiring unknown module "' + id + '"';
     if (__DEV__) {
-      msg += '. If you are sure the module is there, try restarting the packager.';
+      msg += '. If you are sure the module is there, try restarting the packager or running "npm install".';
     }
     throw new Error(msg);
   }
@@ -94,12 +94,12 @@ global.__d = define;
 global.require = require;
 
 if (__DEV__) { // HMR
-  function accept(id, factory) {
+  function accept(id, factory, inverseDependencies) {
     var mod = modules[id];
 
     if (!mod) {
       define(id, factory);
-      return; // new modules don't need to be accepted
+      return true; // new modules don't need to be accepted
     }
 
     if (!mod.module.hot) {
@@ -107,21 +107,50 @@ if (__DEV__) { // HMR
         'Cannot accept module because Hot Module Replacement ' +
         'API was not installed.'
       );
-      return;
+      return false;
     }
 
-    if (mod.module.hot.acceptCallback) {
+    // replace and initialize factory
+    if (factory) {
       mod.factory = factory;
-      mod.isInitialized = false;
-      require(id);
+    }
+    mod.isInitialized = false;
+    require(id);
 
+    if (mod.module.hot.acceptCallback) {
       mod.module.hot.acceptCallback();
+      return true;
     } else {
-      console.warn(
-        '[HMR] Module `' + id + '` can\'t be hot reloaded because it ' +
-        'doesn\'t provide accept callback hook. Reload the app to get the updates.'
-      );
+      // 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);
+    }
+  }
+
+  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.__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 b731a026526e13..ce91842e1a7883 100644
--- a/packager/react-packager/src/Server/__tests__/Server-test.js
+++ b/packager/react-packager/src/Server/__tests__/Server-test.js
@@ -9,7 +9,9 @@
 'use strict';
 
 jest.setMock('worker-farm', function() { return () => {}; })
+    .dontMock('node-haste/node_modules/throat')
     .dontMock('os')
+    .dontMock('lodash')
     .dontMock('path')
     .dontMock('url')
     .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) })
@@ -20,11 +22,12 @@ jest.setMock('worker-farm', function() { return () => {}; })
 const Promise = require('promise');
 
 var Bundler = require('../../Bundler');
-var FileWatcher = require('../../DependencyResolver/FileWatcher');
 var Server = require('../');
 var Server = require('../../Server');
 var AssetServer = require('../../AssetServer');
 
+var FileWatcher;
+
 describe('processRequest', () => {
   var server;
 
@@ -57,6 +60,7 @@ describe('processRequest', () => {
   var triggerFileChange;
 
   beforeEach(() => {
+    FileWatcher = require('node-haste').FileWatcher;
     Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() =>
       Promise.resolve({
         getSource: () => 'this is the source',
@@ -193,7 +197,7 @@ describe('processRequest', () => {
       });
     });
 
-    it('rebuilds the bundles that contain a file when that file is changed', () => {
+    it('does not rebuild the bundles that contain a file when that file is changed', () => {
       const bundleFunc = jest.genMockFunction();
       bundleFunc
         .mockReturnValueOnce(
@@ -229,7 +233,7 @@ describe('processRequest', () => {
       jest.runAllTimers();
       jest.runAllTicks();
 
-      expect(bundleFunc.mock.calls.length).toBe(2);
+      expect(bundleFunc.mock.calls.length).toBe(1);
 
       makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
         .done(response =>
@@ -238,7 +242,7 @@ describe('processRequest', () => {
       jest.runAllTicks();
     });
 
-    it('rebuilds the bundles that contain a file when that file is changed, even when hot loading is enabled', () => {
+    it('does not rebuild the bundles that contain a file when that file is changed, even when hot loading is enabled', () => {
       const bundleFunc = jest.genMockFunction();
       bundleFunc
         .mockReturnValueOnce(
diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js
index 0146e7d6695e55..fe49ebbb354d75 100644
--- a/packager/react-packager/src/Server/index.js
+++ b/packager/react-packager/src/Server/index.js
@@ -10,12 +10,12 @@
 
 const Activity = require('../Activity');
 const AssetServer = require('../AssetServer');
-const FileWatcher = require('../DependencyResolver/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');
 
-const _ = require('underscore');
+const _ = require('lodash');
 const declareOpts = require('../lib/declareOpts');
 const path = require('path');
 const url = require('url');
@@ -73,10 +73,6 @@ const validateOpts = declareOpts({
     type: 'string',
     required: false,
   },
-  disableInternalTransforms: {
-    type: 'boolean',
-    default: false,
-  },
 });
 
 const bundleOpts = declareOpts({
@@ -146,6 +142,10 @@ const dependencyOpts = declareOpts({
     type: 'boolean',
     default: true,
   },
+  hot: {
+    type: 'boolean',
+    default: false,
+  },
 });
 
 class Server {
@@ -182,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,
@@ -197,7 +197,7 @@ class Server {
     this._fileWatcher.on('all', this._onFileChange.bind(this));
 
     this._debouncedFileChangeHandler = _.debounce(filePath => {
-      this._rebuildBundles(filePath);
+      this._clearBundles();
       this._informChangeWatchers();
     }, 50);
   }
@@ -240,8 +240,8 @@ class Server {
     return this.buildBundle(options);
   }
 
-  buildBundleForHMR(modules) {
-    return this._bundler.hmrBundle(modules);
+  buildBundleForHMR(modules, host, port) {
+    return this._bundler.hmrBundle(modules, host, port);
   }
 
   getShallowDependencies(entryFile) {
@@ -259,12 +259,7 @@ class Server {
       }
 
       const opts = dependencyOpts(options);
-      return this._bundler.getDependencies(
-        opts.entryFile,
-        opts.dev,
-        opts.platform,
-        opts.recursive,
-      );
+      return this._bundler.getDependencies(opts);
     });
   }
 
@@ -298,30 +293,6 @@ class Server {
     this._bundles = Object.create(null);
   }
 
-  _rebuildBundles() {
-    const buildBundle = this.buildBundle.bind(this);
-    const bundles = this._bundles;
-
-    Object.keys(bundles).forEach(function(optionsJson) {
-      const options = JSON.parse(optionsJson);
-      // Wait for a previous build (if exists) to finish.
-      bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() {
-        // With finally promise callback we can't change the state of the promise
-        // so we need to reassign the promise.
-        bundles[optionsJson] = buildBundle(options).then(function(p) {
-          // Make a throwaway call to getSource to cache the source string.
-          p.getSource({
-            inlineSourceMap: options.inlineSourceMap,
-            minify: options.minify,
-            dev: options.dev,
-          });
-          return p;
-        });
-      });
-      return bundles[optionsJson];
-    });
-  }
-
   _informChangeWatchers() {
     const watchers = this._changeWatchers;
     const headers = {
@@ -521,7 +492,7 @@ class Server {
       return true;
     }).join('.') + '.js';
 
-    const sourceMapUrlObj = _.clone(urlObj);
+    const sourceMapUrlObj = Object.assign({}, urlObj);
     sourceMapUrlObj.pathname = pathname.replace(/\.bundle$/, '.map');
 
     // try to get the platform from the url
diff --git a/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js b/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
index 729d2a1ddfb20a..4c1aa812c57a7f 100644
--- a/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
+++ b/packager/react-packager/src/SocketInterface/__tests__/SocketInterface-test.js
@@ -8,8 +8,7 @@
  */
 'use strict';
 
-jest.setMock('worker-farm', function() { return () => {}; })
-    .setMock('uglify-js')
+jest.setMock('uglify-js')
     .mock('child_process')
     .dontMock('../');
 
diff --git a/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js b/packager/react-packager/src/SocketInterface/__tests__/SocketServer-test.js
index 39c28d965f5ee4..12512eedc69eb1 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 69b52a4f9579e1..f9f3a77b67cf2d 100644
--- a/packager/react-packager/src/SocketInterface/index.js
+++ b/packager/react-packager/src/SocketInterface/index.js
@@ -11,7 +11,6 @@
 const Promise = require('promise');
 const SocketClient = require('./SocketClient');
 const SocketServer = require('./SocketServer');
-const _ = require('underscore');
 const crypto = require('crypto');
 const debug = require('debug')('ReactNativePackager:SocketInterface');
 const fs = require('fs');
@@ -99,7 +98,7 @@ function createServer(resolve, reject, options, sockPath) {
   const log = fs.openSync(logPath, 'a');
 
   // Enable server debugging by default since it's going to a log file.
-  const env = _.clone(process.env);
+  const env = Object.assign({}, process.env);
   env.DEBUG = 'ReactNativePackager:SocketServer';
 
   // We have to go through the main entry point to make sure
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 64b0c9b1896c3b..adcbf11ad8ebf0 100644
--- a/packager/react-packager/src/__mocks__/fs.js
+++ b/packager/react-packager/src/__mocks__/fs.js
@@ -8,181 +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,
-    });
-  }
-});
-
-const noop = () => {};
-fs.open.mockImpl(function(path) {
-  const callback = arguments[arguments.length - 1] || noop;
-  let data, error, fd;
-  try {
-    data = getToNode(path);
-  } catch (e) {
-    error = e;
-  }
-
-  if (error || data == null) {
-    error = Error(`ENOENT: no such file or directory, open ${path}`);
-  }
-  if (data != null) {
-    /* global Buffer: true */
-    fd = {
-      buffer: new Buffer(data, 'utf8'),
-      position: 0,
-    };
-  }
-
-  callback(error, fd);
-});
-
-fs.read.mockImpl((fd, buffer, writeOffset, length, position, callback = noop) => {
-  let bytesWritten;
-  try {
-    if (position == null || position < 0) {
-      ({position} = fd);
-    }
-    bytesWritten =
-      fd.buffer.copy(buffer, writeOffset, position, position + length);
-    fd.position = position + bytesWritten;
-  } catch (e) {
-    callback(Error('invalid argument'));
-    return;
-  }
-  callback(null, bytesWritten, buffer);
-});
-
-fs.close.mockImpl((fd, callback = noop) => {
-  try {
-    fd.buffer = fs.position = undefined;
-  } catch (e) {
-    callback(Error('invalid argument'));
-    return;
-  }
-  callback(null);
-});
-
-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/lib/ModuleTransport.js b/packager/react-packager/src/lib/ModuleTransport.js
index afb660d386dddd..8ba0aaea83b735 100644
--- a/packager/react-packager/src/lib/ModuleTransport.js
+++ b/packager/react-packager/src/lib/ModuleTransport.js
@@ -21,11 +21,7 @@ function ModuleTransport(data) {
   this.sourcePath = data.sourcePath;
 
   this.virtual = data.virtual;
-
-  if (this.virtual && data.map) {
-    throw new Error('Virtual modules cannot have source maps');
-  }
-
+  this.meta = data.meta;
   this.map = data.map;
 
   Object.freeze(this);
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 b29f1bd55be619..00000000000000
--- 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 7cf0f0a08524fa..00000000000000
--- 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
deleted file mode 100644
index 4d7d142c48a947..00000000000000
--- a/packager/react-packager/src/transforms/whole-program-optimisations/__tests__/dead-module-elimination-test.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';
-
-jest.autoMockOff();
-
-var deadModuleElimintation = require('../dead-module-elimination');
-var babel = require('babel-core');
-
-const compile = (code) =>
-  babel.transform(code, {
-    plugins: [deadModuleElimintation],
-  }).code;
-
-const compare = (source, output) => {
-  const out = trim(compile(source))
-    // workaround babel/source map bug
-    .replace(/^false;/, '');
-
-  expect(out).toEqual(trim(output));
-};
-
-
-const trim = (str) =>
-  str.replace(/\s/g, '');
-
-describe('dead-module-elimination', () => {
-  it('should inline __DEV__', () => {
-    compare(
-      `global.__DEV__ = false;
-      var foo = __DEV__;`,
-      `var foo = false;`
-    );
-  });
-
-  it('should accept unary operators with literals', () => {
-    compare(
-      `global.__DEV__ = !1;
-      var foo = __DEV__;`,
-      `var foo = false;`
-    );
-  });
-
-  it('should kill dead branches', () => {
-    compare(
-      `global.__DEV__ = false;
-      if (__DEV__) {
-        doSomething();
-      }`,
-      ``
-    );
-  });
-
-  it('should kill unreferenced modules', () => {
-    compare(
-      `__d('foo', function() {})`,
-      ``
-    );
-  });
-
-  it('should kill unreferenced modules at multiple levels', () => {
-    compare(
-      `__d('bar', function() {});
-      __d('foo', function() { require('bar'); });`,
-      ``
-    );
-  });
-
-  it('should kill modules referenced only from dead branches', () => {
-    compare(
-      `global.__DEV__ = false;
-      __d('bar', function() {});
-      if (__DEV__) { require('bar'); }`,
-      ``
-    );
-  });
-
-  it('should replace logical expressions with the result', () => {
-    compare(
-      `global.__DEV__ = false;
-      __d('bar', function() {});
-      __DEV__ && require('bar');`,
-      `false;`
-    );
-  });
-
-  it('should keep if result branch', () => {
-    compare(
-      `global.__DEV__ = false;
-      __d('bar', function() {});
-      if (__DEV__) {
-        killWithFire();
-      } else {
-        require('bar');
-      }`,
-      `__d('bar', function() {});
-      require('bar');`
-    );
-  });
-
-  it('should replace falsy ternaries with alternate expression', () => {
-    compare(
-      `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
deleted file mode 100644
index b5f33b4e09bea6..00000000000000
--- a/packager/react-packager/src/transforms/whole-program-optimisations/dead-module-elimination.js
+++ /dev/null
@@ -1,148 +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 t = require('babel-types');
-
-var globals = Object.create(null);
-var requires = Object.create(null);
-var _requires;
-
-const hasDeadModules = modules =>
-  Object.keys(modules).some(key => modules[key] === 0);
-
-function CallExpression(path) {
-  const { node } = path;
-  const fnName = node.callee.name;
-
-  if (fnName === 'require' || fnName === '__d') {
-    var moduleName = node.arguments[0].value;
-    if (fnName === '__d' && _requires && !_requires[moduleName]) {
-      path.remove();
-    } else if (fnName === '__d'){
-      requires[moduleName] = requires[moduleName] || 0;
-    } else {
-      requires[moduleName] = (requires[moduleName] || 0) + 1;
-    }
-  }
-}
-
-function IfStatement(path) {
-    const { node } = path;
-
-    if (node.test.type === 'Identifier' && node.test.name in globals) {
-      if (globals[node.test.name]) {
-        if (node.consequent.type === 'BlockStatement') {
-          path.replaceWithMultiple(node.consequent.body);
-        } else {
-          path.replaceWith(node.consequent);
-        }
-      } else if (node.alternate) {
-        if (node.alternate.type === 'BlockStatement') {
-          path.replaceWithMultiple(node.alternate.body);
-        } else {
-          path.replaceWith(node.alternate);
-        }
-      } else {
-        path.remove();
-      }
-    }
-  }
-
-module.exports = function () {
-  var firstPass = {
-    AssignmentExpression(path) {
-      const { node } = path;
-
-      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;
-        } else if (
-          node.right.type === 'UnaryExpression' &&
-          node.right.operator === '!' &&
-          node.right.argument.type === 'NumericLiteral'
-        ) {
-          value = !node.right.argument.value;
-        } else {
-          return;
-        }
-        globals[node.left.property.name] = value;
-
-        // workaround babel/source map bug - the minifier should strip it
-        path.replaceWith(t.booleanLiteral(value));
-
-        //path.remove();
-        //scope.removeBinding(node.left.name);
-      }
-    },
-    IfStatement,
-    ConditionalExpression: IfStatement,
-    Identifier(path) {
-      const { node } = path;
-
-      var parent = path.parent;
-      if (parent.type === 'AssignmentExpression' && parent.left === node) {
-        return;
-      }
-
-      if (node.name in globals) {
-        path.replaceWith(t.booleanLiteral(globals[node.name]));
-      }
-    },
-
-    CallExpression,
-
-    LogicalExpression(path) {
-      const { node } = path;
-
-      if (node.left.type === 'Identifier' && node.left.name in globals) {
-        const value = globals[node.left.name];
-
-        if (node.operator === '&&') {
-          if (value) {
-            path.replaceWith(node.right);
-          } else {
-            path.replaceWith(t.booleanLiteral(value));
-          }
-        } else if (node.operator === '||') {
-          if (value) {
-            path.replaceWith(t.booleanLiteral(value));
-          } else {
-            path.replaceWith(node.right);
-          }
-        }
-      }
-    }
-  };
-
-  var secondPass = {
-    CallExpression,
-  };
-
-  return {
-    visitor: {
-      Program(path) {
-        path.traverse(firstPass);
-        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 071f4007fab12d..18da16f28d4126 100644
--- a/packager/transformer.js
+++ b/packager/transformer.js
@@ -61,7 +61,7 @@ const getBabelRC = (function() {
     }
 
     return babelRC;
-  }
+  };
 })();
 
 /**
@@ -81,14 +81,16 @@ function buildBabelConfig(filename, options) {
   // Add extra plugins
   const extraPlugins = [externalHelpersPlugin];
 
-  if (options.inlineRequires) {
+  var inlineRequires = options.inlineRequires;
+  var blacklist = inlineRequires && inlineRequires.blacklist;
+  if (inlineRequires && !(blacklist && filename in blacklist)) {
     extraPlugins.push(inlineRequiresPlugin);
   }
 
   config.plugins = extraPlugins.concat(config.plugins);
 
   if (options.hot) {
-    const hmrConfig = makeHMRConfig(options);
+    const hmrConfig = makeHMRConfig(options, filename);
     config = Object.assign({}, config, hmrConfig);
   }
 
@@ -102,7 +104,9 @@ function transform(src, filename, options) {
   const result = babel.transform(src, babelConfig);
 
   return {
+    ast: result.ast,
     code: result.code,
+    map: result.map,
     filename: filename,
   };
 }
diff --git a/runXcodeTests.sh b/runXcodeTests.sh
index ab7ce69c1acc79..fda82d67f0be49 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/e2e-test-lego.sh b/scripts/e2e-test-lego.sh
new file mode 100755
index 00000000000000..e5b44e26d2bb0d
--- /dev/null
+++ b/scripts/e2e-test-lego.sh
@@ -0,0 +1,110 @@
+#!/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)
+TEMP=$(mktemp -d /tmp/react-native-XXXXXXXX)
+
+# When tests run on CI server, we won't be able to see logs
+# from packager because it runs in a separate window. This is
+# a simple workaround, see packager/packager.sh
+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_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=$?
+  set +e
+
+  if [ $EXIT_CODE -ne 0 ];
+  then
+    WATCHMAN_LOGS=/usr/local/Cellar/watchman/3.1/var/run/watchman/$USER.log
+    [ -f $WATCHMAN_LOGS ] && cat $WATCHMAN_LOGS
+
+    [ -f $REACT_PACKAGER_LOG ] && cat $REACT_PACKAGER_LOG
+  fi
+
+  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
+  ${NPM_PATH}npm uninstall -g sinopia
+}
+trap cleanup EXIT
+
+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
+# Temporarily installing sinopia from a github fork
+# TODO t10060166 use npm repo when bug is fixed
+which sinopia || ${NPM_PATH}npm install -g git://github.com/bestander/sinopia.git#68707efbab90569d5f52193138936f9281cc410c
+
+# but in order to make npm use sinopia we temporarily
+# replace its config file
+[ -f ~/.npmrc ] && cp ~/.npmrc ~/.npmrc.bak
+cp $SCRIPTS/e2e-npmrc ~/.npmrc
+
+sinopia --config $SCRIPTS/e2e-sinopia.config.yml &
+SINOPIA_PID=$!
+
+# Make sure to remove old version of react-native in
+# case it was cached
+${NPM_PATH}npm unpublish react-native --force
+${NPM_PATH}npm unpublish react-native-cli --force
+${NPM_PATH}npm publish $ROOT
+${NPM_PATH}npm publish $ROOT/react-native-cli
+
+${NPM_PATH}npm install -g react-native-cli
+react-native init EndToEndTest
+cd EndToEndTest
+
+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/e2e-test.sh b/scripts/e2e-test.sh
index 731a96edf7b277..6147789ed81dea 100755
--- a/scripts/e2e-test.sh
+++ b/scripts/e2e-test.sh
@@ -1,9 +1,19 @@
 #!/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" >&2
+  exit 1
+fi
+
 SCRIPTS=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
 ROOT=$(dirname $SCRIPTS)
 TEMP=$(mktemp -d /tmp/react-native-XXXXXXXX)
@@ -16,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=$?
@@ -30,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
@@ -42,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#68707efbab90569d5f52193138936f9281cc410c
 
 # but in order to make npm use sinopia we temporarily
 # replace its config file
@@ -54,20 +68,45 @@ SINOPIA_PID=$!
 
 # Make sure to remove old version of react-native in
 # case it was cached
+npm cache clear
 npm unpublish react-native --force
 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 --verbose
-cd EndToEndTest/ios
-
-# Make sure we installed local version of react-native
-ls EndToEndTest/`basename $MARKER` > /dev/null
-
-flow --retries 10
-
-../node_modules/react-native/packager/packager.sh --nonPersistent &
-SERVER_PID=$!
-xctool -scheme EndToEndTest -sdk iphonesimulator test
+react-native init EndToEndTest
+cd EndToEndTest
+
+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" >&2
+  exit 1
+  ;;
+*)
+  echo "Please run the script with --ios, --android or --packager" >&2
+  exit 1
+  ;;
+esac
diff --git a/scripts/run-android-instrumentation-tests.sh b/scripts/run-android-instrumentation-tests.sh
new file mode 100755
index 00000000000000..fb2960847ac734
--- /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 850f11c1b9cdf5..9ece2944657e65 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 5c162aab6feedb..3c633b91216b5e 100644
--- a/website/README.md
+++ b/website/README.md
@@ -17,14 +17,8 @@ Anytime you change the contents, just refresh the page and it's going to be upda
 
 # 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.
-
 ```sh
-./setup.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.
-
-```sh
-./publish.sh
-```
diff --git a/website/core/DocsSidebar.js b/website/core/DocsSidebar.js
index 2c18c7c8e75784..d8640e40d7908c 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/Header.js b/website/core/Header.js
index c84c98c401b03f..c83d7334b1b71d 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 afee22f2406be6..339b4290bd4802 100644
--- a/website/core/HeaderLinks.js
+++ b/website/core/HeaderLinks.js
@@ -14,11 +14,10 @@ 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: 'releases', href: 'https://github.com/facebook/react-native/releases', text: 'Releases'},
+    {section: 'docs', href: 'docs/getting-started.html', text: 'Docs'},
+    {section: 'support', href: 'support.html', text: 'Support'},
     {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'},
diff --git a/website/core/Site.js b/website/core/Site.js
index c1cdc5a129e4cd..cb2e7e7b701a62 100644
--- a/website/core/Site.js
+++ b/website/core/Site.js
@@ -11,9 +11,14 @@
 
 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;
+    const algoliaVersion = version === 'next' ? 'master' : 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';
@@ -30,10 +35,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" />
 
+          <base href={basePath} />
+
           <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
 
-          <link rel="shortcut icon" href="/react-native/img/favicon.png?2" />
-          <link rel="stylesheet" href="/react-native/css/react-native.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>
@@ -43,10 +50,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>
@@ -59,6 +69,7 @@ var Site = React.createClass({
           </div>
 
           <div id="fb-root" />
+          <script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
           <script dangerouslySetInnerHTML={{__html: `
             (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
             (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
@@ -70,9 +81,15 @@ var Site = React.createClass({
             !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)
             ){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";
             fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");
+
+            docsearch({
+              apiKey: '2c98749b4a1e588efec53b2acec13025',
+              indexName: 'react-native-versions',
+              inputSelector: '#algolia-doc-search',
+              algoliaOptions: { facetFilters: [ "tags:${algoliaVersion}" ], hitsPerPage: 5 }
+            });
           `}} />
-          <script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
-          <script src="/react-native/js/scripts.js" />
+          <script src="js/scripts.js" />
         </body>
       </html>
     );
diff --git a/website/layout/AutodocsLayout.js b/website/layout/AutodocsLayout.js
index e0b493ae192e43..269314cb1e0443 100644
--- a/website/layout/AutodocsLayout.js
+++ b/website/layout/AutodocsLayout.js
@@ -56,10 +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={'colors.html'}>color</a>
+      return <a href={'docs/colors.html'}>color</a>
     }
     if (type.raw === 'EdgeInsetsPropType') {
       return '{top: number, left: number, bottom: number, right: number}';
@@ -122,7 +122,7 @@ var ComponentDoc = React.createClass({
         </Header>
         {prop.deprecationMessage && <div className="deprecated">
           <div className="deprecatedTitle">
-            <img className="deprecatedIcon" src="/react-native/img/Warning.png" />
+            <img className="deprecatedIcon" src="img/Warning.png" />
             <span>Deprecated</span>
           </div>
           <div className="deprecatedMessage">
@@ -140,7 +140,7 @@ 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>
     );
@@ -175,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}>
@@ -416,7 +416,7 @@ var EmbeddedSimulator = React.createClass({
       <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" />
+          <img alt="Run example in simulator" width="170" height="358" src="img/alertIOS.png" />
         </div>
         <Modal />
       </div>
@@ -448,6 +448,14 @@ var Modal = React.createClass({
 });
 
 var Autodocs = React.createClass({
+  childContextTypes: {
+    permalink: React.PropTypes.string
+  },
+
+  getChildContext: function() {
+    return {permalink: this.props.metadata.permalink};
+  },
+
   renderFullDescription: function(docs) {
     if (!docs.fullDescription) {
       return;
@@ -506,8 +514,8 @@ var Autodocs = React.createClass({
             {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>
 
diff --git a/website/layout/DocsLayout.js b/website/layout/DocsLayout.js
index 673496938b653e..d84d9682d7a190 100644
--- a/website/layout/DocsLayout.js
+++ b/website/layout/DocsLayout.js
@@ -16,6 +16,14 @@ var React = require('React');
 var Site = require('Site');
 
 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;
@@ -32,8 +40,8 @@ var DocsLayout = React.createClass({
             />
             <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 a4ff02c6045849..94697a207d6e79 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 a653d6b4d94f1e..69e3093b99fd43 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.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 0476036b35c4ae..00000000000000
--- 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 00000000000000..25e43d6ea4df9e
--- /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 28ec151e9e9a44..00000000000000
--- a/website/publish.sh
+++ /dev/null
@@ -1,33 +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/
-cp ../circle.yml ../../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 62289e34cdde9d..c1096758c53c63 100644
--- a/website/server/convert.js
+++ b/website/server/convert.js
@@ -125,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/extractDocs.js b/website/server/extractDocs.js
index 6cbbc489f3150f..782d006fbe6ebf 100644
--- a/website/server/extractDocs.js
+++ b/website/server/extractDocs.js
@@ -203,7 +203,6 @@ var components = [
   '../Libraries/Components/Picker/Picker.js',
   '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.android.js',
   '../Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js',
-  '../Libraries/PullToRefresh/PullToRefreshViewAndroid.android.js',
   '../Libraries/Components/RefreshControl/RefreshControl.js',
   '../Libraries/Components/ScrollView/ScrollView.js',
   '../Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js',
@@ -235,6 +234,7 @@ var apis = [
   '../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',
@@ -252,6 +252,7 @@ var apis = [
   '../Libraries/Components/TimePickerAndroid/TimePickerAndroid.android.js',
   '../Libraries/Components/ToastAndroid/ToastAndroid.android.js',
   '../Libraries/Vibration/VibrationIOS.ios.js',
+  '../Libraries/Vibration/Vibration.js',
 ];
 
 var stylesWithPermalink = [
@@ -267,7 +268,7 @@ var stylesForEmbed = [
 ];
 
 var polyfills = [
-  '../Libraries/GeoLocation/Geolocation.js',
+  '../Libraries/Geolocation/Geolocation.js',
 ];
 
 var all = components
diff --git a/website/server/generate.js b/website/server/generate.js
index ab59a51ad048fc..dc96e2e8de8d85 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/src/react-native/circle.yml b/website/src/react-native/circle.yml
new file mode 100644
index 00000000000000..56ad41b2f1499f
--- /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 83cf44113d9862..a434245dd65142 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;
@@ -1182,6 +1190,18 @@ div[data-twttr-id] iframe {
   border-radius: 20px;
 }
 
+.versions th {
+  text-align: right;
+}
+
+.versions td, .versions th {
+  padding: 2px 5px;
+}
+
+.versions tr:nth-child(2n+1) {
+  background-color: hsl(198, 100%, 94%);
+}
+
 @media only screen
   and (max-device-width: 1024px) {
   #content {
@@ -1266,6 +1286,11 @@ div.algolia-search-wrapper {
   margin-left: 15px;
 }
 
+.algolia-autocomplete .aa-dropdown-menu {
+  margin-left: -210px;
+  margin-top: -4px;
+}
+
 @media screen and (max-width: 960px) {
   div.algolia-search-wrapper {
     display: none;
@@ -1273,81 +1298,52 @@ div.algolia-search-wrapper {
 }
 
 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('/react-native/img/search.png') no-repeat left center;
+  background: transparent url('../img/search.png') no-repeat 10px center;
   background-size: 16px 16px;
 
-  padding-left: 30px;
+  padding: 0 10px;
+  padding-left: 35px;
+  margin-top: 10px;
+  height: 30px;
   font-size: 16px;
   line-height: 20px;
-  background-color: #3B3738;
-  border-bottom: solid 3px #3B3738;
+  background-color: #555;
+  border-radius: 4px;
   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;
+  border: none;
+  width: 170px;
 }
 
 input#algolia-doc-search:focus {
-  border-color: #05A5D1;
-  width: 240px;
-}
-
-@media screen and (max-width: 1085px) {
-  input#algolia-doc-search:focus {
-    width: 178px;
-  }
+  width: 220px;
 }
 
 .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;
-  }
+.algolia-docsearch-suggestion {
+  border-bottom-color: hsl(198, 100%, 94%);
+}
+.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/AndroidSDK2.png b/website/src/react-native/img/AndroidSDK2.png
index 5ce88b7e1fcb0d..a554e07d48e2b3 100644
Binary files a/website/src/react-native/img/AndroidSDK2.png and b/website/src/react-native/img/AndroidSDK2.png differ
diff --git a/website/src/react-native/index.js b/website/src/react-native/index.js
index 919b799aeeb4ea..a85130d54412b5 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>
diff --git a/website/src/react-native/js/scripts.js b/website/src/react-native/js/scripts.js
index 8e3d9d440a701a..9bed77965b3cdd 100644
--- a/website/src/react-native/js/scripts.js
+++ b/website/src/react-native/js/scripts.js
@@ -42,11 +42,4 @@
     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 9b3b8722de38b7..909875c49a2688 100644
--- a/website/src/react-native/showcase.js
+++ b/website/src/react-native/showcase.js
@@ -70,12 +70,24 @@ var featured = [
     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',
@@ -83,6 +95,12 @@ var featured = [
     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',
@@ -92,6 +110,21 @@ var featured = [
       'https://blog.gyrosco.pe/the-making-of-gyroscope-running-a4ad10acc0d0',
     ],
   },
+  {
+    name: 'SoundCloud Pulse',
+    icon: 'https://i1.sndcdn.com/artworks-000149203716-k5je96-original.jpg',
+    link: 'https://itunes.apple.com/us/app/soundcloud-pulse-for-creators/id1074278256?mt=8',
+    author: 'SoundCloud',
+  },
+  {
+    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',
@@ -104,6 +137,12 @@ var featured = [
     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',
@@ -120,6 +159,21 @@ var featured = [
       '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',
+  },
+  {
+    name: 'Zhopout',
+    icon: 'http://zhopout.com/Content/Images/zhopout-logo-app-3.png',    
+    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.zhopout',
+    author: 'Jarvis Software Private Limited ',
+    blogs: [
+      "https://medium.com/@murugandurai/how-we-developed-our-mobile-app-in-30-days-using-react-native-45affa6449e8#.29nnretsi",     
+    ],
+  },
 ];
 
 var apps = [
@@ -129,6 +183,12 @@ var apps = [
     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',
@@ -166,12 +226,24 @@ var apps = [
     link: 'https://play.google.com/store/apps/details?id=com.cbssports.fantasy.franchisefootball2015',
     author: 'CBS Sports',
   },
+  {
+    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: '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: '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: 'Company name search',
     icon: 'http://a4.mzstatic.com/us/r30/Purple69/v4/fd/47/53/fd47537c-5861-e208-d1d1-1e26b5e45a36/icon350x350.jpeg',
@@ -187,6 +259,12 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/dareu-dare-your-friends-dare/id1046434563?mt=8',
     author: 'Rishabh Mehan',
   },
+  {
+    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',
@@ -197,13 +275,6 @@ var apps = [
     ],
     author: 'Genki Takiuchi (s21g Inc.)',
   },
-  {
-    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: 'Due',
     icon: 'http://a1.mzstatic.com/us/r30/Purple69/v4/a2/41/5d/a2415d5f-407a-c565-ffb4-4f27f23d8ac2/icon175x175.png',
@@ -235,6 +306,13 @@ var apps = [
     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',
@@ -247,6 +325,12 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/gou-huo/id1001476888?ls=1&mt=8',
     author: 'beijing qingfengyun Technology Co., Ltd.',
   },
+  {
+    name: 'HackerWeb',
+    icon: 'http://a5.mzstatic.com/us/r30/Purple49/v4/5a/bd/39/5abd3951-782c-ef12-8e40-33ebe1e43768/icon175x175.png',
+    link: 'https://itunes.apple.com/us/app/hackerweb/id1084209377?mt=8',
+    author: 'Lim Chee Aun',
+  },
   {
     name: 'Harmonizome',
     icon: 'http://is1.mzstatic.com/image/thumb/Purple6/v4/18/a9/bc/18a9bcde-d2d9-7574-2664-e82fff7b7208/pr_source.png/350x350-75.png',
@@ -271,6 +355,13 @@ var apps = [
     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',
@@ -311,8 +402,7 @@ var apps = [
   {
     name: 'LoadDocs',
     icon: 'http://a2.mzstatic.com/us/r30/Purple3/v4/b5/ca/78/b5ca78ca-392d-6874-48bf-762293482d42/icon350x350.jpeg',
-    linkAppStore: 'https://itunes.apple.com/us/app/loaddocs/id1041596066',
-    linkPlayStore: 'https://play.google.com/store/apps/details?id=com.convoy.loaddoc&hl=en',
+    link: 'https://itunes.apple.com/us/app/loaddocs/id1041596066',
     author: 'LoadDocs',
   },
   {
@@ -358,35 +448,42 @@ var apps = [
     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: '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',
     link: 'https://itunes.apple.com/us/app/mr.-dapper-men-fashion-app/id989735184?ls=1&mt=8',
     author: 'wei ping woon',
   },
+  {
+    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: '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: '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: '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: '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: 'No Fluff: Hiragana',
+    icon: 'https://lh3.googleusercontent.com/kStXwjpbPsu27E1nIEU1gfG0I8j9t5bAR_20OMhGZvu0j2vab3EbBV7O_KNZChjflZ_O',
+    link: 'https://play.google.com/store/apps/details?id=com.hiragana',
+    author: 'Matthias Sieber',
+    source: 'https://github.com/manonthemat/no-fluff-hiragana'
   },
   {
     name: 'Night Light',
@@ -424,6 +521,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',
@@ -448,12 +554,6 @@ var apps = [
     link: 'https://itunes.apple.com/us/app/rota-worker-shifts-on-demand/id1042111289?mt=8',
     author: 'Rota',
   },
-  {
-    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: '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',
@@ -473,6 +573,12 @@ var apps = [
     linkPlayStore: 'https://play.google.com/store/apps/details?id=kr.dobbit.sharehows',
     author: 'Dobbit Co., Ltd.'
   },
+  {
+    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: 'Spero for Cancer',
     icon: 'https://s3-us-west-1.amazonaws.com/cancerspot/site_images/Spero1024.png',
@@ -485,24 +591,37 @@ 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: '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: 'Tong Xing Wang',
     icon: 'http://a3.mzstatic.com/us/r30/Purple1/v4/7d/52/a7/7d52a71f-9532-82a5-b92f-87076624fdb2/icon175x175.jpeg',
     link: 'https://itunes.apple.com/cn/app/tong-xing-wang/id914254459?mt=8',
     author: 'Ho Yin Tsun Eugene',
   },
+  {
+    name: 'Veggies in Season',
+    icon: 'https://s3.amazonaws.com/veggies-assets/icon175x175.png',
+    link: 'https://itunes.apple.com/es/app/veggies-in-season/id1088215278?mt=8',
+    author: 'Victor Delgado',
+  },
   {
     name: 'WEARVR',
     icon: 'http://a2.mzstatic.com/eu/r30/Purple69/v4/4f/5a/28/4f5a2876-9530-ef83-e399-c5ef5b2dab80/icon175x175.png',
@@ -510,18 +629,18 @@ var apps = [
     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',
     icon: 'http://a4.mzstatic.com/us/r30/Purple6/v4/b0/44/f9/b044f93b-dbf3-9ae5-0f36-9b4956628cab/icon350x350.jpeg',
     link: 'https://itunes.apple.com/us/app/woop-app/id790247988?mt=8',
     author: 'Moritz Schwörer (@mosch)',
   },
-  {
-    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',
-  },
   {
     name: 'Yoloci',
     icon: 'http://a5.mzstatic.com/eu/r30/Purple7/v4/fa/e5/26/fae52635-b97c-bd53-2ade-89e2a4326745/icon175x175.jpeg',
@@ -566,6 +685,12 @@ var apps = [
     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 AppList = React.createClass({
@@ -586,6 +711,8 @@ var AppList = React.createClass({
         {app.linkAppStore && app.linkPlayStore ? this._renderLinks(app) : null}
         <p>By {app.author}</p>
         {this._renderBlogPosts(app)}
+				{this._renderSourceLink(app)}
+        {this._renderVideos(app)}
       </div>
     );
 
@@ -595,7 +722,7 @@ var AppList = React.createClass({
 
     return (
       <div className="showcase" key={i}>
-        <a href={app.link} target="blank">
+        <a href={app.link} target="_blank">
           {inner}
         </a>
       </div>
@@ -609,7 +736,7 @@ var AppList = React.createClass({
 
     if (app.blogs.length === 1) {
       return (
-        <p><a href={app.blogs[0]} target="blank">Blog post</a></p>
+        <p><a href={app.blogs[0]} target="_blank">Blog post</a></p>
       );
     } else if (app.blogs.length > 1) {
       return (
@@ -620,7 +747,41 @@ var AppList = React.createClass({
 
   _renderBlogPost: function(url, i) {
     return (
-      <a href={url} target="blank">
+      <a href={url} target="_blank">
+        {i + 1}&nbsp;
+      </a>
+    );
+  },
+
+	_renderSourceLink: function(app) {
+    if (!app.source) {
+      return;
+    }
+
+    return (
+      <p><a href={app.source} target="_blank">Source</a></p>
+    );
+  },
+
+  _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>
     );
@@ -629,9 +790,8 @@ var AppList = React.createClass({
   _renderLinks: function(app) {
     return (
       <p>
-        <a href={app.linkAppStore} target="blank">iOS</a>
-        {" - "}
-        <a href={app.linkPlayStore} target="blank">Android</a>
+        <a href={app.linkAppStore} target="_blank">iOS</a> -
+        <a href={app.linkPlayStore} target="_blank">Android</a>
       </p>
     );
   },
diff --git a/website/src/react-native/support.js b/website/src/react-native/support.js
index abf51306be9849..8bb1a0bd3453de 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">
@@ -33,12 +40,22 @@ var support = React.createClass({
             <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>
 
             <H2>Chat</H2>
-            <p>Join us in <strong><a href="irc://chat.freenode.net/reactnative">#reactnative on Reactiflux</a></strong>.</p>
+            <p>Join us in <strong><a href="https://discord.gg/0ZcbPKXt5bZjGY5n">#react-native on Reactiflux</a></strong>.</p>
+
+            <H2>Product Pains</H2>
+            <p>React Native uses <a href="https://productpains.com/product/react-native/">Product Pains</a> for feature requests. It has a voting system to surface which issues are most important to the community. GitHub issues should only be used for bugs.</p>
+
+            <iframe
+              width="100%"
+              height="600px"
+              scrolling="yes"
+              src="https://productpains.com/widget.html?token=3b929306-e0f7-5c94-7d7c-ecc05d059748"
+            />
 
             <H2>Twitter</H2>
             <p><a href="https://twitter.com/search?q=%23reactnative"><strong>#reactnative</strong> hash tag on Twitter</a> is used to keep up with the latest React Native news.</p>
diff --git a/website/src/react-native/versions.js b/website/src/react-native/versions.js
new file mode 100644
index 00000000000000..92f2d1cc3285b5
--- /dev/null
+++ b/website/src/react-native/versions.js
@@ -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.
+ */
+
+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',
+        release: null
+      },
+    ].concat(availableDocs.map((version) => {
+      const isLatest =  Metadata.config.RN_LATEST_VERSION === version;
+      const isRC = Metadata.config.RN_LATEST_VERSION < version;
+
+      var title = version;
+      if (isLatest) {
+        title = '(current) ' + title;
+      }
+      if (isRC) {
+        title += '-rc';
+      }
+
+      return {
+        title: title,
+        path: isLatest ? '/react-native' : '/react-native/releases/' + version,
+        release: 'https://github.com/facebook/react-native/releases/tag/v' + version + '.0' + (isRC ? '-rc' : '')
+      }
+    }));
+
+    if (!Metadata.config.RN_LATEST_VERSION) {
+      versions = [
+        {
+          title: 'current',
+          path: '/react-native',
+          release: null
+        },
+      ].concat(versions);
+    }
+
+    return (
+      <Site section="versions" title="Documentation archive">
+        <section className="content wrap documentationContent nosidebar">
+          <div className="inner-content">
+            <h1>React Native Versions</h1>
+            <p>React Native is following a 2-week train release. Every two weeks, a Release Candidate (rc) branch is created off of master and the previous rc branch is being officially released.</p>
+            <table className="versions">
+              <tbody>
+                {versions.map((version) =>
+                  <tr>
+                    <th>{version.title}</th>
+                    <td><a href={version.path}>Docs</a></td>
+                    <td>{version.release && <a href={version.release}>Release Notes</a>}</td>
+                  </tr>
+                )}
+              </tbody>
+            </table>
+          </div>
+        </section>
+      </Site>
+    );
+  }
+});
+
+module.exports = versions;