From e1454c29c5b28e8f7b07124a5f10a3e020463a49 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 26 May 2015 16:58:16 -0100 Subject: [PATCH 01/25] [ReactNative] Run UIExplorer tests on sandcastle --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ++++ Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index d72262e78f0..9d3adb2ee63 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,6 +50,10 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#if RUNNING_ON_CI + jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 245054621e5..349bd9a28a1 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.internal.uiexplorer.local CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSLocationWhenInUseUsageDescription + You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -34,8 +36,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSLocationWhenInUseUsageDescription - You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UIViewControllerBasedStatusBarAppearance diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index aa524ef56c1..7ff8c6379e2 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock block in blocks) { - block(cached, data, error); + for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { + cacheDownloadBlock(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9c0cacf709c..8a7e739bbdf 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,7 +35,11 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; +#if RUNNING_ON_CI + _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; +#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; +#endif } return self; } From e68f89bfadba2381e7ea6c958a5aa187bc862713 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 3 Jun 2015 09:49:22 -0700 Subject: [PATCH 02/25] Added ProgressViewIOS --- Examples/UIExplorer/ProgressViewIOSExample.js | 83 +++++++++++++++++ Examples/UIExplorer/UIExplorerList.js | 1 + .../ProgressViewIOS.android.js | 49 ++++++++++ .../ProgressViewIOS/ProgressViewIOS.ios.js | 89 +++++++++++++++++++ Libraries/react-native/react-native.js | 9 +- React/React.xcodeproj/project.pbxproj | 6 ++ React/Views/RCTProgressViewManager.h | 14 +++ React/Views/RCTProgressViewManager.m | 47 ++++++++++ 8 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 Examples/UIExplorer/ProgressViewIOSExample.js create mode 100644 Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js create mode 100644 Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js create mode 100644 React/Views/RCTProgressViewManager.h create mode 100644 React/Views/RCTProgressViewManager.m diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/ProgressViewIOSExample.js new file mode 100644 index 00000000000..f0a17a7c6e7 --- /dev/null +++ b/Examples/UIExplorer/ProgressViewIOSExample.js @@ -0,0 +1,83 @@ +/** + * 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 { + ProgressViewIOS, + StyleSheet, + View, +} = React; +var TimerMixin = require('react-timer-mixin'); + +var ProgressViewExample = React.createClass({ + mixins: [TimerMixin], + + getInitialState() { + return { + progress: 0, + }; + }, + + componentDidMount() { + this.updateProgress(); + }, + + updateProgress() { + var progress = this.state.progress + 0.01; + this.setState({ progress }); + this.requestAnimationFrame(() => this.updateProgress()); + }, + + getProgress(offset) { + var progress = this.state.progress + offset; + return Math.sin(progress % Math.PI) % 1; + }, + + render() { + return ( + + + + + + + + ); + }, +}); + +exports.framework = 'React'; +exports.title = 'ProgressViewIOS'; +exports.description = 'ProgressViewIOS'; +exports.examples = [{ + title: 'ProgressViewIOS', + render() { + return ( + + ); + } +}]; + +var styles = StyleSheet.create({ + container: { + marginTop: -20, + backgroundColor: 'transparent', + }, + progressView: { + marginTop: 20, + } +}); diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index a030220cabb..df9a3b12322 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -45,6 +45,7 @@ var COMPONENTS = [ require('./NavigatorIOSColorsExample'), require('./NavigatorIOSExample'), require('./PickerIOSExample'), + require('./ProgressViewIOSExample'), require('./ScrollViewExample'), require('./SegmentedControlIOSExample'), require('./SliderIOSExample'), diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js new file mode 100644 index 00000000000..b9103c6672b --- /dev/null +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js @@ -0,0 +1,49 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SegmentedControlIOS + */ + +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +var Dummy = React.createClass({ + render: function() { + return ( + + + ProgressViewIOS is not supported on this platform! + + + ); + }, +}); + +var styles = StyleSheet.create({ + dummy: { + width: 120, + height: 20, + backgroundColor: '#ffbcbc', + borderWidth: 1, + borderColor: 'red', + alignItems: 'center', + justifyContent: 'center', + }, + text: { + color: '#333333', + margin: 5, + fontSize: 10, + } +}); + +module.exports = Dummy; diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js new file mode 100644 index 00000000000..b4fb3e76875 --- /dev/null +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 ProgressViewIOS + * @flow + */ +'use strict'; + +var Image = require('Image'); +var NativeMethodsMixin = require('NativeMethodsMixin'); +var NativeModules = require('NativeModules'); +var PropTypes = require('ReactPropTypes'); +var React = require('React'); +var StyleSheet = require('StyleSheet'); + +var requireNativeComponent = require('requireNativeComponent'); +var verifyPropTypes = require('verifyPropTypes'); + +/** + * Use `ProgressViewIOS` to render a UIProgressView on iOS. + */ +var ProgressViewIOS = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * The progress bar style. + */ + progressViewStyle: PropTypes.oneOf(['default', 'bar']), + + /** + * The progress value (between 0 and 1). + */ + progress: PropTypes.number, + + /** + * The tint color of the progress bar itself. + */ + progressTintColor: PropTypes.string, + + /** + * The tint color of the progress bar track. + */ + trackTintColor: PropTypes.string, + + /** + * A stretchable image to display as the progress bar. + */ + progressImage: Image.propTypes.source, + + /** + * A stretchable image to display behind the progress bar. + */ + trackImage: Image.propTypes.source, + }, + + render: function() { + return ( + + ); + } +}); + +var styles = StyleSheet.create({ + progressView: { + height: NativeModules.ProgressViewManager.ComponentHeight + }, +}); + +var RCTProgressView = requireNativeComponent( + 'RCTProgressView', + null +); +if (__DEV__) { + verifyPropTypes( + RCTProgressView, + RCTProgressView.viewConfig + ); +} + +module.exports = ProgressViewIOS; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index c81183b5b2f..6b1c992cced 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -24,11 +24,12 @@ var ReactNative = Object.assign(Object.create(require('React')), { Image: require('Image'), ListView: require('ListView'), MapView: require('MapView'), + Navigator: require('Navigator'), NavigatorIOS: require('NavigatorIOS'), PickerIOS: require('PickerIOS'), - Navigator: require('Navigator'), - SegmentedControlIOS: require('SegmentedControlIOS'), + ProgressViewIOS: require('ProgressViewIOS'), ScrollView: require('ScrollView'), + SegmentedControlIOS: require('SegmentedControlIOS'), SliderIOS: require('SliderIOS'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), @@ -47,12 +48,12 @@ var ReactNative = Object.assign(Object.create(require('React')), { AsyncStorage: require('AsyncStorage'), CameraRoll: require('CameraRoll'), InteractionManager: require('InteractionManager'), - LinkingIOS: require('LinkingIOS'), LayoutAnimation: require('LayoutAnimation'), + LinkingIOS: require('LinkingIOS'), NetInfo: require('NetInfo'), + PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), - PanResponder: require('PanResponder'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index c7309989b4c..3c0cfe72284 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; }; 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; }; 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; }; + 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; }; 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; }; 1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; }; 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; }; @@ -104,6 +105,8 @@ 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = ""; }; 134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = ""; }; 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = ""; }; + 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProgressViewManager.h; sourceTree = ""; }; + 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProgressViewManager.m; sourceTree = ""; }; 13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = ""; }; 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = ""; }; 1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = ""; }; @@ -307,6 +310,8 @@ 58114A141AAE854800E7D092 /* RCTPickerManager.h */, 58114A151AAE854800E7D092 /* RCTPickerManager.m */, 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, + 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */, + 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */, 131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */, 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */, 131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */, @@ -524,6 +529,7 @@ 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, + 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */, diff --git a/React/Views/RCTProgressViewManager.h b/React/Views/RCTProgressViewManager.h new file mode 100644 index 00000000000..ae8a6a38805 --- /dev/null +++ b/React/Views/RCTProgressViewManager.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewManager.h" + +@interface RCTProgressViewManager : RCTViewManager + +@end diff --git a/React/Views/RCTProgressViewManager.m b/React/Views/RCTProgressViewManager.m new file mode 100644 index 00000000000..deb6285a68c --- /dev/null +++ b/React/Views/RCTProgressViewManager.m @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 "RCTProgressViewManager.h" + +#import "RCTConvert.h" + +@implementation RCTConvert (RCTProgressViewManager) + +RCT_ENUM_CONVERTER(UIProgressViewStyle, (@{ + @"default": @(UIProgressViewStyleDefault), + @"bar": @(UIProgressViewStyleBar), +}), UIProgressViewStyleDefault, integerValue) + +@end + +@implementation RCTProgressViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[UIProgressView alloc] init]; +} + +RCT_EXPORT_VIEW_PROPERTY(progressViewStyle, UIProgressViewStyle) +RCT_EXPORT_VIEW_PROPERTY(progress, float) +RCT_EXPORT_VIEW_PROPERTY(progressTintColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(trackTintColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(progressImage, UIImage) +RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage) + +- (NSDictionary *)constantsToExport +{ + UIProgressView *view = [[UIProgressView alloc] init]; + return @{ + @"ComponentHeight": @(view.intrinsicContentSize.height), + }; +} + +@end From bba576a1728b1d228bf3daa26bef5b96c062aeaa Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 10:25:53 -0700 Subject: [PATCH 03/25] press ctrl-i to toggle inspect element --- React/Base/RCTDevMenu.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 2416e974c17..18de3a457e9 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -110,6 +110,13 @@ RCT_EXPORT_MODULE() [weakSelf toggle]; }]; + // Toggle element inspector + [commands registerKeyCommandWithInput:@"i" + modifierFlags:UIKeyModifierCommand + action:^(UIKeyCommand *command) { + [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; + }]; + // Reload in normal mode [commands registerKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand From ca7a764c8c76b8f0c8eb3ff19835f563854a8dac Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 3 Jun 2015 11:35:32 -0700 Subject: [PATCH 04/25] [Cosmetic] Fix typo in packager README Summary: Closes https://github.com/facebook/react-native/pull/693 Github Author: Tyler McGinnis Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/README.md b/packager/README.md index 8f9f649bf5a..c3fae48064e 100644 --- a/packager/README.md +++ b/packager/README.md @@ -62,7 +62,7 @@ if the option is boolean `1/0` or `true/false` is accepted. Here are the current options the packager accepts: * `dev` boolean, defaults to true: sets a global `__DEV__` variable - which will effect how the React Nativeg core libraries behave. + which will effect how the React Native core libraries behave. * `minify` boolean, defaults to false: whether to minify the bundle. * `runModule` boolean, defaults to true: whether to require your entry point module. So if you requested `moduleName`, this option will add From 435125f4a08de906429d8ecc66cb963b5ee64c44 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 3 Jun 2015 17:54:40 -0100 Subject: [PATCH 05/25] Revert "[ReactNative] Run UIExplorer tests on sandcastle" --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ---- Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ---- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 9d3adb2ee63..d72262e78f0 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,10 +50,6 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#if RUNNING_ON_CI - jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#endif - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 349bd9a28a1..245054621e5 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.internal.uiexplorer.local + com.facebook.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,8 +22,6 @@ 1 LSRequiresIPhoneOS - NSLocationWhenInUseUsageDescription - You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -36,6 +34,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSLocationWhenInUseUsageDescription + You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UIViewControllerBasedStatusBarAppearance diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 7ff8c6379e2..aa524ef56c1 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { - cacheDownloadBlock(cached, data, error); + for (RCTCachedDataDownloadBlock block in blocks) { + block(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 8a7e739bbdf..9c0cacf709c 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,11 +35,7 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; -#if RUNNING_ON_CI - _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; -#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; -#endif } return self; } From 5a191dadfc61b1ad1dc3b90560fd2ab1fb997370 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 11:39:39 -0700 Subject: [PATCH 06/25] [react-packager] Add support for nested node_modules Summary: @public The packager's resolver started out imitating node-haste, which meant that we didn't support nested modules. Now this is a problem. Bigger projects are bound to have versions of different versions of the same package at different levels of the dependency tree. This makes loading dependencies lazy for node_modules and implements the node resolution algorithm. However, it also mantains that some modules are still "haste" format, which currently defaults to "react-native" and "react-tools". Finally, this means ~5 seconds speed up on every server start. This should also have a big impact on open source users with projects with big node_modules. Test Plan: 1- test the app with --reset-cache 2- click around test and production apps 3- update the OSS library 4- create a new project 5- npm install multiple modules 6- create some version conflict in your project 7- make sure we do the "right" thing 8- test file changes to make sure it works --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 + .../__tests__/DependencyGraph-test.js | 1959 +++++++++++++++-- .../haste/DependencyGraph/index.js | 382 +++- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 2125 insertions(+), 377 deletions(-) create mode 100644 packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 90db1c4ade5..3c1a1d26dc5 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,6 +8,9 @@ */ 'use strict'; +var Promise = require('bluebird'); +var isAbsolutePath = require('absolute-path'); + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -17,17 +20,13 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } + if (!isAbsolutePath(fields.path)) { + throw new Error('Expected absolute path but found: ' + fields.path); + } this.path = fields.path; - if (!fields.dependencies) { - throw new Error('Missing required fields dependencies'); - } this.dependencies = fields.dependencies; - this.resolveDependency = fields.resolveDependency; - - this.entry = fields.entry || false; - this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -50,12 +49,30 @@ function ModuleDescriptor(fields) { this._fields = fields; } +ModuleDescriptor.prototype.loadDependencies = function(loader) { + if (!this.dependencies) { + if (this._loadingDependencies) { + return this._loadingDependencies; + } + + var self = this; + this._loadingDependencies = loader(this).then(function(dependencies) { + self.dependencies = dependencies; + }); + return this._loadingDependencies; + } + + return Promise.resolve(this.dependencies); +}; + ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; + var ret = {}; + Object.keys(this).forEach(function(prop) { + if (prop[0] !== '_' && typeof this[prop] !== 'function') { + ret[prop] = this[prop]; + } + }, this); + return ret; }; module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js new file mode 100644 index 00000000000..99bed5df70e --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('absolute-path') + .dontMock('../ModuleDescriptor'); + + +describe('ModuleDescriptor', function() { + var ModuleDescriptor; + var Promise; + + beforeEach(function() { + ModuleDescriptor = require('../ModuleDescriptor'); + Promise = require('bluebird'); + }); + + describe('constructor', function() { + it('should validate fields', function() { + /* eslint no-new:0*/ + expect(function() { + new ModuleDescriptor({}); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + }); + }).toThrow(); + + var m = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + expect(m.toJSON()).toEqual({ + altId:undefined, + dependencies: undefined, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + }); + }); + + describe('loadDependencies', function() { + pit('should load dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + + pit('should load cached dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + return mod.loadDependencies(function() { + throw new Error('no!'); + }); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + }); +}); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index c247e59d37a..6c0fd05f6f5 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,11 +10,12 @@ jest .dontMock('../index') + .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); + .dontMock('../../../ModuleDescriptor'); jest.mock('fs'); @@ -34,6 +35,14 @@ describe('DependencyGraph', function() { }; }); + // There are a lot of crap in ModuleDescriptors, this maps an array + // to get the relevant data. + function getDataFromModules(modules) { + return modules.map(function(module) { + return module.toJSON(); + }); + } + describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -58,11 +67,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -95,11 +126,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -126,20 +179,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'] + dependencies: ['./a.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -167,15 +231,31 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -205,20 +285,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'] + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -253,8 +344,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', @@ -264,7 +355,13 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ] + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -272,20 +369,32 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -317,14 +426,20 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -332,6 +447,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'image!a', @@ -339,6 +458,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -368,11 +491,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -402,13 +547,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -433,13 +596,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -468,14 +648,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -503,13 +700,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -534,13 +748,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -568,10 +799,21 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -612,18 +854,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', + { + id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'] + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'c', + { + id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -659,17 +915,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage', + { + id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -693,12 +963,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'] + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); }); @@ -732,16 +1009,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -776,16 +1066,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -820,24 +1124,56 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', + altId: undefined, path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', + altId: undefined, path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/other', + { + id: 'aPackage/other', + altId: undefined, path: '/root/aPackage/other.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -870,16 +1206,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/client', + { + id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -912,16 +1264,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -956,16 +1322,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1000,16 +1379,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1054,28 +1446,58 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1120,20 +1542,576 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'] + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/lol', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/wow', + path: '/root/node_modules/foo/node_modules/bar/wow.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main2', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + altId: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + 'require("submodule");', + ].join('\n'), + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule dontWork', + ' */', + 'hi();', + ].join('\n'), + }, + 'submodule': { + 'package.json': JSON.stringify({ + name: 'submodule', + main: 'main.js', + }), + 'main.js': 'log()', + }, + } + }, + 'ember': { + 'package.json': JSON.stringify({ + name: 'ember', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule wontWork', + ' */', + 'hi();', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + altId:'react-tools/main', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'submodule/main', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1187,22 +2165,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1239,22 +2231,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1291,19 +2297,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1342,7 +2360,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1354,27 +2372,54 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -1400,32 +2445,50 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'] + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1452,30 +2515,49 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'] + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - }, - { id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'index', + altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -1520,7 +2602,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1532,21 +2614,42 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'] + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1584,25 +2687,407 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); + triggerFileChange('change', 'index.js', root); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/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'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/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'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser', + altId: undefined, + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 0881e5dc77d..fc899cb2fe0 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,3 +1,6 @@ +// TODO +// Fix it to work with tests + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -19,6 +22,7 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); +var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -45,7 +49,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + _providesModuleNodeModules: { + type: 'array', + default: ['react-tools', 'react-native'], + }, }); function DependecyGraph(options) { @@ -69,6 +77,8 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); + this._providesModuleNodeModules = opts._providesModuleNodeModules; + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -90,59 +100,101 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } + return this.load().then(function() { + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); + } - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } - var self = this; - var deps = []; - var visited = Object.create(null); + var self = this; + var deps = []; + var visited = Object.create(null); - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; - // Recursively collect the dependency list. - function collect(module) { - deps.push(module); + // Recursively collect the dependency list. + function collect(mod) { + deps.push(mod); - module.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(module, id); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - module.id - ); - return; + if (mod.dependencies == null) { + return mod.loadDependencies(function() { + return readFile(mod.path, 'utf8').then(function(content) { + return extractRequires(content); + }); + }).then(function() { + return iter(mod); + }); } - if (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } + return iter(mod); + } + + function iter(mod) { + var p = Promise.resolve(); + mod.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(mod, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + mod.id + ); + return; + } + + p = p.then(function() { + if (!visited[realId(dep)]) { + visited[realId(dep)] = true; + return collect(dep); + } + return null; + }); + }); + return p; + } + + visited[realId(module)] = true; + return collect(module).then(function() { + return deps; }); - } - - visited[module.id] = true; - collect(module); - - return deps; + }.bind(this)); }; +function browserFieldRedirect(packageJson, modulePath, isMain) { + if (packageJson.browser && typeof packageJson.browser === 'object') { + if (isMain) { + var tmpMain = packageJson.browser[modulePath] || + packageJson.browser[sansExtJs(modulePath)] || + packageJson.browser[withExtJs(modulePath)]; + if (tmpMain) { + return tmpMain; + } + } else { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + return path.join(packageJson._root, tmpModulePath); + } + } + } + return modulePath; +} + /** * Given a module descriptor `fromModule` return the module descriptor for * the required module `depModuleId`. It could be top-level or relative, @@ -157,7 +209,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); + debug('WARNING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -177,19 +229,39 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } + + var packageName = depModuleId.replace(/\/.+/, ''); + packageJson = this._lookupNodePackage(fromModule.path, packageName); + + if (packageJson != null && packageName !== depModuleId) { + modulePath = path.join( + packageJson._root, + path.relative(packageName, depModuleId) + ); + + modulePath = browserFieldRedirect(packageJson, modulePath); + + dep = this._graph[withExtJs(modulePath)]; + if (dep != null) { + return dep; + } + } + // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (this._moduleById[sansExtJs(depModuleId)]) { + if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - - // We are being forgiving here and raising an error because we could be - // processing a file that uses it's own require system. if (packageJson == null) { + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; + } + + // We are being forgiving here and not raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null || packageName !== depModuleId) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -198,33 +270,8 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main; - - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - if (packageJson.browser && typeof packageJson.browser === 'object') { - var tmpMain = packageJson.browser[main] || - packageJson.browser[withExtJs(main)] || - packageJson.browser[sansExtJs(main)]; - if (tmpMain) { - main = tmpMain; - } - } - - modulePath = withExtJs(path.join(packageJson._root, main)); - dep = this._graph[modulePath]; - - // Some packages use just a dir and rely on an index.js inside that dir. - if (dep == null) { - dep = this._graph[path.join(packageJson._root, main, 'index.js')]; - } - + // We are requiring node or a haste package via it's main file. + dep = this._resolvePackageMain(packageJson); if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -248,15 +295,7 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - - if (packageJson.browser && typeof packageJson.browser === 'object') { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - modulePath = path.join(packageJson._root, tmpModulePath); - } - } + modulePath = browserFieldRedirect(packageJson, modulePath); // JS modules can be required without extensios. if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { @@ -291,6 +330,25 @@ DependecyGraph.prototype.resolveDependency = function( } }; +DependecyGraph.prototype._resolvePackageMain = function(packageJson) { + var main; + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + main = browserFieldRedirect(packageJson, main, true); + + var modulePath = withExtJs(path.join(packageJson._root, main)); + + return this._graph[modulePath] || + // Some packages use just a dir and rely on an index.js inside that dir. + this._graph[path.join(packageJson._root, main, 'index.js')]; +}; + /** * Intiates the filewatcher and kicks off the search process. */ @@ -339,9 +397,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -358,10 +416,8 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - var packagePath; - for (var i = 0; i < files.length ; i++) { + for (var i = 0; i < files.length; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -406,12 +462,16 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; + if (!this._isInNodeModules(packageJson._root)) { + this._packagesById[packageJson.name] = packageJson; + } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; + if (!this._isInNodeModules(packageJson._root)) { + delete this._packagesById[packageJson.name]; + } }; /** @@ -441,6 +501,14 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (this._isInNodeModules(modulePath)) { + moduleData.id = this._lookupName(modulePath); + moduleData.dependencies = null; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -484,6 +552,10 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; + if (this._isInNodeModules(module.path)) { + return; + } + // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -504,6 +576,10 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; + if (this._isInNodeModules(module.path)) { + return; + } + if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -527,28 +603,14 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = this._packageByRoot; + return lookupPackage(path.dirname(modulePath), this._packageByRoot); +}; - /** - * Auxiliary function to recursively lookup a package. - */ - function lookupPackage(currDir) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir)); - } - } - } - - return lookupPackage(path.dirname(modulePath)); +/** + * Find the nearest node package to a module. + */ +DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { + return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); }; /** @@ -573,12 +635,14 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; + var packageJson; + if (isPackage) { + packageJson = this._packageByRoot[path.dirname(absPath)]; + } + if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } + if (isPackage && packageJson) { + this._removePackageFromIndices(packageJson); } else { var module = this._graph[absPath]; if (module == null) { @@ -591,7 +655,14 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - return self._processPackage(absPath); + self._removePackageFromIndices(packageJson); + return self._processPackage(absPath) + .then(function(p) { + return self._resolvePackageMain(p); + }) + .then(function(mainModule) { + return self._processModule(mainModule.path); + }); } return self._processModule(absPath); }); @@ -675,6 +746,25 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; +DependecyGraph.prototype._isInNodeModules = function(file) { + var inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + var dirs = this._providesModuleNodeModules; + + for (var i = 0; i < dirs.length; i++) { + var index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; +}; + /** * Extract all required modules from a `code` string. */ @@ -784,6 +874,54 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } +function realId(module) { + if (module._realId) { + return module._realId; + } + + var hash = crypto.createHash('md5'); + hash.update(module.id); + hash.update(module.path); + Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); + return module._realId; +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupPackage(currDir, packageByRoot) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir), packageByRoot); + } + } +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupNodePackage(currDir, packageByRoot, packageName) { + if (currDir === '/') { + return null; + } + var packageRoot = path.join(currDir, 'node_modules', packageName); + + var packageJson = packageByRoot[packageRoot]; + if (packageJson) { + return packageJson; + } else { + return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); + } +} + function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9bc8b8b95c3..1f8c95479bb 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index da68785eacd..d7a8c0eb1b3 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -91,9 +91,8 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); + return depGraph.getOrderedDependencies(main) + .then(function(dependencies) { var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 7be471d1fee2646e39046682acda6a542add1154 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 12:53:08 -0700 Subject: [PATCH 07/25] visualize padding and margin in inspector Summary: This shows margin and padding visually when inspecting an element. @public Test Plan: Go to the "UIExplorer", to the page. Open the inspector, and start selecting things. Padding and margin should be indicated. (Padding in dark blue and margin in orange). --- .../ReactIOS/InspectorOverlay/BorderBox.js | 38 ++++++++++ .../ReactIOS/InspectorOverlay/ElementBox.js | 74 +++++++++++++++++++ .../InspectorOverlay.js | 11 ++- Libraries/ReactIOS/resolveBoxStyle.js | 61 +++++++++++++++ 4 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 Libraries/ReactIOS/InspectorOverlay/BorderBox.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/ElementBox.js rename Libraries/ReactIOS/{ => InspectorOverlay}/InspectorOverlay.js (89%) create mode 100644 Libraries/ReactIOS/resolveBoxStyle.js diff --git a/Libraries/ReactIOS/InspectorOverlay/BorderBox.js b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js new file mode 100644 index 00000000000..caee8ccd6b3 --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/BorderBox.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 BorderBox + * @flow + */ +'use strict'; + +var React = require('React'); +var View = require('View'); + +class BorderBox extends React.Component { + render() { + var box = this.props.box; + if (!box) { + return this.props.children; + } + var style = { + borderTopWidth: box.top, + borderBottomWidth: box.bottom, + borderLeftWidth: box.left, + borderRightWidth: box.right, + }; + return ( + + {this.props.children} + + ); + } +} + +module.exports = BorderBox; + diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js new file mode 100644 index 00000000000..a94312b9c0c --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 ElementBox + * @flow + */ +'use strict'; + +var React = require('React'); +var View = require('View'); +var StyleSheet = require('StyleSheet'); +var BorderBox = require('BorderBox'); +var resolveBoxStyle = require('resolveBoxStyle'); + +var flattenStyle = require('flattenStyle'); + +class ElementBox extends React.Component { + render() { + var style = flattenStyle(this.props.style) || {}; + var margin = resolveBoxStyle('margin', style); + var padding = resolveBoxStyle('padding', style); + var frameStyle = this.props.frame; + if (margin) { + frameStyle = { + top: frameStyle.top - margin.top, + left: frameStyle.left - margin.left, + height: frameStyle.height + margin.top + margin.bottom, + width: frameStyle.width + margin.left + margin.right, + }; + } + var contentStyle = { + width: this.props.frame.width, + height: this.props.frame.height, + }; + if (padding) { + contentStyle = { + width: contentStyle.width - padding.left - padding.right, + height: contentStyle.height - padding.top - padding.bottom, + }; + } + return ( + + + + + + + + ); + } +} + +var styles = StyleSheet.create({ + frame: { + position: 'absolute', + }, + content: { + backgroundColor: 'rgba(0, 0, 255, 0.1)', + }, + padding: { + borderColor: 'rgba(182, 217, 167, 0.3)', + }, + margin: { + borderColor: 'rgba(248, 194, 136, 0.3)', + }, +}); + +module.exports = ElementBox; + diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js similarity index 89% rename from Libraries/ReactIOS/InspectorOverlay.js rename to Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index eeb6e796572..97d08b90bc7 100644 --- a/Libraries/ReactIOS/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -17,6 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var UIManager = require('NativeModules').UIManager; var View = require('View'); +var ElementBox = require('ElementBox'); var InspectorOverlay = React.createClass({ getInitialState: function() { @@ -34,9 +35,11 @@ var InspectorOverlay = React.createClass({ (nativeViewTag, left, top, width, height) => { var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag); var hierarchy = Inspector.getOwnerHierarchy(instance); + var publicInstance = instance.getPublicInstance(); this.setState({ hierarchy, - frame: {left, top, width, height} + frame: {left, top, width, height}, + style: publicInstance.props ? publicInstance.props.style : {}, }); } ); @@ -59,7 +62,7 @@ var InspectorOverlay = React.createClass({ ? 'flex-start' : 'flex-end'; - content.push(); + content.push(); content.push(); } return ( @@ -97,10 +100,6 @@ var styles = StyleSheet.create({ right: 0, bottom: 0, }, - frame: { - position: 'absolute', - backgroundColor: 'rgba(155,155,255,0.3)', - }, info: { backgroundColor: 'rgba(0, 0, 0, 0.7)', padding: 10, diff --git a/Libraries/ReactIOS/resolveBoxStyle.js b/Libraries/ReactIOS/resolveBoxStyle.js new file mode 100644 index 00000000000..15342b56a30 --- /dev/null +++ b/Libraries/ReactIOS/resolveBoxStyle.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule resolveBoxStyle + * @flow + */ +'use strict'; + +/** + * Resolve a style property into it's component parts, e.g. + * + * resolveProperties('margin', {margin: 5, marginBottom: 10}) + * -> + * {top: 5, left: 5, right: 5, bottom: 10} + * + * If none are set, returns false. + */ +function resolveBoxStyle(prefix: String, style: Object): ?Object { + var res = {}; + var subs = ['top', 'left', 'bottom', 'right']; + var set = false; + if (style[prefix]) { + subs.forEach(sub => { + res[sub] = style[prefix]; + }); + set = true; + } + if (style[prefix + 'Vertical']) { + res.top = res.bottom = style[prefix + 'Vertical']; + set = true; + } + if (style[prefix + 'Horizontal']) { + res.left = res.right = style[prefix + 'Horizontal']; + set = true; + } + subs.forEach(sub => { + var val = style[prefix + capFirst(sub)]; + if (val) { + res[sub] = val; + } + if (res[sub]) { + set = true; + } + }); + if (!set) { + return; + } + return res; +} + +function capFirst(text) { + return text[0].toUpperCase() + text.slice(1); +} + +module.exports = resolveBoxStyle; + From 074fa759a66f862c62b3fd55ce0ff20b749aa57e Mon Sep 17 00:00:00 2001 From: jmstout Date: Wed, 3 Jun 2015 12:56:32 -0700 Subject: [PATCH 08/25] [Touchable] Add custom delay props to Touchable components Summary: @public This PR adds quite a bit of functionality to the Touchable components, allowing the ms delays of each of the handlers (`onPressIn, onPressOut, onPress, onLongPress`) to be configured. It adds the following props to `TouchableWithoutFeedback, TouchableOpacity, and TouchableHighlight`: ```javascript /** * Delay in ms, from the release of the touch, before onPress is called. */ delayOnPress: React.PropTypes.number, /** * Delay in ms, from the start of the touch, before onPressIn is called. */ delayOnPressIn: React.PropTypes.number, /** * Delay in ms, from the release of the touch, before onPressOut is called. */ delayOnPressOut: React.PropTypes.number, /** * Delay in ms, from onPressIn, before onLongPress is called. */ delayOnLongPress: React.PropTypes.number, ``` `TouchableHighlight` also gets an additional set of props: ```javascript /** * Delay in ms, from the start of the touch, before the highlight is shown. */ delayHighlightShow: React.PropTypes.number, /** * Del ... ``` Closes https://github.com/facebook/react-native/pull/1255 Github Author: jmstout Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/TouchableExample.js | 46 ++++++++++++++++ .../Touchable/TouchableHighlight.js | 18 ++++++- .../Components/Touchable/TouchableOpacity.js | 53 ++++++++++++++++--- .../Touchable/TouchableWithoutFeedback.js | 35 +++++++++++- .../Touchable/ensurePositiveDelayProps.js | 24 +++++++++ .../interactions/Touchable/Touchable.js | 28 +++++++--- 6 files changed, 187 insertions(+), 17 deletions(-) create mode 100644 Libraries/Components/Touchable/ensurePositiveDelayProps.js diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 45a679b315a..acbba362921 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -75,6 +75,14 @@ exports.examples = [ render: function(): ReactElement { return ; }, +}, { + title: 'Touchable delay for events', + description: ' components also accept delayPressIn, ' + + 'delayPressOut, and delayLongPress as props. These props impact the ' + + 'timing of feedback events.', + render: function(): ReactElement { + return ; + }, }]; var TextOnPressBox = React.createClass({ @@ -148,6 +156,44 @@ var TouchableFeedbackEvents = React.createClass({ }, }); +var TouchableDelayEvents = React.createClass({ + getInitialState: function() { + return { + eventLog: [], + }; + }, + render: function() { + return ( + + + this._appendEvent('press')} + delayPressIn={400} + onPressIn={() => this._appendEvent('pressIn - 400ms delay')} + delayPressOut={1000} + onPressOut={() => this._appendEvent('pressOut - 1000ms delay')} + delayLongPress={800} + onLongPress={() => this._appendEvent('longPress - 800ms delay')}> + + Press Me + + + + + {this.state.eventLog.map((e, ii) => {e})} + + + ); + }, + _appendEvent: function(eventName) { + var limit = 6; + var eventLog = this.state.eventLog.slice(0, limit - 1); + eventLog.unshift(eventName); + this.setState({eventLog}); + }, +}); + var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var styles = StyleSheet.create({ diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 533652f6504..dcbfbeee19d 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -23,6 +23,7 @@ var View = require('View'); var cloneWithProps = require('cloneWithProps'); var ensureComponentIsNative = require('ensureComponentIsNative'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var keyOf = require('keyOf'); var merge = require('merge'); var onlyChild = require('onlyChild'); @@ -111,6 +112,7 @@ var TouchableHighlight = React.createClass({ }, componentDidMount: function() { + ensurePositiveDelayProps(this.props); ensureComponentIsNative(this.refs[CHILD_REF]); }, @@ -119,6 +121,7 @@ var TouchableHighlight = React.createClass({ }, componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); if (nextProps.activeOpacity !== this.props.activeOpacity || nextProps.underlayColor !== this.props.underlayColor || nextProps.style !== this.props.style) { @@ -152,7 +155,8 @@ var TouchableHighlight = React.createClass({ touchableHandlePress: function() { this.clearTimeout(this._hideTimeout); this._showUnderlay(); - this._hideTimeout = this.setTimeout(this._hideUnderlay, 100); + this._hideTimeout = this.setTimeout(this._hideUnderlay, + this.props.delayPressOut || 100); this.props.onPress && this.props.onPress(); }, @@ -164,6 +168,18 @@ var TouchableHighlight = React.createClass({ return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant! }, + touchableGetHighlightDelayMS: function() { + return this.props.delayPressIn; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + _showUnderlay: function() { this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); this.refs[CHILD_REF].setNativeProps(this.state.activeProps); diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 8d93cd8416b..1549faea2d9 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -15,11 +15,13 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var POPAnimationMixin = require('POPAnimationMixin'); var React = require('React'); +var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var cloneWithProps = require('cloneWithProps'); var ensureComponentIsNative = require('ensureComponentIsNative'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var flattenStyle = require('flattenStyle'); var keyOf = require('keyOf'); var onlyChild = require('onlyChild'); @@ -47,7 +49,7 @@ var onlyChild = require('onlyChild'); */ var TouchableOpacity = React.createClass({ - mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin], + mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin], propTypes: { ...TouchableWithoutFeedback.propTypes, @@ -69,6 +71,7 @@ var TouchableOpacity = React.createClass({ }, componentDidMount: function() { + ensurePositiveDelayProps(this.props); ensureComponentIsNative(this.refs[CHILD_REF]); }, @@ -76,6 +79,10 @@ var TouchableOpacity = React.createClass({ ensureComponentIsNative(this.refs[CHILD_REF]); }, + componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); + }, + setOpacityTo: function(value) { if (POPAnimationMixin) { // Reset with animation if POP is available @@ -83,6 +90,7 @@ var TouchableOpacity = React.createClass({ var anim = { type: this.AnimationTypes.linear, property: this.AnimationProperties.opacity, + duration: 0.15, toValue: value, }; this.startAnimation(CHILD_REF, anim); @@ -99,20 +107,26 @@ var TouchableOpacity = React.createClass({ * defined on your component. */ touchableHandleActivePressIn: function() { - this.refs[CHILD_REF].setNativeProps({ - opacity: this.props.activeOpacity - }); + this.clearTimeout(this._hideTimeout); + this._hideTimeout = null; + this._opacityActive(); this.props.onPressIn && this.props.onPressIn(); }, touchableHandleActivePressOut: function() { - var child = onlyChild(this.props.children); - var childStyle = flattenStyle(child.props.style) || {}; - this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity); + if (!this._hideTimeout) { + this._opacityInactive(); + } this.props.onPressOut && this.props.onPressOut(); }, touchableHandlePress: function() { + this.clearTimeout(this._hideTimeout); + this._opacityActive(); + this._hideTimeout = this.setTimeout( + this._opacityInactive, + this.props.delayPressOut || 100 + ); this.props.onPress && this.props.onPress(); }, @@ -125,7 +139,30 @@ var TouchableOpacity = React.createClass({ }, touchableGetHighlightDelayMS: function() { - return 0; + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress === 0 ? 0 : + this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + + _opacityActive: function() { + this.setOpacityTo(this.props.activeOpacity); + }, + + _opacityInactive: function() { + this.clearTimeout(this._hideTimeout); + this._hideTimeout = null; + var child = onlyChild(this.props.children); + var childStyle = flattenStyle(child.props.style) || {}; + this.setOpacityTo( + childStyle.opacity === undefined ? 1 : childStyle.opacity + ); }, render: function() { diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index cd9ea02fdf1..9449f79bb7e 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -12,7 +12,9 @@ 'use strict'; var React = require('React'); +var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var onlyChild = require('onlyChild'); /** @@ -31,7 +33,7 @@ type Event = Object; * one of the primary reason a "web" app doesn't feel "native". */ var TouchableWithoutFeedback = React.createClass({ - mixins: [Touchable.Mixin], + mixins: [TimerMixin, Touchable.Mixin], propTypes: { /** @@ -42,12 +44,32 @@ var TouchableWithoutFeedback = React.createClass({ onPressIn: React.PropTypes.func, onPressOut: React.PropTypes.func, onLongPress: React.PropTypes.func, + /** + * Delay in ms, from the start of the touch, before onPressIn is called. + */ + delayPressIn: React.PropTypes.number, + /** + * Delay in ms, from the release of the touch, before onPressOut is called. + */ + delayPressOut: React.PropTypes.number, + /** + * Delay in ms, from onPressIn, before onLongPress is called. + */ + delayLongPress: React.PropTypes.number, }, getInitialState: function() { return this.touchableGetInitialState(); }, + componentDidMount: function() { + ensurePositiveDelayProps(this.props); + }, + + componentWillReceiveProps: function(nextProps: Object) { + ensurePositiveDelayProps(nextProps); + }, + /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. @@ -73,7 +95,16 @@ var TouchableWithoutFeedback = React.createClass({ }, touchableGetHighlightDelayMS: function(): number { - return 0; + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function(): number { + return this.props.delayLongPress === 0 ? 0 : + this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function(): number { + return this.props.delayPressOut || 0; }, render: function(): ReactElement { diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js new file mode 100644 index 00000000000..4c6525a5413 --- /dev/null +++ b/Libraries/Components/Touchable/ensurePositiveDelayProps.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ensurePositiveDelayProps + * @flow + */ +'use strict'; + +var invariant = require('invariant'); + +var ensurePositiveDelayProps = function(props: any) { + invariant( + !(props.delayPressIn < 0 || props.delayPressOut < 0 || + props.delayLongPress < 0), + 'Touchable components cannot have negative delay properties' + ); +}; + +module.exports = ensurePositiveDelayProps; diff --git a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js index 37c42382712..2a0dd1813ae 100644 --- a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js +++ b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js @@ -232,6 +232,8 @@ var PRESS_EXPAND_PX = 20; var LONG_PRESS_THRESHOLD = 500; +var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS; + var LONG_PRESS_ALLOWED_MOVEMENT = 10; // Default amount "active" region protrudes beyond box @@ -276,7 +278,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * + * | RESPONDER_GRANT (HitRect) * v - * +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+ + * +---------------------------+ DELAY +-------------------------+ T + DELAY +------------------------------+ * |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN| * +---------------------------+ +-------------------------+ +------------------------------+ * + ^ + ^ + ^ @@ -288,7 +290,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT| * +----------------------------+ +--------------------------+ +-------------------------------+ * - * T - DELAY => LONG_PRESS_THRESHOLD - DELAY + * T + DELAY => LONG_PRESS_DELAY_MS + DELAY * * Not drawn are the side effects of each transition. The most important side * effect is the `touchableHandlePress` abstract method invocation that occurs @@ -348,12 +350,16 @@ var TouchableMixin = { // event to make sure it doesn't get reused in the event object pool. e.persist(); + this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout); + this.pressOutDelayTimeout = null; + this.state.touchable.touchState = States.NOT_RESPONDER; this.state.touchable.responderID = dispatchID; this._receiveSignal(Signals.RESPONDER_GRANT, e); var delayMS = this.touchableGetHighlightDelayMS !== undefined ? - this.touchableGetHighlightDelayMS() : HIGHLIGHT_DELAY_MS; + Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS; + delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS; if (delayMS !== 0) { this.touchableDelayTimeout = setTimeout( this._handleDelay.bind(this, e), @@ -363,9 +369,13 @@ var TouchableMixin = { this._handleDelay(e); } + var longDelayMS = + this.touchableGetLongPressDelayMS !== undefined ? + Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS; + longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS; this.longPressDelayTimeout = setTimeout( this._handleLongDelay.bind(this, e), - LONG_PRESS_THRESHOLD - delayMS + longDelayMS + delayMS ); }, @@ -632,8 +642,14 @@ var TouchableMixin = { if (newIsHighlight && !curIsHighlight) { this._savePressInLocation(e); this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(); - } else if (!newIsHighlight && curIsHighlight) { - this.touchableHandleActivePressOut && this.touchableHandleActivePressOut(); + } else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) { + if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) { + this.pressOutDelayTimeout = this.setTimeout(function() { + this.touchableHandleActivePressOut(); + }, this.touchableGetPressOutDelayMS()); + } else { + this.touchableHandleActivePressOut(); + } } if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) { From b2b89c0b68ce5cdf06c5bd3b30014e3c1e50e3b9 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 13:55:52 -0700 Subject: [PATCH 09/25] add style inspector + more margin + styles Summary: - make overlay transparent to avoid obscuring the app - show style in the inspector pane - show margin+padding values, also width/height and abs position @public Test Plan: Open the inspector somewhere, start selecting things. You should see correct padding, margin, and dimentions values; in addition to all style properties enumerated. --- Libraries/Inspector.js | 3 + .../ReactIOS/InspectorOverlay/BoxInspector.js | 113 ++++++++++++++++++ .../ReactIOS/InspectorOverlay/ElementBox.js | 6 +- .../InspectorOverlay/ElementProperties.js | 64 ++++++++++ .../InspectorOverlay/InspectorOverlay.js | 54 +++++---- .../InspectorOverlay/StyleInspector.js | 63 ++++++++++ .../{ => InspectorOverlay}/resolveBoxStyle.js | 8 +- 7 files changed, 277 insertions(+), 34 deletions(-) create mode 100644 Libraries/ReactIOS/InspectorOverlay/BoxInspector.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/ElementProperties.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/StyleInspector.js rename Libraries/ReactIOS/{ => InspectorOverlay}/resolveBoxStyle.js (93%) diff --git a/Libraries/Inspector.js b/Libraries/Inspector.js index 5c70be2e868..e0017c3cf32 100644 --- a/Libraries/Inspector.js +++ b/Libraries/Inspector.js @@ -50,6 +50,9 @@ function findInstanceByNativeTag(rootTag, nativeTag) { var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag]; var rootInstance = ReactNativeMount._instancesByContainerID[containerID]; var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag]; + if (!targetID) { + return undefined; + } return findInstance(rootInstance, targetID); } diff --git a/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js new file mode 100644 index 00000000000..e50d9869aac --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule BoxInspector + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); +var resolveBoxStyle = require('resolveBoxStyle'); + +var blank = { + top: 0, + left: 0, + right: 0, + bottom: 0, +}; + +class BoxInspector extends React.Component { + render() { + var frame = this.props.frame; + var style = this.props.style; + var margin = style && resolveBoxStyle('margin', style) || blank; + var padding = style && resolveBoxStyle('padding', style) || blank; + return ( + + + + + ({frame.left}, {frame.top}) + + + {frame.width} × {frame.height} + + + + + ); + } +} + +class BoxContainer extends React.Component { + render() { + var box = this.props.box; + return ( + + + {this.props.title} + {box.top} + + + {box.left} + {this.props.children} + {box.right} + + {box.bottom} + + ); + } +} + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + marginLabel: { + width: 60, + }, + label: { + fontSize: 10, + color: 'rgb(255,100,0)', + marginLeft: 5, + flex: 1, + textAlign: 'left', + top: -3, + }, + buffer: { + fontSize: 10, + color: 'yellow', + flex: 1, + textAlign: 'center', + }, + innerText: { + color: 'yellow', + fontSize: 12, + textAlign: 'center', + width: 70, + }, + box: { + borderWidth: 1, + borderColor: 'grey', + }, + boxText: { + color: 'white', + fontSize: 12, + marginHorizontal: 3, + marginVertical: 2, + textAlign: 'center', + }, +}); + +module.exports = BoxInspector; + diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js index a94312b9c0c..a3851001c2e 100644 --- a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js +++ b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js @@ -60,13 +60,13 @@ var styles = StyleSheet.create({ position: 'absolute', }, content: { - backgroundColor: 'rgba(0, 0, 255, 0.1)', + backgroundColor: 'rgba(200, 230, 255, 0.8)', }, padding: { - borderColor: 'rgba(182, 217, 167, 0.3)', + borderColor: 'rgba(77, 255, 0, 0.3)', }, margin: { - borderColor: 'rgba(248, 194, 136, 0.3)', + borderColor: 'rgba(255, 132, 0, 0.3)', }, }); diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js new file mode 100644 index 00000000000..9e9ae1d6a00 --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ElementProperties + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); +var PropTypes = require('ReactPropTypes'); +var BoxInspector = require('BoxInspector'); +var StyleInspector = require('StyleInspector'); + +var flattenStyle = require('flattenStyle'); + +var ElementProperties = React.createClass({ + propTypes: { + hierarchy: PropTypes.array.isRequired, + style: PropTypes.array.isRequired, + }, + render: function() { + var style = flattenStyle(this.props.style); + var path = this.props.hierarchy.map((instance) => { + return instance.getName ? instance.getName() : 'Unknown'; + }).join(' > '); + return ( + + + {path} + + + + + + + ); + } +}); + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + info: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 10, + }, + path: { + color: 'white', + fontSize: 9, + }, +}); + +module.exports = ElementProperties; diff --git a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index 97d08b90bc7..1b5009143e4 100644 --- a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -18,6 +18,7 @@ var Text = require('Text'); var UIManager = require('NativeModules').UIManager; var View = require('View'); var ElementBox = require('ElementBox'); +var ElementProperties = require('ElementProperties'); var InspectorOverlay = React.createClass({ getInitialState: function() { @@ -34,6 +35,9 @@ var InspectorOverlay = React.createClass({ [locationX, locationY], (nativeViewTag, left, top, width, height) => { var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag); + if (!instance) { + return; + } var hierarchy = Inspector.getOwnerHierarchy(instance); var publicInstance = instance.getPublicInstance(); this.setState({ @@ -52,18 +56,31 @@ var InspectorOverlay = React.createClass({ render: function() { var content = []; + var justifyContent = 'flex-end'; if (this.state.frame) { var distanceToTop = this.state.frame.top; var distanceToBottom = Dimensions.get('window').height - (this.state.frame.top + this.state.frame.height); - var justifyContent = distanceToTop > distanceToBottom + justifyContent = distanceToTop > distanceToBottom ? 'flex-start' : 'flex-end'; content.push(); - content.push(); + content.push( + + ); + } else { + content.push( + + Welcome to the inspector! Tap something to inspect it. + + ); } return ( { - return instance.getName ? instance.getName() : 'Unknown'; - }).join(' > '); - return ( - - - {path} - - - ); - } -}); - var styles = StyleSheet.create({ + welcomeMessage: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 10, + paddingVertical: 50, + }, + welcomeText: { + color: 'white', + }, inspector: { - backgroundColor: 'rgba(255,255,255,0.8)', + backgroundColor: 'rgba(255,255,255,0.0)', position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, }, - info: { - backgroundColor: 'rgba(0, 0, 0, 0.7)', - padding: 10, - }, - path: { - color: 'white', - fontSize: 9, - } }); module.exports = InspectorOverlay; diff --git a/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js new file mode 100644 index 00000000000..702d01e1da3 --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 StyleInspector + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +class StyleInspector extends React.Component { + render() { + if (!this.props.style) { + return No style; + } + var names = Object.keys(this.props.style); + return ( + + + {names.map(name => {name}:)} + + + {names.map(name => {this.props.style[name]})} + + + ); + } +} + +var styles = StyleSheet.create({ + container: { + flexDirection: 'row', + }, + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + attr: { + fontSize: 10, + color: '#ccc', + }, + value: { + fontSize: 10, + color: 'white', + marginLeft: 10, + }, + noStyle: { + color: 'white', + fontSize: 10, + }, +}); + +module.exports = StyleInspector; + diff --git a/Libraries/ReactIOS/resolveBoxStyle.js b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js similarity index 93% rename from Libraries/ReactIOS/resolveBoxStyle.js rename to Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js index 15342b56a30..e0bfb601c12 100644 --- a/Libraries/ReactIOS/resolveBoxStyle.js +++ b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js @@ -24,10 +24,10 @@ function resolveBoxStyle(prefix: String, style: Object): ?Object { var res = {}; var subs = ['top', 'left', 'bottom', 'right']; var set = false; + subs.forEach(sub => { + res[sub] = style[prefix] || 0; + }); if (style[prefix]) { - subs.forEach(sub => { - res[sub] = style[prefix]; - }); set = true; } if (style[prefix + 'Vertical']) { @@ -42,8 +42,6 @@ function resolveBoxStyle(prefix: String, style: Object): ?Object { var val = style[prefix + capFirst(sub)]; if (val) { res[sub] = val; - } - if (res[sub]) { set = true; } }); From 89a1e94a155ddf6290646e9ecb038db01941a10f Mon Sep 17 00:00:00 2001 From: Andrew Imm Date: Wed, 3 Jun 2015 14:11:20 -0700 Subject: [PATCH 10/25] Add an event for remote notification registration, and improve permissions request Summary: In order to add Push support to the Parse JS SDK in React Native, we need a way to receive the APNS device token from the JS context. This adds another event to PushNotificationIOS, so that code can respond to a successful registration. Additionally, I've updated the `requestPermissions` call to accept an optional map of parameters. This way, developers can request a subset of user notification types. Closes https://github.com/facebook/react-native/pull/1304 Github Author: Andrew Imm Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../PushNotificationIOS.js | 77 +++++++++++++++---- .../RCTPushNotificationManager.h | 1 + .../RCTPushNotificationManager.m | 77 ++++++++++++++----- 3 files changed, 117 insertions(+), 38 deletions(-) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 4d03c664153..732d4c0294c 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -20,6 +20,7 @@ var _initialNotification = RCTPushNotificationManager && RCTPushNotificationManager.initialNotification; var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; +var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; /** * Handle push notifications for your app, including permission handling and @@ -49,30 +50,72 @@ class PushNotificationIOS { } /** - * Attaches a listener to remote notifications while the app is running in the - * foreground or the background. + * Attaches a listener to remote notification events while the app is running + * in the foreground or the background. * - * The handler will get be invoked with an instance of `PushNotificationIOS` + * Valid events are: + * + * - `notification` : Fired when a remote notification is received. The + * handler will be invoked with an instance of `PushNotificationIOS`. + * - `register`: Fired when the user registers for remote notifications. The + * handler will be invoked with a hex string representing the deviceToken. */ static addEventListener(type: string, handler: Function) { invariant( - type === 'notification', - 'PushNotificationIOS only supports `notification` events' - ); - _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( - DEVICE_NOTIF_EVENT, - (notifData) => { - handler(new PushNotificationIOS(notifData)); - } + type === 'notification' || type === 'register', + 'PushNotificationIOS only supports `notification` and `register` events' ); + if (type === 'notification') { + _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( + DEVICE_NOTIF_EVENT, + (notifData) => { + handler(new PushNotificationIOS(notifData)); + } + ); + } else if (type === 'register') { + _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( + NOTIF_REGISTER_EVENT, + (registrationInfo) => { + handler(registrationInfo.deviceToken); + } + ); + } } /** - * Requests all notification permissions from iOS, prompting the user's - * dialog box. + * Requests notification permissions from iOS, prompting the user's + * dialog box. By default, it will request all notification permissions, but + * a subset of these can be requested by passing a map of requested + * permissions. + * The following permissions are supported: + * + * - `alert` + * - `badge` + * - `sound` + * + * If a map is provided to the method, only the permissions with truthy values + * will be requested. */ - static requestPermissions() { - RCTPushNotificationManager.requestPermissions(); + static requestPermissions(permissions?: { + alert?: boolean, + badge?: boolean, + sound?: boolean + }) { + var requestedPermissions = {}; + if (permissions) { + requestedPermissions = { + alert: !!permissions.alert, + badge: !!permissions.badge, + sound: !!permissions.sound + }; + } else { + requestedPermissions = { + alert: true, + badge: true, + sound: true + }; + } + RCTPushNotificationManager.requestPermissions(requestedPermissions); } /** @@ -97,8 +140,8 @@ class PushNotificationIOS { */ static removeEventListener(type: string, handler: Function) { invariant( - type === 'notification', - 'PushNotificationIOS only supports `notification` events' + type === 'notification' || type === 'register', + 'PushNotificationIOS only supports `notification` and `register` events' ); if (!_notifHandlers[handler]) { return; diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index ef1ba1496e8..194bbc5ddee 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -14,6 +14,7 @@ @interface RCTPushNotificationManager : NSObject + (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; ++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification; @end diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 4846c885e30..1966c604528 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -12,7 +12,18 @@ #import "RCTBridge.h" #import "RCTEventDispatcher.h" +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + +#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert +#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge +#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound +#define UIUserNotificationTypeNone UIRemoteNotificationTypeNone +#define UIUserNotificationType UIRemoteNotificationType + +#endif + NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; +NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered"; @implementation RCTPushNotificationManager { @@ -30,6 +41,10 @@ RCT_EXPORT_MODULE() selector:@selector(handleRemoteNotificationReceived:) name:RCTRemoteNotificationReceived object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationsRegistered:) + name:RCTRemoteNotificationsRegistered + object:nil]; } return self; } @@ -52,6 +67,21 @@ RCT_EXPORT_MODULE() } } ++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + NSMutableString *hexString = [NSMutableString string]; + const unsigned char *bytes = [deviceToken bytes]; + for (int i = 0; i < [deviceToken length]; i++) { + [hexString appendFormat:@"%02x", bytes[i]]; + } + NSDictionary *userInfo = @{ + @"deviceToken" : [hexString copy] + }; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered + object:self + userInfo:userInfo]; +} + + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification { [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived @@ -65,6 +95,12 @@ RCT_EXPORT_MODULE() body:[notification userInfo]]; } +- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationsRegistered" + body:[notification userInfo]]; +} + /** * Update the application icon badge number on the home screen */ @@ -83,36 +119,35 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback ]); } -RCT_EXPORT_METHOD(requestPermissions) +RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) { - Class _UIUserNotificationSettings; - if ((_UIUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings"))) { - UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert; - UIUserNotificationSettings *notificationSettings = [_UIUserNotificationSettings settingsForTypes:types categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; + UIUserNotificationType types = UIRemoteNotificationTypeNone; + if (permissions) { + if ([permissions[@"alert"] boolValue]) { + types |= UIUserNotificationTypeAlert; + } + if ([permissions[@"badge"] boolValue]) { + types |= UIUserNotificationTypeBadge; + } + if ([permissions[@"sound"] boolValue]) { + types |= UIUserNotificationTypeSound; + } } else { + types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; + } -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - - [[UIApplication sharedApplication] registerForRemoteNotificationTypes: - UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert]; - +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 + id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; +#else + [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types]; #endif - } } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { - -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - -#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert -#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge -#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound - -#endif - NSUInteger types = 0; if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) { types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; From 856469a24b6f0b0998061c1244533b510aee3ddd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 14:12:03 -0700 Subject: [PATCH 11/25] [react-packager] Support packages with '.' in the name Summary: @public Fixes issue #1055 For some historical reason we used to strip the extension of the module name before passing it to `resolveDependency` which is completly capable of handling all kinds of names. The fix is one line, but added a few tests for this. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PacakgerIntegration * Open app and click around --- .../__tests__/DependencyGraph-test.js | 131 ++++++++++++++++++ .../haste/DependencyGraph/index.js | 3 +- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 6c0fd05f6f5..4dce3b8a4e8 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -577,6 +577,80 @@ describe('DependencyGraph', function() { }); }); + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'x.y.z/main', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should default main package to index.js', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -2116,6 +2190,63 @@ describe('DependencyGraph', function() { ]); }); }); + + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index fc899cb2fe0..74c13127623 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -144,8 +144,7 @@ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { function iter(mod) { var p = Promise.resolve(); mod.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(mod, id); + var dep = self.resolveDependency(mod, name); if (dep == null) { debug( From 6d25ff83ec2b55869342d5b23d559e453348a34f Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Wed, 3 Jun 2015 16:07:39 -0700 Subject: [PATCH 12/25] [react_native] Fix sync --- .../Components/ProgressViewIOS/ProgressViewIOS.android.js | 6 +++--- .../SegmentedControlIOS/SegmentedControlIOS.android.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js index b9103c6672b..abda1c368ca 100644 --- a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js @@ -7,7 +7,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. * - * @providesModule SegmentedControlIOS + * @providesModule ProgressViewIOS */ 'use strict'; @@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); -var Dummy = React.createClass({ +var DummyProgressViewIOS = React.createClass({ render: function() { return ( @@ -46,4 +46,4 @@ var styles = StyleSheet.create({ } }); -module.exports = Dummy; +module.exports = DummyProgressViewIOS; diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js index 28fbea0271d..848144bff7f 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js @@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); -var Dummy = React.createClass({ +var DummySegmentedControlIOS = React.createClass({ render: function() { return ( @@ -46,4 +46,4 @@ var styles = StyleSheet.create({ } }); -module.exports = Dummy; +module.exports = DummySegmentedControlIOS; From fa3491e9f52c4ad7374d71f9a061de12be23f10e Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 3 Jun 2015 16:15:29 -0700 Subject: [PATCH 13/25] [ReactNative] Remove unused touch arrays Summary: These two arrays aren't used. The code is easier to read without them @public Test Plan: Tap around UIExplorer and verify multi-touch and responder system behavior works the same --- React/Base/RCTTouchHandler.m | 5 ----- 1 file changed, 5 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index f95f134c34f..4ffef7ee752 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -36,8 +36,6 @@ BOOL _recordingInteractionTiming; CFTimeInterval _mostRecentEnqueueJS; - NSMutableArray *_pendingTouches; - NSMutableArray *_bridgeInteractionTiming; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -52,9 +50,6 @@ _reactTouches = [[NSMutableArray alloc] init]; _touchViews = [[NSMutableArray alloc] init]; - _pendingTouches = [[NSMutableArray alloc] init]; - _bridgeInteractionTiming = [[NSMutableArray alloc] init]; - // `cancelsTouchesInView` is needed in order to be used as a top level // event delegated recognizer. Otherwise, lower-level components not built // using RCT, will fail to recognize gestures. From 63b826284331fb16d6ebcfd4e7ed4340f85880f7 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 3 Jun 2015 16:39:20 -0700 Subject: [PATCH 14/25] [ReactNative] Allow TouchableWithoutFeedback override accessible attribute --- Libraries/Components/Touchable/TouchableWithoutFeedback.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 9449f79bb7e..227cbeae282 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -40,6 +40,7 @@ var TouchableWithoutFeedback = React.createClass({ * Called when the touch is released, but not if cancelled (e.g. by a scroll * that steals the responder lock). */ + accessible: React.PropTypes.bool, onPress: React.PropTypes.func, onPressIn: React.PropTypes.func, onPressOut: React.PropTypes.func, @@ -110,7 +111,7 @@ var TouchableWithoutFeedback = React.createClass({ render: function(): ReactElement { // Note(avik): remove dynamic typecast once Flow has been upgraded return (React: any).cloneElement(onlyChild(this.props.children), { - accessible: true, + accessible: this.props.accessible !== false, testID: this.props.testID, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, From 9fe71284935776e76a9d50090440f8b1adddc0bc Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 3 Jun 2015 16:40:14 -0700 Subject: [PATCH 15/25] [ReactNative] Refactor DevMenu items construction Summary: The idea behind this change it to couple together menu item title and handler. The code becomes simpler and easier to maintain, but also makes it possible to extend dev menu in the future. @public Test Plan: All menu items works as before. Changed websocket executor class name and made sure that when the class is missing we get nice error message. --- React/Base/RCTDevMenu.h | 6 ++ React/Base/RCTDevMenu.m | 174 ++++++++++++++++++++++++---------------- 2 files changed, 112 insertions(+), 68 deletions(-) diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h index b260fca4a4c..ed1ff90b8cb 100644 --- a/React/Base/RCTDevMenu.h +++ b/React/Base/RCTDevMenu.h @@ -51,6 +51,12 @@ */ - (void)reload; +/** + * Add custom item to the development menu. The handler will be called + * when user selects the item. + */ +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler; + @end /** diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 18de3a457e9..8a5f23f9d2f 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -43,6 +43,28 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu"; @end +@interface RCTDevMenuItem : NSObject + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) dispatch_block_t handler; + +- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler; + +@end + +@implementation RCTDevMenuItem + +- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler +{ + if (self = [super init]) { + self.title = title; + self.handler = handler; + } + return self; +} + +@end + @interface RCTDevMenu () @property (nonatomic, strong) Class executorClass; @@ -57,6 +79,8 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu"; NSURLSessionDataTask *_updateTask; NSURL *_liveReloadURL; BOOL _jsLoaded; + NSArray *_presentedItems; + NSMutableArray *_extraMenuItems; } @synthesize bridge = _bridge; @@ -94,6 +118,7 @@ RCT_EXPORT_MODULE() _defaults = [NSUserDefaults standardUserDefaults]; _settings = [[NSMutableDictionary alloc] init]; + _extraMenuItems = [NSMutableArray array]; // Delay setup until after Bridge init [self settingsDidChange]; @@ -232,32 +257,82 @@ RCT_EXPORT_MODULE() } } +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler +{ + [_extraMenuItems addObject:[[RCTDevMenuItem alloc] initWithTitle:title handler:handler]]; +} + +- (NSArray *)menuItems +{ + NSMutableArray *items = [NSMutableArray array]; + + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Reload" handler:^{ + [self reload]; + }]]; + + Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor"); + if (!chromeExecutorClass) { + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Chrome Debugger Unavailable" handler:^{ + [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" + message:@"You need to include the RCTWebSocket library to enable Chrome debugging" + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] show]; + }]]; + } else { + BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass; + NSString *debugTitleChrome = isDebuggingInChrome ? @"Disable Chrome Debugging" : @"Debug in Chrome"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleChrome handler:^{ + self.executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass; + }]]; + } + + Class safariExecutorClass = NSClassFromString(@"RCTWebViewExecutor"); + BOOL isDebuggingInSafari = _executorClass && _executorClass == safariExecutorClass; + NSString *debugTitleSafari = isDebuggingInSafari ? @"Disable Safari Debugging" : @"Debug in Safari"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleSafari handler:^{ + self.executorClass = isDebuggingInSafari ? Nil : safariExecutorClass; + }]]; + + NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:fpsMonitor handler:^{ + self.showFPS = !_showFPS; + }]]; + + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Inspect Element" handler:^{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; + }]]; + + if (_liveReloadURL) { + NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:liveReloadTitle handler:^{ + self.liveReloadEnabled = !_liveReloadEnabled; + }]]; + + NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:profilingTitle handler:^{ + self.profilingEnabled = !_profilingEnabled; + }]]; + } + + [items addObjectsFromArray:_extraMenuItems]; + + return items; +} + RCT_EXPORT_METHOD(show) { if (_actionSheet || !_bridge) { return; } - NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome"; - NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari"; - NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor"; + UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + actionSheet.title = @"React Native: Development"; + actionSheet.delegate = self; - UIActionSheet *actionSheet = - [[UIActionSheet alloc] initWithTitle:@"React Native: Development" - delegate:self - cancelButtonTitle:nil - destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil]; - - [actionSheet addButtonWithTitle:@"Inspect Element"]; - - if (_liveReloadURL) { - - NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; - NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling"; - - [actionSheet addButtonWithTitle:liveReloadTitle]; - [actionSheet addButtonWithTitle:profilingTitle]; + NSArray *items = [self menuItems]; + for (RCTDevMenuItem *item in items) { + [actionSheet addButtonWithTitle:item.title]; } [actionSheet addButtonWithTitle:@"Cancel"]; @@ -266,13 +341,7 @@ RCT_EXPORT_METHOD(show) actionSheet.actionSheetStyle = UIBarStyleBlack; [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; _actionSheet = actionSheet; -} - -RCT_EXPORT_METHOD(reload) -{ - _jsLoaded = NO; - _liveReloadURL = nil; - [_bridge reload]; + _presentedItems = items; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex @@ -282,48 +351,16 @@ RCT_EXPORT_METHOD(reload) return; } - switch (buttonIndex) { - case 0: { - [self reload]; - break; - } - case 1: { - Class cls = NSClassFromString(@"RCTWebSocketExecutor"); - if (!cls) { - [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" - message:@"You need to include the RCTWebSocket library to enable Chrome debugging" - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil] show]; - return; - } - self.executorClass = (_executorClass == cls) ? Nil : cls; - break; - } - case 2: { - Class cls = NSClassFromString(@"RCTWebViewExecutor"); - self.executorClass = (_executorClass == cls) ? Nil : cls; - break; - } - case 3: { - self.showFPS = !_showFPS; - break; - } - case 4: { - [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; - break; - } - case 5: { - self.liveReloadEnabled = !_liveReloadEnabled; - break; - } - case 6: { - self.profilingEnabled = !_profilingEnabled; - break; - } - default: - break; - } + RCTDevMenuItem *item = _presentedItems[buttonIndex]; + item.handler(); + return; +} + +RCT_EXPORT_METHOD(reload) +{ + _jsLoaded = NO; + _liveReloadURL = nil; + [_bridge reload]; } - (void)setShakeToShow:(BOOL)shakeToShow @@ -445,6 +482,7 @@ RCT_EXPORT_METHOD(reload) - (void)show {} - (void)reload {} +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {} @end From 7ffa7bd1f101166fce2cc8016df405b2fb67d4aa Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 3 Jun 2015 16:57:08 -0700 Subject: [PATCH 16/25] [ReactNative] Implement merge functionality for AsyncStorage --- IntegrationTests/AsyncStorageTest.js | 49 ++++++++++++++----- React/Base/RCTUtils.h | 2 + React/Base/RCTUtils.m | 12 ++++- React/Modules/RCTAsyncLocalStorage.m | 73 ++++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js index 6d13bb6e9fe..911887d3e2d 100644 --- a/IntegrationTests/AsyncStorageTest.js +++ b/IntegrationTests/AsyncStorageTest.js @@ -16,12 +16,19 @@ var { View, } = React; +var deepDiffer = require('deepDiffer'); + var DEBUG = false; var KEY_1 = 'key_1'; var VAL_1 = 'val_1'; var KEY_2 = 'key_2'; var VAL_2 = 'val_2'; +var KEY_MERGE = 'key_merge'; +var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}}; +var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}}; +var VAL_MERGE_EXPECT = + {'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}}; // setup in componentDidMount var done; @@ -40,8 +47,9 @@ function expectTrue(condition, message) { function expectEqual(lhs, rhs, testname) { expectTrue( - lhs === rhs, - 'Error in test ' + testname + ': expected ' + rhs + ', got ' + lhs + !deepDiffer(lhs, rhs), + 'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) + + '\ngot\n' + JSON.stringify(lhs) ); } @@ -93,25 +101,25 @@ function testRemoveItem() { 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')' ); updateMessage('testRemoveItem - add two items'); - AsyncStorage.removeItem(KEY_1, (err) => { - expectAsyncNoError(err); + AsyncStorage.removeItem(KEY_1, (err2) => { + expectAsyncNoError(err2); updateMessage('delete successful '); - AsyncStorage.getItem(KEY_1, (err, result) => { - expectAsyncNoError(err); + AsyncStorage.getItem(KEY_1, (err3, result2) => { + expectAsyncNoError(err3); expectEqual( - result, + result2, null, 'testRemoveItem: key_1 present after delete' ); updateMessage('key properly removed '); - AsyncStorage.getAllKeys((err, result2) => { - expectAsyncNoError(err); + AsyncStorage.getAllKeys((err4, result3) => { + expectAsyncNoError(err4); expectTrue( - result2.indexOf(KEY_1) === -1, - 'Unexpected: KEY_1 present in ' + result2 + result3.indexOf(KEY_1) === -1, + 'Unexpected: KEY_1 present in ' + result3 ); - updateMessage('proper length returned.\nDone!'); - done(); + updateMessage('proper length returned.'); + runTestCase('should merge values', testMerge); }); }); }); @@ -120,6 +128,21 @@ function testRemoveItem() { }); } +function testMerge() { + AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => { + expectAsyncNoError(err1); + AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => { + expectAsyncNoError(err2); + AsyncStorage.getItem(KEY_MERGE, (err3, result) => { + expectAsyncNoError(err3); + expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge'); + updateMessage('objects deeply merged\nDone!'); + done(); + }); + }); + }); +} + var AsyncStorageTest = React.createClass({ getInitialState() { return { diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 5c34d0e0a58..641500b3884 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -18,6 +18,8 @@ // Utility functions for JSON object <-> string serialization/deserialization RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); +RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error); +RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options); // Strip non JSON-safe values from an object graph RCT_EXTERN id RCTJSONClean(id object); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 712e9724e3c..613b1316344 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -24,7 +24,7 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error) return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil; } -id RCTJSONParse(NSString *jsonString, NSError **error) +id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options) { if (!jsonString) { return nil; @@ -39,7 +39,15 @@ id RCTJSONParse(NSString *jsonString, NSError **error) return nil; } } - return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error]; + return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error]; +} + +id RCTJSONParse(NSString *jsonString, NSError **error) { + return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments); +} + +id RCTJSONParseMutable(NSString *jsonString, NSError **error) { + return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves); } id RCTJSONClean(id object) diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 2c01161d48f..76f7fa88555 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -61,6 +61,34 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } +// Only merges objects - all other types are just clobbered (including arrays) +static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source) +{ + for (NSString *key in source) { + id sourceValue = source[key]; + if ([sourceValue isKindOfClass:[NSDictionary class]]) { + id destinationValue = destination[key]; + NSMutableDictionary *nestedDestination; + if ([destinationValue classForCoder] == [NSMutableDictionary class]) { + nestedDestination = destinationValue; + } else { + if ([destinationValue isKindOfClass:[NSDictionary class]]) { + // Ideally we wouldn't eagerly copy here... + nestedDestination = [destinationValue mutableCopy]; + } else { + destination[key] = [sourceValue copy]; + } + } + if (nestedDestination) { + RCTMergeRecursive(nestedDestination, sourceValue); + destination[key] = nestedDestination; + } + } else { + destination[key] = sourceValue; + } + } +} + #pragma mark - RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage @@ -135,13 +163,19 @@ RCT_EXPORT_MODULE() if (errorOut) { return errorOut; } + id value = [self _getValueForKey:key errorOut:&errorOut]; + [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure. + return errorOut; +} + +- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut +{ id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value. if (value == [NSNull null]) { NSString *filePath = [self _filePathForKey:key]; - value = RCTReadFile(filePath, key, &errorOut); + value = RCTReadFile(filePath, key, errorOut); } - [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure. - return errorOut; + return value; } - (id)_writeEntry:(NSArray *)entry @@ -198,7 +232,6 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys id keyError = [self _appendItemForKey:key toArray:result]; RCTAppendError(keyError, &errors); } - [self _writeManifest:&errors]; callback(@[errors ?: [NSNull null], result]); } @@ -221,6 +254,38 @@ RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs } } +RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs + callback:(RCTResponseSenderBlock)callback) +{ + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (__strong NSArray *entry in kvPairs) { + id keyError; + NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError]; + if (keyError) { + RCTAppendError(keyError, &errors); + } else { + if (value) { + NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy]; + RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError)); + entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)]; + } + if (!keyError) { + keyError = [self _writeEntry:entry]; + } + RCTAppendError(keyError, &errors); + } + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } +} + RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { From 015b5cf8e5123d4f6685058919bd177a3a169acb Mon Sep 17 00:00:00 2001 From: Alex Madjar Date: Wed, 3 Jun 2015 17:47:24 -0700 Subject: [PATCH 17/25] [React Native] PickerIOS can update its items list Summary: @public PickerIOS doesn't look at the content of its list. It just keeps a list ref and pushes new items in. Sadly this doesn't work with the naive reference checker so new item lists don't trigger a native rerender. The solution here is to ask PickerIOS to just pass its props down to native directly Test Plan: Implemented a simple Picker hooked up to a store getting new items. Watched the list not update and then update after this diff as new items come in --- Libraries/Picker/PickerIOS.ios.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index b2c3c392771..b1b1d06f466 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -20,8 +20,7 @@ var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants; var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactNativeComponentClass = - require('createReactNativeComponentClass'); +var requireNativeComponent = require('requireNativeComponent'); var merge = require('merge'); var PICKER = 'picker'; @@ -112,14 +111,6 @@ var styles = StyleSheet.create({ }, }); -var rkPickerIOSAttributes = merge(ReactNativeViewAttributes.UIView, { - items: true, - selectedIndex: true, -}); - -var RCTPickerIOS = createReactNativeComponentClass({ - validAttributes: rkPickerIOSAttributes, - uiViewClassName: 'RCTPicker', -}); +var RCTPickerIOS = requireNativeComponent('RCTPicker', null); module.exports = PickerIOS; From 53b2c39cc08270bfd94df655d3d3271066769d83 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Thu, 4 Jun 2015 10:27:51 -0700 Subject: [PATCH 18/25] select up and down the inspector hierarchy Summary: This allows you to select the displayed owner hierarchy, and see the styles, props, and position. @public Test Plan: Open the inspector, select something in the middle of the page. Click the breadcrumb train in the inspector, and verify that: - styles are reflected - margin/padding/box is correct - the highlight updates to show the selected item See video as well. [Video](https://www.latest.facebook.com/pxlcld/mqnl) Screenshot {F22518618} --- .../InspectorOverlay/ElementProperties.js | 65 +++++++++++++++---- .../InspectorOverlay/InspectorOverlay.js | 23 ++++++- Libraries/Utilities/mapWithSeparator.js | 19 ++++++ 3 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 Libraries/Utilities/mapWithSeparator.js diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js index 9e9ae1d6a00..310374fb148 100644 --- a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js +++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js @@ -18,38 +18,79 @@ var View = require('View'); var PropTypes = require('ReactPropTypes'); var BoxInspector = require('BoxInspector'); var StyleInspector = require('StyleInspector'); +var TouchableHighlight = require('TouchableHighlight'); +var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var flattenStyle = require('flattenStyle'); +var mapWithSeparator = require('mapWithSeparator'); var ElementProperties = React.createClass({ propTypes: { hierarchy: PropTypes.array.isRequired, style: PropTypes.array.isRequired, }, + render: function() { var style = flattenStyle(this.props.style); - var path = this.props.hierarchy.map((instance) => { - return instance.getName ? instance.getName() : 'Unknown'; - }).join(' > '); + var selection = this.props.selection; + // Without the `TouchableWithoutFeedback`, taps on this inspector pane + // would change the inspected element to whatever is under the inspector return ( - - - {path} - - - - + + + + {mapWithSeparator( + this.props.hierarchy, + (item, i) => ( + this.props.setSelection(i)}> + + {item.getName ? item.getName() : 'Unknown'} + + + ), + () => + )} + + + + + - + ); } }); var styles = StyleSheet.create({ + breadSep: { + fontSize: 8, + color: 'white', + }, + breadcrumb: { + flexDirection: 'row', + flexWrap: 'wrap', + marginBottom: 5, + }, + selected: { + borderColor: 'white', + borderRadius: 5, + }, + breadItem: { + borderWidth: 1, + borderColor: 'transparent', + marginHorizontal: 2, + }, + breadItemText: { + fontSize: 10, + color: 'white', + marginHorizontal: 5, + }, row: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'space-around', + justifyContent: 'space-between', }, info: { backgroundColor: 'rgba(0, 0, 0, 0.7)', diff --git a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index 1b5009143e4..3b391502ffa 100644 --- a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -24,7 +24,9 @@ var InspectorOverlay = React.createClass({ getInitialState: function() { return { frame: null, + pointerY: 0, hierarchy: [], + selection: -1, }; }, @@ -42,6 +44,8 @@ var InspectorOverlay = React.createClass({ var publicInstance = instance.getPublicInstance(); this.setState({ hierarchy, + pointerY: locationY, + selection: hierarchy.length - 1, frame: {left, top, width, height}, style: publicInstance.props ? publicInstance.props.style : {}, }); @@ -49,6 +53,18 @@ var InspectorOverlay = React.createClass({ ); }, + setSelection(i) { + var instance = this.state.hierarchy[i]; + var publicInstance = instance.getPublicInstance(); + UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => { + this.setState({ + frame: {left, top, width, height}, + style: publicInstance.props ? publicInstance.props.style : {}, + selection: i, + }); + }); + }, + shouldSetResponser: function(e) { this.findViewForTouchEvent(e); return true; @@ -59,9 +75,8 @@ var InspectorOverlay = React.createClass({ var justifyContent = 'flex-end'; if (this.state.frame) { - var distanceToTop = this.state.frame.top; - var distanceToBottom = Dimensions.get('window').height - - (this.state.frame.top + this.state.frame.height); + var distanceToTop = this.state.pointerY; + var distanceToBottom = Dimensions.get('window').height - distanceToTop; justifyContent = distanceToTop > distanceToBottom ? 'flex-start' @@ -73,6 +88,8 @@ var InspectorOverlay = React.createClass({ style={this.state.style} frame={this.state.frame} hierarchy={this.state.hierarchy} + selection={this.state.selection} + setSelection={this.setSelection} /> ); } else { diff --git a/Libraries/Utilities/mapWithSeparator.js b/Libraries/Utilities/mapWithSeparator.js new file mode 100644 index 00000000000..4aa8665c352 --- /dev/null +++ b/Libraries/Utilities/mapWithSeparator.js @@ -0,0 +1,19 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule mapWithSeparator + */ +'use strict'; + +function mapWithSeparator(array, valueFunction, separatorFunction) { + var results = []; + for (var i = 0; i < array.length; i++) { + results.push(valueFunction(array[i], i, array)); + if (i !== array.length - 1) { + results.push(separatorFunction(i)); + } + } + return results; +} + +module.exports = mapWithSeparator; From 30fc7389d18e7e5c65f29556fade37188b70f22f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 4 Jun 2015 15:09:13 -0700 Subject: [PATCH 19/25] [react-packager] Fix more node_modules resolution rules Summary: @public Fixes #773 This fixes `.json` name resolution. And also reads `package.json` when doing a directory module resolution. The algorithm can be found here: https://nodejs.org/api/modules.html I'll probably start including the node (or browserify) modules test in later diffs to make sure we're fully compliant. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PackagerIntegration * open playground and require a json file * test redbox --- .../__tests__/DependencyGraph-test.js | 69 ++++++++++++++++++- .../haste/DependencyGraph/index.js | 35 ++++------ 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 4dce3b8a4e8..935c4e3084b 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -168,9 +168,11 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")' + 'require("./a.json")', + 'require("./b")' ].join('\n'), 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), } }); @@ -186,7 +188,7 @@ describe('DependencyGraph', function() { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'], + dependencies: ['./a.json', './b'], isAsset: false, isAsset_DEPRECATED: false, isJSON: undefined, @@ -205,6 +207,17 @@ describe('DependencyGraph', function() { resolution: undefined, resolveDependency: undefined, }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -851,6 +864,58 @@ describe('DependencyGraph', function() { }); }); + 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({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should ignore malformed packages', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 74c13127623..1aa8b12902a 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,6 +1,3 @@ -// TODO -// Fix it to work with tests - /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -296,16 +293,18 @@ DependecyGraph.prototype.resolveDependency = function( modulePath = path.join(dir, depModuleId); modulePath = browserFieldRedirect(packageJson, modulePath); - // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { - modulePath = withExtJs(modulePath); - } + dep = this._graph[modulePath] || + this._graph[modulePath + '.js'] || + this._graph[modulePath + '.json']; - dep = this._graph[modulePath]; - - // Maybe the dependency is a directory and there is an index.js inside it. + // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. if (dep == null) { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; + if (dirPackageJson) { + dep = this._resolvePackageMain(dirPackageJson); + } else { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } } // Maybe it's an asset with @n.nx resolution and the path doesn't map @@ -444,14 +443,6 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return Promise.resolve(); - } - packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -461,14 +452,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { this._packagesById[packageJson.name] = packageJson; } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { delete this._packagesById[packageJson.name]; } }; @@ -536,7 +527,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { + if (packageJson == null || packageJson.name == null) { return path.resolve(modulePath); } else { var relativePath = From f744c7c4440ce8c16a45f8cc8fd79255c4283ba4 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 4 Jun 2015 20:20:37 -0700 Subject: [PATCH 20/25] [ReactNative] Native touch unique identifier Summary: This bug was causing a redbox when touching rapidly because multiple touches had the same identifier. This code was meant to ensure that a unique ID is found, but it was checking against the wrong property in the react touch array. I don't think this was really affecting prod, because the invariant is guarded by a __DEV__ check @public Test Plan: - Start several touches at once (or just rapidly jam fingers on your device), and you won't see a redbox - Also verify that single touches, touch moves, and multi touches work as before --- React/Base/RCTTouchHandler.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index 4ffef7ee752..0a2ca61a7d4 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -89,11 +89,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) { return; } - // Get new, unique touch id + // Get new, unique touch identifier for the react touch const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices - NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches; + NSInteger touchID = ([_reactTouches.lastObject[@"identifier"] integerValue] + 1) % RCTMaxTouches; for (NSDictionary *reactTouch in _reactTouches) { - NSInteger usedID = [reactTouch[@"target"] integerValue]; + NSInteger usedID = [reactTouch[@"identifier"] integerValue]; if (usedID == touchID) { // ID has already been used, try next value touchID ++; From 7009f0a47bee3f41e344605cc272360248ce863c Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 5 Jun 2015 04:23:51 -0700 Subject: [PATCH 21/25] [ReactNative] Add profiling hooks to bridge modules at runtime Summary: @public Add `RCT_DEV`-only profiling hooks to every method in every bridge modules to add a little bit more detail to the timeline profile. Test Plan: {F22522834} --- React/Base/RCTBridge.m | 51 ++++++++++------- React/Base/RCTProfile.h | 6 +- React/Base/RCTProfile.m | 118 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 25 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 70868f72110..c28778c49ff 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1313,7 +1313,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin #pragma mark - Payload Generation -- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID +- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module +{ + [self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]]; +} + +- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID { RCTAssertJSThread(); @@ -1458,7 +1463,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin if ([module respondsToSelector:@selector(batchDidComplete)]) { [self dispatchBlock:^{ [module batchDidComplete]; - } forModule:moduleID]; + } forModuleID:moduleID]; } }]; } @@ -1526,7 +1531,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin @"selector": NSStringFromSelector(method.selector), @"args": RCTJSONStringify(params ?: [NSNull null], NULL), }); - } forModule:@(moduleID)]; + } forModuleID:@(moduleID)]; return YES; } @@ -1546,7 +1551,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin RCTProfileBeginEvent(); [observer didUpdateFrame:frameUpdate]; RCTProfileEndEvent(name, @"objc_call,fps", nil); - } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; + } forModule:(id)observer]; } } @@ -1591,35 +1596,39 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (void)startProfiling { - RCTAssertMainThread(); + RCTAssertMainThread(); if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) { RCTLogError(@"To run the profiler you must be running from the dev server"); return; } - RCTProfileInit(); + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileInit(self); + }]; } - (void)stopProfiling { RCTAssertMainThread(); - NSString *log = RCTProfileEnd(); - NSURL *bundleURL = _parentBridge.bundleURL; - NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port]; - NSURL *URL = [NSURL URLWithString:URLString]; - NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; - URLRequest.HTTPMethod = @"POST"; - [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest - fromData:[log dataUsingEncoding:NSUTF8StringEncoding] - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - RCTLogError(@"%@", error.localizedDescription); - } - }]; - [task resume]; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + NSString *log = RCTProfileEnd(self); + NSURL *bundleURL = _parentBridge.bundleURL; + NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port]; + NSURL *URL = [NSURL URLWithString:URLString]; + NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; + URLRequest.HTTPMethod = @"POST"; + [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest + fromData:[log dataUsingEncoding:NSUTF8StringEncoding] + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + RCTLogError(@"%@", error.localizedDescription); + } + }]; + [task resume]; + }]; } @end diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index f722b0a02fe..2718871d25e 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -25,6 +25,8 @@ NSString *const RCTProfileDidEndProfiling; #if RCT_DEV +@class RCTBridge; + #define RCTProfileBeginFlowEvent() \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ @@ -45,14 +47,14 @@ RCT_EXTERN BOOL RCTProfileIsProfiling(void); /** * Start collecting profiling information */ -RCT_EXTERN void RCTProfileInit(void); +RCT_EXTERN void RCTProfileInit(RCTBridge *); /** * Stop profiling and return a JSON string of the collected data - The data * returned is compliant with google's trace event format - the format used * as input to trace-viewer */ -RCT_EXTERN NSString *RCTProfileEnd(void); +RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *); /** * Collects the initial event information for the event and returns a reference ID diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 09989d1cb33..1269d7594d7 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -10,10 +10,13 @@ #import "RCTProfile.h" #import +#import +#import #import #import "RCTAssert.h" +#import "RCTBridge.h" #import "RCTDefines.h" #import "RCTUtils.h" @@ -32,6 +35,7 @@ NSDictionary *RCTProfileGetMemoryUsage(void); NSString const *RCTProfileTraceEvents = @"traceEvents"; NSString const *RCTProfileSamples = @"samples"; +NSString *const RCTProfilePrefix = @"rct_profile_"; #pragma mark - Variables @@ -92,6 +96,111 @@ NSDictionary *RCTProfileGetMemoryUsage(void) } } +#pragma mark - Module hooks + +@interface RCTBridge (Private) + +- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module; + +@end + +static const char *RCTProfileProxyClassName(Class); +static const char *RCTProfileProxyClassName(Class class) +{ + return [RCTProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String; +} + +static SEL RCTProfileProxySelector(SEL); +static SEL RCTProfileProxySelector(SEL selector) +{ + NSString *selectorName = NSStringFromSelector(selector); + return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]); +} + +static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *); +static void RCTProfileForwardInvocation(NSObject *self, SEL cmd, NSInvocation *invocation) +{ + NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)]; + SEL newSel = RCTProfileProxySelector(invocation.selector); + + if ([object_getClass(self) instancesRespondToSelector:newSel]) { + invocation.selector = newSel; + RCTProfileBeginEvent(); + [invocation invoke]; + RCTProfileEndEvent(name, @"objc_call,modules,auto", nil); + } else { + // Use original selector to don't change error message + [self doesNotRecognizeSelector:invocation.selector]; + } +} + +static IMP RCTProfileMsgForward(NSObject *, SEL); +static IMP RCTProfileMsgForward(NSObject *self, SEL selector) +{ + IMP imp = _objc_msgForward; +#if !defined(__arm64__) + NSMethodSignature *signature = [self methodSignatureForSelector:selector]; + if (signature.methodReturnType[0] == _C_STRUCT_B && signature.methodReturnLength > 8) { + imp = _objc_msgForward_stret; + } +#endif + return imp; +} + +static void RCTProfileHookModules(RCTBridge *); +static void RCTProfileHookModules(RCTBridge *bridge) +{ + [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) { + [bridge dispatchBlock:^{ + Class moduleClass = object_getClass(module); + Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); + + unsigned int methodCount; + Method *methods = class_copyMethodList(moduleClass, &methodCount); + for (NSUInteger i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) { + continue; + } + IMP originalIMP = method_getImplementation(method); + const char *returnType = method_getTypeEncoding(method); + class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType); + class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType); + } + free(methods); + + for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { + Method oldImp = class_getInstanceMethod(cls, @selector(class)); + class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); + } + + IMP originalFwd = class_replaceMethod(moduleClass, @selector(forwardInvocation:), (IMP)RCTProfileForwardInvocation, "v@:@"); + if (originalFwd != NULL) { + class_addMethod(proxyClass, RCTProfileProxySelector(@selector(forwardInvocation:)), originalFwd, "v@:@"); + } + + objc_registerClassPair(proxyClass); + object_setClass(module, proxyClass); + } forModule:module]; + }]; +} + +void RCTProfileUnhookModules(RCTBridge *); +void RCTProfileUnhookModules(RCTBridge *bridge) +{ + [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) { + [bridge dispatchBlock:^{ + Class proxyClass = object_getClass(module); + if (module.class != proxyClass) { + object_setClass(module, module.class); + objc_disposeClassPair(proxyClass); + } + } forModule:module]; + }]; +} + + #pragma mark - Public Functions BOOL RCTProfileIsProfiling(void) @@ -102,8 +211,10 @@ BOOL RCTProfileIsProfiling(void) return profiling; } -void RCTProfileInit(void) +void RCTProfileInit(RCTBridge *bridge) { + RCTProfileHookModules(bridge); + static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _RCTProfileLock = [[NSLock alloc] init]; @@ -121,7 +232,7 @@ void RCTProfileInit(void) object:nil]; } -NSString *RCTProfileEnd(void) +NSString *RCTProfileEnd(RCTBridge *bridge) { [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:nil]; @@ -132,6 +243,9 @@ NSString *RCTProfileEnd(void) RCTProfileInfo = nil; RCTProfileOngoingEvents = nil; ); + + RCTProfileUnhookModules(bridge); + return log; } From 1a564eed772b6d0da9dd57456defe625fb73e229 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 4 Jun 2015 09:47:19 -0100 Subject: [PATCH 22/25] [ReactNative] Run UIExplorer tests on sandcastle --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ++++ Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index d72262e78f0..9d3adb2ee63 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,6 +50,10 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#if RUNNING_ON_CI + jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 245054621e5..349bd9a28a1 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.internal.uiexplorer.local CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSLocationWhenInUseUsageDescription + You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -34,8 +36,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - NSLocationWhenInUseUsageDescription - You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UIViewControllerBasedStatusBarAppearance diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index aa524ef56c1..7ff8c6379e2 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock block in blocks) { - block(cached, data, error); + for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { + cacheDownloadBlock(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9c0cacf709c..8a7e739bbdf 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,7 +35,11 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; +#if RUNNING_ON_CI + _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; +#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; +#endif } return self; } From f90fa53df91e90d027f9106b19b0e14c8a677626 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 4 Jun 2015 16:57:33 -0700 Subject: [PATCH 23/25] Revert [react-packager] Add support for nested node_modules --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 - .../__tests__/DependencyGraph-test.js | 2157 ++--------------- .../haste/DependencyGraph/index.js | 422 ++-- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 403 insertions(+), 2337 deletions(-) delete mode 100644 packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 3c1a1d26dc5..90db1c4ade5 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,9 +8,6 @@ */ 'use strict'; -var Promise = require('bluebird'); -var isAbsolutePath = require('absolute-path'); - function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -20,13 +17,17 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } - if (!isAbsolutePath(fields.path)) { - throw new Error('Expected absolute path but found: ' + fields.path); - } this.path = fields.path; + if (!fields.dependencies) { + throw new Error('Missing required fields dependencies'); + } this.dependencies = fields.dependencies; + this.resolveDependency = fields.resolveDependency; + + this.entry = fields.entry || false; + this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -49,30 +50,12 @@ function ModuleDescriptor(fields) { this._fields = fields; } -ModuleDescriptor.prototype.loadDependencies = function(loader) { - if (!this.dependencies) { - if (this._loadingDependencies) { - return this._loadingDependencies; - } - - var self = this; - this._loadingDependencies = loader(this).then(function(dependencies) { - self.dependencies = dependencies; - }); - return this._loadingDependencies; - } - - return Promise.resolve(this.dependencies); -}; - ModuleDescriptor.prototype.toJSON = function() { - var ret = {}; - Object.keys(this).forEach(function(prop) { - if (prop[0] !== '_' && typeof this[prop] !== 'function') { - ret[prop] = this[prop]; - } - }, this); - return ret; + return { + id: this.id, + path: this.path, + dependencies: this.dependencies + }; }; module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js deleted file mode 100644 index 99bed5df70e..00000000000 --- a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-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 - .dontMock('absolute-path') - .dontMock('../ModuleDescriptor'); - - -describe('ModuleDescriptor', function() { - var ModuleDescriptor; - var Promise; - - beforeEach(function() { - ModuleDescriptor = require('../ModuleDescriptor'); - Promise = require('bluebird'); - }); - - describe('constructor', function() { - it('should validate fields', function() { - /* eslint no-new:0*/ - expect(function() { - new ModuleDescriptor({}); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - }); - }).toThrow(); - - var m = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - expect(m.toJSON()).toEqual({ - altId:undefined, - dependencies: undefined, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - }); - }); - - describe('loadDependencies', function() { - pit('should load dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - - pit('should load cached dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - return mod.loadDependencies(function() { - throw new Error('no!'); - }); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - }); -}); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 935c4e3084b..c247e59d37a 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -10,12 +10,11 @@ jest .dontMock('../index') - .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .dontMock('../../../ModuleDescriptor'); + .setMock('../../../ModuleDescriptor', function(data) {return data;}); jest.mock('fs'); @@ -35,14 +34,6 @@ describe('DependencyGraph', function() { }; }); - // There are a lot of crap in ModuleDescriptors, this maps an array - // to get the relevant data. - function getDataFromModules(modules) { - return modules.map(function(module) { - return module.toJSON(); - }); - } - describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -67,33 +58,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -126,33 +95,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -168,11 +115,9 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")', - 'require("./b")' + 'require("./a.json")' ].join('\n'), 'a.json': JSON.stringify({}), - 'b.json': JSON.stringify({}), } }); @@ -181,42 +126,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json', './b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./a.json'] }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'package/b.json', - isJSON: true, - path: '/root/b.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -244,31 +167,15 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, + { id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -298,31 +205,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png'] }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -357,8 +253,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', @@ -368,13 +264,7 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + ] }, { id: 'rootPackage/imgs/a.png', @@ -382,32 +272,20 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, ]); }); @@ -439,20 +317,14 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png', 'image!a'] }, { id: 'rootPackage/imgs/a.png', @@ -460,10 +332,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'image!a', @@ -471,10 +339,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, ]); }); @@ -504,33 +368,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: ['index'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, ]); }); }); @@ -560,105 +402,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js', 'x.y.z'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'x.y.z/main', - path: '/root/x.y.z/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -683,30 +433,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -735,31 +468,14 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'EpicModule', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -787,30 +503,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/lib/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -835,82 +534,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'test/lib/index', + {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, + { id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: '/root/lib/main.js', - path: '/root/lib/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -938,21 +568,10 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, ]); }); }); @@ -993,32 +612,18 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ - { - id: 'index', + { id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['c'] }, - { - id: 'c', + { id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1054,31 +659,17 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage', + { id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1102,19 +693,12 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['lolomg'] } ]); }); @@ -1148,29 +732,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1205,30 +776,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', + { id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1263,56 +820,24 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/main', - altId: undefined, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', - altId: undefined, + { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['../other'] }, - { - id: 'aPackage/other', - altId: undefined, + { id: 'aPackage/other', path: '/root/aPackage/other.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1345,32 +870,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/client', - altId: undefined, + { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1403,30 +912,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', - altId: undefined, path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1461,29 +956,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1518,29 +1000,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1585,58 +1054,28 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./node', './dir/server.js'] }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./not-browser'] }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1681,633 +1120,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['node-package'] }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/lol', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/wow', - path: '/root/node_modules/foo/node_modules/bar/wow.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main2', - path: '/root/node_modules/bar/main2.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - path: '/root/path/to/bar.js', - altId: '/root/path/to/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - ].join('\n'), - 'node_modules': { - 'react-tools': { - 'package.json': JSON.stringify({ - name: 'react-tools', - main: 'main.js', - }), - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - 'submodule': { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - } - }, - 'ember': { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['shouldWork', 'dontWork', 'wontWork'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-tools/main.js', - altId:'react-tools/main', - dependencies: ['submodule'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'submodule/main', - path: '/root/node_modules/react-tools/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/node_modules/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2361,36 +1187,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2427,36 +1239,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2493,31 +1291,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2556,7 +1342,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2568,54 +1354,27 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'] + }, + { id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, ]); }); }); @@ -2641,50 +1400,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['image!foo'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + }, + ]); }); }); }); @@ -2711,49 +1452,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./foo.png'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + }, + { id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -2798,7 +1520,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2810,42 +1532,21 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['bar'] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); @@ -2883,407 +1584,25 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); - triggerFileChange('change', 'index.js', root); - - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/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'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root.node_modules.foo['main.js'] = 'lol'; - triggerFileChange('change', 'main.js', '/root/node_modules/foo'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - 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({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/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'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/browser', - altId: undefined, - path: '/root/node_modules/foo/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 1aa8b12902a..0881e5dc77d 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -19,7 +19,6 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); -var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -46,11 +45,7 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - }, - _providesModuleNodeModules: { - type: 'array', - default: ['react-tools', 'react-native'], - }, + } }); function DependecyGraph(options) { @@ -74,8 +69,6 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); - this._providesModuleNodeModules = opts._providesModuleNodeModules; - // Kick off the search process to precompute the dependency graph. this._init(); } @@ -97,99 +90,58 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - return this.load().then(function() { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } - - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } - - var self = this; - var deps = []; - var visited = Object.create(null); - - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; - - // Recursively collect the dependency list. - function collect(mod) { - deps.push(mod); - - if (mod.dependencies == null) { - return mod.loadDependencies(function() { - return readFile(mod.path, 'utf8').then(function(content) { - return extractRequires(content); - }); - }).then(function() { - return iter(mod); - }); - } - - return iter(mod); - } - - function iter(mod) { - var p = Promise.resolve(); - mod.dependencies.forEach(function(name) { - var dep = self.resolveDependency(mod, name); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - mod.id - ); - return; - } - - p = p.then(function() { - if (!visited[realId(dep)]) { - visited[realId(dep)] = true; - return collect(dep); - } - return null; - }); - }); - return p; - } - - visited[realId(module)] = true; - return collect(module).then(function() { - return deps; - }); - }.bind(this)); -}; - -function browserFieldRedirect(packageJson, modulePath, isMain) { - if (packageJson.browser && typeof packageJson.browser === 'object') { - if (isMain) { - var tmpMain = packageJson.browser[modulePath] || - packageJson.browser[sansExtJs(modulePath)] || - packageJson.browser[withExtJs(modulePath)]; - if (tmpMain) { - return tmpMain; - } - } else { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - return path.join(packageJson._root, tmpModulePath); - } - } + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } - return modulePath; -} + + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } + + var self = this; + var deps = []; + var visited = Object.create(null); + + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; + + // Recursively collect the dependency list. + function collect(module) { + deps.push(module); + + module.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(module, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + module.id + ); + return; + } + + if (!visited[dep.id]) { + visited[dep.id] = true; + collect(dep); + } + }); + } + + visited[module.id] = true; + collect(module); + + return deps; +}; /** * Given a module descriptor `fromModule` return the module descriptor for @@ -205,7 +157,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARNING: Cannot find asset:', assetMatch[1]); + debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -225,39 +177,19 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } - - var packageName = depModuleId.replace(/\/.+/, ''); - packageJson = this._lookupNodePackage(fromModule.path, packageName); - - if (packageJson != null && packageName !== depModuleId) { - modulePath = path.join( - packageJson._root, - path.relative(packageName, depModuleId) - ); - - modulePath = browserFieldRedirect(packageJson, modulePath); - - dep = this._graph[withExtJs(modulePath)]; - if (dep != null) { - return dep; - } - } - // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { + if (this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - if (packageJson == null) { - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - } + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; - // We are being forgiving here and not raising an error because we could be + // We are being forgiving here and raising an error because we could be // processing a file that uses it's own require system. - if (packageJson == null || packageName !== depModuleId) { + if (packageJson == null) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -266,8 +198,33 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - // We are requiring node or a haste package via it's main file. - dep = this._resolvePackageMain(packageJson); + var main; + + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + if (packageJson.browser && typeof packageJson.browser === 'object') { + var tmpMain = packageJson.browser[main] || + packageJson.browser[withExtJs(main)] || + packageJson.browser[sansExtJs(main)]; + if (tmpMain) { + main = tmpMain; + } + } + + modulePath = withExtJs(path.join(packageJson._root, main)); + dep = this._graph[modulePath]; + + // Some packages use just a dir and rely on an index.js inside that dir. + if (dep == null) { + dep = this._graph[path.join(packageJson._root, main, 'index.js')]; + } + if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -291,22 +248,28 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - modulePath = browserFieldRedirect(packageJson, modulePath); - dep = this._graph[modulePath] || - this._graph[modulePath + '.js'] || - this._graph[modulePath + '.json']; - - // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. - if (dep == null) { - var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; - if (dirPackageJson) { - dep = this._resolvePackageMain(dirPackageJson); - } else { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + if (packageJson.browser && typeof packageJson.browser === 'object') { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + modulePath = path.join(packageJson._root, tmpModulePath); } } + // JS modules can be required without extensios. + if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { + modulePath = withExtJs(modulePath); + } + + dep = this._graph[modulePath]; + + // Maybe the dependency is a directory and there is an index.js inside it. + if (dep == null) { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } + // Maybe it's an asset with @n.nx resolution and the path doesn't map // to the id if (dep == null && this._isFileAsset(modulePath)) { @@ -328,25 +291,6 @@ DependecyGraph.prototype.resolveDependency = function( } }; -DependecyGraph.prototype._resolvePackageMain = function(packageJson) { - var main; - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - main = browserFieldRedirect(packageJson, main, true); - - var modulePath = withExtJs(path.join(packageJson._root, main)); - - return this._graph[modulePath] || - // Some packages use just a dir and rely on an index.js inside that dir. - this._graph[path.join(packageJson._root, main, 'index.js')]; -}; - /** * Intiates the filewatcher and kicks off the search process. */ @@ -395,9 +339,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -414,8 +358,10 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { + var self = this; + var packagePath; - for (var i = 0; i < files.length; i++) { + for (var i = 0; i < files.length ; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -443,6 +389,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return Promise.resolve(); + } + packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -452,16 +406,12 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - this._packagesById[packageJson.name] = packageJson; - } + this._packagesById[packageJson.name] = packageJson; }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - delete this._packagesById[packageJson.name]; - } + delete this._packagesById[packageJson.name]; }; /** @@ -491,14 +441,6 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } - if (this._isInNodeModules(modulePath)) { - moduleData.id = this._lookupName(modulePath); - moduleData.dependencies = null; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -527,7 +469,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null || packageJson.name == null) { + if (packageJson == null) { return path.resolve(modulePath); } else { var relativePath = @@ -542,10 +484,6 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; - if (this._isInNodeModules(module.path)) { - return; - } - // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -566,10 +504,6 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; - if (this._isInNodeModules(module.path)) { - return; - } - if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -593,14 +527,28 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - return lookupPackage(path.dirname(modulePath), this._packageByRoot); -}; + var packageByRoot = this._packageByRoot; -/** - * Find the nearest node package to a module. - */ -DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { - return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); + /** + * Auxiliary function to recursively lookup a package. + */ + function lookupPackage(currDir) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir)); + } + } + } + + return lookupPackage(path.dirname(modulePath)); }; /** @@ -625,14 +573,12 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; - var packageJson; - if (isPackage) { - packageJson = this._packageByRoot[path.dirname(absPath)]; - } - if (eventType === 'delete') { - if (isPackage && packageJson) { - this._removePackageFromIndices(packageJson); + if (isPackage) { + var packageJson = this._packageByRoot[path.dirname(absPath)]; + if (packageJson) { + this._removePackageFromIndices(packageJson); + } } else { var module = this._graph[absPath]; if (module == null) { @@ -645,14 +591,7 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - self._removePackageFromIndices(packageJson); - return self._processPackage(absPath) - .then(function(p) { - return self._resolvePackageMain(p); - }) - .then(function(mainModule) { - return self._processModule(mainModule.path); - }); + return self._processPackage(absPath); } return self._processModule(absPath); }); @@ -736,25 +675,6 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; -DependecyGraph.prototype._isInNodeModules = function(file) { - var inNodeModules = file.indexOf('/node_modules/') !== -1; - - if (!inNodeModules) { - return false; - } - - var dirs = this._providesModuleNodeModules; - - for (var i = 0; i < dirs.length; i++) { - var index = file.indexOf(dirs[i]); - if (index !== -1) { - return file.slice(index).indexOf('/node_modules/') !== -1; - } - } - - return true; -}; - /** * Extract all required modules from a `code` string. */ @@ -864,54 +784,6 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } -function realId(module) { - if (module._realId) { - return module._realId; - } - - var hash = crypto.createHash('md5'); - hash.update(module.id); - hash.update(module.path); - Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); - return module._realId; -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupPackage(currDir, packageByRoot) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir), packageByRoot); - } - } -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupNodePackage(currDir, packageByRoot, packageName) { - if (currDir === '/') { - return null; - } - var packageRoot = path.join(currDir, 'node_modules', packageName); - - var packageJson = packageByRoot[packageRoot]; - if (packageJson) { - return packageJson; - } else { - return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); - } -} - function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 1f8c95479bb..9bc8b8b95c3 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index d7a8c0eb1b3..da68785eacd 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -91,8 +91,9 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.getOrderedDependencies(main) - .then(function(dependencies) { + return depGraph.load() + .then(function() { + var dependencies = depGraph.getOrderedDependencies(main); var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 45d8fb0ef64a8a41929d61fe822a64c2febe5455 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 5 Jun 2015 08:46:17 -0700 Subject: [PATCH 24/25] Removed deprecated RCT_EXPORT + code paths --- Examples/Movies/SearchScreen.js | 2 +- Examples/UIExplorer/StatusBarIOSExample.js | 22 +- Examples/UIExplorer/UIExplorerList.js | 2 +- .../DatePicker/DatePickerIOS.ios.js | 6 +- .../ProgressViewIOS/ProgressViewIOS.ios.js | 8 +- Libraries/Components/ScrollView/ScrollView.js | 20 +- .../SegmentedControlIOS.ios.js | 8 +- .../Components/StatusBar/StatusBarIOS.ios.js | 28 +- Libraries/Components/TextInput/TextInput.js | 79 ++---- Libraries/Image/Image.ios.js | 15 +- Libraries/Image/RCTNetworkImageViewManager.m | 2 +- Libraries/Image/RCTStaticImageManager.m | 2 +- Libraries/Picker/PickerIOS.ios.js | 4 +- React/Base/RCTBridge.m | 249 +++++++----------- React/Base/RCTBridgeModule.h | 9 - React/Base/RCTConvert.h | 3 +- React/Base/RCTConvert.m | 16 +- React/Base/RCTRedBox.m | 5 - React/Modules/RCTStatusBarManager.h | 8 + React/Modules/RCTStatusBarManager.m | 33 +-- React/Modules/RCTUIManager.m | 72 ----- React/Views/RCTDatePickerManager.h | 7 + React/Views/RCTDatePickerManager.m | 23 +- React/Views/RCTPickerManager.m | 6 +- React/Views/RCTScrollViewManager.h | 7 + React/Views/RCTScrollViewManager.m | 23 +- React/Views/RCTTextFieldManager.m | 3 +- 27 files changed, 233 insertions(+), 429 deletions(-) diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 05ab8018732..e7eb7f2b4a2 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -281,7 +281,7 @@ var SearchScreen = React.createClass({ renderRow={this.renderRow} onEndReached={this.onEndReached} automaticallyAdjustContentInsets={false} - keyboardDismissMode="onDrag" + keyboardDismissMode="on-drag" keyboardShouldPersistTaps={true} showsVerticalScrollIndicator={false} />; diff --git a/Examples/UIExplorer/StatusBarIOSExample.js b/Examples/UIExplorer/StatusBarIOSExample.js index 545136c49ca..ab649197eed 100644 --- a/Examples/UIExplorer/StatusBarIOSExample.js +++ b/Examples/UIExplorer/StatusBarIOSExample.js @@ -32,11 +32,11 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Style).map((key) => + {['default', 'light-content'].map((style) => StatusBarIOS.setStyle(StatusBarIOS.Style[key])}> + onPress={() => StatusBarIOS.setStyle(style)}> - setStyle(StatusBarIOS.Style.{key}) + setStyle('{style}') )} @@ -48,11 +48,11 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Style).map((key) => + {['default', 'light-content'].map((style) => StatusBarIOS.setStyle(StatusBarIOS.Style[key], true)}> + onPress={() => StatusBarIOS.setStyle(style, true)}> - setStyle(StatusBarIOS.Style.{key}, true) + setStyle('{style}', true) )} @@ -64,18 +64,18 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Animation).map((key) => + {['none', 'fade', 'slide'].map((animation) => StatusBarIOS.setHidden(true, StatusBarIOS.Animation[key])}> + onPress={() => StatusBarIOS.setHidden(true, animation)}> - setHidden(true, StatusBarIOS.Animation.{key}) + setHidden(true, '{animation}') StatusBarIOS.setHidden(false, StatusBarIOS.Animation[key])}> + onPress={() => StatusBarIOS.setHidden(false, animation)}> - setHidden(false, StatusBarIOS.Animation.{key}) + setHidden(false, '{animation}') diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index df9a3b12322..cd73e6d06ab 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -157,7 +157,7 @@ class UIExplorerList extends React.Component { renderSectionHeader={this._renderSectionHeader} keyboardShouldPersistTaps={true} automaticallyAdjustContentInsets={false} - keyboardDismissMode="onDrag" + keyboardDismissMode="on-drag" /> ); diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index 41fc9b8779f..f184c6f7931 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -120,7 +120,7 @@ var DatePickerIOS = React.createClass({ ), onTouchStart: this.scrollResponderHandleTouchStart, onTouchMove: this.scrollResponderHandleTouchMove, @@ -308,7 +299,7 @@ var ScrollView = React.createClass({ onResponderRelease: this.scrollResponderHandleResponderRelease, onResponderReject: this.scrollResponderHandleResponderReject, }; - + var ScrollViewClass; if (Platform.OS === 'ios') { ScrollViewClass = RCTScrollView; @@ -318,6 +309,13 @@ var ScrollView = React.createClass({ } else { ScrollViewClass = AndroidScrollView; } + var keyboardDismissModeConstants = { + 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default + 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive, + 'on-drag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag, + }; + props.keyboardDismissMode = props.keyboardDismissMode ? + keyboardDismissModeConstants[props.keyboardDismissMode] : undefined; } invariant( ScrollViewClass !== undefined, diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js index 23d952776bb..ec3b6c61476 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js @@ -108,13 +108,7 @@ var styles = StyleSheet.create({ var RCTSegmentedControl = requireNativeComponent( 'RCTSegmentedControl', - null + SegmentedControlIOS ); -if (__DEV__) { - verifyPropTypes( - RCTSegmentedControl, - RCTSegmentedControl.viewConfig - ); -} module.exports = SegmentedControlIOS; diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js index 14a5cecf2db..adfed78b7b2 100644 --- a/Libraries/Components/StatusBar/StatusBarIOS.ios.js +++ b/Libraries/Components/StatusBar/StatusBarIOS.ios.js @@ -13,26 +13,26 @@ var RCTStatusBarManager = require('NativeModules').StatusBarManager; +type StatusBarStyle = $Enum<{ + 'default': string, + 'light-content': string, +}>; + +type StatusBarAnimation = $Enum<{ + 'none': string, + 'fade': string, + 'slide': string, +}>; + var StatusBarIOS = { - Style: { - default: RCTStatusBarManager.Style.default, - lightContent: RCTStatusBarManager.Style.lightContent - }, - - Animation: { - none: RCTStatusBarManager.Animation.none, - fade: RCTStatusBarManager.Animation.fade, - slide: RCTStatusBarManager.Animation.slide, - }, - - setStyle(style: number, animated?: boolean) { + setStyle(style: StatusBarStyle, animated?: boolean) { animated = animated || false; RCTStatusBarManager.setStyle(style, animated); }, - setHidden(hidden: boolean, animation: number) { - animation = animation || StatusBarIOS.Animation.none; + setHidden(hidden: boolean, animation?: StatusBarAnimation) { + animation = animation || 'none'; RCTStatusBarManager.setHidden(hidden, animation); }, }; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 03f374b67aa..7d3f04b3326 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -31,10 +31,6 @@ var emptyFunction = require('emptyFunction'); var invariant = require('invariant'); var merge = require('merge'); -var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType; -var keyboardTypeConsts = RCTUIManager.UIKeyboardType; -var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType; - var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, { autoCorrect: true, autoCapitalize: true, @@ -96,10 +92,6 @@ var viewConfigAndroid = { validAttributes: AndroidTextInputAttributes, }; -var crossPlatformKeyboardTypeMap = { - 'numeric': 'decimal-pad', -}; - type DefaultProps = { bufferDelay: number; }; @@ -171,8 +163,11 @@ var TextInput = React.createClass({ * Determines which keyboard to open, e.g.`numeric`. */ keyboardType: PropTypes.oneOf([ - 'default', - // iOS + // Cross-platform + 'default', + 'numeric', + 'email-address', + // iOS-only 'ascii-capable', 'numbers-and-punctuation', 'url', @@ -182,9 +177,6 @@ var TextInput = React.createClass({ 'decimal-pad', 'twitter', 'web-search', - // Cross-platform - 'numeric', - 'email-address', ]), /** * Determines how the return key should look. @@ -426,18 +418,12 @@ var TextInput = React.createClass({ _renderIOS: function() { var textContainer; - var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; - var clearButtonMode = RCTUIManager.UITextField.clearButtonMode[this.props.clearButtonMode]; + var props = this.props; + props.style = [styles.input, this.props.style]; - var keyboardType = keyboardTypeConsts[ - crossPlatformKeyboardTypeMap[this.props.keyboardType] || - this.props.keyboardType - ]; - var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType]; - - if (!this.props.multiline) { + if (!props.multiline) { for (var propKey in onlyMultiline) { - if (this.props[propKey]) { + if (props[propKey]) { throw new Error( 'TextInput prop `' + propKey + '` is only supported with multiline.' ); @@ -446,77 +432,48 @@ var TextInput = React.createClass({ textContainer = true} - onLayout={this.props.onLayout} - placeholder={this.props.placeholder} - placeholderTextColor={this.props.placeholderTextColor} text={this.state.bufferedValue} - autoCapitalize={autoCapitalize} - autoCorrect={this.props.autoCorrect} - clearButtonMode={clearButtonMode} - clearTextOnFocus={this.props.clearTextOnFocus} - selectTextOnFocus={this.props.selectTextOnFocus} />; } else { for (var propKey in notMultiline) { - if (this.props[propKey]) { + if (props[propKey]) { throw new Error( 'TextInput prop `' + propKey + '` cannot be used with multiline.' ); } } - var children = this.props.children; + var children = props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); invariant( - !(this.props.value && childCount), + !(props.value && childCount), 'Cannot specify both value and children.' ); if (childCount > 1) { children = {children}; } - if (this.props.inputView) { - children = [children, this.props.inputView]; + if (props.inputView) { + children = [children, props.inputView]; } textContainer = ; } @@ -524,14 +481,14 @@ var TextInput = React.createClass({ + testID={props.testID}> {textContainer} ); }, _renderAndroid: function() { - var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; + var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize]; var children = this.props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 9b84937af84..23e2c7a6372 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -146,20 +146,11 @@ var Image = React.createClass({ if (this.props.style && this.props.style.tintColor) { warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.'); } - var resizeMode = this.props.resizeMode || style.resizeMode; - var contentModes = NativeModules.UIManager.UIView.ContentMode; - var contentMode; - if (resizeMode === ImageResizeMode.stretch) { - contentMode = contentModes.ScaleToFill; - } else if (resizeMode === ImageResizeMode.contain) { - contentMode = contentModes.ScaleAspectFit; - } else { // ImageResizeMode.cover or undefined - contentMode = contentModes.ScaleAspectFill; - } + var resizeMode = this.props.resizeMode || style.resizeMode || 'cover'; var nativeProps = merge(this.props, { style, - contentMode, + resizeMode, tintColor: style.tintColor, }); if (isStored) { @@ -187,7 +178,7 @@ var nativeOnlyProps = { src: true, defaultImageSrc: true, imageTag: true, - contentMode: true, + resizeMode: true, }; if (__DEV__) { verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps); diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 2ecf6997143..005b726cf4d 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -29,6 +29,6 @@ RCT_EXPORT_MODULE() RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL) -RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) +RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) @end diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index ce6aab187ae..bdc6f0596a6 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -26,7 +26,7 @@ RCT_EXPORT_MODULE() } RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) +RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage) { if (json) { diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index b1b1d06f466..42302fa26cd 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -59,7 +59,7 @@ var PickerIOS = React.createClass({ ", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; + return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>", + [self class], self, [self methodName], _JSMethodName]; } @end @@ -562,19 +498,10 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) const char **entries = (const char **)(mach_header + addr); // Create method - RCTModuleMethod *moduleMethod; - if (entries[2] == NULL) { - - // Legacy support for RCT_EXPORT() - moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) - objCMethodName:@(entries[0]) - JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil]; - } else { - moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) - objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil - JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil]; - } - + RCTModuleMethod *moduleMethod = + [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) + objCMethodName:@(entries[1]) + JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil]; // Cache method NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName]; methodsByModuleClassName[moduleMethod.moduleClassName] = @@ -726,7 +653,7 @@ static NSDictionary *RCTLocalModulesConfig() @"methodID": @(methods.count), @"type": @"local" }; - [RCTLocalMethodNames addObject:methodName]; + [RCTLocalMethodNames addObject:methodName]; } // Add module and method lookup @@ -1610,7 +1537,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (void)stopProfiling { - RCTAssertMainThread(); + RCTAssertMainThread(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ NSString *log = RCTProfileEnd(self); diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 34b861ff3f8..f3a9a5a3e42 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -145,15 +145,6 @@ extern const dispatch_queue_t RCTJSThread; static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \ } -/** - * Deprecated, do not use. - */ -#define RCT_EXPORT(js_name) \ - _Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \ - __attribute__((used, section("__DATA,RCTExport"))) \ - __attribute__((__aligned__(1))) \ - static const char *__rct_export_entry__[] = { __func__, #js_name, NULL } - /** * The queue that will be used to call all exported methods. If omitted, this * will call on the default background queue, which is avoids blocking the main diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index ee43c115977..145e88b21bc 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -23,6 +23,8 @@ */ @interface RCTConvert : NSObject ++ (id)id:(id)json; + + (BOOL)BOOL:(id)json; + (double)double:(id)json; + (float)float:(id)json; @@ -52,7 +54,6 @@ + (NSWritingDirection)NSWritingDirection:(id)json; + (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json; + (UITextFieldViewMode)UITextFieldViewMode:(id)json; -+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; + (UIKeyboardType)UIKeyboardType:(id)json; + (UIReturnKeyType)UIReturnKeyType:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 3c9143c17d2..3bdf5975361 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -21,6 +21,8 @@ void RCTLogConvertError(id json, const char *type) json, [json classForCoder], type); } +RCT_CONVERTER(id, id, self) + RCT_CONVERTER(BOOL, BOOL, boolValue) RCT_NUMBER_CONVERTER(double, doubleValue) RCT_NUMBER_CONVERTER(float, floatValue) @@ -219,12 +221,6 @@ RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{ @"always": @(UITextFieldViewModeAlways), }), UITextFieldViewModeNever, integerValue) -RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ - @"none": @(UIScrollViewKeyboardDismissModeNone), - @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag), - @"interactive": @(UIScrollViewKeyboardDismissModeInteractive), -}), UIScrollViewKeyboardDismissModeNone, integerValue) - RCT_ENUM_CONVERTER(UIKeyboardType, (@{ @"default": @(UIKeyboardTypeDefault), @"ascii-capable": @(UIKeyboardTypeASCIICapable), @@ -237,6 +233,8 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{ @"decimal-pad": @(UIKeyboardTypeDecimalPad), @"twitter": @(UIKeyboardTypeTwitter), @"web-search": @(UIKeyboardTypeWebSearch), + // Added for Android compatibility + @"numeric": @(UIKeyboardTypeDecimalPad), }), UIKeyboardTypeDefault, integerValue) RCT_ENUM_CONVERTER(UIReturnKeyType, (@{ @@ -267,7 +265,11 @@ RCT_ENUM_CONVERTER(UIViewContentMode, (@{ @"top-right": @(UIViewContentModeTopRight), @"bottom-left": @(UIViewContentModeBottomLeft), @"bottom-right": @(UIViewContentModeBottomRight), -}), UIViewContentModeScaleToFill, integerValue) + // Cross-platform values + @"cover": @(UIViewContentModeScaleAspectFill), + @"contain": @(UIViewContentModeScaleAspectFit), + @"stretch": @(UIViewContentModeScaleToFill), +}), UIViewContentModeScaleAspectFill, integerValue) RCT_ENUM_CONVERTER(UIBarStyle, (@{ @"default": @(UIBarStyleDefault), diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 2d35dcd6fc2..6330454abe7 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -85,11 +85,6 @@ selector:@selector(dismiss) name:RCTReloadNotification object:nil]; - - [notificationCenter addObserver:self - selector:@selector(dismiss) - name:RCTJavaScriptDidLoadNotification - object:nil]; } return self; } diff --git a/React/Modules/RCTStatusBarManager.h b/React/Modules/RCTStatusBarManager.h index 40feee5c012..aee9b86424f 100644 --- a/React/Modules/RCTStatusBarManager.h +++ b/React/Modules/RCTStatusBarManager.h @@ -10,6 +10,14 @@ #import #import "RCTBridgeModule.h" +#import "RCTConvert.h" + +@interface RCTConvert (UIStatusBar) + ++ (UIStatusBarStyle)UIStatusBarStyle:(id)json; ++ (UIStatusBarAnimation)UIStatusBarAnimation:(id)json; + +@end @interface RCTStatusBarManager : NSObject diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 04bb390387c..cb9ddfe690a 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -11,6 +11,21 @@ #import "RCTLog.h" +@implementation RCTConvert (UIStatusBar) + +RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{ + @"default": @(UIStatusBarStyleDefault), + @"light-content": @(UIStatusBarStyleLightContent), +}), UIStatusBarStyleDefault, integerValue); + +RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ + @"none": @(UIStatusBarAnimationNone), + @"fade": @(UIStatusBarAnimationFade), + @"slide": @(UIStatusBarAnimationSlide), +}), UIStatusBarAnimationNone, integerValue); + +@end + @implementation RCTStatusBarManager static BOOL RCTViewControllerBasedStatusBarAppearance() @@ -18,7 +33,8 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() static BOOL value; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue]; + value = [[[NSBundle mainBundle] objectForInfoDictionaryKey: + @"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue]; }); return value; @@ -55,19 +71,4 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden } } -- (NSDictionary *)constantsToExport -{ - return @{ - @"Style": @{ - @"default": @(UIStatusBarStyleDefault), - @"lightContent": @(UIStatusBarStyleLightContent), - }, - @"Animation": @{ - @"none": @(UIStatusBarAnimationNone), - @"fade": @(UIStatusBarAnimationFade), - @"slide": @(UIStatusBarAnimationSlide), - }, - }; -} - @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index cc580e9036f..570bdfef736 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1397,11 +1397,6 @@ RCT_EXPORT_METHOD(clearJSResponder) NSMutableDictionary *allJSConstants = [@{ @"customBubblingEventTypes": [self customBubblingEventTypes], @"customDirectEventTypes": [self customDirectEventTypes], - @"NSTextAlignment": @{ - @"Left": @(NSTextAlignmentLeft), - @"Center": @(NSTextAlignmentCenter), - @"Right": @(NSTextAlignmentRight), - }, @"Dimensions": @{ @"window": @{ @"width": @(RCTScreenSize().width), @@ -1413,73 +1408,6 @@ RCT_EXPORT_METHOD(clearJSResponder) @"height": @(RCTScreenSize().height), }, }, - @"StyleConstants": @{ - @"PointerEventsValues": @{ - @"none": @(RCTPointerEventsNone), - @"box-none": @(RCTPointerEventsBoxNone), - @"box-only": @(RCTPointerEventsBoxOnly), - @"auto": @(RCTPointerEventsUnspecified), - }, - }, - @"UIText": @{ - @"AutocapitalizationType": @{ - @"characters": @(UITextAutocapitalizationTypeAllCharacters), - @"sentences": @(UITextAutocapitalizationTypeSentences), - @"words": @(UITextAutocapitalizationTypeWords), - @"none": @(UITextAutocapitalizationTypeNone), - }, - }, - @"UITextField": @{ - @"clearButtonMode": @{ - @"never": @(UITextFieldViewModeNever), - @"while-editing": @(UITextFieldViewModeWhileEditing), - @"unless-editing": @(UITextFieldViewModeUnlessEditing), - @"always": @(UITextFieldViewModeAlways), - }, - }, - @"UIKeyboardType": @{ - @"default": @(UIKeyboardTypeDefault), - @"ascii-capable": @(UIKeyboardTypeASCIICapable), - @"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation), - @"url": @(UIKeyboardTypeURL), - @"number-pad": @(UIKeyboardTypeNumberPad), - @"phone-pad": @(UIKeyboardTypePhonePad), - @"name-phone-pad": @(UIKeyboardTypeNamePhonePad), - @"decimal-pad": @(UIKeyboardTypeDecimalPad), - @"email-address": @(UIKeyboardTypeEmailAddress), - @"twitter": @(UIKeyboardTypeTwitter), - @"web-search": @(UIKeyboardTypeWebSearch), - }, - @"UIReturnKeyType": @{ - @"default": @(UIReturnKeyDefault), - @"go": @(UIReturnKeyGo), - @"google": @(UIReturnKeyGoogle), - @"join": @(UIReturnKeyJoin), - @"next": @(UIReturnKeyNext), - @"route": @(UIReturnKeyRoute), - @"search": @(UIReturnKeySearch), - @"send": @(UIReturnKeySend), - @"yahoo": @(UIReturnKeyYahoo), - @"done": @(UIReturnKeyDone), - @"emergency-call": @(UIReturnKeyEmergencyCall), - }, - @"UIView": @{ - @"ContentMode": @{ - @"ScaleToFill": @(UIViewContentModeScaleToFill), - @"ScaleAspectFit": @(UIViewContentModeScaleAspectFit), - @"ScaleAspectFill": @(UIViewContentModeScaleAspectFill), - @"Redraw": @(UIViewContentModeRedraw), - @"Center": @(UIViewContentModeCenter), - @"Top": @(UIViewContentModeTop), - @"Bottom": @(UIViewContentModeBottom), - @"Left": @(UIViewContentModeLeft), - @"Right": @(UIViewContentModeRight), - @"TopLeft": @(UIViewContentModeTopLeft), - @"TopRight": @(UIViewContentModeTopRight), - @"BottomLeft": @(UIViewContentModeBottomLeft), - @"BottomRight": @(UIViewContentModeBottomRight), - }, - }, } mutableCopy]; [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) { diff --git a/React/Views/RCTDatePickerManager.h b/React/Views/RCTDatePickerManager.h index eb424085dc9..73d88b6dd1c 100644 --- a/React/Views/RCTDatePickerManager.h +++ b/React/Views/RCTDatePickerManager.h @@ -8,6 +8,13 @@ */ #import "RCTViewManager.h" +#import "RCTConvert.h" + +@interface RCTConvert(UIDatePicker) + ++ (UIDatePickerMode)UIDatePickerMode:(id)json; + +@end @interface RCTDatePickerManager : RCTViewManager diff --git a/React/Views/RCTDatePickerManager.m b/React/Views/RCTDatePickerManager.m index 36397d6e542..d8bbde7034c 100644 --- a/React/Views/RCTDatePickerManager.m +++ b/React/Views/RCTDatePickerManager.m @@ -10,7 +10,6 @@ #import "RCTDatePickerManager.h" #import "RCTBridge.h" -#import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "UIView+React.h" @@ -20,7 +19,7 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{ @"time": @(UIDatePickerModeTime), @"date": @(UIDatePickerModeDate), @"datetime": @(UIDatePickerModeDateAndTime), - //@"countdown": @(UIDatePickerModeCountDownTimer) // not supported yet + @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet }), UIDatePickerModeTime, integerValue) @end @@ -31,9 +30,12 @@ RCT_EXPORT_MODULE() - (UIView *)view { + // TODO: we crash here if the RCTDatePickerManager is released + // while the UIDatePicker is still sending onChange events. To + // fix this we should maybe subclass UIDatePicker and make it + // be its own event target. UIDatePicker *picker = [[UIDatePicker alloc] init]; - [picker addTarget:self - action:@selector(onChange:) + [picker addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; return picker; } @@ -56,17 +58,10 @@ RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone) - (NSDictionary *)constantsToExport { - UIDatePicker *dp = [[UIDatePicker alloc] init]; - [dp layoutIfNeeded]; - + UIDatePicker *view = [[UIDatePicker alloc] init]; return @{ - @"ComponentHeight": @(CGRectGetHeight(dp.frame)), - @"ComponentWidth": @(CGRectGetWidth(dp.frame)), - @"DatePickerModes": @{ - @"time": @(UIDatePickerModeTime), - @"date": @(UIDatePickerModeDate), - @"datetime": @(UIDatePickerModeDateAndTime), - } + @"ComponentHeight": @(view.intrinsicContentSize.height), + @"ComponentWidth": @(view.intrinsicContentSize.width), }; } diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index 3bbc60b9431..de6c1f9167a 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -27,10 +27,10 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) - (NSDictionary *)constantsToExport { - RCTPicker *pv = [[RCTPicker alloc] init]; + RCTPicker *view = [[RCTPicker alloc] init]; return @{ - @"ComponentHeight": @(CGRectGetHeight(pv.frame)), - @"ComponentWidth": @(CGRectGetWidth(pv.frame)) + @"ComponentHeight": @(view.intrinsicContentSize.height), + @"ComponentWidth": @(view.intrinsicContentSize.width) }; } diff --git a/React/Views/RCTScrollViewManager.h b/React/Views/RCTScrollViewManager.h index 9fec3422d50..83b3126e8f1 100644 --- a/React/Views/RCTScrollViewManager.h +++ b/React/Views/RCTScrollViewManager.h @@ -8,6 +8,13 @@ */ #import "RCTViewManager.h" +#import "RCTConvert.h" + +@interface RCTConvert (UIScrollView) + ++ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; + +@end @interface RCTScrollViewManager : RCTViewManager diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 8441de74d29..15803df1e42 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -10,11 +10,22 @@ #import "RCTScrollViewManager.h" #import "RCTBridge.h" -#import "RCTConvert.h" #import "RCTScrollView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" +@implementation RCTConvert (UIScrollView) + +RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ + @"none": @(UIScrollViewKeyboardDismissModeNone), + @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag), + @"interactive": @(UIScrollViewKeyboardDismissModeInteractive), + // Backwards compatibility + @"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag), +}), UIScrollViewKeyboardDismissModeNone, integerValue) + +@end + @implementation RCTScrollViewManager RCT_EXPORT_MODULE() @@ -53,14 +64,10 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle) - (NSDictionary *)constantsToExport { return @{ + // TODO: unused - remove these? @"DecelerationRate": @{ - @"Normal": @(UIScrollViewDecelerationRateNormal), - @"Fast": @(UIScrollViewDecelerationRateFast), - }, - @"KeyboardDismissMode": @{ - @"None": @(UIScrollViewKeyboardDismissModeNone), - @"Interactive": @(UIScrollViewKeyboardDismissModeInteractive), - @"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag), + @"normal": @(UIScrollViewDecelerationRateNormal), + @"fast": @(UIScrollViewDecelerationRateFast), }, }; } diff --git a/React/Views/RCTTextFieldManager.m b/React/Views/RCTTextFieldManager.m index ff401a719c7..7b867bd0dc4 100644 --- a/React/Views/RCTTextFieldManager.m +++ b/React/Views/RCTTextFieldManager.m @@ -25,7 +25,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) -RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL) +RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(text, NSString) @@ -36,6 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) +RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField) From bb95400f244015c96fe201d22d40f65bd6805775 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Fri, 5 Jun 2015 10:54:17 -0700 Subject: [PATCH 25/25] [ReactNative] Fix POPAnimation export issue from bad transform Summary: Some transform is going wrong here and produces broken code @public Test Plan: Test on OSS repo, e2e tests pass --- Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js index 0245cc14415..c8e28943129 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule POPAnimation - * @flow */ 'use strict'; @@ -17,7 +16,7 @@ if (!RCTPOPAnimationManager) { // workaround to enable its availability to be determined at runtime. // For Flow let's pretend like we always export POPAnimation // so all our users don't need to do null checks - module.exports = ((null: any): typeof POPAnimation); + module.exports = null; } else { var ReactPropTypes = require('ReactPropTypes');