Permalink
Browse files

Use lazy getters to reduce `require('react-native')` overhead

Summary:
public

In the open source React Native implementation, the recommended approach for importing modules is by importing a the `ReactNative` object, which includes all available modules.

This is rather inefficient because it ends up initializing all the JS modules, even if you don't use them.

This diff switches the properties of the `ReactNative ` object to lazy getter functions, which defers the `require` until the module is actually requested.

This doesn't prevent unused modules from being included in the JS bundle, but it will prevent them from being initialized unless/until they are used.

Reviewed By: vjeux

Differential Revision: D2722993

fb-gh-sync-id: 0e9a2beb3aa6cd087a0592bd59a8f9242040be0c
  • Loading branch information...
nicklockwood authored and facebook-github-bot-5 committed Dec 8, 2015
1 parent 0f98ded commit f9b744d50137de25357994fe2e829f98104e2242
Showing with 89 additions and 85 deletions.
  1. +89 −85 Libraries/react-native/react-native.js
@@ -6,110 +6,114 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
- * @flow
+ * @noflow - get/set properties not yet supported by flow. also `...require(x)` is broken #6560135
*/
'use strict';
// Export React, plus some native additions.
-//
-// The use of Object.create/assign is to work around a Flow bug (#6560135).
-// Once that is fixed, change this back to
-//
-// var ReactNative = {...require('React'), /* additions */}
-//
-var ReactNative = Object.assign(Object.create(require('React')), {
+var ReactNative = {
// Components
- ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
- ART: require('ReactNativeART'),
- DatePickerIOS: require('DatePickerIOS'),
- DrawerLayoutAndroid: require('DrawerLayoutAndroid'),
- Image: require('Image'),
- ListView: require('ListView'),
- MapView: require('MapView'),
- Modal: require('Modal'),
- Navigator: require('Navigator'),
- NavigatorIOS: require('NavigatorIOS'),
- PickerIOS: require('PickerIOS'),
- ProgressBarAndroid: require('ProgressBarAndroid'),
- ProgressViewIOS: require('ProgressViewIOS'),
- ScrollView: require('ScrollView'),
- SegmentedControlIOS: require('SegmentedControlIOS'),
- SliderIOS: require('SliderIOS'),
- SnapshotViewIOS: require('SnapshotViewIOS'),
- Switch: require('Switch'),
- PullToRefreshViewAndroid: require('PullToRefreshViewAndroid'),
- SwitchAndroid: require('SwitchAndroid'),
- SwitchIOS: require('SwitchIOS'),
- TabBarIOS: require('TabBarIOS'),
- Text: require('Text'),
- TextInput: require('TextInput'),
- ToastAndroid: require('ToastAndroid'),
- ToolbarAndroid: require('ToolbarAndroid'),
- Touchable: require('Touchable'),
- TouchableHighlight: require('TouchableHighlight'),
- TouchableNativeFeedback: require('TouchableNativeFeedback'),
- TouchableOpacity: require('TouchableOpacity'),
- TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
- View: require('View'),
- ViewPagerAndroid: require('ViewPagerAndroid'),
- WebView: require('WebView'),
+ get ActivityIndicatorIOS() { return require('ActivityIndicatorIOS'); },
+ get ART() { return require('ReactNativeART'); },
+ get DatePickerIOS() { return require('DatePickerIOS'); },
+ get DrawerLayoutAndroid() { return require('DrawerLayoutAndroid'); },
+ get Image() { return require('Image'); },
+ get ListView() { return require('ListView'); },
+ get MapView() { return require('MapView'); },
+ get Modal() { return require('Modal'); },
+ get Navigator() { return require('Navigator'); },
+ get NavigatorIOS() { return require('NavigatorIOS'); },
+ get PickerIOS() { return require('PickerIOS'); },
+ get ProgressBarAndroid() { return require('ProgressBarAndroid'); },
+ get ProgressViewIOS() { return require('ProgressViewIOS'); },
+ get ScrollView() { return require('ScrollView'); },
+ get SegmentedControlIOS() { return require('SegmentedControlIOS'); },
+ get SliderIOS() { return require('SliderIOS'); },
+ get SnapshotViewIOS() { return require('SnapshotViewIOS'); },
+ get Switch() { return require('Switch'); },
+ get PullToRefreshViewAndroid() { return require('PullToRefreshViewAndroid'); },
+ get SwitchAndroid() { return require('SwitchAndroid'); },
+ get SwitchIOS() { return require('SwitchIOS'); },
+ get TabBarIOS() { return require('TabBarIOS'); },
+ get Text() { return require('Text'); },
+ get TextInput() { return require('TextInput'); },
+ get ToastAndroid() { return require('ToastAndroid'); },
+ get ToolbarAndroid() { return require('ToolbarAndroid'); },
+ get Touchable() { return require('Touchable'); },
+ get TouchableHighlight() { return require('TouchableHighlight'); },
+ get TouchableNativeFeedback() { return require('TouchableNativeFeedback'); },
+ get TouchableOpacity() { return require('TouchableOpacity'); },
+ get TouchableWithoutFeedback() { return require('TouchableWithoutFeedback'); },
+ get View() { return require('View'); },
+ get ViewPagerAndroid() { return require('ViewPagerAndroid'); },
+ get WebView() { return require('WebView'); },
// APIs
- ActionSheetIOS: require('ActionSheetIOS'),
- AdSupportIOS: require('AdSupportIOS'),
- AlertIOS: require('AlertIOS'),
- Animated: require('Animated'),
- AppRegistry: require('AppRegistry'),
- AppStateIOS: require('AppStateIOS'),
- AsyncStorage: require('AsyncStorage'),
- BackAndroid: require('BackAndroid'),
- CameraRoll: require('CameraRoll'),
- Dimensions: require('Dimensions'),
- Easing: require('Easing'),
- ImagePickerIOS: require('ImagePickerIOS'),
- IntentAndroid: require('IntentAndroid'),
- InteractionManager: require('InteractionManager'),
- LayoutAnimation: require('LayoutAnimation'),
- LinkingIOS: require('LinkingIOS'),
- NetInfo: require('NetInfo'),
- PanResponder: require('PanResponder'),
- PixelRatio: require('PixelRatio'),
- PushNotificationIOS: require('PushNotificationIOS'),
- Settings: require('Settings'),
- StatusBarIOS: require('StatusBarIOS'),
- StyleSheet: require('StyleSheet'),
- UIManager: require('UIManager'),
- VibrationIOS: require('VibrationIOS'),
+ get ActionSheetIOS() { return require('ActionSheetIOS'); },
+ get AdSupportIOS() { return require('AdSupportIOS'); },
+ get AlertIOS() { return require('AlertIOS'); },
+ get Animated() { return require('Animated'); },
+ get AppRegistry() { return require('AppRegistry'); },
+ get AppStateIOS() { return require('AppStateIOS'); },
+ get AsyncStorage() { return require('AsyncStorage'); },
+ get BackAndroid() { return require('BackAndroid'); },
+ get CameraRoll() { return require('CameraRoll'); },
+ get Dimensions() { return require('Dimensions'); },
+ get Easing() { return require('Easing'); },
+ get ImagePickerIOS() { return require('ImagePickerIOS'); },
+ get IntentAndroid() { return require('IntentAndroid'); },
+ get InteractionManager() { return require('InteractionManager'); },
+ get LayoutAnimation() { return require('LayoutAnimation'); },
+ get LinkingIOS() { return require('LinkingIOS'); },
+ get NetInfo() { return require('NetInfo'); },
+ get PanResponder() { return require('PanResponder'); },
+ get PixelRatio() { return require('PixelRatio'); },
+ get PushNotificationIOS() { return require('PushNotificationIOS'); },
+ get Settings() { return require('Settings'); },
+ get StatusBarIOS() { return require('StatusBarIOS'); },
+ get StyleSheet() { return require('StyleSheet'); },
+ get UIManager() { return require('UIManager'); },
+ get VibrationIOS() { return require('VibrationIOS'); },
// Plugins
- DeviceEventEmitter: require('RCTDeviceEventEmitter'),
- NativeAppEventEmitter: require('RCTNativeAppEventEmitter'),
- NativeModules: require('NativeModules'),
- Platform: require('Platform'),
- processColor: require('processColor'),
- requireNativeComponent: require('requireNativeComponent'),
+ get DeviceEventEmitter() { return require('RCTDeviceEventEmitter'); },
+ get NativeAppEventEmitter() { return require('RCTNativeAppEventEmitter'); },
+ get NativeModules() { return require('NativeModules'); },
+ get Platform() { return require('Platform'); },
+ get processColor() { return require('processColor'); },
+ get requireNativeComponent() { return require('requireNativeComponent'); },
// Prop Types
- EdgeInsetsPropType: require('EdgeInsetsPropType'),
- PointPropType: require('PointPropType'),
+ get EdgeInsetsPropType() { return require('EdgeInsetsPropType'); },
+ get PointPropType() { return require('PointPropType'); },
// See http://facebook.github.io/react/docs/addons.html
addons: {
- LinkedStateMixin: require('LinkedStateMixin'),
+ get LinkedStateMixin() { return require('LinkedStateMixin'); },
Perf: undefined,
- PureRenderMixin: require('ReactComponentWithPureRenderMixin'),
- TestModule: require('NativeModules').TestModule,
+ get PureRenderMixin() { return require('ReactComponentWithPureRenderMixin'); },
+ get TestModule() { return require('NativeModules').TestModule; },
TestUtils: undefined,
- batchedUpdates: require('ReactUpdates').batchedUpdates,
- cloneWithProps: require('cloneWithProps'),
- createFragment: require('ReactFragment').create,
- update: require('update'),
+ get batchedUpdates() { return require('ReactUpdates').batchedUpdates; },
+ get cloneWithProps() { return require('cloneWithProps'); },
+ get createFragment() { return require('ReactFragment').create; },
+ get update() { return require('update'); },
},
-});
+
+ // Note: this must be placed last to prevent eager
+ // evaluation of the getter-wrapped submodules above
+ ...require('React'),

This comment has been minimized.

Show comment
Hide comment

This comment has been minimized.

Show comment
Hide comment
@ajwhite

ajwhite Feb 4, 2016

Contributor

Having the same problem.

@ajwhite

ajwhite Feb 4, 2016

Contributor

Having the same problem.

This comment has been minimized.

Show comment
Hide comment
@yoniholmes

yoniholmes Mar 21, 2016

I'm running into the same problems with tape. Babel in node doesn't want to parse any file inside of /node_modules, apparently the assumption being that those files should already have been transpiled into es5. At time of writing, I haven't been able to force babel to parse those files – has anyone else had any success here?

@yoniholmes

yoniholmes Mar 21, 2016

I'm running into the same problems with tape. Babel in node doesn't want to parse any file inside of /node_modules, apparently the assumption being that those files should already have been transpiled into es5. At time of writing, I haven't been able to force babel to parse those files – has anyone else had any success here?

This comment has been minimized.

Show comment
Hide comment
@jhabdas

jhabdas Mar 21, 2016

Following this update I started transpiling the RN dependency in my app's build pipeline to get the expected ES5.

@jhabdas

jhabdas Mar 21, 2016

Following this update I started transpiling the RN dependency in my app's build pipeline to get the expected ES5.

This comment has been minimized.

Show comment
Hide comment
@jbrodriguez

jbrodriguez Apr 28, 2016

There are some changes coming in 0.25 that might help with this issue since ...require('React'), is being removed https://github.com/facebook/react-native/blob/2eafcd45dbd42f750df1ab9aa3770fed5cdf11ae/Libraries/react-native/react-native.js (the commit 2eafcd4#diff-cb910bd1233e7351becdc4aca5ad3688L121)

If that wouldn't help, the other two options I see are
https://github.com/jhabdas/react-native-webpack-starter-kit (by @jhabdas)
https://github.com/lelandrichardson/react-native-mock (by @lelandrichardson)

Hopefully, one of these options will facilitate testing in react-native (I'm using tape btw)

@jbrodriguez

jbrodriguez Apr 28, 2016

There are some changes coming in 0.25 that might help with this issue since ...require('React'), is being removed https://github.com/facebook/react-native/blob/2eafcd45dbd42f750df1ab9aa3770fed5cdf11ae/Libraries/react-native/react-native.js (the commit 2eafcd4#diff-cb910bd1233e7351becdc4aca5ad3688L121)

If that wouldn't help, the other two options I see are
https://github.com/jhabdas/react-native-webpack-starter-kit (by @jhabdas)
https://github.com/lelandrichardson/react-native-mock (by @lelandrichardson)

Hopefully, one of these options will facilitate testing in react-native (I'm using tape btw)

This comment has been minimized.

Show comment
Hide comment
@jhabdas

jhabdas Apr 28, 2016

When I started building my first RN app a year ago I found myself needing NPM deps using ES2015+ features not yet supported by React Native. As a result I added a simple build pipeline to facilitate scale and, after shipping to the App Store, extracted a kit for the OSS peeps. In doing so I purposely omitted a test framework to help encourage healthy competition between Tape, Jest and Mocha. So there it is.

@jhabdas

jhabdas Apr 28, 2016

When I started building my first RN app a year ago I found myself needing NPM deps using ES2015+ features not yet supported by React Native. As a result I added a simple build pipeline to facilitate scale and, after shipping to the App Store, extracted a kit for the OSS peeps. In doing so I purposely omitted a test framework to help encourage healthy competition between Tape, Jest and Mocha. So there it is.

+};
if (__DEV__) {
- ReactNative.addons.Perf = require('ReactDefaultPerf');
- ReactNative.addons.TestUtils = require('ReactTestUtils');
+ Object.defineProperty(ReactNative.addons, 'Perf', {
+ enumerable: true,
+ get: () => require('ReactDefaultPerf'),
+ });
+ Object.defineProperty(ReactNative.addons, 'TestUtils', {
+ enumerable: true,
+ get: () => require('ReactTestUtils'),
+ });
}
module.exports = ReactNative;

4 comments on commit f9b744d

@chandu0101

This comment has been minimized.

Show comment
Hide comment
@chandu0101

chandu0101 Dec 8, 2015

Contributor

👍 👏

Contributor

chandu0101 replied Dec 8, 2015

👍 👏

@syzer

This comment has been minimized.

Show comment
Hide comment

syzer replied Dec 9, 2015

👍

@jhabdas

This comment has been minimized.

Show comment
Hide comment
@jhabdas

jhabdas Dec 19, 2015

Good stuff! Here's a tip for anyone who lands here who's building with Webpack and sees this error:

ERROR in ./~/react-native/Libraries/react-native/react-native.js
Module parse failed: /Users/jhabdas/Developer/react-native-webpack-starter-kit/node_modules/react-native/Libraries/react-native/react-native.js Line 107: Unexpected token ...
You may need an appropriate loader to handle this file type.
|   // Note: this must be placed last to prevent eager
|   // evaluation of the getter-wrapped submodules above
|   ...require('React'),
| };
| 
 @ ./src/main.ios.js 5:19-42

You're gonna want a Webpack loader which supports ES7 object-rest-spread, e.g.

{
  test: /\.js$/,
  include: /node_modules\/react-native/,
  loader: 'babel',
  query: {
    cacheDirectory: true,
    presets: ['es2015', 'stage-1', 'react']
  }
}

Good stuff! Here's a tip for anyone who lands here who's building with Webpack and sees this error:

ERROR in ./~/react-native/Libraries/react-native/react-native.js
Module parse failed: /Users/jhabdas/Developer/react-native-webpack-starter-kit/node_modules/react-native/Libraries/react-native/react-native.js Line 107: Unexpected token ...
You may need an appropriate loader to handle this file type.
|   // Note: this must be placed last to prevent eager
|   // evaluation of the getter-wrapped submodules above
|   ...require('React'),
| };
| 
 @ ./src/main.ios.js 5:19-42

You're gonna want a Webpack loader which supports ES7 object-rest-spread, e.g.

{
  test: /\.js$/,
  include: /node_modules\/react-native/,
  loader: 'babel',
  query: {
    cacheDirectory: true,
    presets: ['es2015', 'stage-1', 'react']
  }
}
@ThaJay

This comment has been minimized.

Show comment
Hide comment
@ThaJay

ThaJay Feb 29, 2016

I don't know why, but the option "cacheDirectory: true" in query and "externals: {'react-native': "require('react-native/Libraries/react-native/react-native.js')"}" in include solved that error for me.
thanks @jhabdas.

I don't know why, but the option "cacheDirectory: true" in query and "externals: {'react-native': "require('react-native/Libraries/react-native/react-native.js')"}" in include solved that error for me.
thanks @jhabdas.

Please sign in to comment.