From 5793f5c4c4d0dd17905b6683779d479c7960ca57 Mon Sep 17 00:00:00 2001 From: Shuangzuan Date: Tue, 16 Jun 2015 07:07:08 -0700 Subject: [PATCH 01/64] [AnimationExperimental] Fixed CATransaction completion block invoke immediately Summary: [CATransaction Class Reference](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CATransaction_class/index.html) In Tasks > Getting and Setting Completion Block Objects > Discussion: The completion block object that is guaranteed to be called (on the main thread) as soon as all animations subsequently added by this transaction group have completed (or have been removed.) If no animations are added before the current transaction group is committed (or the completion block is set to a different value,) the block will be invoked immediately. Closes https://github.com/facebook/react-native/pull/1400 Github Author: Shuangzuan Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Animation/RCTAnimationExperimentalManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index 6bcda39ae..13c3f079a 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -235,7 +235,6 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag @try { [view.layer setValue:toValue forKey:keypath]; NSString *animationKey = [@"RCT" stringByAppendingString:RCTJSONStringify(@{@"tag": animationTag, @"key": keypath}, nil)]; - [view.layer addAnimation:animation forKey:animationKey]; if (!completionBlockSet) { strongSelf->_callbackRegistry[animationTag] = callback; [CATransaction setCompletionBlock:^{ @@ -247,6 +246,7 @@ RCT_EXPORT_METHOD(startAnimation:(NSNumber *)reactTag }]; completionBlockSet = YES; } + [view.layer addAnimation:animation forKey:animationKey]; } @catch (NSException *exception) { return RCTInvalidAnimationProp(strongSelf->_callbackRegistry, animationTag, keypath, toValue); From 0a875790f58c7de2842b21216c1ceaa5d1185547 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Tue, 16 Jun 2015 09:08:16 -0700 Subject: [PATCH 02/64] [Navigator]: Allow developer to observe the focus change events from the owner or the children of the navigator component. Summary: Per offline discussion with @evv, we'd like to deprecate the `onDidFocus` and `onWillFocus` API that makes it really hard for the descendent children of a navigator to observe its focus change events. @public Since for now the descendent children do have access to the navigator via `this.props.navigator`, this diff makes it easy to observe the focus change event by doing: ``` this.props.navigator.addListener('willfocus', this._onFocus); ``` The goal is to make the event system in navigator more useful and maintainable. Test Plan: Test Video: https://www.facebook.com/pxlcld/mrzS 1. jest: ./Libraries/FBReactKit/js/runTests.js NavigationEventEmitter 2. Load UI Explorer: , see console logs that shows the focus change events fires. --- .../Navigator/NavigationBarSample.js | 25 ++++++ .../UIExplorer/Navigator/NavigatorExample.js | 29 +++++++ .../Navigator/Navigation/NavigationContext.js | 76 ++++++++++++++++++ .../Navigator/Navigation/NavigationEvent.js | 21 +++++ .../Navigation/NavigationEventEmitter.js | 70 +++++++++++++++++ .../__tests__/NavigationEventEmitter-test.js | 78 +++++++++++++++++++ .../CustomComponents/Navigator/Navigator.js | 25 +++++- 7 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js create mode 100644 Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js create mode 100644 Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js create mode 100644 Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js diff --git a/Examples/UIExplorer/Navigator/NavigationBarSample.js b/Examples/UIExplorer/Navigator/NavigationBarSample.js index 2b3f8e250..545f76b82 100644 --- a/Examples/UIExplorer/Navigator/NavigationBarSample.js +++ b/Examples/UIExplorer/Navigator/NavigationBarSample.js @@ -92,6 +92,31 @@ function newRandomRoute() { var NavigationBarSample = React.createClass({ + componentWillMount: function() { + var navigator = this.props.navigator; + + var callback = (event) => { + console.log( + `NavigationBarSample : event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + + // Observe focus change events from this component. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + }, + + componentWillUnmount: function() { + this._listeners && this._listeners.forEach(listener => listener.remove()); + }, + render: function() { return ( listener.remove()); + }, + + _setNavigatorRef: function(navigator) { + if (navigator !== this._navigator) { + this._navigator = navigator; + + if (navigator) { + var callback = (event) => { + console.log( + `TabBarExample: event ${event.type}`, + { + route: JSON.stringify(event.data.route), + target: event.target, + type: event.type, + } + ); + }; + // Observe focus change events from the owner. + this._listeners = [ + navigator.navigationContext.addListener('willfocus', callback), + navigator.navigationContext.addListener('didfocus', callback), + ]; + } + } + }, }); var styles = StyleSheet.create({ diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js new file mode 100644 index 000000000..8169415eb --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @providesModule NavigationContext + * @flow + */ +'use strict'; + +var NavigationEventEmitter = require('NavigationEventEmitter'); +var emptyFunction = require('emptyFunction'); + +type EventSubscription = { + remove: Function +}; + +/** + * Class that contains the info and methods for app navigation. + */ +class NavigationContext { + _eventEmitter: ?NavigationEventEmitter; + + constructor() { + this._eventEmitter = new NavigationEventEmitter(this); + } + + addListener( + eventType: string, + listener: Function, + context: ?Object + ): EventSubscription { + var emitter = this._eventEmitter; + if (emitter) { + return emitter.addListener(eventType, listener, context); + } else { + return {remove: emptyFunction}; + } + } + + emit(eventType: String, data: any): void { + var emitter = this._eventEmitter; + if (emitter) { + emitter.emit(eventType, data); + } + } + + dispose() { + var emitter = this._eventEmitter; + if (emitter) { + emitter.removeAllListeners(); + this._eventEmitter = null; + } + } +} + +module.exports = NavigationContext; diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js new file mode 100644 index 000000000..b6923b4f2 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEvent.js @@ -0,0 +1,21 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule NavigationEvent + * @flow + */ +'use strict'; + +class NavigationEvent { + type: String; + target: Object; + data: any; + + constructor(type: String, target: Object, data: any) { + this.type = type; + this.target = target; + this.data = data; + } +} + +module.exports = NavigationEvent; diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js new file mode 100644 index 000000000..db9e78554 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationEventEmitter.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @providesModule NavigationEventEmitter + * @flow + */ +'use strict'; + +var EventEmitter = require('EventEmitter'); +var NavigationEvent = require('NavigationEvent'); + +type EventParams = { + eventType: String; + data: any; +}; + +class NavigationEventEmitter extends EventEmitter { + _emitQueue: Array; + _emitting: boolean; + _target: Object; + + constructor(target: Object) { + super(); + this._emitting = false; + this._emitQueue = []; + this._target = target; + } + + emit(eventType: String, data: any): void { + if (this._emitting) { + // An event cycle that was previously created hasn't finished yet. + // Put this event cycle into the queue and will finish them later. + this._emitQueue.push({eventType, data}); + return; + } + + this._emitting = true; + var event = new NavigationEvent(eventType, this._target, data); + super.emit(eventType, event); + this._emitting = false; + + while (this._emitQueue.length) { + var arg = this._emitQueue.shift(); + this.emit(arg.eventType, arg.data); + } + } +} + +module.exports = NavigationEventEmitter; diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js new file mode 100644 index 000000000..518fe0724 --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +'use strict'; + +jest + .dontMock('EventEmitter') + .dontMock('NavigationEvent') + .dontMock('NavigationEventEmitter'); + +var NavigationEventEmitter = require('NavigationEventEmitter'); + +describe('NavigationEventEmitter', () => { + it('emit event', () => { + var target = {}; + var emitter = new NavigationEventEmitter(target); + var focusCounter = 0; + var focusTarget; + + emitter.addListener('focus', (event) => { + focusCounter++; + focusTarget = event.target; + }); + + emitter.emit('focus'); + emitter.emit('blur'); + + expect(focusCounter).toBe(1); + expect(focusTarget).toBe(target); + }); + + it('put nested emit call in queue', () => { + var target = {}; + var emitter = new NavigationEventEmitter(target); + var logs = []; + + emitter.addListener('one', () => { + logs.push(1); + emitter.emit('two'); + logs.push(2); + }); + + emitter.addListener('two', () => { + logs.push(3); + emitter.emit('three'); + logs.push(4); + }); + + emitter.addListener('three', () => { + logs.push(5); + }); + + emitter.emit('one'); + + expect(logs).toEqual([1, 2, 3, 4, 5]); + }); +}); diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index af10348fc..93610e973 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -30,11 +30,11 @@ var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule; var Dimensions = require('Dimensions'); var InteractionMixin = require('InteractionMixin'); +var NavigationContext = require('NavigationContext'); var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar'); var NavigatorNavigationBar = require('NavigatorNavigationBar'); var NavigatorSceneConfigs = require('NavigatorSceneConfigs'); var PanResponder = require('PanResponder'); -var Platform = require('Platform'); var React = require('React'); var StaticContainer = require('StaticContainer.react'); var StyleSheet = require('StyleSheet'); @@ -203,11 +203,17 @@ var Navigator = React.createClass({ initialRouteStack: PropTypes.arrayOf(PropTypes.object), /** + * @deprecated + * Use `navigationContext.addListener('willfocus', callback)` instead. + * * Will emit the target route upon mounting and before each nav transition */ onWillFocus: PropTypes.func, /** + * @deprecated + * Use `navigationContext.addListener('didfocus', callback)` instead. + * * Will be called with the new route of each scene after the transition is * complete or after the initial mounting */ @@ -321,7 +327,10 @@ var Navigator = React.createClass({ }, componentWillUnmount: function() { - + if (this._navigationContext) { + this._navigationContext.dispose(); + this._navigationContext = null; + } }, /** @@ -461,12 +470,16 @@ var Navigator = React.createClass({ }, _emitDidFocus: function(route) { + this.navigationContext.emit('didfocus', {route: route}); + if (this.props.onDidFocus) { this.props.onDidFocus(route); } }, _emitWillFocus: function(route) { + this.navigationContext.emit('willfocus', {route: route}); + var navBar = this._navBar; if (navBar && navBar.handleWillFocus) { navBar.handleWillFocus(route); @@ -1139,6 +1152,14 @@ var Navigator = React.createClass({ ); }, + + // Getter for `navigationContext`. + get navigationContext() { + if (!this._navigationContext) { + this._navigationContext = new NavigationContext(); + } + return this._navigationContext; + } }); module.exports = Navigator; From 7dd2dd7962e1c2627166c09ebd54f15848cf785e Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Tue, 16 Jun 2015 10:05:27 -0700 Subject: [PATCH 03/64] [Docs] Fixed word auto-correction in TextInput.js Summary: Closes https://github.com/facebook/react-native/pull/1593 Github Author: Prathamesh Sonpatki Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/TextInput/TextInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 00a27e3c1..902f4100a 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -68,8 +68,8 @@ type Event = Object; /** * A foundational component for inputting text into the app via a - * keyboard. Props provide configurability for several features, such as auto- - * correction, auto-capitalization, placeholder text, and different keyboard + * keyboard. Props provide configurability for several features, such as + * auto-correction, auto-capitalization, placeholder text, and different keyboard * types, such as a numeric keypad. * * The simplest use case is to plop down a `TextInput` and subscribe to the From 477360b8c95320b90cd6314022e761bc495182d0 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Tue, 16 Jun 2015 12:04:22 -0700 Subject: [PATCH 04/64] [react-packager] Make it safe to include files without a newline at the end Summary: @public Fixes #1431 Fixes #1005 Files with no newlines and a comment at the end of the file would've caused a syntax error in the bundle: ```js __d('module', function() { hi(); // wow }) ``` This fixes the issue by inserting a new lines before `})`. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PackagerIntegration * open app to the playground app * add an error * observe that the redbox has the correct lines --- .../haste/__tests__/HasteDependencyResolver-test.js | 3 ++- packager/react-packager/src/DependencyResolver/haste/index.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 9bc8b8b95..0d1296ba1 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -600,7 +600,8 @@ describe('HasteDependencyResolver', function() { 'require("Y")', 'require( \'z\' )', 'require( "a")', - 'require("b" )});', + 'require("b" )', + '});', ].join('\n')); }); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index da68785ea..aaa79c95b 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -20,8 +20,7 @@ var DEFINE_MODULE_CODE = [ '_deps_,', 'function(global, require, requireDynamic, requireLazy, module, exports) {', ' _code_', - '}', - ');', + '\n});', ].join(''); var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; From a331bb752643c406a34240d292e641c520273f65 Mon Sep 17 00:00:00 2001 From: Peter Cottle Date: Tue, 16 Jun 2015 15:40:52 -0700 Subject: [PATCH 05/64] [RFC] Add ScrollView bounded height explanation Summary: As discussed in our internal group, think this is a fairly easy error to run into so I added some explanation. @frantic / @vjeux open to better wording here, but I tried to explain how setting the height directly is discouraged and it's probably better to pipe `flex: 1` all the way down. I didn't regenerate the website since the script assumes some permissions (push permission to master repo) and has some missing npm dependencies (and after fixing that, still had some obscure error :P ) Closes https://github.com/facebook/react-native/pull/1633 Github Author: Peter Cottle Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/ScrollView/ScrollView.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 8952fb0d1..512d8dbd6 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -42,6 +42,14 @@ var INNERVIEW = 'InnerScrollView'; * Component that wraps platform ScrollView while providing * integration with touch locking "responder" system. * + * Keep in mind that ScrollViews must have a bounded height in order to work, + * since they contain unbounded-height children into a bounded container (via + * a scroll interaction). In order to bound the height of a ScrollView, either + * set the height of the view directly (discouraged) or make sure all parent + * views have bounded height. Forgetting to transfer `{flex: 1}` down the + * view stack can lead to errors here, which the element inspector makes + * easy to debug. + * * Doesn't yet support other contained responders from blocking this scroll * view from becoming the responder. */ From 8e07b39a1e3c7883ce3ae476665c27babdfa7141 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Wed, 17 Jun 2015 02:45:12 -0700 Subject: [PATCH 06/64] Update promise to 7.0.3 Summary: Updates promise, and more importantly asap. This fixes some nasty race conditions. See: - https://github.com/then/promise/issues/99 - https://github.com/then/promise/issues/93 @public Test Plan: sh Libraries/FBReactKit/runJestTests.sh Open Catalyst and check it works --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 606c9bf59..b215d6ec4 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "jstransform": "11.0.1", "module-deps": "3.5.6", "optimist": "0.6.1", - "promise": "^7.0.0", + "promise": "^7.0.3", "react-timer-mixin": "^0.13.1", "react-tools": "0.13.2", "rebound": "^0.0.12", From d83ee3bd5a0c14824db284326a1aa125cdc8685f Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 17 Jun 2015 05:37:20 -0700 Subject: [PATCH 07/64] Better text background behavior --- Examples/UIExplorer/TextExample.ios.js | 87 ++++++++---------- .../testTextExampleSnapshot_1@2x.png | Bin 275037 -> 270817 bytes Libraries/Text/RCTShadowText.h | 1 - Libraries/Text/RCTShadowText.m | 17 +++- Libraries/Text/RCTTextManager.m | 7 -- Libraries/Text/Text.js | 2 +- Libraries/Text/TextStylePropTypes.js | 3 +- 7 files changed, 52 insertions(+), 65 deletions(-) diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js index ebc67b672..5abfae323 100644 --- a/Examples/UIExplorer/TextExample.ios.js +++ b/Examples/UIExplorer/TextExample.ios.js @@ -25,7 +25,7 @@ var { var Entity = React.createClass({ render: function() { return ( - + {this.props.children} ); @@ -34,7 +34,12 @@ var Entity = React.createClass({ var AttributeToggler = React.createClass({ getInitialState: function() { - return {fontWeight: '500', fontSize: 15}; + return {fontWeight: 'bold', fontSize: 15}; + }, + toggleWeight: function() { + this.setState({ + fontWeight: this.state.fontWeight === 'bold' ? 'normal' : 'bold' + }); }, increaseSize: function() { this.setState({ @@ -42,22 +47,26 @@ var AttributeToggler = React.createClass({ }); }, render: function() { - var curStyle = {fontSize: this.state.fontSize}; + var curStyle = {fontWeight: this.state.fontWeight, fontSize: this.state.fontSize}; return ( - + Tap the controls below to change attributes. - See how it will even work on{' '} - - this nested text - - - {'>> Increase Size <<'} - + See how it will even work on this nested text - + + Toggle Weight + + + Increase Size + + ); } }); @@ -206,6 +215,12 @@ exports.examples = [ render: function() { return ( + + auto (default) - english LTR + + + أحب اللغة العربية auto (default) - arabic RTL + left left left left left left left left left left left left left left left @@ -282,43 +297,21 @@ exports.examples = [ description: 'backgroundColor is inherited from all types of views.', render: function() { return ( - - - Yellow background inherited from View parent, - - {' '}red background, - - {' '}blue background, - - {' '}inherited blue background, - - {' '}nested green background. - + + Yellow container background, + + {' '}red background, + + {' '}blue background, + + {' '}inherited blue background, + + {' '}nested green background. - - ); - }, -}, { - title: 'containerBackgroundColor attribute', - render: function() { - return ( - - - - - - - Default containerBackgroundColor (inherited) + backgroundColor wash - - - {"containerBackgroundColor: 'transparent' + backgroundColor wash"} - - + ); }, }, { @@ -346,8 +339,4 @@ var styles = StyleSheet.create({ marginBottom: 0, backgroundColor: 'rgba(100, 100, 100, 0.3)' }, - entity: { - fontWeight: '500', - color: '#527fe4', - }, }); diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExampleSnapshot_1@2x.png index f37869de39ea06dcd44dfa79b28d57efcbb5979c..1e764f3c84e65d9b0f13933a7b3067f85274bc1a 100644 GIT binary patch delta 162044 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 b2PZG!ZU)%Y>8t_tG^C=Wp;&O=Ea-m#ZzTmX delta 167990 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

)EQ&a1yCg1PDdJfx-aKHLFQF=9quzXzPF5bhIz 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 diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index d156bb4d6..4343110bf 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -25,7 +25,6 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, assign) NSUInteger numberOfLines; @property (nonatomic, assign) CGSize shadowOffset; @property (nonatomic, assign) NSTextAlignment textAlign; -@property (nonatomic, strong) UIColor *textBackgroundColor; @property (nonatomic, assign) NSWritingDirection writingDirection; - (void)recomputeText; diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 65bee774e..e97024928 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -131,7 +131,8 @@ static css_dim_t RCTMeasure(void *context, float width) fontSize:nil fontWeight:nil fontStyle:nil - letterSpacing:nil]; + letterSpacing:nil + useBackgroundColor:NO]; } - (NSAttributedString *)_attributedStringWithFontFamily:(NSString *)fontFamily @@ -139,6 +140,7 @@ static css_dim_t RCTMeasure(void *context, float width) fontWeight:(NSString *)fontWeight fontStyle:(NSString *)fontStyle letterSpacing:(NSNumber *)letterSpacing + useBackgroundColor:(BOOL)useBackgroundColor { if (![self isTextDirty] && _cachedAttributedString) { return _cachedAttributedString; @@ -166,7 +168,7 @@ static css_dim_t RCTMeasure(void *context, float width) for (RCTShadowView *child in [self reactSubviews]) { if ([child isKindOfClass:[RCTShadowText class]]) { RCTShadowText *shadowText = (RCTShadowText *)child; - [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing]]; + [attributedString appendAttributedString:[shadowText _attributedStringWithFontFamily:fontFamily fontSize:fontSize fontWeight:fontWeight fontStyle:fontStyle letterSpacing:letterSpacing useBackgroundColor:YES]]; } else if ([child isKindOfClass:[RCTShadowRawText class]]) { RCTShadowRawText *shadowRawText = (RCTShadowRawText *)child; [attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:[shadowRawText text] ?: @""]]; @@ -183,8 +185,8 @@ static css_dim_t RCTMeasure(void *context, float width) if (_isHighlighted) { [self _addAttribute:RCTIsHighlightedAttributeName withValue:@YES toAttributedString:attributedString]; } - if (_textBackgroundColor) { - [self _addAttribute:NSBackgroundColorAttributeName withValue:_textBackgroundColor toAttributedString:attributedString]; + if (useBackgroundColor && self.backgroundColor) { + [self _addAttribute:NSBackgroundColorAttributeName withValue:self.backgroundColor toAttributedString:attributedString]; } UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle]; @@ -271,6 +273,12 @@ static css_dim_t RCTMeasure(void *context, float width) [self cssNode]->children_count = 0; } +- (void)setBackgroundColor:(UIColor *)backgroundColor +{ + super.backgroundColor = backgroundColor; + [self dirtyText]; +} + #define RCT_TEXT_PROPERTY(setProp, ivar, type) \ - (void)set##setProp:(type)value; \ { \ @@ -289,7 +297,6 @@ RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat) RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger) RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize) RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment) -RCT_TEXT_PROPERTY(TextBackgroundColor, _textBackgroundColor, UIColor *) RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection) @end diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index 26c6329e2..3d2c73759 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -32,11 +32,6 @@ RCT_EXPORT_MODULE() return [[RCTShadowText alloc] init]; } -#pragma mark - View properties - -RCT_IGNORE_VIEW_PROPERTY(backgroundColor); -RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor) - #pragma mark - Shadow properties RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection) @@ -50,8 +45,6 @@ RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize) RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment) -RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor) -RCT_REMAP_SHADOW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor) RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger) - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js index 7ff16c5d9..d02733749 100644 --- a/Libraries/Text/Text.js +++ b/Libraries/Text/Text.js @@ -97,7 +97,7 @@ var Text = React.createClass({ /** * Invoked on mount and layout changes with * - * {nativeEvent: { layout: {x, y, width, height}}}. + * {nativeEvent: {layout: {x, y, width, height}}}. */ onLayout: React.PropTypes.func, }, diff --git a/Libraries/Text/TextStylePropTypes.js b/Libraries/Text/TextStylePropTypes.js index 450d26f33..b90367509 100644 --- a/Libraries/Text/TextStylePropTypes.js +++ b/Libraries/Text/TextStylePropTypes.js @@ -25,8 +25,7 @@ var TextStylePropTypes = Object.assign(Object.create(ViewStylePropTypes), { fontStyle: ReactPropTypes.oneOf(['normal', 'italic']), lineHeight: ReactPropTypes.number, color: ReactPropTypes.string, - containerBackgroundColor: ReactPropTypes.string, - // NOTE: "justify" is supported only on iOS + // NOTE: 'justify is supported only on iOS textAlign: ReactPropTypes.oneOf( ['auto' /*default*/, 'left', 'right', 'center', 'justify'] ), From 1718b17a372305459c5ae07a1080284fcc08fa7a Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 17 Jun 2015 05:56:46 -0700 Subject: [PATCH 08/64] [ReactNative] Fix extern on RCTProfile.h Summary: @public Add missing `RCT_EXTERN` to the constants in `RCTProfile`'s header Test Plan: Move the the import on `RCTBridge` to the header, build no longer fails due to duplicate symbols. --- React/Base/RCTProfile.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 2718871d2..469a81552 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -20,8 +20,8 @@ * before before using it. */ -NSString *const RCTProfileDidStartProfiling; -NSString *const RCTProfileDidEndProfiling; +RCT_EXTERN NSString *const RCTProfileDidStartProfiling; +RCT_EXTERN NSString *const RCTProfileDidEndProfiling; #if RCT_DEV From 6573d256ba6cdc71ca6546267cf000ee2cc16cb9 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 17 Jun 2015 07:09:23 -0700 Subject: [PATCH 09/64] Improve test architecture so failures don't crash the simulator --- .../IntegrationTestsTests.m | 14 ++--- .../UIExplorerIntegrationTests.m | 54 ------------------- .../js/PromiseTest.js | 4 +- .../js/SimpleSnapshotTest.js | 4 +- Examples/UIExplorer/UIExplorerList.js | 2 +- Libraries/RCTTest/RCTTestModule.h | 10 +++- Libraries/RCTTest/RCTTestModule.m | 22 ++++---- Libraries/RCTTest/RCTTestRunner.m | 11 ++-- 8 files changed, 31 insertions(+), 90 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m index 9783fca38..b69e9a78b 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m @@ -32,10 +32,6 @@ NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion); _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerIntegrationTests/js/IntegrationTestsApp"); - - // If tests have changes, set recordMode = YES below and run the affected - // tests on an iPhone5, iOS 8.3 simulator. - _runner.recordMode = NO; } #pragma mark Logic Tests @@ -53,8 +49,7 @@ expectErrorBlock:nil]; } -// TODO: this seems to stall forever - figure out why -- (void)DISABLED_testTheTester_ExpectError +- (void)testTheTester_ExpectError { [_runner runTest:_cmd module:@"IntegrationTestHarnessTest" @@ -91,12 +86,9 @@ - (void)testSimpleSnapshot { + // If tests have changes, set recordMode = YES below and re-run + _runner.recordMode = NO; [_runner runTest:_cmd module:@"SimpleSnapshotTest"]; } -- (void)testZZZ_NotInRecordMode -{ - RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); -} - @end diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m index 8ee424425..869c32963 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/UIExplorerIntegrationTests.m @@ -21,8 +21,6 @@ #import "RCTRedBox.h" #import "RCTRootView.h" -#define TIMEOUT_SECONDS 240 - @interface UIExplorerTests : XCTestCase { RCTTestRunner *_runner; @@ -40,52 +38,6 @@ NSString *version = [[UIDevice currentDevice] systemVersion]; RCTAssert([version isEqualToString:@"8.3"], @"Snapshot tests should be run on iOS 8.3, found %@", version); _runner = RCTInitRunnerForApp(@"Examples/UIExplorer/UIExplorerApp.ios"); - - // If tests have changes, set recordMode = YES below and run the affected - // tests on an iPhone5, iOS 8.3 simulator. - _runner.recordMode = NO; -} - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -// Make sure this test runs first because the other tests will tear out the rootView -- (void)testAAA_RootViewLoadsAndRenders -{ - // TODO (t7296305) Fix and Re-Enable this UIExplorer Test - return; - - UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; - RCTAssert([vc.view isKindOfClass:[RCTRootView class]], @"This test must run first."); - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - NSString *redboxError = nil; - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - redboxError = [[RCTRedBox sharedInstance] currentErrorMessage]; - foundElement = [self findSubviewInView:vc.view matching:^(UIView *view) { - if ([view.accessibilityLabel isEqualToString:@""]) { - return YES; - } - return NO; - }]; - } - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with '' text in %d seconds", TIMEOUT_SECONDS); } #define RCT_SNAPSHOT_TEST(name, reRecord) \ @@ -102,10 +54,4 @@ RCT_SNAPSHOT_TEST(SwitchExample, NO) RCT_SNAPSHOT_TEST(SliderExample, NO) RCT_SNAPSHOT_TEST(TabBarExample, NO) -// Make sure this test runs last -- (void)testZZZ_NotInRecordMode -{ - RCTAssert(_runner.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); -} - @end diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js index 38660d3d8..3bcc12994 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/PromiseTest.js @@ -22,7 +22,7 @@ var PromiseTest = React.createClass({ Promise.all([ this.testShouldResolve(), this.testShouldReject(), - ]).then(() => RCTTestModule.finish( + ]).then(() => RCTTestModule.markTestPassed( this.shouldResolve && this.shouldReject )); }, @@ -42,7 +42,7 @@ var PromiseTest = React.createClass({ }, render() { - return ; + return ; } }); diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js index 1715f093f..c57700a0c 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/SimpleSnapshotTest.js @@ -24,8 +24,8 @@ var SimpleSnapshotTest = React.createClass({ requestAnimationFrame(() => TestModule.verifySnapshot(this.done)); }, - done() { - TestModule.markTestCompleted(); + done(success) { + TestModule.markTestPassed(success); }, render() { diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index a8050b33b..5af5a32c6 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -115,7 +115,7 @@ COMPONENTS.concat(APIS).forEach((Example) => { // View is still blank after first RAF :\ global.requestAnimationFrame(() => global.requestAnimationFrame(() => TestModule.verifySnapshot( - TestModule.markTestCompleted + TestModule.markTestPassed ) )); }, diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h index f248cbfca..5ea69dcb6 100644 --- a/Libraries/RCTTest/RCTTestModule.h +++ b/Libraries/RCTTest/RCTTestModule.h @@ -12,6 +12,12 @@ #import "RCTBridgeModule.h" #import "RCTDefines.h" +typedef NS_ENUM(NSInteger, RCTTestStatus) { + RCTTestStatusPending = 0, + RCTTestStatusPassed, + RCTTestStatusFailed +}; + @class FBSnapshotTestController; @interface RCTTestModule : NSObject @@ -32,8 +38,8 @@ @property (nonatomic, assign) SEL testSelector; /** - * This is typically polled while running the runloop until true. + * This is polled while running the runloop until true. */ -@property (nonatomic, readonly, getter=isDone) BOOL done; +@property (nonatomic, readonly) RCTTestStatus status; @end diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m index f7d504b06..54c44513e 100644 --- a/Libraries/RCTTest/RCTTestModule.m +++ b/Libraries/RCTTest/RCTTestModule.m @@ -51,16 +51,7 @@ RCT_EXPORT_METHOD(verifySnapshot:(RCTResponseSenderBlock)callback) selector:_testSelector identifier:_snapshotCounter[testName] error:&error]; - - RCTAssert(success, @"Snapshot comparison failed: %@", error); - callback(@[]); - }]; -} - -RCT_EXPORT_METHOD(markTestCompleted) -{ - [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - _done = YES; + callback(@[@(success)]); }]; } @@ -79,11 +70,16 @@ RCT_REMAP_METHOD(shouldReject, shouldReject_resolve:(RCTPromiseResolveBlock)reso reject(nil); } -RCT_EXPORT_METHOD(finish:(BOOL)success) +RCT_EXPORT_METHOD(markTestCompleted) { - RCTAssert(success, @"RCTTestModule finished without success"); - [self markTestCompleted]; + [self markTestPassed:YES]; } +RCT_EXPORT_METHOD(markTestPassed:(BOOL)success) +{ + [_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + _status = success ? RCTTestStatusPassed : RCTTestStatusFailed; + }]; +} @end diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 11c57e0ba..0ab8c0555 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -16,7 +16,7 @@ #import "RCTTestModule.h" #import "RCTUtils.h" -#define TIMEOUT_SECONDS 240 +#define TIMEOUT_SECONDS 60 @interface RCTBridge (RCTTestRunner) @@ -93,7 +93,7 @@ RCT_NOT_IMPLEMENTED(-init) NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; - while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) { + while ([date timeIntervalSinceNow] > 0 && testModule.status == RCTTestStatusPending && error == nil) { [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; error = [[RCTRedBox sharedInstance] currentErrorMessage]; @@ -104,11 +104,12 @@ RCT_NOT_IMPLEMENTED(-init) [[RCTRedBox sharedInstance] dismiss]; if (expectErrorBlock) { RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched."); - } else if (error) { - RCTAssert(error == nil, @"RedBox error: %@", error); } else { - RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS); + RCTAssert(error == nil, @"RedBox error: %@", error); + RCTAssert(testModule.status != RCTTestStatusPending, @"Test didn't finish within %d seconds", TIMEOUT_SECONDS); + RCTAssert(testModule.status == RCTTestStatusPassed, @"Test failed"); } + RCTAssert(self.recordMode == NO, @"Don't forget to turn record mode back to NO before commit."); } @end From 92d98533f1d41095f7106f4e0e80bee1bb532b18 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 17 Jun 2015 07:51:48 -0700 Subject: [PATCH 10/64] [ReactNative] Refactor BatchedBridge and MessageQueue Summary: @public The current implementation of `MessageQueue` is huge, over-complicated and spread across `MethodQueue`, `MethodQueueMixin`, `BatchedBridge` and `BatchedBridgeFactory` Refactored in a simpler way, were it's just a `MessageQueue` class and `BatchedBridge` is only an instance of it. Test Plan: I had to make some updates to the tests, but no real update to the native side. There's also tests covering the `remoteAsync` methods, and more integration tests for UIExplorer. Verified whats being used by Android, and it should be safe, also tests Android tests have been pretty reliable. Manually testing: Create a big hierarchy, like `` example. Use the `TimerMixin` example to generate multiple calls. Test the failure callback on the `Geolocation` example. All the calls go through this entry point, so it's hard to miss if it's broken. --- Libraries/BatchedBridge/BatchedBridge.js | 20 + .../BatchingImplementation/BatchedBridge.js | 37 - .../BatchedBridgeFactory.js | 116 --- Libraries/Utilities/BridgeProfiling.js | 4 +- Libraries/Utilities/MessageQueue.js | 752 ++++++------------ .../Utilities/__tests__/MessageQueue-test.js | 142 ++++ React/Base/RCTBridge.m | 11 - packager/debugger.html | 5 +- 8 files changed, 394 insertions(+), 693 deletions(-) create mode 100644 Libraries/BatchedBridge/BatchedBridge.js delete mode 100644 Libraries/BatchedBridge/BatchingImplementation/BatchedBridge.js delete mode 100644 Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js create mode 100644 Libraries/Utilities/__tests__/MessageQueue-test.js diff --git a/Libraries/BatchedBridge/BatchedBridge.js b/Libraries/BatchedBridge/BatchedBridge.js new file mode 100644 index 000000000..ea49b202f --- /dev/null +++ b/Libraries/BatchedBridge/BatchedBridge.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule BatchedBridge + */ +'use strict'; + +let MessageQueue = require('MessageQueue'); + +let BatchedBridge = new MessageQueue( + __fbBatchedBridgeConfig.remoteModuleConfig, + __fbBatchedBridgeConfig.localModulesConfig, +); + +module.exports = BatchedBridge; diff --git a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridge.js b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridge.js deleted file mode 100644 index 249e27e76..000000000 --- a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridge.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule BatchedBridge - */ -'use strict'; - -var BatchedBridgeFactory = require('BatchedBridgeFactory'); -var MessageQueue = require('MessageQueue'); - -/** - * Signature that matches the native IOS modules/methods that are exposed. We - * indicate which ones accept a callback. The order of modules and methods - * within them implicitly define their numerical *ID* that will be used to - * describe method calls across the wire. This is so that memory is used - * efficiently and we do not need to copy strings in native land - or across any - * wire. - */ - -var remoteModulesConfig = __fbBatchedBridgeConfig.remoteModuleConfig; -var localModulesConfig = __fbBatchedBridgeConfig.localModulesConfig; - - -var BatchedBridge = BatchedBridgeFactory.create( - MessageQueue, - remoteModulesConfig, - localModulesConfig -); - -BatchedBridge._config = remoteModulesConfig; - -module.exports = BatchedBridge; diff --git a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js b/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js deleted file mode 100644 index 3243fb145..000000000 --- a/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule BatchedBridgeFactory - */ -'use strict'; - -var invariant = require('invariant'); -var keyMirror = require('keyMirror'); -var mapObject = require('mapObject'); -var warning = require('warning'); - -var slice = Array.prototype.slice; - -var MethodTypes = keyMirror({ - remote: null, - remoteAsync: null, - local: null, -}); - -type ErrorData = { - message: string; - domain: string; - code: number; - nativeStackIOS?: string; -}; - -/** - * Creates remotely invokable modules. - */ -var BatchedBridgeFactory = { - MethodTypes: MethodTypes, - /** - * @param {MessageQueue} messageQueue Message queue that has been created with - * the `moduleConfig` (among others perhaps). - * @param {object} moduleConfig Configuration of module names/method - * names to callback types. - * @return {object} Remote representation of configured module. - */ - _createBridgedModule: function(messageQueue, moduleConfig, moduleName) { - var remoteModule = mapObject(moduleConfig.methods, function(methodConfig, memberName) { - switch (methodConfig.type) { - case MethodTypes.remoteAsync: - return function(...args) { - return new Promise((resolve, reject) => { - messageQueue.call(moduleName, memberName, args, resolve, (errorData) => { - var error = _createErrorFromErrorData(errorData); - reject(error); - }); - }); - }; - - case MethodTypes.local: - return null; - - default: - return function() { - var lastArg = arguments.length > 0 ? arguments[arguments.length - 1] : null; - var secondLastArg = arguments.length > 1 ? arguments[arguments.length - 2] : null; - var hasSuccCB = typeof lastArg === 'function'; - var hasErrorCB = typeof secondLastArg === 'function'; - hasErrorCB && invariant( - hasSuccCB, - 'Cannot have a non-function arg after a function arg.' - ); - var numCBs = (hasSuccCB ? 1 : 0) + (hasErrorCB ? 1 : 0); - var args = slice.call(arguments, 0, arguments.length - numCBs); - var onSucc = hasSuccCB ? lastArg : null; - var onFail = hasErrorCB ? secondLastArg : null; - return messageQueue.call(moduleName, memberName, args, onFail, onSucc); - }; - } - }); - for (var constName in moduleConfig.constants) { - warning(!remoteModule[constName], 'saw constant and method named %s', constName); - remoteModule[constName] = moduleConfig.constants[constName]; - } - return remoteModule; - }, - - create: function(MessageQueue, modulesConfig, localModulesConfig) { - var messageQueue = new MessageQueue(modulesConfig, localModulesConfig); - return { - callFunction: messageQueue.callFunction.bind(messageQueue), - callFunctionReturnFlushedQueue: - messageQueue.callFunctionReturnFlushedQueue.bind(messageQueue), - invokeCallback: messageQueue.invokeCallback.bind(messageQueue), - invokeCallbackAndReturnFlushedQueue: - messageQueue.invokeCallbackAndReturnFlushedQueue.bind(messageQueue), - flushedQueue: messageQueue.flushedQueue.bind(messageQueue), - RemoteModules: mapObject(modulesConfig, this._createBridgedModule.bind(this, messageQueue)), - setLoggingEnabled: messageQueue.setLoggingEnabled.bind(messageQueue), - getLoggedOutgoingItems: messageQueue.getLoggedOutgoingItems.bind(messageQueue), - getLoggedIncomingItems: messageQueue.getLoggedIncomingItems.bind(messageQueue), - replayPreviousLog: messageQueue.replayPreviousLog.bind(messageQueue), - processBatch: messageQueue.processBatch.bind(messageQueue), - }; - } -}; - -function _createErrorFromErrorData(errorData: ErrorData): Error { - var { - message, - ...extraErrorInfo, - } = errorData; - var error = new Error(message); - error.framesToPop = 1; - return Object.assign(error, extraErrorInfo); -} - -module.exports = BatchedBridgeFactory; diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 02685e01c..1b800901f 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -14,7 +14,7 @@ var GLOBAL = GLOBAL || this; var BridgeProfiling = { - profile(profileName?: string, args?: any) { + profile(profileName?: any, args?: any) { if (GLOBAL.__BridgeProfilingIsProfiling) { if (args) { try { @@ -23,6 +23,8 @@ var BridgeProfiling = { args = err.message; } } + profileName = typeof profileName === 'function' ? + profileName() : profileName; console.profile(profileName, args); } }, diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index cac48a8e5..589ee5e9a 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -7,541 +7,241 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule MessageQueue - * @flow */ + +/*eslint no-bitwise: 0*/ + 'use strict'; -var ErrorUtils = require('ErrorUtils'); -var ReactUpdates = require('ReactUpdates'); +let BridgeProfiling = require('BridgeProfiling'); +let ErrorUtils = require('ErrorUtils'); +let JSTimersExecution = require('JSTimersExecution'); +let ReactUpdates = require('ReactUpdates'); -var invariant = require('invariant'); -var warning = require('warning'); +let invariant = require('invariant'); +let keyMirror = require('keyMirror'); +let stringifySafe = require('stringifySafe'); -var BridgeProfiling = require('BridgeProfiling'); -var JSTimersExecution = require('JSTimersExecution'); +let MODULE_IDS = 0; +let METHOD_IDS = 1; +let PARAMS = 2; -var INTERNAL_ERROR = 'Error in MessageQueue implementation'; +let MethodTypes = keyMirror({ + local: null, + remote: null, + remoteAsync: null, +}); -// Prints all bridge traffic to console.log -var DEBUG_SPY_MODE = false; - -type ModulesConfig = { - [key:string]: { - moduleID: number; - methods: {[key:string]: { - methodID: number; - }}; +var guard = (fn) => { + try { + fn(); + } catch (error) { + ErrorUtils.reportFatalError(error); } +}; + +class MessageQueue { + + constructor(remoteModules, localModules, customRequire) { + this.RemoteModules = {}; + + this._require = customRequire || require; + this._queue = [[],[],[]]; + this._moduleTable = {}; + this._methodTable = {}; + this._callbacks = []; + this._callbackID = 0; + + [ + 'processBatch', + 'invokeCallbackAndReturnFlushedQueue', + 'callFunctionReturnFlushedQueue', + 'flushedQueue', + ].forEach((fn) => this[fn] = this[fn].bind(this)); + + this._genModules(remoteModules); + localModules && this._genLookupTables( + localModules, this._moduleTable, this._methodTable); + + if (__DEV__) { + this._debugInfo = {}; + this._remoteModuleTable = {}; + this._remoteMethodTable = {}; + this._genLookupTables( + remoteModules, this._remoteModuleTable, this._remoteMethodTable); + } + } + + /** + * Public APIs + */ + processBatch(batch) { + ReactUpdates.batchedUpdates(() => { + batch.forEach((call) => { + let method = call.method === 'callFunctionReturnFlushedQueue' ? + '__callFunction' : '__invokeCallback'; + guard(() => this[method].apply(this, call.args)); + }); + BridgeProfiling.profile('ReactUpdates.batchedUpdates()'); + }); + BridgeProfiling.profileEnd(); + return this.flushedQueue(); + } + + callFunctionReturnFlushedQueue(module, method, args) { + guard(() => this.__callFunction(module, method, args)); + return this.flushedQueue(); + } + + invokeCallbackAndReturnFlushedQueue(cbID, args) { + guard(() => this.__invokeCallback(cbID, args)); + return this.flushedQueue(); + } + + flushedQueue() { + BridgeProfiling.profile('JSTimersExecution.callImmediates()'); + guard(() => JSTimersExecution.callImmediates()); + BridgeProfiling.profileEnd(); + let queue = this._queue; + this._queue = [[],[],[]]; + return queue[0].length ? queue : null; + } + + /** + * "Private" methods + */ + __nativeCall(module, method, params, onFail, onSucc) { + if (onFail || onSucc) { + if (__DEV__) { + // eventually delete old debug info + (this._callbackID > (1 << 5)) && + (this._debugInfo[this._callbackID >> 5] = null); + + this._debugInfo[this._callbackID >> 1] = [module, method]; + } + onFail && params.push(this._callbackID); + this._callbacks[this._callbackID++] = onFail; + onSucc && params.push(this._callbackID); + this._callbacks[this._callbackID++] = onSucc; + } + this._queue[MODULE_IDS].push(module); + this._queue[METHOD_IDS].push(method); + this._queue[PARAMS].push(params); + } + + __callFunction(module, method, args) { + BridgeProfiling.profile(() => `${module}.${method}(${stringifySafe(args)})`); + if (isFinite(module)) { + method = this._methodTable[module][method]; + module = this._moduleTable[module]; + } + module = this._require(module); + module[method].apply(module, args); + BridgeProfiling.profileEnd(); + } + + __invokeCallback(cbID, args) { + BridgeProfiling.profile( + () => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`); + let callback = this._callbacks[cbID]; + if (__DEV__ && !callback) { + let debug = this._debugInfo[cbID >> 1]; + let module = this._remoteModuleTable[debug[0]]; + let method = this._remoteMethodTable[debug[0]][debug[1]]; + console.error(`Callback with id ${cbID}: ${module}.${method}() not found`); + } + this._callbacks[cbID & ~1] = null; + this._callbacks[cbID | 1] = null; + callback.apply(null, args); + BridgeProfiling.profileEnd(); + } + + /** + * Private helper methods + */ + _genLookupTables(localModules, moduleTable, methodTable) { + let moduleNames = Object.keys(localModules); + for (var i = 0, l = moduleNames.length; i < l; i++) { + let moduleName = moduleNames[i]; + let methods = localModules[moduleName].methods; + let moduleID = localModules[moduleName].moduleID; + moduleTable[moduleID] = moduleName; + methodTable[moduleID] = {}; + + let methodNames = Object.keys(methods); + for (var j = 0, k = methodNames.length; j < k; j++) { + let methodName = methodNames[j]; + let methodConfig = methods[methodName]; + methodTable[moduleID][methodConfig.methodID] = methodName; + } + } + } + + _genModules(remoteModules) { + let moduleNames = Object.keys(remoteModules); + for (var i = 0, l = moduleNames.length; i < l; i++) { + let moduleName = moduleNames[i]; + let moduleConfig = remoteModules[moduleName]; + this.RemoteModules[moduleName] = this._genModule({}, moduleConfig); + } + } + + _genModule(module, moduleConfig) { + let methodNames = Object.keys(moduleConfig.methods); + for (var i = 0, l = methodNames.length; i < l; i++) { + let methodName = methodNames[i]; + let methodConfig = moduleConfig.methods[methodName]; + module[methodName] = this._genMethod( + moduleConfig.moduleID, methodConfig.methodID, methodConfig.type); + } + Object.assign(module, moduleConfig.constants); + return module; + } + + _genMethod(module, method, type) { + if (type === MethodTypes.local) { + return null; + } + + let self = this; + if (type === MethodTypes.remoteAsync) { + return function(...args) { + return new Promise((resolve, reject) => { + self.__nativeCall(module, method, args, resolve, (errorData) => { + var error = createErrorFromErrorData(errorData); + reject(error); + }); + }); + }; + } else { + return function(...args) { + let lastArg = args.length > 0 ? args[args.length - 1] : null; + let secondLastArg = args.length > 1 ? args[args.length - 2] : null; + let hasSuccCB = typeof lastArg === 'function'; + let hasErrorCB = typeof secondLastArg === 'function'; + hasErrorCB && invariant( + hasSuccCB, + 'Cannot have a non-function arg after a function arg.' + ); + let numCBs = hasSuccCB + hasErrorCB; + let onSucc = hasSuccCB ? lastArg : null; + let onFail = hasErrorCB ? secondLastArg : null; + args = args.slice(0, args.length - numCBs); + return self.__nativeCall(module, method, args, onFail, onSucc); + }; + } + } + } -type NameToID = {[key:string]: number} -type IDToName = {[key:number]: string} +function createErrorFromErrorData(errorData: ErrorData): Error { + var { + message, + ...extraErrorInfo, + } = errorData; + var error = new Error(message); + error.framesToPop = 1; + return Object.assign(error, extraErrorInfo); +} -/** - * So as not to confuse static build system. - */ -var requireFunc = require; - -/** - * @param {Object!} module Module instance, must be loaded. - * @param {string} methodName Name of method in `module`. - * @param {array<*>} params Arguments to method. - * @returns {*} Return value of method invocation. - */ -var jsCall = function(module, methodName, params) { - return module[methodName].apply(module, params); -}; - -/** - * A utility for aggregating "work" to be done, and potentially transferring - * that work to another thread. Each instance of `MessageQueue` has the notion - * of a "target" thread - the thread that the work will be sent to. - * - * TODO: Long running callback results, and streaming callback results (ability - * for a callback to be invoked multiple times). - * - * @param {object} moduleNameToID Used to translate module/method names into - * efficient numeric IDs. - * @class MessageQueue - */ -var MessageQueue = function( - remoteModulesConfig: ModulesConfig, - localModulesConfig: ModulesConfig, - customRequire: (id: string) => any -) { - this._requireFunc = customRequire || requireFunc; - this._initBookeeping(); - this._initNamingMap(remoteModulesConfig, localModulesConfig); -}; - -// REQUEST: Parallell arrays: -var REQUEST_MODULE_IDS = 0; -var REQUEST_METHOD_IDS = 1; -var REQUEST_PARAMSS = 2; -// RESPONSE: Parallell arrays: -var RESPONSE_CBIDS = 3; -var RESPONSE_RETURN_VALUES = 4; - -var applyWithErrorReporter = function(fun: Function, context: ?any, args: ?any) { - try { - return fun.apply(context, args); - } catch (e) { - ErrorUtils.reportFatalError(e); - } -}; - -/** - * Utility to catch errors and prevent having to bind, or execute a bound - * function, while catching errors in a process and returning a resulting - * return value. This ensures that even if a process fails, we can still return - * *some* values (from `_flushedQueueUnguarded` for example). Glorified - * try/catch/finally that invokes the global `onerror`. - * - * @param {function} operation Function to execute, likely populates the - * message buffer. - * @param {Array<*>} operationArguments Arguments passed to `operation`. - * @param {function} getReturnValue Returns a return value - will be invoked - * even if the `operation` fails half way through completing its task. - * @return {object} Return value returned from `getReturnValue`. - */ -var guardReturn = function(operation, operationArguments, getReturnValue, context) { - if (operation) { - applyWithErrorReporter(operation, context, operationArguments); - } - if (getReturnValue) { - return applyWithErrorReporter(getReturnValue, context, null); - } - return null; -}; - -/** - * Bookkeeping logic for callbackIDs. We ensure that success and error - * callbacks are numerically adjacent. - * - * We could have also stored the association between success cbID and errorCBID - * in a map without relying on this adjacency, but the bookkeeping here avoids - * an additional two maps to associate in each direction, and avoids growing - * dictionaries (new fields). Instead, we compute pairs of callback IDs, by - * populating the `res` argument to `allocateCallbackIDs` (in conjunction with - * pooling). Behind this bookeeping API, we ensure that error and success - * callback IDs are always adjacent so that when one is invoked, we always know - * how to free the memory of the other. By using this API, it is impossible to - * create malformed callbackIDs that are not adjacent. - */ -var createBookkeeping = function() { - return { - /** - * Incrementing callback ID. Must start at 1 - otherwise converted null - * values which become zero are not distinguishable from a GUID of zero. - */ - GUID: 1, - errorCallbackIDForSuccessCallbackID: function(successID) { - return successID + 1; - }, - successCallbackIDForErrorCallbackID: function(errorID) { - return errorID - 1; - }, - allocateCallbackIDs: function(res) { - res.successCallbackID = this.GUID++; - res.errorCallbackID = this.GUID++; - }, - isSuccessCallback: function(id) { - return id % 2 === 1; - } - }; -}; - -var MessageQueueMixin = { - /** - * Creates an efficient wire protocol for communicating across a bridge. - * Avoids allocating strings. - * - * @param {object} remoteModulesConfig Configuration of modules and their - * methods. - */ - _initNamingMap: function( - remoteModulesConfig: ModulesConfig, - localModulesConfig: ModulesConfig - ) { - this._remoteModuleNameToModuleID = {}; - this._remoteModuleIDToModuleName = {}; // Reverse - - this._remoteModuleNameToMethodNameToID = {}; - this._remoteModuleNameToMethodIDToName = {}; // Reverse - - this._localModuleNameToModuleID = {}; - this._localModuleIDToModuleName = {}; // Reverse - - this._localModuleNameToMethodNameToID = {}; - this._localModuleNameToMethodIDToName = {}; // Reverse - - function fillMappings( - modulesConfig: ModulesConfig, - moduleNameToModuleID: NameToID, - moduleIDToModuleName: IDToName, - moduleNameToMethodNameToID: {[key:string]: NameToID}, - moduleNameToMethodIDToName: {[key:string]: IDToName} - ) { - for (var moduleName in modulesConfig) { - var moduleConfig = modulesConfig[moduleName]; - var moduleID = moduleConfig.moduleID; - moduleNameToModuleID[moduleName] = moduleID; - moduleIDToModuleName[moduleID] = moduleName; // Reverse - - moduleNameToMethodNameToID[moduleName] = {}; - moduleNameToMethodIDToName[moduleName] = {}; // Reverse - var methods = moduleConfig.methods; - for (var methodName in methods) { - var methodID = methods[methodName].methodID; - moduleNameToMethodNameToID[moduleName][methodName] = - methodID; - moduleNameToMethodIDToName[moduleName][methodID] = - methodName; // Reverse - } - } - } - fillMappings( - remoteModulesConfig, - this._remoteModuleNameToModuleID, - this._remoteModuleIDToModuleName, - this._remoteModuleNameToMethodNameToID, - this._remoteModuleNameToMethodIDToName - ); - - fillMappings( - localModulesConfig, - this._localModuleNameToModuleID, - this._localModuleIDToModuleName, - this._localModuleNameToMethodNameToID, - this._localModuleNameToMethodIDToName - ); - - }, - - _initBookeeping: function() { - this._POOLED_CBIDS = {errorCallbackID: null, successCallbackID: null}; - this._bookkeeping = createBookkeeping(); - - /** - * Stores callbacks so that we may simulate asynchronous return values from - * other threads. Remote invocations in other threads can pass return values - * back asynchronously to the requesting thread. - */ - this._threadLocalCallbacksByID = []; - this._threadLocalScopesByID = []; - - /** - * Memory efficient parallel arrays. Each index cuts through the three - * arrays and forms a remote invocation of methodName(params) whos return - * value will be reported back to the other thread by way of the - * corresponding id in cbIDs. Each entry (A-D in the graphic below), - * represents a work item of the following form: - * - moduleID: ID of module to invoke method from. - * - methodID: ID of method in module to invoke. - * - params: List of params to pass to method. - * - cbID: ID to respond back to originating thread with. - * - * TODO: We can make this even more efficient (memory) by creating a single - * array, that is always pushed `n` elements as a time. - */ - this._outgoingItems = [ - /*REQUEST_MODULE_IDS: */ [/* +-+ +-+ +-+ +-+ */], - /*REQUEST_METHOD_IDS: */ [/* |A| |B| |C| |D| */], - /*REQUEST_PARAMSS: */ [/* |-| |-| |-| |-| */], - - /*RESPONSE_CBIDS: */ [/* +-+ +-+ +-+ +-+ */], - /* |E| |F| |G| |H| */ - /*RESPONSE_RETURN_VALUES: */ [/* +-+ +-+ +-+ +-+ */] - ]; - - /** - * Used to allow returning the buffer, while at the same time clearing it in - * a memory efficient manner. - */ - this._outgoingItemsSwap = [[], [], [], [], []]; - }, - - invokeCallback: function(cbID, args) { - return guardReturn(this._invokeCallback, [cbID, args], null, this); - }, - - _invokeCallback: function(cbID, args) { - try { - var cb = this._threadLocalCallbacksByID[cbID]; - var scope = this._threadLocalScopesByID[cbID]; - warning( - cb, - 'Cannot find callback with CBID %s. Native module may have invoked ' + - 'both the success callback and the error callback.', - cbID - ); - if (DEBUG_SPY_MODE) { - console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')'); - } - BridgeProfiling.profile('Callback#' + cbID + '(' + JSON.stringify(args) + ')'); - cb.apply(scope, args); - BridgeProfiling.profileEnd(); - } catch(ie_requires_catch) { - throw ie_requires_catch; - } finally { - // Clear out the memory regardless of success or failure. - this._freeResourcesForCallbackID(cbID); - } - }, - - invokeCallbackAndReturnFlushedQueue: function(cbID, args) { - if (this._enableLogging) { - this._loggedIncomingItems.push([new Date().getTime(), cbID, args]); - } - return guardReturn( - this._invokeCallback, - [cbID, args], - this._flushedQueueUnguarded, - this - ); - }, - - callFunction: function(moduleID, methodID, params) { - return guardReturn(this._callFunction, [moduleID, methodID, params], null, this); - }, - - _callFunction: function(moduleName, methodName, params) { - if (isFinite(moduleName)) { - moduleName = this._localModuleIDToModuleName[moduleName]; - methodName = this._localModuleNameToMethodIDToName[moduleName][methodName]; - } - - if (DEBUG_SPY_MODE) { - console.log( - 'N->JS: ' + moduleName + '.' + methodName + - '(' + JSON.stringify(params) + ')'); - } - BridgeProfiling.profile(moduleName + '.' + methodName + '(' + JSON.stringify(params) + ')'); - var ret = jsCall(this._requireFunc(moduleName), methodName, params); - BridgeProfiling.profileEnd(); - - return ret; - }, - - callFunctionReturnFlushedQueue: function(moduleID, methodID, params) { - if (this._enableLogging) { - this._loggedIncomingItems.push([new Date().getTime(), moduleID, methodID, params]); - } - return guardReturn( - this._callFunction, - [moduleID, methodID, params], - this._flushedQueueUnguarded, - this - ); - }, - - processBatch: function(batch) { - var self = this; - BridgeProfiling.profile('MessageQueue.processBatch()'); - var flushedQueue = guardReturn(function () { - ReactUpdates.batchedUpdates(function() { - batch.forEach(function(call) { - invariant( - call.module === 'BatchedBridge', - 'All the calls should pass through the BatchedBridge module' - ); - if (call.method === 'callFunctionReturnFlushedQueue') { - self._callFunction.apply(self, call.args); - } else if (call.method === 'invokeCallbackAndReturnFlushedQueue') { - self._invokeCallback.apply(self, call.args); - } else { - throw new Error( - 'Unrecognized method called on BatchedBridge: ' + call.method); - } - }); - BridgeProfiling.profile('React.batchedUpdates()'); - }); - BridgeProfiling.profileEnd(); - }, null, this._flushedQueueUnguarded, this); - BridgeProfiling.profileEnd(); - return flushedQueue; - }, - - setLoggingEnabled: function(enabled) { - this._enableLogging = enabled; - this._loggedIncomingItems = []; - this._loggedOutgoingItems = [[], [], [], [], []]; - }, - - getLoggedIncomingItems: function() { - return this._loggedIncomingItems; - }, - - getLoggedOutgoingItems: function() { - return this._loggedOutgoingItems; - }, - - replayPreviousLog: function(previousLog) { - this._outgoingItems = previousLog; - }, - - /** - * Simple helpers for clearing the queues. This doesn't handle the fact that - * memory in the current buffer is leaked until the next frame or update - but - * that will typically be on the order of < 500ms. - */ - _swapAndReinitializeBuffer: function() { - // Outgoing requests - var currentOutgoingItems = this._outgoingItems; - var nextOutgoingItems = this._outgoingItemsSwap; - - nextOutgoingItems[REQUEST_MODULE_IDS].length = 0; - nextOutgoingItems[REQUEST_METHOD_IDS].length = 0; - nextOutgoingItems[REQUEST_PARAMSS].length = 0; - - // Outgoing responses - nextOutgoingItems[RESPONSE_CBIDS].length = 0; - nextOutgoingItems[RESPONSE_RETURN_VALUES].length = 0; - - this._outgoingItemsSwap = currentOutgoingItems; - this._outgoingItems = nextOutgoingItems; - }, - - /** - * @param {string} moduleID JS module name. - * @param {methodName} methodName Method in module to invoke. - * @param {array<*>?} params Array representing arguments to method. - * @param {string} cbID Unique ID to pass back in potential response. - */ - _pushRequestToOutgoingItems: function(moduleID, methodName, params) { - this._outgoingItems[REQUEST_MODULE_IDS].push(moduleID); - this._outgoingItems[REQUEST_METHOD_IDS].push(methodName); - this._outgoingItems[REQUEST_PARAMSS].push(params); - - if (this._enableLogging) { - this._loggedOutgoingItems[REQUEST_MODULE_IDS].push(moduleID); - this._loggedOutgoingItems[REQUEST_METHOD_IDS].push(methodName); - this._loggedOutgoingItems[REQUEST_PARAMSS].push(params); - } - }, - - /** - * @param {string} cbID Unique ID that other side of bridge has remembered. - * @param {*} returnValue Return value to pass to callback on other side of - * bridge. - */ - _pushResponseToOutgoingItems: function(cbID, returnValue) { - this._outgoingItems[RESPONSE_CBIDS].push(cbID); - this._outgoingItems[RESPONSE_RETURN_VALUES].push(returnValue); - }, - - _freeResourcesForCallbackID: function(cbID) { - var correspondingCBID = this._bookkeeping.isSuccessCallback(cbID) ? - this._bookkeeping.errorCallbackIDForSuccessCallbackID(cbID) : - this._bookkeeping.successCallbackIDForErrorCallbackID(cbID); - this._threadLocalCallbacksByID[cbID] = null; - this._threadLocalScopesByID[cbID] = null; - if (this._threadLocalCallbacksByID[correspondingCBID]) { - this._threadLocalCallbacksByID[correspondingCBID] = null; - this._threadLocalScopesByID[correspondingCBID] = null; - } - }, - - /** - * @param {Function} onFail Function to store in current thread for later - * lookup, when request fails. - * @param {Function} onSucc Function to store in current thread for later - * lookup, when request succeeds. - * @param {Object?=} scope Scope to invoke `cb` with. - * @param {Object?=} res Resulting callback ids. Use `this._POOLED_CBIDS`. - */ - _storeCallbacksInCurrentThread: function(onFail, onSucc, scope) { - invariant(onFail || onSucc, INTERNAL_ERROR); - this._bookkeeping.allocateCallbackIDs(this._POOLED_CBIDS); - var succCBID = this._POOLED_CBIDS.successCallbackID; - var errorCBID = this._POOLED_CBIDS.errorCallbackID; - this._threadLocalCallbacksByID[errorCBID] = onFail; - this._threadLocalCallbacksByID[succCBID] = onSucc; - this._threadLocalScopesByID[errorCBID] = scope; - this._threadLocalScopesByID[succCBID] = scope; - }, - - - /** - * IMPORTANT: There is possibly a timing issue with this form of flushing. We - * are currently not seeing any problems but the potential issue to look out - * for is: - * - While flushing this._outgoingItems contains the work for the other thread - * to perform. - * - To mitigate this, we never allow enqueueing messages if the queue is - * already reserved - as long as it is reserved, it could be in the midst of - * a flush. - * - * If this ever occurs we can easily eliminate the race condition. We can - * completely solve any ambiguity by sending messages such that we'll never - * try to reserve the queue when already reserved. Here's the pseudocode: - * - * var defensiveCopy = efficientDefensiveCopy(this._outgoingItems); - * this._swapAndReinitializeBuffer(); - */ - flushedQueue: function() { - return guardReturn(null, null, this._flushedQueueUnguarded, this); - }, - - _flushedQueueUnguarded: function() { - BridgeProfiling.profile('JSTimersExecution.callImmediates()'); - // Call the functions registered via setImmediate - JSTimersExecution.callImmediates(); - BridgeProfiling.profileEnd(); - - var currentOutgoingItems = this._outgoingItems; - this._swapAndReinitializeBuffer(); - var ret = currentOutgoingItems[REQUEST_MODULE_IDS].length || - currentOutgoingItems[RESPONSE_RETURN_VALUES].length ? currentOutgoingItems : null; - - if (DEBUG_SPY_MODE && ret) { - for (var i = 0; i < currentOutgoingItems[0].length; i++) { - var moduleName = this._remoteModuleIDToModuleName[currentOutgoingItems[0][i]]; - var methodName = - this._remoteModuleNameToMethodIDToName[moduleName][currentOutgoingItems[1][i]]; - console.log( - 'JS->N: ' + moduleName + '.' + methodName + - '(' + JSON.stringify(currentOutgoingItems[2][i]) + ')'); - } - } - - return ret; - }, - - call: function(moduleName, methodName, params, onFail, onSucc, scope) { - invariant( - (!onFail || typeof onFail === 'function') && - (!onSucc || typeof onSucc === 'function'), - 'Callbacks must be functions' - ); - // Store callback _before_ sending the request, just in case the MailBox - // returns the response in a blocking manner. - if (onFail || onSucc) { - this._storeCallbacksInCurrentThread(onFail, onSucc, scope, this._POOLED_CBIDS); - onFail && params.push(this._POOLED_CBIDS.errorCallbackID); - onSucc && params.push(this._POOLED_CBIDS.successCallbackID); - } - var moduleID = this._remoteModuleNameToModuleID[moduleName]; - if (moduleID === undefined || moduleID === null) { - throw new Error('Unrecognized module name:' + moduleName); - } - var methodID = this._remoteModuleNameToMethodNameToID[moduleName][methodName]; - if (methodID === undefined || moduleID === null) { - throw new Error('Unrecognized method name:' + methodName); - } - this._pushRequestToOutgoingItems(moduleID, methodID, params); - }, - __numPendingCallbacksOnlyUseMeInTestCases: function() { - var callbacks = this._threadLocalCallbacksByID; - var total = 0; - for (var i = 0; i < callbacks.length; i++) { - if (callbacks[i]) { - total++; - } - } - return total; - } -}; - -Object.assign(MessageQueue.prototype, MessageQueueMixin); module.exports = MessageQueue; diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js new file mode 100644 index 000000000..a8d5fc5bd --- /dev/null +++ b/Libraries/Utilities/__tests__/MessageQueue-test.js @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest.dontMock('MessageQueue'); + +var ReactUpdates = require('ReactUpdates'); +var MessageQueue = require('MessageQueue'); + +let MODULE_IDS = 0; +let METHOD_IDS = 1; +let PARAMS = 2; + +let TestModule = { + testHook1(){}, testHook2(){}, +}; + +let customRequire = (moduleName) => TestModule; + +let assertQueue = (flushedQueue, index, moduleID, methodID, params) => { + expect(flushedQueue[MODULE_IDS][index]).toEqual(moduleID); + expect(flushedQueue[METHOD_IDS][index]).toEqual(methodID); + expect(flushedQueue[PARAMS][index]).toEqual(params); +}; + +var queue; + +describe('MessageQueue', () => { + + beforeEach(() => { + queue = new MessageQueue( + remoteModulesConfig, + localModulesConfig, + customRequire, + ); + + TestModule.testHook1 = jasmine.createSpy(); + TestModule.testHook2 = jasmine.createSpy(); + }); + + it('should enqueue native calls', () => { + queue.__nativeCall(0, 1, [2]); + let flushedQueue = queue.flushedQueue(); + assertQueue(flushedQueue, 0, 0, 1, [2]); + }); + + it('should call a local function with id', () => { + expect(TestModule.testHook1.callCount).toEqual(0); + queue.__callFunction(0, 0, [1]); + expect(TestModule.testHook1.callCount).toEqual(1); + }); + + it('should call a local function with the function name', () => { + expect(TestModule.testHook2.callCount).toEqual(0); + queue.__callFunction('one', 'testHook2', [2]); + expect(TestModule.testHook2.callCount).toEqual(1); + }); + + it('should generate native modules', () => { + queue.RemoteModules.one.remoteMethod1('foo'); + let flushedQueue = queue.flushedQueue(); + assertQueue(flushedQueue, 0, 0, 0, ['foo']); + }); + + it('should store callbacks', () => { + queue.RemoteModules.one.remoteMethod2('foo', () => {}, () => {}); + let flushedQueue = queue.flushedQueue(); + assertQueue(flushedQueue, 0, 0, 1, ['foo', 0, 1]); + }); + + it('should call the stored callback', (done) => { + var done = false; + queue.RemoteModules.one.remoteMethod1(() => { done = true; }); + queue.__invokeCallback(1); + expect(done).toEqual(true); + }); + + it('should throw when calling the same callback twice', () => { + queue.RemoteModules.one.remoteMethod1(() => {}); + queue.__invokeCallback(1); + expect(() => queue.__invokeCallback(1)).toThrow(); + }); + + it('should throw when calling both success and failure callback', () => { + queue.RemoteModules.one.remoteMethod1(() => {}, () => {}); + queue.__invokeCallback(1); + expect(() => queue.__invokeCallback(0)).toThrow(); + }); + + describe('processBatch', () => { + + beforeEach(() => { + ReactUpdates.batchedUpdates = (fn) => fn(); + }); + + it('should call __invokeCallback for invokeCallbackAndReturnFlushedQueue', () => { + queue.__invokeCallback = jasmine.createSpy(); + queue.processBatch([{ + method: 'invokeCallbackAndReturnFlushedQueue', + args: [], + }]); + expect(queue.__invokeCallback.callCount).toEqual(1); + }); + + it('should call __callFunction for callFunctionReturnFlushedQueue', () => { + queue.__callFunction = jasmine.createSpy(); + queue.processBatch([{ + method: 'callFunctionReturnFlushedQueue', + args: [], + }]); + expect(queue.__callFunction.callCount).toEqual(1); + }); + + }); + +}); + +var remoteModulesConfig = { + 'one': { + 'moduleID':0, + 'methods': { + 'remoteMethod1':{ 'type': 'remote', 'methodID': 0 }, + 'remoteMethod2':{ 'type': 'remote', 'methodID': 1 }, + } + }, +}; + +var localModulesConfig = { + 'one': { + 'moduleID': 0, + 'methods': { + 'testHook1':{ 'type': 'local', 'methodID': 0 }, + 'testHook2':{ 'type': 'local', 'methodID': 1 }, + } + }, +}; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index f8e9eeab7..8dce4584b 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -40,9 +40,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) { RCTBridgeFieldRequestModuleIDs = 0, RCTBridgeFieldMethodIDs, RCTBridgeFieldParamss, - RCTBridgeFieldResponseCBIDs, - RCTBridgeFieldResponseReturnValues, - RCTBridgeFieldFlushDateMillis }; typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) { @@ -1258,14 +1255,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module return; } - NSUInteger bufferRowCount = [requestsArray count]; - NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1; - - if (bufferRowCount != expectedFieldsCount) { - RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount); - return; - } - for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) { id field = [requestsArray objectAtIndex:fieldIndex]; if (![field isKindOfClass:[NSArray class]]) { diff --git a/packager/debugger.html b/packager/debugger.html index d72e40ead..0d0084559 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -48,10 +48,11 @@ var messageHandlers = { loadScript(message.url, sendReply.bind(null, null)); }, 'executeJSCall': function(message, sendReply) { - var returnValue = [[], [], [], [], []]; + var returnValue = null; try { if (window && window.require) { - returnValue = window.require(message.moduleName)[message.moduleMethod].apply(null, message.arguments); + var module = window.require(message.moduleName); + returnValue = module[message.moduleMethod].apply(module, message.arguments); } } finally { sendReply(JSON.stringify(returnValue)); From 51e5794cb8d8db85d3a56f956f3920bade75fb8c Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 17 Jun 2015 07:42:16 -0700 Subject: [PATCH 11/64] [ActivityIndicator] Specify a width Summary: The activity indicator was treated as a zero-width element without an explicit width. Fill it in so the style dimensions match what is displayed on the screen. Closes https://github.com/facebook/react-native/pull/1156 Github Author: James Ide Test Plan: Render an ActivityIndicator with a background, and see that the background shows up as a square behind the spinner instead of not showing up at all (since it was 0px wide previously). --- .../Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js index c229b60c3..53390cabe 100644 --- a/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js +++ b/Libraries/Components/ActivityIndicatorIOS/ActivityIndicatorIOS.ios.js @@ -88,9 +88,11 @@ var styles = StyleSheet.create({ justifyContent: 'center', }, sizeSmall: { + width: 20, height: 20, }, sizeLarge: { + width: 36, height: 36, } }); From 6e3472d13efa15b875e5071fa719670a535e75d7 Mon Sep 17 00:00:00 2001 From: Felix Oghina Date: Wed, 17 Jun 2015 12:21:07 -0700 Subject: [PATCH 12/64] [react_native] JS files from D2164068: support filtering by mimeType in CameraRollManager#getPhotos --- Libraries/CameraRoll/CameraRoll.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 67fa50830..1f5c6c22a 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -71,6 +71,11 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({ * Specifies filter on asset type */ assetType: ReactPropTypes.oneOf(ASSET_TYPE_OPTIONS), + + /** + * Filter by mimetype (e.g. image/jpeg). + */ + mimeTypes: ReactPropTypes.arrayOf(ReactPropTypes.string), }); /** From a48e9b4690821307467490bf3ce2beb4ea00cad9 Mon Sep 17 00:00:00 2001 From: alvaromb Date: Wed, 17 Jun 2015 13:56:14 -0700 Subject: [PATCH 13/64] [WebView] Exposed scalesPageToFit UIWebView property Summary: Added the ``scalesPageToFit`` prop to ``WebView``. This allows ``UIWebView`` to handle user zoom and scale. Closes https://github.com/facebook/react-native/pull/1631 Github Author: alvaromb Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/WebViewExample.js | 3 +++ Libraries/Components/WebView/WebView.ios.js | 6 ++++++ React/Views/RCTWebViewManager.m | 1 + 3 files changed, 10 insertions(+) diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index d1e990cb4..6a93f80e6 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -43,6 +43,7 @@ var WebViewExample = React.createClass({ backButtonEnabled: false, forwardButtonEnabled: false, loading: true, + scalesPageToFit: true, }; }, @@ -97,6 +98,7 @@ var WebViewExample = React.createClass({ javaScriptEnabledAndroid={true} onNavigationStateChange={this.onNavigationStateChange} startInLoadingState={true} + scalesPageToFit={this.state.scalesPageToFit} /> {this.state.status} @@ -124,6 +126,7 @@ var WebViewExample = React.createClass({ url: navState.url, status: navState.title, loading: navState.loading, + scalesPageToFit: true }); }, diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js index 83af4a8ad..15ab9e676 100644 --- a/Libraries/Components/WebView/WebView.ios.js +++ b/Libraries/Components/WebView/WebView.ios.js @@ -95,6 +95,11 @@ var WebView = React.createClass({ * Used for android only, JS is enabled by default for WebView on iOS */ javaScriptEnabledAndroid: PropTypes.bool, + /** + * Used for iOS only, sets whether the webpage scales to fit the view and the + * user can change the scale + */ + scalesPageToFit: PropTypes.bool, }, getInitialState: function() { @@ -155,6 +160,7 @@ var WebView = React.createClass({ onLoadingStart={this.onLoadingStart} onLoadingFinish={this.onLoadingFinish} onLoadingError={this.onLoadingError} + scalesPageToFit={this.props.scalesPageToFit} />; return ( diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 50659a562..a5de572bd 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -30,6 +30,7 @@ RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL); RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets); RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL); RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler, BOOL); +RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL); - (NSDictionary *)constantsToExport { From a885efe02dc25e23647e028fa005261207c8a581 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 17 Jun 2015 14:03:50 -0700 Subject: [PATCH 14/64] [ReactNative] Add more markers and fix FPS graph Summary: @public Add marker to show JavaScript download duration + flow arrows to show the origin of the UI blocks being flushed. Also fixed the condition on `RCTPerfStats`, UI and JS graphs were being created at startup time, now they're just created on the first time they're shown. Test Plan: The markers: {F22577660} To check the FPS graph, enable it on the DevMenu, and it should appear initially empty, instead of previously filled as before. --- React/Base/RCTBridge.m | 7 ++++--- React/Base/RCTPerfStats.m | 4 ++-- React/Modules/RCTUIManager.m | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 8dce4584b..bbf28e25d 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -951,8 +951,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module }); } else { + RCTProfileBeginEvent(); RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) { + RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]); _loading = NO; if (!self.isValid) { @@ -1119,12 +1121,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module { RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); - RCTProfileBeginEvent(); - + RCTProfileBeginFlowEvent(); [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { + RCTProfileEndFlowEvent(); RCTAssertJSThread(); - RCTProfileEndEvent(@"ApplicationScript", @"js_call,init", scriptLoadError); if (scriptLoadError) { onComplete(scriptLoadError); return; diff --git a/React/Base/RCTPerfStats.m b/React/Base/RCTPerfStats.m index 2e5ec8d14..39f72928f 100644 --- a/React/Base/RCTPerfStats.m +++ b/React/Base/RCTPerfStats.m @@ -41,7 +41,7 @@ RCT_EXPORT_MODULE() - (RCTFPSGraph *)jsGraph { - if (!_jsGraph) { + if (!_jsGraph && _container) { UIColor *jsColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:1]; _jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34) graphPosition:RCTFPSGraphPositionRight @@ -54,7 +54,7 @@ RCT_EXPORT_MODULE() - (RCTFPSGraph *)uiGraph { - if (!_uiGraph) { + if (!_uiGraph && _container) { UIColor *uiColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:1]; _uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34) graphPosition:RCTFPSGraphPositionLeft diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 0ffbaf0c3..ff1691b8a 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -979,7 +979,9 @@ RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point call [_pendingUIBlocksLock unlock]; // Execute the previously queued UI blocks + RCTProfileBeginFlowEvent(); dispatch_async(dispatch_get_main_queue(), ^{ + RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); for (dispatch_block_t block in previousPendingUIBlocks) { block(); From e78b8c40fe8c5cb7559a761ebbbe943ee784042a Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 17 Jun 2015 14:10:23 -0700 Subject: [PATCH 15/64] [oss][react-native] unbreak NavigationEventEmitter-test --- .../Navigation/__tests__/NavigationEventEmitter-test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js index 518fe0724..2a8d7d82a 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationEventEmitter-test.js @@ -25,7 +25,9 @@ 'use strict'; jest + .dontMock('EmitterSubscription') .dontMock('EventEmitter') + .dontMock('EventSubscriptionVendor') .dontMock('NavigationEvent') .dontMock('NavigationEventEmitter'); From 3fa8ec0271bbe75425e1a2d87d596046aef20fcb Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Wed, 17 Jun 2015 15:14:42 -0700 Subject: [PATCH 16/64] [ReactNative] Change text input underline color --- Libraries/Components/TextInput/TextInput.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 902f4100a..d2845e601 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -50,6 +50,7 @@ var AndroidTextInputAttributes = { placeholder: true, text: true, testID: true, + underlineColorAndroid: true, }; var viewConfigAndroid = { @@ -260,6 +261,10 @@ var TextInput = React.createClass({ * Used to locate this view in end-to-end tests. */ testID: PropTypes.string, + /** + * The color of the textInput underline. Is only supported on Android. + */ + underlineColorAndroid: PropTypes.string, }, /** @@ -489,6 +494,7 @@ var TextInput = React.createClass({ password={this.props.password || this.props.secureTextEntry} placeholder={this.props.placeholder} text={this.state.bufferedValue} + underlineColorAndroid={this.props.underlineColorAndroid} children={children} />; From 3029511ce4f2d6f2d4ae90d169442b3ef01dd7f2 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 18 Jun 2015 05:26:23 -0700 Subject: [PATCH 17/64] [react_native] JS files from D2163804: [react_native] Add native root tag to createView calls --- Libraries/ReactNative/ReactNativeBaseComponent.js | 9 ++++++++- Libraries/ReactNative/ReactNativeTagHandles.js | 14 +++++++++++++- Libraries/ReactNative/ReactNativeTextComponent.js | 8 +++++++- React/Modules/RCTUIManager.m | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 95af29023..84baf6753 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -251,7 +251,14 @@ ReactNativeBaseComponent.Mixin = { this._currentElement.props, // next props this.viewConfig.validAttributes ); - RCTUIManager.createView(tag, this.viewConfig.uiViewClassName, updatePayload); + + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + RCTUIManager.createView( + tag, + this.viewConfig.uiViewClassName, + nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, + updatePayload + ); this._registerListenersUponCreation(this._currentElement.props); this.initializeChildren( diff --git a/Libraries/ReactNative/ReactNativeTagHandles.js b/Libraries/ReactNative/ReactNativeTagHandles.js index bf1dc59f2..ab350817c 100644 --- a/Libraries/ReactNative/ReactNativeTagHandles.js +++ b/Libraries/ReactNative/ReactNativeTagHandles.js @@ -28,6 +28,7 @@ var warning = require('warning'); * unmount a component with a `rootNodeID`, then mount a new one in its place, */ var INITIAL_TAG_COUNT = 1; +var NATIVE_TOP_ROOT_ID_SEPARATOR = '{TOP_LEVEL}'; var ReactNativeTagHandles = { tagsStartAt: INITIAL_TAG_COUNT, tagCount: INITIAL_TAG_COUNT, @@ -67,7 +68,7 @@ var ReactNativeTagHandles = { this.reactTagIsNativeTopRootID(tag), 'Expect a native root tag, instead got ', tag ); - return '.r[' + tag + ']{TOP_LEVEL}'; + return '.r[' + tag + ']' + NATIVE_TOP_ROOT_ID_SEPARATOR; }, reactTagIsNativeTopRootID: function(reactTag: number): bool { @@ -75,6 +76,17 @@ var ReactNativeTagHandles = { return reactTag % 10 === 1; }, + getNativeTopRootIDFromNodeID: function(nodeID: ?string): ?string { + if (!nodeID) { + return null; + } + var index = nodeID.indexOf(NATIVE_TOP_ROOT_ID_SEPARATOR); + if (index === -1) { + return null; + } + return nodeID.substr(0, index + NATIVE_TOP_ROOT_ID_SEPARATOR.length); + }, + /** * Returns the native `nodeHandle` (`tag`) that was most recently *natively* * mounted at the `rootNodeID`. Just because a React component has been diff --git a/Libraries/ReactNative/ReactNativeTextComponent.js b/Libraries/ReactNative/ReactNativeTextComponent.js index bdca141c0..bb93b9c34 100644 --- a/Libraries/ReactNative/ReactNativeTextComponent.js +++ b/Libraries/ReactNative/ReactNativeTextComponent.js @@ -32,7 +32,13 @@ assign(ReactNativeTextComponent.prototype, { mountComponent: function(rootID, transaction, context) { this._rootNodeID = rootID; var tag = ReactNativeTagHandles.allocateTag(); - RCTUIManager.createView(tag, 'RCTRawText', {text: this._stringText}); + var nativeTopRootID = ReactNativeTagHandles.getNativeTopRootIDFromNodeID(rootID); + RCTUIManager.createView( + tag, + 'RCTRawText', + nativeTopRootID ? ReactNativeTagHandles.rootNodeIDToTag[nativeTopRootID] : null, + {text: this._stringText} + ); return { rootNodeID: rootID, tag: tag, diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index ff1691b8a..873265479 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -804,6 +804,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag viewName:(NSString *)viewName + rootTag:(NSNumber *)rootTag props:(NSDictionary *)props) { RCTViewManager *manager = _viewManagers[viewName]; From 58d01c798158605f7a92feb8efab4d56923989cc Mon Sep 17 00:00:00 2001 From: Olivier Notteghem Date: Thu, 18 Jun 2015 05:42:45 -0700 Subject: [PATCH 18/64] [RN Inspector] : fix CSS not to use view overflow which is not supported on iOS --- Libraries/Inspector/Inspector.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index 46615d967..fa6e20e08 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -66,14 +66,9 @@ class Inspector extends React.Component { } render() { - var panelPosition; - if (this.state.panelPos === 'bottom') { - panelPosition = {bottom: -Dimensions.get('window').height}; - } else { - panelPosition = {top: 0}; - } + var panelContainerStyle = (this.state.panelPos === 'bottom') ? {bottom: 0} : {top: 0}; return ( - + {this.state.inspecting && } - + Date: Thu, 18 Jun 2015 09:29:31 -0700 Subject: [PATCH 19/64] [RN Events] clear disk cache on logout --- React/Modules/RCTAsyncLocalStorage.h | 8 +++- React/Modules/RCTAsyncLocalStorage.m | 64 ++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/React/Modules/RCTAsyncLocalStorage.h b/React/Modules/RCTAsyncLocalStorage.h index 31ff98c6a..0a1aaacbc 100644 --- a/React/Modules/RCTAsyncLocalStorage.h +++ b/React/Modules/RCTAsyncLocalStorage.h @@ -8,6 +8,7 @@ */ #import "RCTBridgeModule.h" +#import "RCTInvalidating.h" /** * A simple, asynchronous, persistent, key-value storage system designed as a @@ -20,7 +21,9 @@ * * Keys and values must always be strings or an error is returned. */ -@interface RCTAsyncLocalStorage : NSObject +@interface RCTAsyncLocalStorage : NSObject + +@property (nonatomic, assign) BOOL clearOnInvalidate; - (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback; - (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback; @@ -28,4 +31,7 @@ - (void)clear:(RCTResponseSenderBlock)callback; - (void)getAllKeys:(RCTResponseSenderBlock)callback; +// For clearing data when the bridge may not exist, e.g. when logging out. ++ (NSError *)clearAllData; + @end diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index a7f389282..5ed2e48e5 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -61,6 +61,13 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } +static NSString *RCTGetStorageDir() +{ + NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES]; + return [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path]; +} + // Only merges objects - all other types are just clobbered (including arrays) static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source) { @@ -106,7 +113,39 @@ RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { - return dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); + // We want all instances to share the same queue since they will be reading/writing the same files. + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + ++ (NSError *)clearAllData +{ + NSError *error; + [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDir() error:&error]; + return error; +} + +- (void)invalidate +{ + if (_clearOnInvalidate) { + [RCTAsyncLocalStorage clearAllData]; + } + _clearOnInvalidate = NO; + _manifest = [[NSMutableDictionary alloc] init]; + _haveSetup = NO; +} +- (BOOL)isValid +{ + return _haveSetup; +} + +- (void)dealloc +{ + [self invalidate]; } - (NSString *)_filePathForKey:(NSString *)key @@ -120,10 +159,7 @@ RCT_EXPORT_MODULE() if (_haveSetup) { return nil; } - NSString *documentDirectory = - [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES]; - _storageDirectory = [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path]; + _storageDirectory = RCTGetStorageDir(); NSError *error; [[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory withIntermediateDirectories:YES @@ -135,10 +171,10 @@ RCT_EXPORT_MODULE() _manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename]; NSDictionary *errorOut; NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut); - _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new]; + _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [[NSMutableDictionary alloc] init]; if (error) { RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); - _manifest = [NSMutableDictionary new]; + _manifest = [[NSMutableDictionary alloc] init]; } _haveSetup = YES; return nil; @@ -312,18 +348,10 @@ RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { - id errorOut = [self _ensureSetup]; - if (!errorOut) { - NSError *error; - for (NSString *key in _manifest) { - NSString *filePath = [self _filePathForKey:key]; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - } - [_manifest removeAllObjects]; - errorOut = [self _writeManifest:nil]; - } + _manifest = [[NSMutableDictionary alloc] init]; + NSError *error = [RCTAsyncLocalStorage clearAllData]; if (callback) { - callback(@[RCTNullIfNil(errorOut)]); + callback(@[RCTNullIfNil(error)]); } } From 5263b233211572e7fd89958e920d51201020ba23 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 18 Jun 2015 09:41:38 -0700 Subject: [PATCH 20/64] Renamed RCTDataManager to RCTNetworking --- .../Initialization/loadSourceMap.js | 6 +++--- .../Network/RCTNetwork.xcodeproj/project.pbxproj | 12 ++++++------ .../Network/{RCTDataManager.h => RCTNetworking.h} | 2 +- .../Network/{RCTDataManager.m => RCTNetworking.m} | 8 ++++---- Libraries/Network/XMLHttpRequest.ios.js | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) rename Libraries/Network/{RCTDataManager.h => RCTNetworking.h} (87%) rename Libraries/Network/{RCTDataManager.m => RCTNetworking.m} (98%) diff --git a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js index c3499ac9f..80b7fc5a3 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/loadSourceMap.js @@ -18,7 +18,7 @@ var SourceMapConsumer = require('SourceMap').SourceMapConsumer; var SourceMapURL = require('./source-map-url'); var RCTSourceCode = NativeModules.SourceCode; -var RCTDataManager = NativeModules.DataManager; +var RCTNetworking = NativeModules.Networking; function loadSourceMap(): Promise { return fetchSourceMap() @@ -34,9 +34,9 @@ function fetchSourceMap(): Promise { return Promise.reject(new Error('RCTSourceCode module is not available')); } - if (!RCTDataManager) { + if (!RCTNetworking) { // Used internally by fetch - return Promise.reject(new Error('RCTDataManager module is not available')); + return Promise.reject(new Error('RCTNetworking module is not available')); } return new Promise(RCTSourceCode.getScriptText) diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj index becf442fa..48b84fd03 100644 --- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; }; 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; }; - 58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTDataManager.m */; }; + 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -30,8 +30,8 @@ 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = ""; }; 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = ""; }; 58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 58B512061A9E6CE300147676 /* RCTDataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataManager.h; sourceTree = ""; }; - 58B512071A9E6CE300147676 /* RCTDataManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataManager.m; sourceTree = ""; }; + 58B512061A9E6CE300147676 /* RCTNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworking.h; sourceTree = ""; }; + 58B512071A9E6CE300147676 /* RCTNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworking.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -48,8 +48,8 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 58B512061A9E6CE300147676 /* RCTDataManager.h */, - 58B512071A9E6CE300147676 /* RCTDataManager.m */, + 58B512061A9E6CE300147676 /* RCTNetworking.h */, + 58B512071A9E6CE300147676 /* RCTNetworking.m */, 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */, 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */, 1372B7351AB03E7B00659ED6 /* RCTReachability.h */, @@ -125,7 +125,7 @@ buildActionMask = 2147483647; files = ( 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */, - 58B512081A9E6CE300147676 /* RCTDataManager.m in Sources */, + 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */, 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Libraries/Network/RCTDataManager.h b/Libraries/Network/RCTNetworking.h similarity index 87% rename from Libraries/Network/RCTDataManager.h rename to Libraries/Network/RCTNetworking.h index 424b65b9c..d8bc39524 100644 --- a/Libraries/Network/RCTDataManager.h +++ b/Libraries/Network/RCTNetworking.h @@ -11,7 +11,7 @@ #import "RCTBridgeModule.h" -@interface RCTDataManager : NSObject +@interface RCTNetworking : NSObject @end diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTNetworking.m similarity index 98% rename from Libraries/Network/RCTDataManager.m rename to Libraries/Network/RCTNetworking.m index 49ce6d754..b5819cfc7 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTNetworking.m @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTDataManager.h" +#import "RCTNetworking.h" #import "RCTAssert.h" #import "RCTConvert.h" @@ -19,7 +19,7 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result); -@interface RCTDataManager () +@interface RCTNetworking () - (void)processDataForHTTPQuery:(NSDictionary *)data callback:(void (^)(NSError *error, NSDictionary *result))callback; @@ -30,7 +30,7 @@ typedef void (^RCTHTTPQueryResult)(NSError *error, NSDictionary *result); */ @interface RCTHTTPFormDataHelper : NSObject -@property (nonatomic, weak) RCTDataManager *dataManager; +@property (nonatomic, weak) RCTNetworking *dataManager; @end @@ -207,7 +207,7 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError /** * Bridge module that provides the JS interface to the network stack. */ -@implementation RCTDataManager +@implementation RCTNetworking { NSInteger _currentRequestID; NSMapTable *_activeRequests; diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 151781c91..6eb586c26 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -12,7 +12,7 @@ 'use strict'; var FormData = require('FormData'); -var RCTDataManager = require('NativeModules').DataManager; +var RCTNetworking = require('NativeModules').Networking; var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); var XMLHttpRequestBase = require('XMLHttpRequestBase'); @@ -89,7 +89,7 @@ class XMLHttpRequest extends XMLHttpRequestBase { if (data instanceof FormData) { data = {formData: data.getParts()}; } - RCTDataManager.sendRequest( + RCTNetworking.sendRequest( { method, url, @@ -103,7 +103,7 @@ class XMLHttpRequest extends XMLHttpRequestBase { abortImpl(): void { if (this._requestId) { - RCTDataManager.cancelRequest(this._requestId); + RCTNetworking.cancelRequest(this._requestId); this._clearSubscriptions(); this._requestId = null; } From 7d62b6077b534fbf93a45c377b0764afa1134efd Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Thu, 18 Jun 2015 19:14:44 -0700 Subject: [PATCH 21/64] [ReactNative] Fix 32 bit check compile error Summary: @public The test file is getting compiled when hitting cmd+R which fails for 64-bit devices even if not trying to run tests. Change back to runtime check to fix. Test Plan: cmd+R works for iPhone 6, cmd+U fails as expected on iPhone 6, works for iPhone 5 --- .../UIExplorerIntegrationTests/IntegrationTestsTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m index b69e9a78b..44d048323 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/IntegrationTestsTests.m @@ -26,7 +26,7 @@ - (void)setUp { #if __LP64__ - #error Tests should be run on 32-bit device simulators (e.g. iPhone 5) + RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)"); #endif NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; From 4ddfc14d29424e0168945571364eadf3eddbae76 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 18 Jun 2015 20:15:59 -0700 Subject: [PATCH 22/64] Fixed stickers --- React/Modules/RCTUIManager.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 873265479..770bc0580 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1332,7 +1332,7 @@ RCT_EXPORT_METHOD(clearJSResponder) }, } mutableCopy]; - for (RCTViewManager *manager in _viewManagers) { + for (RCTViewManager *manager in _viewManagers.allValues) { if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) { NSDictionary *eventTypes = [manager customBubblingEventTypes]; for (NSString *eventName in eventTypes) { @@ -1393,7 +1393,7 @@ RCT_EXPORT_METHOD(clearJSResponder) }, } mutableCopy]; - for (RCTViewManager *manager in _viewManagers) { + for (RCTViewManager *manager in _viewManagers.allValues) { if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) { NSDictionary *eventTypes = [manager customDirectEventTypes]; for (NSString *eventName in eventTypes) { From 9998337220df0ba41a141530f250c33fab925f16 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 18 Jun 2015 21:24:21 -0700 Subject: [PATCH 23/64] [react-packager] Add tests to ensure we return all dependency types --- .../src/Packager/__tests__/Packager-test.js | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 901c467ba..89184c95e 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -23,6 +23,9 @@ describe('Packager', function() { var getDependencies; var wrapModule; var Packager; + var packager; + var assetServer; + var modules; beforeEach(function() { getDependencies = jest.genMockFn(); @@ -35,30 +38,27 @@ describe('Packager', function() { }); Packager = require('../'); - }); - pit('create a package', function() { require('fs').statSync.mockImpl(function() { return { - isDirectory: function() {return true;} + isDirectory: () => true }; }); - require('fs').readFile.mockImpl(function(file, callback) { callback(null, '{"json":true}'); }); - var assetServer = { + assetServer = { getAssetData: jest.genMockFn(), }; - var packager = new Packager({ + packager = new Packager({ projectRoots: ['/root'], assetServer: assetServer, }); - var modules = [ + modules = [ {id: 'foo', path: '/root/foo.js', dependencies: []}, {id: 'bar', path: '/root/bar.js', dependencies: []}, { @@ -116,7 +116,9 @@ describe('Packager', function() { type: 'png', }; }); + }); + pit('create a package', function() { return packager.package('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ @@ -200,4 +202,42 @@ describe('Packager', function() { ]); }); }); + + pit('gets the list of dependencies', function() { + return packager.getDependencies('/root/foo.js', true) + .then(({dependencies}) => { + expect(dependencies).toEqual([ + { + dependencies: [], + id: 'foo', + path: '/root/foo.js', + }, + { + dependencies: [], + id: 'bar', + path: '/root/bar.js', + }, + { + dependencies: [], + id: 'image!img', + isAsset_DEPRECATED: true, + path: '/root/img/img.png', + resolution: 2, + }, + { + dependencies: [], + id: 'new_image.png', + isAsset: true, + path: '/root/img/new_image.png', + resolution: 2, + }, + { + dependencies: [], + id: 'package/file.json', + isJSON: true, + path: '/root/file.json', + }, + ]); + }); + }); }); From cf0e40ad3dbeb5e5ab90b9b92b77ba3b69ad8228 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 18 Jun 2015 15:56:33 -0100 Subject: [PATCH 24/64] [ReactNative] Fix MessageQueue-test on open source Summary: @public Fix mocking on MessageQueue-test Test Plan: Run the test --- Libraries/Utilities/__tests__/MessageQueue-test.js | 11 +++++------ jestSupport/scriptPreprocess.js | 7 ++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Libraries/Utilities/__tests__/MessageQueue-test.js b/Libraries/Utilities/__tests__/MessageQueue-test.js index a8d5fc5bd..60fae4c6b 100644 --- a/Libraries/Utilities/__tests__/MessageQueue-test.js +++ b/Libraries/Utilities/__tests__/MessageQueue-test.js @@ -8,9 +8,12 @@ */ 'use strict'; -jest.dontMock('MessageQueue'); +jest.setMock('ReactUpdates', { + batchedUpdates: fn => fn() +}); -var ReactUpdates = require('ReactUpdates'); +jest.dontMock('MessageQueue'); +jest.dontMock('keyMirror'); var MessageQueue = require('MessageQueue'); let MODULE_IDS = 0; @@ -95,10 +98,6 @@ describe('MessageQueue', () => { describe('processBatch', () => { - beforeEach(() => { - ReactUpdates.batchedUpdates = (fn) => fn(); - }); - it('should call __invokeCallback for invokeCallbackAndReturnFlushedQueue', () => { queue.__invokeCallback = jasmine.createSpy(); queue.processBatch([{ diff --git a/jestSupport/scriptPreprocess.js b/jestSupport/scriptPreprocess.js index fe675a577..ac219c97d 100644 --- a/jestSupport/scriptPreprocess.js +++ b/jestSupport/scriptPreprocess.js @@ -18,16 +18,13 @@ module.exports = { transformSource: transformSource, process: function(src, fileName) { - if (fileName.match(/node_modules/)) { - return src; - } - try { return transformSource(src, fileName); } catch(e) { - throw new Error('\nError transforming file:\n js/' + + console.error('\nError transforming file:\n js/' + (fileName.split('/js/')[1] || fileName) + ':' + e.lineNumber + ': \'' + e.message + '\'\n'); + return src; } } }; From c8c254ce13d88bc809ae2345036b9d2a99b09529 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 19 Jun 2015 04:18:54 -0700 Subject: [PATCH 25/64] Changed methodQueue to a property --- .../UIExplorerUnitTests/RCTBridgeTests.m | 12 +-- Libraries/LinkingIOS/RCTLinkingManager.m | 5 -- Libraries/Network/RCTNetworking.m | 8 +- React/Base/RCTBridge.m | 79 +++++++++++++------ React/Base/RCTBridgeModule.h | 59 ++++++-------- React/Views/RCTTabBarManager.m | 4 +- 6 files changed, 84 insertions(+), 83 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m index ea6af0805..13bcdd7b4 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTBridgeTests.m @@ -79,25 +79,19 @@ RCT_EXPORT_MODULE() { RCTBridge *_bridge; BOOL _testMethodCalled; - dispatch_queue_t _queue; } @end @implementation RCTBridgeTests -RCT_EXPORT_MODULE(TestModule) +@synthesize methodQueue = _methodQueue; -- (dispatch_queue_t)methodQueue -{ - return _queue; -} +RCT_EXPORT_MODULE(TestModule) - (void)setUp { [super setUp]; - _queue = dispatch_queue_create("com.facebook.React.TestQueue", DISPATCH_QUEUE_SERIAL); - _bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[self]; } launchOptions:nil]; @@ -151,7 +145,7 @@ RCT_EXPORT_MODULE(TestModule) [_bridge.batchedBridge _handleBuffer:buffer context:RCTGetExecutorID(executor)]; - dispatch_sync(_queue, ^{ + dispatch_sync(_methodQueue, ^{ // clear the queue XCTAssertTrue(_testMethodCalled); }); diff --git a/Libraries/LinkingIOS/RCTLinkingManager.m b/Libraries/LinkingIOS/RCTLinkingManager.m index 8d4846a3b..5a97f0a60 100644 --- a/Libraries/LinkingIOS/RCTLinkingManager.m +++ b/Libraries/LinkingIOS/RCTLinkingManager.m @@ -37,11 +37,6 @@ RCT_EXPORT_MODULE() [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (dispatch_queue_t)methodQueue -{ - return dispatch_queue_create("com.facebook.React.LinkingManager", DISPATCH_QUEUE_SERIAL); -} - + (BOOL)application:(UIApplication *)application openURL:(NSURL *)URL sourceApplication:(NSString *)sourceApplication diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index b5819cfc7..b4ae07c1a 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -211,10 +211,10 @@ typedef void (^RCTDataLoaderCallback)(NSData *data, NSString *MIMEType, NSError { NSInteger _currentRequestID; NSMapTable *_activeRequests; - dispatch_queue_t _methodQueue; } @synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; RCT_EXPORT_MODULE() @@ -222,7 +222,6 @@ RCT_EXPORT_MODULE() { if ((self = [super init])) { _currentRequestID = 0; - _methodQueue = dispatch_queue_create("com.facebook.React.RCTDataManager", DISPATCH_QUEUE_SERIAL); _activeRequests = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory capacity:0]; @@ -230,11 +229,6 @@ RCT_EXPORT_MODULE() return self; } -- (dispatch_queue_t)methodQueue -{ - return _methodQueue; -} - - (void)buildRequest:(NSDictionary *)query responseSender:(RCTResponseSenderBlock)responseSender { diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index bbf28e25d..924fd0ae7 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -31,8 +31,6 @@ NSString *const RCTReloadNotification = @"RCTReloadNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; -dispatch_queue_t const RCTJSThread = nil; - /** * Must be kept in sync with `MessageQueue.js`. */ @@ -163,7 +161,6 @@ static NSDictionary *RCTJSErrorFromNSError(NSError *error) SEL _selector; NSMethodSignature *_methodSignature; NSArray *_argumentBlocks; - dispatch_block_t _methodQueue; } - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName @@ -567,14 +564,19 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName) @implementation RCTBridge static id _latestJSExecutor; - -#if RCT_DEBUG +dispatch_queue_t RCTJSThread; + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + // Set up JS thread + RCTJSThread = (id)kCFNull; + +#if RCT_DEBUG + + // Set up module classes static unsigned int classCount; Class *classes = objc_copyClassList(&classCount); @@ -587,7 +589,8 @@ static id _latestJSExecutor; if (class_conformsToProtocol(superclass, @protocol(RCTBridgeModule))) { if (![RCTModuleClassesByID containsObject:cls]) { - RCTLogError(@"Class %@ was not exported. Did you forget to use RCT_EXPORT_MODULE()?", NSStringFromClass(cls)); + RCTLogError(@"Class %@ was not exported. Did you forget to use " + "RCT_EXPORT_MODULE()?", NSStringFromClass(cls)); } break; } @@ -597,11 +600,11 @@ static id _latestJSExecutor; free(classes); +#endif + }); } -#endif - - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleProvider:(RCTBridgeModuleProviderBlock)block launchOptions:(NSDictionary *)launchOptions @@ -741,7 +744,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module __weak id _javaScriptExecutor; RCTSparseArray *_modulesByID; RCTSparseArray *_queuesByID; - dispatch_queue_t _methodQueue; NSDictionary *_modulesByName; CADisplayLink *_mainDisplayLink; CADisplayLink *_jsDisplayLink; @@ -783,7 +785,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module * Initialize and register bridge modules *before* adding the display link * so we don't have threading issues */ - _methodQueue = dispatch_queue_create("com.facebook.React.BridgeMethodQueue", DISPATCH_QUEUE_SERIAL); [self registerModules]; /** @@ -896,21 +897,49 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module } } - // Get method queues - [_modulesByID enumerateObjectsUsingBlock: - ^(id module, NSNumber *moduleID, __unused BOOL *stop) { - if ([module respondsToSelector:@selector(methodQueue)]) { - dispatch_queue_t queue = [module methodQueue]; - if (queue) { - _queuesByID[moduleID] = queue; - } else { - _queuesByID[moduleID] = (id)kCFNull; + // Set/get method queues + [_modulesByID enumerateObjectsUsingBlock:^(id module, NSNumber *moduleID, __unused BOOL *stop) { + + dispatch_queue_t queue = nil; + BOOL implementsMethodQueue = [module respondsToSelector:@selector(methodQueue)]; + if (implementsMethodQueue) { + queue = [module methodQueue]; + } + if (!queue) { + + // Need to cache queueNames because they aren't retained by dispatch_queue + static NSMutableDictionary *queueNames; + if (!queueNames) { + queueNames = [[NSMutableDictionary alloc] init]; + } + NSString *moduleName = RCTBridgeModuleNameForClass([module class]); + NSString *queueName = queueNames[moduleName]; + if (!queueName) { + queueName = [NSString stringWithFormat:@"com.facebook.React.%@Queue", moduleName]; + queueNames[moduleName] = queueName; + } + + // Create new queue + queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL); + + // assign it to the module + if (implementsMethodQueue) { + @try { + [(id)module setValue:queue forKey:@"methodQueue"]; + } + @catch (NSException *exception) { + RCTLogError(@"%@ is returning nil for it's methodQueue, which is not " + "permitted. You must either return a pre-initialized " + "queue, or @synthesize the methodQueue to let the bridge " + "create a queue for you.", moduleName); + } } } + _queuesByID[moduleID] = queue; if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { [_frameUpdateObservers addObject:module]; - } + } }]; } @@ -1166,10 +1195,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module queue = _queuesByID[moduleID]; } - if (queue == (id)kCFNull) { + if (queue == RCTJSThread) { [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } else { - dispatch_async(queue ?: _methodQueue, block); + } else if (queue) { + dispatch_async(queue, block); } } @@ -1309,9 +1338,9 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) }); }; - if (queue == (id)kCFNull) { + if (queue == RCTJSThread) { [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; - } else { + } else if (queue) { dispatch_async(queue, block); } } diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 90090e847..4715f9df7 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -41,7 +41,7 @@ typedef void (^RCTPromiseRejectBlock)(NSError *error); * * NOTE: RCTJSThread is not a real libdispatch queue */ -extern const dispatch_queue_t RCTJSThread; +extern dispatch_queue_t RCTJSThread; /** * Provides the interface needed to register a bridge module. @@ -53,10 +53,33 @@ extern const dispatch_queue_t RCTJSThread; * A reference to the RCTBridge. Useful for modules that require access * to bridge features, such as sending events or making JS calls. This * will be set automatically by the bridge when it initializes the module. - * To implement this in your module, just add @synthesize bridge = _bridge; + * To implement this in your module, just add `@synthesize bridge = _bridge;` */ @property (nonatomic, weak) RCTBridge *bridge; +/** + * The queue that will be used to call all exported methods. If omitted, this + * will call on a default background queue, which is avoids blocking the main + * thread. + * + * If the methods in your module need to interact with UIKit methods, they will + * probably need to call those on the main thread, as most of UIKit is main- + * thread-only. You can tell React Native to call your module methods on the + * main thread by returning a reference to the main queue, like this: + * + * - (dispatch_queue_t)methodQueue + * { + * return dispatch_get_main_queue(); + * } + * + * If you don't want to specify the queue yourself, but you need to use it + * inside your class (e.g. if you have internal methods that need to disaptch + * onto that queue), you can just add `@synthesize methodQueue = _methodQueue;` + * and the bridge will populate the methodQueue property for you automatically + * when it initializes the module. + */ +@property (nonatomic, weak, readonly) dispatch_queue_t methodQueue; + /** * Place this macro in your class implementation to automatically register * your module with the bridge when it loads. The optional js_name argument @@ -180,38 +203,6 @@ extern const dispatch_queue_t RCTJSThread; return @[@#js_name, @#method]; \ } \ - -/** - * 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 - * thread. - * - * If the methods in your module need to interact with UIKit methods, they will - * probably need to call those on the main thread, as most of UIKit is main- - * thread-only. You can tell React Native to call your module methods on the - * main thread by returning a reference to the main queue, like this: - * - * - (dispatch_queue_t)methodQueue - * { - * return dispatch_get_main_queue(); - * } - * - * If your methods perform heavy work such as synchronous filesystem or network - * access, you probably don't want to block the default background queue, as - * this will stall other methods. Instead, you should return a custom serial - * queue, like this: - * - * - (dispatch_queue_t)methodQueue - * { - * return dispatch_queue_create("com.mydomain.FileQueue", DISPATCH_QUEUE_SERIAL); - * } - * - * Alternatively, if only some methods of the module should be executed on a - * particular queue you can leave this method unimplemented, and simply - * dispatch_async() to the required queue within the method itself. - */ -- (dispatch_queue_t)methodQueue; - /** * Injects constants into JS. These constants are made accessible via * NativeModules.ModuleName.X. This method is called when the module is diff --git a/React/Views/RCTTabBarManager.m b/React/Views/RCTTabBarManager.m index 0f038c4d7..2290c78c7 100644 --- a/React/Views/RCTTabBarManager.m +++ b/React/Views/RCTTabBarManager.m @@ -14,13 +14,11 @@ @implementation RCTTabBarManager -@synthesize bridge = _bridge; - RCT_EXPORT_MODULE() - (UIView *)view { - return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher]; + return [[RCTTabBar alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; } RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) From 0116abed4f497849646fb68ef48064ce10b90249 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Fri, 19 Jun 2015 03:30:09 -0700 Subject: [PATCH 26/64] [react_native] JS files from D2164109: [react_native] Fix JS error stacktraces on Android --- Libraries/Network/XMLHttpRequestBase.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Network/XMLHttpRequestBase.js b/Libraries/Network/XMLHttpRequestBase.js index 9d06f486a..f4d106b72 100644 --- a/Libraries/Network/XMLHttpRequestBase.js +++ b/Libraries/Network/XMLHttpRequestBase.js @@ -143,7 +143,7 @@ class XMLHttpRequestBase { return; } this.status = status; - this.setResponseHeaders(responseHeaders); + this.setResponseHeaders(responseHeaders || {}); this.responseText = responseText; this.setReadyState(this.DONE); } From 6cf570db3565406c77156d95c4b343fc252fab38 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 19 Jun 2015 07:37:48 -0700 Subject: [PATCH 27/64] [ReactNative] Fix retain cycle on DevMenu Summary: @public There was an iVar being directly referenced from inside a block on RCTDevMenu that was causing a retain cycle and the dev menu wasn't being released. Test Plan: Put a break point on dealloc, it should be called now. --- React/Base/RCTDevMenu.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index bce19e8a9..ed936bbe8 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -142,7 +142,7 @@ RCT_EXPORT_MODULE() [commands registerKeyCommandWithInput:@"i" modifierFlags:UIKeyModifierCommand action:^(__unused UIKeyCommand *command) { - [_bridge.eventDispatcher + [weakSelf.bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; }]; From caffd60a3f38eff27a9186b8468d8a536f602107 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Fri, 19 Jun 2015 08:51:05 -0700 Subject: [PATCH 28/64] [React Native] Update description on RCTText --- Libraries/Text/RCTText.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index 1ae432d90..9dc8e4200 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -34,6 +34,14 @@ return self; } +- (NSString *)description +{ + NSString *superDescription = super.description; + NSRange semicolonRange = [superDescription rangeOfString:@";"]; + NSString *replacement = [NSString stringWithFormat:@"; reactTag: %@; text: %@", self.reactTag, self.textStorage.string]; + return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement]; +} + - (void)reactSetFrame:(CGRect)frame { // Text looks super weird if its frame is animated. From 634cdfb76a303e370c2065e01b66647746929701 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 19 Jun 2015 08:08:14 -0700 Subject: [PATCH 29/64] Removed duplicate key registration bug Summary: @public I was using UIKeyCommand as a key in a dictionary, but it seems iOS wasn't treating identical commands as equal, so it was possible to register the same key command twice, resulting in the command triggering the action multiple times. I've now created a container object for the key commands, and not relying on undocumented hashing behavior of UIKeyCommand for deduplication any more. Test Plan: Reload bridge multiple times, then check that the number of registered keys in the command set inside RCTKeyCommands doesn't keep increasing. --- React/Base/RCTKeyCommands.m | 101 +++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index 52d8c30dd..387bfc1a9 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -13,11 +13,58 @@ #import "RCTUtils.h" +@interface RCTKeyCommand : NSObject + +@property (nonatomic, strong) UIKeyCommand *keyCommand; +@property (nonatomic, copy) void (^block)(UIKeyCommand *); + +@end + +@implementation RCTKeyCommand + +- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand + block:(void (^)(UIKeyCommand *))block +{ + if ((self = [super init])) { + _keyCommand = keyCommand; + _block = block ?: ^(__unused UIKeyCommand *cmd) {}; + } + return self; +} + +RCT_NOT_IMPLEMENTED(-init) + +- (id)copyWithZone:(__unused NSZone *)zone +{ + return self; +} + +- (NSUInteger)hash +{ + return _keyCommand.input.hash ^ _keyCommand.modifierFlags; +} + +- (BOOL)isEqual:(RCTKeyCommand *)object +{ + if (![object isKindOfClass:[RCTKeyCommand class]]) { + return NO; + } + return [self matchesInput:object.keyCommand.input + flags:object.keyCommand.modifierFlags]; +} + +- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags +{ + return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags; +} + +@end + @interface RCTKeyCommands () -@property (nonatomic, strong) NSMutableDictionary *commandBindings; +@property (nonatomic, strong) NSMutableSet *commands; -- (void)RCT_handleKeyCommand:(UIKeyCommand *)key; +- (BOOL)RCT_handleKeyCommand:(UIKeyCommand *)key; @end @@ -25,15 +72,15 @@ - (NSArray *)RCT_keyCommands { - NSDictionary *commandBindings = [RCTKeyCommands sharedInstance].commandBindings; - return [[self RCT_keyCommands] arrayByAddingObjectsFromArray:[commandBindings allKeys]]; + NSSet *commands = [RCTKeyCommands sharedInstance].commands; + return [[self RCT_keyCommands] arrayByAddingObjectsFromArray: + [[commands valueForKeyPath:@"keyCommand"] allObjects]]; } - (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { if (action == @selector(RCT_handleKeyCommand:)) { - [[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender]; - return YES; + return [[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender]; } return [self RCT_sendAction:action to:target from:sender forEvent:event]; } @@ -49,8 +96,6 @@ RCTSwapInstanceMethods([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(RCT_sendAction:to:from:forEvent:)); } -static RCTKeyCommands *RKKeyCommandsSharedInstance = nil; - + (instancetype)sharedInstance { static RCTKeyCommands *sharedInstance; @@ -65,25 +110,11 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil; - (instancetype)init { if ((self = [super init])) { - _commandBindings = [[NSMutableDictionary alloc] init]; + _commands = [[NSMutableSet alloc] init]; } return self; } -- (void)RCT_handleKeyCommand:(UIKeyCommand *)key -{ - // NOTE: We should just be able to do commandBindings[key] here, but curiously, the - // lookup seems to return nil sometimes, even if the key is found in the dictionary. - // To fix this, we use a linear search, since there won't be many keys anyway - - [_commandBindings enumerateKeysAndObjectsUsingBlock: - ^(UIKeyCommand *k, void (^block)(UIKeyCommand *), __unused BOOL *stop) { - if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) { - block(key); - } - }]; -} - - (void)registerKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags action:(void (^)(UIKeyCommand *))block @@ -105,7 +136,19 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil; modifierFlags:flags action:@selector(RCT_handleKeyCommand:)]; - _commandBindings[command] = block ?: ^(__unused UIKeyCommand *cmd) {}; + [_commands addObject:[[RCTKeyCommand alloc] initWithKeyCommand:command block:block]]; +} + +- (BOOL)RCT_handleKeyCommand:(UIKeyCommand *)key +{ + for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { + if ([command.keyCommand.input isEqualToString:key.input] && + command.keyCommand.modifierFlags == key.modifierFlags) { + command.block(key); + return YES; + } + } + return NO; } - (void)unregisterKeyCommandWithInput:(NSString *)input @@ -113,9 +156,9 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil; { RCTAssertMainThread(); - for (UIKeyCommand *key in [_commandBindings allKeys]) { - if ([key.input isEqualToString:input] && key.modifierFlags == flags) { - [_commandBindings removeObjectForKey:key]; + for (RCTKeyCommand *command in _commands.allObjects) { + if ([command matchesInput:input flags:flags]) { + [_commands removeObject:command]; break; } } @@ -126,8 +169,8 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil; { RCTAssertMainThread(); - for (UIKeyCommand *key in [_commandBindings allKeys]) { - if ([key.input isEqualToString:input] && key.modifierFlags == flags) { + for (RCTKeyCommand *command in _commands) { + if ([command matchesInput:input flags:flags]) { return YES; } } From 4d97c01f4bf5785d55d362cea3b0e36680843ab7 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 19 Jun 2015 14:19:17 -0700 Subject: [PATCH 30/64] [ReactNative] Don't activate Chrome when debugger is already open Summary: Before this diff every time you reload in debug mode Chrome window is actiavated. Looks like that behaviour is pretty annoying. Fixes #689 @public Test Plan: ``` $ ./packager/launchChromeDevTools.applescript 'https://www.facebook.com/' ``` First time it opens a new tab and activates Chrome, running this again does not activate Chrome if the tab already exists. --- packager/launchChromeDevTools.applescript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/launchChromeDevTools.applescript b/packager/launchChromeDevTools.applescript index 4b718f5bd..a83907905 100755 --- a/packager/launchChromeDevTools.applescript +++ b/packager/launchChromeDevTools.applescript @@ -11,7 +11,6 @@ on run argv set theURL to item 1 of argv tell application "Chrome" - activate if (count every window) = 0 then make new window @@ -40,6 +39,7 @@ on run argv set theWindow's active tab index to theTabIndex else tell window 1 + activate make new tab with properties {URL:theURL} end tell end if From 080d3b9f62ff02c8471378e114ada1efd5e1d27d Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 19 Jun 2015 14:59:42 -0700 Subject: [PATCH 31/64] [ReactNative] Add PerformanceLogger to measure TTI Summary: @public Add PerformanceLogger to keep track of JS download, initial script execution and full TTI. Test Plan: The Native side currently calls `addTimespans` when it's finish initializing with the six values (start and end for the three events), so I just checked it with a `PerformanceLogger.logTimespans()` at the end of the function. ``` 2015-06-18 16:47:19.096 [info][tid:com.facebook.React.JavaScript] "ScriptDownload: 48ms" 2015-06-18 16:47:19.096 [info][tid:com.facebook.React.JavaScript] "ScriptExecution: 106ms" 2015-06-18 16:47:19.096 [info][tid:com.facebook.React.JavaScript] "TTI: 293ms" ``` --- .../InitializeJavaScriptAppEngine.js | 1 + Libraries/Utilities/PerformanceLogger.js | 96 +++++++++++++++++++ React/Base/RCTBridge.m | 5 + React/Base/RCTPerformanceLogger.h | 24 +++++ React/Base/RCTPerformanceLogger.m | 77 +++++++++++++++ React/Base/RCTRootView.m | 2 + React/Executors/RCTContextExecutor.m | 3 + React/React.xcodeproj/project.pbxproj | 6 ++ 8 files changed, 214 insertions(+) create mode 100644 Libraries/Utilities/PerformanceLogger.js create mode 100644 React/Base/RCTPerformanceLogger.h create mode 100644 React/Base/RCTPerformanceLogger.m diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 2896b01ab..8b6218882 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -24,6 +24,7 @@ // Just to make sure the JS gets packaged up. require('RCTDeviceEventEmitter'); +require('PerformanceLogger'); if (typeof GLOBAL === 'undefined') { GLOBAL = this; diff --git a/Libraries/Utilities/PerformanceLogger.js b/Libraries/Utilities/PerformanceLogger.js new file mode 100644 index 000000000..c05c4b7c2 --- /dev/null +++ b/Libraries/Utilities/PerformanceLogger.js @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule PerformanceLogger + */ +'use strict'; + + +var performanceNow = require('performanceNow'); + +var timespans = {}; + +/** + * This is meant to collect and log performance data in production, which means + * it needs to have minimal overhead. + */ +var PerformanceLogger = { + addTimespan(key, lengthInMs, description) { + if (timespans[key]) { + if (__DEV__) { + console.log( + 'PerformanceLogger: Attempting to add a timespan that already exists' + ); + } + return; + } + + timespans[key] = { + description: description, + totalTime: lengthInMs, + }; + }, + + startTimespan(key, description) { + if (timespans[key]) { + if (__DEV__) { + console.log( + 'PerformanceLogger: Attempting to start a timespan that already exists' + ); + } + return; + } + + timespans[key] = { + description: description, + startTime: performanceNow(), + }; + }, + + stopTimespan(key) { + if (!timespans[key] || !timespans[key].startTime) { + if (__DEV__) { + console.log( + 'PerformanceLogger: Attempting to end a timespan that has not started' + ); + } + return; + } + + timespans[key].endTime = performanceNow(); + timespans[key].totalTime = + timespans[key].endTime - timespans[key].startTime; + }, + + clearTimespans() { + timespans = {}; + }, + + getTimespans() { + return timespans; + }, + + logTimespans() { + for (var key in timespans) { + console.log(key + ': ' + timespans[key].totalTime + 'ms'); + } + }, + + addTimespans(newTimespans, labels) { + for (var i = 0, l = newTimespans.length; i < l; i += 2) { + var label = labels[i / 2]; + PerformanceLogger.addTimespan( + label, + (newTimespans[i + 1] - newTimespans[i]), + label + ); + } + } +}; + +module.exports = PerformanceLogger; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 924fd0ae7..a50079440 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -20,6 +20,7 @@ #import "RCTKeyCommands.h" #import "RCTLog.h" #import "RCTPerfStats.h" +#import "RCTPerformanceLogger.h" #import "RCTProfile.h" #import "RCTRedBox.h" #import "RCTRootView.h" @@ -612,6 +613,8 @@ dispatch_queue_t RCTJSThread; RCTAssertMainThread(); if ((self = [super init])) { + RCTPerformanceLoggerStart(RCTPLTTI); + _bundleURL = bundleURL; _moduleProvider = block; _launchOptions = [launchOptions copy]; @@ -981,8 +984,10 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module } else { RCTProfileBeginEvent(); + RCTPerformanceLoggerStart(RCTPLScriptDownload); RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) { + RCTPerformanceLoggerEnd(RCTPLScriptDownload); RCTProfileEndEvent(@"JavaScript dowload", @"init,download", @[]); _loading = NO; diff --git a/React/Base/RCTPerformanceLogger.h b/React/Base/RCTPerformanceLogger.h new file mode 100644 index 000000000..4c220d170 --- /dev/null +++ b/React/Base/RCTPerformanceLogger.h @@ -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. + */ + +#import + +#import "RCTDefines.h" +#import "RCTBridgeModule.h" + +typedef NS_ENUM(NSUInteger, RCTPLTag) { + RCTPLScriptDownload = 0, + RCTPLAppScriptExecution, + RCTPLTTI, + RCTPLSize +}; + +void RCTPerformanceLoggerStart(RCTPLTag tag); +void RCTPerformanceLoggerEnd(RCTPLTag tag); +NSArray *RCTPerformanceLoggerOutput(void); diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m new file mode 100644 index 000000000..b43ab2252 --- /dev/null +++ b/React/Base/RCTPerformanceLogger.m @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "RCTPerformanceLogger.h" +#import "RCTRootView.h" + +static int64_t RCTPLData[RCTPLSize][2] = {}; + +void RCTPerformanceLoggerStart(RCTPLTag tag) +{ + RCTPLData[tag][0] = CACurrentMediaTime() * 1000; +} + +void RCTPerformanceLoggerEnd(RCTPLTag tag) +{ + RCTPLData[tag][1] = CACurrentMediaTime() * 1000; +} + +NSArray *RCTPerformanceLoggerOutput(void) +{ + return @[ + @(RCTPLData[0][0]), + @(RCTPLData[0][1]), + @(RCTPLData[1][0]), + @(RCTPLData[1][1]), + @(RCTPLData[2][0]), + @(RCTPLData[2][1]), + ]; +} + +@interface RCTPerformanceLogger : NSObject + +@end + +@implementation RCTPerformanceLogger + +RCT_EXPORT_MODULE() + +@synthesize bridge = _bridge; + +- (instancetype)init +{ + if ((self = [super init])) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sendTimespans) + name:RCTContentDidAppearNotification + object:nil]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)sendTimespans +{ + [_bridge enqueueJSCall:@"PerformanceLogger.addTimespans" args:@[ + RCTPerformanceLoggerOutput(), + @[ + @"ScriptDownload", + @"ScriptExecution", + @"TTI", + ], + ]]; +} + +@end diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 094e88840..6f9e7c71c 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -17,6 +17,7 @@ #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" +#import "RCTPerformanceLogger.h" #import "RCTSourceCode.h" #import "RCTTouchHandler.h" #import "RCTUIManager.h" @@ -247,6 +248,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; + RCTPerformanceLoggerEnd(RCTPLTTI); dispatch_async(dispatch_get_main_queue(), ^{ if (!_contentHasAppeared) { _contentHasAppeared = YES; diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 54736613e..200a3f645 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -17,6 +17,7 @@ #import "RCTDefines.h" #import "RCTLog.h" #import "RCTProfile.h" +#import "RCTPerformanceLogger.h" #import "RCTUtils.h" @interface RCTJavaScriptContext : NSObject @@ -446,12 +447,14 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) if (!strongSelf || !strongSelf.isValid) { return; } + RCTPerformanceLoggerStart(RCTPLAppScriptExecution); JSValueRef jsError = NULL; JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script); JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString); JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError); JSStringRelease(jsURL); JSStringRelease(execJSString); + RCTPerformanceLoggerEnd(RCTPLAppScriptExecution); if (onComplete) { NSError *error; diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 81f65d39a..6d10e2978 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; }; 1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */; }; 14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; }; + 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 142014171B32094000CC17BA /* RCTPerformanceLogger.m */; }; 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; }; 14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; }; 146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; }; @@ -177,6 +178,8 @@ 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfStats.m; sourceTree = ""; }; 14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = ""; }; 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = ""; }; + 142014171B32094000CC17BA /* RCTPerformanceLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerformanceLogger.m; sourceTree = ""; }; + 142014181B32094000CC17BA /* RCTPerformanceLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPerformanceLogger.h; sourceTree = ""; }; 1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = ""; }; 14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = ""; }; 14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = ""; }; @@ -439,6 +442,8 @@ 146459251B06C49500B389AA /* RCTFPSGraph.m */, 1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */, 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */, + 142014171B32094000CC17BA /* RCTPerformanceLogger.m */, + 142014181B32094000CC17BA /* RCTPerformanceLogger.h */, ); path = Base; sourceTree = ""; @@ -551,6 +556,7 @@ 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, + 142014191B32094000CC17BA /* RCTPerformanceLogger.m in Sources */, 83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */, 83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */, 13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */, From 0fec3550764768579a0c0a94200891aee92d098a Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 15:14:39 -0700 Subject: [PATCH 32/64] [react-packager] Cache based on options, not url Summary: @public We cached based on url, which wasn't unique becuase some options would be defaulted. This was obvious when starting the server via fbrnios which tries to warmup the bundle. And then when the device woke up it will send a request (that is identical in reality) but would miss the cache. This changes the cache key into a JSON stringification of the options. Test Plan: * ./runJestTests.sh * ./fbrnios.sh run --- packager/react-packager/src/Server/index.js | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 0ce5c5847..3e8ca9e8b 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -131,13 +131,14 @@ Server.prototype._onFileChange = function(type, filepath, root) { Server.prototype._rebuildPackages = function() { var buildPackage = this.buildPackage.bind(this); var packages = this._packages; - Object.keys(packages).forEach(function(key) { - var options = getOptionsFromUrl(key); + + Object.keys(packages).forEach(function(optionsJson) { + var options = JSON.parse(optionsJson); // Wait for a previous build (if exists) to finish. - packages[key] = (packages[key] || Promise.resolve()).finally(function() { + packages[optionsJson] = (packages[optionsJson] || Promise.resolve()).finally(function() { // With finally promise callback we can't change the state of the promise // so we need to reassign the promise. - packages[key] = buildPackage(options).then(function(p) { + packages[optionsJson] = buildPackage(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.inlineSourceMap, @@ -146,7 +147,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); - return packages[key]; + return packages[optionsJson]; }); }; @@ -228,9 +229,9 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { res.end(ret); } else if (parts[1] === 'packages') { ret += '

Cached Packages

'; - Promise.all(Object.keys(this._packages).map(function(url) { - return this._packages[url].then(function(p) { - ret += '

' + url + '

'; + Promise.all(Object.keys(this._packages).map(function(optionsJson) { + return this._packages[optionsJson].then(function(p) { + ret += '

' + optionsJson + '

'; ret += p.getDebugInfo(); }); }, this)).then( @@ -350,10 +351,11 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromUrl(req.url); - var building = this._packages[req.url] || this.buildPackage(options); + var optionsJson = JSON.stringify(options); + var building = this._packages[optionsJson] || this.buildPackage(options); - this._packages[req.url] = building; - building.then( + this._packages[optionsJson] = building; + building.then( function(p) { if (requestType === 'bundle') { res.end(p.getSource({ From 93e98da90845de3b575bed65c729bdedb148452e Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Fri, 19 Jun 2015 15:55:08 -0700 Subject: [PATCH 33/64] [Navigator]: Add a getter to get the current route that is focused from the navigation context. --- .../Navigator/Navigation/NavigationContext.js | 31 ++++++++--- .../__tests__/NavigationContext-test.js | 51 +++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js index 8169415eb..d146f14d0 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js @@ -23,25 +23,34 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @providesModule NavigationContext - * @flow */ 'use strict'; var NavigationEventEmitter = require('NavigationEventEmitter'); -var emptyFunction = require('emptyFunction'); -type EventSubscription = { - remove: Function -}; +var emptyFunction = require('emptyFunction'); +var invariant = require('invariant'); + +import type * as NavigationEvent from 'NavigationEvent'; +import type * as EventSubscription from 'EventSubscription'; /** * Class that contains the info and methods for app navigation. */ class NavigationContext { _eventEmitter: ?NavigationEventEmitter; + _currentRoute: any; constructor() { this._eventEmitter = new NavigationEventEmitter(this); + this._currentRoute = null; + this.addListener('didfocus', this._onDidFocus, this); + } + + // TODO: @flow does not like this getter. Will add @flow check back once + // getter/setter is supported. + get currentRoute(): any { + return this._currentRoute; } addListener( @@ -64,13 +73,23 @@ class NavigationContext { } } - dispose() { + dispose(): void { var emitter = this._eventEmitter; if (emitter) { + // clean up everything. emitter.removeAllListeners(); this._eventEmitter = null; + this._currentRoute = null; } } + + _onDidFocus(event: NavigationEvent): void { + invariant( + event.data && event.data.hasOwnProperty('route'), + 'didfocus event should provide route' + ); + this._currentRoute = event.data.route; + } } module.exports = NavigationContext; diff --git a/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js new file mode 100644 index 000000000..796d5633e --- /dev/null +++ b/Libraries/CustomComponents/Navigator/Navigation/__tests__/NavigationContext-test.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015, Facebook, Inc. All rights reserved. + * + * Facebook, Inc. (“Facebook”) owns all right, title and interest, including + * all intellectual property and other proprietary rights, in and to the React + * Native CustomComponents software (the “Software”). Subject to your + * compliance with these terms, you are hereby granted a non-exclusive, + * worldwide, royalty-free copyright license to (1) use and copy the Software; + * and (2) reproduce and distribute the Software as part of your own software + * (“Your Software”). Facebook reserves all rights not expressly granted to + * you in this license agreement. + * + * THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. + * IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR + * EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +'use strict'; + +jest + .dontMock('EmitterSubscription') + .dontMock('EventEmitter') + .dontMock('EventSubscriptionVendor') + .dontMock('NavigationContext') + .dontMock('NavigationEvent') + .dontMock('NavigationEventEmitter') + .dontMock('invariant'); + +var NavigationContext = require('NavigationContext'); + +describe('NavigationContext', () => { + it('defaults `currentRoute` to null', () => { + var context = new NavigationContext(); + expect(context.currentRoute).toEqual(null); + }); + + it('updates `currentRoute`', () => { + var context = new NavigationContext(); + context.emit('didfocus', {route: {name: 'a'}}); + expect(context.currentRoute.name).toEqual('a'); + }); +}); + + From 5c1ac2a753cbf241f4ca84dd408cacfb77676e01 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Fri, 19 Jun 2015 15:58:12 -0700 Subject: [PATCH 34/64] [ReactNative] Block native from becoming js responder --- .../ReactNative/ReactNativeGlobalResponderHandler.js | 5 +++-- .../vendor/react/browser/eventPlugins/PanResponder.js | 9 +++++++++ .../react/browser/eventPlugins/ResponderEventPlugin.js | 10 ++++++---- React/Modules/RCTUIManager.m | 3 ++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js index 3ba933e8c..1f548c3eb 100644 --- a/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js +++ b/Libraries/ReactNative/ReactNativeGlobalResponderHandler.js @@ -15,10 +15,11 @@ var RCTUIManager = require('NativeModules').UIManager; var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactNativeGlobalResponderHandler = { - onChange: function(from: string, to: string) { + onChange: function(from: string, to: string, blockNativeResponder: boolean) { if (to !== null) { RCTUIManager.setJSResponder( - ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to) + ReactNativeTagHandles.mostRecentMountedNodeHandleForRootNodeID(to), + blockNativeResponder ); } else { RCTUIManager.clearJSResponder(); diff --git a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js index 721da744a..b6f0a765a 100644 --- a/Libraries/vendor/react/browser/eventPlugins/PanResponder.js +++ b/Libraries/vendor/react/browser/eventPlugins/PanResponder.js @@ -73,6 +73,11 @@ var currentCentroidY = TouchHistoryMath.currentCentroidY; * // Another component has become the responder, so this gesture * // should be cancelled * }, + * onShouldBlockNativeResponder: (evt, gestureState) => { + * // Returns whether this component should block native components from becoming the JS + * // responder. Returns true by default. Is currently only supported on android. + * return true; + * }, * }); * }, * @@ -241,6 +246,7 @@ var PanResponder = { * - `onPanResponderMove: (e, gestureState) => {...}` * - `onPanResponderTerminate: (e, gestureState) => {...}` * - `onPanResponderTerminationRequest: (e, gestureState) => {...}` + * - 'onShouldBlockNativeResponder: (e, gestureState) => {...}' * * In general, for events that have capture equivalents, we update the * gestureState once in the capture phase and can use it in the bubble phase @@ -298,6 +304,9 @@ var PanResponder = { gestureState.dx = 0; gestureState.dy = 0; config.onPanResponderGrant && config.onPanResponderGrant(e, gestureState); + // TODO: t7467124 investigate if this can be removed + return config.onShouldBlockNativeResponder === undefined ? true : + config.onShouldBlockNativeResponder(); }, onResponderReject: function(e) { diff --git a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js b/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js index 141b0a970..2a4df8c8d 100644 --- a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js +++ b/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js @@ -55,13 +55,14 @@ var trackedTouchCount = 0; */ var previousActiveTouches = 0; -var changeResponder = function(nextResponderID) { +var changeResponder = function(nextResponderID, blockNativeResponder) { var oldResponderID = responderID; responderID = nextResponderID; if (ResponderEventPlugin.GlobalResponderHandler !== null) { ResponderEventPlugin.GlobalResponderHandler.onChange( oldResponderID, - nextResponderID + nextResponderID, + blockNativeResponder ); } }; @@ -379,6 +380,7 @@ function setResponderAndExtractTransfer( grantEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; EventPropagators.accumulateDirectDispatches(grantEvent); + var blockNativeResponder = executeDirectDispatch(grantEvent) === true; if (responderID) { var terminationRequestEvent = ResponderSyntheticEvent.getPooled( @@ -404,7 +406,7 @@ function setResponderAndExtractTransfer( terminateEvent.touchHistory = ResponderTouchHistoryStore.touchHistory; EventPropagators.accumulateDirectDispatches(terminateEvent); extracted = accumulate(extracted, [grantEvent, terminateEvent]); - changeResponder(wantsResponderID); + changeResponder(wantsResponderID, blockNativeResponder); } else { var rejectEvent = ResponderSyntheticEvent.getPooled( eventTypes.responderReject, @@ -417,7 +419,7 @@ function setResponderAndExtractTransfer( } } else { extracted = accumulate(extracted, grantEvent); - changeResponder(wantsResponderID); + changeResponder(wantsResponderID, blockNativeResponder); } return extracted; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 770bc0580..839682f26 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1215,7 +1215,8 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag * JS sets what *it* considers to be the responder. Later, scroll views can use * this in order to determine if scrolling is appropriate. */ -RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag) +RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag + blockNativeResponder:(BOOL)blockNativeResponder) { [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = viewRegistry[reactTag]; From 2d4055e513fea0cb36873447d67e64229a187aad Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 18:01:21 -0700 Subject: [PATCH 35/64] [react-packager] Rewrite dependency graph (support node_modules, speed, fix bugs etc) Summary: @public Fixes #773, #1055 The resolver was getting a bit unwieldy because a lot has changed since the initial writing (porting node-haste). This also splits up a large complex file into the following: * Makes use of classes: Module, AssetModule, Package, and AssetModule_DEPRECATED (`image!` modules) * DependencyGraph is lazy for everything that isn't haste modules and packages (need to read ahead of time) * Lazy makes it fast, easier to reason about, and easier to add new loaders * Has a centralized filesystem wrapper: fast-fs (ffs) * ffs is async and lazy for any read operation and sync for directory/file lookup which makes it fast * we can easily drop in different adapters for ffs to be able to build up the tree: watchman, git ls-files, etc * use es6 for classes and easier to read promise-based code Follow up diffs will include: * Using new types (Module, AssetModule etc) in the rest of the codebase (currently we convert to plain object which is a bit of a hack) * using watchman to build up the fs * some caching at the object creation level (we are recreating Modules and Packages many times, we can cache them) * A plugin system for loaders (e.g. @tadeuzagallo wants to add a native module loader) Test Plan: * ./runJestTests.sh react-packager * ./runJestTests.sh PackagerIntegration * Export open source and run the e2e test * reset cache * ./fbrnios.sh run and click around --- .../Initialization/SourceMap.js | 2 + packager/blacklist.js | 1 - packager/react-packager/.babelrc | 24 + packager/react-packager/index.js | 4 + .../react-packager/src/AssetServer/index.js | 20 +- .../src/DependencyResolver/AssetModule.js | 46 + .../AssetModule_DEPRECATED.js | 40 + .../__tests__/DependencyGraph-test.js | 3241 +++++++++++++++++ .../{haste => }/DependencyGraph/docblock.js | 0 .../DependencyGraph/index.js | 556 +++ .../src/DependencyResolver/Module.js | 133 + .../src/DependencyResolver/ModuleCache.js | 72 + .../DependencyResolver/ModuleDescriptor.js | 61 - .../src/DependencyResolver/Package.js | 84 + .../__tests__/HasteDependencyResolver-test.js | 334 +- .../src/DependencyResolver/fastfs.js | 302 ++ .../__tests__/DependencyGraph-test.js | 1612 -------- .../haste/DependencyGraph/index.js | 798 ---- .../src/DependencyResolver/haste/index.js | 176 - .../src/DependencyResolver/index.js | 177 +- .../src/DependencyResolver/node/index.js | 51 - .../polyfills/Array.prototype.es6.js | 0 .../polyfills/String.prototype.es6.js | 0 .../{haste => }/polyfills/console.js | 0 .../{haste => }/polyfills/error-guard.js | 0 .../{haste => }/polyfills/polyfills.js | 0 .../{haste => }/polyfills/prelude.js | 0 .../{haste => }/polyfills/prelude_dev.js | 0 .../{haste => }/polyfills/require.js | 0 .../{haste => }/replacePatterns.js | 0 .../src/Packager/__tests__/Packager-test.js | 2 +- packager/react-packager/src/Packager/index.js | 21 +- packager/react-packager/src/__mocks__/fs.js | 20 +- 33 files changed, 4883 insertions(+), 2894 deletions(-) create mode 100644 packager/react-packager/.babelrc create mode 100644 packager/react-packager/src/DependencyResolver/AssetModule.js create mode 100644 packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js create mode 100644 packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js rename packager/react-packager/src/DependencyResolver/{haste => }/DependencyGraph/docblock.js (100%) create mode 100644 packager/react-packager/src/DependencyResolver/DependencyGraph/index.js create mode 100644 packager/react-packager/src/DependencyResolver/Module.js create mode 100644 packager/react-packager/src/DependencyResolver/ModuleCache.js delete mode 100644 packager/react-packager/src/DependencyResolver/ModuleDescriptor.js create mode 100644 packager/react-packager/src/DependencyResolver/Package.js rename packager/react-packager/src/DependencyResolver/{haste => }/__tests__/HasteDependencyResolver-test.js (65%) create mode 100644 packager/react-packager/src/DependencyResolver/fastfs.js delete mode 100644 packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js delete mode 100644 packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js delete mode 100644 packager/react-packager/src/DependencyResolver/haste/index.js delete mode 100644 packager/react-packager/src/DependencyResolver/node/index.js rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/Array.prototype.es6.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/String.prototype.es6.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/console.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/error-guard.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/polyfills.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/prelude.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/prelude_dev.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/polyfills/require.js (100%) rename packager/react-packager/src/DependencyResolver/{haste => }/replacePatterns.js (100%) diff --git a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js index 311e573fc..53d08f829 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js +++ b/Libraries/JavaScriptAppEngine/Initialization/SourceMap.js @@ -1,3 +1,4 @@ + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -8,6 +9,7 @@ * * @providesModule SourceMap * @generated + * @extern * * This module was generated from `node_modules/source-map` by running * diff --git a/packager/blacklist.js b/packager/blacklist.js index 9bfeda509..a2ba71673 100644 --- a/packager/blacklist.js +++ b/packager/blacklist.js @@ -11,7 +11,6 @@ // Don't forget to everything listed here to `testConfig.json` // modulePathIgnorePatterns. var sharedBlacklist = [ - __dirname, 'website', 'node_modules/react-tools/src/utils/ImmutableObject.js', 'node_modules/react-tools/src/core/ReactInstanceHandles.js', diff --git a/packager/react-packager/.babelrc b/packager/react-packager/.babelrc new file mode 100644 index 000000000..c1b12d816 --- /dev/null +++ b/packager/react-packager/.babelrc @@ -0,0 +1,24 @@ +// Keep in sync with packager/transformer.js +{ + "retainLines": true, + "compact": true, + "comments": false, + "whitelist": [ + "es6.arrowFunctions", + "es6.blockScoping", + // This is the only place where we differ from transformer.js + "es6.constants", + "es6.classes", + "es6.destructuring", + "es6.parameters.rest", + "es6.properties.computed", + "es6.properties.shorthand", + "es6.spread", + "es6.templateLiterals", + "es7.trailingFunctionCommas", + "es7.objectRestSpread", + "flow", + "react" + ], + "sourceMaps": false +} diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index 6be111997..d4ea0dd39 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -8,6 +8,10 @@ */ 'use strict'; +require('babel/register')({ + only: /react-packager\/src/ +}); + useGracefulFs(); var Activity = require('./src/Activity'); diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js index 9bae26823..0479afc2a 100644 --- a/packager/react-packager/src/AssetServer/index.js +++ b/packager/react-packager/src/AssetServer/index.js @@ -15,7 +15,7 @@ var Promise = require('bluebird'); var fs = require('fs'); var crypto = require('crypto'); -var lstat = Promise.promisify(fs.lstat); +var stat = Promise.promisify(fs.stat); var readDir = Promise.promisify(fs.readdir); var readFile = Promise.promisify(fs.readFile); @@ -98,14 +98,14 @@ AssetServer.prototype.getAssetData = function(assetPath) { return Promise.all( record.files.map(function(file) { - return lstat(file); + return stat(file); }) ); }).then(function(stats) { var hash = crypto.createHash('md5'); - stats.forEach(function(stat) { - hash.update(stat.mtime.getTime().toString()); + stats.forEach(function(fstat) { + hash.update(fstat.mtime.getTime().toString()); }); data.hash = hash.digest('hex'); @@ -117,18 +117,18 @@ function findRoot(roots, dir) { return Promise.some( roots.map(function(root) { var absPath = path.join(root, dir); - return lstat(absPath).then(function(stat) { - if (!stat.isDirectory()) { + return stat(absPath).then(function(fstat) { + if (!fstat.isDirectory()) { throw new Error('Looking for dirs'); } - stat._path = absPath; - return stat; + fstat._path = absPath; + return fstat; }); }), 1 ).spread( - function(stat) { - return stat._path; + function(fstat) { + return fstat._path; } ); } diff --git a/packager/react-packager/src/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js new file mode 100644 index 000000000..431e6d01c --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/AssetModule.js @@ -0,0 +1,46 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('bluebird'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule extends Module { + + isHaste() { + return Promise.resolve(false); + } + + getDependencies() { + return Promise.resolve([]); + } + + _read() { + return Promise.resolve({}); + } + + getName() { + return super.getName().then(id => { + const {name, type} = getAssetDataFromName(this.path); + return id.replace(/\/[^\/]+$/, `/${name}.${type}`); + }); + } + + getPlainObject() { + return this.getName().then(name => this.addReference({ + path: this.path, + isJSON: false, + isAsset: true, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: getAssetDataFromName(this.path).resolution, + id: name, + dependencies: [], + })); + } + + hash() { + return `AssetModule : ${this.path}`; + } +} + +module.exports = AssetModule; diff --git a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js new file mode 100644 index 000000000..7a25c5905 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -0,0 +1,40 @@ +'use strict'; + +const Module = require('./Module'); +const Promise = require('bluebird'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); + +class AssetModule_DEPRECATED extends Module { + isHaste() { + return Promise.resolve(false); + } + + getName() { + return Promise.resolve(this.name); + } + + getDependencies() { + return Promise.resolve([]); + } + + getPlainObject() { + const {name, resolution} = getAssetDataFromName(this.path); + + return Promise.resolve(this.addReference({ + path: this.path, + id: `image!${name}`, + resolution, + isAsset_DEPRECATED: true, + dependencies: [], + isJSON: false, + isPolyfill: false, + isAsset: false, + })); + } + + hash() { + return `AssetModule_DEPRECATED : ${this.path}`; + } +} + +module.exports = AssetModule_DEPRECATED; diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js new file mode 100644 index 000000000..dbe7dc3aa --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -0,0 +1,3241 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('../index') + .dontMock('crypto') + .dontMock('absolute-path') + .dontMock('../docblock') + .dontMock('../../replacePatterns') + .dontMock('../../../lib/getAssetDataFromName') + .dontMock('../../fastfs') + .dontMock('../../AssetModule_DEPRECATED') + .dontMock('../../AssetModule') + .dontMock('../../Module') + .dontMock('../../Package') + .dontMock('../../ModuleCache'); + +jest.mock('fs'); + +describe('DependencyGraph', function() { + var DependencyGraph; + var fileWatcher; + var fs; + + beforeEach(function() { + fs = require('fs'); + DependencyGraph = require('../index'); + + fileWatcher = { + on: function() { + return this; + } + }; + }); + + describe('getOrderedDependencies', function() { + pit('should get dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + ]); + }); + }); + + pit('should get dependencies with the correct extensions', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")' + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + 'a.js.orig': [ + '/**', + ' * @providesModule a', + ' */', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get json dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'package' + }), + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./a.json")', + 'require("./b")' + ].join('\n'), + 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./a.json', './b'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/a.json', + isJSON: true, + path: '/root/a.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should get dependencies with deprecated assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!a")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with relative assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")' + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get dependencies with assets and resolution', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png");', + 'require("./imgs/b.png");', + 'require("./imgs/c.png");', + ].join('\n'), + 'imgs': { + 'a@1.5x.png': '', + 'b@.7x.png': '', + 'c.png': '', + 'c@2x.png': '', + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [ + './imgs/a.png', + './imgs/b.png', + './imgs/c.png', + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a@1.5x.png', + resolution: 1.5, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/b.png', + path: '/root/imgs/b@.7x.png', + resolution: 0.7, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/c.png', + path: '/root/imgs/c.png', + resolution: 1, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('Deprecated and relative assets can live together', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png")', + 'require("image!a")', + ].join('\n'), + 'imgs': { + 'a.png': '' + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + assetRoots_DEPRECATED: ['/root/imgs'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + + pit('should get recursive dependencies', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("a")', + ].join('\n'), + 'a.js': [ + '/**', + ' * @providesModule a', + ' */', + 'require("index")', + ].join('\n'), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'a', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with packages with a dot in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + 'require("x.y.z")', + ].join('\n'), + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + }, + 'x.y.z': { + 'package.json': JSON.stringify({ + name: 'x.y.z', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'x.y.z/main.js', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default main package to index.js', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': 'lol', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve using alternative ids', () => { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + }), + 'index.js': [ + '/**', + ' * @providesModule EpicModule', + ' */', + ].join('\n'), + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'EpicModule', + path: '/root/aPackage/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should default use index.js if main is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require("aPackage")', + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'lib', + }), + lib: { + 'index.js': 'lol', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/lib/index.js', + path: '/root/aPackage/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to index if it is a dir', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'index.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'test/lib/index.js', + path: '/root/lib/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should resolve require to main if it is a dir w/ a package.json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'package.json': JSON.stringify({ + 'main': 'main.js', + }), + 'index.js': 'lol', + 'main.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'test/index.js', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore malformed packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': 'lol', + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('can have multiple modules with the same name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("b")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule b', + ' */', + ].join('\n'), + 'c.js': [ + '/**', + ' * @providesModule c', + ' */', + ].join('\n'), + 'somedir': { + 'somefile.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("c")', + ].join('\n') + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/somedir/somefile.js', + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'c', + path: '/root/c.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('providesModule wins when conflict with package', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'b.js': [ + '/**', + ' * @providesModule aPackage', + ' */', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + 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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage', + path: '/root/b.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should be forgiving with missing requires', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("lolomg")', + ].join('\n') + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + }); + }); + + pit('should work with packages with subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with packages with symlinked subdirs', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'symlinkedPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'lol', + 'subdir': { + 'lolynot.js': 'lolynot' + } + }, + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage/subdir/lolynot")', + ].join('\n'), + 'aPackage': { SYMLINK: '/symlinkedPackage' }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work with relative modules in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'require("./subdir/lolynot")', + 'subdir': { + 'lolynot.js': 'require("../other")' + }, + 'other.js': 'some code' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/subdir/lolynot.js', + path: '/root/aPackage/subdir/lolynot.js', + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/other.js', + path: '/root/aPackage/other.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support simple browser field in packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client.js', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser field in packages w/o .js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'client', + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support mapping main in browser field json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should work do correct browser mapping w/o js ext', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping of files', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main': './client.js', + './node.js': './not-node.js', + './not-browser': './browser.js', + './dir/server.js': './dir/client', + './hello.js': './bye.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'require("./node")\nrequire("./dir/server.js")', + 'not-node.js': 'require("./not-browser")', + 'not-browser.js': 'require("./dir/server")', + 'browser.js': 'some browser code', + 'dir': { + 'server.js': 'some node code', + 'client.js': 'require("../hello")', + }, + 'hello.js': 'hello', + 'bye.js': 'bye', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/client.js', + path: '/root/aPackage/client.js', + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/not-node.js', + path: '/root/aPackage/not-node.js', + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/dir/client.js', + path: '/root/aPackage/dir/client.js', + dependencies: ['../hello'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/bye.js', + path: '/root/aPackage/bye.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should support browser mapping for packages', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + browser: { + 'node-package': 'browser-package', + } + }), + 'index.js': 'require("node-package")', + 'node-package': { + 'package.json': JSON.stringify({ + 'name': 'node-package', + }), + 'index.js': 'some node code', + }, + 'browser-package': { + 'package.json': JSON.stringify({ + 'name': 'browser-package', + }), + 'index.js': 'some browser code', + }, + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'aPackage/index.js', + path: '/root/aPackage/index.js', + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { id: 'browser-package/index.js', + path: '/root/aPackage/browser-package/index.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('node_modules', function() { + pit('should work with nested node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with browser field', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + browser: { + './lol': './wow' + } + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + 'wow.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + browser: './main2', + }), + 'main2.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/lol.js', + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar/main2.js', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('node_modules should support multi level', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': '', + }, + }, + 'path': { + 'to': { + 'bar.js': [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")', + ].join('\n'), + }, + 'node_modules': {}, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-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(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'submodule/main.js', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { + // For example SourceMap.js implements it's own require system. + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/lol");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + + pit('should work with node packages with a .js in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + ].join('\n'), + 'node_modules': { + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'sha.js/main.js', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + describe('file watch updating', function() { + var triggerFileChange; + var mockStat = { + isDirectory: () => false + }; + + beforeEach(function() { + var callbacks = []; + triggerFileChange = (...args) => + callbacks.map(callback => callback(...args)); + + fileWatcher = { + on: function(eventType, callback) { + if (eventType !== 'all') { + throw new Error('Can only handle "all" event in watcher.'); + } + callbacks.push(callback); + return this; + } + }; + }); + + pit('updates module dependencies', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js', root, mockStat); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file change', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = + filesystem.root['index.js'].replace('require("foo")', ''); + triggerFileChange('change', 'index.js', root, mockStat); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file delete', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + delete filesystem.root.foo; + triggerFileChange('delete', 'foo.js', root); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on file add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'bar', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on deprecated asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots_DEPRECATED: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates module dependencies on relative asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./foo.png")' + ].join('\n'), + 'package.json': JSON.stringify({ + name: 'aPackage' + }), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher, + }); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('runs changes through ignore filter', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + ignoreFilePath: function(filePath) { + if (filePath === '/root/bar.js') { + return true; + } + return false; + } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['bar.js'] = [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")' + ].join('\n'); + triggerFileChange('add', 'bar.js', root, mockStat); + + filesystem.root.aPackage['main.js'] = 'require("bar")'; + triggerFileChange('change', 'aPackage/main.js', root, mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should ignore directory updates', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + 'require("foo")' + ].join('\n'), + 'foo.js': [ + '/**', + ' * @providesModule foo', + ' */', + 'require("aPackage")' + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + triggerFileChange('change', 'aPackage', '/root', { + isDirectory: function(){ return true; } + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates package.json', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + 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, mockStat); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main.js', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('changes to browser field', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser.js', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('removes old package from cache', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main.js', + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main.js', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package main changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + 'browser.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + 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', mockStat); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(deps2) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser.js', + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + }); +}); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/DependencyGraph/docblock.js rename to packager/react-packager/src/DependencyResolver/DependencyGraph/docblock.js diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js new file mode 100644 index 000000000..5bb630849 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -0,0 +1,556 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const path = require('path'); +const Fastfs = require('../fastfs'); +const ModuleCache = require('../ModuleCache'); +const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); +const declareOpts = require('../../lib/declareOpts'); +const isAbsolutePath = require('absolute-path'); +const debug = require('debug')('DependencyGraph'); +const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const util = require('util'); +const Promise = require('bluebird'); +const _ = require('underscore'); + +const validateOpts = declareOpts({ + roots: { + type: 'array', + required: true, + }, + ignoreFilePath: { + type: 'function', + default: function(){} + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetRoots_DEPRECATED: { + type: 'array', + default: [], + }, + assetExts: { + type: 'array', + required: true, + }, + providesModuleNodeModules: { + type: 'array', + default: [ + 'react-tools', + 'react-native', + // Parse requires AsyncStorage. They will + // change that to require('react-native') which + // should work after this release and we can + // remove it from here. + 'parse', + ], + }, +}); + +class DependencyGraph { + constructor(options) { + this._opts = validateOpts(options); + this._hasteMap = Object.create(null); + this._immediateResolutionCache = Object.create(null); + this.load(); + } + + load() { + if (this._loading) { + return this._loading; + } + + const modulePattern = new RegExp( + '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' + ); + + this._fastfs = new Fastfs(this._opts.roots,this._opts.fileWatcher, { + pattern: modulePattern, + ignore: this._opts.ignoreFilePath, + }); + + this._fastfs.on('change', this._processFileChange.bind(this)); + + this._moduleCache = new ModuleCache(this._fastfs); + + this._loading = Promise.all([ + this._fastfs.build().then(() => this._buildHasteMap()), + this._buildAssetMap_DEPRECATED(), + ]); + + return this._loading; + } + + resolveDependency(fromModule, toModuleName) { + if (fromModule._ref) { + fromModule = fromModule._ref; + } + + const resHash = resolutionHash(fromModule.path, toModuleName); + + if (this._immediateResolutionCache[resHash]) { + return Promise.resolve(this._immediateResolutionCache[resHash]); + } + + const asset_DEPRECATED = this._resolveAsset_DEPRECATED( + fromModule, + toModuleName + ); + if (asset_DEPRECATED) { + return Promise.resolve(asset_DEPRECATED); + } + + const cacheResult = (result) => { + this._immediateResolutionCache[resHash] = result; + return result; + }; + + const forgive = () => { + console.warn( + 'Unable to resolve module %s from %s', + toModuleName, + fromModule.path + ); + return null; + }; + + if (!this._isNodeModulesDir(fromModule.path) + && toModuleName[0] !== '.' && + toModuleName[0] !== '/') { + return this._resolveHasteDependency(fromModule, toModuleName).catch( + () => this._resolveNodeDependency(fromModule, toModuleName) + ).then( + cacheResult, + forgive + ); + } + + return this._resolveNodeDependency(fromModule, toModuleName) + .then( + cacheResult, + forgive + ); + } + + getOrderedDependencies(entryPath) { + return this.load().then(() => { + const absolutePath = path.resolve(this._getAbsolutePath(entryPath)); + + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._opts.roots + ); + } + + const entry = this._moduleCache.getModule(absolutePath); + const deps = []; + const visited = Object.create(null); + visited[entry.hash()] = true; + + const collect = (mod) => { + deps.push(mod); + return mod.getDependencies().then( + depNames => Promise.all( + depNames.map(name => this.resolveDependency(mod, name)) + ).then((dependencies) => [depNames, dependencies]) + ).then(([depNames, dependencies]) => { + let p = Promise.resolve(); + dependencies.forEach((modDep, i) => { + if (modDep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`', + depNames[i], + mod.path + ); + return; + } + + p = p.then(() => { + if (!visited[modDep.hash()]) { + visited[modDep.hash()] = true; + return collect(modDep); + } + return null; + }); + }); + + return p; + }); + }; + + return collect(entry) + .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))); + }); + } + + _getAbsolutePath(filePath) { + if (isAbsolutePath(filePath)) { + return filePath; + } + + for (let i = 0; i < this._opts.roots.length; i++) { + const root = this._opts.roots[i]; + const absPath = path.join(root, filePath); + if (this._fastfs.fileExists(absPath)) { + return absPath; + } + } + + return null; + } + + _resolveHasteDependency(fromModule, toModuleName) { + toModuleName = normalizePath(toModuleName); + + let p = fromModule.getPackage(); + if (p) { + p = p.redirectRequire(toModuleName); + } else { + p = Promise.resolve(toModuleName); + } + + return p.then((realModuleName) => { + let dep = this._hasteMap[realModuleName]; + + if (dep && dep.type === 'Module') { + return dep; + } + + let packageName = realModuleName; + + while (packageName && packageName !== '.') { + dep = this._hasteMap[packageName]; + if (dep && dep.type === 'Package') { + break; + } + packageName = path.dirname(packageName); + } + + if (dep && dep.type === 'Package') { + const potentialModulePath = path.join( + dep.root, + path.relative(packageName, realModuleName) + ); + return this._loadAsFile(potentialModulePath) + .catch(() => this._loadAsDir(potentialModulePath)); + } + + throw new Error('Unable to resolve dependency'); + }); + } + + _redirectRequire(fromModule, modulePath) { + return Promise.resolve(fromModule.getPackage()).then(p => { + if (p) { + return p.redirectRequire(modulePath); + } + return modulePath; + }); + } + + _resolveNodeDependency(fromModule, toModuleName) { + if (toModuleName[0] === '.' || toModuleName[1] === '/') { + const potentialModulePath = isAbsolutePath(toModuleName) ? + toModuleName : + path.join(path.dirname(fromModule.path), toModuleName); + return this._redirectRequire(fromModule, potentialModulePath).then( + realModuleName => this._loadAsFile(realModuleName) + .catch(() => this._loadAsDir(realModuleName)) + ); + } else { + return this._redirectRequire(fromModule, toModuleName).then( + realModuleName => { + const searchQueue = []; + for (let currDir = path.dirname(fromModule.path); + currDir !== '/'; + currDir = path.dirname(currDir)) { + searchQueue.push( + path.join(currDir, 'node_modules', realModuleName) + ); + } + + let p = Promise.reject(new Error('Node module not found')); + searchQueue.forEach(potentialModulePath => { + p = p.catch( + () => this._loadAsFile(potentialModulePath) + ).catch( + () => this._loadAsDir(potentialModulePath) + ); + }); + + return p; + }); + } + } + + _resolveAsset_DEPRECATED(fromModule, toModuleName) { + if (this._assetMap_DEPRECATED != null) { + const assetMatch = toModuleName.match(/^image!(.+)/); + // Process DEPRECATED global asset requires. + if (assetMatch && assetMatch[1]) { + if (!this._assetMap_DEPRECATED[assetMatch[1]]) { + debug('WARINING: Cannot find asset:', assetMatch[1]); + return null; + } + return this._assetMap_DEPRECATED[assetMatch[1]]; + } + } + return null; + } + + _isAssetFile(file) { + return this._opts.assetExts.indexOf(extname(file)) !== -1; + } + + _loadAsFile(potentialModulePath) { + return Promise.resolve().then(() => { + if (this._isAssetFile(potentialModulePath)) { + const {name, type} = getAssetDataFromName(potentialModulePath); + const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type); + // We arbitrarly grab the first one, because scale selection + // will happen somewhere + const [assetFile] = this._fastfs.matches( + path.dirname(potentialModulePath), + pattern + ); + + if (assetFile) { + return this._moduleCache.getAssetModule(assetFile); + } + } + + let file; + if (this._fastfs.fileExists(potentialModulePath)) { + file = potentialModulePath; + } else if (this._fastfs.fileExists(potentialModulePath + '.js')) { + file = potentialModulePath + '.js'; + } else if (this._fastfs.fileExists(potentialModulePath + '.json')) { + file = potentialModulePath + '.json'; + } else { + throw new Error(`File ${potentialModulePath} doesnt exist`); + } + + return this._moduleCache.getModule(file); + }); + } + + _loadAsDir(potentialDirPath) { + return Promise.resolve().then(() => { + if (!this._fastfs.dirExists(potentialDirPath)) { + throw new Error(`Invalid directory ${potentialDirPath}`); + } + + const packageJsonPath = path.join(potentialDirPath, 'package.json'); + if (this._fastfs.fileExists(packageJsonPath)) { + return this._moduleCache.getPackage(packageJsonPath) + .getMain().then( + (main) => this._loadAsFile(main).catch( + () => this._loadAsDir(main) + ) + ); + } + + return this._loadAsFile(path.join(potentialDirPath, 'index')); + }); + } + + _buildHasteMap() { + let promises = this._fastfs.findFilesByExt('js', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHasteModule(file)); + + promises = promises.concat( + this._fastfs.findFilesByName('package.json', { + ignore: (file) => this._isNodeModulesDir(file) + }).map(file => this._processHastePackage(file)) + ); + + return Promise.all(promises); + } + + _processHasteModule(file) { + const module = this._moduleCache.getModule(file); + return module.isHaste().then( + isHaste => isHaste && module.getName() + .then(name => this._updateHasteMap(name, module)) + ); + } + + _processHastePackage(file) { + file = path.resolve(file); + const p = this._moduleCache.getPackage(file, this._fastfs); + return p.isHaste() + .then(isHaste => isHaste && p.getName() + .then(name => this._updateHasteMap(name, p))) + .catch(e => { + if (e instanceof SyntaxError) { + // Malformed package.json. + return; + } + throw e; + }); + } + + _updateHasteMap(name, mod) { + if (this._hasteMap[name]) { + debug('WARNING: conflicting haste modules: ' + name); + if (mod.type === 'Package' && + this._hasteMap[name].type === 'Module') { + // Modules takes precendence over packages. + return; + } + } + this._hasteMap[name] = mod; + } + + _isNodeModulesDir(file) { + const inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + const dirs = this._opts.providesModuleNodeModules; + + for (let i = 0; i < dirs.length; i++) { + const index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; + } + + _processAsset_DEPRECATED(file) { + let ext = extname(file); + if (this._opts.assetExts.indexOf(ext) !== -1) { + let name = assetName(file, ext); + if (this._assetMap_DEPRECATED[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap_DEPRECATED[name] = new AssetModule_DEPRECATED(file); + } + } + + _buildAssetMap_DEPRECATED() { + if (this._opts.assetRoots_DEPRECATED == null || + this._opts.assetRoots_DEPRECATED.length === 0) { + return Promise.resolve(); + } + + this._assetMap_DEPRECATED = Object.create(null); + + const pattern = new RegExp( + '\.(' + this._opts.assetExts.join('|') + ')$' + ); + + const fastfs = new Fastfs( + this._opts.assetRoots_DEPRECATED, + this._opts.fileWatcher, + { pattern, ignore: this._opts.ignoreFilePath } + ); + + fastfs.on('change', this._processAssetChange_DEPRECATED.bind(this)); + + return fastfs.build().then( + () => fastfs.findFilesByExts(this._opts.assetExts).map( + file => this._processAsset_DEPRECATED(file) + ) + ); + } + + _processAssetChange_DEPRECATED(type, filePath, root, fstat) { + const name = assetName(filePath); + if (type === 'change' || type === 'delete') { + delete this._assetMap_DEPRECATED[name]; + } + + if (type === 'change' || type === 'add') { + this._loading = this._loading.then( + () => this._processAsset_DEPRECATED(path.join(root, filePath)) + ); + } + } + + _processFileChange(type, filePath, root, fstat) { + // It's really hard to invalidate the right module resolution cache + // so we just blow it up with every file change. + this._immediateResolutionCache = Object.create(null); + + const absPath = path.join(root, filePath); + if ((fstat && fstat.isDirectory()) || + this._opts.ignoreFilePath(absPath) || + this._isNodeModulesDir(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + _.each(this._hasteMap, (mod, name) => { + if (mod.path === absPath) { + delete this._hasteMap[name]; + } + }); + + if (type === 'delete') { + return; + } + } + + if (extname(absPath) === 'js' || extname(absPath) === 'json') { + this._loading = this._loading.then(() => { + if (path.basename(filePath) === 'package.json') { + return this._processHastePackage(absPath); + } else { + return this._processHasteModule(absPath); + } + }); + } + } +} + +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + +function resolutionHash(modulePath, depName) { + return `${path.resolve(modulePath)}:${depName}`; +} + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +function normalizePath(modulePath) { + if (path.sep === '/') { + modulePath = path.normalize(modulePath); + } else if (path.posix) { + modulePath = path.posix.normalize(modulePath); + } + + return modulePath.replace(/\/$/, ''); +} + +util.inherits(NotFoundError, Error); + +module.exports = DependencyGraph; diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js new file mode 100644 index 000000000..d0be012ba --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/Module.js @@ -0,0 +1,133 @@ +'use strict'; + +const Promise = require('bluebird'); +const docblock = require('./DependencyGraph/docblock'); +const isAbsolutePath = require('absolute-path'); +const path = require('path'); +const replacePatterns = require('./replacePatterns'); + +class Module { + + constructor(file, fastfs, moduleCache) { + if (!isAbsolutePath(file)) { + throw new Error('Expected file to be absolute path but got ' + file); + } + + this.path = path.resolve(file); + this.type = 'Module'; + + this._fastfs = fastfs; + this._moduleCache = moduleCache; + } + + isHaste() { + return this._read().then(data => !!data.id); + } + + getName() { + return this._read().then(data => { + if (data.id) { + return data.id; + } + + const p = this.getPackage(); + + if (!p) { + // Name is full path + return this.path; + } + + return p.getName() + .then(name => { + if (!name) { + return this.path; + } + + return path.join(name, path.relative(p.root, this.path)); + }); + }); + } + + getPackage() { + return this._moduleCache.getPackageForModule(this); + } + + getDependencies() { + return this._read().then(data => data.dependencies); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path).then(content => { + const data = {}; + const moduleDocBlock = docblock.parseAsObject(content); + if (moduleDocBlock.providesModule || moduleDocBlock.provides) { + data.id = /^(\S*)/.exec( + moduleDocBlock.providesModule || moduleDocBlock.provides + )[1]; + } + + // Ignore requires in generated code. An example of this is prebuilt + // files like the SourceMap library. + if ('extern' in moduleDocBlock) { + data.dependencies = []; + } else { + data.dependencies = extractRequires(content); + } + + return data; + }); + } + + return this._reading; + } + + getPlainObject() { + return Promise.all([ + this.getName(), + this.getDependencies(), + ]).then(([name, dependencies]) => this.addReference({ + path: this.path, + isJSON: path.extname(this.path) === '.json', + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + id: name, + dependencies + })); + } + + hash() { + return `Module : ${this.path}`; + } + + addReference(obj) { + Object.defineProperty(obj, '_ref', { value: this }); + return obj; + } +} + +/** + * Extract all required modules from a `code` string. + */ +var blockCommentRe = /\/\*(.|\n)*?\*\//g; +var lineCommentRe = /\/\/.+(\n|$)/g; +function extractRequires(code /*: string*/) /*: Array*/ { + var deps = []; + + code + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { + deps.push(dep); + return match; + }) + .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { + deps.push(dep); + }); + + return deps; +} + +module.exports = Module; diff --git a/packager/react-packager/src/DependencyResolver/ModuleCache.js b/packager/react-packager/src/DependencyResolver/ModuleCache.js new file mode 100644 index 000000000..3fa02cc17 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/ModuleCache.js @@ -0,0 +1,72 @@ +'use strict'; + +const AssetModule = require('./AssetModule'); +const Package = require('./Package'); +const Module = require('./Module'); +const path = require('path'); + +class ModuleCache { + + constructor(fastfs) { + this._moduleCache = Object.create(null); + this._packageCache = Object.create(null); + this._fastfs = fastfs; + fastfs.on('change', this._processFileChange.bind(this)); + } + + getModule(filePath) { + filePath = path.resolve(filePath); + if (!this._moduleCache[filePath]) { + this._moduleCache[filePath] = new Module(filePath, this._fastfs, this); + } + return this._moduleCache[filePath]; + } + + getAssetModule(filePath) { + filePath = path.resolve(filePath); + if (!this._moduleCache[filePath]) { + this._moduleCache[filePath] = new AssetModule( + filePath, + this._fastfs, + this + ); + } + return this._moduleCache[filePath]; + } + + getPackage(filePath) { + filePath = path.resolve(filePath); + if (!this._packageCache[filePath]){ + this._packageCache[filePath] = new Package(filePath, this._fastfs); + } + return this._packageCache[filePath]; + } + + getPackageForModule(module) { + // TODO(amasad): use ES6 Map. + if (module.__package) { + if (this._packageCache[module.__package]) { + return this._packageCache[module.__package]; + } else { + delete module.__package; + } + } + + const packagePath = this._fastfs.closest(module.path, 'package.json'); + + if (!packagePath) { + return null; + } + + module.__package = packagePath; + return this.getPackage(packagePath); + } + + _processFileChange(type, filePath, root) { + const absPath = path.join(root, filePath); + delete this._moduleCache[absPath]; + delete this._packageCache[absPath]; + } +} + +module.exports = ModuleCache; diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js deleted file mode 100644 index 90db1c4ad..000000000 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ /dev/null @@ -1,61 +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'; - -function ModuleDescriptor(fields) { - if (!fields.id) { - throw new Error('Missing required fields id'); - } - this.id = fields.id; - - if (!fields.path) { - throw new Error('Missing required 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; - this.isAsset = fields.isAsset || false; - - if (this.isAsset_DEPRECATED && this.isAsset) { - throw new Error('Cannot be an asset and a deprecated asset'); - } - - this.resolution = fields.resolution; - - if (this.isAsset && isNaN(this.resolution)) { - throw new Error('Expected resolution to be a number for asset modules'); - } - - this.altId = fields.altId; - - this.isJSON = fields.isJSON; - - this._fields = fields; -} - -ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; -}; - -module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/Package.js b/packager/react-packager/src/DependencyResolver/Package.js new file mode 100644 index 000000000..f52ff42b8 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/Package.js @@ -0,0 +1,84 @@ +'use strict'; + +const isAbsolutePath = require('absolute-path'); +const path = require('path'); + +class Package { + + constructor(file, fastfs) { + this.path = path.resolve(file); + this.root = path.dirname(this.path); + this._fastfs = fastfs; + this.type = 'Package'; + } + + getMain() { + return this._read().then(json => { + if (typeof json.browser === 'string') { + return path.join(this.root, json.browser); + } + + let main = json.main || 'index'; + + if (json.browser && typeof json.browser === 'object') { + main = json.browser[main] || + json.browser[main + '.js'] || + json.browser[main + '.json'] || + json.browser[main.replace(/(\.js|\.json)$/, '')] || + main; + } + + return path.join(this.root, main); + }); + } + + isHaste() { + return this._read().then(json => !!json.name); + } + + getName() { + return this._read().then(json => json.name); + } + + redirectRequire(name) { + return this._read().then(json => { + const {browser} = json; + + if (!browser || typeof browser !== 'object') { + return name; + } + + if (name[0] !== '/') { + return browser[name] || name; + } + + if (!isAbsolutePath(name)) { + throw new Error(`Expected ${name} to be absolute path`); + } + + const relPath = './' + path.relative(this.root, name); + const redirect = browser[relPath] || + browser[relPath + '.js'] || + browser[relPath + '.json']; + if (redirect) { + return path.join( + this.root, + redirect + ); + } + + return name; + }); + } + + _read() { + if (!this._reading) { + this._reading = this._fastfs.readFile(this.path) + .then(jsonStr => JSON.parse(jsonStr)); + } + + return this._reading; + } +} + +module.exports = Package; diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js similarity index 65% rename from packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js rename to packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 0d1296ba1..f97828869 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -9,9 +9,8 @@ 'use strict'; jest.dontMock('../') - .dontMock('q') - .dontMock('../replacePatterns') - .setMock('../../ModuleDescriptor', function(data) {return data;}); + .dontMock('q') + .dontMock('../replacePatterns'); jest.mock('path'); @@ -20,6 +19,11 @@ var Promise = require('bluebird'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; + function createModule(o) { + o.getPlainObject = () => Promise.resolve(o); + return o; + } + beforeEach(function() { // For the polyfillDeps require('path').join.mockImpl(function(a, b) { @@ -30,7 +34,10 @@ describe('HasteDependencyResolver', function() { describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -40,7 +47,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(); @@ -113,7 +120,12 @@ describe('HasteDependencyResolver', function() { }); pit('should get dependencies with polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'], + }); + var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -123,7 +135,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(); @@ -196,7 +208,11 @@ describe('HasteDependencyResolver', function() { }); pit('should pass in more polyfills', function() { - var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; + var module = createModule({ + id: 'index', + path: '/root/index.js', + dependencies: ['a'] + }); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -207,7 +223,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(); @@ -294,7 +310,7 @@ describe('HasteDependencyResolver', function() { }); describe('wrapModule', function() { - it('should resolve modules', function() { + pit('should resolve modules', function() { var depResolver = new HasteDependencyResolver({ projectRoot: '/root', }); @@ -446,163 +462,165 @@ describe('HasteDependencyResolver', function() { depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { - return { + return Promise.resolve(createModule({ id: 'changed' - }; + })); } else if (toModuleName === 'y') { - return { id: 'Y' }; + return Promise.resolve(createModule({ id: 'Y' })); } - return null; + + return Promise.resolve(null); }); - var processedCode = depResolver.wrapModule({ + return depResolver.wrapModule({ id: 'test module', path: '/root/test.js', dependencies: dependencies - }, code); + }, code).then(processedCode => { - expect(processedCode).toEqual([ - '__d(\'test module\',["changed","Y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) { ' + - "import'x';", - "import 'changed';", - "import 'changed' ;", - "import Default from 'changed';", - "import * as All from 'changed';", - "import {} from 'changed';", - "import { } from 'changed';", - "import {Foo} from 'changed';", - "import { Foo } from 'changed';", - "import { Foo, } from 'changed';", - "import {Foo as Bar} from 'changed';", - "import { Foo as Bar } from 'changed';", - "import { Foo as Bar, } from 'changed';", - "import { Foo, Bar } from 'changed';", - "import { Foo, Bar, } from 'changed';", - "import { Foo as Bar, Baz } from 'changed';", - "import { Foo as Bar, Baz, } from 'changed';", - "import { Foo, Bar as Baz } from 'changed';", - "import { Foo, Bar as Baz, } from 'changed';", - "import { Foo as Bar, Baz as Qux } from 'changed';", - "import { Foo as Bar, Baz as Qux, } from 'changed';", - "import { Foo, Bar, Baz } from 'changed';", - "import { Foo, Bar, Baz, } from 'changed';", - "import { Foo as Bar, Baz, Qux } from 'changed';", - "import { Foo as Bar, Baz, Qux, } from 'changed';", - "import { Foo, Bar as Baz, Qux } from 'changed';", - "import { Foo, Bar as Baz, Qux, } from 'changed';", - "import { Foo, Bar, Baz as Qux } from 'changed';", - "import { Foo, Bar, Baz as Qux, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", - "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", - "import Default, * as All from 'changed';", - "import Default, { } from 'changed';", - "import Default, { Foo } from 'changed';", - "import Default, { Foo, } from 'changed';", - "import Default, { Foo as Bar } from 'changed';", - "import Default, { Foo as Bar, } from 'changed';", - "import Default, { Foo, Bar } from 'changed';", - "import Default, { Foo, Bar, } from 'changed';", - "import Default, { Foo as Bar, Baz } from 'changed';", - "import Default, { Foo as Bar, Baz, } from 'changed';", - "import Default, { Foo, Bar as Baz } from 'changed';", - "import Default, { Foo, Bar as Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz } from 'changed';", - "import Default, { Foo, Bar, Baz, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux } from 'changed';", - "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", - "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", - "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", - "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", - "import Default , { } from 'changed';", - 'import "changed";', - 'import Default from "changed";', - 'import * as All from "changed";', - 'import { } from "changed";', - 'import { Foo } from "changed";', - 'import { Foo, } from "changed";', - 'import { Foo as Bar } from "changed";', - 'import { Foo as Bar, } from "changed";', - 'import { Foo, Bar } from "changed";', - 'import { Foo, Bar, } from "changed";', - 'import { Foo as Bar, Baz } from "changed";', - 'import { Foo as Bar, Baz, } from "changed";', - 'import { Foo, Bar as Baz } from "changed";', - 'import { Foo, Bar as Baz, } from "changed";', - 'import { Foo as Bar, Baz as Qux } from "changed";', - 'import { Foo as Bar, Baz as Qux, } from "changed";', - 'import { Foo, Bar, Baz } from "changed";', - 'import { Foo, Bar, Baz, } from "changed";', - 'import { Foo as Bar, Baz, Qux } from "changed";', - 'import { Foo as Bar, Baz, Qux, } from "changed";', - 'import { Foo, Bar as Baz, Qux } from "changed";', - 'import { Foo, Bar as Baz, Qux, } from "changed";', - 'import { Foo, Bar, Baz as Qux } from "changed";', - 'import { Foo, Bar, Baz as Qux, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', - 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', - 'import Default, * as All from "changed";', - 'import Default, { } from "changed";', - 'import Default, { Foo } from "changed";', - 'import Default, { Foo, } from "changed";', - 'import Default, { Foo as Bar } from "changed";', - 'import Default, { Foo as Bar, } from "changed";', - 'import Default, { Foo, Bar } from "changed";', - 'import Default, { Foo, Bar, } from "changed";', - 'import Default, { Foo as Bar, Baz } from "changed";', - 'import Default, { Foo as Bar, Baz, } from "changed";', - 'import Default, { Foo, Bar as Baz } from "changed";', - 'import Default, { Foo, Bar as Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz } from "changed";', - 'import Default, { Foo, Bar, Baz, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux } from "changed";', - 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', - 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', - 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', - 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', - 'import Default from "Y";', - 'import * as All from \'z\';', - 'require("changed")', - 'require("Y")', - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - '});', - ].join('\n')); + expect(processedCode).toEqual([ + '__d(\'test module\',["changed","Y"],function(global,' + + ' require, requireDynamic, requireLazy, module, exports) { ' + + "import'x';", + "import 'changed';", + "import 'changed' ;", + "import Default from 'changed';", + "import * as All from 'changed';", + "import {} from 'changed';", + "import { } from 'changed';", + "import {Foo} from 'changed';", + "import { Foo } from 'changed';", + "import { Foo, } from 'changed';", + "import {Foo as Bar} from 'changed';", + "import { Foo as Bar } from 'changed';", + "import { Foo as Bar, } from 'changed';", + "import { Foo, Bar } from 'changed';", + "import { Foo, Bar, } from 'changed';", + "import { Foo as Bar, Baz } from 'changed';", + "import { Foo as Bar, Baz, } from 'changed';", + "import { Foo, Bar as Baz } from 'changed';", + "import { Foo, Bar as Baz, } from 'changed';", + "import { Foo as Bar, Baz as Qux } from 'changed';", + "import { Foo as Bar, Baz as Qux, } from 'changed';", + "import { Foo, Bar, Baz } from 'changed';", + "import { Foo, Bar, Baz, } from 'changed';", + "import { Foo as Bar, Baz, Qux } from 'changed';", + "import { Foo as Bar, Baz, Qux, } from 'changed';", + "import { Foo, Bar as Baz, Qux } from 'changed';", + "import { Foo, Bar as Baz, Qux, } from 'changed';", + "import { Foo, Bar, Baz as Qux } from 'changed';", + "import { Foo, Bar, Baz as Qux, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf } from 'changed';", + "import { Foo as Bar, Baz as Qux, Norf as Enuf, } from 'changed';", + "import Default, * as All from 'changed';", + "import Default, { } from 'changed';", + "import Default, { Foo } from 'changed';", + "import Default, { Foo, } from 'changed';", + "import Default, { Foo as Bar } from 'changed';", + "import Default, { Foo as Bar, } from 'changed';", + "import Default, { Foo, Bar } from 'changed';", + "import Default, { Foo, Bar, } from 'changed';", + "import Default, { Foo as Bar, Baz } from 'changed';", + "import Default, { Foo as Bar, Baz, } from 'changed';", + "import Default, { Foo, Bar as Baz } from 'changed';", + "import Default, { Foo, Bar as Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz } from 'changed';", + "import Default, { Foo, Bar, Baz, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux, } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux } from 'changed';", + "import Default, { Foo, Bar, Baz as Qux, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf } from 'changed';", + "import Default, { Foo as Bar, Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf } from 'changed';", + "import Default, { Foo, Bar as Baz, Qux as Norf, } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore } from 'changed';", + "import Default, { Foo as Bar, Baz as Qux, Norf as NoMore, } from 'changed';", + "import Default , { } from 'changed';", + 'import "changed";', + 'import Default from "changed";', + 'import * as All from "changed";', + 'import { } from "changed";', + 'import { Foo } from "changed";', + 'import { Foo, } from "changed";', + 'import { Foo as Bar } from "changed";', + 'import { Foo as Bar, } from "changed";', + 'import { Foo, Bar } from "changed";', + 'import { Foo, Bar, } from "changed";', + 'import { Foo as Bar, Baz } from "changed";', + 'import { Foo as Bar, Baz, } from "changed";', + 'import { Foo, Bar as Baz } from "changed";', + 'import { Foo, Bar as Baz, } from "changed";', + 'import { Foo as Bar, Baz as Qux } from "changed";', + 'import { Foo as Bar, Baz as Qux, } from "changed";', + 'import { Foo, Bar, Baz } from "changed";', + 'import { Foo, Bar, Baz, } from "changed";', + 'import { Foo as Bar, Baz, Qux } from "changed";', + 'import { Foo as Bar, Baz, Qux, } from "changed";', + 'import { Foo, Bar as Baz, Qux } from "changed";', + 'import { Foo, Bar as Baz, Qux, } from "changed";', + 'import { Foo, Bar, Baz as Qux } from "changed";', + 'import { Foo, Bar, Baz as Qux, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore } from "changed";', + 'import { Foo as Bar, Baz as Qux, Norf as NoMore, } from "changed";', + 'import Default, * as All from "changed";', + 'import Default, { } from "changed";', + 'import Default, { Foo } from "changed";', + 'import Default, { Foo, } from "changed";', + 'import Default, { Foo as Bar } from "changed";', + 'import Default, { Foo as Bar, } from "changed";', + 'import Default, { Foo, Bar } from "changed";', + 'import Default, { Foo, Bar, } from "changed";', + 'import Default, { Foo as Bar, Baz } from "changed";', + 'import Default, { Foo as Bar, Baz, } from "changed";', + 'import Default, { Foo, Bar as Baz } from "changed";', + 'import Default, { Foo, Bar as Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz } from "changed";', + 'import Default, { Foo, Bar, Baz, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux, } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux } from "changed";', + 'import Default, { Foo, Bar, Baz as Qux, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf } from "changed";', + 'import Default, { Foo as Bar, Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf } from "changed";', + 'import Default, { Foo, Bar as Baz, Qux as Norf, } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf } from "changed";', + 'import Default, { Foo as Bar, Baz as Qux, Norf as Enuf, } from "changed";', + 'import Default from "Y";', + 'import * as All from \'z\';', + 'require("changed")', + 'require("Y")', + 'require( \'z\' )', + 'require( "a")', + 'require("b" )', + '});', + ].join('\n')); + }); }); }); }); diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js new file mode 100644 index 000000000..56ffe166f --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/fastfs.js @@ -0,0 +1,302 @@ +'use strict'; + +const Promise = require('bluebird'); +const {EventEmitter} = require('events'); + +const _ = require('underscore'); +const debug = require('debug')('DependencyGraph'); +const fs = require('fs'); +const path = require('path'); + +const readDir = Promise.promisify(fs.readdir); +const readFile = Promise.promisify(fs.readFile); +const stat = Promise.promisify(fs.stat); + +class Fastfs extends EventEmitter { + constructor(roots, fileWatcher, {ignore, pattern}) { + super(); + this._fileWatcher = fileWatcher; + this._ignore = ignore; + this._pattern = pattern; + this._roots = roots.map(root => new File(root, { isDir: true })); + } + + build() { + const queue = this._roots.slice(); + return this._search(queue).then(() => { + this._fileWatcher.on('all', this._processFileChange.bind(this)); + }); + } + + stat(filePath) { + return Promise.resolve().then(() => { + const file = this._getFile(filePath); + return file.stat(); + }); + } + + getAllFiles() { + return _.chain(this._roots) + .map(root => root.getFiles()) + .flatten() + .value(); + } + + findFilesByExt(ext, { ignore }) { + return this.getAllFiles() + .filter( + file => file.ext() === ext && (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + findFilesByExts(exts) { + return this.getAllFiles() + .filter(file => exts.indexOf(file.ext()) !== -1) + .map(file => file.path); + } + + findFilesByName(name, { ignore }) { + return this.getAllFiles() + .filter( + file => path.basename(file.path) === name && + (!ignore || !ignore(file.path)) + ) + .map(file => file.path); + } + + readFile(filePath) { + return this._getFile(filePath).read(); + } + + closest(filePath, name) { + for (let file = this._getFile(filePath).parent; + file; + file = file.parent) { + if (file.children[name]) { + return file.children[name].path; + } + } + return null; + } + + fileExists(filePath) { + const file = this._getFile(filePath); + return file && !file.isDir; + } + + dirExists(filePath) { + const file = this._getFile(filePath); + return file && file.isDir; + } + + matches(dir, pattern) { + let dirFile = this._getFile(dir); + if (!dirFile.isDir) { + throw new Error(`Expected file ${dirFile.path} to be a directory`); + } + + return Object.keys(dirFile.children) + .filter(name => name.match(pattern)) + .map(name => path.join(dirFile.path, name)); + } + + _getRoot(filePath) { + for (let i = 0; i < this._roots.length; i++) { + let possibleRoot = this._roots[i]; + if (isDescendant(possibleRoot.path, filePath)) { + return possibleRoot; + } + } + return null; + } + + _getAndAssertRoot(filePath) { + const root = this._getRoot(filePath); + if (!root) { + throw new Error(`File ${filePath} not found in any of the roots`); + } + return root; + } + + _getFile(filePath) { + return this._getAndAssertRoot(filePath).getFileFromPath(filePath); + } + + _add(file) { + this._getAndAssertRoot(file.path).addChild(file); + } + + _search(queue) { + const dir = queue.shift(); + if (!dir) { + return Promise.resolve(); + } + + return readAndStatDir(dir.path).then(([filePaths, stats]) => { + filePaths.forEach((filePath, i) => { + if (this._ignore(filePath)) { + return; + } + + if (stats[i].isDirectory()) { + queue.push( + new File(filePath, { isDir: true, fstat: stats[i] }) + ); + return; + } + + if (filePath.match(this._pattern)) { + this._add(new File(filePath, { fstat: stats[i] })); + } + }); + return this._search(queue); + }); + } + + _processFileChange(type, filePath, root, fstat) { + const absPath = path.join(root, filePath); + if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { + return; + } + + // Make sure this event belongs to one of our roots. + + if (!this._getRoot(absPath)) { + return; + } + + if (type === 'delete' || type === 'change') { + const file = this._getFile(absPath); + if (file) { + file.remove(); + } + } + + if (type !== 'delete') { + this._add(new File(absPath, { + isDir: false, + fstat + })); + } + + this.emit('change', type, filePath, root, fstat); + } +} + +class File { + constructor(filePath, {isDir, fstat}) { + this.path = filePath; + this.isDir = Boolean(isDir); + if (this.isDir) { + this.children = Object.create(null); + } + + if (fstat) { + this._stat = Promise.resolve(fstat); + } + } + + read() { + if (!this._read) { + this._read = readFile(this.path, 'utf8'); + } + return this._read; + } + + stat() { + if (!this._stat) { + this._stat = stat(this.path); + } + + return this._stat; + } + + addChild(file) { + const parts = path.relative(this.path, file.path).split(path.sep); + + if (parts.length === 0) { + return; + } + + if (parts.length === 1) { + this.children[parts[0]] = file; + file.parent = this; + } else if (this.children[parts[0]]) { + this.children[parts[0]].addChild(file); + } else { + const dir = new File(path.join(this.path, parts[0]), { isDir: true }); + dir.parent = this; + this.children[parts[0]] = dir; + dir.addChild(file); + } + } + + getFileFromPath(filePath) { + const parts = path.relative(this.path, filePath) + .split(path.sep); + + /*eslint consistent-this:0*/ + let file = this; + for (let i = 0; i < parts.length; i++) { + let fileName = parts[i]; + if (!fileName) { + continue; + } + + if (!file || !file.isDir) { + // File not found. + return null; + } + + file = file.children[fileName]; + } + + return file; + } + + getFiles() { + return _.flatten(_.values(this.children).map(file => { + if (file.isDir) { + return file.getFiles(); + } else { + return file; + } + })); + } + + ext() { + return path.extname(this.path).replace(/^\./, ''); + } + + remove() { + if (!this.parent) { + throw new Error(`No parent to delete ${this.path} from`); + } + + delete this.parent.children[path.basename(this.path)]; + } +} + +function isDescendant(root, child) { + return path.relative(root, child).indexOf('..') !== 0; +} + +function readAndStatDir(dir) { + return readDir(dir) + .then(files => Promise.all(files.map(f => path.join(dir, f)))) + .then(files => Promise.all( + files.map(f => stat(f).catch(handleBrokenLink)) + ).then(stats => [ + // Remove broken links. + files.filter((file, i ) => !!stats[i]), + stats.filter(Boolean), + ])); +} + +function handleBrokenLink(e) { + debug('WARNING: error stating, possibly broken symlink', e.message); + return Promise.resolve(); +} + +module.exports = Fastfs; 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 deleted file mode 100644 index c247e59d3..000000000 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ /dev/null @@ -1,1612 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -jest - .dontMock('../index') - .dontMock('absolute-path') - .dontMock('../docblock') - .dontMock('../../replacePatterns') - .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); - -jest.mock('fs'); - -describe('DependencyGraph', function() { - var DependencyGraph; - var fileWatcher; - var fs; - - beforeEach(function() { - fs = require('fs'); - DependencyGraph = require('../index'); - - fileWatcher = { - on: function() { - return this; - } - }; - }); - - describe('getOrderedDependencies', function() { - pit('should get dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get dependencies with the correct extensions', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")' - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - 'a.js.orig': [ - '/**', - ' * @providesModule a', - ' */', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, - ]); - }); - }); - - pit('should get json dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'package' - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./a.json")' - ].join('\n'), - 'a.json': JSON.stringify({}), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { - id: 'index', - altId: 'package/index', - path: '/root/index.js', - dependencies: ['./a.json'] - }, - { - id: 'package/a.json', - isJSON: true, - path: '/root/a.json', - dependencies: [] - }, - ]); - }); - }); - - pit('should get dependencies with deprecated assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!a")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - 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']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get dependencies with relative assets', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")' - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - 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'] - }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get dependencies with assets and resolution', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - 'imgs': { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - 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', - './imgs/b.png', - './imgs/c.png', - ] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.png', - resolution: 1, - dependencies: [], - isAsset: true - }, - ]); - }); - }); - - pit('Deprecated and relative assets can live together', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - 'require("image!a")', - ].join('\n'), - 'imgs': { - 'a.png': '' - }, - 'package.json': JSON.stringify({ - name: 'rootPackage' - }), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - assetRoots_DEPRECATED: ['/root/imgs'], - }); - 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'] - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - - pit('should get recursive dependencies', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("index")', - ].join('\n'), - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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']}, - ]); - }); - }); - - pit('should work with packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default main package to index.js', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': 'lol', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should have altId for a package with providesModule', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': [ - '/**', - ' * @providesModule EpicModule', - ' */', - ].join('\n'), - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', - altId: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should default use index.js if main is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': 'require("aPackage")', - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'lib', - }), - lib: { - 'index.js': 'lol', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', - path: '/root/aPackage/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should resolve require to index if it is a dir', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'index.js': 'lol', - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', - path: '/root/lib/index.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should ignore malformed packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': 'lol', - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - ]); - }); - }); - - pit('can have multiple modules with the same name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("b")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - ].join('\n'), - 'c.js': [ - '/**', - ' * @providesModule c', - ' */', - ].join('\n'), - 'somedir': { - 'somefile.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("c")', - ].join('\n') - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) - .toEqual([ - { id: 'index', - altId: '/root/somedir/somefile.js', - path: '/root/somedir/somefile.js', - dependencies: ['c'] - }, - { id: 'c', - altId: '/root/c.js', - path: '/root/c.js', - dependencies: [] - }, - ]); - }); - }); - - pit('providesModule wins when conflict with package', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule aPackage', - ' */', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage', - altId: '/root/b.js', - path: '/root/b.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should be forgiving with missing requires', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("lolomg")', - ].join('\n') - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['lolomg'] - } - ]); - }); - }); - - pit('should work with packages with subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with packages with symlinked subdirs', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'symlinkedPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'lol', - 'subdir': { - 'lolynot.js': 'lolynot' - } - }, - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - 'aPackage': { SYMLINK: '/symlinkedPackage' }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work with relative modules in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'require("./subdir/lolynot")', - 'subdir': { - 'lolynot.js': 'require("../other")' - }, - 'other.js': 'some code' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] - }, - { id: 'aPackage/subdir/lolynot', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] - }, - { id: 'aPackage/other', - path: '/root/aPackage/other.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support simple browser field in packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should supportbrowser field in packages w/o .js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'client', - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support mapping main in browser field json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should work do correct browser mapping w/o js ext', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }), - 'main.js': 'some other code', - 'client.js': 'some code', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping of files', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - }, - }), - 'main.js': 'some other code', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': 'some browser code', - 'dir': { - 'server.js': 'some node code', - 'client.js': 'some browser code', - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/client', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] - }, - { id: 'aPackage/not-node', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] - }, - { id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [] - }, - { id: 'aPackage/dir/client', - path: '/root/aPackage/dir/client.js', - dependencies: [] - }, - ]); - }); - }); - - pit('should support browser mapping for packages', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - } - }), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - 'name': 'node-package', - }), - 'index.js': 'some node code', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - 'name': 'browser-package', - }), - 'index.js': 'some browser code', - }, - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/index', - path: '/root/aPackage/index.js', - dependencies: ['node-package'] - }, - { id: 'browser-package/index', - path: '/root/aPackage/browser-package/index.js', - dependencies: [] - }, - ]); - }); - }); - }); - - describe('file watch updating', function() { - var triggerFileChange; - - beforeEach(function() { - fileWatcher = { - on: function(eventType, callback) { - if (eventType !== 'all') { - throw new Error('Can only handle "all" event in watcher.'); - } - triggerFileChange = callback; - return this; - } - }; - }); - - pit('updates module dependencies', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().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')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file change', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().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')) - .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file delete', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - delete filesystem.root.foo; - triggerFileChange('delete', 'foo.js', root); - 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'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on file add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - 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'] - }, - { 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'] - }, - ]); - }); - }); - }); - - pit('updates module dependencies on deprecated asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("image!foo")' - ].join('\n'), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetRoots_DEPRECATED: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - 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'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - 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'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); - }); - }); - }); - - pit('updates module dependencies on relative asset add', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./foo.png")' - ].join('\n'), - 'package.json': JSON.stringify({ - name: 'aPackage' - }), - }, - }); - - var dgraph = new DependencyGraph({ - roots: [root], - assetExts: ['png'], - fileWatcher: fileWatcher, - }); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - } - ]); - - filesystem.root['foo.png'] = ''; - triggerFileChange('add', 'foo.png', root); - - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) - .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, - }, - ]); - }); - }); - }); - - pit('runs changes through ignore filter', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - ignoreFilePath: function(filePath) { - if (filePath === '/root/bar.js') { - return true; - } - return false; - } - }); - return dgraph.load().then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")' - ].join('\n'); - triggerFileChange('add', 'bar.js', root); - - filesystem.root.aPackage['main.js'] = 'require("bar")'; - triggerFileChange('change', 'aPackage/main.js', root); - - 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'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - - pit('should ignore directory updates', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")' - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")' - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.load().then(function() { - triggerFileChange('change', 'aPackage', '/root', { - isDirectory: function(){ return true; } - }); - 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'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, - ]); - }); - }); - }); - }); -}); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js deleted file mode 100644 index 0881e5dc7..000000000 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ /dev/null @@ -1,798 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -var ModuleDescriptor = require('../../ModuleDescriptor'); -var Promise = require('bluebird'); -var fs = require('fs'); -var docblock = require('./docblock'); -var replacePatterns = require('../replacePatterns'); -var path = require('path'); -var isAbsolutePath = require('absolute-path'); -var debug = require('debug')('DependecyGraph'); -var util = require('util'); -var declareOpts = require('../../../lib/declareOpts'); -var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); - -var readFile = Promise.promisify(fs.readFile); -var readDir = Promise.promisify(fs.readdir); -var lstat = Promise.promisify(fs.lstat); -var realpath = Promise.promisify(fs.realpath); - -var validateOpts = declareOpts({ - roots: { - type: 'array', - required: true, - }, - ignoreFilePath: { - type: 'function', - default: function(){} - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetRoots_DEPRECATED: { - type: 'array', - default: [], - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function DependecyGraph(options) { - var opts = validateOpts(options); - - this._roots = opts.roots; - this._assetRoots_DEPRECATED = opts.assetRoots_DEPRECATED; - this._assetExts = opts.assetExts; - this._ignoreFilePath = opts.ignoreFilePath; - this._fileWatcher = options.fileWatcher; - - this._loaded = false; - this._queue = this._roots.slice(); - this._graph = Object.create(null); - this._packageByRoot = Object.create(null); - this._packagesById = Object.create(null); - this._moduleById = Object.create(null); - this._debugUpdateEvents = []; - - this._moduleExtPattern = new RegExp( - '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' - ); - - // Kick off the search process to precompute the dependency graph. - this._init(); -} - -DependecyGraph.prototype.load = function() { - if (this._loading != null) { - return this._loading; - } - - this._loading = Promise.all([ - this._search(), - this._buildAssetMap_DEPRECATED(), - ]); - - return this._loading; -}; - -/** - * 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 - ); - } - - 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 - * the required module `depModuleId`. It could be top-level or relative, - * or both. - */ -DependecyGraph.prototype.resolveDependency = function( - fromModule, - depModuleId -) { - if (this._assetMap_DEPRECATED != null) { - var assetMatch = depModuleId.match(/^image!(.+)/); - // Process DEPRECATED global asset requires. - if (assetMatch && assetMatch[1]) { - if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); - return null; - } - return this._assetMap_DEPRECATED[assetMatch[1]]; - } - } - - var packageJson, modulePath, dep; - - // Package relative modules starts with '.' or '..'. - if (depModuleId[0] !== '.') { - - // Check if we need to map the dependency to something else via the - // `browser` field in package.json - var fromPackageJson = this._lookupPackage(fromModule.path); - if (fromPackageJson && fromPackageJson.browser && - fromPackageJson.browser[depModuleId]) { - depModuleId = fromPackageJson.browser[depModuleId]; - } - - // `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)]) { - 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) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - depModuleId, - fromModule.id - ); - 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')]; - } - - if (dep == null) { - throw new Error( - 'Cannot find package main file for package: ' + packageJson._root - ); - } - return dep; - } else { - - // `depModuleId` is a module defined in a package relative to `fromModule`. - packageJson = this._lookupPackage(fromModule.path); - - if (packageJson == null) { - throw new Error( - 'Expected relative module lookup from ' + fromModule.id + ' to ' + - depModuleId + ' to be within a package but no package.json found.' - ); - } - - // Example: depModuleId: ../a/b - // fromModule.path: /x/y/z - // 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); - } - } - - // 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)) { - dep = this._moduleById[this._lookupName(modulePath)]; - } - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.' + - ' Inferred required module path is %s', - depModuleId, - fromModule.id, - modulePath - ); - return null; - } - - return dep; - } -}; - -/** - * Intiates the filewatcher and kicks off the search process. - */ -DependecyGraph.prototype._init = function() { - var processChange = this._processFileChange.bind(this); - var watcher = this._fileWatcher; - - this._loading = this.load().then(function() { - watcher.on('all', processChange); - }); -}; - -/** - * Implements a DFS over the file system looking for modules and packages. - */ -DependecyGraph.prototype._search = function() { - var self = this; - var dir = this._queue.shift(); - - if (dir == null) { - return Promise.resolve(this._graph); - } - - // Steps: - // 1. Read a dir and stat all the entries. - // 2. Filter the files and queue up the directories. - // 3. Process any package.json in the files - // 4. recur. - return readAndStatDir(dir) - .spread(function(files, stats) { - var modulePaths = files.filter(function(filePath, i) { - if (self._ignoreFilePath(filePath)) { - return false; - } - - if (stats[i].isDirectory()) { - self._queue.push(filePath); - return false; - } - - if (stats[i].isSymbolicLink()) { - return false; - } - - return filePath.match(self._moduleExtPattern); - }); - - var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); - - return Promise.all([ - processing, - self._search() - ]); - }) - .then(function() { - return self; - }); -}; - -/** - * Given a list of files find a `package.json` file, and if found parse it - * and update indices. - */ -DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - - var packagePath; - for (var i = 0; i < files.length ; i++) { - var file = files[i]; - if (path.basename(file) === 'package.json') { - packagePath = file; - break; - } - } - - if (packagePath != null) { - return this._processPackage(packagePath); - } else { - return Promise.resolve(); - } -}; - -DependecyGraph.prototype._processPackage = function(packagePath) { - var packageRoot = path.dirname(packagePath); - var self = this; - return readFile(packagePath, 'utf8') - .then(function(content) { - var packageJson; - try { - packageJson = JSON.parse(content); - } catch (e) { - debug('WARNING: malformed package.json: ', 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); - - return packageJson; - }); -}; - -DependecyGraph.prototype._addPackageToIndices = function(packageJson) { - this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; -}; - -DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { - delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; -}; - -/** - * Parse a module and update indices. - */ -DependecyGraph.prototype._processModule = function(modulePath) { - var moduleData = { path: path.resolve(modulePath) }; - var module; - - if (this._assetExts.indexOf(extname(modulePath)) > -1) { - var assetData = getAssetDataFromName(this._lookupName(modulePath)); - moduleData.id = assetData.assetName; - moduleData.resolution = assetData.resolution; - moduleData.isAsset = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - if (extname(modulePath) === 'json') { - moduleData.id = this._lookupName(modulePath); - moduleData.isJSON = true; - moduleData.dependencies = []; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - - var self = this; - return readFile(modulePath, 'utf8') - .then(function(content) { - var moduleDocBlock = docblock.parseAsObject(content); - if (moduleDocBlock.providesModule || moduleDocBlock.provides) { - moduleData.id = /^(\S*)/.exec( - moduleDocBlock.providesModule || moduleDocBlock.provides - )[1]; - - // Incase someone wants to require this module via - // packageName/path/to/module - moduleData.altId = self._lookupName(modulePath); - } else { - moduleData.id = self._lookupName(modulePath); - } - moduleData.dependencies = extractRequires(content); - - module = new ModuleDescriptor(moduleData); - self._updateGraphWithModule(module); - return module; - }); -}; - -/** - * Compute the name of module relative to a package it may belong to. - */ -DependecyGraph.prototype._lookupName = function(modulePath) { - var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { - return path.resolve(modulePath); - } else { - var relativePath = - sansExtJs(path.relative(packageJson._root, modulePath)); - return path.join(packageJson.name, relativePath); - } -}; - -DependecyGraph.prototype._deleteModule = function(module) { - delete this._graph[module.path]; - - // Others may keep a reference so we mark it as deleted. - module.deleted = true; - - // Haste allows different module to have the same id. - if (this._moduleById[module.id] === module) { - delete this._moduleById[module.id]; - } - - if (module.altId && this._moduleById[module.altId] === module) { - delete this._moduleById[module.altId]; - } -}; - -/** - * Update the graph and indices with the module. - */ -DependecyGraph.prototype._updateGraphWithModule = function(module) { - if (this._graph[module.path]) { - this._deleteModule(this._graph[module.path]); - } - - this._graph[module.path] = module; - - if (this._moduleById[module.id]) { - debug( - 'WARNING: Top-level module name conflict `%s`.\n' + - 'module with path `%s` will replace `%s`', - module.id, - module.path, - this._moduleById[module.id].path - ); - } - - this._moduleById[module.id] = module; - - // Some module maybe refrenced by both @providesModule and - // require(package/moduleName). - if (module.altId != null && this._moduleById[module.altId] == null) { - this._moduleById[module.altId] = module; - } -}; - -/** - * Find the nearest package to a module. - */ -DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = 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)); -}; - -/** - * Process a filewatcher change event. - */ -DependecyGraph.prototype._processFileChange = function( - eventType, - filePath, - root, - stat -) { - var absPath = path.join(root, filePath); - if (this._ignoreFilePath(absPath)) { - return; - } - - this._debugUpdateEvents.push({event: eventType, path: filePath}); - - if (this._assetExts.indexOf(extname(filePath)) > -1) { - this._processAssetChange_DEPRECATED(eventType, absPath); - // Fall through because new-style assets are actually modules. - } - - var isPackage = path.basename(filePath) === 'package.json'; - if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } - } else { - var module = this._graph[absPath]; - if (module == null) { - return; - } - - this._deleteModule(module); - } - } else if (!(stat && stat.isDirectory())) { - var self = this; - this._loading = this._loading.then(function() { - if (isPackage) { - return self._processPackage(absPath); - } - return self._processModule(absPath); - }); - } -}; - -DependecyGraph.prototype.getDebugInfo = function() { - return '

FileWatcher Update Events

' + - '
' + util.inspect(this._debugUpdateEvents) + '
' + - '

Graph dump

' + - '
' + util.inspect(this._graph) + '
'; -}; - -/** - * Searches all roots for the file and returns the first one that has file of - * the same path. - */ -DependecyGraph.prototype._getAbsolutePath = function(filePath) { - if (isAbsolutePath(filePath)) { - return filePath; - } - - for (var i = 0; i < this._roots.length; i++) { - var root = this._roots[i]; - var absPath = path.join(root, filePath); - if (this._graph[absPath]) { - return absPath; - } - } - - return null; -}; - -DependecyGraph.prototype._buildAssetMap_DEPRECATED = function() { - if (this._assetRoots_DEPRECATED == null || - this._assetRoots_DEPRECATED.length === 0) { - return Promise.resolve(); - } - - this._assetMap_DEPRECATED = Object.create(null); - return buildAssetMap_DEPRECATED( - this._assetRoots_DEPRECATED, - this._processAsset_DEPRECATED.bind(this) - ); -}; - -DependecyGraph.prototype._processAsset_DEPRECATED = function(file) { - var ext = extname(file); - if (this._assetExts.indexOf(ext) !== -1) { - var name = assetName(file, ext); - if (this._assetMap_DEPRECATED[name] != null) { - debug('Conflcting assets', name); - } - - this._assetMap_DEPRECATED[name] = new ModuleDescriptor({ - id: 'image!' + name, - path: path.resolve(file), - isAsset_DEPRECATED: true, - dependencies: [], - resolution: getAssetDataFromName(file).resolution, - }); - } -}; - -DependecyGraph.prototype._processAssetChange_DEPRECATED = function(eventType, file) { - if (this._assetMap_DEPRECATED == null) { - return; - } - - var name = assetName(file, extname(file)); - if (eventType === 'change' || eventType === 'delete') { - delete this._assetMap_DEPRECATED[name]; - } - - if (eventType === 'change' || eventType === 'add') { - this._processAsset_DEPRECATED(file); - } -}; - -DependecyGraph.prototype._isFileAsset = function(file) { - return this._assetExts.indexOf(extname(file)) !== -1; -}; - -/** - * Extract all required modules from a `code` string. - */ -var blockCommentRe = /\/\*(.|\n)*?\*\//g; -var lineCommentRe = /\/\/.+(\n|$)/g; -function extractRequires(code) { - var deps = []; - - code - .replace(blockCommentRe, '') - .replace(lineCommentRe, '') - .replace(replacePatterns.IMPORT_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - return match; - }) - .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { - deps.push(dep); - }); - - return deps; -} - -/** - * `file` without the .js extension. - */ -function sansExtJs(file) { - if (file.match(/\.js$/)) { - return file.slice(0, -3); - } else { - return file; - } -} - -/** - * `file` with the .js extension. - */ -function withExtJs(file) { - if (file.match(/\.js$/)) { - return file; - } else { - return file + '.js'; - } -} - -function handleBrokenLink(e) { - debug('WARNING: error stating, possibly broken symlink', e.message); - return Promise.resolve(); -} - -function readAndStatDir(dir) { - return readDir(dir) - .then(function(files){ - return Promise.all(files.map(function(filePath) { - return realpath(path.join(dir, filePath)).catch(handleBrokenLink); - })); - }).then(function(files) { - files = files.filter(function(f) { - return !!f; - }); - - var stats = files.map(function(filePath) { - return lstat(filePath).catch(handleBrokenLink); - }); - - return [ - files, - Promise.all(stats), - ]; - }); -} - -/** - * Given a list of roots and list of extensions find all the files in - * the directory with that extension and build a map of those assets. - */ -function buildAssetMap_DEPRECATED(roots, processAsset) { - var queue = roots.slice(0); - - function search() { - var root = queue.shift(); - - if (root == null) { - return Promise.resolve(); - } - - return readAndStatDir(root).spread(function(files, stats) { - files.forEach(function(file, i) { - if (stats[i].isDirectory()) { - queue.push(file); - } else { - processAsset(file); - } - }); - - return search(); - }); - } - - return search(); -} - -function assetName(file, ext) { - return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); -} - -function extname(name) { - return path.extname(name).replace(/^\./, ''); -} - -function NotFoundError() { - Error.call(this); - Error.captureStackTrace(this, this.constructor); - var msg = util.format.apply(util, arguments); - this.message = msg; - this.type = this.name = 'NotFoundError'; - this.status = 404; -} - -util.inherits(NotFoundError, Error); - -module.exports = DependecyGraph; diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js deleted file mode 100644 index aaa79c95b..000000000 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -var path = require('path'); -var DependencyGraph = require('./DependencyGraph'); -var replacePatterns = require('./replacePatterns'); -var ModuleDescriptor = require('../ModuleDescriptor'); -var declareOpts = require('../../lib/declareOpts'); - -var DEFINE_MODULE_CODE = [ - '__d(', - '\'_moduleName_\',', - '_deps_,', - 'function(global, require, requireDynamic, requireLazy, module, exports) {', - ' _code_', - '\n});', -].join(''); - -var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g; - -var validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, - nonPersistent: { - type: 'boolean', - default: false, - }, - moduleFormat: { - type: 'string', - default: 'haste', - }, - assetRoots: { - type: 'array', - default: [], - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetExts: { - type: 'array', - required: true, - } -}); - -function HasteDependencyResolver(options) { - var opts = validateOpts(options); - - this._depGraph = new DependencyGraph({ - roots: opts.projectRoots, - assetRoots_DEPRECATED: opts.assetRoots, - assetExts: opts.assetExts, - ignoreFilePath: function(filepath) { - return filepath.indexOf('__tests__') !== -1 || - (opts.blacklistRE && opts.blacklistRE.test(filepath)); - }, - fileWatcher: opts.fileWatcher, - }); - - - this._polyfillModuleNames = opts.polyfillModuleNames || []; -} - -var getDependenciesValidateOpts = declareOpts({ - dev: { - type: 'boolean', - default: true, - }, -}); - -HasteDependencyResolver.prototype.getDependencies = function(main, options) { - var opts = getDependenciesValidateOpts(options); - - var depGraph = this._depGraph; - var self = this; - - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); - var mainModuleId = dependencies[0].id; - - self._prependPolyfillDependencies(dependencies, opts.dev); - - return { - mainModuleId: mainModuleId, - dependencies: dependencies - }; - }); -}; - -HasteDependencyResolver.prototype._prependPolyfillDependencies = function( - dependencies, - isDev -) { - var polyfillModuleNames = [ - isDev - ? path.join(__dirname, 'polyfills/prelude_dev.js') - : path.join(__dirname, 'polyfills/prelude.js'), - path.join(__dirname, 'polyfills/require.js'), - path.join(__dirname, 'polyfills/polyfills.js'), - path.join(__dirname, 'polyfills/console.js'), - path.join(__dirname, 'polyfills/error-guard.js'), - path.join(__dirname, 'polyfills/String.prototype.es6.js'), - path.join(__dirname, 'polyfills/Array.prototype.es6.js'), - ].concat(this._polyfillModuleNames); - - var polyfillModules = polyfillModuleNames.map( - function(polyfillModuleName, idx) { - return new ModuleDescriptor({ - path: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - isPolyfill: true - }); - } - ); - dependencies.unshift.apply(dependencies, polyfillModules); -}; - -HasteDependencyResolver.prototype.wrapModule = function(module, code) { - if (module.isPolyfill) { - return code; - } - - var resolvedDeps = Object.create(null); - var resolvedDepsArr = []; - - for (var i = 0; i < module.dependencies.length; i++) { - var depName = module.dependencies[i]; - var dep = this._depGraph.resolveDependency(module, depName); - if (dep) { - resolvedDeps[depName] = dep.id; - resolvedDepsArr.push(dep.id); - } - } - - var relativizeCode = function(codeMatch, pre, quot, depName, post) { - var depId = resolvedDeps[depName]; - if (depId) { - return pre + quot + depId + post; - } else { - return codeMatch; - } - }; - - return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) { - return { - '_moduleName_': module.id, - '_code_': code.replace(replacePatterns.IMPORT_RE, relativizeCode) - .replace(replacePatterns.REQUIRE_RE, relativizeCode), - '_deps_': JSON.stringify(resolvedDepsArr), - }[key]; - }); -}; - -HasteDependencyResolver.prototype.getDebugInfo = function() { - return this._depGraph.getDebugInfo(); -}; - -module.exports = HasteDependencyResolver; diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index ca80ab0b8..bfc4e9432 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -8,15 +8,174 @@ */ 'use strict'; -var HasteDependencyResolver = require('./haste'); -var NodeDependencyResolver = require('./node'); +var path = require('path'); +var DependencyGraph = require('./DependencyGraph'); +var replacePatterns = require('./replacePatterns'); +var declareOpts = require('../lib/declareOpts'); +var Promise = require('bluebird'); -module.exports = function createDependencyResolver(options) { - if (options.moduleFormat === 'haste') { - return new HasteDependencyResolver(options); - } else if (options.moduleFormat === 'node') { - return new NodeDependencyResolver(options); - } else { - throw new Error('unsupported'); +var validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, + assetRoots: { + type: 'array', + default: [], + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetExts: { + type: 'array', + required: true, } +}); + +function HasteDependencyResolver(options) { + var opts = validateOpts(options); + + this._depGraph = new DependencyGraph({ + roots: opts.projectRoots, + assetRoots_DEPRECATED: opts.assetRoots, + assetExts: opts.assetExts, + ignoreFilePath: function(filepath) { + return filepath.indexOf('__tests__') !== -1 || + (opts.blacklistRE && opts.blacklistRE.test(filepath)); + }, + fileWatcher: opts.fileWatcher, + }); + + + this._polyfillModuleNames = opts.polyfillModuleNames || []; +} + +var getDependenciesValidateOpts = declareOpts({ + dev: { + type: 'boolean', + default: true, + }, +}); + +HasteDependencyResolver.prototype.getDependencies = function(main, options) { + var opts = getDependenciesValidateOpts(options); + + var depGraph = this._depGraph; + var self = this; + return depGraph.load().then( + () => depGraph.getOrderedDependencies(main).then( + dependencies => { + const mainModuleId = dependencies[0].id; + self._prependPolyfillDependencies( + dependencies, + opts.dev + ); + + return { + mainModuleId: mainModuleId, + dependencies: dependencies + }; + } + ) + ); }; + +HasteDependencyResolver.prototype._prependPolyfillDependencies = function( + dependencies, + isDev +) { + var polyfillModuleNames = [ + isDev + ? path.join(__dirname, 'polyfills/prelude_dev.js') + : path.join(__dirname, 'polyfills/prelude.js'), + path.join(__dirname, 'polyfills/require.js'), + path.join(__dirname, 'polyfills/polyfills.js'), + path.join(__dirname, 'polyfills/console.js'), + path.join(__dirname, 'polyfills/error-guard.js'), + path.join(__dirname, 'polyfills/String.prototype.es6.js'), + path.join(__dirname, 'polyfills/Array.prototype.es6.js'), + ].concat(this._polyfillModuleNames); + + var polyfillModules = polyfillModuleNames.map( + (polyfillModuleName, idx) => ({ + path: polyfillModuleName, + id: polyfillModuleName, + dependencies: polyfillModuleNames.slice(0, idx), + isPolyfill: true, + }) + ); + + dependencies.unshift.apply(dependencies, polyfillModules); +}; + +HasteDependencyResolver.prototype.wrapModule = function(module, code) { + if (module.isPolyfill) { + return Promise.resolve(code); + } + + const resolvedDeps = Object.create(null); + const resolvedDepsArr = []; + + return Promise.all( + module.dependencies.map(depName => { + return this._depGraph.resolveDependency(module, depName) + .then((dep) => dep && dep.getPlainObject().then(mod => { + if (mod) { + resolvedDeps[depName] = mod.id; + resolvedDepsArr.push(mod.id); + } + })); + }) + ).then(() => { + const relativizeCode = (codeMatch, pre, quot, depName, post) => { + const depId = resolvedDeps[depName]; + if (depId) { + return pre + quot + depId + post; + } else { + return codeMatch; + } + }; + + return defineModuleCode({ + code: code + .replace(replacePatterns.IMPORT_RE, relativizeCode) + .replace(replacePatterns.REQUIRE_RE, relativizeCode), + deps: JSON.stringify(resolvedDepsArr), + moduleName: module.id, + }); + }); +}; + +HasteDependencyResolver.prototype.getDebugInfo = function() { + return this._depGraph.getDebugInfo(); +}; + +function defineModuleCode({moduleName, code, deps}) { + return [ + `__d(`, + `'${moduleName}',`, + `${deps},`, + 'function(global, require, ', + 'requireDynamic, requireLazy, module, exports) {', + ` ${code}`, + '\n});', + ].join(''); +} + +module.exports = HasteDependencyResolver; diff --git a/packager/react-packager/src/DependencyResolver/node/index.js b/packager/react-packager/src/DependencyResolver/node/index.js deleted file mode 100644 index 573885646..000000000 --- a/packager/react-packager/src/DependencyResolver/node/index.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -var Promise = require('bluebird'); -var ModuleDescriptor = require('../ModuleDescriptor'); - -var mdeps = require('module-deps'); -var path = require('path'); - -exports.getRuntimeCode = function() {}; - -exports.wrapModule = function(id, source) { - return Promise.resolve( - 'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n' - + source + '\n});' - ); -}; - -exports.getDependencies = function(root, fileEntryPath) { - return new Promise(function(resolve) { - fileEntryPath = path.join(process.cwd(), root, fileEntryPath); - - var md = mdeps(); - - md.end({file: fileEntryPath}); - - var deps = []; - - md.on('data', function(data) { - deps.push( - new ModuleDescriptor({ - id: data.id, - deps: data.deps, - path: data.file, - entry: data.entry - }) - ); - }); - - md.on('end', function() { - resolve(deps); - }); - }); -}; diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js b/packager/react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/Array.prototype.es6.js rename to packager/react-packager/src/DependencyResolver/polyfills/Array.prototype.es6.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js b/packager/react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/String.prototype.es6.js rename to packager/react-packager/src/DependencyResolver/polyfills/String.prototype.es6.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/console.js b/packager/react-packager/src/DependencyResolver/polyfills/console.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/console.js rename to packager/react-packager/src/DependencyResolver/polyfills/console.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js b/packager/react-packager/src/DependencyResolver/polyfills/error-guard.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/error-guard.js rename to packager/react-packager/src/DependencyResolver/polyfills/error-guard.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js b/packager/react-packager/src/DependencyResolver/polyfills/polyfills.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/polyfills.js rename to packager/react-packager/src/DependencyResolver/polyfills/polyfills.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/prelude.js b/packager/react-packager/src/DependencyResolver/polyfills/prelude.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/prelude.js rename to packager/react-packager/src/DependencyResolver/polyfills/prelude.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js b/packager/react-packager/src/DependencyResolver/polyfills/prelude_dev.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/prelude_dev.js rename to packager/react-packager/src/DependencyResolver/polyfills/prelude_dev.js diff --git a/packager/react-packager/src/DependencyResolver/haste/polyfills/require.js b/packager/react-packager/src/DependencyResolver/polyfills/require.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/polyfills/require.js rename to packager/react-packager/src/DependencyResolver/polyfills/require.js diff --git a/packager/react-packager/src/DependencyResolver/haste/replacePatterns.js b/packager/react-packager/src/DependencyResolver/replacePatterns.js similarity index 100% rename from packager/react-packager/src/DependencyResolver/haste/replacePatterns.js rename to packager/react-packager/src/DependencyResolver/replacePatterns.js diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index 89184c95e..aaa8963c2 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -101,7 +101,7 @@ describe('Packager', function() { }); wrapModule.mockImpl(function(module, code) { - return 'lol ' + code + ' lol'; + return Promise.resolve('lol ' + code + ' lol'); }); require('image-size').mockImpl(function(path, cb) { diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index a85281d2a..04404ff19 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -159,16 +159,17 @@ Packager.prototype._transformModule = function(ppackage, module) { } var resolver = this._resolver; - return transform.then(function(transformed) { - var code = resolver.wrapModule(module, transformed.code); - return new ModuleTransport({ - code: code, - map: transformed.map, - sourceCode: transformed.sourceCode, - sourcePath: transformed.sourcePath, - virtual: transformed.virtual, - }); - }); + return transform.then( + transformed => resolver.wrapModule(module, transformed.code).then( + code => new ModuleTransport({ + code: code, + map: transformed.map, + sourceCode: transformed.sourceCode, + sourcePath: transformed.sourcePath, + virtual: transformed.virtual, + }) + ) + ); }; Packager.prototype.getGraphDebugInfo = function() { diff --git a/packager/react-packager/src/__mocks__/fs.js b/packager/react-packager/src/__mocks__/fs.js index d0e08a2f4..b5251a447 100644 --- a/packager/react-packager/src/__mocks__/fs.js +++ b/packager/react-packager/src/__mocks__/fs.js @@ -51,7 +51,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { var node = getToNode(filepath); // dir check if (node && typeof node === 'object' && node.SYMLINK == null) { - callback(new Error('Trying to read a dir, ESIDR, or whatever')); + callback(new Error('Error readFile a dir: ' + filepath)); } return callback(null, node); } catch (e) { @@ -59,12 +59,13 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { } }); -fs.lstat.mockImpl(function(filepath, callback) { +fs.stat.mockImpl(function(filepath, callback) { var node; try { node = getToNode(filepath); } catch (e) { - return callback(e); + callback(e); + return; } var mtime = { @@ -73,7 +74,12 @@ fs.lstat.mockImpl(function(filepath, callback) { } }; - if (node && typeof node === 'object' && node.SYMLINK == null) { + if (node.SYMLINK) { + fs.stat(node.SYMLINK, callback); + return; + } + + if (node && typeof node === 'object') { callback(null, { isDirectory: function() { return true; @@ -89,9 +95,6 @@ fs.lstat.mockImpl(function(filepath, callback) { return false; }, isSymbolicLink: function() { - if (typeof node === 'object' && node.SYMLINK) { - return true; - } return false; }, mtime: mtime, @@ -113,6 +116,9 @@ function getToNode(filepath) { } var node = filesystem; parts.slice(1).forEach(function(part) { + if (node && node.SYMLINK) { + node = getToNode(node.SYMLINK); + } node = node[part]; }); From 2845e780800e78d1c55f1300798cc0b32aac6f76 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 19 Jun 2015 22:48:36 -0700 Subject: [PATCH 36/64] [react-packager] Cache in-memory file lookups (~200ms win on file change) --- .../react-packager/src/DependencyResolver/fastfs.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js index 56ffe166f..fc09ea8b3 100644 --- a/packager/react-packager/src/DependencyResolver/fastfs.js +++ b/packager/react-packager/src/DependencyResolver/fastfs.js @@ -11,6 +11,7 @@ const path = require('path'); const readDir = Promise.promisify(fs.readdir); const readFile = Promise.promisify(fs.readFile); const stat = Promise.promisify(fs.stat); +const hasOwn = Object.prototype.hasOwnProperty; class Fastfs extends EventEmitter { constructor(roots, fileWatcher, {ignore, pattern}) { @@ -19,6 +20,7 @@ class Fastfs extends EventEmitter { this._ignore = ignore; this._pattern = pattern; this._roots = roots.map(root => new File(root, { isDir: true })); + this._fastPaths = Object.create(null); } build() { @@ -120,7 +122,12 @@ class Fastfs extends EventEmitter { } _getFile(filePath) { - return this._getAndAssertRoot(filePath).getFileFromPath(filePath); + filePath = path.normalize(filePath); + if (!hasOwn.call(this._fastPaths, filePath)) { + this._fastPaths[filePath] = this._getAndAssertRoot(filePath).getFileFromPath(filePath); + } + + return this._fastPaths[filePath]; } _add(file) { @@ -161,7 +168,6 @@ class Fastfs extends EventEmitter { } // Make sure this event belongs to one of our roots. - if (!this._getRoot(absPath)) { return; } @@ -173,6 +179,8 @@ class Fastfs extends EventEmitter { } } + delete this._fastPaths[path.normalize(absPath)]; + if (type !== 'delete') { this._add(new File(absPath, { isDir: false, From 5aee4cec98a7ebe1d49ad2292191787f62a0fdbe Mon Sep 17 00:00:00 2001 From: Bill Fisher Date: Sat, 20 Jun 2015 16:29:38 -0700 Subject: [PATCH 37/64] [ReactNative][easy] fix server 500 response typo Summary: @public corrected small typo in the 500 response from the packager server Test Plan: add throw to promise function prior to error handler, run packager, cache a bundle with bundle extension URI, open /debug/packages, see clean 500 error --- packager/react-packager/src/Server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 3e8ca9e8b..914075fea 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -237,7 +237,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { }, this)).then( function() { res.end(ret); }, function(e) { - res.wrteHead(500); + res.writeHead(500); res.end('Internal Error'); console.log(e.stack); } From 6e568ee81167024b6cc8c1c29aa7ac3d9f76d516 Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Mon, 22 Jun 2015 03:07:02 -0700 Subject: [PATCH 38/64] [SampleApp] Remove $(SRCROOT) from INFOPLIST_FILE to fix agvtool Summary: Fixed #879 - I'm not too familiar with this aspect of XCode so a sanity check here would be great @tadeuzagallo :wink: Closes https://github.com/facebook/react-native/pull/1466 Github Author: Brent Vatne Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj index 05480738f..e714ca038 100644 --- a/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/SampleApp.xcodeproj/project.pbxproj @@ -600,7 +600,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", ); - INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist"; + INFOPLIST_FILE = "iOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = SampleApp; @@ -616,7 +616,7 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../../React/**", ); - INFOPLIST_FILE = "$(SRCROOT)/iOS/Info.plist"; + INFOPLIST_FILE = "iOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = SampleApp; From c9f193cdd08975341e3ac2bb99ae02e039dc6b03 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 22 Jun 2015 03:56:12 -0700 Subject: [PATCH 39/64] [ReactNative] Stop build tests on non-test builds Summary: @public UIExplrer's test targets were being built on normal builds as well, remove it. Test Plan: Insert an `#error` on a test file, and run the project. It shouldn't fail anymore. --- .../xcshareddata/xcschemes/UIExplorer.xcscheme | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index c3dcde8eb..e2f84182e 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> @@ -22,10 +22,10 @@ + buildForAnalyzing = "NO"> + buildForAnalyzing = "NO"> Date: Mon, 22 Jun 2015 03:06:47 -0700 Subject: [PATCH 40/64] Add support for selecting media from library Summary: This PR adds support for UIImagePickerController to allow selecting a photo / video from the users camera roll. ![ios simulator screen shot jun 14 2015 4 50 03 pm](https://cloud.githubusercontent.com/assets/688326/8147758/ae6dc8d4-12b6-11e5-80f0-2bcaa964a5d8.png) Example: Selecting something from camera roll ``` ImagePickerIOS.openSelectDialog(, , ); ImagePickerIOS.openSelectDialog({ showImages: true, // defaults to true showVideos: false // defaults to false }, function (data) { console.info("Got a callback!"); console.info(data); // file URL as in assets-library://asset/asset.JPG?id=E2741A73-D185-44B6-A2E6-2D55F69CD088&ext=JPG }, function() { console.info("Cancelled"); }); ``` Using camera ``` ImagePickerIOS.openCameraDialog(, , ); ImagePickerIOS.openSelectDialog({ videoMode: false, // defaults to true, whether to record videos instead }, function (data) { console.info("Got Closes https://github.com/facebook/react-native/pull/1620 Github Author: David Mohl Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Image/ImagePickerIOS.js | 40 ++++++ .../Image/RCTImage.xcodeproj/project.pbxproj | 6 + Libraries/Image/RCTImagePickerManager.h | 15 ++ Libraries/Image/RCTImagePickerManager.m | 135 ++++++++++++++++++ Libraries/react-native/react-native.js | 1 + 5 files changed, 197 insertions(+) create mode 100644 Libraries/Image/ImagePickerIOS.js create mode 100644 Libraries/Image/RCTImagePickerManager.h create mode 100644 Libraries/Image/RCTImagePickerManager.m diff --git a/Libraries/Image/ImagePickerIOS.js b/Libraries/Image/ImagePickerIOS.js new file mode 100644 index 000000000..9b2f75e5b --- /dev/null +++ b/Libraries/Image/ImagePickerIOS.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ImagePickerIOS + * @flow + */ +'use strict'; + +var RCTImagePicker = require('NativeModules').ImagePickerIOS; + +var ImagePickerIOS = { + canRecordVideos: function(callback: Function) { + return RCTImagePicker.canRecordVideos(callback); + }, + canUseCamera: function(callback: Function) { + return RCTImagePicker.canUseCamera(callback); + }, + openCameraDialog: function(config: Object, successCallback: Function, cancelCallback: Function) { + config = { + videoMode: false, + ...config, + } + return RCTImagePicker.openCameraDialog(config, successCallback, cancelCallback); + }, + openSelectDialog: function(config: Object, successCallback: Function, cancelCallback: Function) { + config = { + showImages: true, + showVideos: false, + ...config, + } + return RCTImagePicker.openSelectDialog(config, successCallback, cancelCallback); + }, +}; + +module.exports = ImagePickerIOS; diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 9e5427bf3..1e3cf75c8 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; 1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; }; + 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; }; 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; }; @@ -39,6 +40,8 @@ 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; }; 1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = ""; }; 1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = ""; }; + 137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = ""; }; + 137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = ""; }; 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = ""; }; 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCameraRollManager.m; sourceTree = ""; }; 143879361AAD32A300F088A5 /* RCTImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageLoader.h; sourceTree = ""; }; @@ -74,6 +77,8 @@ 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */, 58B511891A9E6BD600147676 /* RCTImageDownloader.h */, 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */, + 137620331B31C53500677FF0 /* RCTImagePickerManager.h */, + 137620341B31C53500677FF0 /* RCTImagePickerManager.m */, 1345A8371B26592900583190 /* RCTImageRequestHandler.h */, 1345A8381B26592900583190 /* RCTImageRequestHandler.m */, 58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */, @@ -155,6 +160,7 @@ buildActionMask = 2147483647; files = ( 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */, + 137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */, 58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */, 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */, 1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */, diff --git a/Libraries/Image/RCTImagePickerManager.h b/Libraries/Image/RCTImagePickerManager.h new file mode 100644 index 000000000..a008c46f3 --- /dev/null +++ b/Libraries/Image/RCTImagePickerManager.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 "RCTBridgeModule.h" + +@interface RCTImagePickerManager : NSObject + +@end diff --git a/Libraries/Image/RCTImagePickerManager.m b/Libraries/Image/RCTImagePickerManager.m new file mode 100644 index 000000000..7fad953b0 --- /dev/null +++ b/Libraries/Image/RCTImagePickerManager.m @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2013, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 "RCTImagePickerManager.h" +#import "RCTRootView.h" + +#import + +#import + +@interface RCTImagePickerManager () + +@end + +@implementation RCTImagePickerManager +{ + NSMutableArray *_pickers; + NSMutableArray *_pickerCallbacks; + NSMutableArray *_pickerCancelCallbacks; +} + +RCT_EXPORT_MODULE(ImagePickerIOS); + +- (instancetype)init +{ + if ((self = [super init])) { + _pickers = [[NSMutableArray alloc] init]; + _pickerCallbacks = [[NSMutableArray alloc] init]; + _pickerCancelCallbacks = [[NSMutableArray alloc] init]; + } + return self; +} + +RCT_EXPORT_METHOD(canRecordVideos:(RCTResponseSenderBlock)callback) +{ + NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera]; + callback(@[@([availableMediaTypes containsObject:(NSString *)kUTTypeMovie])]); +} + +RCT_EXPORT_METHOD(canUseCamera:(RCTResponseSenderBlock)callback) +{ + callback(@[@([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])]); +} + +RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config + successCallback:(RCTResponseSenderBlock)callback + cancelCallback:(RCTResponseSenderBlock)cancelCallback) +{ + UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; + UIViewController *rootViewController = keyWindow.rootViewController; + + UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; + imagePicker.delegate = self; + imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; + + if ([config[@"videoMode"] boolValue]) { + imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo; + } + + [_pickers addObject:imagePicker]; + [_pickerCallbacks addObject:callback]; + [_pickerCancelCallbacks addObject:cancelCallback]; + + [rootViewController presentViewController:imagePicker animated:YES completion:nil]; +} + +RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config + successCallback:(RCTResponseSenderBlock)callback + cancelCallback:(RCTResponseSenderBlock)cancelCallback) +{ + UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; + UIViewController *rootViewController = keyWindow.rootViewController; + + UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; + imagePicker.delegate = self; + imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + + NSMutableArray *allowedTypes = [[NSMutableArray alloc] init]; + if ([config[@"showImages"] boolValue]) { + [allowedTypes addObject:(NSString *)kUTTypeImage]; + } + if ([config[@"showVideos"] boolValue]) { + [allowedTypes addObject:(NSString *)kUTTypeMovie]; + } + + imagePicker.mediaTypes = allowedTypes; + + [_pickers addObject:imagePicker]; + [_pickerCallbacks addObject:callback]; + [_pickerCancelCallbacks addObject:cancelCallback]; + + [rootViewController presentViewController:imagePicker animated:YES completion:nil]; +} + +- (void)imagePickerController:(UIImagePickerController *)picker +didFinishPickingMediaWithInfo:(NSDictionary *)info +{ + NSUInteger index = [_pickers indexOfObject:picker]; + RCTResponseSenderBlock callback = _pickerCallbacks[index]; + + [_pickers removeObjectAtIndex:index]; + [_pickerCallbacks removeObjectAtIndex:index]; + [_pickerCancelCallbacks removeObjectAtIndex:index]; + + UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; + UIViewController *rootViewController = keyWindow.rootViewController; + [rootViewController dismissViewControllerAnimated:YES completion:nil]; + + callback(@[[info[UIImagePickerControllerReferenceURL] absoluteString]]); +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + NSUInteger index = [_pickers indexOfObject:picker]; + RCTResponseSenderBlock callback = _pickerCancelCallbacks[index]; + + [_pickers removeObjectAtIndex:index]; + [_pickerCallbacks removeObjectAtIndex:index]; + [_pickerCancelCallbacks removeObjectAtIndex:index]; + + UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; + UIViewController *rootViewController = keyWindow.rootViewController; + [rootViewController dismissViewControllerAnimated:YES completion:nil]; + + callback(@[]); +} + +@end diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index 479476cbe..367276567 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -47,6 +47,7 @@ var ReactNative = Object.assign(Object.create(require('React')), { AppStateIOS: require('AppStateIOS'), AsyncStorage: require('AsyncStorage'), CameraRoll: require('CameraRoll'), + ImagePickerIOS: require('ImagePickerIOS'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), LinkingIOS: require('LinkingIOS'), From 2cb0546d15284efab5ffddbc011c554f6c0f156a Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Mon, 22 Jun 2015 03:12:35 -0700 Subject: [PATCH 41/64] Added support for React installed in the application via Cocoapods Summary: Similarly to npm-installed react, this change makes changes to the packager so that it understands that it's been installed via Cocoapods and determines the project and asset roots properly (from the main application directory). Closes https://github.com/facebook/react-native/pull/1568 Github Author: Jarek Potiuk Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/packager.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packager/packager.js b/packager/packager.js index 7dd013c67..c719b8608 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -66,6 +66,9 @@ if (options.projectRoots) { if (__dirname.match(/node_modules\/react-native\/packager$/)) { // packager is running from node_modules of another project options.projectRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + // packager is running from node_modules of another project + options.projectRoots = [path.resolve(__dirname, '../../..')]; } else { options.projectRoots = [path.resolve(__dirname, '..')]; } @@ -88,6 +91,8 @@ if (options.assetRoots) { } else { if (__dirname.match(/node_modules\/react-native\/packager$/)) { options.assetRoots = [path.resolve(__dirname, '../../..')]; + } else if (__dirname.match(/Pods\/React\/packager$/)) { + options.assetRoots = [path.resolve(__dirname, '../../..')]; } else { options.assetRoots = [path.resolve(__dirname, '..')]; } From 9228873fb4b8161ba7370a56712ba121804b14b1 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 22 Jun 2015 04:55:55 -0700 Subject: [PATCH 42/64] [ReactNative] Fix racing conditions on reload Summary: @public That was eventually being released before all the queues had been cleared. Update it so the each modules' queue is immediately invalidated after sending the `-invalidate` message to it, and introduce an intentional retain cycle so the bridge is only released together with all modules, when all the messages have been dispatched. Test Plan: Launch the UIExplorer, and reload it, like, a lot. --- React/Base/RCTBridge.m | 53 ++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index a50079440..d7ec0ac48 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1042,11 +1042,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module - (NSDictionary *)modules { - if (!self.isValid) { - return nil; - } - - RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. " + RCTAssert(!self.isValid || _modulesByName != nil, @"Bridge modules have not yet been initialized. " "You may be trying to access a module too early in the startup procedure."); return _modulesByName; @@ -1074,19 +1070,21 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module _mainDisplayLink = nil; // Invalidate modules + dispatch_group_t group = dispatch_group_create(); for (id target in _modulesByID.allObjects) { if ([target respondsToSelector:@selector(invalidate)]) { [self dispatchBlock:^{ [(id)target invalidate]; - } forModule:target]; + } forModule:target dispatchGroup:group]; } + _queuesByID[RCTModuleIDsByName[RCTBridgeModuleNameForClass([target class])]] = nil; } - - // Release modules (breaks retain cycle if module has strong bridge reference) - _frameUpdateObservers = nil; - _modulesByID = nil; - _queuesByID = nil; - _modulesByName = nil; + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + _queuesByID = nil; + _modulesByID = nil; + _modulesByName = nil; + _frameUpdateObservers = nil; + }); }; if (!_javaScriptExecutor) { @@ -1185,13 +1183,30 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module } #pragma mark - Payload Generation - -- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module +- (void)dispatchBlock:(dispatch_block_t)block + forModule:(id)module { - [self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]]; + [self dispatchBlock:block forModule:module dispatchGroup:NULL]; } -- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID +- (void)dispatchBlock:(dispatch_block_t)block + forModule:(id)module + dispatchGroup:(dispatch_group_t)group +{ + [self dispatchBlock:block + forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])] + dispatchGroup:group]; +} + +- (void)dispatchBlock:(dispatch_block_t)block + forModuleID:(NSNumber *)moduleID +{ + [self dispatchBlock:block forModuleID:moduleID dispatchGroup:NULL]; +} + +- (void)dispatchBlock:(dispatch_block_t)block + forModuleID:(NSNumber *)moduleID + dispatchGroup:(dispatch_group_t)group { RCTAssertJSThread(); @@ -1203,7 +1218,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module if (queue == RCTJSThread) { [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; } else if (queue) { - dispatch_async(queue, block); + if (group != NULL) { + dispatch_group_async(group, queue, block); + } else { + dispatch_async(queue, block); + } } } From 22ea66923f437fe91657f90b146404eb0d0e51ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Oghin=C4=83?= Date: Mon, 22 Jun 2015 13:06:11 -0100 Subject: [PATCH 43/64] [react_native] JS files from D2172754: support setting the cursor position in TextInput --- Libraries/Components/TextInput/TextInput.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index d2845e601..a69491254 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -44,6 +44,8 @@ var AndroidTextInputAttributes = { autoCapitalize: true, autoCorrect: true, autoFocus: true, + textAlign: true, + textAlignVertical: true, keyboardType: true, multiline: true, password: true, @@ -124,6 +126,19 @@ var TextInput = React.createClass({ * If true, focuses the input on componentDidMount. Default value is false. */ autoFocus: PropTypes.bool, + /** + * Set the position of the cursor from where editing will begin. + */ + textAlign: PropTypes.oneOf([ + 'start', + 'center', + 'end', + ]), + textAlignVertical: PropTypes.oneOf([ + 'top', + 'center', + 'bottom', + ]), /** * If false, text is not editable. Default value is true. */ @@ -466,6 +481,10 @@ var TextInput = React.createClass({ _renderAndroid: function() { var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize]; + var textAlign = + RCTUIManager.AndroidTextInput.Constants.TextAlign[this.props.textAlign]; + var textAlignVertical = + RCTUIManager.AndroidTextInput.Constants.TextAlignVertical[this.props.textAlignVertical]; var children = this.props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); @@ -482,6 +501,8 @@ var TextInput = React.createClass({ style={[this.props.style]} autoCapitalize={autoCapitalize} autoCorrect={this.props.autoCorrect} + textAlign={textAlign} + textAlignVertical={textAlignVertical} keyboardType={this.props.keyboardType} multiline={this.props.multiline} onFocus={this._onFocus} From 457fca4cb3ea81faf8e7ca9d4a9b4da2f724c073 Mon Sep 17 00:00:00 2001 From: Ruben Niculcea Date: Mon, 22 Jun 2015 06:55:35 -0700 Subject: [PATCH 44/64] Allow live reload even on errors. Summary: Live reload is disabled when an error has occurred. This requires the developer to fix the error and then switch to the simulator to reload the device manually; impacting developer flow and increasing alt tabbing. This pull request fixes that by allowing live reload to work even on errors. This fixes issue: #1343. Closes https://github.com/facebook/react-native/pull/1549 Github Author: Ruben Niculcea Test Plan: Imported from GitHub, without a `Test Plan:` line. --- React/Base/RCTBridge.m | 2 ++ React/Base/RCTDevMenu.m | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index d7ec0ac48..5a03d25b0 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -995,6 +995,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module return; } + [[RCTRedBox sharedInstance] dismiss]; + RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; sourceCodeModule.scriptURL = bundleURL; sourceCodeModule.scriptText = script; diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index ed936bbe8..a37562912 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -119,6 +119,11 @@ RCT_EXPORT_MODULE() name:RCTJavaScriptDidLoadNotification object:nil]; + [notificationCenter addObserver:self + selector:@selector(jsLoaded:) + name:RCTJavaScriptDidFailToLoadNotification + object:nil]; + _defaults = [NSUserDefaults standardUserDefaults]; _settings = [[NSMutableDictionary alloc] init]; _extraMenuItems = [NSMutableArray array]; From eda44edad94d63ece576c53b3ebd8a1bdeec8e59 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 22 Jun 2015 08:08:35 -0700 Subject: [PATCH 45/64] Fixed Cmd-R shortcut on iOS 9 --- React/Base/RCTKeyCommands.m | 123 ++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 28 deletions(-) diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index 387bfc1a9..a9d358482 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -11,8 +11,16 @@ #import +#import "RCTDefines.h" #import "RCTUtils.h" +#if RCT_DEV + +static BOOL RCTIsIOS8OrEarlier() +{ + return [UIDevice currentDevice].systemVersion.floatValue < 9; +} + @interface RCTKeyCommand : NSObject @property (nonatomic, strong) UIKeyCommand *keyCommand; @@ -27,7 +35,7 @@ { if ((self = [super init])) { _keyCommand = keyCommand; - _block = block ?: ^(__unused UIKeyCommand *cmd) {}; + _block = block; } return self; } @@ -58,29 +66,58 @@ RCT_NOT_IMPLEMENTED(-init) return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>", + [self class], self, _keyCommand.input, _keyCommand.modifierFlags, + _block ? @"YES" : @"NO"]; +} + @end @interface RCTKeyCommands () @property (nonatomic, strong) NSMutableSet *commands; -- (BOOL)RCT_handleKeyCommand:(UIKeyCommand *)key; +@end + +@implementation UIResponder (RCTKeyCommands) + +- (NSArray *)RCT_keyCommands +{ + NSSet *commands = [RCTKeyCommands sharedInstance].commands; + return [[commands valueForKeyPath:@"keyCommand"] allObjects]; +} + +- (void)RCT_handleKeyCommand:(UIKeyCommand *)key +{ + // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: + // method gets called repeatedly if the command key is held down. + + static NSTimeInterval lastCommand = 0; + if (RCTIsIOS8OrEarlier() || CACurrentMediaTime() - lastCommand > 0.5) { + for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { + if ([command.keyCommand.input isEqualToString:key.input] && + command.keyCommand.modifierFlags == key.modifierFlags) { + if (command.block) { + command.block(key); + lastCommand = CACurrentMediaTime(); + } + } + } + } +} @end @implementation UIApplication (RCTKeyCommands) -- (NSArray *)RCT_keyCommands -{ - NSSet *commands = [RCTKeyCommands sharedInstance].commands; - return [[self RCT_keyCommands] arrayByAddingObjectsFromArray: - [[commands valueForKeyPath:@"keyCommand"] allObjects]]; -} - +// Required for iOS 8.x - (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event { if (action == @selector(RCT_handleKeyCommand:)) { - return [[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender]; + [self RCT_handleKeyCommand:sender]; + return YES; } return [self RCT_sendAction:action to:target from:sender forEvent:event]; } @@ -91,9 +128,23 @@ RCT_NOT_IMPLEMENTED(-init) + (void)initialize { - //swizzle UIApplication - RCTSwapInstanceMethods([UIApplication class], @selector(keyCommands), @selector(RCT_keyCommands)); - RCTSwapInstanceMethods([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(RCT_sendAction:to:from:forEvent:)); + if (RCTIsIOS8OrEarlier()) { + + //swizzle UIApplication + RCTSwapInstanceMethods([UIApplication class], + @selector(keyCommands), + @selector(RCT_keyCommands)); + + RCTSwapInstanceMethods([UIApplication class], + @selector(sendAction:to:from:forEvent:), + @selector(RCT_sendAction:to:from:forEvent:)); + } else { + + //swizzle UIResponder + RCTSwapInstanceMethods([UIResponder class], + @selector(keyCommands), + @selector(RCT_keyCommands)); + } } + (instancetype)sharedInstance @@ -121,11 +172,11 @@ RCT_NOT_IMPLEMENTED(-init) { RCTAssertMainThread(); - if (input.length && flags) { + if (input.length && flags && RCTIsIOS8OrEarlier()) { // Workaround around the first cmd not working: http://openradar.appspot.com/19613391 // You can register just the cmd key and do nothing. This ensures that - // command-key modified commands will work first time. + // command-key modified commands will work first time. Fixed in iOS 9. [self registerKeyCommandWithInput:@"" modifierFlags:flags @@ -136,19 +187,9 @@ RCT_NOT_IMPLEMENTED(-init) modifierFlags:flags action:@selector(RCT_handleKeyCommand:)]; - [_commands addObject:[[RCTKeyCommand alloc] initWithKeyCommand:command block:block]]; -} - -- (BOOL)RCT_handleKeyCommand:(UIKeyCommand *)key -{ - for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) { - if ([command.keyCommand.input isEqualToString:key.input] && - command.keyCommand.modifierFlags == key.modifierFlags) { - command.block(key); - return YES; - } - } - return NO; + RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block]; + [_commands removeObject:keyCommand]; + [_commands addObject:keyCommand]; } - (void)unregisterKeyCommandWithInput:(NSString *)input @@ -178,3 +219,29 @@ RCT_NOT_IMPLEMENTED(-init) } @end + +#else + +@implementation RCTKeyCommands + ++ (instancetype)sharedInstance +{ + return nil; +} + +- (void)registerKeyCommandWithInput:(NSString *)input + modifierFlags:(UIKeyModifierFlags)flags + action:(void (^)(UIKeyCommand *))block {} + +- (void)unregisterKeyCommandWithInput:(NSString *)input + modifierFlags:(UIKeyModifierFlags)flags {} + +- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input + modifierFlags:(UIKeyModifierFlags)flags +{ + return NO; +} + +@end + +#endif From fccea2f365983bbd05bc24c7960dbbb31bf26775 Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Mon, 22 Jun 2015 08:42:02 -0700 Subject: [PATCH 46/64] Replace bluebird with promise --- packager/react-packager/__mocks__/bluebird.js | 5 --- .../AssetServer/__tests__/AssetServer-test.js | 2 +- .../react-packager/src/AssetServer/index.js | 41 +++++++++++-------- .../src/DependencyResolver/AssetModule.js | 2 +- .../AssetModule_DEPRECATED.js | 2 +- .../DependencyGraph/index.js | 2 +- .../src/DependencyResolver/Module.js | 2 +- .../__tests__/HasteDependencyResolver-test.js | 2 +- .../src/DependencyResolver/fastfs.js | 8 ++-- .../src/DependencyResolver/index.js | 2 +- .../react-packager/src/FileWatcher/index.js | 4 +- .../react-packager/src/JSTransformer/Cache.js | 14 ++++--- .../src/JSTransformer/__tests__/Cache-test.js | 2 +- .../react-packager/src/JSTransformer/index.js | 6 +-- .../src/Packager/__tests__/Packager-test.js | 2 +- packager/react-packager/src/Packager/index.js | 10 +++-- .../src/Server/__tests__/Server-test.js | 2 +- packager/react-packager/src/Server/index.js | 2 +- 18 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 packager/react-packager/__mocks__/bluebird.js diff --git a/packager/react-packager/__mocks__/bluebird.js b/packager/react-packager/__mocks__/bluebird.js deleted file mode 100644 index 9ac6e14b6..000000000 --- a/packager/react-packager/__mocks__/bluebird.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -jest.autoMockOff(); -module.exports = require.requireActual('bluebird'); -jest.autoMockOn(); diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js index c6acc6a84..95916c9ea 100644 --- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -8,7 +8,7 @@ jest .mock('crypto') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('AssetServer', function() { var AssetServer; diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js index 0479afc2a..2cd365fdc 100644 --- a/packager/react-packager/src/AssetServer/index.js +++ b/packager/react-packager/src/AssetServer/index.js @@ -11,13 +11,13 @@ var declareOpts = require('../lib/declareOpts'); var getAssetDataFromName = require('../lib/getAssetDataFromName'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var fs = require('fs'); var crypto = require('crypto'); -var stat = Promise.promisify(fs.stat); -var readDir = Promise.promisify(fs.readdir); -var readFile = Promise.promisify(fs.readFile); +var stat = Promise.denodeify(fs.stat); +var readDir = Promise.denodeify(fs.readdir); +var readFile = Promise.denodeify(fs.readFile); module.exports = AssetServer; @@ -56,12 +56,15 @@ AssetServer.prototype._getAssetRecord = function(assetPath) { this._roots, path.dirname(assetPath) ).then(function(dir) { - return [ + return Promise.all([ dir, readDir(dir), - ]; - }).spread(function(dir, files) { + ]); + }).then(function(res) { + var dir = res[0]; + var files = res[1]; var assetData = getAssetDataFromName(filename); + var map = buildAssetMap(dir, files); var record = map[assetData.assetName]; @@ -114,21 +117,23 @@ AssetServer.prototype.getAssetData = function(assetPath) { }; function findRoot(roots, dir) { - return Promise.some( + return Promise.all( roots.map(function(root) { var absPath = path.join(root, dir); return stat(absPath).then(function(fstat) { - if (!fstat.isDirectory()) { - throw new Error('Looking for dirs'); - } - fstat._path = absPath; - return fstat; + return {path: absPath, isDirectory: fstat.isDirectory()}; + }, function (err) { + return {path: absPath, isDirectory: false}; }); - }), - 1 - ).spread( - function(fstat) { - return fstat._path; + }) + ).then( + function(stats) { + for (var i = 0; i < stats.length; i++) { + if (stats[i].isDirectory) { + return stats[i].path; + } + } + throw new Error('Could not find any directories'); } ); } diff --git a/packager/react-packager/src/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js index 431e6d01c..bfe4b6f88 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule.js @@ -1,7 +1,7 @@ 'use strict'; const Module = require('./Module'); -const Promise = require('bluebird'); +const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule extends Module { diff --git a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index 7a25c5905..fd4cb7081 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -1,7 +1,7 @@ 'use strict'; const Module = require('./Module'); -const Promise = require('bluebird'); +const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule_DEPRECATED extends Module { diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 5bb630849..63ba7875e 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -17,7 +17,7 @@ const isAbsolutePath = require('absolute-path'); const debug = require('debug')('DependencyGraph'); const getAssetDataFromName = require('../../lib/getAssetDataFromName'); const util = require('util'); -const Promise = require('bluebird'); +const Promise = require('promise'); const _ = require('underscore'); const validateOpts = declareOpts({ diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js index d0be012ba..3ae9354d3 100644 --- a/packager/react-packager/src/DependencyResolver/Module.js +++ b/packager/react-packager/src/DependencyResolver/Module.js @@ -1,6 +1,6 @@ 'use strict'; -const Promise = require('bluebird'); +const Promise = require('promise'); const docblock = require('./DependencyGraph/docblock'); const isAbsolutePath = require('absolute-path'); const path = require('path'); diff --git a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index f97828869..da159b5e9 100644 --- a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -14,7 +14,7 @@ jest.dontMock('../') jest.mock('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js index fc09ea8b3..0053b14e3 100644 --- a/packager/react-packager/src/DependencyResolver/fastfs.js +++ b/packager/react-packager/src/DependencyResolver/fastfs.js @@ -1,6 +1,6 @@ 'use strict'; -const Promise = require('bluebird'); +const Promise = require('promise'); const {EventEmitter} = require('events'); const _ = require('underscore'); @@ -8,9 +8,9 @@ const debug = require('debug')('DependencyGraph'); const fs = require('fs'); const path = require('path'); -const readDir = Promise.promisify(fs.readdir); -const readFile = Promise.promisify(fs.readFile); -const stat = Promise.promisify(fs.stat); +const readDir = Promise.denodeify(fs.readdir); +const readFile = Promise.denodeify(fs.readFile); +const stat = Promise.denodeify(fs.stat); const hasOwn = Object.prototype.hasOwnProperty; class Fastfs extends EventEmitter { diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index bfc4e9432..0ddf5c3cc 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -12,7 +12,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); var replacePatterns = require('./replacePatterns'); var declareOpts = require('../lib/declareOpts'); -var Promise = require('bluebird'); +var Promise = require('promise'); var validateOpts = declareOpts({ projectRoots: { diff --git a/packager/react-packager/src/FileWatcher/index.js b/packager/react-packager/src/FileWatcher/index.js index cd1a28e55..aac211ad2 100644 --- a/packager/react-packager/src/FileWatcher/index.js +++ b/packager/react-packager/src/FileWatcher/index.js @@ -10,7 +10,7 @@ var EventEmitter = require('events').EventEmitter; var sane = require('sane'); -var Promise = require('bluebird'); +var Promise = require('promise'); var util = require('util'); var exec = require('child_process').exec; @@ -57,7 +57,7 @@ util.inherits(FileWatcher, EventEmitter); FileWatcher.prototype.end = function() { return this._loading.then(function(watchers) { watchers.forEach(function(watcher) { - return Promise.promisify(watcher.close, watcher)(); + return Promise.denodeify(watcher.close).call(watcher); }); }); }; diff --git a/packager/react-packager/src/JSTransformer/Cache.js b/packager/react-packager/src/JSTransformer/Cache.js index 584077b6c..aee8d4f21 100644 --- a/packager/react-packager/src/JSTransformer/Cache.js +++ b/packager/react-packager/src/JSTransformer/Cache.js @@ -14,7 +14,7 @@ var declareOpts = require('../lib/declareOpts'); var fs = require('fs'); var isAbsolutePath = require('absolute-path'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var tmpdir = require('os').tmpDir(); var version = require('../../../../package.json').version; @@ -74,11 +74,13 @@ Cache.prototype.get = function(filepath, loaderCb) { Cache.prototype._set = function(filepath, loaderPromise) { this._data[filepath] = loaderPromise.then(function(data) { - return [ + return Promise.all([ data, - Promise.promisify(fs.stat)(filepath) - ]; - }).spread(function(data, stat) { + Promise.denodeify(fs.stat)(filepath) + ]); + }).then(function(ref) { + var data = ref[0]; + var stat = ref[1]; this._persistEventually(); return { data: data, @@ -113,7 +115,7 @@ Cache.prototype._persistCache = function() { Object.keys(data).forEach(function(key, i) { json[key] = values[i]; }); - return Promise.promisify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); + return Promise.denodeify(fs.writeFile)(cacheFilepath, JSON.stringify(json)); }) .then(function() { this._persisting = null; diff --git a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js index f91490ba0..3877b3dd5 100644 --- a/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js +++ b/packager/react-packager/src/JSTransformer/__tests__/Cache-test.js @@ -17,7 +17,7 @@ jest .mock('os') .mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('JSTransformer Cache', function() { var Cache; diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 513d4394e..7c20e10ed 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -9,14 +9,14 @@ 'use strict'; var fs = require('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Cache = require('./Cache'); var workerFarm = require('worker-farm'); var declareOpts = require('../lib/declareOpts'); var util = require('util'); var ModuleTransport = require('../lib/ModuleTransport'); -var readFile = Promise.promisify(fs.readFile); +var readFile = Promise.denodeify(fs.readFile); module.exports = Transformer; Transformer.TransformError = TransformError; @@ -69,7 +69,7 @@ function Transformer(options) { options.transformModulePath ); - this._transform = Promise.promisify(this._workers); + this._transform = Promise.denodeify(this._workers); } } diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js index aaa8963c2..216e9009f 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js @@ -17,7 +17,7 @@ jest jest.mock('fs'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('Packager', function() { var getDependencies; diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js index 04404ff19..3c6d1a2fc 100644 --- a/packager/react-packager/src/Packager/index.js +++ b/packager/react-packager/src/Packager/index.js @@ -11,7 +11,7 @@ var assert = require('assert'); var fs = require('fs'); var path = require('path'); -var Promise = require('bluebird'); +var Promise = require('promise'); var Transformer = require('../JSTransformer'); var DependencyResolver = require('../DependencyResolver'); var Package = require('./Package'); @@ -20,8 +20,8 @@ var ModuleTransport = require('../lib/ModuleTransport'); var declareOpts = require('../lib/declareOpts'); var imageSize = require('image-size'); -var sizeOf = Promise.promisify(imageSize); -var readFile = Promise.promisify(fs.readFile); +var sizeOf = Promise.denodeify(imageSize); +var readFile = Promise.denodeify(fs.readFile); var validateOpts = declareOpts({ projectRoots: { @@ -207,7 +207,9 @@ Packager.prototype.generateAssetModule = function(ppackage, module) { return Promise.all([ sizeOf(module.path), this._assetServer.getAssetData(relPath), - ]).spread(function(dimensions, assetData) { + ]).then(function(res) { + var dimensions = res[0]; + var assetData = res[1]; var img = { __packager_asset: true, fileSystemLocation: path.dirname(module.path), diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index e4e7b5088..32c9060a4 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -20,7 +20,7 @@ jest.setMock('worker-farm', function() { return function() {}; }) .setMock('uglify-js') .dontMock('../'); -var Promise = require('bluebird'); +var Promise = require('promise'); describe('processRequest', function() { var server; diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 914075fea..1d2140ef5 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -15,7 +15,7 @@ var FileWatcher = require('../FileWatcher'); var Packager = require('../Packager'); var Activity = require('../Activity'); var AssetServer = require('../AssetServer'); -var Promise = require('bluebird'); +var Promise = require('promise'); var _ = require('underscore'); var exec = require('child_process').exec; var fs = require('fs'); From 972b546fc69d17215e13f4d268d8ec8bb762c96b Mon Sep 17 00:00:00 2001 From: Gabe Levi Date: Mon, 22 Jun 2015 09:43:30 -0700 Subject: [PATCH 47/64] [Flow] Fix or suppress errors in react-native for Flow v0.13.0 --- .flowconfig | 5 +++-- Examples/Movies/SearchScreen.js | 2 ++ Examples/UIExplorer/DatePickerIOSExample.js | 1 + Examples/UIExplorer/ImageExample.js | 1 + Examples/UIExplorer/MapViewExample.js | 1 + Examples/UIExplorer/PickerIOSExample.js | 1 + Examples/UIExplorer/ProgressViewIOSExample.js | 1 + Examples/UIExplorer/ScrollViewExample.js | 1 + Examples/UIExplorer/TextInputExample.js | 1 + Examples/UIExplorer/TouchableExample.js | 1 + Examples/UIExplorer/WebViewExample.js | 1 + Libraries/AppRegistry/AppRegistry.js | 2 +- 12 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.flowconfig b/.flowconfig index bf06f66d3..56d38f308 100644 --- a/.flowconfig +++ b/.flowconfig @@ -40,8 +40,9 @@ suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-2]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy [version] 0.12.0 diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index e7eb7f2b4..21819df46 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -77,12 +77,14 @@ var SearchScreen = React.createClass({ var apiKey = API_KEYS[this.state.queryNumber % API_KEYS.length]; if (query) { return ( + // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'movies.json?apikey=' + apiKey + '&q=' + encodeURIComponent(query) + '&page_limit=20&page=' + pageNumber ); } else { // With no query, load latest movies return ( + // $FlowFixMe(>=0.13.0) - pageNumber may be null or undefined API_URL + 'lists/movies/in_theaters.json?apikey=' + apiKey + '&page_limit=20&page=' + pageNumber ); diff --git a/Examples/UIExplorer/DatePickerIOSExample.js b/Examples/UIExplorer/DatePickerIOSExample.js index 36ff9cd52..fc7686880 100644 --- a/Examples/UIExplorer/DatePickerIOSExample.js +++ b/Examples/UIExplorer/DatePickerIOSExample.js @@ -125,6 +125,7 @@ var Heading = React.createClass({ } }); +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Select dates and times using the native UIDatePicker.'; exports.examples = [ diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js index b4c54f997..60a4a5ab1 100644 --- a/Examples/UIExplorer/ImageExample.js +++ b/Examples/UIExplorer/ImageExample.js @@ -25,6 +25,7 @@ var { var ImageCapInsetsExample = require('./ImageCapInsetsExample'); +exports.displayName = (undefined: ?string); exports.framework = 'React'; exports.title = ''; exports.description = 'Base component for displaying different types of images.'; diff --git a/Examples/UIExplorer/MapViewExample.js b/Examples/UIExplorer/MapViewExample.js index 23789c7f1..0d1061ace 100644 --- a/Examples/UIExplorer/MapViewExample.js +++ b/Examples/UIExplorer/MapViewExample.js @@ -236,6 +236,7 @@ var styles = StyleSheet.create({ }, }); +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Base component to display maps'; exports.examples = [ diff --git a/Examples/UIExplorer/PickerIOSExample.js b/Examples/UIExplorer/PickerIOSExample.js index 14361e760..31c81cccc 100644 --- a/Examples/UIExplorer/PickerIOSExample.js +++ b/Examples/UIExplorer/PickerIOSExample.js @@ -112,6 +112,7 @@ var PickerExample = React.createClass({ }, }); +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Render lists of selectable options with UIPickerView.'; exports.examples = [ diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/ProgressViewIOSExample.js index f0a17a7c6..e294a3370 100644 --- a/Examples/UIExplorer/ProgressViewIOSExample.js +++ b/Examples/UIExplorer/ProgressViewIOSExample.js @@ -60,6 +60,7 @@ var ProgressViewExample = React.createClass({ }, }); +exports.displayName = (undefined: ?string); exports.framework = 'React'; exports.title = 'ProgressViewIOS'; exports.description = 'ProgressViewIOS'; diff --git a/Examples/UIExplorer/ScrollViewExample.js b/Examples/UIExplorer/ScrollViewExample.js index 69f3ac9c7..1ca8baf9a 100644 --- a/Examples/UIExplorer/ScrollViewExample.js +++ b/Examples/UIExplorer/ScrollViewExample.js @@ -23,6 +23,7 @@ var { Image } = React; +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Component that enables scrolling through child components'; exports.examples = [ diff --git a/Examples/UIExplorer/TextInputExample.js b/Examples/UIExplorer/TextInputExample.js index 10064491d..06cc12ee3 100644 --- a/Examples/UIExplorer/TextInputExample.js +++ b/Examples/UIExplorer/TextInputExample.js @@ -133,6 +133,7 @@ var styles = StyleSheet.create({ }, }); +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Single and multi-line text inputs.'; exports.examples = [ diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index acbba3629..494d7771d 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -26,6 +26,7 @@ var { View, } = React; +exports.displayName = (undefined: ?string); exports.title = ' and onPress'; exports.examples = [ { diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js index 6a93f80e6..fe3cbef6f 100644 --- a/Examples/UIExplorer/WebViewExample.js +++ b/Examples/UIExplorer/WebViewExample.js @@ -220,6 +220,7 @@ var styles = StyleSheet.create({ }, }); +exports.displayName = (undefined: ?string); exports.title = ''; exports.description = 'Base component to display web content'; exports.examples = [ diff --git a/Libraries/AppRegistry/AppRegistry.js b/Libraries/AppRegistry/AppRegistry.js index 157cbaa37..7465d85b9 100644 --- a/Libraries/AppRegistry/AppRegistry.js +++ b/Libraries/AppRegistry/AppRegistry.js @@ -68,7 +68,7 @@ var AppRegistry = { console.log( 'Running application "' + appKey + '" with appParams: ' + JSON.stringify(appParameters) + '. ' + - '__DEV__ === ' + __DEV__ + + '__DEV__ === ' + String(__DEV__) + ', development-level warning are ' + (__DEV__ ? 'ON' : 'OFF') + ', performance optimizations are ' + (__DEV__ ? 'OFF' : 'ON') ); From 1429b78af59937d102eb006808f599e9c5e305ce Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Mon, 22 Jun 2015 10:08:20 -0700 Subject: [PATCH 48/64] [ReactNative] Ignore bad inputs to parseErrorStack --- .../__tests__/parseErrorStack-test.js | 49 +++++++++++++++++++ .../Initialization/parseErrorStack.js | 4 ++ 2 files changed, 53 insertions(+) create mode 100644 Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js diff --git a/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js b/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.js new file mode 100644 index 000000000..b32c5acfa --- /dev/null +++ b/Libraries/JavaScriptAppEngine/Initialization/__tests__/parseErrorStack-test.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. + */ +'use strict'; + + +require('mock-modules').autoMockOff(); + +var parseErrorStack = require('parseErrorStack'); + +function getFakeError() { + return new Error('Happy Cat'); +} + +describe('parseErrorStack', function() { + + it('parses error stack', function() { + var stack = parseErrorStack(getFakeError()); + expect(stack.length).toBeGreaterThan(0); + + var firstFrame = stack[0]; + expect(firstFrame.methodName).toEqual('getFakeError'); + expect(firstFrame.file).toMatch(/parseErrorStack-test\.js$/); + }); + + it('supports framesToPop', function() { + function getWrappedError() { + var error = getFakeError(); + error.framesToPop = 1; + return error; + } + + // Make sure framesToPop == 1 causes it to ignore getFakeError + // stack frame + var stack = parseErrorStack(getWrappedError()); + expect(stack[0].methodName).toEqual('getWrappedError'); + }); + + it('ignores bad inputs', function() { + expect(parseErrorStack({})).toEqual([]); + expect(parseErrorStack(null)).toEqual([]); + }); + +}); diff --git a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js index 9dd7c47bb..bbaa1a276 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js +++ b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js @@ -28,6 +28,10 @@ function resolveSourceMaps(sourceMapInstance, stackFrame) { } function parseErrorStack(e, sourceMapInstance) { + if (!e || !e.stack) { + return []; + } + var stack = stacktraceParser.parse(e.stack); var framesToPop = e.framesToPop || 0; From 7f54506f96ff44add072ea1f83fd6650ab7a750b Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Mon, 22 Jun 2015 11:04:40 -0700 Subject: [PATCH 49/64] : Fix the getter for `navigationContext`. Summary: @public The current getter for `navigationContext` always return a static context, and it should return an instance-based one, instead. Test Plan: Use console.log() in inspect that two different navigators do have their own `navigationContext` created. --- Libraries/CustomComponents/Navigator/Navigator.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index 93610e973..c6e3a888b 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -289,6 +289,9 @@ var Navigator = React.createClass({ }, componentWillMount: function() { + // TODO(t7489503): Don't need this once ES6 Class landed. + this.__defineGetter__('navigationContext', this._getNavigationContext); + this._subRouteFocus = []; this.parentNavigator = this.props.navigator; this._handlers = {}; @@ -1153,8 +1156,7 @@ var Navigator = React.createClass({ ); }, - // Getter for `navigationContext`. - get navigationContext() { + _getNavigationContext: function() { if (!this._navigationContext) { this._navigationContext = new NavigationContext(); } From a8011f283d6ad0714147573ca8a9a7af81172f4d Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Mon, 22 Jun 2015 12:58:12 -0700 Subject: [PATCH 50/64] [React Native][Packager] allow --assetRoots to be relative paths --- packager/packager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packager/packager.js b/packager/packager.js index c719b8608..f2d526e06 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -86,7 +86,9 @@ if (options.root) { if (options.assetRoots) { if (!Array.isArray(options.assetRoots)) { - options.assetRoots = options.assetRoots.split(','); + options.assetRoots = options.assetRoots.split(',').map(function (dir) { + return path.resolve(process.cwd(), dir); + }); } } else { if (__dirname.match(/node_modules\/react-native\/packager$/)) { From 3d6ffcf903f02bc3e67ebee1e57c7baf62fe1398 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Mon, 22 Jun 2015 13:29:39 -0700 Subject: [PATCH 51/64] [ReactNative][Profiler] Fix initialize extra call + add popup Summary: @public When the profile is initialized, it automatically hooks into every method of the bridge modules, that was causing `+initialize` to be called twice. Also add a popup to notify the user that the profile has been created. Test Plan: Run the UIExplorer, start the profiler, try to rage shake to open the dev menu again. It should now work, and show an alertview with some information once the profile is stopped. --- React/Base/RCTBridge.m | 6 ++++++ React/Base/RCTProfile.m | 2 ++ 2 files changed, 8 insertions(+) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 5a03d25b0..c1b4373c9 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1529,6 +1529,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module ^(__unused NSData *data, __unused NSURLResponse *response, NSError *error) { if (error) { RCTLogError(@"%@", error.localizedDescription); + } else { + [[[UIAlertView alloc] initWithTitle:@"Profile" + message:@"The profile has been generated, check the dev server log for instructions." + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] show]; } }]; diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index a2b3d7106..d81d545f2 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -172,6 +172,8 @@ static void RCTProfileHookModules(RCTBridge *bridge) } free(methods); + class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); + for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { Method oldImp = class_getInstanceMethod(cls, @selector(class)); class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); From 5b476d0e41914372e1c536a1d691f14216cd19b7 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Mon, 22 Jun 2015 13:37:25 -0700 Subject: [PATCH 52/64] [ReactNative] Fix manual ListView loading Summary: @public If something changes in the list view that should trigger more loads, it wouldn't. Example case is tap to load more - only the first new row would load, but it wouldn't trigger a re-measure and subsequent layout of additional new rows. Test Plan: View More in Events works. --- Libraries/CustomComponents/ListView/ListView.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 857f476c1..dda32340c 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -275,6 +275,10 @@ var ListView = React.createClass({ } }, + componentDidUpdate: function() { + this._measureAndUpdateScrollProps(); + }, + onRowHighlighted: function(sectionID, rowID) { this.setState({highlightedRow: {sectionID, rowID}}); }, @@ -368,7 +372,6 @@ var ListView = React.createClass({ if (!props.scrollEventThrottle) { props.scrollEventThrottle = DEFAULT_SCROLL_CALLBACK_THROTTLE; } - return ( From 86639450ee0f707d414ce4b6e18c82deca168752 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 22 Jun 2015 14:37:11 -0700 Subject: [PATCH 53/64] Fixed AsyncLocalStorage bug --- .../js/AsyncStorageTest.js | 26 +++--- React/Modules/RCTAsyncLocalStorage.h | 2 +- React/Modules/RCTAsyncLocalStorage.m | 82 +++++++++++-------- 3 files changed, 64 insertions(+), 46 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js index 911887d3e..c440d10cd 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js @@ -53,15 +53,15 @@ function expectEqual(lhs, rhs, testname) { ); } -function expectAsyncNoError(err) { - expectTrue(err === null, 'Unexpected Async error: ' + JSON.stringify(err)); +function expectAsyncNoError(place, err) { + expectTrue(err === null, 'Unexpected error in ' + place + ': ' + JSON.stringify(err)); } function testSetAndGet() { AsyncStorage.setItem(KEY_1, VAL_1, (err1) => { - expectAsyncNoError(err1); + expectAsyncNoError('testSetAndGet/setItem', err1); AsyncStorage.getItem(KEY_1, (err2, result) => { - expectAsyncNoError(err2); + expectAsyncNoError('testSetAndGet/getItem', err2); expectEqual(result, VAL_1, 'testSetAndGet setItem'); updateMessage('get(key_1) correctly returned ' + result); runTestCase('should get null for missing key', testMissingGet); @@ -71,7 +71,7 @@ function testSetAndGet() { function testMissingGet() { AsyncStorage.getItem(KEY_2, (err, result) => { - expectAsyncNoError(err); + expectAsyncNoError('testMissingGet/setItem', err); expectEqual(result, null, 'testMissingGet'); updateMessage('missing get(key_2) correctly returned ' + result); runTestCase('check set twice results in a single key', testSetTwice); @@ -82,7 +82,7 @@ function testSetTwice() { AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ AsyncStorage.getItem(KEY_1, (err, result) => { - expectAsyncNoError(err); + expectAsyncNoError('testSetTwice/setItem', err); expectEqual(result, VAL_1, 'testSetTwice'); updateMessage('setTwice worked as expected'); runTestCase('test removeItem', testRemoveItem); @@ -95,17 +95,17 @@ function testRemoveItem() { AsyncStorage.setItem(KEY_1, VAL_1, ()=>{ AsyncStorage.setItem(KEY_2, VAL_2, ()=>{ AsyncStorage.getAllKeys((err, result) => { - expectAsyncNoError(err); + expectAsyncNoError('testRemoveItem/getAllKeys', err); expectTrue( result.indexOf(KEY_1) >= 0 && result.indexOf(KEY_2) >= 0, 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')' ); updateMessage('testRemoveItem - add two items'); AsyncStorage.removeItem(KEY_1, (err2) => { - expectAsyncNoError(err2); + expectAsyncNoError('testRemoveItem/removeItem', err2); updateMessage('delete successful '); AsyncStorage.getItem(KEY_1, (err3, result2) => { - expectAsyncNoError(err3); + expectAsyncNoError('testRemoveItem/getItem', err3); expectEqual( result2, null, @@ -113,7 +113,7 @@ function testRemoveItem() { ); updateMessage('key properly removed '); AsyncStorage.getAllKeys((err4, result3) => { - expectAsyncNoError(err4); + expectAsyncNoError('testRemoveItem/getAllKeys', err4); expectTrue( result3.indexOf(KEY_1) === -1, 'Unexpected: KEY_1 present in ' + result3 @@ -130,11 +130,11 @@ function testRemoveItem() { function testMerge() { AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => { - expectAsyncNoError(err1); + expectAsyncNoError('testMerge/setItem', err1); AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => { - expectAsyncNoError(err2); + expectAsyncNoError('testMerge/mergeItem', err2); AsyncStorage.getItem(KEY_MERGE, (err3, result) => { - expectAsyncNoError(err3); + expectAsyncNoError('testMerge/setItem', err3); expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge'); updateMessage('objects deeply merged\nDone!'); done(); diff --git a/React/Modules/RCTAsyncLocalStorage.h b/React/Modules/RCTAsyncLocalStorage.h index 0a1aaacbc..4fd1064ad 100644 --- a/React/Modules/RCTAsyncLocalStorage.h +++ b/React/Modules/RCTAsyncLocalStorage.h @@ -32,6 +32,6 @@ - (void)getAllKeys:(RCTResponseSenderBlock)callback; // For clearing data when the bridge may not exist, e.g. when logging out. -+ (NSError *)clearAllData; ++ (void)clearAllData; @end diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 5ed2e48e5..50b5312f9 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -96,6 +96,26 @@ static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *so } } +static dispatch_queue_t RCTGetMethodQueue() +{ + // We want all instances to share the same queue since they will be reading/writing the same files. + static dispatch_queue_t queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + queue = dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); + }); + return queue; +} + +static BOOL RCTHasCreatedStorageDirectory = NO; +static NSError *RCTDeleteStorageDirectory() +{ + NSError *error; + [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDir() error:&error]; + RCTHasCreatedStorageDirectory = NO; + return error; +} + #pragma mark - RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage @@ -113,26 +133,20 @@ RCT_EXPORT_MODULE() - (dispatch_queue_t)methodQueue { - // We want all instances to share the same queue since they will be reading/writing the same files. - static dispatch_queue_t queue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("com.facebook.React.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL); - }); - return queue; + return RCTGetMethodQueue(); } -+ (NSError *)clearAllData ++ (void)clearAllData { - NSError *error; - [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDir() error:&error]; - return error; + dispatch_async(RCTGetMethodQueue(), ^{ + RCTDeleteStorageDirectory(); + }); } - (void)invalidate { if (_clearOnInvalidate) { - [RCTAsyncLocalStorage clearAllData]; + RCTDeleteStorageDirectory(); } _clearOnInvalidate = NO; _manifest = [[NSMutableDictionary alloc] init]; @@ -156,27 +170,31 @@ RCT_EXPORT_MODULE() - (id)_ensureSetup { - if (_haveSetup) { - return nil; + RCTAssertThread(RCTGetMethodQueue(), @"Must be executed on storage thread"); + + NSError *error = nil; + if (!RCTHasCreatedStorageDirectory) { + _storageDirectory = RCTGetStorageDir(); + [[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) { + return RCTMakeError(@"Failed to create storage directory.", error, nil); + } + RCTHasCreatedStorageDirectory = YES; } - _storageDirectory = RCTGetStorageDir(); - NSError *error; - [[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory - withIntermediateDirectories:YES - attributes:nil - error:&error]; - if (error) { - return RCTMakeError(@"Failed to create storage directory.", error, nil); + if (!_haveSetup) { + _manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename]; + NSDictionary *errorOut; + NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut); + _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [[NSMutableDictionary alloc] init]; + if (error) { + RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); + _manifest = [[NSMutableDictionary alloc] init]; + } + _haveSetup = YES; } - _manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename]; - NSDictionary *errorOut; - NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut); - _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [[NSMutableDictionary alloc] init]; - if (error) { - RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); - _manifest = [[NSMutableDictionary alloc] init]; - } - _haveSetup = YES; return nil; } @@ -349,7 +367,7 @@ RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { _manifest = [[NSMutableDictionary alloc] init]; - NSError *error = [RCTAsyncLocalStorage clearAllData]; + NSError *error = RCTDeleteStorageDirectory(); if (callback) { callback(@[RCTNullIfNil(error)]); } From d105ae7e519d0c26578aa0c6e7b6e1768008cbf3 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Mon, 22 Jun 2015 16:43:06 -0700 Subject: [PATCH 54/64] [AdsManager] Fix Navigation focus events for logging --- .../Navigator/Navigation/NavigationContext.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js index d146f14d0..35ed24e3a 100644 --- a/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js +++ b/Libraries/CustomComponents/Navigator/Navigation/NavigationContext.js @@ -44,7 +44,8 @@ class NavigationContext { constructor() { this._eventEmitter = new NavigationEventEmitter(this); this._currentRoute = null; - this.addListener('didfocus', this._onDidFocus, this); + this.addListener('willfocus', this._onFocus, this); + this.addListener('didfocus', this._onFocus, this); } // TODO: @flow does not like this getter. Will add @flow check back once @@ -83,7 +84,7 @@ class NavigationContext { } } - _onDidFocus(event: NavigationEvent): void { + _onFocus(event: NavigationEvent): void { invariant( event.data && event.data.hasOwnProperty('route'), 'didfocus event should provide route' From 0c38229e8e500687be928e22dcaf20511f566158 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Mon, 22 Jun 2015 16:58:32 -0700 Subject: [PATCH 55/64] [Navigator] Fix overswipe to -1, move guard --- Libraries/CustomComponents/Navigator/Navigator.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index c6e3a888b..d9e452d22 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -412,13 +412,11 @@ var Navigator = React.createClass({ ); } else if (this.state.activeGesture != null) { var presentedToIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture); - if (presentedToIndex > -1) { - this._transitionBetween( - this.state.presentedIndex, - presentedToIndex, - this.spring.getCurrentValue() - ); - } + this._transitionBetween( + this.state.presentedIndex, + presentedToIndex, + this.spring.getCurrentValue() + ); } }, @@ -822,7 +820,7 @@ var Navigator = React.createClass({ this._transitionSceneStyle(fromIndex, toIndex, progress, fromIndex); this._transitionSceneStyle(fromIndex, toIndex, progress, toIndex); var navBar = this._navBar; - if (navBar && navBar.updateProgress) { + if (navBar && navBar.updateProgress && toIndex >= 0 && fromIndex >= 0) { navBar.updateProgress(progress, fromIndex, toIndex); } }, From bd3f9763f78cca6a3d6f11e57f45f4b428d76ba9 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 23 Jun 2015 08:17:29 -0100 Subject: [PATCH 56/64] [ReactNative] Fix alert out of main thread Summary: @public I've added an alert to notify when the profile has been generated, but it was being created out of the main thread. Test Plan: Launch the UIExplorer, start and stop profiling, an alert should show up, and everything should just keep working as expected. --- React/Base/RCTBridge.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index c1b4373c9..e844cd8b1 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1530,11 +1530,13 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(__unused NSString *)module if (error) { RCTLogError(@"%@", error.localizedDescription); } else { - [[[UIAlertView alloc] initWithTitle:@"Profile" - message:@"The profile has been generated, check the dev server log for instructions." - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil] show]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[[UIAlertView alloc] initWithTitle:@"Profile" + message:@"The profile has been generated, check the dev server log for instructions." + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] show]; + }); } }]; From 9ed2bd628555109a50c5a31fcb5a5a8890a67ed4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 23 Jun 2015 05:17:36 -0700 Subject: [PATCH 57/64] Fixed nil safety issue in RKSounds --- React/Base/RCTConvert.h | 6 ++++++ React/Base/RCTConvert.m | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 145e88b21..e96e4562d 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -46,6 +46,9 @@ + (NSURL *)NSURL:(id)json; + (NSURLRequest *)NSURLRequest:(id)json; +typedef NSURL RCTFileURL; ++ (RCTFileURL *)RCTFileURL:(id)json; + + (NSDate *)NSDate:(id)json; + (NSTimeZone *)NSTimeZone:(id)json; + (NSTimeInterval)NSTimeInterval:(id)json; @@ -95,6 +98,9 @@ typedef NSArray NSDictionaryArray; typedef NSArray NSURLArray; + (NSURLArray *)NSURLArray:(id)json; +typedef NSArray RCTFileURLArray; ++ (RCTFileURLArray *)RCTFileURLArray:(id)json; + typedef NSArray NSNumberArray; + (NSNumberArray *)NSNumberArray:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 804faf87e..201b783cb 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -122,6 +122,20 @@ RCT_CONVERTER(NSString *, NSString, description) return URL ? [NSURLRequest requestWithURL:URL] : nil; } ++ (RCTFileURL *)RCTFileURL:(id)json +{ + NSURL *fileURL = [self NSURL:json]; + if (![fileURL isFileURL]) { + RCTLogError(@"URI must be a local file, '%@' isn't.", fileURL); + return nil; + } + if (![[NSFileManager defaultManager] fileExistsAtPath:fileURL.path]) { + RCTLogError(@"File '%@' could not be found.", fileURL); + return nil; + } + return fileURL; +} + + (NSDate *)NSDate:(id)json { if ([json isKindOfClass:[NSNumber class]]) { @@ -854,6 +868,7 @@ NSArray *RCTConvertArrayValue(SEL type, id json) RCT_ARRAY_CONVERTER(NSString) RCT_ARRAY_CONVERTER(NSDictionary) RCT_ARRAY_CONVERTER(NSURL) +RCT_ARRAY_CONVERTER(RCTFileURL) RCT_ARRAY_CONVERTER(NSNumber) RCT_ARRAY_CONVERTER(UIColor) From 1e66e5f53d27b0d25afcb5874aa48a080619d6e9 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 23 Jun 2015 06:43:55 -0700 Subject: [PATCH 58/64] Fixed border bug on events dashboard --- React/Views/RCTView.m | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index a151efb5c..1acb1b2d6 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -494,12 +494,17 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:unused) !RCTRunningInTestEnvironment() && RCTCornerRadiiAreEqual(cornerRadii) && RCTBorderInsetsAreEqual(borderInsets) && - RCTBorderColorsAreEqual(borderColors); + RCTBorderColorsAreEqual(borderColors) && - // TODO: A problem with this is that iOS draws borders in front of the content - // whereas CSS draws them behind the content. Also iOS clips to the outside of - // the border, but CSS clips to the inside. To solve this, we'll need to add - // a container view inside the main view to correctly clip the subviews. + // iOS draws borders in front of the content whereas CSS draws them behind + // the content. For this reason, only use iOS border drawing when clipping + // or when the border is hidden. + + (borderInsets.top == 0 || CGColorGetAlpha(borderColors.top) == 0 || self.clipsToBounds); + + // iOS clips to the outside of the border, but CSS clips to the inside. To + // solve this, we'll need to add a container view inside the main view to + // correctly clip the subviews. if (useIOSBorderRendering) { layer.cornerRadius = cornerRadii.topLeft; From 47508566a0635bd54e7c67f66ac883c97a14ced2 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Tue, 23 Jun 2015 08:55:04 -0700 Subject: [PATCH 59/64] [ReactNative] resizeMode is not a nativeOnly prop Summary: resizeMode is a native prop, but it is also in the propTypes, so this causes an incorrect warning: ``` Prop resizeMode = `contain` should not be set directly on Image. ``` @public Test Plan: No warnings on image example in UIExplorer --- Libraries/Image/Image.ios.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 23e2c7a63..7a629ce9a 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -178,7 +178,6 @@ var nativeOnlyProps = { src: true, defaultImageSrc: true, imageTag: true, - resizeMode: true, }; if (__DEV__) { verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps); From 0624a0fe52288d2214d053efec681632dce3929e Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 23 Jun 2015 14:17:31 -0700 Subject: [PATCH 60/64] Fixed async local storage --- .../js/AsyncStorageTest.js | 3 ++ React/Modules/RCTAsyncLocalStorage.m | 44 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js index c440d10cd..dd7de2d69 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/js/AsyncStorageTest.js @@ -54,6 +54,9 @@ function expectEqual(lhs, rhs, testname) { } function expectAsyncNoError(place, err) { + if (err instanceof Error) { + err = err.message; + } expectTrue(err === null, 'Unexpected error in ' + place + ': ' + JSON.stringify(err)); } diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 50b5312f9..8300cc869 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -17,9 +17,9 @@ #import "RCTLog.h" #import "RCTUtils.h" -static NSString *const kStorageDir = @"RCTAsyncLocalStorage_V1"; -static NSString *const kManifestFilename = @"manifest.json"; -static const NSUInteger kInlineValueThreshold = 100; +static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1"; +static NSString *const RCTManifestFileName = @"manifest.json"; +static const NSUInteger RCTInlineValueThreshold = 100; #pragma mark - Static helper functions @@ -61,11 +61,25 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } -static NSString *RCTGetStorageDir() +static NSString *RCTGetStorageDirectory() { - NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; - NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES]; - return [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path]; + static NSString *storageDirectory = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + storageDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + storageDirectory = [storageDirectory stringByAppendingPathComponent:RCTStorageDirectory]; + }); + return storageDirectory; +} + +static NSString *RCTGetManifestFilePath() +{ + static NSString *manifestFilePath = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName]; + }); + return manifestFilePath; } // Only merges objects - all other types are just clobbered (including arrays) @@ -111,7 +125,7 @@ static BOOL RCTHasCreatedStorageDirectory = NO; static NSError *RCTDeleteStorageDirectory() { NSError *error; - [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDir() error:&error]; + [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error]; RCTHasCreatedStorageDirectory = NO; return error; } @@ -125,8 +139,6 @@ static NSError *RCTDeleteStorageDirectory() // in separate files (as opposed to nil values which don't exist). The manifest is read off disk at startup, and // written to disk after all mutations. NSMutableDictionary *_manifest; - NSString *_manifestPath; - NSString *_storageDirectory; } RCT_EXPORT_MODULE() @@ -165,7 +177,7 @@ RCT_EXPORT_MODULE() - (NSString *)_filePathForKey:(NSString *)key { NSString *safeFileName = RCTMD5Hash(key); - return [_storageDirectory stringByAppendingPathComponent:safeFileName]; + return [RCTGetStorageDirectory() stringByAppendingPathComponent:safeFileName]; } - (id)_ensureSetup @@ -174,8 +186,7 @@ RCT_EXPORT_MODULE() NSError *error = nil; if (!RCTHasCreatedStorageDirectory) { - _storageDirectory = RCTGetStorageDir(); - [[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory + [[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory() withIntermediateDirectories:YES attributes:nil error:&error]; @@ -185,9 +196,8 @@ RCT_EXPORT_MODULE() RCTHasCreatedStorageDirectory = YES; } if (!_haveSetup) { - _manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename]; NSDictionary *errorOut; - NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut); + NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), nil, &errorOut); _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [[NSMutableDictionary alloc] init]; if (error) { RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); @@ -202,7 +212,7 @@ RCT_EXPORT_MODULE() { NSError *error; NSString *serialized = RCTJSONStringify(_manifest, &error); - [serialized writeToFile:_manifestPath atomically:YES encoding:NSUTF8StringEncoding error:&error]; + [serialized writeToFile:RCTGetManifestFilePath() atomically:YES encoding:NSUTF8StringEncoding error:&error]; id errorOut; if (error) { errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil); @@ -248,7 +258,7 @@ RCT_EXPORT_MODULE() NSString *value = entry[1]; NSString *filePath = [self _filePathForKey:key]; NSError *error; - if (value.length <= kInlineValueThreshold) { + if (value.length <= RCTInlineValueThreshold) { if (_manifest[key] && _manifest[key] != (id)kCFNull) { // If the value already existed but wasn't inlined, remove the old file. [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil]; From b58578e9354e22f7867618fddbc2777877a1e727 Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Tue, 23 Jun 2015 15:30:54 -0700 Subject: [PATCH 61/64] [rn] Add PerformanceOverlay to the inspector --- Libraries/Inspector/Inspector.js | 12 ++++ Libraries/Inspector/InspectorPanel.js | 14 ++++- Libraries/Inspector/PerformanceOverlay.js | 67 +++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Libraries/Inspector/PerformanceOverlay.js diff --git a/Libraries/Inspector/Inspector.js b/Libraries/Inspector/Inspector.js index fa6e20e08..6b1ac5789 100644 --- a/Libraries/Inspector/Inspector.js +++ b/Libraries/Inspector/Inspector.js @@ -26,6 +26,7 @@ class Inspector extends React.Component { this.state = { panelPos: 'bottom', inspecting: true, + perfing: false, inspected: null, }; } @@ -59,9 +60,18 @@ class Inspector extends React.Component { }); } + setPerfing(val: bool) { + this.setState({ + perfing: val, + inspecting: false, + inspected: null, + }); + } + setInspecting(val: bool) { this.setState({ inspecting: val, + inspected: null }); } @@ -79,6 +89,8 @@ class Inspector extends React.Component { ); + } else if (this.props.perfing) { + contents = ( + + ); } else { contents = ( @@ -58,7 +63,12 @@ class InspectorPanel extends React.Component {