From 57a6a02dffdfb2517535d05938a737296c1fa7e4 Mon Sep 17 00:00:00 2001 From: Adam Roth Date: Tue, 11 Aug 2015 08:41:30 -0700 Subject: [PATCH 001/139] [CameraRoll] Image orientation fix for #1845 Summary: PR for #1845 Use ALAssetOrientationUp for image orientation. Originally committed at https://github.com/facebook/react-native/commit/b34a85f4da87c62656ca9d29c5085a46e768d147 Closes https://github.com/facebook/react-native/pull/2214 Github Author: Adam Roth --- Libraries/Image/RCTImageLoader.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 34a7dc2fc..44ad8d6b1 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -103,7 +103,7 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, if (imageRef) { UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale - orientation:(UIImageOrientation)representation.orientation]; + orientation:UIImageOrientationUp]; CGImageRelease(imageRef); return image; } From a5e9f83a0acd40e1907fc56420457d61bb0361a8 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 11 Aug 2015 08:33:28 -0700 Subject: [PATCH 002/139] Implemented lazy parsing of method signatures to improve TTI --- .../RCTConvert_UIFontTests.m | 4 +- .../RCTModuleMethodTests.m | 8 +- React/Base/RCTModuleData.m | 2 +- React/Base/RCTModuleMethod.h | 8 +- React/Base/RCTModuleMethod.m | 431 +++++++++--------- 5 files changed, 240 insertions(+), 213 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m index 0ccc13631..8521ab2ef 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m @@ -162,8 +162,8 @@ - (void)testFamilyStyleAndWeight { { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14]; - UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue", @"fontStyle": @"italic", @"fontWeight": @"100"}]; + UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-LightItalic" size:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Helvetica Neue", @"fontStyle": @"italic", @"fontWeight": @"300"}]; RCTAssertEqualFonts(expected, result); } { diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m index 9c3949d0e..78a95e2a8 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m @@ -63,9 +63,11 @@ static BOOL RCTLogsError(void (^block)(void)) // Specifying an NSNumber param without nonnull isn't allowed XCTAssertTrue(RCTLogsError(^{ NSString *methodName = @"doFooWithNumber:(NSNumber *)n"; - (void)[[RCTModuleMethod alloc] initWithObjCMethodName:methodName - JSMethodName:nil - moduleClass:[self class]]; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName + JSMethodName:nil + moduleClass:[self class]]; + // Invoke method to trigger parsing + [method invokeWithBridge:nil module:self arguments:@[@1]]; })); } diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index a5f929aea..c4c248643 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -88,7 +88,7 @@ RCT_NOT_IMPLEMENTED(-init); [self.methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger idx, __unused BOOL *stop) { methodconfig[method.JSMethodName] = @{ @"methodID": @(idx), - @"type": method.functionKind == RCTJavaScriptFunctionKindAsync ? @"remoteAsync" : @"remote", + @"type": method.functionType == RCTFunctionTypePromise ? @"remoteAsync" : @"remote", }; }]; config[@"methods"] = [methodconfig copy]; diff --git a/React/Base/RCTModuleMethod.h b/React/Base/RCTModuleMethod.h index ddb459056..5768ac4bc 100644 --- a/React/Base/RCTModuleMethod.h +++ b/React/Base/RCTModuleMethod.h @@ -11,9 +11,9 @@ @class RCTBridge; -typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) { - RCTJavaScriptFunctionKindNormal, - RCTJavaScriptFunctionKindAsync, +typedef NS_ENUM(NSUInteger, RCTFunctionType) { + RCTFunctionTypeNormal, + RCTFunctionTypePromise, }; typedef NS_ENUM(NSUInteger, RCTNullability) { @@ -35,7 +35,7 @@ typedef NS_ENUM(NSUInteger, RCTNullability) { @property (nonatomic, copy, readonly) NSString *JSMethodName; @property (nonatomic, readonly) Class moduleClass; @property (nonatomic, readonly) SEL selector; -@property (nonatomic, readonly) RCTJavaScriptFunctionKind functionKind; +@property (nonatomic, readonly) RCTFunctionType functionType; - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName JSMethodName:(NSString *)JSMethodName diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index c876f4c0a..df751097a 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -46,9 +46,10 @@ typedef void (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @implementation RCTModuleMethod { Class _moduleClass; - SEL _selector; NSInvocation *_invocation; NSArray *_argumentBlocks; + NSString *_objCMethodName; + SEL _selector; } static void RCTLogArgumentError(RCTModuleMethod *method, NSUInteger index, @@ -117,13 +118,8 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) { if ((self = [super init])) { - NSArray *arguments; - RCTParseObjCMethodName(&objCMethodName, &arguments); - _moduleClass = moduleClass; - _selector = NSSelectorFromString(objCMethodName); - RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName); - + _objCMethodName = [objCMethodName copy]; _JSMethodName = JSMethodName.length > 0 ? JSMethodName : ({ NSString *methodName = objCMethodName; NSRange colonRange = [methodName rangeOfString:@":"]; @@ -135,26 +131,139 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) methodName; }); - // Create method invocation - NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector]; - RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName); - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - [invocation setSelector:_selector]; - [invocation retainArguments]; - _invocation = invocation; + if ([_objCMethodName rangeOfString:@"RCTPromise"].length) { + _functionType = RCTFunctionTypePromise; + } else { + _functionType = RCTFunctionTypeNormal; + } + } - // Process arguments - NSUInteger numberOfArguments = methodSignature.numberOfArguments; - NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; + return self; +} + +- (void)processMethodSignature +{ + NSArray *arguments; + NSString *objCMethodName = _objCMethodName; + RCTParseObjCMethodName(&objCMethodName, &arguments); + + _selector = NSSelectorFromString(objCMethodName); + RCTAssert(_selector, @"%@ is not a valid selector", objCMethodName); + + // Create method invocation + NSMethodSignature *methodSignature = [_moduleClass instanceMethodSignatureForSelector:_selector]; + RCTAssert(methodSignature, @"%@ is not a recognized Objective-C method.", objCMethodName); + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + [invocation setSelector:_selector]; + [invocation retainArguments]; + _invocation = invocation; + + // Process arguments + NSUInteger numberOfArguments = methodSignature.numberOfArguments; + NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2]; #define RCT_ARG_BLOCK(_logic) \ - [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \ - _logic \ - [invocation setArgument:&value atIndex:(index) + 2]; \ - }]; +[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \ + _logic \ + [invocation setArgument:&value atIndex:(index) + 2]; \ +}]; - __weak RCTModuleMethod *weakSelf = self; - void (^addBlockArgument)(void) = ^{ + __weak RCTModuleMethod *weakSelf = self; + void (^addBlockArgument)(void) = ^{ + RCT_ARG_BLOCK( + + if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { + RCTLogArgumentError(weakSelf, index, json, "should be a function"); + return; + } + + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing id value = (json ? ^(NSArray *args) { + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, args]]; + } : ^(__unused NSArray *unused) {}); + ) + }; + + for (NSUInteger i = 2; i < numberOfArguments; i++) { + const char *objcType = [methodSignature getArgumentTypeAtIndex:i]; + BOOL isNullableType = NO; + RCTMethodArgument *argument = arguments[i - 2]; + NSString *typeName = argument.type; + SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]); + if ([RCTConvert respondsToSelector:selector]) { + switch (objcType[0]) { + +#define RCT_CASE(_value, _type) \ + case _value: { \ + _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \ + RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ + break; \ + } + + RCT_CASE(_C_CHR, char) + RCT_CASE(_C_UCHR, unsigned char) + RCT_CASE(_C_SHT, short) + RCT_CASE(_C_USHT, unsigned short) + RCT_CASE(_C_INT, int) + RCT_CASE(_C_UINT, unsigned int) + RCT_CASE(_C_LNG, long) + RCT_CASE(_C_ULNG, unsigned long) + RCT_CASE(_C_LNG_LNG, long long) + RCT_CASE(_C_ULNG_LNG, unsigned long long) + RCT_CASE(_C_FLT, float) + RCT_CASE(_C_DBL, double) + RCT_CASE(_C_BOOL, BOOL) + +#define RCT_NULLABLE_CASE(_value, _type) \ + case _value: { \ + isNullableType = YES; \ + _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \ + RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ + break; \ + } + + RCT_NULLABLE_CASE(_C_SEL, SEL) + RCT_NULLABLE_CASE(_C_CHARPTR, const char *) + RCT_NULLABLE_CASE(_C_PTR, void *) + RCT_NULLABLE_CASE(_C_ID, id) + + case _C_STRUCT_B: { + + NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector]; + NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature]; + [typeInvocation setSelector:selector]; + [typeInvocation setTarget:[RCTConvert class]]; + + [argumentBlocks addObject: + ^(__unused RCTBridge *bridge, NSUInteger index, id json) { + + void *returnValue = malloc(typeSignature.methodReturnLength); + [typeInvocation setArgument:&json atIndex:2]; + [typeInvocation invoke]; + [typeInvocation getReturnValue:returnValue]; + + [invocation setArgument:returnValue atIndex:index + 2]; + + free(returnValue); + }]; + break; + } + + default: { + static const char *blockType = @encode(typeof(^{})); + if (!strcmp(objcType, blockType)) { + addBlockArgument(); + } else { + RCTLogError(@"Unsupported argument type '%@' in method %@.", + typeName, [self methodName]); + } + } + } + } else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) { + addBlockArgument(); + } else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) { RCT_ARG_BLOCK( if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { @@ -163,202 +272,115 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) } // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing id value = (json ? ^(NSArray *args) { + __autoreleasing id value = (json ? ^(NSError *error) { [bridge _invokeAndProcessModule:@"BatchedBridge" method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, args]]; - } : ^(__unused NSArray *unused) {}); + arguments:@[json, @[RCTJSErrorFromNSError(error)]]]; + } : ^(__unused NSError *error) {}); ) - }; - - for (NSUInteger i = 2; i < numberOfArguments; i++) { - const char *objcType = [methodSignature getArgumentTypeAtIndex:i]; - BOOL isNullableType = NO; - RCTMethodArgument *argument = arguments[i - 2]; - NSString *typeName = argument.type; - SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]); - if ([RCTConvert respondsToSelector:selector]) { - switch (objcType[0]) { - -#define RCT_CASE(_value, _type) \ - case _value: { \ - _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \ - RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ - break; \ - } - - RCT_CASE(_C_CHR, char) - RCT_CASE(_C_UCHR, unsigned char) - RCT_CASE(_C_SHT, short) - RCT_CASE(_C_USHT, unsigned short) - RCT_CASE(_C_INT, int) - RCT_CASE(_C_UINT, unsigned int) - RCT_CASE(_C_LNG, long) - RCT_CASE(_C_ULNG, unsigned long) - RCT_CASE(_C_LNG_LNG, long long) - RCT_CASE(_C_ULNG_LNG, unsigned long long) - RCT_CASE(_C_FLT, float) - RCT_CASE(_C_DBL, double) - RCT_CASE(_C_BOOL, BOOL) - -#define RCT_NULLABLE_CASE(_value, _type) \ - case _value: { \ - isNullableType = YES; \ - _type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \ - RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \ - break; \ - } - - RCT_NULLABLE_CASE(_C_SEL, SEL) - RCT_NULLABLE_CASE(_C_CHARPTR, const char *) - RCT_NULLABLE_CASE(_C_PTR, void *) - RCT_NULLABLE_CASE(_C_ID, id) - - case _C_STRUCT_B: { - - NSMethodSignature *typeSignature = [RCTConvert methodSignatureForSelector:selector]; - NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature]; - [typeInvocation setSelector:selector]; - [typeInvocation setTarget:[RCTConvert class]]; - - [argumentBlocks addObject: - ^(__unused RCTBridge *bridge, NSUInteger index, id json) { - - void *returnValue = malloc(typeSignature.methodReturnLength); - [typeInvocation setArgument:&json atIndex:2]; - [typeInvocation invoke]; - [typeInvocation getReturnValue:returnValue]; - - [invocation setArgument:returnValue atIndex:index + 2]; - - free(returnValue); - }]; - break; - } - - default: { - static const char *blockType = @encode(typeof(^{})); - if (!strcmp(objcType, blockType)) { - addBlockArgument(); - } else { - RCTLogError(@"Unsupported argument type '%@' in method %@.", - typeName, [self methodName]); - } - } - } - } else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) { - addBlockArgument(); - } else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) { - RCT_ARG_BLOCK( - - if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { - RCTLogArgumentError(weakSelf, index, json, "should be a function"); - return; - } - - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing id value = (json ? ^(NSError *error) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, @[RCTJSErrorFromNSError(error)]]]; - } : ^(__unused NSError *error) {}); - ) - } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) { - RCTAssert(i == numberOfArguments - 2, - @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", - _moduleClass, objCMethodName); - RCT_ARG_BLOCK( - if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { - RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function"); - return; - } - - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing RCTPromiseResolveBlock value = (^(id result) { - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, result ? @[result] : @[]]]; - }); - ) - _functionKind = RCTJavaScriptFunctionKindAsync; - } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) { - RCTAssert(i == numberOfArguments - 1, - @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", - _moduleClass, objCMethodName); - RCT_ARG_BLOCK( - if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { - RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function"); - return; - } - - // Marked as autoreleasing, because NSInvocation doesn't retain arguments - __autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) { - NSDictionary *errorJSON = RCTJSErrorFromNSError(error); - [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" - arguments:@[json, @[errorJSON]]]; - }); - ) - _functionKind = RCTJavaScriptFunctionKindAsync; - } else { - - // Unknown argument type - RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert" - " to support this type.", typeName, [self methodName]); - } - - if (RCT_DEBUG) { - - RCTNullability nullability = argument.nullability; - if (!isNullableType) { - if (nullability == RCTNullable) { - RCTLogArgumentError(weakSelf, i - 2, typeName, "is marked as " - "nullable, but is not a nullable type."); - } - nullability = RCTNonnullable; + } else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) { + RCTAssert(i == numberOfArguments - 2, + @"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]", + _moduleClass, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function"); + return; } - /** - * Special case - Numbers are not nullable in Android, so we - * don't support this for now. In future we may allow it. - */ - if ([typeName isEqualToString:@"NSNumber"]) { - BOOL unspecified = (nullability == RCTNullabilityUnspecified); - if (!argument.unused && (nullability == RCTNullable || unspecified)) { - RCTLogArgumentError(weakSelf, i - 2, typeName, - [unspecified ? @"has unspecified nullability" : @"is marked as nullable" - stringByAppendingString: @" but React requires that all NSNumber " - "arguments are explicitly marked as `nonnull` to ensure " - "compatibility with Android."].UTF8String); - } - nullability = RCTNonnullable; + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseResolveBlock value = (^(id result) { + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, result ? @[result] : @[]]]; + }); + ) + } else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) { + RCTAssert(i == numberOfArguments - 1, + @"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]", + _moduleClass, objCMethodName); + RCT_ARG_BLOCK( + if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { + RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function"); + return; } - if (nullability == RCTNonnullable) { - RCTArgumentBlock oldBlock = argumentBlocks[i - 2]; - argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) { - if (json == nil || json == (id)kCFNull) { - RCTLogArgumentError(weakSelf, index, typeName, "must not be null"); - id null = nil; - [invocation setArgument:&null atIndex:index + 2]; - } else { - oldBlock(bridge, index, json); - } - }; - } - } + // Marked as autoreleasing, because NSInvocation doesn't retain arguments + __autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) { + NSDictionary *errorJSON = RCTJSErrorFromNSError(error); + [bridge _invokeAndProcessModule:@"BatchedBridge" + method:@"invokeCallbackAndReturnFlushedQueue" + arguments:@[json, @[errorJSON]]]; + }); + ) + } else { + + // Unknown argument type + RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert" + " to support this type.", typeName, [self methodName]); } - _argumentBlocks = [argumentBlocks copy]; + if (RCT_DEBUG) { + + RCTNullability nullability = argument.nullability; + if (!isNullableType) { + if (nullability == RCTNullable) { + RCTLogArgumentError(weakSelf, i - 2, typeName, "is marked as " + "nullable, but is not a nullable type."); + } + nullability = RCTNonnullable; + } + + /** + * Special case - Numbers are not nullable in Android, so we + * don't support this for now. In future we may allow it. + */ + if ([typeName isEqualToString:@"NSNumber"]) { + BOOL unspecified = (nullability == RCTNullabilityUnspecified); + if (!argument.unused && (nullability == RCTNullable || unspecified)) { + RCTLogArgumentError(weakSelf, i - 2, typeName, + [unspecified ? @"has unspecified nullability" : @"is marked as nullable" + stringByAppendingString: @" but React requires that all NSNumber " + "arguments are explicitly marked as `nonnull` to ensure " + "compatibility with Android."].UTF8String); + } + nullability = RCTNonnullable; + } + + if (nullability == RCTNonnullable) { + RCTArgumentBlock oldBlock = argumentBlocks[i - 2]; + argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) { + if (json == nil || json == (id)kCFNull) { + RCTLogArgumentError(weakSelf, index, typeName, "must not be null"); + id null = nil; + [invocation setArgument:&null atIndex:index + 2]; + } else { + oldBlock(bridge, index, json); + } + }; + } + } } - return self; + _argumentBlocks = [argumentBlocks copy]; +} + +- (SEL)selector +{ + if (_selector == NULL) { + [self processMethodSignature]; + } + return _selector; } - (void)invokeWithBridge:(RCTBridge *)bridge module:(id)module arguments:(NSArray *)arguments { + if (_argumentBlocks == nil) { + [self processMethodSignature]; + } + if (RCT_DEBUG) { // Sanity check @@ -371,7 +393,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) NSInteger expectedCount = _argumentBlocks.count; // Subtract the implicit Promise resolver and rejecter functions for implementations of async functions - if (_functionKind == RCTJavaScriptFunctionKindAsync) { + if (_functionType == RCTFunctionTypePromise) { actualCount -= 2; expectedCount -= 2; } @@ -398,6 +420,9 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) - (NSString *)methodName { + if (_selector == NULL) { + [self processMethodSignature]; + } return [NSString stringWithFormat:@"-[%@ %@]", _moduleClass, NSStringFromSelector(_selector)]; } From eb402d8c7d56f3d4321ba75767b28056a6211b59 Mon Sep 17 00:00:00 2001 From: Chace Liang Date: Tue, 11 Aug 2015 10:41:15 -0700 Subject: [PATCH 003/139] [e2e] Fix TouchableHighlight in e2e test --- Libraries/Components/Touchable/TouchableHighlight.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index f15ac8929..180cb61b1 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -214,12 +214,12 @@ var TouchableHighlight = React.createClass({ onResponderGrant={this.touchableHandleResponderGrant} onResponderMove={this.touchableHandleResponderMove} onResponderRelease={this.touchableHandleResponderRelease} - onResponderTerminate={this.touchableHandleResponderTerminate}> + onResponderTerminate={this.touchableHandleResponderTerminate} + testID={this.props.testID}> {cloneWithProps( onlyChild(this.props.children), { ref: CHILD_REF, - testID: this.props.testID, } )} From 70624dc34730407ef7be53ecfba551cc514a69a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 11 Aug 2015 11:04:34 -0700 Subject: [PATCH 004/139] [react-packager] Fix Cache-test --- packager/react-packager/src/Cache/__tests__/Cache-test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packager/react-packager/src/Cache/__tests__/Cache-test.js b/packager/react-packager/src/Cache/__tests__/Cache-test.js index 8172a2430..f4aef9147 100644 --- a/packager/react-packager/src/Cache/__tests__/Cache-test.js +++ b/packager/react-packager/src/Cache/__tests__/Cache-test.js @@ -52,6 +52,14 @@ describe('JSTransformer Cache', () => { }); pit('supports storing multiple fields', () => { + require('fs').stat.mockImpl((file, callback) => { + callback(null, { + mtime: { + getTime: () => {} + } + }); + }); + var cache = new Cache({ projectRoots: ['/rootDir'], transformModulePath: 'x.js', From 1e1efce69b4b7e188a9de8d3e905ff93ad160b0c Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 11 Aug 2015 17:23:30 -0700 Subject: [PATCH 005/139] Add .watchmanconfig Summary: This file tells watchman to watch this folder if "root_restrict_files" is turned on. Fixes #2042 Closes https://github.com/facebook/react-native/pull/2263 Github Author: Alex Kotliarskyi --- Examples/SampleApp/_watchmanconfig | 1 + 1 file changed, 1 insertion(+) create mode 100644 Examples/SampleApp/_watchmanconfig diff --git a/Examples/SampleApp/_watchmanconfig b/Examples/SampleApp/_watchmanconfig new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Examples/SampleApp/_watchmanconfig @@ -0,0 +1 @@ +{} From 39230000deade28e10e2bbeecce5330b4f7eb5b6 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 11 Aug 2015 21:12:55 -0100 Subject: [PATCH 006/139] Fix ReactART --- 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 3a1c2f25a..40c0f7eb8 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -765,11 +765,11 @@ RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag } RCT_EXPORT_METHOD(updateView:(nonnull NSNumber *)reactTag - viewName:(__unused NSString *)viewName // not reliable, do not use + viewName:(NSString *)viewName // not always reliable, use shadowView.viewName if available props:(NSDictionary *)props) { RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; - RCTComponentData *componentData = _componentDataByName[shadowView.viewName]; + RCTComponentData *componentData = _componentDataByName[shadowView.viewName ?: viewName]; [componentData setProps:props forShadowView:shadowView]; [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { From a86e6b76fbbef6585052f1ca6eea58d9ab8fb3ad Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 11 Aug 2015 19:18:08 -0100 Subject: [PATCH 007/139] Fixed fuzzer app and ensured that React does not crash when fuzzed --- Libraries/Utilities/MessageQueue.js | 7 +++++-- React/Base/RCTModuleMethod.m | 26 ++++++++++++++------------ React/Executors/RCTContextExecutor.m | 2 +- React/Modules/RCTUIManager.m | 12 +++++++++--- React/Views/RCTWebViewManager.m | 11 +++++++---- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 98b0a9fe0..ffca2bca5 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -218,9 +218,10 @@ class MessageQueue { return null; } + let fn = null; let self = this; if (type === MethodTypes.remoteAsync) { - return function(...args) { + fn = function(...args) { return new Promise((resolve, reject) => { self.__nativeCall(module, method, args, resolve, (errorData) => { var error = createErrorFromErrorData(errorData); @@ -229,7 +230,7 @@ class MessageQueue { }); }; } else { - return function(...args) { + fn = 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'; @@ -245,6 +246,8 @@ class MessageQueue { return self.__nativeCall(module, method, args, onFail, onSucc); }; } + fn.type = type; + return fn; } } diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index df751097a..a64bc46ab 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -17,7 +17,7 @@ #import "RCTLog.h" #import "RCTUtils.h" -typedef void (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); +typedef BOOL (^RCTArgumentBlock)(RCTBridge *, NSUInteger, id); @implementation RCTMethodArgument @@ -166,6 +166,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { \ _logic \ [invocation setArgument:&value atIndex:(index) + 2]; \ + return YES; \ }]; __weak RCTModuleMethod *weakSelf = self; @@ -174,7 +175,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -268,13 +269,13 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments __autoreleasing id value = (json ? ^(NSError *error) { [bridge _invokeAndProcessModule:@"BatchedBridge" - method:@"invokeCallbackAndReturnFlushedQueue" + method:@"invokeCallbackAndReturnFlushedQueue" arguments:@[json, @[RCTJSErrorFromNSError(error)]]]; } : ^(__unused NSError *error) {}); ) @@ -285,7 +286,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise resolver function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -302,7 +303,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) RCT_ARG_BLOCK( if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) { RCTLogArgumentError(weakSelf, index, json, "should be a promise rejecter function"); - return; + return NO; } // Marked as autoreleasing, because NSInvocation doesn't retain arguments @@ -350,12 +351,11 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) if (nullability == RCTNonnullable) { RCTArgumentBlock oldBlock = argumentBlocks[i - 2]; argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSUInteger index, id json) { - if (json == nil || json == (id)kCFNull) { + if (json == nil) { RCTLogArgumentError(weakSelf, index, typeName, "must not be null"); - id null = nil; - [invocation setArgument:&null atIndex:index + 2]; + return NO; } else { - oldBlock(bridge, index, json); + return oldBlock(bridge, index, json); } }; } @@ -408,9 +408,11 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) // Set arguments NSUInteger index = 0; for (id json in arguments) { - id arg = RCTNilIfNull(json); RCTArgumentBlock block = _argumentBlocks[index]; - block(bridge, index, arg); + if (!block(bridge, index, RCTNilIfNull(json))) { + // Invalid argument, abort + return; + } index++; } diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index b31c7e98c..8c8174773 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -558,7 +558,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) }), @"js_call,json_call", (@{@"objectName": objectName}))]; } -RCT_EXPORT_METHOD(setContextName:(NSString *)name) +RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name) { if (JSGlobalContextSetName != NULL) { JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)name); diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 40c0f7eb8..0a8ac410c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -820,6 +820,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po - (void)batchDidComplete { RCTProfileBeginEvent(); + // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) for (RCTComponentData *componentData in _componentDataByName.allValues) { @@ -871,8 +872,13 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po dispatch_async(dispatch_get_main_queue(), ^{ RCTProfileEndFlowEvent(); RCTProfileBeginEvent(); - for (dispatch_block_t block in previousPendingUIBlocks) { - block(); + @try { + for (dispatch_block_t block in previousPendingUIBlocks) { + block(); + } + } + @catch (NSException *exception) { + RCTLogError(@"Exception thrown while executing UI block: %@", exception); } RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{ @"count": @(previousPendingUIBlocks.count), @@ -1043,7 +1049,7 @@ RCT_EXPORT_METHOD(setMainScrollViewTag:(nonnull NSNumber *)reactTag) uiManager.mainScrollView = (id)view; uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate; } else { - RCTAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); + RCTLogError(@"Tag #%@ does not conform to RCTScrollableProtocol", reactTag); } } else { uiManager.mainScrollView = nil; diff --git a/React/Views/RCTWebViewManager.m b/React/Views/RCTWebViewManager.m index 6cd6e6502..1fd2cfe3f 100644 --- a/React/Views/RCTWebViewManager.m +++ b/React/Views/RCTWebViewManager.m @@ -62,8 +62,9 @@ RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view goBack]; } - [view goBack]; }]; } @@ -73,8 +74,9 @@ RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) id view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view goForward]; } - [view goForward]; }]; } @@ -84,9 +86,10 @@ RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag) [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTWebView *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebView class]]) { - RCTLogMustFix(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view); + } else { + [view reload]; } - [view reload]; }]; } From abdd0e09b39eb36dc7a3fd49b05b6346f62cf179 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 12 Aug 2015 10:53:10 -0700 Subject: [PATCH 008/139] [npm] Upgrade to stacktrace-parser 0.1.2, which supports io.js Summary: stacktrace-parser used to list only Node 0.10 under its list of supported engines. This new version includes Node 1.x and 2.x (i.e. io.js) as well, which addresses the warning during `npm install`. There's no problem with using the older version of stacktrace-parser; this just clears the warning. Closes https://github.com/facebook/react-native/pull/1738 Github Author: James Ide --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c2b25419..0d7a1be91 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "sane": "^1.1.2", "semver": "^4.3.6", "source-map": "0.1.31", - "stacktrace-parser": "frantic/stacktrace-parser#493c5e5638", + "stacktrace-parser": "0.1.2", "uglify-js": "2.4.16", "underscore": "1.7.0", "wordwrap": "^1.0.0", From c483db1b88a620be4944ed3f2931327713c23eb9 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 12 Aug 2015 11:48:37 -0700 Subject: [PATCH 009/139] [ReactNative] Open source ProgressBarAndroid, remove example from the RN launcher --- .../ProgressBarAndroid/ProgressBarAndroid.ios.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.ios.js diff --git a/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.ios.js b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.ios.js new file mode 100644 index 000000000..de4dbf9d8 --- /dev/null +++ b/Libraries/Components/ProgressBarAndroid/ProgressBarAndroid.ios.js @@ -0,0 +1,8 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule ProgressBarAndroid + */ +'use strict'; + +module.exports = require('UnimplementedView'); From 309326db2eea13be3cbce438955b9034f6781076 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 12 Aug 2015 11:47:02 -0700 Subject: [PATCH 010/139] [react-packager] Rename 'Package' to 'Bundle' Summary: The word Package is overloaded, it may mean npm package, or may mean a collection of bundles. Neither is what we mean. We mean `bundle`. This renames it and modernize some of the Bundler code. --- packager/react-packager/index.js | 13 +- packager/react-packager/src/Bundler/Bundle.js | 307 ++++++++++++++++++ .../__tests__/Bundle-test.js} | 64 ++-- .../__tests__/Bundler-test.js} | 18 +- .../src/{Packager => Bundler}/base64-vlq.js | 0 packager/react-packager/src/Bundler/index.js | 291 +++++++++++++++++ .../DependencyGraph/index.js | 1 - .../react-packager/src/Packager/Package.js | 300 ----------------- packager/react-packager/src/Packager/index.js | 289 ----------------- .../src/Server/__tests__/Server-test.js | 40 +-- packager/react-packager/src/Server/index.js | 68 ++-- 11 files changed, 702 insertions(+), 689 deletions(-) create mode 100644 packager/react-packager/src/Bundler/Bundle.js rename packager/react-packager/src/{Packager/__tests__/Package-test.js => Bundler/__tests__/Bundle-test.js} (82%) rename packager/react-packager/src/{Packager/__tests__/Packager-test.js => Bundler/__tests__/Bundler-test.js} (94%) rename packager/react-packager/src/{Packager => Bundler}/base64-vlq.js (100%) create mode 100644 packager/react-packager/src/Bundler/index.js delete mode 100644 packager/react-packager/src/Packager/Package.js delete mode 100644 packager/react-packager/src/Packager/index.js diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index c47d762a1..83f312285 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -22,18 +22,23 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; -exports.buildPackage = function(options, packageOptions) { + +// Renamed "package" to "bundle". But maintain backwards +// compat. +exports.buildPackage = +exports.buildBundle = function(options, bundleOptions) { var server = createServer(options); - return server.buildPackage(packageOptions) + return server.buildBundle(bundleOptions) .then(function(p) { server.end(); return p; }); }; -exports.buildPackageFromUrl = function(options, reqUrl) { +exports.buildPackageFromUrl = +exports.buildBundleFromUrl = function(options, reqUrl) { var server = createServer(options); - return server.buildPackageFromUrl(reqUrl) + return server.buildBundleFromUrl(reqUrl) .then(function(p) { server.end(); return p; diff --git a/packager/react-packager/src/Bundler/Bundle.js b/packager/react-packager/src/Bundler/Bundle.js new file mode 100644 index 000000000..418f7a9ef --- /dev/null +++ b/packager/react-packager/src/Bundler/Bundle.js @@ -0,0 +1,307 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const _ = require('underscore'); +const base64VLQ = require('./base64-vlq'); +const UglifyJS = require('uglify-js'); +const ModuleTransport = require('../lib/ModuleTransport'); + +const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; + +class Bundle { + constructor(sourceMapUrl) { + this._finalized = false; + this._modules = []; + this._assets = []; + this._sourceMapUrl = sourceMapUrl; + this._shouldCombineSourceMaps = false; + } + + setMainModuleId(moduleId) { + this._mainModuleId = moduleId; + } + + addModule(module) { + if (!(module instanceof ModuleTransport)) { + throw new Error('Expeceted a ModuleTransport object'); + } + + // If we get a map from the transformer we'll switch to a mode + // were we're combining the source maps as opposed to + if (!this._shouldCombineSourceMaps && module.map != null) { + this._shouldCombineSourceMaps = true; + } + + this._modules.push(module); + } + + getModules() { + return this._modules; + } + + addAsset(asset) { + this._assets.push(asset); + } + + finalize(options) { + options = options || {}; + if (options.runMainModule) { + const runCode = ';require("' + this._mainModuleId + '");'; + this.addModule(new ModuleTransport({ + code: runCode, + virtual: true, + sourceCode: runCode, + sourcePath: 'RunMainModule.js' + })); + } + + Object.freeze(this._modules); + Object.seal(this._modules); + Object.freeze(this._assets); + Object.seal(this._assets); + this._finalized = true; + } + + _assertFinalized() { + if (!this._finalized) { + throw new Error('Bundle needs to be finalized before getting any source'); + } + } + + _getSource() { + if (this._source == null) { + this._source = _.pluck(this._modules, 'code').join('\n'); + } + return this._source; + } + + _getInlineSourceMap() { + if (this._inlineSourceMap == null) { + const sourceMap = this.getSourceMap({excludeSource: true}); + /*eslint-env node*/ + const encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64'); + this._inlineSourceMap = 'data:application/json;base64,' + encoded; + } + return this._inlineSourceMap; + } + + getSource(options) { + this._assertFinalized(); + + options = options || {}; + + if (options.minify) { + return this.getMinifiedSourceAndMap().code; + } + + let source = this._getSource(); + + if (options.inlineSourceMap) { + source += SOURCEMAPPING_URL + this._getInlineSourceMap(); + } else if (this._sourceMapUrl) { + source += SOURCEMAPPING_URL + this._sourceMapUrl; + } + + return source; + } + + getMinifiedSourceAndMap() { + this._assertFinalized(); + + const source = this._getSource(); + try { + return UglifyJS.minify(source, { + fromString: true, + outSourceMap: 'bundle.js', + inSourceMap: this.getSourceMap(), + }); + } catch(e) { + // Sometimes, when somebody is using a new syntax feature that we + // don't yet have transform for, the untransformed line is sent to + // uglify, and it chokes on it. This code tries to print the line + // and the module for easier debugging + let errorMessage = 'Error while minifying JS\n'; + if (e.line) { + errorMessage += 'Transformed code line: "' + + source.split('\n')[e.line - 1] + '"\n'; + } + if (e.pos) { + let fromIndex = source.lastIndexOf('__d(\'', e.pos); + if (fromIndex > -1) { + fromIndex += '__d(\''.length; + const toIndex = source.indexOf('\'', fromIndex); + errorMessage += 'Module name (best guess): ' + + source.substring(fromIndex, toIndex) + '\n'; + } + } + errorMessage += e.toString(); + throw new Error(errorMessage); + } + } + + /** + * I found a neat trick in the sourcemap spec that makes it easy + * to concat sourcemaps. The `sections` field allows us to combine + * the sourcemap easily by adding an offset. Tested on chrome. + * Seems like it's not yet in Firefox but that should be fine for + * now. + */ + _getCombinedSourceMaps(options) { + const result = { + version: 3, + file: 'bundle.js', + sections: [], + }; + + let line = 0; + this._modules.forEach(function(module) { + let map = module.map; + if (module.virtual) { + map = generateSourceMapForVirtualModule(module); + } + + if (options.excludeSource) { + map = _.extend({}, map, {sourcesContent: []}); + } + + result.sections.push({ + offset: { line: line, column: 0 }, + map: map, + }); + line += module.code.split('\n').length; + }); + + return result; + } + + getSourceMap(options) { + this._assertFinalized(); + + options = options || {}; + + if (this._shouldCombineSourceMaps) { + return this._getCombinedSourceMaps(options); + } + + const mappings = this._getMappings(); + const map = { + file: 'bundle.js', + sources: _.pluck(this._modules, 'sourcePath'), + version: 3, + names: [], + mappings: mappings, + sourcesContent: options.excludeSource + ? [] : _.pluck(this._modules, 'sourceCode') + }; + return map; + } + + getAssets() { + return this._assets; + } + + _getMappings() { + const modules = this._modules; + + // The first line mapping in our package is basically the base64vlq code for + // zeros (A). + const firstLine = 'AAAA'; + + // Most other lines in our mappings are all zeros (for module, column etc) + // except for the lineno mappinp: curLineno - prevLineno = 1; Which is C. + const line = 'AACA'; + + const moduleLines = Object.create(null); + let mappings = ''; + for (let i = 0; i < modules.length; i++) { + const module = modules[i]; + const code = module.code; + let lastCharNewLine = false; + moduleLines[module.sourcePath] = 0; + for (let t = 0; t < code.length; t++) { + if (t === 0 && i === 0) { + mappings += firstLine; + } else if (t === 0) { + mappings += 'AC'; + + // This is the only place were we actually don't know the mapping ahead + // of time. When it's a new module (and not the first) the lineno + // mapping is 0 (current) - number of lines in prev module. + mappings += base64VLQ.encode( + 0 - moduleLines[modules[i - 1].sourcePath] + ); + mappings += 'A'; + } else if (lastCharNewLine) { + moduleLines[module.sourcePath]++; + mappings += line; + } + lastCharNewLine = code[t] === '\n'; + if (lastCharNewLine) { + mappings += ';'; + } + } + if (i !== modules.length - 1) { + mappings += ';'; + } + } + return mappings; + } + + getJSModulePaths() { + return this._modules.filter(function(module) { + // Filter out non-js files. Like images etc. + return !module.virtual; + }).map(function(module) { + return module.sourcePath; + }); + } + + getDebugInfo() { + return [ + '

Main Module:

' + this._mainModuleId + '
', + '', + '

Module paths and transformed code:

', + this._modules.map(function(m) { + return '

Path:

' + m.sourcePath + '

Source:

' + + '
'; + }).join('\n'), + ].join('\n'); + } +} + +function generateSourceMapForVirtualModule(module) { + // All lines map 1-to-1 + let mappings = 'AAAA;'; + + for (let i = 1; i < module.code.split('\n').length; i++) { + mappings += 'AACA;'; + } + + return { + version: 3, + sources: [ module.sourcePath ], + names: [], + mappings: mappings, + file: module.sourcePath, + sourcesContent: [ module.sourceCode ], + }; +} + +module.exports = Bundle; diff --git a/packager/react-packager/src/Packager/__tests__/Package-test.js b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js similarity index 82% rename from packager/react-packager/src/Packager/__tests__/Package-test.js rename to packager/react-packager/src/Bundler/__tests__/Bundle-test.js index d43c65c0f..74d189240 100644 --- a/packager/react-packager/src/Packager/__tests__/Package-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundle-test.js @@ -12,35 +12,35 @@ jest.autoMockOff(); var SourceMapGenerator = require('source-map').SourceMapGenerator; -describe('Package', function() { +describe('Bundle', function() { var ModuleTransport; - var Package; - var ppackage; + var Bundle; + var bundle; beforeEach(function() { - Package = require('../Package'); + Bundle = require('../Bundle'); ModuleTransport = require('../../lib/ModuleTransport'); - ppackage = new Package('test_url'); - ppackage.getSourceMap = jest.genMockFn().mockImpl(function() { + bundle = new Bundle('test_url'); + bundle.getSourceMap = jest.genMockFn().mockImpl(function() { return 'test-source-map'; }); }); - describe('source package', function() { - it('should create a package and get the source', function() { - ppackage.addModule(new ModuleTransport({ + describe('source bundle', function() { + it('should create a bundle and get the source', function() { + bundle.addModule(new ModuleTransport({ code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path', })); - ppackage.addModule(new ModuleTransport({ + bundle.addModule(new ModuleTransport({ code: 'transformed bar;', sourceCode: 'source bar', sourcePath: 'bar path', })); - ppackage.finalize({}); - expect(ppackage.getSource()).toBe([ + bundle.finalize({}); + expect(bundle.getSource()).toBe([ 'transformed foo;', 'transformed bar;', '\/\/@ sourceMappingURL=test_url' @@ -48,7 +48,7 @@ describe('Package', function() { }); it('should be ok to leave out the source map url', function() { - var p = new Package(); + var p = new Bundle(); p.addModule(new ModuleTransport({ code: 'transformed foo;', sourceCode: 'source foo', @@ -67,22 +67,22 @@ describe('Package', function() { ].join('\n')); }); - it('should create a package and add run module code', function() { - ppackage.addModule(new ModuleTransport({ + it('should create a bundle and add run module code', function() { + bundle.addModule(new ModuleTransport({ code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path' })); - ppackage.addModule(new ModuleTransport({ + bundle.addModule(new ModuleTransport({ code: 'transformed bar;', sourceCode: 'source bar', sourcePath: 'bar path' })); - ppackage.setMainModuleId('foo'); - ppackage.finalize({runMainModule: true}); - expect(ppackage.getSource()).toBe([ + bundle.setMainModuleId('foo'); + bundle.finalize({runMainModule: true}); + expect(bundle.getSource()).toBe([ 'transformed foo;', 'transformed bar;', ';require("foo");', @@ -100,19 +100,19 @@ describe('Package', function() { return minified; }; - ppackage.addModule(new ModuleTransport({ + bundle.addModule(new ModuleTransport({ code: 'transformed foo;', sourceCode: 'source foo', sourcePath: 'foo path' })); - ppackage.finalize(); - expect(ppackage.getMinifiedSourceAndMap()).toBe(minified); + bundle.finalize(); + expect(bundle.getMinifiedSourceAndMap()).toBe(minified); }); }); - describe('sourcemap package', function() { + describe('sourcemap bundle', function() { it('should create sourcemap', function() { - var p = new Package('test_url'); + var p = new Bundle('test_url'); p.addModule(new ModuleTransport({ code: [ 'transformed foo', @@ -143,11 +143,11 @@ describe('Package', function() { p.setMainModuleId('foo'); p.finalize({runMainModule: true}); var s = p.getSourceMap(); - expect(s).toEqual(genSourceMap(p._modules)); + expect(s).toEqual(genSourceMap(p.getModules())); }); it('should combine sourcemaps', function() { - var p = new Package('test_url'); + var p = new Bundle('test_url'); p.addModule(new ModuleTransport({ code: 'transformed foo;\n', @@ -215,7 +215,7 @@ describe('Package', function() { describe('getAssets()', function() { it('should save and return asset objects', function() { - var p = new Package('test_url'); + var p = new Bundle('test_url'); var asset1 = {}; var asset2 = {}; p.addAsset(asset1); @@ -227,7 +227,7 @@ describe('Package', function() { describe('getJSModulePaths()', function() { it('should return module paths', function() { - var p = new Package('test_url'); + var p = new Bundle('test_url'); p.addModule(new ModuleTransport({ code: 'transformed foo;\n', sourceCode: 'source foo', @@ -248,7 +248,7 @@ describe('Package', function() { function genSourceMap(modules) { var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3}); - var packageLineNo = 0; + var bundleLineNo = 0; for (var i = 0; i < modules.length; i++) { var module = modules[i]; var transformedCode = module.code; @@ -259,7 +259,7 @@ describe('Package', function() { for (var t = 0; t < transformedCode.length; t++) { if (t === 0 || lastCharNewLine) { sourceMapGen.addMapping({ - generated: {line: packageLineNo + 1, column: 0}, + generated: {line: bundleLineNo + 1, column: 0}, original: {line: transformedLineCount + 1, column: 0}, source: sourcePath }); @@ -267,10 +267,10 @@ describe('Package', function() { lastCharNewLine = transformedCode[t] === '\n'; if (lastCharNewLine) { transformedLineCount++; - packageLineNo++; + bundleLineNo++; } } - packageLineNo++; + bundleLineNo++; sourceMapGen.setSourceContent( sourcePath, sourceCode diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js similarity index 94% rename from packager/react-packager/src/Packager/__tests__/Packager-test.js rename to packager/react-packager/src/Bundler/__tests__/Bundler-test.js index 216e9009f..a66bc0592 100644 --- a/packager/react-packager/src/Packager/__tests__/Packager-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -9,7 +9,7 @@ 'use strict'; jest - .setMock('worker-farm', function() { return function() {};}) + .setMock('worker-farm', () => () => undefined) .dontMock('underscore') .dontMock('../../lib/ModuleTransport') .setMock('uglify-js') @@ -19,11 +19,11 @@ jest.mock('fs'); var Promise = require('promise'); -describe('Packager', function() { +describe('Bundler', function() { var getDependencies; var wrapModule; - var Packager; - var packager; + var Bundler; + var bundler; var assetServer; var modules; @@ -37,7 +37,7 @@ describe('Packager', function() { }; }); - Packager = require('../'); + Bundler = require('../'); require('fs').statSync.mockImpl(function() { return { @@ -53,7 +53,7 @@ describe('Packager', function() { getAssetData: jest.genMockFn(), }; - packager = new Packager({ + bundler = new Bundler({ projectRoots: ['/root'], assetServer: assetServer, }); @@ -118,8 +118,8 @@ describe('Packager', function() { }); }); - pit('create a package', function() { - return packager.package('/root/foo.js', true, 'source_map_url') + pit('create a bundle', function() { + return bundler.bundle('/root/foo.js', true, 'source_map_url') .then(function(p) { expect(p.addModule.mock.calls[0][0]).toEqual({ code: 'lol transformed /root/foo.js lol', @@ -204,7 +204,7 @@ describe('Packager', function() { }); pit('gets the list of dependencies', function() { - return packager.getDependencies('/root/foo.js', true) + return bundler.getDependencies('/root/foo.js', true) .then(({dependencies}) => { expect(dependencies).toEqual([ { diff --git a/packager/react-packager/src/Packager/base64-vlq.js b/packager/react-packager/src/Bundler/base64-vlq.js similarity index 100% rename from packager/react-packager/src/Packager/base64-vlq.js rename to packager/react-packager/src/Bundler/base64-vlq.js diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js new file mode 100644 index 000000000..e7ea12496 --- /dev/null +++ b/packager/react-packager/src/Bundler/index.js @@ -0,0 +1,291 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const Promise = require('promise'); +const Cache = require('../Cache'); +const Transformer = require('../JSTransformer'); +const DependencyResolver = require('../DependencyResolver'); +const Bundle = require('./Bundle'); +const Activity = require('../Activity'); +const ModuleTransport = require('../lib/ModuleTransport'); +const declareOpts = require('../lib/declareOpts'); +const imageSize = require('image-size'); + +const sizeOf = Promise.denodeify(imageSize); +const readFile = Promise.denodeify(fs.readFile); + +const validateOpts = declareOpts({ + projectRoots: { + type: 'array', + required: true, + }, + blacklistRE: { + type: 'object', // typeof regex is object + }, + moduleFormat: { + type: 'string', + default: 'haste', + }, + polyfillModuleNames: { + type: 'array', + default: [], + }, + cacheVersion: { + type: 'string', + default: '1.0', + }, + resetCache: { + type: 'boolean', + default: false, + }, + transformModulePath: { + type:'string', + required: false, + }, + nonPersistent: { + type: 'boolean', + default: false, + }, + assetRoots: { + type: 'array', + required: false, + }, + assetExts: { + type: 'array', + default: ['png'], + }, + fileWatcher: { + type: 'object', + required: true, + }, + assetServer: { + type: 'object', + required: true, + } +}); + +class Bundler { + + constructor(options) { + const opts = this._opts = validateOpts(options); + + opts.projectRoots.forEach(verifyRootExists); + + this._cache = opts.nonPersistent + ? new DummyCache() + : new Cache({ + resetCache: opts.resetCache, + cacheVersion: opts.cacheVersion, + projectRoots: opts.projectRoots, + transformModulePath: opts.transformModulePath, + }); + + this._resolver = new DependencyResolver({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + polyfillModuleNames: opts.polyfillModuleNames, + nonPersistent: opts.nonPersistent, + moduleFormat: opts.moduleFormat, + assetRoots: opts.assetRoots, + fileWatcher: opts.fileWatcher, + assetExts: opts.assetExts, + cache: this._cache, + }); + + this._transformer = new Transformer({ + projectRoots: opts.projectRoots, + blacklistRE: opts.blacklistRE, + cache: this._cache, + transformModulePath: opts.transformModulePath, + }); + + this._projectRoots = opts.projectRoots; + this._assetServer = opts.assetServer; + } + + kill() { + this._transformer.kill(); + return this._cache.end(); + } + + bundle(main, runModule, sourceMapUrl, isDev) { + const bundle = new Bundle(sourceMapUrl); + + const transformModule = this._transformModule.bind(this, bundle); + const findEventId = Activity.startEvent('find dependencies'); + let transformEventId; + + return this.getDependencies(main, isDev) + .then(function(result) { + Activity.endEvent(findEventId); + transformEventId = Activity.startEvent('transform'); + + bundle.setMainModuleId(result.mainModuleId); + return Promise.all( + result.dependencies.map(transformModule) + ); + }) + .then(function(transformedModules) { + Activity.endEvent(transformEventId); + + transformedModules.forEach(function(moduleTransport) { + bundle.addModule(moduleTransport); + }); + + bundle.finalize({ runMainModule: runModule }); + return bundle; + }); + } + + invalidateFile(filePath) { + this._transformer.invalidateFile(filePath); + } + + getDependencies(main, isDev) { + return this._resolver.getDependencies(main, { dev: isDev }); + } + + _transformModule(bundle, module) { + let transform; + + if (module.isAsset_DEPRECATED) { + transform = this.generateAssetModule_DEPRECATED(bundle, module); + } else if (module.isAsset) { + transform = this.generateAssetModule(bundle, module); + } else if (module.isJSON) { + transform = generateJSONModule(module); + } else { + transform = this._transformer.loadFileAndTransform( + path.resolve(module.path) + ); + } + + const resolver = this._resolver; + 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, + }) + ) + ); + } + + getGraphDebugInfo() { + return this._resolver.getDebugInfo(); + } + + generateAssetModule_DEPRECATED(bundle, module) { + return sizeOf(module.path).then(function(dimensions) { + const img = { + __packager_asset: true, + isStatic: true, + path: module.path, + uri: module.id.replace(/^[^!]+!/, ''), + width: dimensions.width / module.resolution, + height: dimensions.height / module.resolution, + deprecated: true, + }; + + bundle.addAsset(img); + + const code = 'module.exports = ' + JSON.stringify(img) + ';'; + + return new ModuleTransport({ + code: code, + sourceCode: code, + sourcePath: module.path, + virtual: true, + }); + }); + } + + generateAssetModule(bundle, module) { + const relPath = getPathRelativeToRoot(this._projectRoots, module.path); + + return Promise.all([ + sizeOf(module.path), + this._assetServer.getAssetData(relPath), + ]).then(function(res) { + const dimensions = res[0]; + const assetData = res[1]; + const img = { + __packager_asset: true, + fileSystemLocation: path.dirname(module.path), + httpServerLocation: path.join('/assets', path.dirname(relPath)), + width: dimensions.width / module.resolution, + height: dimensions.height / module.resolution, + scales: assetData.scales, + hash: assetData.hash, + name: assetData.name, + type: assetData.type, + }; + + bundle.addAsset(img); + + const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; + const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img)); + + return new ModuleTransport({ + code: code, + sourceCode: code, + sourcePath: module.path, + virtual: true, + }); + }); + } +} + +function generateJSONModule(module) { + return readFile(module.path).then(function(data) { + const code = 'module.exports = ' + data.toString('utf8') + ';'; + + return new ModuleTransport({ + code: code, + sourceCode: code, + sourcePath: module.path, + virtual: true, + }); + }); +} + +function getPathRelativeToRoot(roots, absPath) { + for (let i = 0; i < roots.length; i++) { + const relPath = path.relative(roots[i], absPath); + if (relPath[0] !== '.') { + return relPath; + } + } + + throw new Error( + 'Expected root module to be relative to one of the project roots' + ); +} + +function verifyRootExists(root) { + // Verify that the root exists. + assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); +} + +class DummyCache { + get(filepath, field, loaderCb) { + return loaderCb(); + } + + end(){} + invalidate(filepath){} +} +module.exports = Bundler; diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 7e7185829..d4cced0ca 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -13,7 +13,6 @@ const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED'); const Fastfs = require('../fastfs'); const ModuleCache = require('../ModuleCache'); const Promise = require('promise'); -const _ = require('underscore'); const crawl = require('../crawlers'); const debug = require('debug')('DependencyGraph'); const declareOpts = require('../../lib/declareOpts'); diff --git a/packager/react-packager/src/Packager/Package.js b/packager/react-packager/src/Packager/Package.js deleted file mode 100644 index 6b5389461..000000000 --- a/packager/react-packager/src/Packager/Package.js +++ /dev/null @@ -1,300 +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 _ = require('underscore'); -var base64VLQ = require('./base64-vlq'); -var UglifyJS = require('uglify-js'); -var ModuleTransport = require('../lib/ModuleTransport'); - -module.exports = Package; - -var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL='; - -function Package(sourceMapUrl) { - this._finalized = false; - this._modules = []; - this._assets = []; - this._sourceMapUrl = sourceMapUrl; - this._shouldCombineSourceMaps = false; -} - -Package.prototype.setMainModuleId = function(moduleId) { - this._mainModuleId = moduleId; -}; - -Package.prototype.addModule = function(module) { - if (!(module instanceof ModuleTransport)) { - throw new Error('Expeceted a ModuleTransport object'); - } - - // If we get a map from the transformer we'll switch to a mode - // were we're combining the source maps as opposed to - if (!this._shouldCombineSourceMaps && module.map != null) { - this._shouldCombineSourceMaps = true; - } - - this._modules.push(module); -}; - -Package.prototype.addAsset = function(asset) { - this._assets.push(asset); -}; - -Package.prototype.finalize = function(options) { - options = options || {}; - if (options.runMainModule) { - var runCode = ';require("' + this._mainModuleId + '");'; - this.addModule(new ModuleTransport({ - code: runCode, - virtual: true, - sourceCode: runCode, - sourcePath: 'RunMainModule.js' - })); - } - - Object.freeze(this._modules); - Object.seal(this._modules); - Object.freeze(this._assets); - Object.seal(this._assets); - this._finalized = true; -}; - -Package.prototype._assertFinalized = function() { - if (!this._finalized) { - throw new Error('Package need to be finalized before getting any source'); - } -}; - -Package.prototype._getSource = function() { - if (this._source == null) { - this._source = _.pluck(this._modules, 'code').join('\n'); - } - return this._source; -}; - -Package.prototype._getInlineSourceMap = function() { - if (this._inlineSourceMap == null) { - var sourceMap = this.getSourceMap({excludeSource: true}); - var encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64'); - this._inlineSourceMap = 'data:application/json;base64,' + encoded; - } - return this._inlineSourceMap; -}; - -Package.prototype.getSource = function(options) { - this._assertFinalized(); - - options = options || {}; - - if (options.minify) { - return this.getMinifiedSourceAndMap().code; - } - - var source = this._getSource(); - - if (options.inlineSourceMap) { - source += SOURCEMAPPING_URL + this._getInlineSourceMap(); - } else if (this._sourceMapUrl) { - source += SOURCEMAPPING_URL + this._sourceMapUrl; - } - - return source; -}; - -Package.prototype.getMinifiedSourceAndMap = function() { - this._assertFinalized(); - - var source = this._getSource(); - try { - return UglifyJS.minify(source, { - fromString: true, - outSourceMap: 'bundle.js', - inSourceMap: this.getSourceMap(), - }); - } catch(e) { - // Sometimes, when somebody is using a new syntax feature that we - // don't yet have transform for, the untransformed line is sent to - // uglify, and it chokes on it. This code tries to print the line - // and the module for easier debugging - var errorMessage = 'Error while minifying JS\n'; - if (e.line) { - errorMessage += 'Transformed code line: "' + - source.split('\n')[e.line - 1] + '"\n'; - } - if (e.pos) { - var fromIndex = source.lastIndexOf('__d(\'', e.pos); - if (fromIndex > -1) { - fromIndex += '__d(\''.length; - var toIndex = source.indexOf('\'', fromIndex); - errorMessage += 'Module name (best guess): ' + - source.substring(fromIndex, toIndex) + '\n'; - } - } - errorMessage += e.toString(); - throw new Error(errorMessage); - } -}; - -/** - * I found a neat trick in the sourcemap spec that makes it easy - * to concat sourcemaps. The `sections` field allows us to combine - * the sourcemap easily by adding an offset. Tested on chrome. - * Seems like it's not yet in Firefox but that should be fine for - * now. - */ -Package.prototype._getCombinedSourceMaps = function(options) { - var result = { - version: 3, - file: 'bundle.js', - sections: [], - }; - - var line = 0; - this._modules.forEach(function(module) { - var map = module.map; - if (module.virtual) { - map = generateSourceMapForVirtualModule(module); - } - - if (options.excludeSource) { - map = _.extend({}, map, {sourcesContent: []}); - } - - result.sections.push({ - offset: { line: line, column: 0 }, - map: map, - }); - line += module.code.split('\n').length; - }); - - return result; -}; - -Package.prototype.getSourceMap = function(options) { - this._assertFinalized(); - - options = options || {}; - - if (this._shouldCombineSourceMaps) { - return this._getCombinedSourceMaps(options); - } - - var mappings = this._getMappings(); - var map = { - file: 'bundle.js', - sources: _.pluck(this._modules, 'sourcePath'), - version: 3, - names: [], - mappings: mappings, - sourcesContent: options.excludeSource - ? [] : _.pluck(this._modules, 'sourceCode') - }; - return map; -}; - -Package.prototype.getAssets = function() { - return this._assets; -}; - -Package.prototype._getMappings = function() { - var modules = this._modules; - - // The first line mapping in our package is basically the base64vlq code for - // zeros (A). - var firstLine = 'AAAA'; - - // Most other lines in our mappings are all zeros (for module, column etc) - // except for the lineno mappinp: curLineno - prevLineno = 1; Which is C. - var line = 'AACA'; - - var moduleLines = Object.create(null); - var mappings = ''; - for (var i = 0; i < modules.length; i++) { - var module = modules[i]; - var code = module.code; - var lastCharNewLine = false; - moduleLines[module.sourcePath] = 0; - for (var t = 0; t < code.length; t++) { - if (t === 0 && i === 0) { - mappings += firstLine; - } else if (t === 0) { - mappings += 'AC'; - - // This is the only place were we actually don't know the mapping ahead - // of time. When it's a new module (and not the first) the lineno - // mapping is 0 (current) - number of lines in prev module. - mappings += base64VLQ.encode( - 0 - moduleLines[modules[i - 1].sourcePath] - ); - mappings += 'A'; - } else if (lastCharNewLine) { - moduleLines[module.sourcePath]++; - mappings += line; - } - lastCharNewLine = code[t] === '\n'; - if (lastCharNewLine) { - mappings += ';'; - } - } - if (i !== modules.length - 1) { - mappings += ';'; - } - } - return mappings; -}; - -Package.prototype.getJSModulePaths = function() { - return this._modules.filter(function(module) { - // Filter out non-js files. Like images etc. - return !module.virtual; - }).map(function(module) { - return module.sourcePath; - }); -}; - -Package.prototype.getDebugInfo = function() { - return [ - '

Main Module:

' + this._mainModuleId + '
', - '', - '

Module paths and transformed code:

', - this._modules.map(function(m) { - return '

Path:

' + m.sourcePath + '

Source:

' + - '
'; - }).join('\n'), - ].join('\n'); -}; - -function generateSourceMapForVirtualModule(module) { - // All lines map 1-to-1 - var mappings = 'AAAA;'; - - for (var i = 1; i < module.code.split('\n').length; i++) { - mappings += 'AACA;'; - } - - return { - version: 3, - sources: [ module.sourcePath ], - names: [], - mappings: mappings, - file: module.sourcePath, - sourcesContent: [ module.sourceCode ], - }; -} diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js deleted file mode 100644 index a718bd264..000000000 --- a/packager/react-packager/src/Packager/index.js +++ /dev/null @@ -1,289 +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 assert = require('assert'); -var fs = require('fs'); -var path = require('path'); -var Promise = require('promise'); -var Cache = require('../Cache'); -var Transformer = require('../JSTransformer'); -var DependencyResolver = require('../DependencyResolver'); -var Package = require('./Package'); -var Activity = require('../Activity'); -var ModuleTransport = require('../lib/ModuleTransport'); -var declareOpts = require('../lib/declareOpts'); -var imageSize = require('image-size'); - -var sizeOf = Promise.denodeify(imageSize); -var readFile = Promise.denodeify(fs.readFile); - -var validateOpts = declareOpts({ - projectRoots: { - type: 'array', - required: true, - }, - blacklistRE: { - type: 'object', // typeof regex is object - }, - moduleFormat: { - type: 'string', - default: 'haste', - }, - polyfillModuleNames: { - type: 'array', - default: [], - }, - cacheVersion: { - type: 'string', - default: '1.0', - }, - resetCache: { - type: 'boolean', - default: false, - }, - transformModulePath: { - type:'string', - required: false, - }, - nonPersistent: { - type: 'boolean', - default: false, - }, - assetRoots: { - type: 'array', - required: false, - }, - assetExts: { - type: 'array', - default: ['png'], - }, - fileWatcher: { - type: 'object', - required: true, - }, - assetServer: { - type: 'object', - required: true, - } -}); - -function Packager(options) { - var opts = this._opts = validateOpts(options); - - opts.projectRoots.forEach(verifyRootExists); - - this._cache = opts.nonPersistent - ? new DummyCache() - : new Cache({ - resetCache: opts.resetCache, - cacheVersion: opts.cacheVersion, - projectRoots: opts.projectRoots, - transformModulePath: opts.transformModulePath, - }); - - this._resolver = new DependencyResolver({ - projectRoots: opts.projectRoots, - blacklistRE: opts.blacklistRE, - polyfillModuleNames: opts.polyfillModuleNames, - nonPersistent: opts.nonPersistent, - moduleFormat: opts.moduleFormat, - assetRoots: opts.assetRoots, - fileWatcher: opts.fileWatcher, - assetExts: opts.assetExts, - cache: this._cache, - }); - - this._transformer = new Transformer({ - projectRoots: opts.projectRoots, - blacklistRE: opts.blacklistRE, - cache: this._cache, - transformModulePath: opts.transformModulePath, - }); - - this._projectRoots = opts.projectRoots; - this._assetServer = opts.assetServer; -} - -Packager.prototype.kill = function() { - this._transformer.kill(); - return this._cache.end(); -}; - -Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { - var ppackage = new Package(sourceMapUrl); - - var transformModule = this._transformModule.bind(this, ppackage); - var findEventId = Activity.startEvent('find dependencies'); - var transformEventId; - - return this.getDependencies(main, isDev) - .then(function(result) { - Activity.endEvent(findEventId); - transformEventId = Activity.startEvent('transform'); - - ppackage.setMainModuleId(result.mainModuleId); - return Promise.all( - result.dependencies.map(transformModule) - ); - }) - .then(function(transformedModules) { - Activity.endEvent(transformEventId); - - transformedModules.forEach(function(moduleTransport) { - ppackage.addModule(moduleTransport); - }); - - ppackage.finalize({ runMainModule: runModule }); - return ppackage; - }); -}; - -Packager.prototype.invalidateFile = function(filePath) { - this._transformer.invalidateFile(filePath); -}; - -Packager.prototype.getDependencies = function(main, isDev) { - return this._resolver.getDependencies(main, { dev: isDev }); -}; - -Packager.prototype._transformModule = function(ppackage, module) { - var transform; - - if (module.isAsset_DEPRECATED) { - transform = this.generateAssetModule_DEPRECATED(ppackage, module); - } else if (module.isAsset) { - transform = this.generateAssetModule(ppackage, module); - } else if (module.isJSON) { - transform = generateJSONModule(module); - } else { - transform = this._transformer.loadFileAndTransform( - path.resolve(module.path) - ); - } - - var resolver = this._resolver; - 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() { - return this._resolver.getDebugInfo(); -}; - -Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) { - return sizeOf(module.path).then(function(dimensions) { - var img = { - __packager_asset: true, - isStatic: true, - path: module.path, - uri: module.id.replace(/^[^!]+!/, ''), - width: dimensions.width / module.resolution, - height: dimensions.height / module.resolution, - deprecated: true, - }; - - ppackage.addAsset(img); - - var code = 'module.exports = ' + JSON.stringify(img) + ';'; - - return new ModuleTransport({ - code: code, - sourceCode: code, - sourcePath: module.path, - virtual: true, - }); - }); -}; - -Packager.prototype.generateAssetModule = function(ppackage, module) { - var relPath = getPathRelativeToRoot(this._projectRoots, module.path); - - return Promise.all([ - sizeOf(module.path), - this._assetServer.getAssetData(relPath), - ]).then(function(res) { - var dimensions = res[0]; - var assetData = res[1]; - var img = { - __packager_asset: true, - fileSystemLocation: path.dirname(module.path), - httpServerLocation: path.join('/assets', path.dirname(relPath)), - width: dimensions.width / module.resolution, - height: dimensions.height / module.resolution, - scales: assetData.scales, - hash: assetData.hash, - name: assetData.name, - type: assetData.type, - }; - - ppackage.addAsset(img); - - var ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);'; - var code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img)); - - return new ModuleTransport({ - code: code, - sourceCode: code, - sourcePath: module.path, - virtual: true, - }); - }); -}; - -function generateJSONModule(module) { - return readFile(module.path).then(function(data) { - var code = 'module.exports = ' + data.toString('utf8') + ';'; - - return new ModuleTransport({ - code: code, - sourceCode: code, - sourcePath: module.path, - virtual: true, - }); - }); -} - -function getPathRelativeToRoot(roots, absPath) { - for (var i = 0; i < roots.length; i++) { - var relPath = path.relative(roots[i], absPath); - if (relPath[0] !== '.') { - return relPath; - } - } - - throw new Error( - 'Expected root module to be relative to one of the project roots' - ); -} - -function verifyRootExists(root) { - // Verify that the root exists. - assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); -} - -class DummyCache { - get(filepath, field, loaderCb) { - return loaderCb(); - } - - end(){} - invalidate(filepath){} -} - -module.exports = Packager; diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index 7d399cb2a..5461b89ff 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -24,7 +24,7 @@ var Promise = require('promise'); describe('processRequest', function() { var server; - var Packager; + var Bundler; var FileWatcher; var options = { @@ -57,10 +57,10 @@ describe('processRequest', function() { var triggerFileChange; beforeEach(function() { - Packager = require('../../Packager'); + Bundler = require('../../Bundler'); FileWatcher = require('../../FileWatcher'); - Packager.prototype.package = jest.genMockFunction().mockImpl(function() { + Bundler.prototype.bundle = jest.genMockFunction().mockImpl(function() { return Promise.resolve({ getSource: function() { return 'this is the source'; @@ -81,7 +81,7 @@ describe('processRequest', function() { return this; }; - Packager.prototype.invalidateFile = invalidatorFunc; + Bundler.prototype.invalidateFile = invalidatorFunc; var Server = require('../'); server = new Server(options); @@ -121,7 +121,7 @@ describe('processRequest', function() { 'index.ios.includeRequire.bundle' ).then(function(response) { expect(response).toEqual('this is the source'); - expect(Packager.prototype.package).toBeCalledWith( + expect(Bundler.prototype.bundle).toBeCalledWith( 'index.ios.js', true, 'index.ios.includeRequire.map', @@ -142,7 +142,7 @@ describe('processRequest', function() { describe('file changes', function() { - pit('invalides files in package when file is updated', function() { + pit('invalides files in bundle when file is updated', function() { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' @@ -153,9 +153,9 @@ describe('processRequest', function() { }); }); - pit('rebuilds the packages that contain a file when that file is changed', function() { - var packageFunc = jest.genMockFunction(); - packageFunc + pit('rebuilds the bundles that contain a file when that file is changed', function() { + var bundleFunc = jest.genMockFunction(); + bundleFunc .mockReturnValueOnce( Promise.resolve({ getSource: function() { @@ -173,7 +173,7 @@ describe('processRequest', function() { }) ); - Packager.prototype.package = packageFunc; + Bundler.prototype.bundle = bundleFunc; var Server = require('../../Server'); server = new Server(options); @@ -184,13 +184,13 @@ describe('processRequest', function() { return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the first source'); - expect(packageFunc.mock.calls.length).toBe(1); + expect(bundleFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); jest.runAllTimers(); jest.runAllTimers(); }) .then(function() { - expect(packageFunc.mock.calls.length).toBe(2); + expect(bundleFunc.mock.calls.length).toBe(2); return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') .then(function(response) { expect(response).toEqual('this is the rebuilt source'); @@ -259,12 +259,12 @@ describe('processRequest', function() { }); }); - describe('buildPackage(options)', function() { - it('Calls the packager with the correct args', function() { - server.buildPackage({ + describe('buildBundle(options)', function() { + it('Calls the bundler with the correct args', function() { + server.buildBundle({ entryFile: 'foo file' }); - expect(Packager.prototype.package).toBeCalledWith( + expect(Bundler.prototype.bundle).toBeCalledWith( 'foo file', true, undefined, @@ -273,10 +273,10 @@ describe('processRequest', function() { }); }); - describe('buildPackageFromUrl(options)', function() { - it('Calls the packager with the correct args', function() { - server.buildPackageFromUrl('/path/to/foo.bundle?dev=false&runModule=false'); - expect(Packager.prototype.package).toBeCalledWith( + describe('buildBundleFromUrl(options)', function() { + it('Calls the bundler with the correct args', function() { + server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false'); + expect(Bundler.prototype.bundle).toBeCalledWith( 'path/to/foo.js', false, '/path/to/foo.map', diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index fdeffc65e..fcb6e1a9e 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -12,7 +12,7 @@ var url = require('url'); var path = require('path'); var declareOpts = require('../lib/declareOpts'); var FileWatcher = require('../FileWatcher'); -var Packager = require('../Packager'); +var Bundler = require('../Bundler'); var Activity = require('../Activity'); var AssetServer = require('../AssetServer'); var Promise = require('promise'); @@ -68,7 +68,7 @@ function Server(options) { var opts = validateOpts(options); this._projectRoots = opts.projectRoots; - this._packages = Object.create(null); + this._bundles = Object.create(null); this._changeWatchers = []; var assetGlobs = opts.assetExts.map(function(ext) { @@ -105,40 +105,40 @@ function Server(options) { assetExts: opts.assetExts, }); - var packagerOpts = Object.create(opts); - packagerOpts.fileWatcher = this._fileWatcher; - packagerOpts.assetServer = this._assetServer; - this._packager = new Packager(packagerOpts); + var bundlerOpts = Object.create(opts); + bundlerOpts.fileWatcher = this._fileWatcher; + bundlerOpts.assetServer = this._assetServer; + this._bundler = new Bundler(bundlerOpts); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); var self = this; this._debouncedFileChangeHandler = _.debounce(function(filePath) { - self._rebuildPackages(filePath); + self._rebuildBundles(filePath); self._informChangeWatchers(); }, 50); } Server.prototype._onFileChange = function(type, filepath, root) { var absPath = path.join(root, filepath); - this._packager.invalidateFile(absPath); + this._bundler.invalidateFile(absPath); // Make sure the file watcher event runs through the system before - // we rebuild the packages. + // we rebuild the bundles. this._debouncedFileChangeHandler(absPath); }; -Server.prototype._rebuildPackages = function() { - var buildPackage = this.buildPackage.bind(this); - var packages = this._packages; +Server.prototype._rebuildBundles = function() { + var buildBundle = this.buildBundle.bind(this); + var bundles = this._bundles; - Object.keys(packages).forEach(function(optionsJson) { + Object.keys(bundles).forEach(function(optionsJson) { var options = JSON.parse(optionsJson); // Wait for a previous build (if exists) to finish. - packages[optionsJson] = (packages[optionsJson] || Promise.resolve()).finally(function() { + bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() { // With finally promise callback we can't change the state of the promise // so we need to reassign the promise. - packages[optionsJson] = buildPackage(options).then(function(p) { + bundles[optionsJson] = buildBundle(options).then(function(p) { // Make a throwaway call to getSource to cache the source string. p.getSource({ inlineSourceMap: options.inlineSourceMap, @@ -147,7 +147,7 @@ Server.prototype._rebuildPackages = function() { return p; }); }); - return packages[optionsJson]; + return bundles[optionsJson]; }); }; @@ -168,11 +168,11 @@ Server.prototype._informChangeWatchers = function() { Server.prototype.end = function() { Promise.all([ this._fileWatcher.end(), - this._packager.kill(), + this._bundler.kill(), ]); }; -var packageOpts = declareOpts({ +var bundleOpts = declareOpts({ sourceMapUrl: { type: 'string', required: false, @@ -199,10 +199,10 @@ var packageOpts = declareOpts({ }, }); -Server.prototype.buildPackage = function(options) { - var opts = packageOpts(options); +Server.prototype.buildBundle = function(options) { + var opts = bundleOpts(options); - return this._packager.package( + return this._bundler.bundle( opts.entryFile, opts.runModule, opts.sourceMapUrl, @@ -210,13 +210,13 @@ Server.prototype.buildPackage = function(options) { ); }; -Server.prototype.buildPackageFromUrl = function(reqUrl) { +Server.prototype.buildBundleFromUrl = function(reqUrl) { var options = getOptionsFromUrl(reqUrl); - return this.buildPackage(options); + return this.buildBundle(options); }; Server.prototype.getDependencies = function(main) { - return this._packager.getDependencies(main); + return this._bundler.getDependencies(main); }; Server.prototype._processDebugRequest = function(reqUrl, res) { @@ -224,13 +224,13 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { var pathname = url.parse(reqUrl).pathname; var parts = pathname.split('/').filter(Boolean); if (parts.length === 1) { - ret += ''; + ret += ''; ret += ''; res.end(ret); - } else if (parts[1] === 'packages') { - ret += '

Cached Packages

'; - Promise.all(Object.keys(this._packages).map(function(optionsJson) { - return this._packages[optionsJson].then(function(p) { + } else if (parts[1] === 'bundles') { + ret += '

Cached Bundles

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

' + optionsJson + '

'; ret += p.getDebugInfo(); }); @@ -244,7 +244,7 @@ Server.prototype._processDebugRequest = function(reqUrl, res) { ); } else if (parts[1] === 'graph'){ ret += '

Dependency Graph

'; - ret += this._packager.getGraphDebugInfo(); + ret += this._bundler.getGraphDebugInfo(); res.end(ret); } else { res.writeHead('404'); @@ -352,9 +352,9 @@ Server.prototype.processRequest = function(req, res, next) { var startReqEventId = Activity.startEvent('request:' + req.url); var options = getOptionsFromUrl(req.url); var optionsJson = JSON.stringify(options); - var building = this._packages[optionsJson] || this.buildPackage(options); + var building = this._bundles[optionsJson] || this.buildBundle(options); - this._packages[optionsJson] = building; + this._bundles[optionsJson] = building; building.then( function(p) { if (requestType === 'bundle') { @@ -376,7 +376,7 @@ Server.prototype.processRequest = function(req, res, next) { ).done(); }; -Server.prototype._handleError = function(res, packageID, error) { +Server.prototype._handleError = function(res, bundleID, error) { res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', }); @@ -390,7 +390,7 @@ Server.prototype._handleError = function(res, packageID, error) { res.end(JSON.stringify(error)); if (error.type === 'NotFoundError') { - delete this._packages[packageID]; + delete this._bundles[bundleID]; } } else { console.error(error.stack || error); From cfcf604b3dc9e081b4142a286a8cca50063a0bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 12 Aug 2015 12:00:58 -0700 Subject: [PATCH 011/139] [react-packager] Introduce `require.ensure` Summary: This is the first step to add support for splitting the JS bundle into multiple ones. This diff adds support for keeping track of the async dependencies each module has. To do so we introduce the following syntax: require.ensure(['dep1', 'dep2, ..., 'depN'], callback); Where the callback function is asynchronously invoked once all the indicated modules are loaded. Internally, the packager keeps track of every set of async dependencies a module has. So for instance if a module looks like this: require.ensure(['dep1'], () => {...}); require.ensure(['dep2'], () => {...}); the `Module` object will keep track of each set of dependencies separately (because we might want to put them on separate bundles). --- .../src/DependencyResolver/AssetModule.js | 4 + .../AssetModule_DEPRECATED.js | 4 + .../src/DependencyResolver/Module.js | 76 +++++++++- .../__tests__/HasteDependencyResolver-test.js | 4 +- .../__tests__/Module-test.js | 133 ++++++++++++++++++ .../src/DependencyResolver/index.js | 3 +- .../DependencyResolver/polyfills/require.js | 69 ++------- .../src/DependencyResolver/replacePatterns.js | 1 + 8 files changed, 227 insertions(+), 67 deletions(-) create mode 100644 packager/react-packager/src/DependencyResolver/__tests__/Module-test.js diff --git a/packager/react-packager/src/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js index bfe4b6f88..7a45addb1 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule.js @@ -14,6 +14,10 @@ class AssetModule extends Module { return Promise.resolve([]); } + getAsyncDependencies() { + return Promise.resolve([]); + } + _read() { return Promise.resolve({}); } diff --git a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index fd4cb7081..2adb73d58 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -17,6 +17,10 @@ class AssetModule_DEPRECATED extends Module { return Promise.resolve([]); } + getAsyncDependencies() { + return Promise.resolve([]); + } + getPlainObject() { const {name, resolution} = getAssetDataFromName(this.path); diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js index 3f1b13efa..b1fc58d6e 100644 --- a/packager/react-packager/src/DependencyResolver/Module.js +++ b/packager/react-packager/src/DependencyResolver/Module.js @@ -1,3 +1,11 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ 'use strict'; const Promise = require('promise'); @@ -69,6 +77,10 @@ class Module { this._cache.invalidate(this.path); } + getAsyncDependencies() { + return this._read().then(data => data.asyncDependencies); + } + _read() { if (!this._reading) { this._reading = this._fastfs.readFile(this.path).then(content => { @@ -85,7 +97,9 @@ class Module { if ('extern' in moduleDocBlock) { data.dependencies = []; } else { - data.dependencies = extractRequires(content); + var dependencies = extractRequires(content); + data.dependencies = dependencies.sync; + data.asyncDependencies = dependencies.async; } return data; @@ -124,20 +138,68 @@ class Module { /** * Extract all required modules from a `code` string. */ -var blockCommentRe = /\/\*(.|\n)*?\*\//g; -var lineCommentRe = /\/\/.+(\n|$)/g; +const blockCommentRe = /\/\*(.|\n)*?\*\//g; +const lineCommentRe = /\/\/.+(\n|$)/g; +const trailingCommaRe = /,\s*$/g; +const removeSpacesRe = /\s/g; +const quotesRe = /'/g; function extractRequires(code /*: string*/) /*: Array*/ { - var deps = []; + var deps = { + sync: [], + async: [], + }; code .replace(blockCommentRe, '') .replace(lineCommentRe, '') + // Parse sync dependencies. See comment below for further detils. .replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => { - deps.push(dep); + deps.sync.push(dep); return match; }) - .replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) { - deps.push(dep); + // Parse the sync dependencies this module has. When the module is + // required, all it's sync dependencies will be loaded into memory. + // Sync dependencies can be defined either using `require` or the ES6 + // `import` syntax: + // var dep1 = require('dep1'); + .replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => { + deps.sync.push(dep); + }) + // Parse async dependencies this module has. As opposed to what happens + // with sync dependencies, when the module is required, it's async + // dependencies won't be loaded into memory. This is deferred till the + // code path gets to a `require.ensure` statement. The syntax is similar + // to webpack's one: + // require.ensure(['dep1', 'dep2'], () => { + // var dep1 = require('dep1'); + // var dep2 = require('dep2'); + // // do something with dep1 and dep2 + // }); + .replace(replacePatterns.REQUIRE_ENSURE_RE, (match, dep, post) => { + dep = dep + .replace(blockCommentRe, '') + .replace(lineCommentRe, '') + .replace(trailingCommaRe, '') + .replace(removeSpacesRe, '') + .replace(quotesRe, '"'); + + if (dep) { + try { + dep = JSON.parse('[' + dep + ']'); + } catch(e) { + throw 'Error processing `require.ensure` while attemping to parse ' + + 'dependencies `[' + dep + ']`: ' + e; + } + + dep.forEach(d => { + if (typeof d !== 'string') { + throw 'Error processing `require.ensure`: dependencies `[' + + d + ']` must be string literals'; + } + }); + + deps.async.push(dep); + } }); return deps; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index da159b5e9..3242c6771 100644 --- a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -479,8 +479,8 @@ describe('HasteDependencyResolver', function() { }, code).then(processedCode => { expect(processedCode).toEqual([ - '__d(\'test module\',["changed","Y"],function(global,' + - ' require, requireDynamic, requireLazy, module, exports) { ' + + '__d(\'test module\',["changed","Y"],function(global, require,' + + ' module, exports) { ' + "import'x';", "import 'changed';", "import 'changed' ;", diff --git a/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js b/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js new file mode 100644 index 000000000..637e84cbe --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/__tests__/Module-test.js @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('absolute-path') + .dontMock('../fastfs') + .dontMock('../replacePatterns') + .dontMock('../DependencyGraph/docblock') + .dontMock('../../FileWatcher') + .dontMock('../Module'); + +jest + .mock('fs'); + +describe('Module', () => { + var Fastfs; + var Module; + var ModuleCache; + var Promise; + var fs; + + const FileWatcher = require('../../FileWatcher'); + const fileWatcher = new FileWatcher(['/root']); + + beforeEach(function() { + Fastfs = require('../fastfs'); + Module = require('../Module'); + ModuleCache = require('../ModuleCache'); + Promise = require('promise'); + fs = require('fs'); + }); + + describe('Async Dependencies', () => { + function expectAsyncDependenciesToEqual(expected) { + var fastfs = new Fastfs( + ['/root'], + fileWatcher, + {crawling: Promise.resolve(['/root/index.js']), ignore: []}, + ); + + return fastfs.build().then(() => { + var module = new Module('/root/index.js', fastfs, new ModuleCache(fastfs)); + + return module.getAsyncDependencies().then(actual => + expect(actual).toEqual(expected) + ); + }); + } + + pit('should recognize single dependency', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1"], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1']]); + }); + + pit('should parse single quoted dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure([\'dep1\'], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1']]); + }); + + pit('should recognize multiple dependencies on the same statement', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", "dep2"], function() {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('should group async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + 'require.ensure(["dep1", "dep2"], function() {});', + 'require.ensure(["dep3", "dep4"], function() {});', + ].join('\n'), + } + }); + + return expectAsyncDependenciesToEqual([ + ['dep1', 'dep2'], + ['dep3', 'dep4'] + ]); + }); + + pit('shouldn\'t throw with ES6 arrow functions', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", "dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('parse fine new lines', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", \n"dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + + pit('ignore comments', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': 'require.ensure(["dep1", /*comment*/"dep2"], () => {});', + } + }); + + return expectAsyncDependenciesToEqual([['dep1', 'dep2']]); + }); + }); +}); diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index eae2e3da0..33b9c781f 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -176,8 +176,7 @@ function defineModuleCode({moduleName, code, deps}) { `__d(`, `'${moduleName}',`, `${deps},`, - 'function(global, require, ', - 'requireDynamic, requireLazy, module, exports) {', + 'function(global, require, module, exports) {', ` ${code}`, '\n});', ].join(''); diff --git a/packager/react-packager/src/DependencyResolver/polyfills/require.js b/packager/react-packager/src/DependencyResolver/polyfills/require.js index 04a0bff75..daedb4eaa 100644 --- a/packager/react-packager/src/DependencyResolver/polyfills/require.js +++ b/packager/react-packager/src/DependencyResolver/polyfills/require.js @@ -303,6 +303,18 @@ return _totalFactories; }; + /** + * Asynchronously loads any missing dependency and executes the provided + * callback once all of them are satisfied. + * + * Note that the dependencies on the provided array must be string literals + * as the packager uses this information to figure out how the modules are + * packaged into different bundles. + */ + require.ensure = function(dependencies, callback) { + throw '`require.ensure` is still not supported'; + }; + /** * The define function conforming to CommonJS proposal: * http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition @@ -464,56 +476,6 @@ } } - /** - * Special version of define that executes the factory as soon as all - * dependencies are met. - * - * define() does just that, defines a module. Module's factory will not be - * called until required by other module. This makes sense for most of our - * library modules: we do not want to execute the factory unless it's being - * used by someone. - * - * On the other hand there are modules, that you can call "entrance points". - * You want to run the "factory" method for them as soon as all dependencies - * are met. - * - * @example - * - * define('BaseClass', [], function() { return ... }); - * // ^^ factory for BaseClass was just stored in modulesMap - * - * define('SubClass', ['BaseClass'], function() { ... }); - * // SubClass module is marked as ready (waiting == 0), factory is just - * // stored - * - * define('OtherClass, ['BaseClass'], function() { ... }); - * // OtherClass module is marked as ready (waiting == 0), factory is just - * // stored - * - * requireLazy(['SubClass', 'ChatConfig'], - * function() { ... }); - * // ChatRunner is waiting for ChatConfig to come - * - * define('ChatConfig', [], { foo: 'bar' }); - * // at this point ChatRunner is marked as ready, and its factory - * // executed + all dependent factories are executed too: BaseClass, - * // SubClass, ChatConfig notice that OtherClass's factory won't be - * // executed unless explicitly required by someone - * - * @param {Array} dependencies - * @param {Object|Function} factory - */ - function requireLazy(dependencies, factory, context) { - return define( - dependencies, - factory, - undefined, - REQUIRE_WHEN_READY, - context, - 1 - ); - } - function _uid() { return '__mod__' + _counter++; } @@ -595,12 +557,8 @@ _register('global', global); _register('require', require); - _register('requireDynamic', require); - _register('requireLazy', requireLazy); global.require = require; - global.requireDynamic = require; - global.requireLazy = requireLazy; require.__debug = { modules: modulesMap, @@ -621,8 +579,7 @@ * out for every module which would be a lot of extra bytes. */ global.__d = function(id, deps, factory, _special, _inlineRequires) { - var defaultDeps = ['global', 'require', 'requireDynamic', 'requireLazy', - 'module', 'exports']; + var defaultDeps = ['global', 'require', 'module', 'exports']; define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, null, null, _inlineRequires); }; diff --git a/packager/react-packager/src/DependencyResolver/replacePatterns.js b/packager/react-packager/src/DependencyResolver/replacePatterns.js index cde2d873c..f683331a6 100644 --- a/packager/react-packager/src/DependencyResolver/replacePatterns.js +++ b/packager/react-packager/src/DependencyResolver/replacePatterns.js @@ -11,3 +11,4 @@ exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g; exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g; +exports.REQUIRE_ENSURE_RE = /\brequire\.ensure\s*\(\s*(?:\[([^\]]+)\])?/g; From 08d1bc8c9fa1844b9ea8714f6962947e8c5f4ea0 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 12 Aug 2015 15:24:59 -0700 Subject: [PATCH 012/139] [TextInput] Fix multiline TextInput so text and cursor stay in bounds Summary: With a multiline TextInput, the text is initially rendered correctly but once you try to edit it, the cursor slides off the top of the view and out of its bounds. Also if the TextInput is scrollable, you can scroll the text out of the bounds of the view, which looks buggy unless you clip the overflow. This occurs because the top content inset is applied to the RCTTextView instead of the UITextView's text container. This diff fixes both bugs by applying the vertical insets to the UITextView's textContainerInset instead of the RCTTextView's frame (which is a wrapper around a real UITextView). The left inset is still applied to the frame because there is a bug with the text rendering when the left textContainerInset is negative: the initial text doesn't show up until you focus the text view. The bug doesn't occur when setting the right textContainerInset, so I apply this workaround to only the left inset. Closes https://github.com/facebook/react-native/pull/2297 Github Author: James Ide --- Libraries/Text/RCTTextView.m | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 2363d52dc..337e4463a 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -47,14 +47,25 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) - (void)updateFrames { // Adjust the insets so that they are as close as possible to single-line - // RCTTextField defaults - UIEdgeInsets adjustedInset = (UIEdgeInsets){ - _contentInset.top - 5, _contentInset.left - 4, - _contentInset.bottom, _contentInset.right - }; - - [_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)]; - [_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)]; + // RCTTextField defaults, using the system defaults of font size 17 and a + // height of 31 points. + // + // We apply the left inset to the frame since a negative left text-container + // inset mysteriously causes the text to be hidden until the text view is + // first focused. + UIEdgeInsets adjustedFrameInset = UIEdgeInsetsZero; + adjustedFrameInset.left = _contentInset.left - 5; + + UIEdgeInsets adjustedTextContainerInset = _contentInset; + adjustedTextContainerInset.top += 5; + adjustedTextContainerInset.left = 0; + + CGRect frame = UIEdgeInsetsInsetRect(self.bounds, adjustedFrameInset); + _textView.frame = frame; + _placeholderView.frame = frame; + + _textView.textContainerInset = adjustedTextContainerInset; + _placeholderView.textContainerInset = adjustedTextContainerInset; } - (void)updatePlaceholder From 7750db05e610899ecceace88df78b2fa4e25c340 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 12 Aug 2015 19:04:54 -0700 Subject: [PATCH 013/139] [react-packager] Set a lifetime on workers to avoid memory leaks --- .../react-packager/src/JSTransformer/index.js | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index f78840163..52bb24ad1 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -8,19 +8,16 @@ */ 'use strict'; -var fs = require('fs'); -var Promise = require('promise'); -var workerFarm = require('worker-farm'); -var declareOpts = require('../lib/declareOpts'); -var util = require('util'); -var ModuleTransport = require('../lib/ModuleTransport'); +const ModuleTransport = require('../lib/ModuleTransport'); +const Promise = require('promise'); +const declareOpts = require('../lib/declareOpts'); +const fs = require('fs'); +const util = require('util'); +const workerFarm = require('worker-farm'); -var readFile = Promise.denodeify(fs.readFile); +const readFile = Promise.denodeify(fs.readFile); -module.exports = Transformer; -Transformer.TransformError = TransformError; - -var validateOpts = declareOpts({ +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -42,38 +39,45 @@ var validateOpts = declareOpts({ }, }); -function Transformer(options) { - var opts = validateOpts(options); +// Avoid memory leaks caused in workers. This number seems to be a good enough number +// to avoid any memory leak while not slowing down initial builds. +// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. +const MAX_CALLS_PER_WORKER = 600; - this._cache = opts.cache; +class Transformer { + constructor(options) { + const opts = validateOpts(options); - if (options.transformModulePath != null) { - this._workers = workerFarm( - {autoStart: true, maxConcurrentCallsPerWorker: 1}, - options.transformModulePath - ); + this._cache = opts.cache; - this._transform = Promise.denodeify(this._workers); - } -} + if (opts.transformModulePath != null) { + this._workers = workerFarm({ + autoStart: true, + maxConcurrentCallsPerWorker: 1, + maxCallsPerWorker: MAX_CALLS_PER_WORKER, + }, opts.transformModulePath); -Transformer.prototype.kill = function() { - this._workers && workerFarm.end(this._workers); -}; - -Transformer.prototype.invalidateFile = function(filePath) { - this._cache.invalidate(filePath); -}; - -Transformer.prototype.loadFileAndTransform = function(filePath) { - if (this._transform == null) { - return Promise.reject(new Error('No transfrom module')); + this._transform = Promise.denodeify(this._workers); + } } - var transform = this._transform; - return this._cache.get(filePath, 'transformedSource', function() { - // TODO: use fastfs to avoid reading file from disk again - return readFile(filePath) + kill() { + this._workers && workerFarm.end(this._workers); + } + + invalidateFile(filePath) { + this._cache.invalidate(filePath); + } + + loadFileAndTransform(filePath) { + if (this._transform == null) { + return Promise.reject(new Error('No transfrom module')); + } + + var transform = this._transform; + return this._cache.get(filePath, 'transformedSource', function() { + // TODO: use fastfs to avoid reading file from disk again + return readFile(filePath) .then(function(buffer) { var sourceCode = buffer.toString(); @@ -102,8 +106,13 @@ Transformer.prototype.loadFileAndTransform = function(filePath) { }).catch(function(err) { throw formatError(err, filePath); }); - }); -}; + }); + } +} + +module.exports = Transformer; + +Transformer.TransformError = TransformError; function TransformError() { Error.captureStackTrace && Error.captureStackTrace(this, TransformError); From 1d072076db65c07fd29ff15acd982e080625ab67 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 10 Aug 2015 17:42:32 -0100 Subject: [PATCH 014/139] Reduce +[UIImage imageNamed:] overhead --- React/Base/RCTConvert.m | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index ca091592e..afa0bea46 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -664,12 +664,29 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ NSURL *URL = [self NSURL:path]; NSString *scheme = [URL.scheme lowercaseString]; - if ([scheme isEqualToString:@"file"]) { - - if ([NSThread currentThread] == [NSThread mainThread]) { - // Image may reside inside a .car file, in which case we have no choice - // but to use +[UIImage imageNamed] - but this method isn't thread safe - image = [UIImage imageNamed:path]; + if (path && [scheme isEqualToString:@"file"]) { + if (RCT_DEBUG || [NSThread currentThread] == [NSThread mainThread]) { + if ([URL.path hasPrefix:[[NSBundle mainBundle] resourcePath]]) { + // Image may reside inside a .car file, in which case we have no choice + // but to use +[UIImage imageNamed] - but this method isn't thread safe + static NSMutableDictionary *XCAssetMap = nil; + if (!XCAssetMap) { + XCAssetMap = [[NSMutableDictionary alloc] init]; + } + NSNumber *isAsset = XCAssetMap[path]; + if (!isAsset || isAsset.boolValue) { + image = [UIImage imageNamed:path]; + if (RCT_DEBUG && image) { + // If we succeeded in loading the image via imageNamed, and the + // method wasn't called on the main thread, that's a coding error + RCTAssertMainThread(); + } + } + if (!isAsset) { + // Avoid calling `+imageNamed` again in future if it's not needed. + XCAssetMap[path] = @(image != nil); + } + } } if (!image) { @@ -680,13 +697,6 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ image = [UIImage imageWithContentsOfFile:path]; } - // We won't warn about nil images because there are legitimate cases - // where we find out if a string is an image by using this method, but - // we do enforce thread-safe API usage with the following check - if (RCT_DEBUG && !image && [UIImage imageNamed:path]) { - RCTAssertMainThread(); - } - } else if ([scheme isEqualToString:@"data"]) { image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]]; } else { From 0487c55c254e765e97379300b6efb3a7ee22916b Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 13 Aug 2015 04:01:06 -0700 Subject: [PATCH 015/139] Removed unnecessary overhead in RCTConvert functions --- React/Base/RCTConvert.h | 10 +++-- React/Base/RCTConvert.m | 93 ++++++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 415a5968f..cf7f056e3 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -17,7 +17,6 @@ #import "RCTLog.h" #import "RCTPointerEvents.h" - /** * This class provides a collection of conversion functions for mapping * JSON objects to native types and classes. These are useful when writing @@ -39,10 +38,11 @@ + (NSUInteger)NSUInteger:(id)json; + (NSArray *)NSArray:(id)json; -+ (NSSet *)NSSet:(id)json; + (NSDictionary *)NSDictionary:(id)json; + (NSString *)NSString:(id)json; + (NSNumber *)NSNumber:(id)json; + ++ (NSSet *)NSSet:(id)json; + (NSData *)NSData:(id)json; + (NSIndexSet *)NSIndexSet:(id)json; @@ -94,6 +94,9 @@ typedef NSURL RCTFileURL; size:(id)size weight:(id)weight style:(id)style scaleMultiplier:(CGFloat)scaleMultiplier; +typedef NSArray NSArrayArray; ++ (NSArrayArray *)NSArrayArray:(id)json; + typedef NSArray NSStringArray; + (NSStringArray *)NSStringArray:(id)json; @@ -164,7 +167,6 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter]) #define RCT_CUSTOM_CONVERTER(type, name, code) \ + (type)name:(id)json \ { \ - json = (json == (id)kCFNull) ? nil : json; \ if (!RCT_DEBUG) { \ return code; \ } else { \ @@ -185,7 +187,7 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter]) * detailed error reporting if an invalid value is passed in. */ #define RCT_NUMBER_CONVERTER(type, getter) \ -RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter]) +RCT_CUSTOM_CONVERTER(type, type, [RCT_DEBUG ? [self NSNumber:json] : json getter]) /** * This macro is used for creating converters for enum types. diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index afa0bea46..b83089973 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -29,38 +29,33 @@ RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue); RCT_NUMBER_CONVERTER(NSInteger, integerValue) RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue) -RCT_CUSTOM_CONVERTER(NSArray *, NSArray, [NSArray arrayWithArray:json]) +/** + * This macro is used for creating converter functions for directly + * representable json values that require no conversion. + */ +#if RCT_DEBUG +#define RCT_JSON_CONVERTER(type) \ ++ (type *)type:(id)json \ +{ \ + if ([json isKindOfClass:[type class]]) { \ + return json; \ + } else if (json) { \ + RCTLogConvertError(json, @#type); \ + } \ + return nil; \ +} +#else +#define RCT_JSON_CONVERTER(type) \ ++ (type *)type:(id)json { return json; } +#endif + +RCT_JSON_CONVERTER(NSArray) +RCT_JSON_CONVERTER(NSDictionary) +RCT_JSON_CONVERTER(NSString) +RCT_JSON_CONVERTER(NSNumber) + RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json]) -RCT_CUSTOM_CONVERTER(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json]) -RCT_CONVERTER(NSString *, NSString, description) - -+ (NSNumber *)NSNumber:(id)json -{ - if ([json isKindOfClass:[NSNumber class]]) { - return json; - } else if ([json isKindOfClass:[NSString class]]) { - static NSNumberFormatter *formatter; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - formatter = [[NSNumberFormatter alloc] init]; - formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - }); - NSNumber *number = [formatter numberFromString:json]; - if (!number) { - RCTLogConvertError(json, @"a number"); - } - return number; - } else if (json && json != (id)kCFNull) { - RCTLogConvertError(json, @"a number"); - } - return nil; -} - -+ (NSData *)NSData:(id)json -{ - // TODO: should we automatically decode base64 data? Probably not... - return [[self NSString:json] dataUsingEncoding:NSUTF8StringEncoding]; -} +RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncoding]) + (NSIndexSet *)NSIndexSet:(id)json { @@ -79,7 +74,7 @@ RCT_CONVERTER(NSString *, NSString, description) + (NSURL *)NSURL:(id)json { NSString *path = [self NSString:json]; - if (!path.length) { + if (!path) { return nil; } @@ -155,7 +150,7 @@ RCT_CONVERTER(NSString *, NSString, description) "Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json); } return date; - } else if (json && json != (id)kCFNull) { + } else if (json) { RCTLogConvertError(json, @"a date"); } return nil; @@ -169,7 +164,7 @@ RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFro NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNumber *defaultValue, id json) { - if (!json || json == (id)kCFNull) { + if (!json) { return defaultValue; } if ([json isKindOfClass:[NSNumber class]]) { @@ -180,13 +175,12 @@ NSNumber *RCTConvertEnumValue(const char *typeName, NSDictionary *mapping, NSNum RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, allValues); return defaultValue; } - - if (![json isKindOfClass:[NSString class]]) { + if (RCT_DEBUG && ![json isKindOfClass:[NSString class]]) { RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", typeName, [json classForCoder], json); } id value = mapping[json]; - if (!value && [json description].length > 0) { + if (RCT_DEBUG && !value && [json description].length > 0) { RCTLogError(@"Invalid %s '%@'. should be one of: %@", typeName, json, [[mapping allKeys] sortedArrayUsingSelector: @selector(caseInsensitiveCompare:)]); } return value ?: defaultValue; @@ -332,7 +326,7 @@ static void RCTConvertCGStructValue(const char *type, NSArray *fields, NSDiction for (NSUInteger i = 0; i < count; i++) { result[i] = [RCTConvert CGFloat:json[fields[i]]]; } - } else if (RCT_DEBUG && json && json != (id)kCFNull) { + } else if (json) { RCTLogConvertError(json, @(type)); } } @@ -619,8 +613,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ blue:[self CGFloat:json[@"b"]] alpha:[self CGFloat:json[@"a"] ?: @1]]; - } - else if (RCT_DEBUG && json && json != (id)kCFNull) { + } else if (json) { RCTLogConvertError(json, @"a color"); } @@ -646,7 +639,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ // TODO: we might as well cache the result of these checks (and possibly the // image itself) so as to reduce overhead on subsequent checks of the same input - if (!json || json == (id)kCFNull) { + if (!json) { return nil; } @@ -933,13 +926,25 @@ NSArray *RCTConvertArrayValue(SEL type, id json) return values; } -RCT_ARRAY_CONVERTER(NSString) -RCT_ARRAY_CONVERTER(NSDictionary) RCT_ARRAY_CONVERTER(NSURL) RCT_ARRAY_CONVERTER(RCTFileURL) -RCT_ARRAY_CONVERTER(NSNumber) RCT_ARRAY_CONVERTER(UIColor) +/** + * This macro is used for creating converter functions for directly + * representable json array values that require no conversion. + */ +#if RCT_DEBUG +#define RCT_JSON_ARRAY_CONVERTER(type) RCT_ARRAY_CONVERTER(type) +#else +#define RCT_JSON_ARRAY_CONVERTER(type) + (NSArray *)type##Array:(id)json { return json; } +#endif + +RCT_JSON_ARRAY_CONVERTER(NSArray) +RCT_JSON_ARRAY_CONVERTER(NSString) +RCT_JSON_ARRAY_CONVERTER(NSDictionary) +RCT_JSON_ARRAY_CONVERTER(NSNumber) + // Can't use RCT_ARRAY_CONVERTER due to bridged cast + (NSArray *)CGColorArray:(id)json { From b9dc29bcb39d761972b26e18b955cba28bcc2495 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 13 Aug 2015 06:16:08 -0700 Subject: [PATCH 016/139] [ReactNative] Fix SampleApp xcodeproj configuration Summary: Make SampleApp scheme shared. --- .../xcshareddata/xcschemes/SampleApp.xcscheme | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme diff --git a/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme b/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme new file mode 100644 index 000000000..f0a3ea9ae --- /dev/null +++ b/Examples/SampleApp/iOS/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 672b77f355f0703fc94884fff66ad81b7f856949 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Thu, 13 Aug 2015 07:41:42 -0700 Subject: [PATCH 017/139] Perform clip update on RCTScrollView in increments of 50 pt Summary: This diff removes calls to `-updateClippedSubviews` by only re-clipping when the scroll view moves by a certain number of pixels. leeway = 50pt => 46.9% of calls removed leeway = 10pt => 13.2% " " " --- React/React.xcodeproj/project.pbxproj | 2 ++ React/Views/RCTScrollView.m | 31 ++++++++++++++++++++++++++- React/Views/UIView+Private.h | 18 ++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 React/Views/UIView+Private.h diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index d3a404dab..8d8ef9269 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -256,6 +256,7 @@ 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = ""; }; 83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; }; 83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; }; + 83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = ""; }; E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = ""; }; E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAccessibilityManager.h; sourceTree = ""; }; E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAccessibilityManager.m; sourceTree = ""; }; @@ -405,6 +406,7 @@ 13C156041AB1A2840079392D /* RCTWebViewManager.m */, 13B080231A694A8400A75B9A /* RCTWrapperViewController.h */, 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */, + 83F15A171B7CC46900F10295 /* UIView+Private.h */, 13E067531A70F44B002CDEE1 /* UIView+React.h */, 13E067541A70F44B002CDEE1 /* UIView+React.m */, ); diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 98dec5271..89afd8b63 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -16,6 +16,7 @@ #import "RCTLog.h" #import "RCTUIManager.h" #import "RCTUtils.h" +#import "UIView+Private.h" #import "UIView+React.h" CGFloat const ZINDEX_DEFAULT = 0; @@ -365,6 +366,7 @@ RCT_NOT_IMPLEMENTED(-init) NSTimeInterval _lastScrollDispatchTime; NSMutableArray *_cachedChildFrames; BOOL _allowNextScrollNoMatterWhat; + CGRect _lastClippedToRect; } @synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate; @@ -374,7 +376,6 @@ RCT_NOT_IMPLEMENTED(-init) RCTAssertParam(eventDispatcher); if ((self = [super initWithFrame:CGRectZero])) { - _eventDispatcher = eventDispatcher; _scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero]; _scrollView.delegate = self; @@ -382,6 +383,7 @@ RCT_NOT_IMPLEMENTED(-init) _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; _contentSize = CGSizeZero; + _lastClippedToRect = CGRectNull; _scrollEventThrottle = 0.0; _lastScrollDispatchTime = CACurrentMediaTime(); @@ -469,6 +471,33 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) [self updateClippedSubviews]; } +- (void)updateClippedSubviews +{ + // Find a suitable view to use for clipping + UIView *clipView = [self react_findClipView]; + if (!clipView) { + return; + } + + static const CGFloat leeway = 50.0; + + const CGSize contentSize = _scrollView.contentSize; + const CGRect bounds = _scrollView.bounds; + const BOOL scrollsHorizontally = contentSize.width > bounds.size.width; + const BOOL scrollsVertically = contentSize.height > bounds.size.height; + + const BOOL shouldClipAgain = + CGRectIsNull(_lastClippedToRect) || + (scrollsHorizontally && (bounds.size.width < leeway || fabs(_lastClippedToRect.origin.x - bounds.origin.x) >= leeway)) || + (scrollsVertically && (bounds.size.height < leeway || fabs(_lastClippedToRect.origin.y - bounds.origin.y) >= leeway)); + + if (shouldClipAgain) { + const CGRect clipRect = CGRectInset(clipView.bounds, -leeway, -leeway); + [self react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView]; + _lastClippedToRect = bounds; + } +} + - (void)setContentInset:(UIEdgeInsets)contentInset { CGPoint contentOffset = _scrollView.contentOffset; diff --git a/React/Views/UIView+Private.h b/React/Views/UIView+Private.h new file mode 100644 index 000000000..14e6fcd2b --- /dev/null +++ b/React/Views/UIView+Private.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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 + +@interface UIView (RCTViewUnmounting) + +- (void)react_remountAllSubviews; +- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView; +- (UIView *)react_findClipView; + +@end From 7232b1bbc7d0477233829b19c91e8654dbb9203f Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 12 Aug 2015 13:07:24 -0100 Subject: [PATCH 018/139] Fixed image clipping / resizing logic --- .../UIExplorer.xcodeproj/project.pbxproj | 8 +-- ...RCTClipRectTests.m => RCTImageUtilTests.m} | 36 +++++----- Libraries/Image/RCTImageDownloader.m | 13 ++-- Libraries/Image/RCTImageLoader.m | 4 +- Libraries/Image/RCTImageUtils.h | 22 +++--- Libraries/Image/RCTImageUtils.m | 68 +++++++++++-------- Libraries/Image/RCTImageView.m | 34 +++++++--- 7 files changed, 107 insertions(+), 78 deletions(-) rename Examples/UIExplorer/UIExplorerUnitTests/{RCTClipRectTests.m => RCTImageUtilTests.m} (65%) diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index 5b9e04288..d40120041 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -25,7 +25,7 @@ 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; }; 141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; }; 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; }; - 144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; }; + 144D21241B2204C5006DB32B /* RCTImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */; }; 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */; }; 1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */; }; @@ -191,7 +191,7 @@ 143BC5951B21E3E100462512 /* UIExplorerIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 143BC5981B21E3E100462512 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIExplorerSnapshotTests.m; sourceTree = ""; }; - 144D21231B2204C5006DB32B /* RCTClipRectTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTClipRectTests.m; sourceTree = ""; }; + 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtilTests.m; sourceTree = ""; }; 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAllocationTests.m; sourceTree = ""; }; 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridgeTests.m; sourceTree = ""; }; 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutorTests.m; sourceTree = ""; }; @@ -357,12 +357,12 @@ 1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */, 1497CFA51B21F5E400C1F8F2 /* RCTBridgeTests.m */, 138D6A151B53CD440074A87E /* RCTCacheTests.m */, - 144D21231B2204C5006DB32B /* RCTClipRectTests.m */, 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */, 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */, 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */, 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */, 1300627E1B59179B0043FE5A /* RCTGzipTests.m */, + 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */, 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */, 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */, 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */, @@ -793,7 +793,7 @@ buildActionMask = 2147483647; files = ( 1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */, - 144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */, + 144D21241B2204C5006DB32B /* RCTImageUtilTests.m in Sources */, 1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */, 1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */, 1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */, diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m similarity index 65% rename from Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m rename to Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m index 0041a1b46..f1effc8d6 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTClipRectTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageUtilTests.m @@ -33,11 +33,11 @@ RCTAssertEqualPoints(a.origin, b.origin); \ RCTAssertEqualSizes(a.size, b.size); \ } -@interface RCTClipRectTests : XCTestCase +@interface RCTImageUtilTests : XCTestCase @end -@implementation RCTClipRectTests +@implementation RCTImageUtilTests - (void)testLandscapeSourceLandscapeTarget { @@ -46,19 +46,19 @@ RCTAssertEqualSizes(a.size, b.size); \ { CGRect expected = {CGPointZero, {100, 20}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); RCTAssertEqualRects(expected, result); } { CGRect expected = {CGPointZero, {100, 10}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); RCTAssertEqualRects(expected, result); } { CGRect expected = {{-50, 0}, {200, 20}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); RCTAssertEqualRects(expected, result); } } @@ -69,20 +69,20 @@ RCTAssertEqualSizes(a.size, b.size); \ CGSize target = {100, 20}; { - CGRect expected = {CGPointZero, {10, 20}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill); + CGRect expected = {CGPointZero, {100, 20}}; + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); RCTAssertEqualRects(expected, result); } { CGRect expected = {CGPointZero, {2, 20}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); RCTAssertEqualRects(expected, result); } { - CGRect expected = {{0, -49}, {10, 100}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill); + CGRect expected = {{0, -490}, {100, 1000}}; + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); RCTAssertEqualRects(expected, result); } } @@ -93,20 +93,20 @@ RCTAssertEqualSizes(a.size, b.size); \ CGSize target = {20, 50}; { - CGRect expected = {CGPointZero, {10, 50}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill); + CGRect expected = {CGPointZero, {20, 50}}; + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); RCTAssertEqualRects(expected, result); } { CGRect expected = {CGPointZero, {5, 50}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFit); RCTAssertEqualRects(expected, result); } { - CGRect expected = {{0, -37.5}, {10, 100}}; - CGRect result = RCTClipRect(content, 2, target, 2, UIViewContentModeScaleAspectFill); + CGRect expected = {{0, -75}, {20, 200}}; + CGRect result = RCTTargetRect(content, target, 2, UIViewContentModeScaleAspectFill); RCTAssertEqualRects(expected, result); } } @@ -117,8 +117,8 @@ RCTAssertEqualSizes(a.size, b.size); \ CGSize target = {20, 50}; { - CGRect expected = {{0, -38}, {10, 100}}; - CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill); + CGRect expected = {{0, -75}, {20, 200}}; + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleAspectFill); RCTAssertEqualRects(expected, result); } } @@ -129,7 +129,7 @@ RCTAssertEqualSizes(a.size, b.size); \ CGSize target = {3, 3}; CGRect expected = {CGPointZero, {3, 3}}; - CGRect result = RCTClipRect(content, 2, target, 1, UIViewContentModeScaleToFill); + CGRect result = RCTTargetRect(content, target, 1, UIViewContentModeScaleToFill); RCTAssertEqualRects(expected, result); } diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 8bae60112..6213431e2 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -15,9 +15,6 @@ #import "RCTNetworking.h" #import "RCTUtils.h" -CGSize RCTTargetSizeForClipRect(CGRect); -CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); - @implementation RCTImageDownloader { NSURLCache *_cache; @@ -131,14 +128,14 @@ RCT_EXPORT_MODULE() UIImage *image = [UIImage imageWithData:data scale:scale]; if (image && !CGSizeEqualToSize(size, CGSizeZero)) { - // Get scale and size - CGRect imageRect = RCTClipRect(image.size, scale, size, scale, resizeMode); - CGSize destSize = RCTTargetSizeForClipRect(imageRect); + // Get destination size + CGSize targetSize = RCTTargetSize(image.size, image.scale, + size, scale, resizeMode, NO); // Decompress image at required size BOOL opaque = !RCTImageHasAlpha(image.CGImage); - UIGraphicsBeginImageContextWithOptions(destSize, opaque, scale); - [image drawInRect:imageRect]; + UIGraphicsBeginImageContextWithOptions(targetSize, opaque, scale); + [image drawInRect:(CGRect){CGPointZero, targetSize}]; image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 44ad8d6b1..dc0fbf640 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -85,8 +85,8 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, } CGSize sourceSize = representation.dimensions; - CGRect targetRect = RCTClipRect(sourceSize, representation.scale, size, scale, resizeMode); - CGSize targetSize = targetRect.size; + CGSize targetSize = RCTTargetSize(sourceSize, representation.scale, + size, scale, resizeMode, NO); NSDictionary *options = @{ (id)kCGImageSourceShouldAllowFloat: @YES, diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h index cbb38cda8..1bca23241 100644 --- a/Libraries/Image/RCTImageUtils.h +++ b/Libraries/Image/RCTImageUtils.h @@ -13,20 +13,24 @@ #import "RCTDefines.h" /** - * Returns the optimal context size for an image drawn using the clip rect - * returned by RCTClipRect. + * This function takes an input content size (typically from an image), a target + * size and scale that it will be drawn at (typically in a CGContext) and then + * calculates the rectangle to draw the image into so that it will be sized and + * positioned correctly if drawn using the specified content mode. */ -RCT_EXTERN CGSize RCTTargetSizeForClipRect(CGRect clipRect); +RCT_EXTERN CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, + CGFloat destScale, UIViewContentMode resizeMode); /** * This function takes an input content size & scale (typically from an image), - * a target size & scale that it will be drawn into (typically a CGContext) and - * then calculates the optimal rectangle to draw the image into so that it will - * be sized and positioned correctly if drawn using the specified content mode. + * a target size & scale at which it will be displayed (typically in a + * UIImageView) and then calculates the optimal size at which to redraw the + * image so that it will be displayed correctly with the specified content mode. */ -RCT_EXTERN CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, - CGSize destSize, CGFloat destScale, - UIViewContentMode resizeMode); +RCT_EXTERN CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode, + BOOL allowUpscaling); /** * This function takes an input content size & scale (typically from an image), diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m index 82a97a778..78008dcad 100644 --- a/Libraries/Image/RCTImageUtils.m +++ b/Libraries/Image/RCTImageUtils.m @@ -29,28 +29,14 @@ static CGSize RCTCeilSize(CGSize size, CGFloat scale) }; } -CGSize RCTTargetSizeForClipRect(CGRect clipRect) -{ - return (CGSize){ - clipRect.size.width + clipRect.origin.x * 2, - clipRect.size.height + clipRect.origin.y * 2 - }; -} - -CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, - CGSize destSize, CGFloat destScale, - UIViewContentMode resizeMode) +CGRect RCTTargetRect(CGSize sourceSize, CGSize destSize, + CGFloat destScale, UIViewContentMode resizeMode) { if (CGSizeEqualToSize(destSize, CGSizeZero)) { // Assume we require the largest size available return (CGRect){CGPointZero, sourceSize}; } - // Precompensate for scale - CGFloat scale = sourceScale / destScale; - sourceSize.width *= scale; - sourceSize.height *= scale; - CGFloat aspect = sourceSize.width / sourceSize.height; // If only one dimension in destSize is non-zero (for example, an Image // with `flex: 1` whose height is indeterminate), calculate the unknown @@ -61,7 +47,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, if (destSize.height == 0) { destSize.height = destSize.width / aspect; } - + // Calculate target aspect ratio if needed (don't bother if resizeMode == stretch) CGFloat targetAspect = 0.0; if (resizeMode != UIViewContentModeScaleToFill) { @@ -74,20 +60,18 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, switch (resizeMode) { case UIViewContentModeScaleToFill: // stretch - sourceSize.width = MIN(destSize.width, sourceSize.width); - sourceSize.height = MIN(destSize.height, sourceSize.height); - return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)}; + return (CGRect){CGPointZero, RCTCeilSize(destSize, destScale)}; case UIViewContentModeScaleAspectFit: // contain if (targetAspect <= aspect) { // target is taller than content - sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); + sourceSize.width = destSize.width = destSize.width; sourceSize.height = sourceSize.width / aspect; } else { // target is wider than content - sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); + sourceSize.height = destSize.height = destSize.height; sourceSize.width = sourceSize.height * aspect; } return (CGRect){CGPointZero, RCTCeilSize(sourceSize, destScale)}; @@ -96,7 +80,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, if (targetAspect <= aspect) { // target is taller than content - sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height); + sourceSize.height = destSize.height = destSize.height; sourceSize.width = sourceSize.height * aspect; destSize.width = destSize.height * targetAspect; return (CGRect){ @@ -106,7 +90,7 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, } else { // target is wider than content - sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width); + sourceSize.width = destSize.width = destSize.width; sourceSize.height = sourceSize.width / aspect; destSize.height = destSize.width / targetAspect; return (CGRect){ @@ -122,9 +106,39 @@ CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale, } } -RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale, - CGSize destSize, CGFloat destScale, - UIViewContentMode resizeMode) +CGSize RCTTargetSize(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode, + BOOL allowUpscaling) +{ + switch (resizeMode) { + case UIViewContentModeScaleToFill: // stretch + + if (!allowUpscaling) { + CGFloat scale = sourceScale / destScale; + destSize.width = MIN(sourceSize.width * scale, destSize.width); + destSize.height = MIN(sourceSize.height * scale, destSize.height); + } + return RCTCeilSize(destSize, destScale); + + default: { + + // Get target size + CGSize size = RCTTargetRect(sourceSize, destSize, destScale, resizeMode).size; + if (!allowUpscaling) { + // return sourceSize if target size is larger + if (sourceSize.width * sourceScale < size.width * destScale) { + return sourceSize; + } + } + return size; + } + } +} + +BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale, + CGSize destSize, CGFloat destScale, + UIViewContentMode resizeMode) { if (CGSizeEqualToSize(destSize, CGSizeZero)) { // Assume we require the largest size available diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 56c37b21a..f56b3d183 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -14,6 +14,7 @@ #import "RCTEventDispatcher.h" #import "RCTGIFImage.h" #import "RCTImageLoader.h" +#import "RCTImageUtils.h" #import "RCTUtils.h" #import "UIView+React.h" @@ -43,7 +44,7 @@ RCT_NOT_IMPLEMENTED(-init) -- (void)_updateImage +- (void)updateImage { UIImage *image = self.image; if (!image) { @@ -72,7 +73,7 @@ RCT_NOT_IMPLEMENTED(-init) image = image ?: _defaultImage; if (image != super.image) { super.image = image; - [self _updateImage]; + [self updateImage]; } } @@ -80,7 +81,7 @@ RCT_NOT_IMPLEMENTED(-init) { if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) { _capInsets = capInsets; - [self _updateImage]; + [self updateImage]; } } @@ -88,7 +89,7 @@ RCT_NOT_IMPLEMENTED(-init) { if (_renderingMode != renderingMode) { _renderingMode = renderingMode; - [self _updateImage]; + [self updateImage]; } } @@ -100,6 +101,16 @@ RCT_NOT_IMPLEMENTED(-init) } } +- (void)setContentMode:(UIViewContentMode)contentMode +{ + if (self.contentMode != contentMode) { + super.contentMode = contentMode; + if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) { + [self reloadImage]; + } + } +} + - (void)reloadImage { if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) { @@ -165,12 +176,15 @@ RCT_NOT_IMPLEMENTED(-init) if (self.image == nil) { [self reloadImage]; } else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) { - CGSize imageSize = { - self.image.size.width / RCTScreenScale(), - self.image.size.height / RCTScreenScale() - }; - CGFloat widthChangeFraction = imageSize.width ? ABS(imageSize.width - frame.size.width) / imageSize.width : 1; - CGFloat heightChangeFraction = imageSize.height ? ABS(imageSize.height - frame.size.height) / imageSize.height : 1; + + // Get optimal image size + CGSize currentSize = self.image.size; + CGSize idealSize = RCTTargetSize(self.image.size, self.image.scale, frame.size, + RCTScreenScale(), self.contentMode, YES); + + CGFloat widthChangeFraction = ABS(currentSize.width - idealSize.width) / currentSize.width; + CGFloat heightChangeFraction = ABS(currentSize.height - idealSize.height) / currentSize.height; + // If the combined change is more than 20%, reload the asset in case there is a better size. if (widthChangeFraction + heightChangeFraction > 0.2) { [self reloadImage]; From f552495e7fba836f256d1375016d57c4109dc97b Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Thu, 13 Aug 2015 08:05:09 -0700 Subject: [PATCH 019/139] Fix NavigatorIOS freeze when pushing before initialization completes Summary: This freezes the app in the UIExplorer because we try to push a new view controller onto the screen before the navigator finishes loading. This was exacerbated by @tadeuzagallo's diff that made the loading even faster. Hehe --- Examples/UIExplorer/UIExplorerList.ios.js | 27 ----------------------- 1 file changed, 27 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 25f69684d..3db7074d7 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -123,32 +123,6 @@ class UIExplorerList extends React.Component { ); } - componentDidMount() { - var wasUIExplorer = false; - var didOpenExample = false; - - this.props.navigator.navigationContext.addListener('didfocus', (event) => { - var isUIExplorer = event.data.route.title === 'UIExplorer'; - - if (!didOpenExample && isUIExplorer) { - didOpenExample = true; - - var visibleExampleTitle = Settings.get('visibleExample'); - if (visibleExampleTitle) { - var predicate = (example) => example.title === visibleExampleTitle; - var foundExample = APIS.find(predicate) || COMPONENTS.find(predicate); - if (foundExample) { - setTimeout(() => this._openExample(foundExample), 100); - } - } else if (!wasUIExplorer && isUIExplorer) { - Settings.set({visibleExample: null}); - } - } - - wasUIExplorer = isUIExplorer; - }); - } - renderAdditionalView(renderRow: Function, renderTextInput: Function): React.Component { return renderTextInput(styles.searchTextInput); } @@ -171,7 +145,6 @@ class UIExplorerList extends React.Component { } onPressRow(example: any) { - Settings.set({visibleExample: example.title}); this._openExample(example); } } From 41a88c3eb1741b853b66ccb90c0c48be3855fd4e Mon Sep 17 00:00:00 2001 From: Martino Luca Date: Tue, 4 Aug 2015 10:57:36 -0700 Subject: [PATCH 020/139] Fixing typos in imports --- JSCLegacyProfiler/JSCLegacyProfiler.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSCLegacyProfiler/JSCLegacyProfiler.mm b/JSCLegacyProfiler/JSCLegacyProfiler.mm index 218c5e55d..b946c8fd1 100644 --- a/JSCLegacyProfiler/JSCLegacyProfiler.mm +++ b/JSCLegacyProfiler/JSCLegacyProfiler.mm @@ -8,7 +8,7 @@ #include "JSProfilerPrivate.h" #include "JSStringRef.h" -#include +#include #define GEN_AND_CHECK(expr) \ do { \ From c084793e91ce558ad3bdb1b21f65c07c708949de Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Thu, 13 Aug 2015 08:27:16 -0700 Subject: [PATCH 021/139] Fix ModalFullScreenView's window not dismissing after being removed --- React/Views/RCTModalHostView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index dd16f9e6c..0322e02ca 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -74,6 +74,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:coder) [super didMoveToSuperview]; if (self.superview) { + RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller"); [self.reactViewController presentViewController:_modalViewController animated:self.animated completion:nil]; } else { [_modalViewController dismissViewControllerAnimated:self.animated completion:nil]; From 5cad2e937037151bb0896dde05fc22209c9f157b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 13 Aug 2015 12:31:57 -0700 Subject: [PATCH 022/139] [react-packager] Introduce Bundler Summary: Introduce a Bundler capable of generating the layout of modules for a given entry point. The current algorithm is the most trivial we could come up with: (1)it puts all the sync dependencies into the same bundle and (2) each group of async dependencies with all their dependencies into a separate bundle. For async dependencies we do this recursivelly, meaning that async dependencies could have async dependencies which will end up on separate bundles as well. The output of of the layout is an array of bundles. Each bundle is just an array for now with the dependencies in the order the requires where processed. Using this information we should be able to generate the actual bundles by using the `/path/to/entry/point.bundle` endpoint. We might change the structure of this json in the future, for instance to account for parent/child bundles relationships. The next step will be to improve this algorithm to avoid repeating quite a bit dependencies across bundles. --- .../__tests__/BundlesLayout-test.js | 150 +++++ .../BundlesLayoutIntegration-test.js | 512 ++++++++++++++++++ .../react-packager/src/BundlesLayout/index.js | 76 +++ .../DependencyGraph/index.js | 76 ++- .../src/DependencyResolver/Module.js | 2 + .../src/DependencyResolver/index.js | 18 +- 6 files changed, 799 insertions(+), 35 deletions(-) create mode 100644 packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js create mode 100644 packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js create mode 100644 packager/react-packager/src/BundlesLayout/index.js diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js new file mode 100644 index 000000000..154a29c35 --- /dev/null +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in 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'); + +const Promise = require('promise'); + +describe('BundlesLayout', () => { + var BundlesLayout; + var DependencyResolver; + + beforeEach(() => { + BundlesLayout = require('../index'); + DependencyResolver = require('../../DependencyResolver'); + }); + + describe('generate', () => { + function newBundlesLayout() { + return new BundlesLayout({ + dependencyResolver: new DependencyResolver(), + }); + } + + function dep(path) { + return {path}; + } + + pit('should bundle sync dependencies', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js'), dep('/root/a.js')], + ]) + ); + }); + + pit('should separate async dependencies into different bundle', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js')], + ]) + ); + }); + + pit('separate async dependencies of async dependencies', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js')], + [dep('/root/b.js')], + ]) + ); + }); + + pit('separate bundle sync dependencies of async ones on same bundle', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js')], + asyncDependencies: [['/root/a.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js'), dep('/root/b.js')], + asyncDependencies: [], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(bundles).toEqual([ + [dep('/root/index.js')], + [dep('/root/a.js'), dep('/root/b.js')], + ]) + ); + }); + }); +}); diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js new file mode 100644 index 000000000..02379ca9f --- /dev/null +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -0,0 +1,512 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('absolute-path') + .dontMock('crypto') + .dontMock('underscore') + .dontMock('../index') + .dontMock('../../lib/getAssetDataFromName') + .dontMock('../../DependencyResolver/crawlers') + .dontMock('../../DependencyResolver/crawlers/node') + .dontMock('../../DependencyResolver/DependencyGraph/docblock') + .dontMock('../../DependencyResolver/fastfs') + .dontMock('../../DependencyResolver/replacePatterns') + .dontMock('../../DependencyResolver') + .dontMock('../../DependencyResolver/DependencyGraph') + .dontMock('../../DependencyResolver/AssetModule_DEPRECATED') + .dontMock('../../DependencyResolver/AssetModule') + .dontMock('../../DependencyResolver/Module') + .dontMock('../../DependencyResolver/Package') + .dontMock('../../DependencyResolver/ModuleCache'); + +const Promise = require('promise'); + +jest.mock('fs'); + +describe('BundlesLayout', () => { + var BundlesLayout; + var Cache; + var DependencyResolver; + var fileWatcher; + var fs; + + beforeEach(() => { + fs = require('fs'); + BundlesLayout = require('../index'); + Cache = require('../../Cache'); + DependencyResolver = require('../../DependencyResolver'); + + fileWatcher = { + on: () => this, + isWatchman: () => Promise.resolve(false) + }; + }); + + describe('generate', () => { + const polyfills = [ + 'polyfills/prelude_dev.js', + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js', + ]; + + function newBundlesLayout() { + const resolver = new DependencyResolver({ + projectRoots: ['/root'], + fileWatcher: fileWatcher, + cache: new Cache(), + assetExts: ['js', 'png'], + assetRoots: ['/root'], + }); + + return new BundlesLayout({dependencyResolver: resolver}); + } + + function modulePaths(bundles) { + if (!bundles) { + return null; + } + + return bundles.map(bundle => { + return bundle + .filter(module => { // filter polyfills + for (let p of polyfills) { + if (module.id.indexOf(p) !== -1) { + return false; + } + } + return true; + }) + .map(module => module.path); + }); + } + + pit('should bundle dependant modules', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require("a");`, + 'a.js': ` + /**, + * @providesModule a + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js', '/root/a.js'], + ]) + ); + }); + + pit('should split bundles for async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ]) + ); + }); + + pit('should split into multiple bundles separate async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ['/root/b.js'], + ]) + ); + }); + + pit('should put related async dependencies into the same bundle', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a", "b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/b.js'], + ]) + ); + }); + + pit('should fully traverse sync dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require("a"); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */`, + 'b.js': ` + /** + * @providesModule b + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js', '/root/a.js'], + ['/root/b.js'], + ]) + ); + }); + + pit('should include sync dependencies async dependencies might have', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("b");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should allow duplicated dependencies across bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("c");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/c.js'], + ['/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should put in separate bundles async dependencies of async dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require.ensure(["b"]);`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js'], + ['/root/b.js', '/root/c.js'], + ]) + ); + }); + + pit('should dedup same async bundle duplicated dependencies', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a", "b"]);`, + 'a.js': ` + /**, + * @providesModule a + */, + require("c");`, + 'b.js': ` + /** + * @providesModule b + */ + require("c");`, + 'c.js': ` + /** + * @providesModule c + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/c.js', '/root/b.js'], + ]) + ); + }); + + pit('should put image dependencies into separate bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("./img.png");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ]) + ); + }); + + pit('should put image dependencies across bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]); + require.ensure(["b"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("./img.png");`, + 'b.js':` + /**, + * @providesModule b + */, + require("./img.png");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ['/root/b.js', '/root/img.png'], + ]) + ); + }); + + pit('could async require asset', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["./img.png"]);`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/img.png'], + ]) + ); + }); + + pit('should include deprecated assets into separate bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["a"]);`, + 'a.js':` + /**, + * @providesModule a + */, + require("image!img");`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/a.js', '/root/img.png'], + ]) + ); + }); + + pit('could async require deprecated asset', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["image!img"]);`, + 'img.png': '', + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/img.png'], + ]) + ); + }); + + pit('should put packages into bundles', () => { + fs.__setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */ + require.ensure(["aPackage"]);`, + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: './main.js', + browser: { + './main.js': './client.js', + }, + }), + 'main.js': 'some other code', + 'client.js': 'some code', + }, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + expect(modulePaths(bundles)).toEqual([ + ['/root/index.js'], + ['/root/aPackage/client.js'], + ]) + ); + }); + }); +}); diff --git a/packager/react-packager/src/BundlesLayout/index.js b/packager/react-packager/src/BundlesLayout/index.js new file mode 100644 index 000000000..88e616c00 --- /dev/null +++ b/packager/react-packager/src/BundlesLayout/index.js @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const _ = require('underscore'); +const declareOpts = require('../lib/declareOpts'); + +const validateOpts = declareOpts({ + dependencyResolver: { + type: 'object', + required: true, + }, +}); + +/** + * Class that takes care of separating the graph of dependencies into + * separate bundles + */ +class BundlesLayout { + constructor(options) { + const opts = validateOpts(options); + this._resolver = opts.dependencyResolver; + } + + generateLayout(entryPaths, isDev) { + const bundles = []; + var pending = [entryPaths]; + + return promiseWhile( + () => pending.length > 0, + () => bundles, + () => { + const pendingPaths = pending.shift(); + return Promise + .all(pendingPaths.map(path => + this._resolver.getDependencies(path, {dev: isDev}) + )) + .then(modulesDeps => { + let syncDependencies = Object.create(null); + modulesDeps.forEach(moduleDeps => { + moduleDeps.dependencies.forEach(dep => + syncDependencies[dep.path] = dep + ); + pending = pending.concat(moduleDeps.asyncDependencies); + }); + + syncDependencies = _.values(syncDependencies); + if (syncDependencies.length > 0) { + bundles.push(syncDependencies); + } + + return Promise.resolve(bundles); + }); + }, + ); + } +} + +// Runs the body Promise meanwhile the condition callback is satisfied. +// Once it's not satisfied anymore, it returns what the results callback +// indicates +function promiseWhile(condition, result, body) { + if (!condition()) { + return Promise.resolve(result()); + } + + return body().then(() => promiseWhile(condition, result, body)); +} + +module.exports = BundlesLayout; diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index d4cced0ca..d2f9fcf60 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -162,33 +162,7 @@ class DependencyGraph { getOrderedDependencies(entryPath) { return this.load().then(() => { - const absPath = this._getAbsolutePath(entryPath); - - if (absPath == null) { - throw new NotFoundError( - 'Could not find source file at %s', - entryPath - ); - } - - const absolutePath = path.resolve(absPath); - - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._opts.roots - ); - } - - const platformExt = getPlatformExt(entryPath); - if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { - this._platformExt = platformExt; - } else { - this._platformExt = null; - } - - const entry = this._moduleCache.getModule(absolutePath); + const entry = this._getModuleForEntryPath(entryPath); const deps = []; const visited = Object.create(null); visited[entry.hash()] = true; @@ -225,7 +199,23 @@ class DependencyGraph { }; return collect(entry) - .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))); + .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))) + .then(); + }); + } + + getAsyncDependencies(entryPath) { + return this.load().then(() => { + const mod = this._getModuleForEntryPath(entryPath); + return mod.getAsyncDependencies().then(bundles => + Promise + .all(bundles.map(bundle => + Promise.all(bundle.map( + dep => this.resolveDependency(mod, dep) + )) + )) + .then(bs => bs.map(bundle => bundle.map(dep => dep.path))) + ); }); } @@ -245,6 +235,36 @@ class DependencyGraph { return null; } + _getModuleForEntryPath(entryPath) { + const absPath = this._getAbsolutePath(entryPath); + + if (absPath == null) { + throw new NotFoundError( + 'Could not find source file at %s', + entryPath + ); + } + + const absolutePath = path.resolve(absPath); + + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._opts.roots + ); + } + + const platformExt = getPlatformExt(entryPath); + if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { + this._platformExt = platformExt; + } else { + this._platformExt = null; + } + + return this._moduleCache.getModule(absolutePath); + } + _resolveHasteDependency(fromModule, toModuleName) { toModuleName = normalizePath(toModuleName); diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js index b1fc58d6e..3b3526b6a 100644 --- a/packager/react-packager/src/DependencyResolver/Module.js +++ b/packager/react-packager/src/DependencyResolver/Module.js @@ -198,6 +198,8 @@ function extractRequires(code /*: string*/) /*: Array*/ { } }); + // TODO: throw error if there are duplicate dependencies + deps.async.push(dep); } }); diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index 33b9c781f..c4f63c1bf 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -83,22 +83,26 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load().then( - () => depGraph.getOrderedDependencies(main).then( - dependencies => { + return depGraph + .load() + .then(() => Promise.all([ + depGraph.getOrderedDependencies(main), + depGraph.getAsyncDependencies(main), + ])) + .then(([dependencies, asyncDependencies]) => { const mainModuleId = dependencies[0].id; self._prependPolyfillDependencies( dependencies, - opts.dev + opts.dev, ); return { mainModuleId: mainModuleId, - dependencies: dependencies + dependencies: dependencies, + asyncDependencies: asyncDependencies, }; } - ) - ); + ); }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( From abce124198fd2b0ee65cad5b3ddb78a9d1ab2345 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 13 Aug 2015 12:30:59 -0700 Subject: [PATCH 023/139] [react-packager] Remove horribly outdated example_project Summary: Removes some old unused code. --- .../react-packager/example_project/bar.js | 12 -- .../example_project/config.json | 10 -- .../react-packager/example_project/foo/foo.js | 30 ----- .../react-packager/example_project/index.js | 16 --- .../example_project/js/Channel.js | 53 --------- .../react-packager/example_project/js/XHR.js | 29 ----- .../react-packager/example_project/js/code.js | 58 ---------- .../react-packager/example_project/js/main.js | 64 ----------- .../example_project/public/css/index.css | 104 ------------------ .../example_project/public/index.html | 38 ------- 10 files changed, 414 deletions(-) delete mode 100644 packager/react-packager/example_project/bar.js delete mode 100644 packager/react-packager/example_project/config.json delete mode 100644 packager/react-packager/example_project/foo/foo.js delete mode 100644 packager/react-packager/example_project/index.js delete mode 100644 packager/react-packager/example_project/js/Channel.js delete mode 100644 packager/react-packager/example_project/js/XHR.js delete mode 100644 packager/react-packager/example_project/js/code.js delete mode 100644 packager/react-packager/example_project/js/main.js delete mode 100644 packager/react-packager/example_project/public/css/index.css delete mode 100644 packager/react-packager/example_project/public/index.html diff --git a/packager/react-packager/example_project/bar.js b/packager/react-packager/example_project/bar.js deleted file mode 100644 index 6653bdf7c..000000000 --- a/packager/react-packager/example_project/bar.js +++ /dev/null @@ -1,12 +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 bar - */ - - module.exports = setInterval; diff --git a/packager/react-packager/example_project/config.json b/packager/react-packager/example_project/config.json deleted file mode 100644 index 0acdcb514..000000000 --- a/packager/react-packager/example_project/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "port": 3000, - "devPort": 3001, - "publicDir": "./public", - "rootPath": "../example_project", - "moduleOptions": { - "format": "haste", - "main": "index.js" - } -} diff --git a/packager/react-packager/example_project/foo/foo.js b/packager/react-packager/example_project/foo/foo.js deleted file mode 100644 index fe3c8cd13..000000000 --- a/packager/react-packager/example_project/foo/foo.js +++ /dev/null @@ -1,30 +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 foo - */ - - -var bar = require('bar'); - -class Logger { - log() { - console.log('youll have to change me lol'); - } -} - -class SecretLogger extends Logger { - log(secret) { - console.log('logging ', secret); - } -} - -module.exports = (secret) => { - if (secret !== 'secret') throw new Error('wrong secret'); - bar(new SecretLogger().log.bind(SecretLogger, secret), 400); -}; diff --git a/packager/react-packager/example_project/index.js b/packager/react-packager/example_project/index.js deleted file mode 100644 index d63b51932..000000000 --- a/packager/react-packager/example_project/index.js +++ /dev/null @@ -1,16 +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 index - */ - -require('main'); -require('code'); - -var foo = require('foo'); -foo('secret'); diff --git a/packager/react-packager/example_project/js/Channel.js b/packager/react-packager/example_project/js/Channel.js deleted file mode 100644 index 6cbfce6f9..000000000 --- a/packager/react-packager/example_project/js/Channel.js +++ /dev/null @@ -1,53 +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 Channel - */ - -var XHR = require('XHR'); - -/** - * Client implementation of a server-push channel. - * - * @see Channel.js for full documentation - */ -var channel = null, at = null, delay = 0; -var Channel = {}; - -Channel.connect = function() { - var url = '/pull'; - if (channel) { - url += '?channel=' + channel + '&at=' + at; - } - XHR.get(url, function(err, xhr) { - if (err) { - delay = Math.min(Math.max(1000, delay * 2), 30000); - } else { - var res = xhr.responseText; - res = JSON.parse(res); - - delay = 0; - - // Cache channel state - channel = res.channel; - at = res.at; - - var messages = res.messages; - messages.forEach(function(message) { - var ev = document.createEvent('CustomEvent'); - ev.initCustomEvent(message.event, true, true, message.detail); - window.dispatchEvent(ev); - }); - } - - // Reconnect - setTimeout(Channel.connect, delay); - }); -}; - -module.exports = Channel; diff --git a/packager/react-packager/example_project/js/XHR.js b/packager/react-packager/example_project/js/XHR.js deleted file mode 100644 index bede8ca50..000000000 --- a/packager/react-packager/example_project/js/XHR.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule XHR - */ - -function request(method, url, callback) { - var xhr = new XMLHttpRequest(); - xhr.open(method, url); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - callback(null, xhr); - } else { - callback(new Error('status = ' + xhr.status, xhr)); - } - } - }; - xhr.send(); -} - -exports.get = function(url, callback) { - request('GET', url, callback); -}; diff --git a/packager/react-packager/example_project/js/code.js b/packager/react-packager/example_project/js/code.js deleted file mode 100644 index f99a90c9c..000000000 --- a/packager/react-packager/example_project/js/code.js +++ /dev/null @@ -1,58 +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 code - */ -var XHR = require('XHR'); - -var $ = function(sel) {return document.querySelector(sel);}; - -function getListItems(files) { - var items = []; - files.forEach(function(file) { - var displayName = file.name + (file.type == 1 ? '/' : ''); - items.push( - React.DOM.li({ - className: 'type' + file.type, - key: file.ino - }, displayName) - ); - if (file.type === 1) { - items.push(getListItems(file.nodes)); - } - }); - - return React.DOM.ol(null, items); -} - -var FileList = React.createClass({ - getInitialState: function() { - return {files: []}; - }, - - componentDidMount: function() { - XHR.get( - this.props.source, - function(err, xhr) { - if (err) {throw err;} - - var files = JSON.parse(xhr.responseText); - this.setState({files: files}); - }.bind(this) - ); - }, - - render: function() { - return getListItems(this.state.files); - } -}); - -window.addEventListener('load', function() { - React.render(React.createElement(FileList, {source: '/files'}), - $('#code')); -}); diff --git a/packager/react-packager/example_project/js/main.js b/packager/react-packager/example_project/js/main.js deleted file mode 100644 index 405d015e6..000000000 --- a/packager/react-packager/example_project/js/main.js +++ /dev/null @@ -1,64 +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 main - */ -var Channel = require('Channel'); - -function toArray(arr) {return Array.prototype.slice.apply(arr);} -function $(sel) {return document.querySelector(sel);} -function $$(sel) {return toArray(document.querySelectorAll(sel));} - -window.addEventListener('load', function() { - function channelLog() { - var args = Array.prototype.slice.apply(arguments); - var ts = new Date(); - var el = document.createElement('li'); - args.unshift(ts.getHours() + ':' + - ('0' + ts.getMinutes()).substr(0,2) + ':' + - ('0' + ts.getSeconds()).substr(0,2)); - el.className = 'console-entry'; - el.innerHTML = args.join(' '); - $('#console').appendChild(el); - el.scrollIntoView(); - } - - global.addEventListener('ChannelInit', function(event) { - $('#console').innerHTML = ''; - channelLog(event.type); - }); - - global.addEventListener('ChannelLog', function(event) { - channelLog.apply(null, event.detail); - }); - - // Tab pane support - function showTab(paneId) { - paneId = paneId.replace(/\W/g, ''); - if (paneId) { - $$('#nav-panes > div').forEach(function(pane) { - pane.classList.toggle('active', pane.id === paneId); - }); - $$('#nav-tabs li').forEach(function(tab) { - tab.classList.toggle('active', - tab.getAttribute('data-pane') === paneId); - }); - global.history.replaceState(null, null, '#' + paneId); - } - } - - $('#nav-tabs').onclick = function(e) { - showTab(e.target.getAttribute('data-pane')); - }; - - // Show current pane - showTab(location.hash); - - // Connect to server-push channel - Channel.connect(); -}); diff --git a/packager/react-packager/example_project/public/css/index.css b/packager/react-packager/example_project/public/css/index.css deleted file mode 100644 index 651f33263..000000000 --- a/packager/react-packager/example_project/public/css/index.css +++ /dev/null @@ -1,104 +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. - */ - - -html { - font-family: sans-serif; -} -body { - margin-right: 200px -} - -#nav-tabs { - margin: 0; - padding: 0; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - background-color: #eee; - border-bottom: solid 1px black; - font-size: 10pt; - font-weight: bold; - vertical-align: bottom; - line-height: 20px; - height: 29px; -} -#nav-tabs li { - padding: 0 10px; - margin: 0; - border-bottom-width: 0; - display:inline-block; - cursor: pointer; - line-height: 29px; -} -#nav-tabs li:first-child { - color: #666; -} -#nav-tabs li.active { - background-color: #fff; -} - -#nav-panes { - position: absolute; - top: 30px; - left: 0px; - right: 0px; - bottom: 0px; - scroll: auto; - overflow: auto; - background-color: #fff; -} - -#nav-panes .pane { - display: none; -} -#nav-panes .active { - display: block; -} - -.pane { - padding: 10px; -} - -#console { - padding-left: 5px; -} -#console li { - font-size: 10pt; - font-family: monospace; - white-space: nowrap; - margin: 0; - list-style: none; -} - -#code > ol { - font-size: 10pt; - font-family: monospace; - margin: 0; - padding: 0; - cursor: pointer; -} -#code ol ol { - margin-left: 1em; - padding-left: 1em; - border-left: dashed 1px #ddd; -} -#code li { - color: #000; - font-weight: normal; - list-style: none; - line-height: 1.2em; -} -#code .type1 { - color: #009; -} -#code .type2 { - color: #909; -} diff --git a/packager/react-packager/example_project/public/index.html b/packager/react-packager/example_project/public/index.html deleted file mode 100644 index e0e2ce7fb..000000000 --- a/packager/react-packager/example_project/public/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - From 7bfc1baf7f9eaa89108a3850a87b0ecf4722af02 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 13 Aug 2015 14:12:09 -0700 Subject: [PATCH 024/139] [react-packager] Upgrade uglifyjs for Max call stack bug Summary: We're hitting an issue with large code size and `Maximum call stack size exceeded` error. https://github.com/mishoo/UglifyJS2/issues/414 We're seeing on inconsistently failing on landcastle. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d7a1be91..07698215a 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "semver": "^4.3.6", "source-map": "0.1.31", "stacktrace-parser": "0.1.2", - "uglify-js": "2.4.16", + "uglify-js": "2.4.24", "underscore": "1.7.0", "wordwrap": "^1.0.0", "worker-farm": "^1.3.1", From bc28a35bda0e358a8296a7b53eb7315b736c6e4b Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 13 Aug 2015 15:30:02 -0700 Subject: [PATCH 025/139] [react-packager] Use module objects across the codebase (rid of getPlainObject etc) Summary: Instead of using plain objects and having to convert to and from them we just use the `Module` class across the codebase. This seems cleaner and can enforce the type as opposed to fuzzy objects. --- .../src/Bundler/__tests__/Bundler-test.js | 79 ++++---- packager/react-packager/src/Bundler/index.js | 45 ++--- .../BundlesLayoutIntegration-test.js | 28 +-- .../src/DependencyResolver/AssetModule.js | 35 ++-- .../AssetModule_DEPRECATED.js | 37 ++-- .../__tests__/DependencyGraph-test.js | 152 +++++++------- .../DependencyGraph/index.js | 7 +- .../src/DependencyResolver/Module.js | 33 ++-- .../src/DependencyResolver/Polyfill.js | 38 ++++ .../__tests__/HasteDependencyResolver-test.js | 185 +++--------------- .../src/DependencyResolver/index.js | 67 ++++--- 11 files changed, 310 insertions(+), 396 deletions(-) create mode 100644 packager/react-packager/src/DependencyResolver/Polyfill.js diff --git a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js index a66bc0592..20bb1d4a4 100644 --- a/packager/react-packager/src/Bundler/__tests__/Bundler-test.js +++ b/packager/react-packager/src/Bundler/__tests__/Bundler-test.js @@ -58,29 +58,50 @@ describe('Bundler', function() { assetServer: assetServer, }); + + function createModule({ + path, + id, + dependencies, + isAsset, + isAsset_DEPRECATED, + isJSON, + resolution, + }) { + return { + path, + resolution, + getDependencies() { return Promise.resolve(dependencies); }, + getName() { return Promise.resolve(id); }, + isJSON() { return isJSON; }, + isAsset() { return isAsset; }, + isAsset_DEPRECATED() { return isAsset_DEPRECATED; }, + }; + } + modules = [ - {id: 'foo', path: '/root/foo.js', dependencies: []}, - {id: 'bar', path: '/root/bar.js', dependencies: []}, - { - id: 'image!img', + createModule({id: 'foo', path: '/root/foo.js', dependencies: []}), + createModule({id: 'bar', path: '/root/bar.js', dependencies: []}), + createModule({ path: '/root/img/img.png', + id: 'image!img', isAsset_DEPRECATED: true, dependencies: [], resolution: 2, - }, - { + }), + createModule({ id: 'new_image.png', path: '/root/img/new_image.png', isAsset: true, resolution: 2, dependencies: [] - }, - { + }), + createModule({ id: 'package/file.json', path: '/root/file.json', isJSON: true, dependencies: [], - }, + }), ]; getDependencies.mockImpl(function() { @@ -203,41 +224,11 @@ describe('Bundler', function() { }); }); - pit('gets the list of dependencies', function() { + pit('gets the list of dependencies from the resolver', function() { return bundler.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', - }, - ]); - }); + .then( + () => expect(getDependencies) + .toBeCalledWith('/root/foo.js', { dev: true }) + ); }); }); diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index e7ea12496..4c60924b8 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -125,26 +125,24 @@ class Bundler { const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - return this.getDependencies(main, isDev) - .then(function(result) { - Activity.endEvent(findEventId); - transformEventId = Activity.startEvent('transform'); + return this.getDependencies(main, isDev).then((result) => { + Activity.endEvent(findEventId); + transformEventId = Activity.startEvent('transform'); - bundle.setMainModuleId(result.mainModuleId); - return Promise.all( - result.dependencies.map(transformModule) - ); - }) - .then(function(transformedModules) { - Activity.endEvent(transformEventId); + bundle.setMainModuleId(result.mainModuleId); + return Promise.all( + result.dependencies.map(transformModule) + ); + }).then((transformedModules) => { + Activity.endEvent(transformEventId); - transformedModules.forEach(function(moduleTransport) { - bundle.addModule(moduleTransport); - }); + transformedModules.forEach(function(moduleTransport) { + bundle.addModule(moduleTransport); + }); - bundle.finalize({ runMainModule: runModule }); - return bundle; - }); + bundle.finalize({ runMainModule: runModule }); + return bundle; + }); } invalidateFile(filePath) { @@ -158,11 +156,11 @@ class Bundler { _transformModule(bundle, module) { let transform; - if (module.isAsset_DEPRECATED) { + if (module.isAsset_DEPRECATED()) { transform = this.generateAssetModule_DEPRECATED(bundle, module); - } else if (module.isAsset) { + } else if (module.isAsset()) { transform = this.generateAssetModule(bundle, module); - } else if (module.isJSON) { + } else if (module.isJSON()) { transform = generateJSONModule(module); } else { transform = this._transformer.loadFileAndTransform( @@ -189,12 +187,15 @@ class Bundler { } generateAssetModule_DEPRECATED(bundle, module) { - return sizeOf(module.path).then(function(dimensions) { + return Promise.all([ + sizeOf(module.path), + module.getName(), + ]).then(([dimensions, id]) => { const img = { __packager_asset: true, isStatic: true, path: module.path, - uri: module.id.replace(/^[^!]+!/, ''), + uri: id.replace(/^[^!]+!/, ''), width: dimensions.width / module.resolution, height: dimensions.height / module.resolution, deprecated: true, diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 02379ca9f..672829c2b 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -25,6 +25,7 @@ jest .dontMock('../../DependencyResolver/AssetModule') .dontMock('../../DependencyResolver/Module') .dontMock('../../DependencyResolver/Package') + .dontMock('../../DependencyResolver/Polyfill') .dontMock('../../DependencyResolver/ModuleCache'); const Promise = require('promise'); @@ -51,17 +52,6 @@ describe('BundlesLayout', () => { }); describe('generate', () => { - const polyfills = [ - 'polyfills/prelude_dev.js', - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - ]; - function newBundlesLayout() { const resolver = new DependencyResolver({ projectRoots: ['/root'], @@ -79,18 +69,10 @@ describe('BundlesLayout', () => { return null; } - return bundles.map(bundle => { - return bundle - .filter(module => { // filter polyfills - for (let p of polyfills) { - if (module.id.indexOf(p) !== -1) { - return false; - } - } - return true; - }) - .map(module => module.path); - }); + return bundles.map( + bundle => bundle.filter(module => !module.isPolyfill()) + .map(module => module.path) + ); } pit('should bundle dependant modules', () => { diff --git a/packager/react-packager/src/DependencyResolver/AssetModule.js b/packager/react-packager/src/DependencyResolver/AssetModule.js index 7a45addb1..2e1031599 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule.js @@ -5,6 +5,13 @@ const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule extends Module { + constructor(...args) { + super(...args); + const { resolution, name, type } = getAssetDataFromName(this.path); + this.resolution = resolution; + this._name = name; + this._type = type; + } isHaste() { return Promise.resolve(false); @@ -23,28 +30,22 @@ class AssetModule extends Module { } 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: [], - })); + return super.getName().then( + id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`) + ); } hash() { return `AssetModule : ${this.path}`; } + + isJSON() { + return false; + } + + isAsset() { + return true; + } } module.exports = AssetModule; diff --git a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js index 2adb73d58..19817d4b7 100644 --- a/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js +++ b/packager/react-packager/src/DependencyResolver/AssetModule_DEPRECATED.js @@ -5,12 +5,19 @@ const Promise = require('promise'); const getAssetDataFromName = require('../lib/getAssetDataFromName'); class AssetModule_DEPRECATED extends Module { + constructor(...args) { + super(...args); + const {resolution, name} = getAssetDataFromName(this.path); + this.resolution = resolution; + this.name = name; + } + isHaste() { return Promise.resolve(false); } getName() { - return Promise.resolve(this.name); + return Promise.resolve(`image!${this.name}`); } getDependencies() { @@ -21,24 +28,22 @@ class AssetModule_DEPRECATED extends Module { 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}`; } + + isJSON() { + return false; + } + + isAsset_DEPRECATED() { + return true; + } + + resolution() { + return getAssetDataFromName(this.path).resolution; + } + } module.exports = AssetModule_DEPRECATED; diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index 595b1f7cf..bf4f65964 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -35,6 +35,24 @@ describe('DependencyGraph', function() { var fileWatcher; var fs; + function getOrderedDependenciesAsJSON(dgraph, entry) { + return dgraph.getOrderedDependencies(entry).then( + deps => Promise.all(deps.map(dep => Promise.all([ + dep.getName(), + dep.getDependencies(), + ]).then(([name, dependencies]) => ({ + path: dep.path, + isJSON: dep.isJSON(), + isAsset: dep.isAsset(), + isAsset_DEPRECATED: dep.isAsset_DEPRECATED(), + isPolyfill: dep.isPolyfill(), + resolution: dep.resolution, + id: name, + dependencies + }))) + )); + } + beforeEach(function() { fs = require('fs'); Cache = require('../../../Cache'); @@ -75,7 +93,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -133,7 +151,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -185,7 +203,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -245,7 +263,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: ['/root/imgs'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -297,7 +315,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -354,7 +372,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -432,7 +450,7 @@ describe('DependencyGraph', function() { assetRoots_DEPRECATED: ['/root/imgs'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -494,7 +512,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -547,7 +565,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -600,7 +618,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -661,7 +679,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -718,7 +736,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -769,7 +787,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -819,7 +837,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -866,7 +884,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -917,7 +935,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -966,7 +984,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1020,7 +1038,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/somedir/somefile.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1078,7 +1096,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1126,7 +1144,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1173,7 +1191,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1232,7 +1250,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1291,7 +1309,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1370,7 +1388,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1427,7 +1445,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1484,7 +1502,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1541,7 +1559,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1613,7 +1631,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1716,7 +1734,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -1797,7 +1815,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1879,7 +1897,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -1962,7 +1980,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2059,7 +2077,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2145,7 +2163,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2250,7 +2268,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2319,7 +2337,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/react-tools/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2376,7 +2394,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2421,7 +2439,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2482,7 +2500,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2538,7 +2556,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2587,7 +2605,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2671,11 +2689,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2736,11 +2754,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2801,10 +2819,10 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2865,7 +2883,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2877,7 +2895,7 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2948,7 +2966,7 @@ describe('DependencyGraph', function() { cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -2967,7 +2985,7 @@ describe('DependencyGraph', function() { filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3020,7 +3038,7 @@ describe('DependencyGraph', function() { cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { id: 'index', @@ -3038,7 +3056,7 @@ describe('DependencyGraph', function() { filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3107,7 +3125,7 @@ describe('DependencyGraph', function() { }, cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -3119,7 +3137,7 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root, mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3192,11 +3210,11 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3263,7 +3281,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); triggerFileChange('change', 'index.js', root, mockStat); @@ -3273,7 +3291,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3330,7 +3348,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ name: 'aPackage', main: 'main.js', @@ -3338,7 +3356,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3395,14 +3413,14 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() { filesystem.root.aPackage['package.json'] = JSON.stringify({ name: 'bPackage', main: 'main.js', }); triggerFileChange('change', 'package.json', '/root/aPackage', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3458,7 +3476,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { expect(deps) .toEqual([ { @@ -3499,7 +3517,7 @@ describe('DependencyGraph', function() { 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) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { @@ -3558,7 +3576,7 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], cache: cache, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ name: 'foo', main: 'main.js', @@ -3566,7 +3584,7 @@ describe('DependencyGraph', function() { }); triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) { expect(deps2) .toEqual([ { diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index d2f9fcf60..3825507c9 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -110,10 +110,6 @@ class DependencyGraph { } resolveDependency(fromModule, toModuleName) { - if (fromModule._ref) { - fromModule = fromModule._ref; - } - const resHash = resolutionHash(fromModule.path, toModuleName); if (this._immediateResolutionCache[resHash]) { @@ -199,8 +195,7 @@ class DependencyGraph { }; return collect(entry) - .then(() => Promise.all(deps.map(dep => dep.getPlainObject()))) - .then(); + .then(() => deps); }); } diff --git a/packager/react-packager/src/DependencyResolver/Module.js b/packager/react-packager/src/DependencyResolver/Module.js index 3b3526b6a..f7ae802a8 100644 --- a/packager/react-packager/src/DependencyResolver/Module.js +++ b/packager/react-packager/src/DependencyResolver/Module.js @@ -109,29 +109,24 @@ class Module { 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; + isJSON() { + return path.extname(this.path) === '.json'; + } + + isAsset() { + return false; + } + + isPolyfill() { + return false; + } + + isAsset_DEPRECATED() { + return false; } } diff --git a/packager/react-packager/src/DependencyResolver/Polyfill.js b/packager/react-packager/src/DependencyResolver/Polyfill.js new file mode 100644 index 000000000..752b864b8 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/Polyfill.js @@ -0,0 +1,38 @@ +'use strict'; + +const Promise = require('promise'); +const Module = require('./Module'); + +class Polyfill extends Module { + constructor({ path, id, dependencies }) { + super(path); + this._id = id; + this._dependencies = dependencies; + } + + isHaste() { + return Promise.resolve(false); + } + + getName() { + return Promise.resolve(this._id); + } + + getPackage() { + return null; + } + + getDependencies() { + return Promise.resolve(this._dependencies); + } + + isJSON() { + return false; + } + + isPolyfill() { + return true; + } +} + +module.exports = Polyfill; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js index 3242c6771..be71b3a00 100644 --- a/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/__tests__/HasteDependencyResolver-test.js @@ -9,22 +9,24 @@ 'use strict'; jest.dontMock('../') - .dontMock('q') + .dontMock('underscore') .dontMock('../replacePatterns'); jest.mock('path'); var Promise = require('promise'); +var _ = require('underscore'); describe('HasteDependencyResolver', function() { var HasteDependencyResolver; - - function createModule(o) { - o.getPlainObject = () => Promise.resolve(o); - return o; - } + var Module; + var Polyfill; beforeEach(function() { + Module = require('../Module'); + Polyfill = require('../Polyfill'); + Polyfill.mockClear(); + // For the polyfillDeps require('path').join.mockImpl(function(a, b) { return b; @@ -32,12 +34,16 @@ describe('HasteDependencyResolver', function() { HasteDependencyResolver = require('../'); }); + function createModule(id, dependencies) { + var module = new Module(); + module.getName.mockImpl(() => Promise.resolve(id)); + module.getDependencies.mockImpl(() => Promise.resolve(dependencies)); + return module; + } + describe('getDependencies', function() { pit('should get dependencies with polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', dependencies: ['a'] - }); + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -56,7 +62,8 @@ describe('HasteDependencyResolver', function() { return depResolver.getDependencies('/root/index.js', { dev: false }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ + expect(result.dependencies[result.dependencies.length - 1]).toBe(module); + expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([ { path: 'polyfills/prelude.js', id: 'polyfills/prelude.js', isPolyfill: true, @@ -114,18 +121,12 @@ describe('HasteDependencyResolver', function() { 'polyfills/String.prototype.es6.js', ], }, - module ]); }); }); pit('should get dependencies with polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - }); - + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -144,75 +145,15 @@ describe('HasteDependencyResolver', function() { return depResolver.getDependencies('/root/index.js', { dev: true }) .then(function(result) { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ - { path: 'polyfills/prelude_dev.js', - id: 'polyfills/prelude_dev.js', - isPolyfill: true, - dependencies: [] - }, - { path: 'polyfills/require.js', - id: 'polyfills/require.js', - isPolyfill: true, - dependencies: ['polyfills/prelude_dev.js'] - }, - { path: 'polyfills/polyfills.js', - id: 'polyfills/polyfills.js', - isPolyfill: true, - dependencies: ['polyfills/prelude_dev.js', 'polyfills/require.js'] - }, - { id: 'polyfills/console.js', - isPolyfill: true, - path: 'polyfills/console.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js' - ], - }, - { id: 'polyfills/error-guard.js', - isPolyfill: true, - path: 'polyfills/error-guard.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js' - ], - }, - { id: 'polyfills/String.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/String.prototype.es6.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js' - ], - }, - { id: 'polyfills/Array.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/Array.prototype.es6.js', - dependencies: [ - 'polyfills/prelude_dev.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js' - ], - }, - module - ]); + expect(depGraph.getOrderedDependencies).toBeCalledWith('/root/index.js'); + expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]); + expect(result.dependencies[result.dependencies.length - 1]) + .toBe(module); }); }); pit('should pass in more polyfills', function() { - var module = createModule({ - id: 'index', - path: '/root/index.js', - dependencies: ['a'] - }); + var module = createModule('index'); var deps = [module]; var depResolver = new HasteDependencyResolver({ @@ -230,66 +171,9 @@ describe('HasteDependencyResolver', function() { }); return depResolver.getDependencies('/root/index.js', { dev: false }) - .then(function(result) { + .then((result) => { expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies).toEqual([ - { path: 'polyfills/prelude.js', - id: 'polyfills/prelude.js', - isPolyfill: true, - dependencies: [] - }, - { path: 'polyfills/require.js', - id: 'polyfills/require.js', - isPolyfill: true, - dependencies: ['polyfills/prelude.js'] - }, - { path: 'polyfills/polyfills.js', - id: 'polyfills/polyfills.js', - isPolyfill: true, - dependencies: ['polyfills/prelude.js', 'polyfills/require.js'] - }, - { id: 'polyfills/console.js', - isPolyfill: true, - path: 'polyfills/console.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js' - ], - }, - { id: 'polyfills/error-guard.js', - isPolyfill: true, - path: 'polyfills/error-guard.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js' - ], - }, - { id: 'polyfills/String.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/String.prototype.es6.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js' - ], - }, - { id: 'polyfills/Array.prototype.es6.js', - isPolyfill: true, - path: 'polyfills/Array.prototype.es6.js', - dependencies: [ - 'polyfills/prelude.js', - 'polyfills/require.js', - 'polyfills/polyfills.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/String.prototype.es6.js', - ], - }, + expect(Polyfill.mock.calls[result.dependencies.length - 2]).toEqual([ { path: 'some module', id: 'some module', isPolyfill: true, @@ -303,7 +187,6 @@ describe('HasteDependencyResolver', function() { 'polyfills/Array.prototype.es6.js' ] }, - module ]); }); }); @@ -462,22 +345,18 @@ describe('HasteDependencyResolver', function() { depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) { if (toModuleName === 'x') { - return Promise.resolve(createModule({ - id: 'changed' - })); + return Promise.resolve(createModule('changed')); } else if (toModuleName === 'y') { - return Promise.resolve(createModule({ id: 'Y' })); + return Promise.resolve(createModule('Y')); } return Promise.resolve(null); }); - return depResolver.wrapModule({ - id: 'test module', - path: '/root/test.js', - dependencies: dependencies - }, code).then(processedCode => { - + return depResolver.wrapModule( + createModule('test module', ['x', 'y']), + code + ).then(processedCode => { expect(processedCode).toEqual([ '__d(\'test module\',["changed","Y"],function(global, require,' + ' module, exports) { ' + diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index c4f63c1bf..3fdb19fb7 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -11,6 +11,7 @@ var path = require('path'); var DependencyGraph = require('./DependencyGraph'); var replacePatterns = require('./replacePatterns'); +var Polyfill = require('./Polyfill'); var declareOpts = require('../lib/declareOpts'); var Promise = require('promise'); @@ -83,25 +84,28 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; + return depGraph .load() .then(() => Promise.all([ depGraph.getOrderedDependencies(main), depGraph.getAsyncDependencies(main), ])) - .then(([dependencies, asyncDependencies]) => { - const mainModuleId = dependencies[0].id; - self._prependPolyfillDependencies( - dependencies, - opts.dev, - ); + .then( + ([dependencies, asyncDependencies]) => dependencies[0].getName().then( + mainModuleId => { + self._prependPolyfillDependencies( + dependencies, + opts.dev, + ); - return { - mainModuleId: mainModuleId, - dependencies: dependencies, - asyncDependencies: asyncDependencies, - }; - } + return { + mainModuleId, + dependencies, + asyncDependencies, + }; + } + ) ); }; @@ -122,7 +126,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( ].concat(this._polyfillModuleNames); var polyfillModules = polyfillModuleNames.map( - (polyfillModuleName, idx) => ({ + (polyfillModuleName, idx) => new Polyfill({ path: polyfillModuleName, id: polyfillModuleName, dependencies: polyfillModuleNames.slice(0, idx), @@ -134,23 +138,26 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function( }; HasteDependencyResolver.prototype.wrapModule = function(module, code) { - if (module.isPolyfill) { + 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); - } - })); - }) + return module.getDependencies().then( + dependencies => Promise.all(dependencies.map( + depName => this._depGraph.resolveDependency(module, depName) + .then(depModule => { + if (depModule) { + return depModule.getName().then(name => { + resolvedDeps[depName] = name; + resolvedDepsArr.push(name); + }); + } + }) + ) + ) ).then(() => { const relativizeCode = (codeMatch, pre, quot, depName, post) => { const depId = resolvedDeps[depName]; @@ -161,13 +168,15 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { } }; - return defineModuleCode({ - code: code + return module.getName().then( + name => defineModuleCode({ + code: code .replace(replacePatterns.IMPORT_RE, relativizeCode) .replace(replacePatterns.REQUIRE_RE, relativizeCode), - deps: JSON.stringify(resolvedDepsArr), - moduleName: module.id, - }); + deps: JSON.stringify(resolvedDepsArr), + moduleName: name, + }) + ); }); }; From f5d68f6ecb0499229185120fd1fe747c1142a2b4 Mon Sep 17 00:00:00 2001 From: Zack Gomez Date: Thu, 13 Aug 2015 17:59:50 -0700 Subject: [PATCH 026/139] [ReactNative] Fix pixel rounding Summary: Too many rounds, we only want/need one. --- React/Views/RCTShadowView.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 4defde806..981473020 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -133,13 +133,13 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st _layoutLifecycle = RCTUpdateLifecycleComputed; CGPoint absoluteTopLeft = { - RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT]), - RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP]) + absolutePosition.x + node->layout.position[CSS_LEFT], + absolutePosition.y + node->layout.position[CSS_TOP] }; CGPoint absoluteBottomRight = { - RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH]), - RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT]) + absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH], + absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT] }; CGRect frame = {{ From fa07736ee1dcd885b7f940468175eef6073f0a63 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 14 Aug 2015 01:59:42 -0700 Subject: [PATCH 027/139] Remove isValid from RCTInvalidating Summary: We only actually use it on RCTBridge and RCTJavaScriptExecutor, so add it to these interfaces explicitly --- .../UIExplorerUnitTests/RCTAllocationTests.m | 7 ++++--- React/Base/RCTBatchedBridge.m | 3 +-- React/Base/RCTBridge.h | 5 +++++ React/Base/RCTBridge.m | 7 +------ React/Base/RCTInvalidating.h | 5 ----- React/Base/RCTJavaScriptExecutor.h | 5 +++++ React/Base/RCTRootView.m | 7 +------ React/Modules/RCTAsyncLocalStorage.h | 2 ++ React/Modules/RCTAsyncLocalStorage.m | 1 + React/Modules/RCTDevMenu.h | 1 - React/Modules/RCTDevMenu.m | 5 ----- React/Modules/RCTTiming.m | 7 +------ React/Modules/RCTUIManager.m | 13 ++++--------- React/Views/RCTNavigator.h | 1 - 14 files changed, 25 insertions(+), 44 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index c3a626552..7e91ae015 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -42,15 +42,16 @@ _Pragma("clang diagnostic pop") @end @interface AllocationTestModule : NSObject + +@property (nonatomic, assign, getter=isValid) BOOL valid; + @end @implementation AllocationTestModule RCT_EXPORT_MODULE(); -@synthesize valid = _valid; - -- (id)init +- (instancetype)init { if ((self = [super init])) { _valid = YES; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 108b80cca..7af103b31 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -62,6 +62,7 @@ id RCTGetLatestExecutor(void) @implementation RCTBatchedBridge { BOOL _loading; + BOOL _valid; __weak id _javaScriptExecutor; NSMutableArray *_moduleDataByID; RCTModuleMap *_modulesByName; @@ -72,8 +73,6 @@ id RCTGetLatestExecutor(void) RCTSparseArray *_scheduledCallbacks; } -@synthesize valid = _valid; - - (instancetype)initWithParentBridge:(RCTBridge *)bridge { RCTAssertMainThread(); diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index dce1c1f5f..88bcf8971 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -145,6 +145,11 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass); */ @property (nonatomic, readonly, getter=isLoading) BOOL loading; +/** + * Use this to check if the bridge has been invalidated. + */ +@property (nonatomic, readonly, getter=isValid) BOOL valid; + /** * The block passed in the constructor with pre-initialized modules */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index de6caaf70..fd06342b3 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -244,11 +244,6 @@ RCT_NOT_IMPLEMENTED(-init) return _batchedBridge.loading; } -- (BOOL)isValid -{ - return _batchedBridge.isValid; -} - - (void)invalidate { RCTAssertMainThread(); @@ -260,7 +255,7 @@ RCT_NOT_IMPLEMENTED(-init) + (void)logMessage:(NSString *)message level:(NSString *)level { dispatch_async(dispatch_get_main_queue(), ^{ - if (!RCTGetLatestExecutor().isValid) { + if (![RCTGetLatestExecutor() isValid]) { return; } diff --git a/React/Base/RCTInvalidating.h b/React/Base/RCTInvalidating.h index e961a3979..1c8cf7920 100644 --- a/React/Base/RCTInvalidating.h +++ b/React/Base/RCTInvalidating.h @@ -9,13 +9,8 @@ #import -// TODO (#5906496): This protocol is only used to add method definitions to -// classes. We should decide if it's actually necessary. - @protocol RCTInvalidating -@property (nonatomic, assign, readonly, getter = isValid) BOOL valid; - - (void)invalidate; @end diff --git a/React/Base/RCTJavaScriptExecutor.h b/React/Base/RCTJavaScriptExecutor.h index af4b4cef6..87d4dda78 100644 --- a/React/Base/RCTJavaScriptExecutor.h +++ b/React/Base/RCTJavaScriptExecutor.h @@ -29,6 +29,11 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error); */ - (void)setUp; +/** + * Whether the executor has been invalidated + */ +@property (nonatomic, readonly, getter=isValid) BOOL valid; + /** * Executes given method with arguments on JS thread and calls the given callback * with JSValue and JSContext as a result of the JS module call. diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 0cdd1b78c..a804c810b 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -292,14 +292,9 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) [_bridge.uiManager registerRootView:self]; } -- (BOOL)isValid -{ - return self.userInteractionEnabled; -} - - (void)invalidate { - if (self.isValid) { + if (self.userInteractionEnabled) { self.userInteractionEnabled = NO; [(RCTRootView *)self.superview contentViewInvalidated]; [_bridge enqueueJSCall:@"ReactNative.unmountComponentAtNodeAndRemoveContainer" diff --git a/React/Modules/RCTAsyncLocalStorage.h b/React/Modules/RCTAsyncLocalStorage.h index 4fd1064ad..e7e871b02 100644 --- a/React/Modules/RCTAsyncLocalStorage.h +++ b/React/Modules/RCTAsyncLocalStorage.h @@ -25,6 +25,8 @@ @property (nonatomic, assign) BOOL clearOnInvalidate; +@property (nonatomic, readonly, getter=isValid) BOOL valid; + - (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback; - (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback; - (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback; diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 8300cc869..923a3a14a 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -164,6 +164,7 @@ RCT_EXPORT_MODULE() _manifest = [[NSMutableDictionary alloc] init]; _haveSetup = NO; } + - (BOOL)isValid { return _haveSetup; diff --git a/React/Modules/RCTDevMenu.h b/React/Modules/RCTDevMenu.h index ed1ff90b8..13ddb0689 100644 --- a/React/Modules/RCTDevMenu.h +++ b/React/Modules/RCTDevMenu.h @@ -11,7 +11,6 @@ #import "RCTBridge.h" #import "RCTBridgeModule.h" -#import "RCTInvalidating.h" /** * Developer menu, useful for exposing extra functionality when debugging. diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index e8762a62d..32f3946b1 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -223,11 +223,6 @@ RCT_EXPORT_MODULE() }); } -- (BOOL)isValid -{ - return NO; -} - - (void)dealloc { [_updateTask cancel]; diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index 29e3b3f92..a776c58dc 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -112,11 +112,6 @@ RCT_EXPORT_MODULE() return RCTJSThread; } -- (BOOL)isValid -{ - return _bridge != nil; -} - - (void)invalidate { [self stopTimers]; @@ -130,7 +125,7 @@ RCT_EXPORT_MODULE() - (void)startTimers { - if (![self isValid] || _timers.count == 0) { + if (!_bridge || _timers.count == 0) { return; } diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 0a8ac410c..19d478284 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -253,11 +253,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); }); } -- (BOOL)isValid -{ - return _viewRegistry != nil; -} - - (void)invalidate { /** @@ -325,7 +320,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); __weak RCTUIManager *weakSelf = self; dispatch_async(_shadowQueue, ^{ RCTUIManager *strongSelf = weakSelf; - if (!strongSelf.isValid) { + if (!_viewRegistry) { return; } RCTShadowView *shadowView = [[RCTShadowView alloc] init]; @@ -369,7 +364,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); __weak RCTUIManager *weakSelf = self; dispatch_async(_shadowQueue, ^{ RCTUIManager *strongSelf = weakSelf; - if (!strongSelf.isValid) { + if (!_viewRegistry) { return; } RCTShadowView *rootShadowView = strongSelf->_shadowViewRegistry[reactTag]; @@ -410,14 +405,14 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); return; } - if (!self.isValid) { + if (!_viewRegistry) { return; } __weak RCTUIManager *weakViewManager = self; dispatch_block_t outerBlock = ^{ RCTUIManager *strongViewManager = weakViewManager; - if (strongViewManager && strongViewManager.isValid) { + if (strongViewManager && strongViewManager->_viewRegistry) { block(strongViewManager, strongViewManager->_viewRegistry); } }; diff --git a/React/Views/RCTNavigator.h b/React/Views/RCTNavigator.h index c59c9a3d3..a1905a87d 100644 --- a/React/Views/RCTNavigator.h +++ b/React/Views/RCTNavigator.h @@ -10,7 +10,6 @@ #import #import "RCTFrameUpdate.h" -#import "RCTInvalidating.h" @class RCTBridge; From e9735556f660838c1347ee66ba97d9abed0df695 Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Fri, 14 Aug 2015 02:36:00 -0700 Subject: [PATCH 028/139] [ReactNative] Sync [react_native] Fix keyboard behavior for android --- Libraries/Components/ScrollResponder.js | 4 ++-- Libraries/Components/ScrollView/ScrollView.js | 21 ++++++++----------- Libraries/Utilities/dismissKeyboard.js | 16 ++++++++++++++ 3 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 Libraries/Utilities/dismissKeyboard.js diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 920b56478..b0f132075 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -181,7 +181,7 @@ var ScrollResponderMixin = { scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean { // First see if we want to eat taps while the keyboard is up var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (!this.props.keyboardShouldPersistTaps && + if (this.props.keyboardShouldPersistTaps === false && currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput) { return true; @@ -242,7 +242,7 @@ var ScrollResponderMixin = { // By default scroll views will unfocus a textField // if another touch occurs outside of it var currentlyFocusedTextInput = TextInputState.currentlyFocusedField(); - if (!this.props.keyboardShouldPersistTaps && + if (this.props.keyboardShouldPersistTaps === false && currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput && !this.state.observedScrollSinceBecomingResponder && diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 765934bfc..1ee37a84f 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -15,7 +15,6 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType'); var Platform = require('Platform'); var PointPropType = require('PointPropType'); var RCTScrollView = require('NativeModules').UIManager.RCTScrollView; -var RCTScrollViewConsts = RCTScrollView.Constants; var React = require('React'); var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); var RCTUIManager = require('NativeModules').UIManager; @@ -27,6 +26,7 @@ var ViewStylePropTypes = require('ViewStylePropTypes'); var createReactNativeComponentClass = require('createReactNativeComponentClass'); var deepDiffer = require('deepDiffer'); +var dismissKeyboard = require('dismissKeyboard'); var flattenStyle = require('flattenStyle'); var insetsDiffer = require('insetsDiffer'); var invariant = require('invariant'); @@ -156,9 +156,9 @@ var ScrollView = React.createClass({ * Determines whether the keyboard gets dismissed in response to a drag. * - 'none' (the default), drags do not dismiss the keyboard. * - 'on-drag', the keyboard is dismissed when a drag begins. - * - 'interactive', the keyboard is dismissed interactively with the drag - * and moves in synchrony with the touch; dragging upwards cancels the - * dismissal. + * - 'interactive', the keyboard is dismissed interactively with the drag and moves in + * synchrony with the touch; dragging upwards cancels the dismissal. + * On android this is not supported and it will have the same behavior as 'none'. */ keyboardDismissMode: PropTypes.oneOf([ 'none', // default @@ -170,7 +170,6 @@ var ScrollView = React.createClass({ * is up dismisses the keyboard. When true, the scroll view will not catch * taps, and the keyboard will not dismiss automatically. The default value * is false. - * @platform ios */ keyboardShouldPersistTaps: PropTypes.bool, /** @@ -310,6 +309,11 @@ var ScrollView = React.createClass({ ); } } + if (Platform.OS === 'android') { + if (this.props.keyboardDismissMode === 'on-drag') { + dismissKeyboard(); + } + } this.scrollResponderHandleScroll(e); }, @@ -380,13 +384,6 @@ var ScrollView = React.createClass({ } else { ScrollViewClass = AndroidScrollView; } - var keyboardDismissModeConstants = { - 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default - 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive, - 'on-drag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag, - }; - props.keyboardDismissMode = props.keyboardDismissMode ? - keyboardDismissModeConstants[props.keyboardDismissMode] : undefined; } invariant( ScrollViewClass !== undefined, diff --git a/Libraries/Utilities/dismissKeyboard.js b/Libraries/Utilities/dismissKeyboard.js new file mode 100644 index 000000000..d8949e8b3 --- /dev/null +++ b/Libraries/Utilities/dismissKeyboard.js @@ -0,0 +1,16 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule dismissKeyboard + * + * This function dismisses the currently-open keyboard, if any + */ +'use strict'; + +var TextInputState = require('TextInputState'); + +function dismissKeyboard() { + TextInputState.blurTextInput(TextInputState.currentlyFocusedField()); +} + +module.exports = dismissKeyboard; From a2c51b3b283d95bad44408a5887d690d74cd7ef9 Mon Sep 17 00:00:00 2001 From: Dorota Kapturkiewicz Date: Fri, 14 Aug 2015 03:11:05 -0700 Subject: [PATCH 029/139] [ReactNative] expose importantForAccessibility to JS --- Libraries/Components/View/View.js | 26 +++++++++++++++++++ .../ReactNative/ReactNativeViewAttributes.js | 1 + 2 files changed, 27 insertions(+) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 5566cc7c9..2b11e94d6 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -100,6 +100,7 @@ var View = React.createClass({ /** * Indicates to accessibility services to treat UI component like a * native one. Works for Android only. + * @platform android */ accessibilityComponentType: PropTypes.oneOf([ 'none', @@ -111,6 +112,7 @@ var View = React.createClass({ * when this view changes. Works for Android API >= 19 only. * See http://developer.android.com/reference/android/view/View.html#attr_android:accessibilityLiveRegion * for references. + * @platform android */ accessibilityLiveRegion: PropTypes.oneOf([ 'none', @@ -118,9 +120,33 @@ var View = React.createClass({ 'assertive', ]), + /** + * Controls how view is important for accessibility which is if it + * fires accessibility events and if it is reported to accessibility services + * that query the screen. Works for Android only. + * See http://developer.android.com/reference/android/R.attr.html#importantForAccessibility + * for references. + * Possible values: + * 'auto' - The system determines whether the view is important for accessibility - + * default (recommended). + * 'yes' - The view is important for accessibility. + * 'no' - The view is not important for accessibility. + * 'no-hide-descendants' - The view is not important for accessibility, + * nor are any of its descendant views. + * + * @platform android + */ + importantForAccessibility: PropTypes.oneOf([ + 'auto', + 'yes', + 'no', + 'no-hide-descendants', + ]), + /** * Provides additional traits to screen reader. By default no traits are * provided unless specified otherwise in element + * @platform ios */ accessibilityTraits: PropTypes.oneOfType([ PropTypes.oneOf(AccessibilityTraits), diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js index b8ad5761c..ae47b7529 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -22,6 +22,7 @@ ReactNativeViewAttributes.UIView = { accessibilityComponentType: true, accessibilityLiveRegion: true, accessibilityTraits: true, + importantForAccessibility: true, testID: true, shouldRasterizeIOS: true, onLayout: true, From 5c0805b071124a7b4022d752acdd6d03fa866829 Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Fri, 14 Aug 2015 05:07:49 -0700 Subject: [PATCH 030/139] Fix failing UIExplorer test (after isValid removal) --- .../UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 7e91ae015..7fe2a7445 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -178,15 +178,15 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil]; - __weak id rootContentView; + __weak UIView *rootContentView; @autoreleasepool { RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""]; RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"])) - XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid"); + XCTAssertTrue(rootContentView.userInteractionEnabled, @"RCTContentView should be valid"); (void)rootView; } - XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated"); + XCTAssertFalse(rootContentView.userInteractionEnabled, @"RCTContentView should have been invalidated"); } - (void)testUnderlyingBridgeIsDeallocated From dc01ecbc9875820d7800b89154e03082cb2f432d Mon Sep 17 00:00:00 2001 From: Alexsander Akers Date: Fri, 14 Aug 2015 11:33:23 -0100 Subject: [PATCH 031/139] Remove now useless comment --- Libraries/Components/View/View.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 2b11e94d6..c541dd448 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -44,13 +44,6 @@ var AccessibilityTraits = [ 'pageTurn', ]; - -// <<<<< WARNING >>>>> -// If adding any properties to View that could change the way layout-only status -// works on iOS, make sure to update ReactNativeViewAttributes.js and -// RCTShadowView.m (in the -[RCTShadowView isLayoutOnly] method). -// <<<<< WARNING >>>>> - /** * The most fundamental component for building UI, `View` is a * container that supports layout with flexbox, style, some touch handling, and From 0f149339487ae68cd740f6295cc94a04ff8cf2f5 Mon Sep 17 00:00:00 2001 From: Alexsander Akers Date: Thu, 13 Aug 2015 15:09:10 -0100 Subject: [PATCH 032/139] Enable transparent modal presentation with Summary: Enable transparent modal backgrounds using `UIModalPresentationCustom` modal presentation style. --- Examples/UIExplorer/ModalExample.js | 137 ++++++++++++++++++++------ Libraries/Modal/Modal.js | 17 +++- React/Views/RCTModalHostView.h | 5 +- React/Views/RCTModalHostView.m | 17 ++++ React/Views/RCTModalHostViewManager.h | 4 +- React/Views/RCTModalHostViewManager.m | 25 ++++- 6 files changed, 168 insertions(+), 37 deletions(-) diff --git a/Examples/UIExplorer/ModalExample.js b/Examples/UIExplorer/ModalExample.js index 37d2cc245..3202cb622 100644 --- a/Examples/UIExplorer/ModalExample.js +++ b/Examples/UIExplorer/ModalExample.js @@ -19,6 +19,7 @@ var React = require('react-native'); var { Modal, StyleSheet, + SwitchIOS, Text, TouchableHighlight, View, @@ -29,53 +30,98 @@ exports.framework = 'React'; exports.title = ''; exports.description = 'Component for presenting modal views.'; -var ModalExample = React.createClass({ - getInitialState: function() { +var Button = React.createClass({ + getInitialState() { return { - openModal: null, + active: false, }; }, - _closeModal: function() { - this.setState({openModal: null}); + _onHighlight() { + this.setState({active: true}); }, - _openAnimatedModal: function() { - this.setState({openModal: 'animated'}); + _onUnhighlight() { + this.setState({active: false}); }, - _openNotAnimatedModal: function() { - this.setState({openModal: 'not-animated'}); + render() { + var colorStyle = { + color: this.state.active ? '#fff' : '#000', + }; + return ( + + {this.props.children} + + ); + } +}); + +var ModalExample = React.createClass({ + getInitialState() { + return { + animated: true, + modalVisible: false, + transparent: false, + }; }, - render: function() { + _setModalVisible(visible) { + this.setState({modalVisible: visible}); + }, + + _toggleAnimated() { + this.setState({animated: !this.state.animated}); + }, + + _toggleTransparent() { + this.setState({transparent: !this.state.transparent}); + }, + + render() { + var modalBackgroundStyle = { + backgroundColor: this.state.transparent ? 'rgba(0, 0, 0, 0.5)' : '#f5fcff', + }; + var innerContainerTransparentStyle = this.state.transparent + ? {backgroundColor: '#fff', padding: 20} + : null; + return ( - - - This modal was presented with animation. - - Close - + + + + This modal was presented {this.state.animated ? 'with' : 'without'} animation. + + - - - This modal was presented immediately, without animation. - - Close - - - + + Animated + + - - Present Animated - + + Transparent + + - - Present Without Animation - + ); }, @@ -91,9 +137,36 @@ exports.examples = [ var styles = StyleSheet.create({ container: { - alignItems: 'center', - backgroundColor: '#f5fcff', flex: 1, justifyContent: 'center', + padding: 20, + }, + innerContainer: { + borderRadius: 10, + }, + row: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + marginBottom: 20, + }, + rowTitle: { + flex: 1, + fontWeight: 'bold', + }, + button: { + borderRadius: 5, + flex: 1, + height: 44, + justifyContent: 'center', + overflow: 'hidden', + }, + buttonText: { + fontSize: 18, + margin: 5, + textAlign: 'center', + }, + modalButton: { + marginTop: 10, }, }); diff --git a/Libraries/Modal/Modal.js b/Libraries/Modal/Modal.js index 23bf730ec..2063f0fdd 100644 --- a/Libraries/Modal/Modal.js +++ b/Libraries/Modal/Modal.js @@ -11,6 +11,7 @@ */ 'use strict'; +var PropTypes = require('ReactPropTypes'); var React = require('React'); var StyleSheet = require('StyleSheet'); var View = require('View'); @@ -24,9 +25,16 @@ class Modal extends React.Component { return null; } + if (this.props.transparent) { + var containerBackgroundColor = {backgroundColor: 'transparent'}; + } + return ( - - + + {this.props.children} @@ -34,6 +42,11 @@ class Modal extends React.Component { } } +Modal.propTypes = { + animated: PropTypes.bool, + transparent: PropTypes.bool, +}; + var styles = StyleSheet.create({ modal: { position: 'absolute', diff --git a/React/Views/RCTModalHostView.h b/React/Views/RCTModalHostView.h index a39df2815..cafe771c8 100644 --- a/React/Views/RCTModalHostView.h +++ b/React/Views/RCTModalHostView.h @@ -9,11 +9,14 @@ #import +#import "RCTInvalidating.h" + @class RCTBridge; -@interface RCTModalHostView : UIView +@interface RCTModalHostView : UIView @property (nonatomic, assign, getter=isAnimated) BOOL animated; +@property (nonatomic, assign, getter=isTransparent) BOOL transparent; - (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index 0322e02ca..a4d1db93e 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -81,4 +81,21 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:coder) } } +- (void)invalidate +{ + dispatch_async(dispatch_get_main_queue(), ^{ + [_modalViewController dismissViewControllerAnimated:self.animated completion:nil]; + }); +} + +- (BOOL)isTransparent +{ + return _modalViewController.modalPresentationStyle == UIModalPresentationCustom; +} + +- (void)setTransparent:(BOOL)transparent +{ + _modalViewController.modalPresentationStyle = transparent ? UIModalPresentationCustom : UIModalPresentationFullScreen; +} + @end diff --git a/React/Views/RCTModalHostViewManager.h b/React/Views/RCTModalHostViewManager.h index e47794542..fb5972d34 100644 --- a/React/Views/RCTModalHostViewManager.h +++ b/React/Views/RCTModalHostViewManager.h @@ -9,6 +9,8 @@ #import "RCTViewManager.h" -@interface RCTModalHostViewManager : RCTViewManager +#import "RCTInvalidating.h" + +@interface RCTModalHostViewManager : RCTViewManager @end diff --git a/React/Views/RCTModalHostViewManager.m b/React/Views/RCTModalHostViewManager.m index 458e65081..3ad022b21 100644 --- a/React/Views/RCTModalHostViewManager.m +++ b/React/Views/RCTModalHostViewManager.m @@ -14,14 +14,37 @@ #import "RCTTouchHandler.h" @implementation RCTModalHostViewManager +{ + NSHashTable *_hostViews; +} RCT_EXPORT_MODULE() +- (instancetype)init +{ + if ((self = [super init])) { + _hostViews = [NSHashTable weakObjectsHashTable]; + } + + return self; +} + - (UIView *)view { - return [[RCTModalHostView alloc] initWithBridge:self.bridge]; + UIView *view = [[RCTModalHostView alloc] initWithBridge:self.bridge]; + [_hostViews addObject:view]; + return view; +} + +- (void)invalidate +{ + for (RCTModalHostView *hostView in _hostViews) { + [hostView invalidate]; + } + [_hostViews removeAllObjects]; } RCT_EXPORT_VIEW_PROPERTY(animated, BOOL) +RCT_EXPORT_VIEW_PROPERTY(transparent, BOOL) @end From 96277ca3f5ec29c3c6438bfe8ec146f64100ab0b Mon Sep 17 00:00:00 2001 From: Alexsander Akers Date: Fri, 14 Aug 2015 10:58:45 -0100 Subject: [PATCH 033/139] Add HSL/HSLA support --- .../UIExplorer.xcodeproj/project.pbxproj | 4 + .../RCTConvert_UIColorTests.m | 79 +++++++++++++++++++ React/Base/RCTConvert.m | 78 +++++++++++++----- 3 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIColorTests.m diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index d40120041..fb20657a3 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; 83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; }; + 83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; /* End PBXBuildFile section */ @@ -216,6 +217,7 @@ 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = ""; }; + 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = ""; }; D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -359,6 +361,7 @@ 138D6A151B53CD440074A87E /* RCTCacheTests.m */, 1497CFA61B21F5E400C1F8F2 /* RCTContextExecutorTests.m */, 1497CFA71B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m */, + 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */, 1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */, 1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */, 1300627E1B59179B0043FE5A /* RCTGzipTests.m */, @@ -805,6 +808,7 @@ 138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */, 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */, 1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */, + 83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */, 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */, 138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */, ); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIColorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIColorTests.m new file mode 100644 index 000000000..f74429974 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIColorTests.m @@ -0,0 +1,79 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "RCTConvert.h" + +@interface RCTConvert_UIColorTests : XCTestCase + +@end + +@implementation RCTConvert_UIColorTests + +#define XCTAssertEqualColors(color1, color2) do { \ + CGFloat r1, g1, b1, a1; \ + CGFloat r2, g2, b2, a2; \ + XCTAssertTrue([(color1) getRed:&r1 green:&g1 blue:&b1 alpha:&a1] && \ + [(color2) getRed:&r2 green:&g2 blue:&b2 alpha:&a2] && \ + r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2, \ + @"rgba(%d, %d, %d, %.3f) != rgba(%d, %d, %d, %.3f)", \ + (int)(r1 * 255), (int)(g1 * 255), (int)(b1 * 255), a1, \ + (int)(r2 * 255), (int)(g2 * 255), (int)(b2 * 255), a2 \ + ); \ +} while (0) + +- (void)testHex3 +{ + UIColor *color = [RCTConvert UIColor:@"#333"]; + UIColor *expected = [UIColor colorWithWhite:0.2 alpha:1.0]; + XCTAssertEqualColors(color, expected); +} + +- (void)testHex6 +{ + UIColor *color = [RCTConvert UIColor:@"#666"]; + UIColor *expected = [UIColor colorWithWhite:0.4 alpha:1.0]; + XCTAssertEqualColors(color, expected); +} + +- (void)testRGB +{ + UIColor *color = [RCTConvert UIColor:@"rgb(51, 102, 153)"]; + UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:1.0]; + XCTAssertEqualColors(color, expected); +} + +- (void)testRGBA +{ + UIColor *color = [RCTConvert UIColor:@"rgba(51, 102, 153, 0.5)"]; + UIColor *expected = [UIColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.5]; + XCTAssertEqualColors(color, expected); +} + +- (void)testHSL +{ + UIColor *color = [RCTConvert UIColor:@"hsl(30, 50%, 50%)"]; + UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:1.0]; + XCTAssertEqualColors(color, expected); +} + +- (void)testHSLA +{ + UIColor *color = [RCTConvert UIColor:@"hsla(30, 50%, 50%, 0.5)"]; + UIColor *expected = [UIColor colorWithHue:30.0 / 360.0 saturation:0.5 brightness:0.5 alpha:0.5]; + XCTAssertEqualColors(color, expected); +} + +@end diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index b83089973..51134f98e 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -559,37 +559,66 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ } // Parse color - double red = 0, green = 0, blue = 0; - double alpha = 1.0; + enum { + MODE_RGB = 0, + MODE_HSB = 1, + }; + struct { + union { + struct { + double r, g, b; + } rgb; + struct { + double h, s, b; + } hsb; + }; + double a; + unsigned int mode: 1; + } components = { + .a = 1.0, + .mode = MODE_RGB, + }; + if ([colorString hasPrefix:@"#"]) { uint32_t redInt = 0, greenInt = 0, blueInt = 0; if (colorString.length == 4) { // 3 digit hex sscanf([colorString UTF8String], "#%01x%01x%01x", &redInt, &greenInt, &blueInt); // expand to 6 digit hex - red = redInt | (redInt << 4); - green = greenInt | (greenInt << 4); - blue = blueInt | (blueInt << 4); + components.rgb.r = redInt / 15.0; + components.rgb.g = greenInt / 15.0; + components.rgb.b = blueInt / 15.0; } else if (colorString.length == 7) { // 6 digit hex sscanf(colorString.UTF8String, "#%02x%02x%02x", &redInt, &greenInt, &blueInt); - red = redInt; - green = greenInt; - blue = blueInt; + components.rgb.r = redInt / 255.0; + components.rgb.g = greenInt / 255.0; + components.rgb.b = blueInt / 255.0; } else { RCTLogError(@"Invalid hex color %@. Hex colors should be 3 or 6 digits long.", colorString); - alpha = -1; + components.a = -1; } - } else if ([colorString hasPrefix:@"rgba("]) { - sscanf(colorString.UTF8String, "rgba(%lf,%lf,%lf,%lf)", &red, &green, &blue, &alpha); - } else if ([colorString hasPrefix:@"rgb("]) { - sscanf(colorString.UTF8String, "rgb(%lf,%lf,%lf)", &red, &green, &blue); + } else if (4 == sscanf(colorString.UTF8String, "rgba(%lf,%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b, &components.a) || + 3 == sscanf(colorString.UTF8String, "rgb(%lf,%lf,%lf)", &components.rgb.r, &components.rgb.g, &components.rgb.b)) { + components.rgb.r /= 255.0; + components.rgb.g /= 255.0; + components.rgb.b /= 255.0; + } else if (4 == sscanf(colorString.UTF8String, "hsla(%lf,%lf%%,%lf%%,%lf)", &components.hsb.h, &components.hsb.s, &components.hsb.b, &components.a) || + 3 == sscanf(colorString.UTF8String, "hsl(%lf,%lf%%,%lf%%)", &components.hsb.h, &components.hsb.s, &components.hsb.b)) { + components.hsb.h /= 360.0; + components.hsb.s /= 100.0; + components.hsb.b /= 100.0; + components.mode = MODE_HSB; } else { RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb or a valid CSS color name.", colorString); - alpha = -1; + components.a = -1; } - if (alpha < 0) { + if (components.a < 0) { RCTLogError(@"Invalid color string '%@'", colorString); } else { - color = [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:alpha]; + if (components.mode == MODE_RGB) { + color = [UIColor colorWithRed:components.rgb.r green:components.rgb.g blue:components.rgb.b alpha:components.a]; + } else { + color = [UIColor colorWithHue:components.hsb.h saturation:components.hsb.s brightness:components.hsb.b alpha:components.a]; + } } } else if ([json isKindOfClass:[NSArray class]]) { @@ -608,10 +637,19 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ } else if ([json isKindOfClass:[NSDictionary class]]) { // Color dictionary - color = [UIColor colorWithRed:[self CGFloat:json[@"r"]] - green:[self CGFloat:json[@"g"]] - blue:[self CGFloat:json[@"b"]] - alpha:[self CGFloat:json[@"a"] ?: @1]]; + if (json[@"r"]) { + color = [UIColor colorWithRed:[self CGFloat:json[@"r"]] + green:[self CGFloat:json[@"g"]] + blue:[self CGFloat:json[@"b"]] + alpha:[self CGFloat:json[@"a"] ?: @1]]; + } else if (json[@"h"]) { + color = [UIColor colorWithHue:[self CGFloat:json[@"h"]] + saturation:[self CGFloat:json[@"s"]] + brightness:[self CGFloat:json[@"b"]] + alpha:[self CGFloat:json[@"a"] ?: @1]]; + } else { + RCTLogError(@"Expected dictionary with keys {r,g,b} or {h,s,b}, got: %@", [json allKeys]); + } } else if (json) { RCTLogConvertError(json, @"a color"); From 3f5fcadbd59e47cdf27b6d0c84d413d6b850ea3e Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 14 Aug 2015 05:13:40 -0700 Subject: [PATCH 034/139] Fix null values in network requests --- Libraries/Network/RCTNetworking.m | 8 ++++---- Libraries/Network/XMLHttpRequest.ios.js | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index c892363b0..a2f0475e2 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -140,12 +140,12 @@ RCT_EXPORT_MODULE() - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query completionBlock:(void (^)(NSURLRequest *request))block { - NSURL *URL = [RCTConvert NSURL:query[@"url"]]; + NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; - request.HTTPMethod = [[RCTConvert NSString:query[@"method"]] uppercaseString] ?: @"GET"; + request.HTTPMethod = [[RCTConvert NSString:RCTNilIfNull(query[@"method"])] uppercaseString] ?: @"GET"; request.allHTTPHeaderFields = [RCTConvert NSDictionary:query[@"headers"]]; - NSDictionary *data = [RCTConvert NSDictionary:query[@"data"]]; + NSDictionary *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])]; return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary *result) { if (error) { RCTLogError(@"Error processing request body: %@", error); @@ -220,7 +220,7 @@ RCT_EXPORT_MODULE() * - @"contentType" (NSString): the content type header of the request * */ -- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary *)query callback: +- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary *)query callback: (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary *result))callback { if (!query) { diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 7b84e35d1..29b5597ad 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -104,8 +104,7 @@ class XMLHttpRequest extends XMLHttpRequestBase { sendImpl(method: ?string, url: ?string, headers: Object, data: any): void { if (typeof data === 'string') { data = {string: data}; - } - if (data instanceof FormData) { + } else if (data instanceof FormData) { data = {formData: data.getParts()}; } RCTNetworking.sendRequest( From decf35c271d8b8b209332d21d15aba4be89ed287 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 14 Aug 2015 13:49:32 -0100 Subject: [PATCH 035/139] Fixed log prefixes to provide more context for prop binding errors --- React/Base/RCTLog.m | 85 ++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 0cb095494..3fa343303 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -37,7 +37,7 @@ static RCTLogLevel RCTCurrentLogThreshold; __attribute__((constructor)) static void RCTLogSetup() { - RCTCurrentLogFunction = RCTDefaultLogFunction; + RCTSetLogFunction(RCTDefaultLogFunction); #if RCT_DEBUG RCTCurrentLogThreshold = RCTLogLevelInfo - 1; @@ -52,8 +52,7 @@ RCTLogFunction RCTDefaultLogFunction = ^( NSString *fileName, NSNumber *lineNumber, NSString *message -) -{ +) { NSString *log = RCTFormatLog( [NSDate date], level, fileName, lineNumber, message ); @@ -82,7 +81,52 @@ RCTLogFunction RCTDefaultLogFunction = ^( void RCTSetLogFunction(RCTLogFunction logFunction) { + +#if RCT_DEBUG // Red box is only available in debug mode + + RCTCurrentLogFunction = ^( + RCTLogLevel level, + NSString *fileName, + NSNumber *lineNumber, + NSString *message + ) { + // Log to red box + if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { + NSArray *stackSymbols = [NSThread callStackSymbols]; + NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)]; + [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) { + if (idx > 0) { // don't include the current frame + NSString *address = [[frameSymbols componentsSeparatedByString:@"0x"][1] componentsSeparatedByString:@" "][0]; + NSRange addressRange = [frameSymbols rangeOfString:address]; + NSString *methodName = [frameSymbols substringFromIndex:(addressRange.location + addressRange.length + 1)]; + if (idx == 1 && fileName && lineNumber) { + [stack addObject:@{ + @"methodName": methodName, + @"file": fileName.lastPathComponent, + @"lineNumber": lineNumber + }]; + } else { + [stack addObject:@{@"methodName": methodName}]; + } + } + }]; + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + } + + // Log to JS executor + [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; + + if (logFunction) { + logFunction(level, fileName, lineNumber, message); + } + }; + +#else + RCTCurrentLogFunction = logFunction; + +#endif + } RCTLogFunction RCTGetLogFunction() @@ -151,8 +195,7 @@ NSString *RCTFormatLog( NSString *fileName, NSNumber *lineNumber, NSString *message -) -{ +) { NSMutableString *log = [[NSMutableString alloc] init]; if (timestamp) { static NSDateFormatter *formatter; @@ -188,8 +231,8 @@ void _RCTLogFormat( RCTLogLevel level, const char *fileName, int lineNumber, - NSString *format, ...) -{ + NSString *format, ... +) { RCTLogFunction logFunction = RCTGetLocalLogFunction(); BOOL log = RCT_DEBUG || (logFunction != nil); if (log && level >= RCTCurrentLogThreshold) { @@ -204,33 +247,5 @@ void _RCTLogFormat( if (logFunction) { logFunction(level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message); } - -#if RCT_DEBUG // Red box is only available in debug mode - - // Log to red box - if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { - NSArray *stackSymbols = [NSThread callStackSymbols]; - NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)]; - [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) { - if (idx > 0) { // don't include the current frame - NSString *address = [[frameSymbols componentsSeparatedByString:@"0x"][1] componentsSeparatedByString:@" "][0]; - NSRange addressRange = [frameSymbols rangeOfString:address]; - NSString *methodName = [frameSymbols substringFromIndex:(addressRange.location + addressRange.length + 1)]; - if (idx == 1) { - NSString *file = [[@(fileName) componentsSeparatedByString:@"/"] lastObject]; - [stack addObject:@{@"methodName": methodName, @"file": file, @"lineNumber": @(lineNumber)}]; - } else { - [stack addObject:@{@"methodName": methodName}]; - } - } - }]; - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; - } - - // Log to JS executor - [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; - -#endif - } } From 91c8362176cf2adf4bcc9326dc7e752bed3714f8 Mon Sep 17 00:00:00 2001 From: Alexsander Akers Date: Fri, 14 Aug 2015 11:08:25 -0100 Subject: [PATCH 036/139] Rename RCTReachability to RCTNetInfo --- Libraries/Network/NetInfo.js | 11 +++-------- .../Network/{RCTReachability.h => RCTNetInfo.h} | 2 +- .../Network/{RCTReachability.m => RCTNetInfo.m} | 12 ++++++------ .../Network/RCTNetwork.xcodeproj/project.pbxproj | 12 ++++++------ 4 files changed, 16 insertions(+), 21 deletions(-) rename Libraries/Network/{RCTReachability.h => RCTNetInfo.h} (89%) rename Libraries/Network/{RCTReachability.m => RCTNetInfo.m} (90%) diff --git a/Libraries/Network/NetInfo.js b/Libraries/Network/NetInfo.js index 63df3802b..f51a95529 100644 --- a/Libraries/Network/NetInfo.js +++ b/Libraries/Network/NetInfo.js @@ -15,14 +15,9 @@ var Map = require('Map'); var NativeModules = require('NativeModules'); var Platform = require('Platform'); var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter'); +var RCTNetInfo = NativeModules.NetInfo; -if (Platform.OS === 'ios') { - var RCTNetInfo = NativeModules.Reachability; -} else if (Platform.OS === 'android') { - var RCTNetInfo = NativeModules.NetInfo; -} - -var DEVICE_REACHABILITY_EVENT = 'reachabilityDidChange'; +var DEVICE_REACHABILITY_EVENT = 'networkDidChange'; type ChangeEventName = $Enum<{ change: string; @@ -151,7 +146,7 @@ var NetInfo = { var listener = RCTDeviceEventEmitter.addListener( DEVICE_REACHABILITY_EVENT, (appStateData) => { - handler(appStateData.network_reachability); + handler(appStateData.network_info); } ); _subscriptions.set(handler, listener); diff --git a/Libraries/Network/RCTReachability.h b/Libraries/Network/RCTNetInfo.h similarity index 89% rename from Libraries/Network/RCTReachability.h rename to Libraries/Network/RCTNetInfo.h index ba74fcb42..6c2556e0a 100644 --- a/Libraries/Network/RCTReachability.h +++ b/Libraries/Network/RCTNetInfo.h @@ -11,7 +11,7 @@ #import "RCTBridgeModule.h" -@interface RCTReachability : NSObject +@interface RCTNetInfo : NSObject - (instancetype)initWithHost:(NSString *)host NS_DESIGNATED_INITIALIZER; diff --git a/Libraries/Network/RCTReachability.m b/Libraries/Network/RCTNetInfo.m similarity index 90% rename from Libraries/Network/RCTReachability.m rename to Libraries/Network/RCTNetInfo.m index 7c5530127..5aab64432 100644 --- a/Libraries/Network/RCTReachability.m +++ b/Libraries/Network/RCTNetInfo.m @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "RCTReachability.h" +#import "RCTNetInfo.h" #import "RCTAssert.h" #import "RCTBridge.h" @@ -18,7 +18,7 @@ static NSString *const RCTReachabilityStateNone = @"none"; static NSString *const RCTReachabilityStateWifi = @"wifi"; static NSString *const RCTReachabilityStateCell = @"cell"; -@implementation RCTReachability +@implementation RCTNetInfo { SCNetworkReachabilityRef _reachability; NSString *_status; @@ -30,7 +30,7 @@ RCT_EXPORT_MODULE() static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) { - RCTReachability *self = (__bridge id)info; + RCTNetInfo *self = (__bridge id)info; NSString *status = RCTReachabilityStateUnknown; if ((flags & kSCNetworkReachabilityFlagsReachable) == 0 || (flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0) { @@ -51,8 +51,8 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC if (![status isEqualToString:self->_status]) { self->_status = status; - [self->_bridge.eventDispatcher sendDeviceEventWithName:@"reachabilityDidChange" - body:@{@"network_reachability": status}]; + [self->_bridge.eventDispatcher sendDeviceEventWithName:@"networkDidChange" + body:@{@"network_info": status}]; } } @@ -90,7 +90,7 @@ static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SC RCT_EXPORT_METHOD(getCurrentReachability:(RCTResponseSenderBlock)getSuccess withErrorCallback:(__unused RCTResponseSenderBlock)getError) { - getSuccess(@[@{@"network_reachability": _status}]); + getSuccess(@[@{@"network_info": _status}]); } @end diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj index 8243ac65f..546b301c2 100644 --- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj +++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTReachability.m */; }; + 1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; }; 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; }; 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; }; 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; }; @@ -26,8 +26,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 1372B7351AB03E7B00659ED6 /* RCTReachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTReachability.h; sourceTree = ""; }; - 1372B7361AB03E7B00659ED6 /* RCTReachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTReachability.m; sourceTree = ""; }; + 1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = ""; }; 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = ""; }; 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = ""; }; @@ -55,10 +55,10 @@ 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */, 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */, 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */, + 1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */, + 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */, 58B512061A9E6CE300147676 /* RCTNetworking.h */, 58B512071A9E6CE300147676 /* RCTNetworking.m */, - 1372B7351AB03E7B00659ED6 /* RCTReachability.h */, - 1372B7361AB03E7B00659ED6 /* RCTReachability.m */, 58B511DC1A9E6C8500147676 /* Products */, ); indentWidth = 2; @@ -130,7 +130,7 @@ buildActionMask = 2147483647; files = ( 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */, - 1372B7371AB03E7B00659ED6 /* RCTReachability.m in Sources */, + 1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */, 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */, 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */, ); From 8bd1cb8ccee495bead04c3a7058aea4dd2123091 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 14 Aug 2015 10:30:19 -0700 Subject: [PATCH 037/139] [react-native] Add babel-plugin-node-env-inline plugin Summary: Check in the `process.env.NODE_ENV` inline plugin. This will be used in conjuction with uglifyjs to eliminate dead code resulting from environment checks. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 07698215a..c9e2e40e3 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "absolute-path": "0.0.0", "babel": "5.4.3", "babel-core": "5.6.4", + "babel-plugin-node-env-inline": "^1.0.1", "chalk": "1.0.0", "connect": "2.8.3", "debug": "2.1.0", From 76e37c44238e61c31a2da23b463d14381cb38ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:08:05 -0700 Subject: [PATCH 038/139] [react-packager] Modernize `Server-test` by using ES6 features --- .../src/Server/__tests__/Server-test.js | 171 ++++++++---------- 1 file changed, 72 insertions(+), 99 deletions(-) diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index 5461b89ff..1677776d6 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -8,69 +8,54 @@ */ 'use strict'; -jest.setMock('worker-farm', function() { return function() {}; }) +jest.setMock('worker-farm', function() { return () => {}; }) .dontMock('os') .dontMock('path') .dontMock('url') - .setMock('timers', { - setImmediate: function(fn) { - return setTimeout(fn, 0); - } - }) + .setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) }) .setMock('uglify-js') .dontMock('../'); -var Promise = require('promise'); +const Promise = require('promise'); -describe('processRequest', function() { +describe('processRequest', () => { var server; var Bundler; var FileWatcher; - var options = { + const options = { projectRoots: ['root'], blacklistRE: null, cacheVersion: null, polyfillModuleNames: null }; - var makeRequest = function(requestHandler, requrl) { - return new Promise(function(resolve) { - requestHandler( - { url: requrl }, - { - setHeader: jest.genMockFunction(), - end: function(res) { - resolve(res); - } - }, - { - next: function() {} - } - ); - }); - }; + const makeRequest = (reqHandler, requrl) => new Promise(resolve => + reqHandler( + { url: requrl }, + { + setHeader: jest.genMockFunction(), + end: res => resolve(res), + }, + { next: () => {} }, + ) + ); - var invalidatorFunc = jest.genMockFunction(); - var watcherFunc = jest.genMockFunction(); + const invalidatorFunc = jest.genMockFunction(); + const watcherFunc = jest.genMockFunction(); var requestHandler; var triggerFileChange; - beforeEach(function() { + beforeEach(() => { Bundler = require('../../Bundler'); FileWatcher = require('../../FileWatcher'); - Bundler.prototype.bundle = jest.genMockFunction().mockImpl(function() { - return Promise.resolve({ - getSource: function() { - return 'this is the source'; - }, - getSourceMap: function() { - return 'this is the source map'; - }, - }); - }); - + Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() => + Promise.resolve({ + getSource: () => 'this is the source', + getSourceMap: () => 'this is the source map', + }) + ); FileWatcher.prototype.on = function(eventType, callback) { if (eventType !== 'all') { @@ -83,43 +68,43 @@ describe('processRequest', function() { Bundler.prototype.invalidateFile = invalidatorFunc; - var Server = require('../'); + const Server = require('../'); server = new Server(options); requestHandler = server.processRequest.bind(server); }); - pit('returns JS bundle source on request of *.bundle',function() { + pit('returns JS bundle source on request of *.bundle', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function(response) { - expect(response).toEqual('this is the source'); - }); + ).then(response => + expect(response).toEqual('this is the source') + ); }); - pit('returns JS bundle source on request of *.bundle (compat)',function() { + pit('returns JS bundle source on request of *.bundle (compat)', () => { return makeRequest( requestHandler, 'mybundle.runModule.bundle' - ).then(function(response) { - expect(response).toEqual('this is the source'); - }); + ).then(response => + expect(response).toEqual('this is the source') + ); }); - pit('returns sourcemap on request of *.map', function() { + pit('returns sourcemap on request of *.map', () => { return makeRequest( requestHandler, 'mybundle.map?runModule=true' - ).then(function(response) { - expect(response).toEqual('"this is the source map"'); - }); + ).then(response => + expect(response).toEqual('"this is the source map"') + ); }); - pit('works with .ios.js extension', function() { + pit('works with .ios.js extension', () => { return makeRequest( requestHandler, 'index.ios.includeRequire.bundle' - ).then(function(response) { + ).then(response => { expect(response).toEqual('this is the source'); expect(Bundler.prototype.bundle).toBeCalledWith( 'index.ios.js', @@ -130,81 +115,75 @@ describe('processRequest', function() { }); }); - pit('watches all files in projectRoot', function() { + pit('watches all files in projectRoot', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function() { + ).then(() => { expect(watcherFunc.mock.calls[0][0]).toEqual('all'); expect(watcherFunc.mock.calls[0][1]).not.toBe(null); }); }); - - describe('file changes', function() { - pit('invalides files in bundle when file is updated', function() { + describe('file changes', () => { + pit('invalides files in bundle when file is updated', () => { return makeRequest( requestHandler, 'mybundle.bundle?runModule=true' - ).then(function() { - var onFileChange = watcherFunc.mock.calls[0][1]; + ).then(() => { + const onFileChange = watcherFunc.mock.calls[0][1]; onFileChange('all','path/file.js', options.projectRoots[0]); expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js'); }); }); - pit('rebuilds the bundles that contain a file when that file is changed', function() { - var bundleFunc = jest.genMockFunction(); + pit('rebuilds the bundles that contain a file when that file is changed', () => { + const bundleFunc = jest.genMockFunction(); bundleFunc .mockReturnValueOnce( Promise.resolve({ - getSource: function() { - return 'this is the first source'; - }, - getSourceMap: function() {}, + getSource: () => 'this is the first source', + getSourceMap: () => {}, }) ) .mockReturnValue( Promise.resolve({ - getSource: function() { - return 'this is the rebuilt source'; - }, - getSourceMap: function() {}, + getSource: () => 'this is the rebuilt source', + getSourceMap: () => {}, }) ); Bundler.prototype.bundle = bundleFunc; - var Server = require('../../Server'); + const Server = require('../../Server'); server = new Server(options); requestHandler = server.processRequest.bind(server); - return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .then(function(response) { + .then(response => { expect(response).toEqual('this is the first source'); expect(bundleFunc.mock.calls.length).toBe(1); triggerFileChange('all','path/file.js', options.projectRoots[0]); jest.runAllTimers(); jest.runAllTimers(); }) - .then(function() { + .then(() => { expect(bundleFunc.mock.calls.length).toBe(2); return makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .then(function(response) { - expect(response).toEqual('this is the rebuilt source'); - }); + .then(response => + expect(response).toEqual('this is the rebuilt source') + ); }); }); }); - describe('/onchange endpoint', function() { + describe('/onchange endpoint', () => { var EventEmitter; var req; var res; - beforeEach(function() { + beforeEach(() => { EventEmitter = require.requireActual('events').EventEmitter; req = new EventEmitter(); req.url = '/onchange'; @@ -214,14 +193,14 @@ describe('processRequest', function() { }; }); - it('should hold on to request and inform on change', function() { + it('should hold on to request and inform on change', () => { server.processRequest(req, res); triggerFileChange('all', 'path/file.js', options.projectRoots[0]); jest.runAllTimers(); expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); }); - it('should not inform changes on disconnected clients', function() { + it('should not inform changes on disconnected clients', () => { server.processRequest(req, res); req.emit('close'); jest.runAllTimers(); @@ -231,36 +210,30 @@ describe('processRequest', function() { }); }); - describe('/assets endpoint', function() { + describe('/assets endpoint', () => { var AssetServer; - beforeEach(function() { + beforeEach(() => { AssetServer = require('../../AssetServer'); }); - it('should serve simple case', function() { - var req = { - url: '/assets/imgs/a.png', - }; - var res = { - end: jest.genMockFn(), - }; + it('should serve simple case', () => { + const req = {url: '/assets/imgs/a.png'}; + const res = {end: jest.genMockFn()}; - AssetServer.prototype.get.mockImpl(function() { - return Promise.resolve('i am image'); - }); + AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image')); server.processRequest(req, res); jest.runAllTimers(); expect(res.end).toBeCalledWith('i am image'); }); - it('should return 404', function() { + it('should return 404', () => { }); }); - describe('buildBundle(options)', function() { - it('Calls the bundler with the correct args', function() { + describe('buildbundle(options)', () => { + it('Calls the bundler with the correct args', () => { server.buildBundle({ entryFile: 'foo file' }); @@ -273,8 +246,8 @@ describe('processRequest', function() { }); }); - describe('buildBundleFromUrl(options)', function() { - it('Calls the bundler with the correct args', function() { + describe('buildBundleFromUrl(options)', () => { + it('Calls the bundler with the correct args', () => { server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false'); expect(Bundler.prototype.bundle).toBeCalledWith( 'path/to/foo.js', From 06233718145ac7cc009102dcc43903a6a967965f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:10:13 -0700 Subject: [PATCH 039/139] [react-packager] Cache in which bundle is each module Summary: Not that at the moment a module can be present in multiple bundles, so the new API will return only one of them. In the near future we'll impose the invariant that a module can only be present in a single bundle so this API will return the exact bundle in which it is. --- .../__tests__/BundlesLayout-test.js | 37 +++++++++++++++++++ .../react-packager/src/BundlesLayout/index.js | 11 +++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index 154a29c35..fce23226a 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -146,5 +146,42 @@ describe('BundlesLayout', () => { ]) ); }); + + pit('separate cache in which bundle is each dependency', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [['/root/c.js']], + }); + case '/root/c.js': + return Promise.resolve({ + dependencies: [dep('/root/c.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + var layout = newBundlesLayout(); + return layout.generateLayout(['/root/index.js']).then(() => { + expect(layout.getBundleIDForModule('/root/index.js')).toBe(0); + expect(layout.getBundleIDForModule('/root/a.js')).toBe(0); + expect(layout.getBundleIDForModule('/root/b.js')).toBe(1); + expect(layout.getBundleIDForModule('/root/c.js')).toBe(2); + }); + }); }); }); diff --git a/packager/react-packager/src/BundlesLayout/index.js b/packager/react-packager/src/BundlesLayout/index.js index 88e616c00..165a33b34 100644 --- a/packager/react-packager/src/BundlesLayout/index.js +++ b/packager/react-packager/src/BundlesLayout/index.js @@ -26,6 +26,8 @@ class BundlesLayout { constructor(options) { const opts = validateOpts(options); this._resolver = opts.dependencyResolver; + + this._moduleToBundle = Object.create(null); } generateLayout(entryPaths, isDev) { @@ -44,9 +46,10 @@ class BundlesLayout { .then(modulesDeps => { let syncDependencies = Object.create(null); modulesDeps.forEach(moduleDeps => { - moduleDeps.dependencies.forEach(dep => + moduleDeps.dependencies.forEach(dep => { syncDependencies[dep.path] = dep - ); + this._moduleToBundle[dep.path] = bundles.length; + }); pending = pending.concat(moduleDeps.asyncDependencies); }); @@ -60,6 +63,10 @@ class BundlesLayout { }, ); } + + getBundleIDForModule(path) { + return this._moduleToBundle[path]; + } } // Runs the body Promise meanwhile the condition callback is satisfied. From 3be2b1d9661ff528fc5cefc7b3e8149083953716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Fri, 14 Aug 2015 12:08:46 -0700 Subject: [PATCH 040/139] [react-packager] Modernize `Server` to ES6 --- packager/react-packager/src/Server/index.js | 695 ++++++++++---------- 1 file changed, 346 insertions(+), 349 deletions(-) diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index fcb6e1a9e..8ff8fda45 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -8,21 +8,20 @@ */ 'use strict'; -var url = require('url'); -var path = require('path'); -var declareOpts = require('../lib/declareOpts'); -var FileWatcher = require('../FileWatcher'); -var Bundler = require('../Bundler'); -var Activity = require('../Activity'); -var AssetServer = require('../AssetServer'); -var Promise = require('promise'); -var _ = require('underscore'); -var exec = require('child_process').exec; -var fs = require('fs'); +const Activity = require('../Activity'); +const AssetServer = require('../AssetServer'); +const FileWatcher = require('../FileWatcher'); +const Bundler = require('../Bundler'); +const Promise = require('promise'); -module.exports = Server; +const _ = require('underscore'); +const declareOpts = require('../lib/declareOpts'); +const exec = require('child_process').exec; +const fs = require('fs'); +const path = require('path'); +const url = require('url'); -var validateOpts = declareOpts({ +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -64,115 +63,7 @@ var validateOpts = declareOpts({ }, }); -function Server(options) { - var opts = validateOpts(options); - - this._projectRoots = opts.projectRoots; - this._bundles = Object.create(null); - this._changeWatchers = []; - - var assetGlobs = opts.assetExts.map(function(ext) { - return '**/*.' + ext; - }); - - var watchRootConfigs = opts.projectRoots.map(function(dir) { - return { - dir: dir, - globs: [ - '**/*.js', - '**/*.json', - ].concat(assetGlobs), - }; - }); - - if (opts.assetRoots != null) { - watchRootConfigs = watchRootConfigs.concat( - opts.assetRoots.map(function(dir) { - return { - dir: dir, - globs: assetGlobs, - }; - }) - ); - } - - this._fileWatcher = options.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(watchRootConfigs); - - this._assetServer = new AssetServer({ - projectRoots: opts.projectRoots, - assetExts: opts.assetExts, - }); - - var bundlerOpts = Object.create(opts); - bundlerOpts.fileWatcher = this._fileWatcher; - bundlerOpts.assetServer = this._assetServer; - this._bundler = new Bundler(bundlerOpts); - - var onFileChange = this._onFileChange.bind(this); - this._fileWatcher.on('all', onFileChange); - - var self = this; - this._debouncedFileChangeHandler = _.debounce(function(filePath) { - self._rebuildBundles(filePath); - self._informChangeWatchers(); - }, 50); -} - -Server.prototype._onFileChange = function(type, filepath, root) { - var absPath = path.join(root, filepath); - this._bundler.invalidateFile(absPath); - // Make sure the file watcher event runs through the system before - // we rebuild the bundles. - this._debouncedFileChangeHandler(absPath); -}; - -Server.prototype._rebuildBundles = function() { - var buildBundle = this.buildBundle.bind(this); - var bundles = this._bundles; - - Object.keys(bundles).forEach(function(optionsJson) { - var options = JSON.parse(optionsJson); - // Wait for a previous build (if exists) to finish. - bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() { - // With finally promise callback we can't change the state of the promise - // so we need to reassign the promise. - bundles[optionsJson] = buildBundle(options).then(function(p) { - // Make a throwaway call to getSource to cache the source string. - p.getSource({ - inlineSourceMap: options.inlineSourceMap, - minify: options.minify, - }); - return p; - }); - }); - return bundles[optionsJson]; - }); -}; - -Server.prototype._informChangeWatchers = function() { - var watchers = this._changeWatchers; - var headers = { - 'Content-Type': 'application/json; charset=UTF-8', - }; - - watchers.forEach(function(w) { - w.res.writeHead(205, headers); - w.res.end(JSON.stringify({ changed: true })); - }); - - this._changeWatchers = []; -}; - -Server.prototype.end = function() { - Promise.all([ - this._fileWatcher.end(), - this._bundler.kill(), - ]); -}; - -var bundleOpts = declareOpts({ +const bundleOpts = declareOpts({ sourceMapUrl: { type: 'string', required: false, @@ -199,245 +90,351 @@ var bundleOpts = declareOpts({ }, }); -Server.prototype.buildBundle = function(options) { - var opts = bundleOpts(options); +class Server { + constructor(options) { + const opts = validateOpts(options); - return this._bundler.bundle( - opts.entryFile, - opts.runModule, - opts.sourceMapUrl, - opts.dev - ); -}; + this._projectRoots = opts.projectRoots; + this._bundles = Object.create(null); + this._changeWatchers = []; -Server.prototype.buildBundleFromUrl = function(reqUrl) { - var options = getOptionsFromUrl(reqUrl); - return this.buildBundle(options); -}; + const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext); -Server.prototype.getDependencies = function(main) { - return this._bundler.getDependencies(main); -}; + var watchRootConfigs = opts.projectRoots.map(dir => { + return { + dir: dir, + globs: [ + '**/*.js', + '**/*.json', + ].concat(assetGlobs), + }; + }); -Server.prototype._processDebugRequest = function(reqUrl, res) { - var ret = ''; - var pathname = url.parse(reqUrl).pathname; - var parts = pathname.split('/').filter(Boolean); - if (parts.length === 1) { - ret += ''; - ret += ''; - res.end(ret); - } else if (parts[1] === 'bundles') { - ret += '

Cached Bundles

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

' + optionsJson + '

'; - ret += p.getDebugInfo(); - }); - }, this)).then( - function() { res.end(ret); }, - function(e) { - res.writeHead(500); - res.end('Internal Error'); - console.log(e.stack); - } - ); - } else if (parts[1] === 'graph'){ - ret += '

Dependency Graph

'; - ret += this._bundler.getGraphDebugInfo(); - res.end(ret); - } else { - res.writeHead('404'); - res.end('Invalid debug request'); - return; - } -}; - -Server.prototype._processOnChangeRequest = function(req, res) { - var watchers = this._changeWatchers; - - watchers.push({ - req: req, - res: res, - }); - - req.on('close', function() { - for (var i = 0; i < watchers.length; i++) { - if (watchers[i] && watchers[i].req === req) { - watchers.splice(i, 1); - break; - } + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(dir => { + return { + dir: dir, + globs: assetGlobs, + }; + }) + ); } - }); -}; -Server.prototype._processAssetsRequest = function(req, res) { - var urlObj = url.parse(req.url, true); - var assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); - this._assetServer.get(assetPath[1]) - .then( - function(data) { - res.end(data); - }, - function(error) { - console.error(error.stack); - res.writeHead('404'); - res.end('Asset not found'); - } - ).done(); -}; + this._fileWatcher = options.nonPersistent + ? FileWatcher.createDummyWatcher() + : new FileWatcher(watchRootConfigs); -Server.prototype._processProfile = function(req, res) { - console.log('Dumping profile information...'); - var dumpName = '/tmp/dump_' + Date.now() + '.json'; - var prefix = process.env.TRACE_VIEWER_PATH || ''; - var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName; - fs.writeFileSync(dumpName, req.rawBody); - exec(cmd, function (error) { - if (error) { - if (error.code === 127) { - console.error( - '\n** Failed executing `' + cmd + '` **\n\n' + - 'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' + - 'You can get it at:\n\n' + - ' https://github.com/google/trace-viewer\n\n' + - 'If it\'s not in your path, you can set a custom path with:\n\n' + - ' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' + - 'NOTE: Your profile data was kept at:\n\n' + - ' ' + dumpName - ); - } else { - console.error('Unknown error', error); - } - res.end(); - return; + this._assetServer = new AssetServer({ + projectRoots: opts.projectRoots, + assetExts: opts.assetExts, + }); + + const bundlerOpts = Object.create(opts); + bundlerOpts.fileWatcher = this._fileWatcher; + bundlerOpts.assetServer = this._assetServer; + this._bundler = new Bundler(bundlerOpts); + + this._fileWatcher.on('all', this._onFileChange.bind(this)); + + this._debouncedFileChangeHandler = _.debounce(filePath => { + this._rebuildBundles(filePath); + this._informChangeWatchers(); + }, 50); + } + + end() { + Promise.all([ + this._fileWatcher.end(), + this._bundler.kill(), + ]); + } + + buildBundle(options) { + const opts = bundleOpts(options); + + return this._bundler.bundle( + opts.entryFile, + opts.runModule, + opts.sourceMapUrl, + opts.dev + ); + } + + buildBundleFromUrl(reqUrl) { + const options = this._getOptionsFromUrl(reqUrl); + return this.buildBundle(options); + } + + getDependencies(main) { + return this._bundler.getDependencies(main); + } + + _onFileChange(type, filepath, root) { + const absPath = path.join(root, filepath); + this._bundler.invalidateFile(absPath); + // Make sure the file watcher event runs through the system before + // we rebuild the bundles. + this._debouncedFileChangeHandler(absPath); + } + + _rebuildBundles() { + const buildBundle = this.buildBundle.bind(this); + const bundles = this._bundles; + + Object.keys(bundles).forEach(function(optionsJson) { + const options = JSON.parse(optionsJson); + // Wait for a previous build (if exists) to finish. + bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() { + // With finally promise callback we can't change the state of the promise + // so we need to reassign the promise. + bundles[optionsJson] = buildBundle(options).then(function(p) { + // Make a throwaway call to getSource to cache the source string. + p.getSource({ + inlineSourceMap: options.inlineSourceMap, + minify: options.minify, + }); + return p; + }); + }); + return bundles[optionsJson]; + }); + } + + _informChangeWatchers() { + const watchers = this._changeWatchers; + const headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + + watchers.forEach(function(w) { + w.res.writeHead(205, headers); + w.res.end(JSON.stringify({ changed: true })); + }); + + this._changeWatchers = []; + } + + _processDebugRequest(reqUrl, res) { + var ret = ''; + const pathname = url.parse(reqUrl).pathname; + const parts = pathname.split('/').filter(Boolean); + if (parts.length === 1) { + ret += ''; + ret += ''; + res.end(ret); + } else if (parts[1] === 'bundles') { + ret += '

Cached Bundles

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

' + optionsJson + '

'; + ret += p.getDebugInfo(); + }) + )).then( + () => res.end(ret), + e => { + res.writeHead(500); + res.end('Internal Error'); + console.log(e.stack); + } + ); + } else if (parts[1] === 'graph'){ + ret += '

Dependency Graph

'; + ret += this._bundler.getGraphDebugInfo(); + res.end(ret); } else { - exec('rm ' + dumpName); - exec('open ' + dumpName.replace(/json$/, 'html'), function (error) { - if (error) { - console.error(error); + res.writeHead('404'); + res.end('Invalid debug request'); + return; + } + } + + _processOnChangeRequest(req, res) { + const watchers = this._changeWatchers; + + watchers.push({ + req: req, + res: res, + }); + + req.on('close', () => { + for (let i = 0; i < watchers.length; i++) { + if (watchers[i] && watchers[i].req === req) { + watchers.splice(i, 1); + break; + } + } + }); + } + + _processAssetsRequest(req, res) { + const urlObj = url.parse(req.url, true); + const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); + this._assetServer.get(assetPath[1]) + .then( + data => res.end(data), + error => { + console.error(error.stack); + res.writeHead('404'); + res.end('Asset not found'); + } + ).done(); + } + + _processProfile(req, res) { + console.log('Dumping profile information...'); + const dumpName = '/tmp/dump_' + Date.now() + '.json'; + const prefix = process.env.TRACE_VIEWER_PATH || ''; + const cmd = path.join(prefix, 'trace2html') + ' ' + dumpName; + fs.writeFileSync(dumpName, req.rawBody); + exec(cmd, error => { + if (error) { + if (error.code === 127) { + console.error( + '\n** Failed executing `' + cmd + '` **\n\n' + + 'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' + + 'You can get it at:\n\n' + + ' https://github.com/google/trace-viewer\n\n' + + 'If it\'s not in your path, you can set a custom path with:\n\n' + + ' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' + + 'NOTE: Your profile data was kept at:\n\n' + + ' ' + dumpName + ); + } else { + console.error('Unknown error', error); } res.end(); - }); - } - }); -}; - -Server.prototype.processRequest = function(req, res, next) { - var urlObj = url.parse(req.url, true); - var pathname = urlObj.pathname; - - var requestType; - if (pathname.match(/\.bundle$/)) { - requestType = 'bundle'; - } else if (pathname.match(/\.map$/)) { - requestType = 'map'; - } else if (pathname.match(/^\/debug/)) { - this._processDebugRequest(req.url, res); - return; - } else if (pathname.match(/^\/onchange\/?$/)) { - this._processOnChangeRequest(req, res); - return; - } else if (pathname.match(/^\/assets\//)) { - this._processAssetsRequest(req, res); - return; - } else if (pathname.match(/^\/profile\/?$/)) { - this._processProfile(req, res); - return; - } else { - next(); - return; - } - - var startReqEventId = Activity.startEvent('request:' + req.url); - var options = getOptionsFromUrl(req.url); - var optionsJson = JSON.stringify(options); - var building = this._bundles[optionsJson] || this.buildBundle(options); - - this._bundles[optionsJson] = building; - building.then( - function(p) { - if (requestType === 'bundle') { - var bundleSource = p.getSource({ - inlineSourceMap: options.inlineSourceMap, - minify: options.minify, + return; + } else { + exec('rm ' + dumpName); + exec('open ' + dumpName.replace(/json$/, 'html'), err => { + if (err) { + console.error(err); + } + res.end(); }); - res.setHeader('Content-Type', 'application/javascript'); - res.end(bundleSource); - Activity.endEvent(startReqEventId); - } else if (requestType === 'map') { - var sourceMap = JSON.stringify(p.getSourceMap()); - res.setHeader('Content-Type', 'application/json'); - res.end(sourceMap); - Activity.endEvent(startReqEventId); } - }, - this._handleError.bind(this, res, optionsJson) - ).done(); -}; - -Server.prototype._handleError = function(res, bundleID, error) { - res.writeHead(error.status || 500, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - - if (error.type === 'TransformError' || error.type === 'NotFoundError') { - error.errors = [{ - description: error.description, - filename: error.filename, - lineNumber: error.lineNumber, - }]; - res.end(JSON.stringify(error)); - - if (error.type === 'NotFoundError') { - delete this._bundles[bundleID]; - } - } else { - console.error(error.stack || error); - res.end(JSON.stringify({ - type: 'InternalError', - message: 'react-packager has encountered an internal error, ' + - 'please check your terminal error output for more details', - })); - } -}; - -function getOptionsFromUrl(reqUrl) { - // `true` to parse the query param as an object. - var urlObj = url.parse(reqUrl, true); - // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 - urlObj.query = urlObj.query || {}; - - var pathname = decodeURIComponent(urlObj.pathname); - - // Backwards compatibility. Options used to be as added as '.' to the - // entry module name. We can safely remove these options. - var entryFile = pathname.replace(/^\//, '').split('.').filter(function(part) { - if (part === 'includeRequire' || part === 'runModule' || - part === 'bundle' || part === 'map') { - return false; - } - return true; - }).join('.') + '.js'; - - return { - sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), - entryFile: entryFile, - dev: getBoolOptionFromQuery(urlObj.query, 'dev', true), - minify: getBoolOptionFromQuery(urlObj.query, 'minify'), - runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true), - inlineSourceMap: getBoolOptionFromQuery( - urlObj.query, - 'inlineSourceMap', - false - ), - }; -} - -function getBoolOptionFromQuery(query, opt, defaultVal) { - if (query[opt] == null && defaultVal != null) { - return defaultVal; + }); } - return query[opt] === 'true' || query[opt] === '1'; + processRequest(req, res, next) { + const urlObj = url.parse(req.url, true); + var pathname = urlObj.pathname; + + var requestType; + if (pathname.match(/\.bundle$/)) { + requestType = 'bundle'; + } else if (pathname.match(/\.map$/)) { + requestType = 'map'; + } else if (pathname.match(/^\/debug/)) { + this._processDebugRequest(req.url, res); + return; + } else if (pathname.match(/^\/onchange\/?$/)) { + this._processOnChangeRequest(req, res); + return; + } else if (pathname.match(/^\/assets\//)) { + this._processAssetsRequest(req, res); + return; + } else if (pathname.match(/^\/profile\/?$/)) { + this._processProfile(req, res); + return; + } else { + next(); + return; + } + + const startReqEventId = Activity.startEvent('request:' + req.url); + const options = this._getOptionsFromUrl(req.url); + const optionsJson = JSON.stringify(options); + const building = this._bundles[optionsJson] || this.buildBundle(options); + + this._bundles[optionsJson] = building; + building.then( + p => { + if (requestType === 'bundle') { + var bundleSource = p.getSource({ + inlineSourceMap: options.inlineSourceMap, + minify: options.minify, + }); + res.setHeader('Content-Type', 'application/javascript'); + res.end(bundleSource); + Activity.endEvent(startReqEventId); + } else if (requestType === 'map') { + var sourceMap = JSON.stringify(p.getSourceMap()); + res.setHeader('Content-Type', 'application/json'); + res.end(sourceMap); + Activity.endEvent(startReqEventId); + } + }, + this._handleError.bind(this, res, optionsJson) + ).done(); + } + + _handleError(res, bundleID, error) { + res.writeHead(error.status || 500, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + + if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + res.end(JSON.stringify(error)); + + if (error.type === 'NotFoundError') { + delete this._bundles[bundleID]; + } + } else { + console.error(error.stack || error); + res.end(JSON.stringify({ + type: 'InternalError', + message: 'react-packager has encountered an internal error, ' + + 'please check your terminal error output for more details', + })); + } + } + + _getOptionsFromUrl(reqUrl) { + // `true` to parse the query param as an object. + const urlObj = url.parse(reqUrl, true); + // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 + urlObj.query = urlObj.query || {}; + + const pathname = decodeURIComponent(urlObj.pathname); + + // Backwards compatibility. Options used to be as added as '.' to the + // entry module name. We can safely remove these options. + const entryFile = pathname.replace(/^\//, '').split('.').filter(part => { + if (part === 'includeRequire' || part === 'runModule' || + part === 'bundle' || part === 'map') { + return false; + } + return true; + }).join('.') + '.js'; + + return { + sourceMapUrl: pathname.replace(/\.bundle$/, '.map'), + entryFile: entryFile, + dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true), + minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'), + runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true), + inlineSourceMap: this._getBoolOptionFromQuery( + urlObj.query, + 'inlineSourceMap', + false + ), + }; + } + + _getBoolOptionFromQuery(query, opt, defaultVal) { + if (query[opt] == null && defaultVal != null) { + return defaultVal; + } + + return query[opt] === 'true' || query[opt] === '1'; + } } + +module.exports = Server; From 324d154cf7cb364ce1d8ee61c824d8e476e25b31 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Fri, 14 Aug 2015 13:58:19 -0700 Subject: [PATCH 041/139] [ReactNative] Add ability to listen for Packager events --- packager/react-packager/index.js | 1 + packager/react-packager/src/Activity/index.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index 83f312285..4d87733fd 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -22,6 +22,7 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; +exports.activityEvents = Activity.eventEmitter; // Renamed "package" to "bundle". But maintain backwards // compat. diff --git a/packager/react-packager/src/Activity/index.js b/packager/react-packager/src/Activity/index.js index 8e593f9ff..aeaf3143e 100644 --- a/packager/react-packager/src/Activity/index.js +++ b/packager/react-packager/src/Activity/index.js @@ -9,6 +9,7 @@ 'use strict'; var chalk = require('chalk'); +var events = require('events'); var COLLECTION_PERIOD = 1000; @@ -18,6 +19,7 @@ var _queuedActions = []; var _scheduledCollectionTimer = null; var _uuid = 1; var _enabled = true; +var _eventEmitter = new events.EventEmitter(); function endEvent(eventId) { var eventEndTime = Date.now(); @@ -98,6 +100,7 @@ function _runCollection() { function _scheduleAction(action) { _queuedActions.push(action); + _eventEmitter.emit(action.action, action); if (_scheduledCollectionTimer === null) { _scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD); @@ -171,3 +174,4 @@ exports.endEvent = endEvent; exports.signal = signal; exports.startEvent = startEvent; exports.disable = disable; +exports.eventEmitter = _eventEmitter; From 9d96d00163d72123ad374bec6b2676fc0a3ab3c8 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 12:17:01 -0700 Subject: [PATCH 042/139] [react-native] Check in babel-plugin to inline __DEV__ Summary: This plugin resolves __DEV__ to the value of `NODE_ENV === 'development'`. This can then be evaluated and dead code can be removed by uglifyjs. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c9e2e40e3..637edd926 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "absolute-path": "0.0.0", "babel": "5.4.3", "babel-core": "5.6.4", - "babel-plugin-node-env-inline": "^1.0.1", + "babel-plugin-dunderscore-dev-inline": "1.0.1", + "babel-plugin-node-env-inline": "1.0.1", "chalk": "1.0.0", "connect": "2.8.3", "debug": "2.1.0", From 9eb5151bd28d9a513d23f178d47e5a0cba1c3bc4 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 12:21:50 -0700 Subject: [PATCH 043/139] [react-native] Set NODE_ENV and use node-env-inline plugin Summary: This sets NODE_ENV based on the value of the `dev` option when bundling the apps. This would then be inlined by the node-env-inline babel plugin. And finally -- if unreachable -- will be dead-code-eliminated by uglify. This is not used in development because we set NODE_ENV to the value of __DEV__, which can be switched via a query param. However, the plugin has minimal overhead and to avoid complexity in the transformers I just enabled it by default. --- packager/transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packager/transformer.js b/packager/transformer.js index a77119312..0c0a98a92 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -35,6 +35,7 @@ function transform(srcTxt, filename, options) { 'react', 'regenerator', ], + plugins: ['node-env-inline'], sourceFileName: filename, sourceMaps: false, extra: options || {}, From 3388d8fe79901f9cf08ec2f5ecff7c9429b4850d Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 12:29:35 -0700 Subject: [PATCH 044/139] [reat-packager] Switch platform resolution based on query param Summary: Currently the platform selection is controlled by the blacklist. However, since we want to use the same server instance for cross-platform development, we need this to be controlled per request. One outstanding issue, is that the DependencyGraph class wasn't designed that way and it doesn't have a per-request state. This means that with the current design race conditions is possible. If we got a request for a different platfrom while processing the previous request, we may change the outcome of the previous request. To fix this a larger refactor is needed. I'll follow up a diff to do that. Finally, so I don't break the universe like last time, I'll leave it up to the RN guys to update the call sites. --- packager/react-packager/src/Bundler/index.js | 8 ++-- .../DependencyGraph/index.js | 35 ++++++++++++--- .../src/DependencyResolver/index.js | 45 ++++++++++--------- .../src/Server/__tests__/Server-test.js | 25 +++++++++-- packager/react-packager/src/Server/index.js | 9 +++- 5 files changed, 85 insertions(+), 37 deletions(-) diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 4c60924b8..854bd7148 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -118,14 +118,14 @@ class Bundler { return this._cache.end(); } - bundle(main, runModule, sourceMapUrl, isDev) { + bundle(main, runModule, sourceMapUrl, isDev, platform) { const bundle = new Bundle(sourceMapUrl); const transformModule = this._transformModule.bind(this, bundle); const findEventId = Activity.startEvent('find dependencies'); let transformEventId; - return this.getDependencies(main, isDev).then((result) => { + return this.getDependencies(main, isDev, platform).then((result) => { Activity.endEvent(findEventId); transformEventId = Activity.startEvent('transform'); @@ -149,8 +149,8 @@ class Bundler { this._transformer.invalidateFile(filePath); } - getDependencies(main, isDev) { - return this._resolver.getDependencies(main, { dev: isDev }); + getDependencies(main, isDev, platform) { + return this._resolver.getDependencies(main, { dev: isDev, platform }); } _transformModule(bundle, module) { diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 3825507c9..4cdf0c2ee 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -69,7 +69,7 @@ class DependencyGraph { constructor(options) { this._opts = validateOpts(options); this._hasteMap = Object.create(null); - this._immediateResolutionCache = Object.create(null); + this._resetResolutionCache(); this._cache = this._opts.cache; this.load(); } @@ -109,6 +109,20 @@ class DependencyGraph { return this._loading; } + setup({ platform }) { + if (platform && this._opts.platforms.indexOf(platform) === -1) { + throw new Error('Unrecognized platform: ' + platform); + } + + // TODO(amasad): This is a potential race condition. Mutliple requests could + // interfere with each other. This needs a refactor to fix -- which will + // follow this diff. + if (this._platformExt !== platform) { + this._resetResolutionCache(); + } + this._platformExt = platform; + } + resolveDependency(fromModule, toModuleName) { const resHash = resolutionHash(fromModule.path, toModuleName); @@ -250,11 +264,14 @@ class DependencyGraph { ); } - const platformExt = getPlatformExt(entryPath); - if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { - this._platformExt = platformExt; - } else { - this._platformExt = null; + // `platformExt` could be set in the `setup` method. + if (!this._platformExt) { + const platformExt = getPlatformExt(entryPath); + if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { + this._platformExt = platformExt; + } else { + this._platformExt = null; + } } return this._moduleCache.getModule(absolutePath); @@ -563,7 +580,7 @@ class DependencyGraph { _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); + this._resetResolutionCache(); const absPath = path.join(root, filePath); if ((fstat && fstat.isDirectory()) || @@ -599,6 +616,10 @@ class DependencyGraph { }); } } + + _resetResolutionCache() { + this._immediateResolutionCache = Object.create(null); + } } function assetName(file, ext) { diff --git a/packager/react-packager/src/DependencyResolver/index.js b/packager/react-packager/src/DependencyResolver/index.js index 3fdb19fb7..329ae8b7c 100644 --- a/packager/react-packager/src/DependencyResolver/index.js +++ b/packager/react-packager/src/DependencyResolver/index.js @@ -77,6 +77,10 @@ var getDependenciesValidateOpts = declareOpts({ type: 'boolean', default: true, }, + platform: { + type: 'string', + required: false, + }, }); HasteDependencyResolver.prototype.getDependencies = function(main, options) { @@ -85,28 +89,27 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph - .load() - .then(() => Promise.all([ - depGraph.getOrderedDependencies(main), - depGraph.getAsyncDependencies(main), - ])) - .then( - ([dependencies, asyncDependencies]) => dependencies[0].getName().then( - mainModuleId => { - self._prependPolyfillDependencies( - dependencies, - opts.dev, - ); + depGraph.setup({ platform: opts.platform }); - return { - mainModuleId, - dependencies, - asyncDependencies, - }; - } - ) - ); + return Promise.all([ + depGraph.getOrderedDependencies(main), + depGraph.getAsyncDependencies(main), + ]).then( + ([dependencies, asyncDependencies]) => dependencies[0].getName().then( + mainModuleId => { + self._prependPolyfillDependencies( + dependencies, + opts.dev, + ); + + return { + mainModuleId, + dependencies, + asyncDependencies, + }; + } + ) + ); }; HasteDependencyResolver.prototype._prependPolyfillDependencies = function( diff --git a/packager/react-packager/src/Server/__tests__/Server-test.js b/packager/react-packager/src/Server/__tests__/Server-test.js index 1677776d6..9e0a6890c 100644 --- a/packager/react-packager/src/Server/__tests__/Server-test.js +++ b/packager/react-packager/src/Server/__tests__/Server-test.js @@ -110,7 +110,24 @@ describe('processRequest', () => { 'index.ios.js', true, 'index.ios.includeRequire.map', - true + true, + undefined + ); + }); + }); + + pit('passes in the platform param', function() { + return makeRequest( + requestHandler, + 'index.bundle?platform=ios' + ).then(function(response) { + expect(response).toEqual('this is the source'); + expect(Bundler.prototype.bundle).toBeCalledWith( + 'index.js', + true, + 'index.map', + true, + 'ios', ); }); }); @@ -241,7 +258,8 @@ describe('processRequest', () => { 'foo file', true, undefined, - true + true, + undefined ); }); }); @@ -253,7 +271,8 @@ describe('processRequest', () => { 'path/to/foo.js', false, '/path/to/foo.map', - false + false, + undefined ); }); }); diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 8ff8fda45..8f11be3c9 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -88,6 +88,10 @@ const bundleOpts = declareOpts({ type: 'boolean', default: false, }, + platform: { + type: 'string', + required: false, + } }); class Server { @@ -152,12 +156,12 @@ class Server { buildBundle(options) { const opts = bundleOpts(options); - return this._bundler.bundle( opts.entryFile, opts.runModule, opts.sourceMapUrl, - opts.dev + opts.dev, + opts.platform ); } @@ -425,6 +429,7 @@ class Server { 'inlineSourceMap', false ), + platform: urlObj.query.platform, }; } From ca389084230298e3c02da9792f2852c55414be27 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Sat, 15 Aug 2015 15:59:37 -0700 Subject: [PATCH 045/139] [react-packager] In production resolve __DEV__ to NODE_ENV === 'development' --- packager/transformer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packager/transformer.js b/packager/transformer.js index 0c0a98a92..8f7a48c29 100644 --- a/packager/transformer.js +++ b/packager/transformer.js @@ -13,6 +13,12 @@ var babel = require('babel-core'); function transform(srcTxt, filename, options) { + var plugins = []; + + if (process.env.NODE_ENV === 'production') { + plugins = plugins.concat(['node-env-inline', 'dunderscore-dev-inline']); + } + var result = babel.transform(srcTxt, { retainLines: true, compact: true, @@ -35,7 +41,7 @@ function transform(srcTxt, filename, options) { 'react', 'regenerator', ], - plugins: ['node-env-inline'], + plugins: plugins, sourceFileName: filename, sourceMaps: false, extra: options || {}, From 8460db57bce6a63860897094ed11c5856e2658e7 Mon Sep 17 00:00:00 2001 From: Ludo Fardel Date: Mon, 17 Aug 2015 02:05:29 -0700 Subject: [PATCH 046/139] Make flow check async --- .../InitializeJavaScriptAppEngine.js | 8 +++ .../Initialization/checkFlowAtRuntime.js | 59 +++++++++++++++++++ .../Initialization/parseErrorStack.js | 2 +- packager/getFlowTypeCheckMiddleware.js | 51 +++++++++++++--- 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index 1aefe0c58..f53a59024 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -59,6 +59,13 @@ function setUpRedBoxConsoleErrorHandler() { } } +function setupFlowChecker() { + if (__DEV__) { + var checkFlowAtRuntime = require('checkFlowAtRuntime'); + checkFlowAtRuntime(); + } +} + /** * Sets up a set of window environment wrappers that ensure that the * BatchedBridge is flushed after each tick. In both the case of the @@ -143,3 +150,4 @@ setUpGeolocation(); setUpWebSockets(); setupProfile(); setUpProcessEnv(); +setupFlowChecker(); diff --git a/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js b/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js new file mode 100644 index 000000000..8165ae667 --- /dev/null +++ b/Libraries/JavaScriptAppEngine/Initialization/checkFlowAtRuntime.js @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule checkFlowAtRuntime + * + */ +'use strict'; + +function checkFlowAtRuntime() { + var url = getPackagerURL(); + if (!url) { + return; + } + fetch(url + 'flow/') + .then(response => response.json()) + .then(response => { + if (response.silentError) { + return; + } + throw { + message: response.message, + stack: response.errors.map(err => { + return { + ...err, + methodName: err.description, + file: err.filename, + }; + }), + }; + }, + () => { + //if fetch fails, silently give up + }) + .done(); +} + +function getPackagerURL() { + var NativeModules = require('NativeModules'); + var scriptURL = (NativeModules + && NativeModules.SourceCode + && NativeModules.SourceCode.scriptURL) + || ''; + + // extract the url of the packager from the whole scriptURL + // we match until the first / after http(s):// + // i.e. http://www.mypackger.com/debug/my/bundle -> http://www.mypackger.com/ + return getFirstOrNull(scriptURL.match(/^https?:\/\/[^/]+\//)); +} + +function getFirstOrNull(ar) { + return ar ? ar[0] : null; +} + +module.exports = checkFlowAtRuntime; diff --git a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js index bbaa1a276..63076c7b8 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js +++ b/Libraries/JavaScriptAppEngine/Initialization/parseErrorStack.js @@ -32,7 +32,7 @@ function parseErrorStack(e, sourceMapInstance) { return []; } - var stack = stacktraceParser.parse(e.stack); + var stack = Array.isArray(e.stack) ? e.stack : stacktraceParser.parse(e.stack); var framesToPop = e.framesToPop || 0; while (framesToPop--) { diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js index c7f3e2b1c..3b6597610 100644 --- a/packager/getFlowTypeCheckMiddleware.js +++ b/packager/getFlowTypeCheckMiddleware.js @@ -10,17 +10,23 @@ var chalk = require('chalk'); var exec = require('child_process').exec; +var url = require('url'); var Activity = require('./react-packager/src/Activity'); var hasWarned = {}; -var DISABLE_FLOW_CHECK = true; // temporarily disable while we figure out versioning issues. function getFlowTypeCheckMiddleware(options) { return function(req, res, next) { - var isBundle = req.url.indexOf('.bundle') !== -1; - if (DISABLE_FLOW_CHECK || options.skipflow || !isBundle) { + var reqObj = url.parse(req.url); + var isFlowCheck = (reqObj.path.match(/^\/flow\//)); + + if (!isFlowCheck) { return next(); } + if (options.skipflow) { + _endSkipFlow(res); + return; + } if (options.flowroot || options.projectRoots.length === 1) { var flowroot = options.flowroot || options.projectRoots[0]; } else { @@ -28,7 +34,8 @@ function getFlowTypeCheckMiddleware(options) { hasWarned.noRoot = true; console.warn('flow: No suitable root'); } - return next(); + _endFlowBad(res); + return; } exec('command -v flow >/dev/null 2>&1', function(error, stdout) { if (error) { @@ -37,7 +44,8 @@ function getFlowTypeCheckMiddleware(options) { console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' + '`brew install flow`.')); } - return next(); + _endFlowBad(res); + return; } else { return doFlowTypecheck(res, flowroot, next); } @@ -51,7 +59,8 @@ function doFlowTypecheck(res, flowroot, next) { exec(flowCmd, function(flowError, stdout, stderr) { Activity.endEvent(eventId); if (!flowError) { - return next(); + _endFlowOk(res); + return; } else { try { var flowResponse = JSON.parse(stdout); @@ -73,7 +82,7 @@ function doFlowTypecheck(res, flowroot, next) { errorNum++; }); var error = { - status: 500, + status: 200, message: 'Flow found type errors. If you think these are wrong, ' + 'make sure your flow bin and .flowconfig are up to date, or ' + 'disable with --skipflow.', @@ -102,10 +111,36 @@ function doFlowTypecheck(res, flowroot, next) { )); } } - return next(); + _endFlowBad(res); + return; } } }); } +function _endRes(res, message, code, silentError) { + res.writeHead(code, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify({ + message: message, + errors: [], + silentError: silentError, + })); +} + +function _endFlowOk(res) { + _endRes(res, 'No Flow Error', '200', true); +} + +function _endFlowBad(res) { + // we want to show that flow failed + // status 200 is need for the fetch to not be rejected + _endRes(res, 'Flow failed to run! Please look at the console for more details.', '200', false); +} + +function _endSkipFlow(res) { + _endRes(res, 'Flow was skipped, check the server options', '200', true); +} + module.exports = getFlowTypeCheckMiddleware; From 261f9434e54274a68d2cb50644c436197ef48baa Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 17 Aug 2015 04:38:19 -0700 Subject: [PATCH 047/139] Avoid dispatch_async in RCTRootView when bridge has already started Summary: There's no good reason for initialProperties to be mutable after the RCTRootView has been created. Passing it in through the constructor means we can skip one dispatch_async. --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 3 +- .../UIExplorerUnitTests/RCTAllocationTests.m | 3 +- Libraries/RCTTest/RCTTestRunner.m | 3 +- React/Base/RCTRootView.h | 6 ++- React/Base/RCTRootView.m | 41 ++++++++++--------- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index f2a83422b..5a3a7d87d 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -30,7 +30,8 @@ launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge - moduleName:@"UIExplorerApp"]; + moduleName:@"UIExplorerApp" + initialProperties:nil]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [[UIViewController alloc] init]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index 7fe2a7445..b17fd3ddc 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -82,6 +82,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a @autoreleasepool { RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:nil moduleName:@"" + initialProperties:nil launchOptions:nil]; weakBridge = view.bridge; XCTAssertNotNil(weakBridge, @"RCTBridge should have been created"); @@ -180,7 +181,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a launchOptions:nil]; __weak UIView *rootContentView; @autoreleasepool { - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"" initialProperties:nil]; RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"])) XCTAssertTrue(rootContentView.userInteractionEnabled, @"RCTContentView should be valid"); (void)rootView; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index f270c3b81..d0326112f 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -87,8 +87,7 @@ RCT_NOT_IMPLEMENTED(-init) moduleProvider:_moduleProvider launchOptions:nil]; - RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName]; - rootView.initialProperties = initialProps; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:initialProps]; rootView.frame = CGRectMake(0, 0, 320, 2000); // Constant size for testing on multiple devices NSString *testModuleName = RCTBridgeModuleNameForClass([RCTTestModule class]); diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index d18ac176c..80f86d0cb 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -29,7 +29,8 @@ extern NSString *const RCTContentDidAppearNotification; * - Designated initializer - */ - (instancetype)initWithBridge:(RCTBridge *)bridge - moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER; + moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER; /** * - Convenience initializer - @@ -40,6 +41,7 @@ extern NSString *const RCTContentDidAppearNotification; */ - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions; /** @@ -60,7 +62,7 @@ extern NSString *const RCTContentDidAppearNotification; * The default properties to apply to the view when the script bundle * is first loaded. Defaults to nil/empty. */ -@property (nonatomic, copy) NSDictionary *initialProperties; +@property (nonatomic, copy, readonly) NSDictionary *initialProperties; /** * The class of the RCTJavaScriptExecutor to use with this view. diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index a804c810b..e2ae3e60d 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -58,6 +58,7 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat - (instancetype)initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties { RCTAssertMainThread(); RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView"); @@ -69,6 +70,7 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat _bridge = bridge; _moduleName = moduleName; + _initialProperties = [initialProperties copy]; _loadingViewFadeDelay = 0.25; _loadingViewFadeDuration = 0.25; @@ -81,7 +83,7 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat selector:@selector(hideLoadingView) name:RCTContentDidAppearNotification object:self]; - if (!_bridge.batchedBridge.isLoading) { + if (!_bridge.loading) { [self bundleFinishedLoading:_bridge.batchedBridge]; } @@ -92,13 +94,14 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *)moduleName + initialProperties:(NSDictionary *)initialProperties launchOptions:(NSDictionary *)launchOptions { RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:nil launchOptions:launchOptions]; - return [self initWithBridge:bridge moduleName:moduleName]; + return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties]; } RCT_NOT_IMPLEMENTED(-initWithFrame:(CGRect)frame) @@ -158,30 +161,30 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) - (void)javaScriptDidLoad:(NSNotification *)notification { RCTBridge *bridge = notification.userInfo[@"bridge"]; - [self bundleFinishedLoading:bridge]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self bundleFinishedLoading:bridge]; + }); } - (void)bundleFinishedLoading:(RCTBridge *)bridge { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!bridge.isValid) { - return; - } + if (!bridge.valid) { + return; + } - [_contentView removeFromSuperview]; - _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge]; - _contentView.backgroundColor = self.backgroundColor; - [self insertSubview:_contentView atIndex:0]; + [_contentView removeFromSuperview]; + _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds bridge:bridge]; + _contentView.backgroundColor = self.backgroundColor; + [self insertSubview:_contentView atIndex:0]; - NSString *moduleName = _moduleName ?: @""; - NSDictionary *appParameters = @{ - @"rootTag": _contentView.reactTag, - @"initialProps": _initialProperties ?: @{}, - }; + NSString *moduleName = _moduleName ?: @""; + NSDictionary *appParameters = @{ + @"rootTag": _contentView.reactTag, + @"initialProps": _initialProperties ?: @{}, + }; - [bridge enqueueJSCall:@"AppRegistry.runApplication" - args:@[moduleName, appParameters]]; - }); + [bridge enqueueJSCall:@"AppRegistry.runApplication" + args:@[moduleName, appParameters]]; } - (void)layoutSubviews From 12d5f0d6f4ae2aa9359169cd30ae1b26864ac206 Mon Sep 17 00:00:00 2001 From: James Ide Date: Mon, 17 Aug 2015 05:53:42 -0700 Subject: [PATCH 048/139] [Views] Change rasterization scale to the main screen's scale Summary: `view.screen` can be nil if the view has not yet been added to the view hierarchy (e.g. new view), so we should use `[UIScreen mainScreen]` instead. In the future, if we need to support multiple screens, one possible fix is to set the rasterization scale in didMoveToWindow/Superview. For now we have just one screen, though. Closes https://github.com/facebook/react-native/pull/2334 Github Author: James Ide --- React/Views/RCTViewManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index e6feb5ca9..9c7a97483 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -126,7 +126,7 @@ RCT_REMAP_VIEW_PROPERTY(overflow, clipsToBounds, css_clip_t) RCT_CUSTOM_VIEW_PROPERTY(shouldRasterizeIOS, BOOL, RCTView) { view.layer.shouldRasterize = json ? [RCTConvert BOOL:json] : defaultView.layer.shouldRasterize; - view.layer.rasterizationScale = view.layer.shouldRasterize ? view.window.screen.scale : defaultView.layer.rasterizationScale; + view.layer.rasterizationScale = view.layer.shouldRasterize ? [UIScreen mainScreen].scale : defaultView.layer.rasterizationScale; } RCT_CUSTOM_VIEW_PROPERTY(transformMatrix, CATransform3D, RCTView) { From 65713992c4bfe783197147947e201b3f51db5fa6 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 17 Aug 2015 06:11:29 -0700 Subject: [PATCH 049/139] Let native modules know when root views get added and removed --- React/Modules/RCTUIManager.h | 17 +++++++++++++++++ React/Modules/RCTUIManager.m | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index beb9e5d5a..d7d91c997 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -20,6 +20,23 @@ */ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification; +/** + * Posted whenever a new root view is registered with RCTUIManager. The userInfo property + * will contain a RCTUIManagerRootViewKey with the registered root view. + */ +RCT_EXTERN NSString *const RCTUIManagerDidRegisterRootViewNotification; + +/** + * Posted whenever a root view is removed from the RCTUIManager. The userInfo property + * will contain a RCTUIManagerRootViewKey with the removed root view. + */ +RCT_EXTERN NSString *const RCTUIManagerDidRemoveRootViewNotification; + +/** + * Key for the root view property in the above notifications + */ +RCT_EXTERN NSString *const RCTUIManagerRootViewKey; + @protocol RCTScrollableProtocol; /** diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 19d478284..d1a29d3a8 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -41,6 +41,9 @@ static void RCTTraverseViewNodes(id view, void (^block)(id_shadowViewRegistry[shadowView.reactTag] = shadowView; [strongSelf->_rootViewTags addObject:reactTag]; }); + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification + object:self + userInfo:@{ RCTUIManagerRootViewKey: rootView }]; } - (UIView *)viewForReactTag:(NSNumber *)reactTag @@ -637,6 +644,10 @@ RCT_EXPORT_METHOD(removeRootView:(nonnull NSNumber *)rootReactTag) UIView *rootView = viewRegistry[rootReactTag]; [uiManager _purgeChildren:rootView.reactSubviews fromRegistry:viewRegistry]; viewRegistry[rootReactTag] = nil; + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRemoveRootViewNotification + object:uiManager + userInfo:@{ RCTUIManagerRootViewKey: rootView }]; }]; } From bce7b9c63861ba722ed4e423edf2992ece0d6e27 Mon Sep 17 00:00:00 2001 From: Mike Armstrong Date: Mon, 17 Aug 2015 06:10:20 -0700 Subject: [PATCH 050/139] generalise handling of uniforms and samplers --- Libraries/Utilities/MatrixMath.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Libraries/Utilities/MatrixMath.js b/Libraries/Utilities/MatrixMath.js index 6103e1d1e..21ad74ae2 100755 --- a/Libraries/Utilities/MatrixMath.js +++ b/Libraries/Utilities/MatrixMath.js @@ -31,6 +31,23 @@ var MatrixMath = { ]; }, + createOrthographic: function(left, right, bottom, top, near, far) { + var a = 2 / (right - left); + var b = 2 / (top - bottom); + var c = -2 / (far - near); + + var tx = -(right + left) / (right - left); + var ty = -(top + bottom) / (top - bottom); + var tz = -(far + near) / (far - near); + + return [ + a, 0, 0, 0, + 0, b, 0, 0, + 0, 0, c, 0, + tx, ty, tz, 1 + ]; + }, + createFrustum: function(left, right, bottom, top, near, far) { var r_width = 1 / (right - left); var r_height = 1 / (top - bottom); @@ -49,6 +66,19 @@ var MatrixMath = { ]; }, + createPerspective: function(fovInRadians, aspect, near, far) { + var h = 1 / Math.tan(fovInRadians); + var r_depth = 1 / (near - far); + var C = (far + near) * r_depth; + var D = 2 * (far * near * r_depth); + return [ + h/aspect, 0, 0, 0, + 0, h, 0, 0, + 0, 0, C,-1, + 0, 0, D, 0, + ]; + }, + createTranslate2d: function(x, y) { var mat = MatrixMath.createIdentityMatrix(); MatrixMath.reuseTranslate2dCommand(mat, x, y); From 869ff92dbcda38085b8c87f0f7db5bb5ce2d85e1 Mon Sep 17 00:00:00 2001 From: Dorota Kapturkiewicz Date: Mon, 17 Aug 2015 06:20:07 -0700 Subject: [PATCH 051/139] [ReactNative] add importantForAccessibility to accessibility sample app --- .../UIExplorer/AccessibilityAndroidExample.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/Examples/UIExplorer/AccessibilityAndroidExample.js b/Examples/UIExplorer/AccessibilityAndroidExample.js index 92a511bcc..d75907a86 100644 --- a/Examples/UIExplorer/AccessibilityAndroidExample.js +++ b/Examples/UIExplorer/AccessibilityAndroidExample.js @@ -27,6 +27,8 @@ var AccessibilityInfo = require('AccessibilityInfo'); var UIExplorerBlock = require('./UIExplorerBlock'); var UIExplorerPage = require('./UIExplorerPage'); +var importantForAccessibilityValues = ['auto', 'yes', 'no', 'no-hide-descendants']; + var AccessibilityAndroidExample = React.createClass({ statics: { @@ -38,6 +40,8 @@ var AccessibilityAndroidExample = React.createClass({ return { count: 0, talkbackEnabled: false, + backgroundImportantForAcc: 0, + forgroundImportantForAcc: 0, }; }, @@ -79,6 +83,18 @@ var AccessibilityAndroidExample = React.createClass({ }); }, + _changeBackgroundImportantForAcc: function() { + this.setState({ + backgroundImportantForAcc: (this.state.backgroundImportantForAcc + 1) % 4, + }); + }, + + _changeForgroundImportantForAcc: function() { + this.setState({ + forgroundImportantForAcc: (this.state.forgroundImportantForAcc + 1) % 4, + }); + }, + render: function() { return ( @@ -148,6 +164,77 @@ var AccessibilityAndroidExample = React.createClass({ + + + + + + Hello + + + + + + + world + + + + + + + + Change importantForAccessibility for background layout. + + + + + + Background layout importantForAccessibility + + + {importantForAccessibilityValues[this.state.backgroundImportantForAcc]} + + + + + + Change importantForAccessibility for forground layout. + + + + + + Forground layout importantForAccessibility + + + {importantForAccessibilityValues[this.state.forgroundImportantForAcc]} + + + + ); }, @@ -158,6 +245,12 @@ var styles = StyleSheet.create({ backgroundColor: 'yellow', padding:10, }, + container: { + flex: 1, + backgroundColor: 'white', + padding: 10, + height:150, + }, }); module.exports = AccessibilityAndroidExample; From 8d1e02b8bd09f7a94be941a0c078f0fe4ed60bf5 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 17 Aug 2015 07:35:34 -0700 Subject: [PATCH 052/139] Convert alloc/init to new to please linter --- .../SampleApp/iOS/SampleApp/AppDelegate.m | 2 +- Examples/UIExplorer/UIExplorer/AppDelegate.m | 2 +- .../RCTUIManagerScenarioTests.m | 4 +-- .../UIExplorerUnitTests/RCTAllocationTests.m | 4 +-- .../UIExplorerUnitTests/RCTCacheTests.m | 2 +- .../RCTContextExecutorTests.m | 4 +-- .../RCTEventDispatcherTests.m | 2 +- .../UIExplorerUnitTests/RCTGzipTests.m | 2 +- .../UIExplorerUnitTests/RCTShadowViewTests.m | 2 +- .../UIExplorerUnitTests/RCTSparseArrayTests.m | 6 ++--- .../UIExplorerUnitTests/RCTUIManagerTests.m | 4 +-- .../ActionSheetIOS/RCTActionSheetManager.m | 4 +-- Libraries/Geolocation/RCTLocationObserver.m | 6 ++--- Libraries/Image/RCTCameraRollManager.m | 2 +- Libraries/Image/RCTImageDownloader.m | 2 +- Libraries/Image/RCTImageLoader.m | 4 +-- Libraries/Image/RCTImagePickerManager.m | 12 ++++----- Libraries/Image/RCTImageStoreManager.m | 2 +- Libraries/Network/RCTDownloadTask.m | 2 +- Libraries/Network/RCTHTTPRequestHandler.m | 2 +- Libraries/Network/RCTNetworking.m | 6 ++--- .../RCTPushNotificationManager.m | 4 +-- .../FBSnapshotTestController.m | 2 +- Libraries/RCTTest/RCTTestRunner.m | 2 +- Libraries/Text/RCTRawTextManager.m | 2 +- Libraries/Text/RCTShadowText.m | 8 +++--- Libraries/Text/RCTText.m | 2 +- Libraries/Text/RCTTextField.m | 2 +- Libraries/Text/RCTTextManager.m | 4 +-- Libraries/WebSocket/RCTSRWebSocket.m | 18 ++++++------- Libraries/WebSocket/RCTWebSocketExecutor.m | 4 +-- Libraries/WebSocket/RCTWebSocketManager.m | 2 +- React/Base/RCTAssert.m | 2 +- React/Base/RCTBatchedBridge.m | 24 ++++++++--------- React/Base/RCTBridge.m | 2 +- React/Base/RCTCache.m | 8 +++--- React/Base/RCTConvert.m | 10 +++---- React/Base/RCTEventDispatcher.m | 8 +++--- React/Base/RCTFPSGraph.m | 2 +- React/Base/RCTJavaScriptLoader.m | 2 +- React/Base/RCTKeyCommands.m | 4 +-- React/Base/RCTLog.m | 6 ++--- React/Base/RCTModuleData.m | 6 ++--- React/Base/RCTPerfStats.m | 2 +- React/Base/RCTProfile.m | 8 +++--- React/Base/RCTRedBox.m | 8 +++--- React/Base/RCTSparseArray.m | 2 +- React/Base/RCTTouchHandler.m | 8 +++--- React/Executors/RCTContextExecutor.m | 2 +- React/Executors/RCTWebViewExecutor.m | 4 +-- React/Modules/RCTAccessibilityManager.m | 2 +- React/Modules/RCTAlertManager.m | 6 ++--- React/Modules/RCTAsyncLocalStorage.m | 8 +++--- React/Modules/RCTDevMenu.m | 4 +-- React/Modules/RCTTiming.m | 4 +-- React/Modules/RCTUIManager.m | 26 +++++++++---------- React/Views/RCTActivityIndicatorViewManager.m | 2 +- React/Views/RCTComponentData.m | 6 ++--- React/Views/RCTConvert+MapKit.m | 4 +-- React/Views/RCTDatePickerManager.m | 4 +-- React/Views/RCTMap.m | 10 +++---- React/Views/RCTMapManager.m | 2 +- React/Views/RCTModalHostView.m | 2 +- React/Views/RCTNavItemManager.m | 2 +- React/Views/RCTPickerManager.m | 2 +- React/Views/RCTProgressViewManager.m | 4 +-- React/Views/RCTScrollView.m | 4 +-- React/Views/RCTSegmentedControlManager.m | 2 +- React/Views/RCTSliderManager.m | 2 +- React/Views/RCTSwitchManager.m | 2 +- React/Views/RCTTabBar.m | 4 +-- React/Views/RCTTabBarItem.m | 2 +- React/Views/RCTTabBarItemManager.m | 2 +- React/Views/RCTViewManager.m | 4 +-- 74 files changed, 172 insertions(+), 172 deletions(-) diff --git a/Examples/SampleApp/iOS/SampleApp/AppDelegate.m b/Examples/SampleApp/iOS/SampleApp/AppDelegate.m index 7e8d5fecf..762a35439 100644 --- a/Examples/SampleApp/iOS/SampleApp/AppDelegate.m +++ b/Examples/SampleApp/iOS/SampleApp/AppDelegate.m @@ -50,7 +50,7 @@ launchOptions:launchOptions]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [[UIViewController alloc] init]; + UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 5a3a7d87d..f394c9903 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -34,7 +34,7 @@ initialProperties:nil]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [[UIViewController alloc] init]; + UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m index c7d30539f..923f32caa 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m @@ -45,11 +45,11 @@ { [super setUp]; - _uiManager = [[RCTUIManager alloc] init]; + _uiManager = [RCTUIManager new]; // Register 20 views to use in the tests for (NSInteger i = 1; i <= 20; i++) { - UIView *registeredView = [[UIView alloc] init]; + UIView *registeredView = [UIView new]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index b17fd3ddc..670530114 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -94,7 +94,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a - (void)testModulesAreInvalidated { - AllocationTestModule *module = [[AllocationTestModule alloc] init]; + AllocationTestModule *module = [AllocationTestModule new]; @autoreleasepool { RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ @@ -113,7 +113,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a { __weak AllocationTestModule *weakModule; @autoreleasepool { - AllocationTestModule *module = [[AllocationTestModule alloc] init]; + AllocationTestModule *module = [AllocationTestModule new]; RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[module]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m index 30313d23b..b6bc22188 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTCacheTests.m @@ -37,7 +37,7 @@ - (void)setUp { - self.cache = [[RCTCache alloc] init]; + self.cache = [RCTCache new]; self.cache.countLimit = 3; self.cache.totalCostLimit = 100; } diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m index f7db2d46f..fa48fcfbf 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTContextExecutorTests.m @@ -33,7 +33,7 @@ - (void)setUp { [super setUp]; - _executor = [[RCTContextExecutor alloc] init]; + _executor = [RCTContextExecutor new]; [_executor setUp]; } @@ -129,7 +129,7 @@ static uint64_t _get_time_nanoseconds(void) "; [_executor executeApplicationScript:script sourceURL:[NSURL URLWithString:@"http://localhost:8081/"] onComplete:^(__unused NSError *error) { - NSMutableArray *params = [[NSMutableArray alloc] init]; + NSMutableArray *params = [NSMutableArray new]; id data = @1; for (int i = 0; i < 4; i++) { double samples[runs / frequency]; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m index 96e6bd45c..bfa52b94f 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m @@ -61,7 +61,7 @@ [super setUp]; _bridge = [OCMockObject mockForClass:[RCTBridge class]]; - _eventDispatcher = [[RCTEventDispatcher alloc] init]; + _eventDispatcher = [RCTEventDispatcher new]; ((id)_eventDispatcher).bridge = _bridge; _eventName = RCTNormalizeInputEventName(@"sampleEvent"); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m index 5e74bf7c4..ceb58b972 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTGzipTests.m @@ -67,7 +67,7 @@ extern BOOL RCTIsGzippedData(NSData *data); @"headers": @{@"Content-Encoding": @"gzip"}, }; - RCTNetworking *networker = [[RCTNetworking alloc] init]; + RCTNetworking *networker = [RCTNetworking new]; __block NSURLRequest *request = nil; [networker buildRequest:query completionBlock:^(NSURLRequest *_request) { request = _request; diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m index f396131d5..9bdda8a8d 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTShadowViewTests.m @@ -99,7 +99,7 @@ - (RCTShadowView *)_shadowViewWithStyle:(void(^)(css_style_t *style))styleBlock { - RCTShadowView *shadowView = [[RCTShadowView alloc] init]; + RCTShadowView *shadowView = [RCTShadowView new]; css_style_t style = shadowView.cssNode->style; styleBlock(&style); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m index cee52d036..57e7a0bf2 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTSparseArrayTests.m @@ -25,13 +25,13 @@ - (void)testDictionary { - id myView = [[UIView alloc] init]; + id myView = [UIView new]; myView.reactTag = @4; - id myOtherView = [[UIView alloc] init]; + id myOtherView = [UIView new]; myOtherView.reactTag = @5; - RCTSparseArray *registry = [[RCTSparseArray alloc] init]; + RCTSparseArray *registry = [RCTSparseArray new]; XCTAssertNil(registry[@4], @"how did you have a view when none are registered?"); XCTAssertNil(registry[@5], @"how did you have a view when none are registered?"); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index b587d9bca..a4cf3150c 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -44,11 +44,11 @@ { [super setUp]; - _uiManager = [[RCTUIManager alloc] init]; + _uiManager = [RCTUIManager new]; // Register 20 views to use in the tests for (NSInteger i = 1; i <= 20; i++) { - UIView *registeredView = [[UIView alloc] init]; + UIView *registeredView = [UIView new]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; } diff --git a/Libraries/ActionSheetIOS/RCTActionSheetManager.m b/Libraries/ActionSheetIOS/RCTActionSheetManager.m index 56df9f38d..22abba34e 100644 --- a/Libraries/ActionSheetIOS/RCTActionSheetManager.m +++ b/Libraries/ActionSheetIOS/RCTActionSheetManager.m @@ -27,7 +27,7 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _callbacks = [[NSMutableDictionary alloc] init]; + _callbacks = [NSMutableDictionary new]; } return self; } @@ -41,7 +41,7 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options failureCallback:(__unused RCTResponseSenderBlock)failureCallback successCallback:(RCTResponseSenderBlock)successCallback) { - UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + UIActionSheet *actionSheet = [UIActionSheet new]; actionSheet.title = options[@"title"]; diff --git a/Libraries/Geolocation/RCTLocationObserver.m b/Libraries/Geolocation/RCTLocationObserver.m index 0fe5fed75..8efd16787 100644 --- a/Libraries/Geolocation/RCTLocationObserver.m +++ b/Libraries/Geolocation/RCTLocationObserver.m @@ -115,11 +115,11 @@ RCT_EXPORT_MODULE() { if ((self = [super init])) { - _locationManager = [[CLLocationManager alloc] init]; + _locationManager = [CLLocationManager new]; _locationManager.distanceFilter = RCT_DEFAULT_LOCATION_ACCURACY; _locationManager.delegate = self; - _pendingRequests = [[NSMutableArray alloc] init]; + _pendingRequests = [NSMutableArray new]; } return self; } @@ -231,7 +231,7 @@ RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options } // Create request - RCTLocationRequest *request = [[RCTLocationRequest alloc] init]; + RCTLocationRequest *request = [RCTLocationRequest new]; request.successBlock = successBlock; request.errorBlock = errorBlock ?: ^(NSArray *args){}; request.options = options; diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m index d28198ac0..afe4d7284 100644 --- a/Libraries/Image/RCTCameraRollManager.m +++ b/Libraries/Image/RCTCameraRollManager.m @@ -94,7 +94,7 @@ RCT_EXPORT_METHOD(getPhotos:(NSDictionary *)params BOOL __block foundAfter = NO; BOOL __block hasNextPage = NO; BOOL __block calledCallback = NO; - NSMutableArray *assets = [[NSMutableArray alloc] init]; + NSMutableArray *assets = [NSMutableArray new]; [_bridge.assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 6213431e2..22bbe9031 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -30,7 +30,7 @@ RCT_EXPORT_MODULE() static RCTImageDownloader *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[RCTImageDownloader alloc] init]; + sharedInstance = [RCTImageDownloader new]; }); return sharedInstance; } diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index dc0fbf640..575842c46 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -114,7 +114,7 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, - (ALAssetsLibrary *)assetsLibrary { if (!_assetsLibrary) { - _assetsLibrary = [[ALAssetsLibrary alloc] init]; + _assetsLibrary = [ALAssetsLibrary new]; } return _assetsLibrary; } @@ -181,7 +181,7 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, PHAsset *asset = [results firstObject]; - PHImageRequestOptions *imageOptions = [[PHImageRequestOptions alloc] init]; + PHImageRequestOptions *imageOptions = [PHImageRequestOptions new]; BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero); CGSize targetSize; diff --git a/Libraries/Image/RCTImagePickerManager.m b/Libraries/Image/RCTImagePickerManager.m index 7fad953b0..107f399b6 100644 --- a/Libraries/Image/RCTImagePickerManager.m +++ b/Libraries/Image/RCTImagePickerManager.m @@ -31,9 +31,9 @@ RCT_EXPORT_MODULE(ImagePickerIOS); - (instancetype)init { if ((self = [super init])) { - _pickers = [[NSMutableArray alloc] init]; - _pickerCallbacks = [[NSMutableArray alloc] init]; - _pickerCancelCallbacks = [[NSMutableArray alloc] init]; + _pickers = [NSMutableArray new]; + _pickerCallbacks = [NSMutableArray new]; + _pickerCancelCallbacks = [NSMutableArray new]; } return self; } @@ -56,7 +56,7 @@ RCT_EXPORT_METHOD(openCameraDialog:(NSDictionary *)config UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; UIViewController *rootViewController = keyWindow.rootViewController; - UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; + UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; @@ -78,11 +78,11 @@ RCT_EXPORT_METHOD(openSelectDialog:(NSDictionary *)config UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; UIViewController *rootViewController = keyWindow.rootViewController; - UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; + UIImagePickerController *imagePicker = [UIImagePickerController new]; imagePicker.delegate = self; imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - NSMutableArray *allowedTypes = [[NSMutableArray alloc] init]; + NSMutableArray *allowedTypes = [NSMutableArray new]; if ([config[@"showImages"] boolValue]) { [allowedTypes addObject:(NSString *)kUTTypeImage]; } diff --git a/Libraries/Image/RCTImageStoreManager.m b/Libraries/Image/RCTImageStoreManager.m index e751466f9..2193dd01f 100644 --- a/Libraries/Image/RCTImageStoreManager.m +++ b/Libraries/Image/RCTImageStoreManager.m @@ -26,7 +26,7 @@ RCT_EXPORT_MODULE() if ((self = [super init])) { // TODO: need a way to clear this store - _store = [[NSMutableDictionary alloc] init]; + _store = [NSMutableDictionary new]; } return self; } diff --git a/Libraries/Network/RCTDownloadTask.m b/Libraries/Network/RCTDownloadTask.m index 5b065675f..f86b4b990 100644 --- a/Libraries/Network/RCTDownloadTask.m +++ b/Libraries/Network/RCTDownloadTask.m @@ -101,7 +101,7 @@ RCT_NOT_IMPLEMENTED(-init) { if ([self validateRequestToken:requestToken]) { if (!_data) { - _data = [[NSMutableData alloc] init]; + _data = [NSMutableData new]; } [_data appendData:data]; if (_incrementalDataBlock) { diff --git a/Libraries/Network/RCTHTTPRequestHandler.m b/Libraries/Network/RCTHTTPRequestHandler.m index d5ee89a45..37165cfff 100644 --- a/Libraries/Network/RCTHTTPRequestHandler.m +++ b/Libraries/Network/RCTHTTPRequestHandler.m @@ -55,7 +55,7 @@ RCT_EXPORT_MODULE() { // Lazy setup if (!_session && [self isValid]) { - NSOperationQueue *callbackQueue = [[NSOperationQueue alloc] init]; + NSOperationQueue *callbackQueue = [NSOperationQueue new]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:configuration delegate:self diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index a2f0475e2..cbd8ebbfc 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -65,7 +65,7 @@ static NSString *RCTGenerateFormBoundary() parts = [formData mutableCopy]; _callback = callback; - multipartBody = [[NSMutableData alloc] init]; + multipartBody = [NSMutableData new]; boundary = RCTGenerateFormBoundary(); return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) { @@ -132,7 +132,7 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _tasksByRequestID = [[NSMutableDictionary alloc] init]; + _tasksByRequestID = [NSMutableDictionary new]; } return self; } @@ -248,7 +248,7 @@ RCT_EXPORT_MODULE() } NSDictionaryArray *formData = [RCTConvert NSDictionaryArray:query[@"formData"]]; if (formData) { - RCTHTTPFormDataHelper *formDataHelper = [[RCTHTTPFormDataHelper alloc] init]; + RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new]; formDataHelper.networker = self; return [formDataHelper process:formData callback:callback]; } diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index ac683fc2a..3f187b171 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -32,7 +32,7 @@ NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegister + (UILocalNotification *)UILocalNotification:(id)json { NSDictionary *details = [self NSDictionary:json]; - UILocalNotification *notification = [[UILocalNotification alloc] init]; + UILocalNotification *notification = [UILocalNotification new]; notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; return notification; @@ -186,7 +186,7 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) } - NSMutableDictionary *permissions = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *permissions = [NSMutableDictionary new]; permissions[@"alert"] = @((types & UIUserNotificationTypeAlert) > 0); permissions[@"badge"] = @((types & UIUserNotificationTypeBadge) > 0); permissions[@"sound"] = @((types & UIUserNotificationTypeSound) > 0); diff --git a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m index 7d12736d1..7d7d32947 100644 --- a/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m +++ b/Libraries/RCTTest/FBSnapshotTestCase/FBSnapshotTestController.m @@ -51,7 +51,7 @@ typedef struct RGBAPixel { { if ((self = [super init])) { _testName = [testName copy]; - _fileManager = [[NSFileManager alloc] init]; + _fileManager = [NSFileManager new]; } return self; } diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index d0326112f..63a82a0ea 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -98,7 +98,7 @@ RCT_NOT_IMPLEMENTED(-init) testModule.view = rootView; UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController; - vc.view = [[UIView alloc] init]; + vc.view = [UIView new]; [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; diff --git a/Libraries/Text/RCTRawTextManager.m b/Libraries/Text/RCTRawTextManager.m index beb716f75..460b127ba 100644 --- a/Libraries/Text/RCTRawTextManager.m +++ b/Libraries/Text/RCTRawTextManager.m @@ -17,7 +17,7 @@ RCT_EXPORT_MODULE() - (RCTShadowView *)shadowView { - return [[RCTShadowRawText alloc] init]; + return [RCTShadowRawText new]; } RCT_EXPORT_SHADOW_PROPERTY(text, NSString) diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 61f1373c1..49cc35901 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -111,12 +111,12 @@ static css_dim_t RCTMeasure(void *context, float width) return _cachedTextStorage; } - NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; + NSLayoutManager *layoutManager = [NSLayoutManager new]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString]; [textStorage addLayoutManager:layoutManager]; - NSTextContainer *textContainer = [[NSTextContainer alloc] init]; + NSTextContainer *textContainer = [NSTextContainer new]; textContainer.lineFragmentPadding = 0.0; textContainer.lineBreakMode = _numberOfLines > 0 ? NSLineBreakByTruncatingTail : NSLineBreakByClipping; textContainer.maximumNumberOfLines = _numberOfLines; @@ -183,7 +183,7 @@ static css_dim_t RCTMeasure(void *context, float width) _effectiveLetterSpacing = letterSpacing.doubleValue; - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + NSMutableAttributedString *attributedString = [NSMutableAttributedString new]; for (RCTShadowView *child in [self reactSubviews]) { if ([child isKindOfClass:[RCTShadowText class]]) { RCTShadowText *shadowText = (RCTShadowText *)child; @@ -265,7 +265,7 @@ static css_dim_t RCTMeasure(void *context, float width) // if we found anything, set it :D if (hasParagraphStyle) { - NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; paragraphStyle.alignment = _textAlign; paragraphStyle.baseWritingDirection = _writingDirection; CGFloat lineHeight = round(_lineHeight * self.fontSizeMultiplier); diff --git a/Libraries/Text/RCTText.m b/Libraries/Text/RCTText.m index 162e16f70..ae244c574 100644 --- a/Libraries/Text/RCTText.m +++ b/Libraries/Text/RCTText.m @@ -23,7 +23,7 @@ - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { - _textStorage = [[NSTextStorage alloc] init]; + _textStorage = [NSTextStorage new]; _reactSubviews = [NSMutableArray array]; self.isAccessibilityElement = YES; diff --git a/Libraries/Text/RCTTextField.m b/Libraries/Text/RCTTextField.m index 261c0cdb2..a99771153 100644 --- a/Libraries/Text/RCTTextField.m +++ b/Libraries/Text/RCTTextField.m @@ -31,7 +31,7 @@ [self addTarget:self action:@selector(textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin]; [self addTarget:self action:@selector(textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd]; [self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit]; - _reactSubviews = [[NSMutableArray alloc] init]; + _reactSubviews = [NSMutableArray new]; } return self; } diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index 06d52088a..92d9eb498 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -25,12 +25,12 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[RCTText alloc] init]; + return [RCTText new]; } - (RCTShadowView *)shadowView { - return [[RCTShadowText alloc] init]; + return [RCTShadowText new]; } #pragma mark - Shadow properties diff --git a/Libraries/WebSocket/RCTSRWebSocket.m b/Libraries/WebSocket/RCTSRWebSocket.m index b65a847ee..2b6f63523 100644 --- a/Libraries/WebSocket/RCTSRWebSocket.m +++ b/Libraries/WebSocket/RCTSRWebSocket.m @@ -297,16 +297,16 @@ RCT_NOT_IMPLEMENTED(-init) _delegateDispatchQueue = dispatch_get_main_queue(); - _readBuffer = [[NSMutableData alloc] init]; - _outputBuffer = [[NSMutableData alloc] init]; + _readBuffer = [NSMutableData new]; + _outputBuffer = [NSMutableData new]; - _currentFrameData = [[NSMutableData alloc] init]; + _currentFrameData = [NSMutableData new]; - _consumers = [[NSMutableArray alloc] init]; + _consumers = [NSMutableArray new]; - _consumerPool = [[RCTSRIOConsumerPool alloc] init]; + _consumerPool = [RCTSRIOConsumerPool new]; - _scheduledRunloops = [[NSMutableSet alloc] init]; + _scheduledRunloops = [NSMutableSet new]; [self _initializeStreams]; @@ -506,7 +506,7 @@ RCT_NOT_IMPLEMENTED(-init) if (_secure) { - NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *SSLOptions = [NSMutableDictionary new]; [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; @@ -1478,7 +1478,7 @@ static const size_t RCTSRFrameHeaderOverhead = 32; consumer = [_bufferedConsumers lastObject]; [_bufferedConsumers removeLastObject]; } else { - consumer = [[RCTSRIOConsumer alloc] init]; + consumer = [RCTSRIOConsumer new]; } [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes]; @@ -1579,7 +1579,7 @@ static NSRunLoop *networkRunLoop = nil; { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - networkThread = [[_RCTSRRunLoopThread alloc] init]; + networkThread = [_RCTSRRunLoopThread new]; networkThread.name = @"com.squareup.SocketRocket.NetworkThread"; [networkThread start]; networkRunLoop = networkThread.runLoop; diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index df661a54b..bd9600d3c 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -57,8 +57,8 @@ RCT_EXPORT_MODULE() _jsQueue = dispatch_queue_create("com.facebook.React.WebSocketExecutor", DISPATCH_QUEUE_SERIAL); _socket = [[RCTSRWebSocket alloc] initWithURL:_url]; _socket.delegate = self; - _callbacks = [[RCTSparseArray alloc] init]; - _injectedObjects = [[NSMutableDictionary alloc] init]; + _callbacks = [RCTSparseArray new]; + _injectedObjects = [NSMutableDictionary new]; [_socket setDelegateDispatchQueue:_jsQueue]; NSURL *startDevToolsURL = [NSURL URLWithString:@"/launch-chrome-devtools" relativeToURL:_url]; diff --git a/Libraries/WebSocket/RCTWebSocketManager.m b/Libraries/WebSocket/RCTWebSocketManager.m index c842cedc6..28d6c3ed0 100644 --- a/Libraries/WebSocket/RCTWebSocketManager.m +++ b/Libraries/WebSocket/RCTWebSocketManager.m @@ -45,7 +45,7 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _sockets = [[RCTSparseArray alloc] init]; + _sockets = [RCTSparseArray new]; } return self; } diff --git a/React/Base/RCTAssert.m b/React/Base/RCTAssert.m index a990f7c1b..b3d729634 100644 --- a/React/Base/RCTAssert.m +++ b/React/Base/RCTAssert.m @@ -72,7 +72,7 @@ void RCTPerformBlockWithAssertFunction(void (^block)(void), RCTAssertFunction as NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; NSMutableArray *functionStack = threadDictionary[RCTAssertFunctionStack]; if (!functionStack) { - functionStack = [[NSMutableArray alloc] init]; + functionStack = [NSMutableArray new]; threadDictionary[RCTAssertFunctionStack] = functionStack; } [functionStack addObject:assertFunction]; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index 7af103b31..c8387242c 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -89,10 +89,10 @@ id RCTGetLatestExecutor(void) */ _valid = YES; _loading = YES; - _moduleDataByID = [[NSMutableArray alloc] init]; - _frameUpdateObservers = [[NSMutableSet alloc] init]; - _scheduledCalls = [[NSMutableArray alloc] init]; - _scheduledCallbacks = [[RCTSparseArray alloc] init]; + _moduleDataByID = [NSMutableArray new]; + _frameUpdateObservers = [NSMutableSet new]; + _scheduledCalls = [NSMutableArray new]; + _scheduledCallbacks = [RCTSparseArray new]; _jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)]; if (RCT_DEV) { @@ -213,7 +213,7 @@ id RCTGetLatestExecutor(void) RCTAssertMainThread(); // Register passed-in module instances - NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *preregisteredModules = [NSMutableDictionary new]; NSArray *extraModules = nil; if (self.delegate) { @@ -229,7 +229,7 @@ id RCTGetLatestExecutor(void) } // Instantiate modules - _moduleDataByID = [[NSMutableArray alloc] init]; + _moduleDataByID = [NSMutableArray new]; NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy]; for (Class moduleClass in RCTGetModuleClasses()) { NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass); @@ -241,7 +241,7 @@ id RCTGetLatestExecutor(void) // Preregistered instances takes precedence, no questions asked if (!preregisteredModules[moduleName]) { // It's OK to have a name collision as long as the second instance is nil - RCTAssert([[moduleClass alloc] init] == nil, + RCTAssert([moduleClass new] == nil, @"Attempted to register RCTBridgeModule class %@ for the name " "'%@', but name was already registered by class %@", moduleClass, moduleName, [modulesByName[moduleName] class]); @@ -254,7 +254,7 @@ id RCTGetLatestExecutor(void) } } else { // Module name hasn't been used before, so go ahead and instantiate - module = [[moduleClass alloc] init]; + module = [moduleClass new]; } if (module) { modulesByName[moduleName] = module; @@ -299,7 +299,7 @@ id RCTGetLatestExecutor(void) - (NSString *)moduleConfig { - NSMutableDictionary *config = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *config = [NSMutableDictionary new]; for (RCTModuleData *moduleData in _moduleDataByID) { config[moduleData.name] = moduleData.config; if ([moduleData.instance conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) { @@ -667,7 +667,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } NSMutableOrderedSet *set = [buckets objectForKey:moduleData]; if (!set) { - set = [[NSMutableOrderedSet alloc] init]; + set = [NSMutableOrderedSet new]; [buckets setObject:set forKey:moduleData]; } [set addObject:@(i)]; @@ -785,8 +785,8 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL ) if (calls.count > 0) { - _scheduledCalls = [[NSMutableArray alloc] init]; - _scheduledCallbacks = [[RCTSparseArray alloc] init]; + _scheduledCalls = [NSMutableArray new]; + _scheduledCallbacks = [RCTSparseArray new]; [self _actuallyInvokeAndProcessModule:@"BatchedBridge" method:@"processBatch" arguments:@[[calls valueForKey:@"js_args"]]]; diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index fd06342b3..2efe49f94 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -57,7 +57,7 @@ void RCTRegisterModule(Class moduleClass) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - RCTModuleClasses = [[NSMutableArray alloc] init]; + RCTModuleClasses = [NSMutableArray new]; }); RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)], diff --git a/React/Base/RCTCache.m b/React/Base/RCTCache.m index a0646a401..b61922ae4 100644 --- a/React/Base/RCTCache.m +++ b/React/Base/RCTCache.m @@ -57,9 +57,9 @@ if ((self = [super init])) { //create storage - _cache = [[NSMutableDictionary alloc] init]; - _entryPool = [[NSMutableArray alloc] init]; - _lock = [[NSLock alloc] init]; + _cache = [NSMutableDictionary new]; + _entryPool = [NSMutableArray new]; + _lock = [NSLock new]; _totalCost = 0; #if TARGET_OS_IPHONE @@ -256,7 +256,7 @@ _totalCost += g; RCTCacheEntry *entry = _cache[key]; if (!entry) { - entry = [[RCTCacheEntry alloc] init]; + entry = [RCTCacheEntry new]; _cache[key] = entry; } entry.object = obj; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 51134f98e..c0d0ac0e2 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -60,7 +60,7 @@ RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncod + (NSIndexSet *)NSIndexSet:(id)json { json = [self NSNumberArray:json]; - NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init]; + NSMutableIndexSet *indexSet = [NSMutableIndexSet new]; for (NSNumber *number in json) { NSInteger index = number.integerValue; if (RCT_DEBUG && index < 0) { @@ -139,7 +139,7 @@ RCT_CUSTOM_CONVERTER(NSData *, NSData, [json dataUsingEncoding:NSUTF8StringEncod static NSDateFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - formatter = [[NSDateFormatter alloc] init]; + formatter = [NSDateFormatter new]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"; formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; @@ -383,7 +383,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ static RCTCache *colorCache = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - colorCache = [[RCTCache alloc] init]; + colorCache = [RCTCache new]; colorCache.countLimit = 128; }); UIColor *color = colorCache[json]; @@ -702,7 +702,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ // but to use +[UIImage imageNamed] - but this method isn't thread safe static NSMutableDictionary *XCAssetMap = nil; if (!XCAssetMap) { - XCAssetMap = [[NSMutableDictionary alloc] init]; + XCAssetMap = [NSMutableDictionary new]; } NSNumber *isAsset = XCAssetMap[path]; if (!isAsset || isAsset.boolValue) { @@ -986,7 +986,7 @@ RCT_JSON_ARRAY_CONVERTER(NSNumber) // Can't use RCT_ARRAY_CONVERTER due to bridged cast + (NSArray *)CGColorArray:(id)json { - NSMutableArray *colors = [[NSMutableArray alloc] init]; + NSMutableArray *colors = [NSMutableArray new]; for (id value in [self NSArray:json]) { [colors addObject:(__bridge id)[self CGColor:value]]; } diff --git a/React/Base/RCTEventDispatcher.m b/React/Base/RCTEventDispatcher.m index 135bf93a5..5e6ac6b4d 100644 --- a/React/Base/RCTEventDispatcher.m +++ b/React/Base/RCTEventDispatcher.m @@ -99,8 +99,8 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _eventQueue = [[NSMutableDictionary alloc] init]; - _eventQueueLock = [[NSLock alloc] init]; + _eventQueue = [NSMutableDictionary new]; + _eventQueueLock = [NSLock new]; } return self; } @@ -176,7 +176,7 @@ RCT_EXPORT_MODULE() - (void)dispatchEvent:(id)event { - NSMutableArray *arguments = [[NSMutableArray alloc] init]; + NSMutableArray *arguments = [NSMutableArray new]; if (event.viewTag) { [arguments addObject:event.viewTag]; @@ -201,7 +201,7 @@ RCT_EXPORT_MODULE() { [_eventQueueLock lock]; NSDictionary *eventQueue = _eventQueue; - _eventQueue = [[NSMutableDictionary alloc] init]; + _eventQueue = [NSMutableDictionary new]; _paused = YES; [_eventQueueLock unlock]; diff --git a/React/Base/RCTFPSGraph.m b/React/Base/RCTFPSGraph.m index 1aa5efd7d..c5580a2f4 100644 --- a/React/Base/RCTFPSGraph.m +++ b/React/Base/RCTFPSGraph.m @@ -72,7 +72,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) - (CAShapeLayer *)createGraph:(UIColor *)color { CGFloat left = _position & RCTFPSGraphPositionLeft ? 0 : _length; - CAShapeLayer *graph = [[CAShapeLayer alloc] init]; + CAShapeLayer *graph = [CAShapeLayer new]; graph.frame = CGRectMake(left, 0, 2 * _margin + _length, self.frame.size.height); graph.backgroundColor = [[color colorWithAlphaComponent:.2] CGColor]; graph.fillColor = [color CGColor]; diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 3a69aaf96..021d9a065 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -68,7 +68,7 @@ RCT_NOT_IMPLEMENTED(-init) NSDictionary *errorDetails = RCTJSONParse(rawText, nil); if ([errorDetails isKindOfClass:[NSDictionary class]] && [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { - NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; + NSMutableArray *fakeStack = [NSMutableArray new]; for (NSDictionary *err in errorDetails[@"errors"]) { [fakeStack addObject: @{ @"methodName": err[@"description"] ?: @"", diff --git a/React/Base/RCTKeyCommands.m b/React/Base/RCTKeyCommands.m index a9d358482..d3b80ac55 100644 --- a/React/Base/RCTKeyCommands.m +++ b/React/Base/RCTKeyCommands.m @@ -152,7 +152,7 @@ RCT_NOT_IMPLEMENTED(-init) static RCTKeyCommands *sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] init]; + sharedInstance = [self new]; }); return sharedInstance; @@ -161,7 +161,7 @@ RCT_NOT_IMPLEMENTED(-init) - (instancetype)init { if ((self = [super init])) { - _commands = [[NSMutableSet alloc] init]; + _commands = [NSMutableSet new]; } return self; } diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 3fa343303..7e530664f 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -171,7 +171,7 @@ void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunct NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary; NSMutableArray *functionStack = threadDictionary[RCTLogFunctionStack]; if (!functionStack) { - functionStack = [[NSMutableArray alloc] init]; + functionStack = [NSMutableArray new]; threadDictionary[RCTLogFunctionStack] = functionStack; } [functionStack addObject:logFunction]; @@ -196,12 +196,12 @@ NSString *RCTFormatLog( NSNumber *lineNumber, NSString *message ) { - NSMutableString *log = [[NSMutableString alloc] init]; + NSMutableString *log = [NSMutableString new]; if (timestamp) { static NSDateFormatter *formatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - formatter = [[NSDateFormatter alloc] init]; + formatter = [NSDateFormatter new]; formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS "; }); [log appendString:[formatter stringFromDate:timestamp]]; diff --git a/React/Base/RCTModuleData.m b/React/Base/RCTModuleData.m index c4c248643..3d492c8b2 100644 --- a/React/Base/RCTModuleData.m +++ b/React/Base/RCTModuleData.m @@ -49,7 +49,7 @@ RCT_NOT_IMPLEMENTED(-init); - (NSArray *)methods { if (!_methods) { - NSMutableArray *moduleMethods = [[NSMutableArray alloc] init]; + NSMutableArray *moduleMethods = [NSMutableArray new]; unsigned int methodCount; Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount); @@ -77,14 +77,14 @@ RCT_NOT_IMPLEMENTED(-init); - (NSDictionary *)config { - NSMutableDictionary *config = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *config = [NSMutableDictionary new]; config[@"moduleID"] = _moduleID; if (_constants) { config[@"constants"] = _constants; } - NSMutableDictionary *methodconfig = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *methodconfig = [NSMutableDictionary new]; [self.methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger idx, __unused BOOL *stop) { methodconfig[method.JSMethodName] = @{ @"methodID": @(idx), diff --git a/React/Base/RCTPerfStats.m b/React/Base/RCTPerfStats.m index 39f72928f..2f025a03b 100644 --- a/React/Base/RCTPerfStats.m +++ b/React/Base/RCTPerfStats.m @@ -32,7 +32,7 @@ RCT_EXPORT_MODULE() - (UIView *)container { if (!_container) { - _container = [[UIView alloc] init]; + _container = [UIView new]; _container.backgroundColor = [UIColor colorWithRed:0 green:0 blue:34/255.0 alpha:1]; _container.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; } diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index a7021415f..2de44b206 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -216,14 +216,14 @@ void RCTProfileInit(RCTBridge *bridge) static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _RCTProfileLock = [[NSRecursiveLock alloc] init]; + _RCTProfileLock = [NSRecursiveLock new]; }); RCTProfileLock( RCTProfileStartTime = CACurrentMediaTime(); - RCTProfileOngoingEvents = [[NSMutableDictionary alloc] init]; + RCTProfileOngoingEvents = [NSMutableDictionary new]; RCTProfileInfo = @{ - RCTProfileTraceEvents: [[NSMutableArray alloc] init], - RCTProfileSamples: [[NSMutableArray alloc] init], + RCTProfileTraceEvents: [NSMutableArray new], + RCTProfileSamples: [NSMutableArray new], }; ); diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 862b360a7..0af8ac819 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -41,7 +41,7 @@ self.backgroundColor = _redColor; self.hidden = YES; - UIViewController *rootController = [[UIViewController alloc] init]; + UIViewController *rootController = [UIViewController new]; self.rootViewController = rootController; _rootView = rootController.view; _rootView.backgroundColor = [UIColor clearColor]; @@ -102,7 +102,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) { NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding]; NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[stackFrameJSON length]]; - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; + NSMutableURLRequest *request = [NSMutableURLRequest new]; request.URL = [RCTConvert NSURL:@"http://localhost:8081/open-stack-frame"]; request.HTTPMethod = @"POST"; request.HTTPBody = stackFrameJSON; @@ -200,7 +200,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; cell.backgroundColor = [UIColor clearColor]; - cell.selectedBackgroundView = [[UIView alloc] init]; + cell.selectedBackgroundView = [UIView new]; cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2]; } @@ -279,7 +279,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) static RCTRedBox *_sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _sharedInstance = [[RCTRedBox alloc] init]; + _sharedInstance = [RCTRedBox new]; }); return _sharedInstance; } diff --git a/React/Base/RCTSparseArray.m b/React/Base/RCTSparseArray.m index 2332f96c5..236e64d17 100644 --- a/React/Base/RCTSparseArray.m +++ b/React/Base/RCTSparseArray.m @@ -37,7 +37,7 @@ + (instancetype)sparseArray { - return [[self alloc] init]; + return [self new]; } + (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index cf84ecdf7..3dd64bbdf 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -48,9 +48,9 @@ _bridge = bridge; _dispatchedInitialTouches = NO; - _nativeTouches = [[NSMutableOrderedSet alloc] init]; - _reactTouches = [[NSMutableArray alloc] init]; - _touchViews = [[NSMutableArray alloc] init]; + _nativeTouches = [NSMutableOrderedSet new]; + _reactTouches = [NSMutableArray new]; + _touchViews = [NSMutableArray new]; // `cancelsTouchesInView` is needed in order to be used as a top level // event delegated recognizer. Otherwise, lower-level components not built @@ -168,7 +168,7 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) { originatingTime:(__unused CFTimeInterval)originatingTime { // Update touches - NSMutableArray *changedIndexes = [[NSMutableArray alloc] init]; + NSMutableArray *changedIndexes = [NSMutableArray new]; for (UITouch *touch in touches) { NSInteger index = [_nativeTouches indexOfObject:touch]; if (index == NSNotFound) { diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 8c8174773..9eb147dbf 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -142,7 +142,7 @@ static JSValueRef RCTConsoleProfile(JSContextRef context, __unused JSObjectRef o { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - profiles = [[NSMutableArray alloc] init]; + profiles = [NSMutableArray new]; }); static int profileCounter = 1; diff --git a/React/Executors/RCTWebViewExecutor.m b/React/Executors/RCTWebViewExecutor.m index 539b9e779..14c21e8b2 100644 --- a/React/Executors/RCTWebViewExecutor.m +++ b/React/Executors/RCTWebViewExecutor.m @@ -66,10 +66,10 @@ RCT_EXPORT_MODULE() - (void)setUp { if (!_webView) { - _webView = [[UIWebView alloc] init]; + _webView = [UIWebView new]; } - _objectsToInject = [[NSMutableDictionary alloc] init]; + _objectsToInject = [NSMutableDictionary new]; _commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL], _scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL], _webView.delegate = self; diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m index 7fc58786a..d016e6a01 100644 --- a/React/Modules/RCTAccessibilityManager.m +++ b/React/Modules/RCTAccessibilityManager.m @@ -129,7 +129,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(setAccessibilityContentSizeMultipliers:(NSDictionary *)JSMultipliers) { - NSMutableDictionary *multipliers = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *multipliers = [NSMutableDictionary new]; for (NSString *__nonnull JSCategory in JSMultipliers) { NSNumber *m = JSMultipliers[JSCategory]; NSString *UIKitCategory = [self.class UIKitCategoryFromJSCategory:JSCategory]; diff --git a/React/Modules/RCTAlertManager.m b/React/Modules/RCTAlertManager.m index f93ef812a..ea8cd9d90 100644 --- a/React/Modules/RCTAlertManager.m +++ b/React/Modules/RCTAlertManager.m @@ -28,9 +28,9 @@ RCT_EXPORT_MODULE() - (instancetype)init { if ((self = [super init])) { - _alerts = [[NSMutableArray alloc] init]; - _alertCallbacks = [[NSMutableArray alloc] init]; - _alertButtonKeys = [[NSMutableArray alloc] init]; + _alerts = [NSMutableArray new]; + _alertCallbacks = [NSMutableArray new]; + _alertButtonKeys = [NSMutableArray new]; } return self; } diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 923a3a14a..262b578be 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -161,7 +161,7 @@ RCT_EXPORT_MODULE() RCTDeleteStorageDirectory(); } _clearOnInvalidate = NO; - _manifest = [[NSMutableDictionary alloc] init]; + _manifest = [NSMutableDictionary new]; _haveSetup = NO; } @@ -199,10 +199,10 @@ RCT_EXPORT_MODULE() if (!_haveSetup) { NSDictionary *errorOut; NSString *serialized = RCTReadFile(RCTGetManifestFilePath(), nil, &errorOut); - _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [[NSMutableDictionary alloc] init]; + _manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new]; if (error) { RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error); - _manifest = [[NSMutableDictionary alloc] init]; + _manifest = [NSMutableDictionary new]; } _haveSetup = YES; } @@ -377,7 +377,7 @@ RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback) { - _manifest = [[NSMutableDictionary alloc] init]; + _manifest = [NSMutableDictionary new]; NSError *error = RCTDeleteStorageDirectory(); if (callback) { callback(@[RCTNullIfNil(error)]); diff --git a/React/Modules/RCTDevMenu.m b/React/Modules/RCTDevMenu.m index 32f3946b1..08d2ebe26 100644 --- a/React/Modules/RCTDevMenu.m +++ b/React/Modules/RCTDevMenu.m @@ -125,7 +125,7 @@ RCT_EXPORT_MODULE() object:nil]; _defaults = [NSUserDefaults standardUserDefaults]; - _settings = [[NSMutableDictionary alloc] init]; + _settings = [NSMutableDictionary new]; _extraMenuItems = [NSMutableArray array]; // Delay setup until after Bridge init @@ -331,7 +331,7 @@ RCT_EXPORT_METHOD(show) return; } - UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + UIActionSheet *actionSheet = [UIActionSheet new]; actionSheet.title = @"React Native: Development"; actionSheet.delegate = self; diff --git a/React/Modules/RCTTiming.m b/React/Modules/RCTTiming.m index a776c58dc..09adc9560 100644 --- a/React/Modules/RCTTiming.m +++ b/React/Modules/RCTTiming.m @@ -78,7 +78,7 @@ RCT_EXPORT_MODULE() { if ((self = [super init])) { _paused = YES; - _timers = [[RCTSparseArray alloc] init]; + _timers = [RCTSparseArray new]; for (NSString *name in @[UIApplicationWillResignActiveNotification, UIApplicationDidEnterBackgroundNotification, @@ -134,7 +134,7 @@ RCT_EXPORT_MODULE() - (void)didUpdateFrame:(__unused RCTFrameUpdate *)update { - NSMutableArray *timersToCall = [[NSMutableArray alloc] init]; + NSMutableArray *timersToCall = [NSMutableArray new]; for (RCTTimer *timer in _timers.allObjects) { if ([timer updateFoundNeedsJSUpdate]) { [timersToCall addObject:timer.callbackID]; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index d1a29d3a8..7f72a8074 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -219,16 +219,16 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); _shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL); - _pendingUIBlocksLock = [[NSLock alloc] init]; + _pendingUIBlocksLock = [NSLock new]; - _shadowViewRegistry = [[RCTSparseArray alloc] init]; - _viewRegistry = [[RCTSparseArray alloc] init]; + _shadowViewRegistry = [RCTSparseArray new]; + _viewRegistry = [RCTSparseArray new]; // Internal resources - _pendingUIBlocks = [[NSMutableArray alloc] init]; - _rootViewTags = [[NSMutableSet alloc] init]; + _pendingUIBlocks = [NSMutableArray new]; + _rootViewTags = [NSMutableSet new]; - _bridgeTransactionListeners = [[NSMutableSet alloc] init]; + _bridgeTransactionListeners = [NSMutableSet new]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNewContentSizeMultiplier) @@ -284,10 +284,10 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance"); _bridge = bridge; - _shadowViewRegistry = [[RCTSparseArray alloc] init]; + _shadowViewRegistry = [RCTSparseArray new]; // Get view managers from bridge - NSMutableDictionary *componentDataByName = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *componentDataByName = [NSMutableDictionary new]; for (RCTViewManager *manager in _bridge.modules.allValues) { if ([manager isKindOfClass:[RCTViewManager class]]) { RCTComponentData *componentData = [[RCTComponentData alloc] initWithManager:manager]; @@ -326,7 +326,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); if (!_viewRegistry) { return; } - RCTShadowView *shadowView = [[RCTShadowView alloc] init]; + RCTShadowView *shadowView = [RCTShadowView new]; shadowView.reactTag = reactTag; shadowView.frame = frame; shadowView.backgroundColor = rootView.backgroundColor; @@ -479,7 +479,7 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); // reactSetFrame: has been called. Note that if reactSetFrame: is not called, // these won't be called either, so this is not a suitable place to update // properties that aren't related to layout. - NSMutableArray *updateBlocks = [[NSMutableArray alloc] init]; + NSMutableArray *updateBlocks = [NSMutableArray new]; for (RCTShadowView *shadowView in viewsWithNewFrames) { RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager]; RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView]; @@ -870,7 +870,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po // processing the pending blocks in another thread. [_pendingUIBlocksLock lock]; NSArray *previousPendingUIBlocks = _pendingUIBlocks; - _pendingUIBlocks = [[NSMutableArray alloc] init]; + _pendingUIBlocks = [NSMutableArray new]; [_pendingUIBlocksLock unlock]; // Execute the previously queued UI blocks @@ -1130,7 +1130,7 @@ RCT_EXPORT_METHOD(clearJSResponder) - (NSDictionary *)bubblingEventsConfig { - NSMutableDictionary *customBubblingEventTypesConfigs = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *customBubblingEventTypesConfigs = [NSMutableDictionary new]; for (RCTComponentData *componentData in _componentDataByName.allValues) { RCTViewManager *manager = componentData.manager; if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) { @@ -1160,7 +1160,7 @@ RCT_EXPORT_METHOD(clearJSResponder) - (NSDictionary *)directEventsConfig { - NSMutableDictionary *customDirectEventTypes = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *customDirectEventTypes = [NSMutableDictionary new]; for (RCTComponentData *componentData in _componentDataByName.allValues) { RCTViewManager *manager = componentData.manager; if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) { diff --git a/React/Views/RCTActivityIndicatorViewManager.m b/React/Views/RCTActivityIndicatorViewManager.m index 3876400df..2a7cadf35 100644 --- a/React/Views/RCTActivityIndicatorViewManager.m +++ b/React/Views/RCTActivityIndicatorViewManager.m @@ -29,7 +29,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[UIActivityIndicatorView alloc] init]; + return [UIActivityIndicatorView new]; } RCT_EXPORT_VIEW_PROPERTY(color, UIColor) diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 2f3aa8458..462513c53 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -48,8 +48,8 @@ typedef void (^RCTPropBlock)(id view, id json); { if ((self = [super init])) { _manager = manager; - _viewPropBlocks = [[NSMutableDictionary alloc] init]; - _shadowPropBlocks = [[NSMutableDictionary alloc] init]; + _viewPropBlocks = [NSMutableDictionary new]; + _shadowPropBlocks = [NSMutableDictionary new]; _name = RCTBridgeModuleNameForClass([manager class]); RCTAssert(_name.length, @"Invalid moduleName '%@'", _name); @@ -292,7 +292,7 @@ RCT_NOT_IMPLEMENTED(-init) - (NSDictionary *)viewConfig { Class managerClass = [_manager class]; - NSMutableDictionary *propTypes = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *propTypes = [NSMutableDictionary new]; unsigned int count = 0; Method *methods = class_copyMethodList(object_getClass(managerClass), &count); diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index a1ba0ac98..9206c3a40 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -35,7 +35,7 @@ json = [self NSDictionary:json]; // TODO: more shape types - MKShape *shape = [[MKPointAnnotation alloc] init]; + MKShape *shape = [MKPointAnnotation new]; shape.coordinate = [self CLLocationCoordinate2D:json]; shape.title = [RCTConvert NSString:json[@"title"]]; shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; @@ -53,7 +53,7 @@ RCT_ENUM_CONVERTER(MKMapType, (@{ + (RCTPointAnnotation *)RCTPointAnnotation:(id)json { json = [self NSDictionary:json]; - RCTPointAnnotation *shape = [[RCTPointAnnotation alloc] init]; + RCTPointAnnotation *shape = [RCTPointAnnotation new]; shape.coordinate = [self CLLocationCoordinate2D:json]; shape.title = [RCTConvert NSString:json[@"title"]]; shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; diff --git a/React/Views/RCTDatePickerManager.m b/React/Views/RCTDatePickerManager.m index a5d63786f..229abb785 100644 --- a/React/Views/RCTDatePickerManager.m +++ b/React/Views/RCTDatePickerManager.m @@ -34,7 +34,7 @@ RCT_EXPORT_MODULE() // while the UIDatePicker is still sending onChange events. To // fix this we should maybe subclass UIDatePicker and make it // be its own event target. - UIDatePicker *picker = [[UIDatePicker alloc] init]; + UIDatePicker *picker = [UIDatePicker new]; [picker addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; return picker; @@ -58,7 +58,7 @@ RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone) - (NSDictionary *)constantsToExport { - UIDatePicker *view = [[UIDatePicker alloc] init]; + UIDatePicker *view = [UIDatePicker new]; return @{ @"ComponentHeight": @(view.intrinsicContentSize.height), @"ComponentWidth": @(view.intrinsicContentSize.width), diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m index d51af5a01..16208957b 100644 --- a/React/Views/RCTMap.m +++ b/React/Views/RCTMap.m @@ -80,7 +80,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; { if (self.showsUserLocation != showsUserLocation) { if (showsUserLocation && !_locationManager) { - _locationManager = [[CLLocationManager alloc] init]; + _locationManager = [CLLocationManager new]; if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { [_locationManager requestWhenInUseAuthorization]; } @@ -114,9 +114,9 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; - (void)setAnnotations:(RCTPointAnnotationArray *)annotations { - NSMutableArray *newAnnotationIds = [[NSMutableArray alloc] init]; - NSMutableArray *annotationsToDelete = [[NSMutableArray alloc] init]; - NSMutableArray *annotationsToAdd = [[NSMutableArray alloc] init]; + NSMutableArray *newAnnotationIds = [NSMutableArray new]; + NSMutableArray *annotationsToDelete = [NSMutableArray new]; + NSMutableArray *annotationsToAdd = [NSMutableArray new]; for (RCTPointAnnotation *annotation in annotations) { if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { @@ -150,7 +150,7 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01; [self addAnnotations:annotationsToAdd]; } - NSMutableArray *newIds = [[NSMutableArray alloc] init]; + NSMutableArray *newIds = [NSMutableArray new]; for (RCTPointAnnotation *anno in self.annotations) { if ([anno isKindOfClass:[MKUserLocation class]]) { continue; diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index 032da4d7a..130446df0 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -31,7 +31,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - RCTMap *map = [[RCTMap alloc] init]; + RCTMap *map = [RCTMap new]; map.delegate = self; return map; } diff --git a/React/Views/RCTModalHostView.m b/React/Views/RCTModalHostView.m index a4d1db93e..1650636ef 100644 --- a/React/Views/RCTModalHostView.m +++ b/React/Views/RCTModalHostView.m @@ -31,7 +31,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:coder) { if ((self = [super initWithFrame:CGRectZero])) { _bridge = bridge; - _modalViewController = [[RCTModalHostViewController alloc] init]; + _modalViewController = [RCTModalHostViewController new]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge]; __weak RCTModalHostView *weakSelf = self; diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m index 8350af2e9..f785934af 100644 --- a/React/Views/RCTNavItemManager.m +++ b/React/Views/RCTNavItemManager.m @@ -18,7 +18,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[RCTNavItem alloc] init]; + return [RCTNavItem new]; } RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL) diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index 8e31e5fe9..6de76bcb7 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -27,7 +27,7 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) - (NSDictionary *)constantsToExport { - UIPickerView *view = [[UIPickerView alloc] init]; + UIPickerView *view = [UIPickerView new]; return @{ @"ComponentHeight": @(view.intrinsicContentSize.height), @"ComponentWidth": @(view.intrinsicContentSize.width) diff --git a/React/Views/RCTProgressViewManager.m b/React/Views/RCTProgressViewManager.m index deb6285a6..0cd9049d3 100644 --- a/React/Views/RCTProgressViewManager.m +++ b/React/Views/RCTProgressViewManager.m @@ -26,7 +26,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[UIProgressView alloc] init]; + return [UIProgressView new]; } RCT_EXPORT_VIEW_PROPERTY(progressViewStyle, UIProgressViewStyle) @@ -38,7 +38,7 @@ RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage) - (NSDictionary *)constantsToExport { - UIProgressView *view = [[UIProgressView alloc] init]; + UIProgressView *view = [UIProgressView new]; return @{ @"ComponentHeight": @(view.intrinsicContentSize.height), }; diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 89afd8b63..9010bce8d 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -387,7 +387,7 @@ RCT_NOT_IMPLEMENTED(-init) _scrollEventThrottle = 0.0; _lastScrollDispatchTime = CACurrentMediaTime(); - _cachedChildFrames = [[NSMutableArray alloc] init]; + _cachedChildFrames = [NSMutableArray new]; [self addSubview:_scrollView]; } @@ -582,7 +582,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) - (NSArray *)calculateChildFramesData { - NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init]; + NSMutableArray *updatedChildFrames = [NSMutableArray new]; [[_contentView reactSubviews] enumerateObjectsUsingBlock: ^(UIView *subview, NSUInteger idx, __unused BOOL *stop) { diff --git a/React/Views/RCTSegmentedControlManager.m b/React/Views/RCTSegmentedControlManager.m index d7e1156ff..1c5621e7a 100644 --- a/React/Views/RCTSegmentedControlManager.m +++ b/React/Views/RCTSegmentedControlManager.m @@ -30,7 +30,7 @@ RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL) - (NSDictionary *)constantsToExport { - RCTSegmentedControl *view = [[RCTSegmentedControl alloc] init]; + RCTSegmentedControl *view = [RCTSegmentedControl new]; return @{ @"ComponentHeight": @(view.intrinsicContentSize.height), }; diff --git a/React/Views/RCTSliderManager.m b/React/Views/RCTSliderManager.m index 100c2b4c3..9ce7699d1 100644 --- a/React/Views/RCTSliderManager.m +++ b/React/Views/RCTSliderManager.m @@ -20,7 +20,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - RCTSlider *slider = [[RCTSlider alloc] init]; + RCTSlider *slider = [RCTSlider new]; [slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside]; [slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpOutside]; diff --git a/React/Views/RCTSwitchManager.m b/React/Views/RCTSwitchManager.m index 7b39afa89..42a54210c 100644 --- a/React/Views/RCTSwitchManager.m +++ b/React/Views/RCTSwitchManager.m @@ -20,7 +20,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - RCTSwitch *switcher = [[RCTSwitch alloc] init]; + RCTSwitch *switcher = [RCTSwitch new]; [switcher addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; diff --git a/React/Views/RCTTabBar.m b/React/Views/RCTTabBar.m index 4f14a943f..34c8a3b04 100644 --- a/React/Views/RCTTabBar.m +++ b/React/Views/RCTTabBar.m @@ -36,8 +36,8 @@ if ((self = [super initWithFrame:CGRectZero])) { _eventDispatcher = eventDispatcher; - _tabViews = [[NSMutableArray alloc] init]; - _tabController = [[UITabBarController alloc] init]; + _tabViews = [NSMutableArray new]; + _tabController = [UITabBarController new]; _tabController.delegate = self; [self addSubview:_tabController.view]; } diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m index 24a22cf5f..0727a846f 100644 --- a/React/Views/RCTTabBarItem.m +++ b/React/Views/RCTTabBarItem.m @@ -20,7 +20,7 @@ - (UITabBarItem *)barItem { if (!_barItem) { - _barItem = [[UITabBarItem alloc] init]; + _barItem = [UITabBarItem new]; } return _barItem; } diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m index d1a96ef2a..9adccfdd9 100644 --- a/React/Views/RCTTabBarItemManager.m +++ b/React/Views/RCTTabBarItemManager.m @@ -18,7 +18,7 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[RCTTabBarItem alloc] init]; + return [RCTTabBarItem new]; } RCT_EXPORT_VIEW_PROPERTY(selected, BOOL); diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 9c7a97483..bf152ea39 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -56,12 +56,12 @@ RCT_EXPORT_MODULE() - (UIView *)view { - return [[RCTView alloc] init]; + return [RCTView new]; } - (RCTShadowView *)shadowView { - return [[RCTShadowView alloc] init]; + return [RCTShadowView new]; } - (NSArray *)customBubblingEventTypes From 65692b7235d6a79d3880d9b24c82cbdd9785b9f4 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Mon, 17 Aug 2015 14:56:10 -0700 Subject: [PATCH 053/139] [ReactNative] Don't redbox for React warnings when not using Chrome executor --- .../JavaScriptAppEngine/Initialization/ExceptionsManager.js | 4 ++++ .../src/DependencyResolver/polyfills/console.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js index 14bdc8240..a284ce6e3 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js +++ b/Libraries/JavaScriptAppEngine/Initialization/ExceptionsManager.js @@ -80,6 +80,9 @@ function installConsoleErrorReporter() { console.reportException = reportException; console.errorOriginal = console.error.bind(console); console.error = function reactConsoleError() { + // Note that when using the built-in context executor on iOS (i.e., not + // Chrome debugging), console.error is already stubbed out to cause a + // redbox via RCTNativeLoggingHook. console.errorOriginal.apply(null, arguments); if (!console.reportErrorsAsExceptions) { return; @@ -88,6 +91,7 @@ function installConsoleErrorReporter() { if (str.slice(0, 10) === '"Warning: ') { // React warnings use console.error so that a stack trace is shown, but // we don't (currently) want these to show a redbox + // (Note: Logic duplicated in polyfills/console.js.) return; } var error: any = new Error('console.error: ' + str); diff --git a/packager/react-packager/src/DependencyResolver/polyfills/console.js b/packager/react-packager/src/DependencyResolver/polyfills/console.js index ff2ff39ff..e04597402 100644 --- a/packager/react-packager/src/DependencyResolver/polyfills/console.js +++ b/packager/react-packager/src/DependencyResolver/polyfills/console.js @@ -376,6 +376,12 @@ var str = Array.prototype.map.call(arguments, function(arg) { return inspect(arg, {depth: 10}); }).join(', '); + if (str.slice(0, 10) === "'Warning: " && level >= LOG_LEVELS.error) { + // React warnings use console.error so that a stack trace is shown, + // but we don't (currently) want these to show a redbox + // (Note: Logic duplicated in ExceptionsManager.js.) + level = LOG_LEVELS.warn; + } global.nativeLoggingHook(str, level); }; } From a4e64196bcd9ea0f498351b7694281163023e4d3 Mon Sep 17 00:00:00 2001 From: Natthu Bharambe Date: Mon, 17 Aug 2015 15:41:47 -0700 Subject: [PATCH 054/139] Update `GLOBAL.process` instead of over-writing during initialization. --- .../Initialization/InitializeJavaScriptAppEngine.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js index f53a59024..5275a2f4f 100644 --- a/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js +++ b/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js @@ -137,7 +137,8 @@ function setupProfile() { } function setUpProcessEnv() { - GLOBAL.process = {env: {NODE_ENV: __DEV__ ? 'development' : 'production'}}; + GLOBAL.process = GLOBAL.process || {}; + GLOBAL.process.env = {NODE_ENV: __DEV__ ? 'development' : 'production'}; } setUpRedBoxErrorHandler(); From 76a9baaf2f6b84cc8904d95213fe14df6d43bda4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Tue, 18 Aug 2015 12:55:25 -0100 Subject: [PATCH 055/139] Fixed bug where method calls containing struct arguments would fail silently Summary: The arg block for handling structs did not return a value, which was being intepreted as failure. This diff returns YES for success, and also adds an error log for this case so future regressions won't fail silently. --- .../UIExplorerUnitTests/RCTModuleMethodTests.m | 16 ++++++++++++++++ React/Base/RCTModuleMethod.m | 9 ++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m index 78a95e2a8..1fb9f5527 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTModuleMethodTests.m @@ -35,6 +35,9 @@ static BOOL RCTLogsError(void (^block)(void)) @end @implementation RCTModuleMethodTests +{ + CGRect _s; +} - (void)doFooWithBar:(__unused NSString *)bar { } @@ -56,6 +59,7 @@ static BOOL RCTLogsError(void (^block)(void)) - (void)doFooWithNumber:(__unused NSNumber *)n { } - (void)doFooWithDouble:(__unused double)n { } - (void)doFooWithInteger:(__unused NSInteger)n { } +- (void)doFooWithCGRect:(CGRect)s { _s = s; } - (void)testNumbersNonnull { @@ -102,4 +106,16 @@ static BOOL RCTLogsError(void (^block)(void)) } } +- (void)testStructArgument +{ + NSString *methodName = @"doFooWithCGRect:(CGRect)s"; + RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName + JSMethodName:nil + moduleClass:[self class]]; + + CGRect r = CGRectMake(10, 20, 30, 40); + [method invokeWithBridge:nil module:self arguments:@[@[@10, @20, @30, @40]]]; + XCTAssertTrue(CGRectEqualToRect(r, _s)); +} + @end diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index a64bc46ab..f9a493e26 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -237,17 +237,14 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) [typeInvocation setSelector:selector]; [typeInvocation setTarget:[RCTConvert class]]; - [argumentBlocks addObject: - ^(__unused RCTBridge *bridge, NSUInteger index, id json) { - + [argumentBlocks addObject:^(__unused RCTBridge *bridge, NSUInteger index, id json) { void *returnValue = malloc(typeSignature.methodReturnLength); [typeInvocation setArgument:&json atIndex:2]; [typeInvocation invoke]; [typeInvocation getReturnValue:returnValue]; - [invocation setArgument:returnValue atIndex:index + 2]; - free(returnValue); + return YES; }]; break; } @@ -411,6 +408,8 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments) RCTArgumentBlock block = _argumentBlocks[index]; if (!block(bridge, index, RCTNilIfNull(json))) { // Invalid argument, abort + RCTLogArgumentError(self, index, json, + "could not be processed. Aborting method call."); return; } index++; From 67746fea2f3ccee7b9e7f2d09e693e00a38c6e5a Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Tue, 18 Aug 2015 08:13:09 -0700 Subject: [PATCH 056/139] [ReactNative] Use FbHttp for handling http requests from React Native. --- Libraries/Network/FormData.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Network/FormData.js b/Libraries/Network/FormData.js index 8339e49ba..ea5d8cc29 100644 --- a/Libraries/Network/FormData.js +++ b/Libraries/Network/FormData.js @@ -74,7 +74,7 @@ class FormData { var contentDisposition = 'form-data; name="' + name + '"'; var headers: Headers = {'content-disposition': contentDisposition}; if (typeof value === 'string') { - return {string: value, headers}; + return {string: value, headers, fieldName: name}; } // The body part is a "blob", which in React Native just means @@ -87,7 +87,7 @@ class FormData { if (typeof value.type === 'string') { headers['content-type'] = value.type; } - return {...value, headers}; + return {...value, headers, fieldName: name}; }); } } From 51596f8674abb5895b30e5a2c4ae56be7fbcd47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 18 Aug 2015 13:34:23 -0700 Subject: [PATCH 057/139] [react-packager] Modernize AssetServer to ES6 --- .../AssetServer/__tests__/AssetServer-test.js | 92 +++---- .../react-packager/src/AssetServer/index.js | 251 +++++++++--------- 2 files changed, 168 insertions(+), 175 deletions(-) diff --git a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 95916c9ea..54ef70120 100644 --- a/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/packager/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -8,22 +8,22 @@ jest .mock('crypto') .mock('fs'); -var Promise = require('promise'); +const Promise = require('promise'); -describe('AssetServer', function() { - var AssetServer; - var crypto; - var fs; +describe('AssetServer', () => { + let AssetServer; + let crypto; + let fs; - beforeEach(function() { + beforeEach(() => { AssetServer = require('../'); crypto = require('crypto'); fs = require('fs'); }); - describe('assetServer.get', function() { - pit('should work for the simple case', function() { - var server = new AssetServer({ + describe('assetServer.get', () => { + pit('should work for the simple case', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -40,15 +40,15 @@ describe('AssetServer', function() { return Promise.all([ server.get('imgs/b.png'), server.get('imgs/b@1x.png'), - ]).then(function(resp) { - resp.forEach(function(data) { - expect(data).toBe('b image'); - }); - }); + ]).then(resp => + resp.forEach(data => + expect(data).toBe('b image') + ) + ); }); - pit('should work for the simple case with jpg', function() { - var server = new AssetServer({ + pit('should work for the simple case with jpg', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png', 'jpg'], }); @@ -65,16 +65,16 @@ describe('AssetServer', function() { return Promise.all([ server.get('imgs/b.jpg'), server.get('imgs/b.png'), - ]).then(function(data) { + ]).then(data => expect(data).toEqual([ 'jpeg image', 'png image', - ]); - }); + ]) + ); }); - pit('should pick the bigger one', function() { - var server = new AssetServer({ + pit('should pick the bigger one', () => { + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -90,13 +90,13 @@ describe('AssetServer', function() { } }); - return server.get('imgs/b@3x.png').then(function(data) { - expect(data).toBe('b4 image'); - }); + return server.get('imgs/b@3x.png').then(data => + expect(data).toBe('b4 image') + ); }); - pit('should support multiple project roots', function() { - var server = new AssetServer({ + pit('should support multiple project roots', () => { + const server = new AssetServer({ projectRoots: ['/root', '/root2'], assetExts: ['png'], }); @@ -116,27 +116,23 @@ describe('AssetServer', function() { }, }); - return server.get('newImages/imgs/b.png').then(function(data) { - expect(data).toBe('b1 image'); - }); + return server.get('newImages/imgs/b.png').then(data => + expect(data).toBe('b1 image') + ); }); }); - describe('assetSerer.getAssetData', function() { - pit('should get assetData', function() { - var hash = { + describe('assetSerer.getAssetData', () => { + pit('should get assetData', () => { + const hash = { update: jest.genMockFn(), digest: jest.genMockFn(), }; - hash.digest.mockImpl(function() { - return 'wow such hash'; - }); - crypto.createHash.mockImpl(function() { - return hash; - }); + hash.digest.mockImpl(() => 'wow such hash'); + crypto.createHash.mockImpl(() => hash); - var server = new AssetServer({ + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png'], }); @@ -152,7 +148,7 @@ describe('AssetServer', function() { } }); - return server.getAssetData('imgs/b.png').then(function(data) { + return server.getAssetData('imgs/b.png').then(data => { expect(hash.update.mock.calls.length).toBe(4); expect(data).toEqual({ type: 'png', @@ -163,20 +159,16 @@ describe('AssetServer', function() { }); }); - pit('should get assetData for non-png images', function() { - var hash = { + pit('should get assetData for non-png images', () => { + const hash = { update: jest.genMockFn(), digest: jest.genMockFn(), }; - hash.digest.mockImpl(function() { - return 'wow such hash'; - }); - crypto.createHash.mockImpl(function() { - return hash; - }); + hash.digest.mockImpl(() => 'wow such hash'); + crypto.createHash.mockImpl(() => hash); - var server = new AssetServer({ + const server = new AssetServer({ projectRoots: ['/root'], assetExts: ['png', 'jpeg'], }); @@ -192,7 +184,7 @@ describe('AssetServer', function() { } }); - return server.getAssetData('imgs/b.jpg').then(function(data) { + return server.getAssetData('imgs/b.jpg').then(data => { expect(hash.update.mock.calls.length).toBe(4); expect(data).toEqual({ type: 'jpg', diff --git a/packager/react-packager/src/AssetServer/index.js b/packager/react-packager/src/AssetServer/index.js index 2cd365fdc..f442f6b89 100644 --- a/packager/react-packager/src/AssetServer/index.js +++ b/packager/react-packager/src/AssetServer/index.js @@ -8,20 +8,20 @@ */ 'use strict'; -var declareOpts = require('../lib/declareOpts'); -var getAssetDataFromName = require('../lib/getAssetDataFromName'); -var path = require('path'); -var Promise = require('promise'); -var fs = require('fs'); -var crypto = require('crypto'); +const Promise = require('promise'); -var stat = Promise.denodeify(fs.stat); -var readDir = Promise.denodeify(fs.readdir); -var readFile = Promise.denodeify(fs.readFile); +const crypto = require('crypto'); +const declareOpts = require('../lib/declareOpts'); +const fs = require('fs'); +const getAssetDataFromName = require('../lib/getAssetDataFromName'); +const path = require('path'); -module.exports = AssetServer; +const stat = Promise.denodeify(fs.stat); +const readDir = Promise.denodeify(fs.readdir); +const readFile = Promise.denodeify(fs.readFile); -var validateOpts = declareOpts({ + +const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, @@ -32,135 +32,136 @@ var validateOpts = declareOpts({ }, }); -function AssetServer(options) { - var opts = validateOpts(options); - this._roots = opts.projectRoots; - this._assetExts = opts.assetExts; -} +class AssetServer { + constructor(options) { + const opts = validateOpts(options); + this._roots = opts.projectRoots; + this._assetExts = opts.assetExts; + } -/** - * Given a request for an image by path. That could contain a resolution - * postfix, we need to find that image (or the closest one to it's resolution) - * in one of the project roots: - * - * 1. We first parse the directory of the asset - * 2. We check to find a matching directory in one of the project roots - * 3. We then build a map of all assets and their scales in this directory - * 4. Then pick the closest resolution (rounding up) to the requested one - */ - -AssetServer.prototype._getAssetRecord = function(assetPath) { - var filename = path.basename(assetPath); - - return findRoot( - this._roots, - path.dirname(assetPath) - ).then(function(dir) { - return Promise.all([ - dir, - readDir(dir), - ]); - }).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]; - - if (!record) { - throw new Error('Asset not found'); - } - - return record; - }); -}; - -AssetServer.prototype.get = function(assetPath) { - var assetData = getAssetDataFromName(assetPath); - return this._getAssetRecord(assetPath).then(function(record) { - for (var i = 0; i < record.scales.length; i++) { - if (record.scales[i] >= assetData.resolution) { - return readFile(record.files[i]); + get(assetPath) { + const assetData = getAssetDataFromName(assetPath); + return this._getAssetRecord(assetPath).then(record => { + for (let i = 0; i < record.scales.length; i++) { + if (record.scales[i] >= assetData.resolution) { + return readFile(record.files[i]); + } } - } - return readFile(record.files[record.files.length - 1]); - }); -}; + return readFile(record.files[record.files.length - 1]); + }); + } -AssetServer.prototype.getAssetData = function(assetPath) { - var nameData = getAssetDataFromName(assetPath); - var data = { - name: nameData.name, - type: nameData.type, - }; + getAssetData(assetPath) { + const nameData = getAssetDataFromName(assetPath); + const data = { + name: nameData.name, + type: nameData.type, + }; - return this._getAssetRecord(assetPath).then(function(record) { - data.scales = record.scales; + return this._getAssetRecord(assetPath).then(record => { + data.scales = record.scales; - return Promise.all( - record.files.map(function(file) { - return stat(file); + return Promise.all( + record.files.map(file => stat(file)) + ); + }).then(stats => { + const hash = crypto.createHash('md5'); + + stats.forEach(fstat => + hash.update(fstat.mtime.getTime().toString()) + ); + + data.hash = hash.digest('hex'); + return data; + }); + } + + /** + * Given a request for an image by path. That could contain a resolution + * postfix, we need to find that image (or the closest one to it's resolution) + * in one of the project roots: + * + * 1. We first parse the directory of the asset + * 2. We check to find a matching directory in one of the project roots + * 3. We then build a map of all assets and their scales in this directory + * 4. Then pick the closest resolution (rounding up) to the requested one + */ + _getAssetRecord(assetPath) { + const filename = path.basename(assetPath); + + return ( + this._findRoot( + this._roots, + path.dirname(assetPath) + ) + .then(dir => Promise.all([ + dir, + readDir(dir), + ])) + .then(res => { + const dir = res[0]; + const files = res[1]; + const assetData = getAssetDataFromName(filename); + + const map = this._buildAssetMap(dir, files); + const record = map[assetData.assetName]; + + if (!record) { + throw new Error('Asset not found'); + } + + return record; }) ); - }).then(function(stats) { - var hash = crypto.createHash('md5'); + } - stats.forEach(function(fstat) { - hash.update(fstat.mtime.getTime().toString()); - }); - - data.hash = hash.digest('hex'); - return data; - }); -}; - -function findRoot(roots, dir) { - return Promise.all( - roots.map(function(root) { - var absPath = path.join(root, dir); - return stat(absPath).then(function(fstat) { - return {path: absPath, isDirectory: fstat.isDirectory()}; - }, function (err) { - return {path: absPath, isDirectory: false}; - }); - }) - ).then( - function(stats) { - for (var i = 0; i < stats.length; i++) { + _findRoot(roots, dir) { + return Promise.all( + roots.map(root => { + const absPath = path.join(root, dir); + return stat(absPath).then(fstat => { + return {path: absPath, isDirectory: fstat.isDirectory()}; + }, err => { + return {path: absPath, isDirectory: false}; + }); + }) + ).then(stats => { + for (let i = 0; i < stats.length; i++) { if (stats[i].isDirectory) { return stats[i].path; } } throw new Error('Could not find any directories'); - } - ); -} + }); + } -function buildAssetMap(dir, files) { - var assets = files.map(getAssetDataFromName); - var map = Object.create(null); - assets.forEach(function(asset, i) { - var file = files[i]; - var record = map[asset.assetName]; - if (!record) { - record = map[asset.assetName] = { - scales: [], - files: [], - }; - } - - var insertIndex; - var length = record.scales.length; - for (insertIndex = 0; insertIndex < length; insertIndex++) { - if (asset.resolution < record.scales[insertIndex]) { - break; + _buildAssetMap(dir, files) { + const assets = files.map(getAssetDataFromName); + const map = Object.create(null); + assets.forEach(function(asset, i) { + const file = files[i]; + let record = map[asset.assetName]; + if (!record) { + record = map[asset.assetName] = { + scales: [], + files: [], + }; } - } - record.scales.splice(insertIndex, 0, asset.resolution); - record.files.splice(insertIndex, 0, path.join(dir, file)); - }); - return map; + let insertIndex; + const length = record.scales.length; + for (insertIndex = 0; insertIndex < length; insertIndex++) { + if (asset.resolution < record.scales[insertIndex]) { + break; + } + } + record.scales.splice(insertIndex, 0, asset.resolution); + record.files.splice(insertIndex, 0, path.join(dir, file)); + }); + + return map; + } } + +module.exports = AssetServer; From 291c8ff9ecbcee70a345490467b0cf66e5af4f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 18 Aug 2015 13:33:07 -0700 Subject: [PATCH 058/139] [react-packager] Modernize Activity to ES6 --- .../src/Activity/__mocks__/chalk.js | 2 +- .../src/Activity/__tests__/Activity-test.js | 58 +++++++++---------- packager/react-packager/src/Activity/index.js | 45 +++++++------- 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/packager/react-packager/src/Activity/__mocks__/chalk.js b/packager/react-packager/src/Activity/__mocks__/chalk.js index 2981f979d..58bf03a6b 100644 --- a/packager/react-packager/src/Activity/__mocks__/chalk.js +++ b/packager/react-packager/src/Activity/__mocks__/chalk.js @@ -9,5 +9,5 @@ 'use strict'; module.exports = { - dim: function(s) { return s; }, + dim: s => s, }; diff --git a/packager/react-packager/src/Activity/__tests__/Activity-test.js b/packager/react-packager/src/Activity/__tests__/Activity-test.js index c854aa335..90ac43e85 100644 --- a/packager/react-packager/src/Activity/__tests__/Activity-test.js +++ b/packager/react-packager/src/Activity/__tests__/Activity-test.js @@ -10,64 +10,63 @@ jest.autoMockOff(); -describe('Activity', function() { - var Activity; +describe('Activity', () => { + const origConsoleLog = console.log; + let Activity; - var origConsoleLog = console.log; - - beforeEach(function() { + beforeEach(() => { console.log = jest.genMockFn(); Activity = require('../'); jest.runOnlyPendingTimers(); }); - afterEach(function() { + afterEach(() => { console.log = origConsoleLog; }); - describe('startEvent', function() { - it('writes a START event out to the console', function() { - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('startEvent', () => { + it('writes a START event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; Activity.startEvent(EVENT_NAME, DATA); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(1); - var consoleMsg = console.log.mock.calls[0][0]; + const consoleMsg = console.log.mock.calls[0][0]; expect(consoleMsg).toContain('START'); expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); }); - describe('endEvent', function() { - it('writes an END event out to the console', function() { - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('endEvent', () => { + it('writes an END event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; - var eventID = Activity.startEvent(EVENT_NAME, DATA); + const eventID = Activity.startEvent(EVENT_NAME, DATA); Activity.endEvent(eventID); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(2); - var consoleMsg = console.log.mock.calls[1][0]; + const consoleMsg = console.log.mock.calls[1][0]; expect(consoleMsg).toContain('END'); expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); - it('throws when called with an invalid eventId', function() { - expect(function() { - Activity.endEvent(42); - }).toThrow('event(42) is not a valid event id!'); + it('throws when called with an invalid eventId', () => { + expect(() => Activity.endEvent(42)).toThrow( + 'event(42) is not a valid event id!', + ); }); - it('throws when called with an expired eventId', function() { - var eid = Activity.startEvent('', ''); + it('throws when called with an expired eventId', () => { + const eid = Activity.startEvent('', ''); Activity.endEvent(eid); - expect(function() { + expect(() => { Activity.endEvent(eid); }).toThrow('event(3) has already ended!'); @@ -75,17 +74,16 @@ describe('Activity', function() { }); }); - describe('signal', function() { - it('writes a SIGNAL event out to the console', function() { - - var EVENT_NAME = 'EVENT_NAME'; - var DATA = {someData: 42}; + describe('signal', () => { + it('writes a SIGNAL event out to the console', () => { + const EVENT_NAME = 'EVENT_NAME'; + const DATA = {someData: 42}; Activity.signal(EVENT_NAME, DATA); jest.runOnlyPendingTimers(); expect(console.log.mock.calls.length).toBe(1); - var consoleMsg = console.log.mock.calls[0][0]; + const consoleMsg = console.log.mock.calls[0][0]; expect(consoleMsg).toContain(EVENT_NAME); expect(consoleMsg).toContain(JSON.stringify(DATA)); }); diff --git a/packager/react-packager/src/Activity/index.js b/packager/react-packager/src/Activity/index.js index aeaf3143e..eccebd28b 100644 --- a/packager/react-packager/src/Activity/index.js +++ b/packager/react-packager/src/Activity/index.js @@ -8,21 +8,22 @@ */ 'use strict'; -var chalk = require('chalk'); -var events = require('events'); +const chalk = require('chalk'); +const events = require('events'); -var COLLECTION_PERIOD = 1000; +const COLLECTION_PERIOD = 1000; -var _endedEvents = Object.create(null); -var _eventStarts = Object.create(null); -var _queuedActions = []; -var _scheduledCollectionTimer = null; -var _uuid = 1; -var _enabled = true; -var _eventEmitter = new events.EventEmitter(); +const _endedEvents = Object.create(null); +const _eventStarts = Object.create(null); +const _queuedActions = []; +const _eventEmitter = new events.EventEmitter(); + +let _scheduledCollectionTimer = null; +let _uuid = 1; +let _enabled = true; function endEvent(eventId) { - var eventEndTime = Date.now(); + const eventEndTime = Date.now(); if (!_eventStarts[eventId]) { _throw('event(' + eventId + ') is not a valid event id!'); @@ -41,7 +42,7 @@ function endEvent(eventId) { } function signal(eventName, data) { - var signalTime = Date.now(); + const signalTime = Date.now(); if (eventName == null) { _throw('No event name specified'); @@ -60,7 +61,7 @@ function signal(eventName, data) { } function startEvent(eventName, data) { - var eventStartTime = Date.now(); + const eventStartTime = Date.now(); if (eventName == null) { _throw('No event name specified'); @@ -70,8 +71,8 @@ function startEvent(eventName, data) { data = null; } - var eventId = _uuid++; - var action = { + const eventId = _uuid++; + const action = { action: 'startEvent', data: data, eventId: eventId, @@ -90,7 +91,7 @@ function disable() { function _runCollection() { /* jshint -W084 */ - var action; + let action; while ((action = _queuedActions.shift())) { _writeAction(action); } @@ -117,10 +118,10 @@ function _scheduleAction(action) { * won't be adding such a non-trivial optimization anytime soon) */ function _throw(msg) { - var err = new Error(msg); + const err = new Error(msg); // Strip off the call to _throw() - var stack = err.stack.split('\n'); + const stack = err.stack.split('\n'); stack.splice(1, 1); err.stack = stack.join('\n'); @@ -132,8 +133,8 @@ function _writeAction(action) { return; } - var data = action.data ? ': ' + JSON.stringify(action.data) : ''; - var fmtTime = new Date(action.tstamp).toLocaleTimeString(); + const data = action.data ? ': ' + JSON.stringify(action.data) : ''; + const fmtTime = new Date(action.tstamp).toLocaleTimeString(); switch (action.action) { case 'startEvent': @@ -145,8 +146,8 @@ function _writeAction(action) { break; case 'endEvent': - var startAction = _eventStarts[action.eventId]; - var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; + const startAction = _eventStarts[action.eventId]; + const startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : ''; console.log(chalk.dim( '[' + fmtTime + '] ' + ' ' + startAction.eventName + From b18d73b568f75bcac006bb3b3f67d8206ace2678 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 18 Aug 2015 17:36:35 -0700 Subject: [PATCH 059/139] [RN] Unbreak packager --- packager/getFlowTypeCheckMiddleware.js | 2 +- packager/react-packager/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js index 3b6597610..4ef2fd0c5 100644 --- a/packager/getFlowTypeCheckMiddleware.js +++ b/packager/getFlowTypeCheckMiddleware.js @@ -11,7 +11,7 @@ var chalk = require('chalk'); var exec = require('child_process').exec; var url = require('url'); -var Activity = require('./react-packager/src/Activity'); +var Activity = require('./react-packager').Activity; var hasWarned = {}; diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index 4d87733fd..23a15c095 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -22,7 +22,7 @@ exports.middleware = function(options) { return server.processRequest.bind(server); }; -exports.activityEvents = Activity.eventEmitter; +exports.Activity = Activity; // Renamed "package" to "bundle". But maintain backwards // compat. From 26c4be62c89ec17af3512ca2fe5f184f75e8f484 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 19 Aug 2015 03:18:04 -0700 Subject: [PATCH 060/139] Fixed test flakiness due to a race condition with RCTRedBox Summary: The test runner relied on checking the current error message in the RCTRedbox, which is a singleton (yay, shared mutable state!). This lead to some tests that worked individually but failed when run together due to error messages in RCTRedBox left over from previous tests. I've replaced the call to -[RCTRedBox currentErrorMessage] by injecting a custom logging function to intercept the errors at source, which is a much more reliable solution. --- Libraries/RCTTest/RCTTestRunner.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 63a82a0ea..eed38cb38 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -11,7 +11,7 @@ #import "FBSnapshotTestController.h" #import "RCTAssert.h" -#import "RCTRedBox.h" +#import "RCTLog.h" #import "RCTRootView.h" #import "RCTTestModule.h" #import "RCTUtils.h" @@ -83,6 +83,13 @@ RCT_NOT_IMPLEMENTED(-init) - (void)runTest:(SEL)test module:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock { + __block NSString *error = nil; + RCTSetLogFunction(^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (level >= RCTLogLevelError) { + error = message; + } + }); + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL moduleProvider:_moduleProvider launchOptions:nil]; @@ -102,20 +109,19 @@ RCT_NOT_IMPLEMENTED(-init) [vc.view addSubview:rootView]; // Add as subview so it doesn't get resized NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage]; 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]; } [rootView removeFromSuperview]; + RCTSetLogFunction(RCTDefaultLogFunction); + NSArray *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) { return ![NSStringFromClass([subview class]) isEqualToString:@"_UILayoutGuide"]; }]]; RCTAssert(nonLayoutSubviews.count == 0, @"There shouldn't be any other views: %@", nonLayoutSubviews); - [[RCTRedBox sharedInstance] dismiss]; if (expectErrorBlock) { RCTAssert(expectErrorBlock(error), @"Expected an error but nothing matched."); } else { From deaef94471f86f06fa659c2c7893b01a4d9c41e0 Mon Sep 17 00:00:00 2001 From: James Ide Date: Wed, 19 Aug 2015 09:41:37 -0700 Subject: [PATCH 061/139] [Podspec] Remove AnimationExperimental subspec and fix Image subspec Summary: AnimationExperimental is gone; remove it. The Image subspec now depends on the networking code, so we have to pull in those files. These changes were necessary to publish 0.10.0-rc to CocoaPods, so would be convenient to get them into master for 0.11. Fixes #2354 Closes https://github.com/facebook/react-native/pull/2341 Github Author: James Ide --- React.podspec | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/React.podspec b/React.podspec index d5193bfcb..9634b6964 100644 --- a/React.podspec +++ b/React.podspec @@ -50,12 +50,6 @@ Pod::Spec.new do |s| ss.preserve_paths = "Libraries/AdSupport/*.js" end - s.subspec 'RCTAnimationExperimental' do |ss| - ss.dependency 'React/Core' - ss.source_files = "Libraries/Animation/RCTAnimationExperimental*.{h,m}" - ss.preserve_paths = "Libraries/Animation/*.js" - end - s.subspec 'RCTGeolocation' do |ss| ss.dependency 'React/Core' ss.source_files = "Libraries/Geolocation/*.{h,m}" @@ -64,6 +58,7 @@ Pod::Spec.new do |s| s.subspec 'RCTImage' do |ss| ss.dependency 'React/Core' + ss.dependency 'React/RCTNetwork' ss.source_files = "Libraries/Image/*.{h,m}" ss.preserve_paths = "Libraries/Image/*.js" end From ade571065e20bae76771e2f35f8943296f10d857 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Wed, 19 Aug 2015 10:44:23 -0700 Subject: [PATCH 063/139] [ReactNative] Open source NetworkingModule --- Examples/UIExplorer/{XHRExample.js => XHRExample.ios.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Examples/UIExplorer/{XHRExample.js => XHRExample.ios.js} (100%) diff --git a/Examples/UIExplorer/XHRExample.js b/Examples/UIExplorer/XHRExample.ios.js similarity index 100% rename from Examples/UIExplorer/XHRExample.js rename to Examples/UIExplorer/XHRExample.ios.js From 1aa29ceaf211c54fc6eb6b177ac32c70b932fe67 Mon Sep 17 00:00:00 2001 From: Abhishek Sood Date: Wed, 19 Aug 2015 14:12:22 -0700 Subject: [PATCH 065/139] @build-break --- Examples/UIExplorer/UIExplorerList.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 3db7074d7..03e7778b1 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -74,7 +74,7 @@ var APIS = [ require('./StatusBarIOSExample'), require('./TimerExample'), require('./VibrationIOSExample'), - require('./XHRExample'), + require('./XHRExample.ios'), require('./ImageEditingExample'), ]; From f4c7bb110354c2e6a4839d24d6268b100d5f9472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Wed, 19 Aug 2015 12:27:27 -0700 Subject: [PATCH 066/139] [react-packager] Fix bug on Bundles Layout algorithm Summary: The layout algorithm wasn't getting deep into the sync dependencies recursively to async dependencies. --- .../__tests__/BundlesLayout-test.js | 31 +++++++ .../BundlesLayoutIntegration-test.js | 89 +++++++++++++++---- .../react-packager/src/BundlesLayout/index.js | 50 ++++++----- .../src/DependencyResolver/fastfs.js | 1 - 4 files changed, 133 insertions(+), 38 deletions(-) diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index fce23226a..2531b1151 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -147,6 +147,37 @@ describe('BundlesLayout', () => { ); }); + pit('separate cache in which bundle is each dependency', () => { + DependencyResolver.prototype.getDependencies.mockImpl((path) => { + switch (path) { + case '/root/index.js': + return Promise.resolve({ + dependencies: [dep('/root/index.js'), dep('/root/a.js')], + asyncDependencies: [], + }); + case '/root/a.js': + return Promise.resolve({ + dependencies: [dep('/root/a.js')], + asyncDependencies: [['/root/b.js']], + }); + case '/root/b.js': + return Promise.resolve({ + dependencies: [dep('/root/b.js')], + asyncDependencies: [], + }); + default: + throw 'Undefined path: ' + path; + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then( + bundles => expect(bundles).toEqual([ + [dep('/root/index.js'), dep('/root/a.js')], + [dep('/root/b.js')], + ]) + ); + }); + pit('separate cache in which bundle is each dependency', () => { DependencyResolver.prototype.getDependencies.mockImpl((path) => { switch (path) { diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 672829c2b..5ad4b523f 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -12,6 +12,7 @@ jest .dontMock('absolute-path') .dontMock('crypto') .dontMock('underscore') + .dontMock('path') .dontMock('../index') .dontMock('../../lib/getAssetDataFromName') .dontMock('../../DependencyResolver/crawlers') @@ -29,6 +30,7 @@ jest .dontMock('../../DependencyResolver/ModuleCache'); const Promise = require('promise'); +const path = require('path'); jest.mock('fs'); @@ -39,6 +41,18 @@ describe('BundlesLayout', () => { var fileWatcher; var fs; + const polyfills = [ + 'polyfills/prelude_dev.js', + 'polyfills/prelude.js', + 'polyfills/require.js', + 'polyfills/polyfills.js', + 'polyfills/console.js', + 'polyfills/error-guard.js', + 'polyfills/String.prototype.es6.js', + 'polyfills/Array.prototype.es6.js', + ]; + const baseFs = getBaseFs(); + beforeEach(() => { fs = require('fs'); BundlesLayout = require('../index'); @@ -54,7 +68,7 @@ describe('BundlesLayout', () => { describe('generate', () => { function newBundlesLayout() { const resolver = new DependencyResolver({ - projectRoots: ['/root'], + projectRoots: ['/root', '/' + __dirname.split('/')[1]], fileWatcher: fileWatcher, cache: new Cache(), assetExts: ['js', 'png'], @@ -75,8 +89,31 @@ describe('BundlesLayout', () => { ); } + function setMockFilesystem(mockFs) { + fs.__setMockFilesystem(Object.assign(mockFs, baseFs)); + } + + pit('should bundle single-module app', () => { + setMockFilesystem({ + 'root': { + 'index.js': ` + /** + * @providesModule index + */`, + } + }); + + return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => + modulePaths(bundles).then(paths => + expect(paths).toEqual([ + ['/root/index.js'], + ]) + ) + ); + }); + pit('should bundle dependant modules', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -84,7 +121,7 @@ describe('BundlesLayout', () => { */ require("a");`, 'a.js': ` - /**, + /** * @providesModule a */`, } @@ -98,7 +135,7 @@ describe('BundlesLayout', () => { }); pit('should split bundles for async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -121,7 +158,7 @@ describe('BundlesLayout', () => { }); pit('should split into multiple bundles separate async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -150,7 +187,7 @@ describe('BundlesLayout', () => { }); pit('should put related async dependencies into the same bundle', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -177,7 +214,7 @@ describe('BundlesLayout', () => { }); pit('should fully traverse sync dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -205,7 +242,7 @@ describe('BundlesLayout', () => { }); pit('should include sync dependencies async dependencies might have', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -238,7 +275,7 @@ describe('BundlesLayout', () => { }); pit('should allow duplicated dependencies across bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -273,7 +310,7 @@ describe('BundlesLayout', () => { }); pit('should put in separate bundles async dependencies of async dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -307,7 +344,7 @@ describe('BundlesLayout', () => { }); pit('should dedup same async bundle duplicated dependencies', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -340,7 +377,7 @@ describe('BundlesLayout', () => { }); pit('should put image dependencies into separate bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -365,7 +402,7 @@ describe('BundlesLayout', () => { }); pit('should put image dependencies across bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -397,7 +434,7 @@ describe('BundlesLayout', () => { }); pit('could async require asset', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -417,7 +454,7 @@ describe('BundlesLayout', () => { }); pit('should include deprecated assets into separate bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -442,7 +479,7 @@ describe('BundlesLayout', () => { }); pit('could async require deprecated asset', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -462,7 +499,7 @@ describe('BundlesLayout', () => { }); pit('should put packages into bundles', () => { - fs.__setMockFilesystem({ + setMockFilesystem({ 'root': { 'index.js': ` /** @@ -491,4 +528,22 @@ describe('BundlesLayout', () => { ); }); }); + + function getBaseFs() { + const p = path.join(__dirname, '../../../DependencyResolver/polyfills').substring(1); + const root = {}; + let currentPath = root; + + p.split('/').forEach(part => { + const child = {}; + currentPath[part] = child; + currentPath = child; + }); + + polyfills.forEach(polyfill => + currentPath[polyfill.split('/')[1]] = '' + ); + + return root; + } }); diff --git a/packager/react-packager/src/BundlesLayout/index.js b/packager/react-packager/src/BundlesLayout/index.js index 165a33b34..507155549 100644 --- a/packager/react-packager/src/BundlesLayout/index.js +++ b/packager/react-packager/src/BundlesLayout/index.js @@ -38,28 +38,38 @@ class BundlesLayout { () => pending.length > 0, () => bundles, () => { - const pendingPaths = pending.shift(); - return Promise - .all(pendingPaths.map(path => - this._resolver.getDependencies(path, {dev: isDev}) - )) - .then(modulesDeps => { - let syncDependencies = Object.create(null); - modulesDeps.forEach(moduleDeps => { - moduleDeps.dependencies.forEach(dep => { - syncDependencies[dep.path] = dep - this._moduleToBundle[dep.path] = bundles.length; - }); - pending = pending.concat(moduleDeps.asyncDependencies); - }); + // pending sync dependencies we still need to explore for the current + // pending dependency + let pendingSyncDeps = pending.shift(); - syncDependencies = _.values(syncDependencies); - if (syncDependencies.length > 0) { - bundles.push(syncDependencies); + // accum variable for sync dependencies of the current pending + // dependency we're processing + const syncDependencies = Object.create(null); + + return promiseWhile( + () => pendingSyncDeps.length > 0, + () => { + const dependencies = _.values(syncDependencies); + if (dependencies.length > 0) { + bundles.push(dependencies); } - - return Promise.resolve(bundles); - }); + }, + () => { + const pendingSyncDep = pendingSyncDeps.shift(); + return this._resolver + .getDependencies(pendingSyncDep, {dev: isDev}) + .then(deps => { + deps.dependencies.forEach(dep => { + if (dep.path !== pendingSyncDep && !dep.isPolyfill) { + pendingSyncDeps.push(dep.path); + } + syncDependencies[dep.path] = dep; + this._moduleToBundle[dep.path] = bundles.length; + }); + pending = pending.concat(deps.asyncDependencies); + }); + }, + ); }, ); } diff --git a/packager/react-packager/src/DependencyResolver/fastfs.js b/packager/react-packager/src/DependencyResolver/fastfs.js index 884296023..944d411a0 100644 --- a/packager/react-packager/src/DependencyResolver/fastfs.js +++ b/packager/react-packager/src/DependencyResolver/fastfs.js @@ -155,7 +155,6 @@ class Fastfs extends EventEmitter { this._getAndAssertRoot(file.path).addChild(file); } - _processFileChange(type, filePath, root, fstat) { const absPath = path.join(root, filePath); if (this._ignore(absPath) || (fstat && fstat.isDirectory())) { From 0f3ea9fb8c8172cf271b4416f40e50545c258bdf Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 19 Aug 2015 13:44:22 -0700 Subject: [PATCH 067/139] [ReactNative] Fix switching to null event listener --- Libraries/ReactNative/ReactNativeBaseComponent.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Libraries/ReactNative/ReactNativeBaseComponent.js b/Libraries/ReactNative/ReactNativeBaseComponent.js index 84baf6753..844923a2b 100644 --- a/Libraries/ReactNative/ReactNativeBaseComponent.js +++ b/Libraries/ReactNative/ReactNativeBaseComponent.js @@ -27,6 +27,7 @@ var warning = require('warning'); var registrationNames = ReactNativeEventEmitter.registrationNames; var putListener = ReactNativeEventEmitter.putListener; +var deleteListener = ReactNativeEventEmitter.deleteListener; var deleteAllListeners = ReactNativeEventEmitter.deleteAllListeners; type ReactNativeBaseComponentViewConfig = { @@ -230,7 +231,11 @@ ReactNativeBaseComponent.Mixin = { _reconcileListenersUponUpdate: function(prevProps, nextProps) { for (var key in nextProps) { if (registrationNames[key] && (nextProps[key] !== prevProps[key])) { - putListener(this._rootNodeID, key, nextProps[key]); + if (nextProps[key]) { + putListener(this._rootNodeID, key, nextProps[key]); + } else { + deleteListener(this._rootNodeID, key); + } } } }, From d64ac201199031ad329aeaefa283eb62dd6882cf Mon Sep 17 00:00:00 2001 From: Abhishek Sood Date: Wed, 19 Aug 2015 15:20:09 -0700 Subject: [PATCH 068/139] Revert "[ReactNative] Open source NetworkingModule" --- Examples/UIExplorer/{XHRExample.ios.js => XHRExample.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Examples/UIExplorer/{XHRExample.ios.js => XHRExample.js} (100%) diff --git a/Examples/UIExplorer/XHRExample.ios.js b/Examples/UIExplorer/XHRExample.js similarity index 100% rename from Examples/UIExplorer/XHRExample.ios.js rename to Examples/UIExplorer/XHRExample.js From 5fc2d67ee556491880bcc1f462acda1ab000d198 Mon Sep 17 00:00:00 2001 From: Abhishek Sood Date: Wed, 19 Aug 2015 15:21:16 -0700 Subject: [PATCH 069/139] Revert "@build-break" --- Examples/UIExplorer/UIExplorerList.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 03e7778b1..3db7074d7 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -74,7 +74,7 @@ var APIS = [ require('./StatusBarIOSExample'), require('./TimerExample'), require('./VibrationIOSExample'), - require('./XHRExample.ios'), + require('./XHRExample'), require('./ImageEditingExample'), ]; From 966a3bbc66a78e9ea32e0ad93b86a3df830117d5 Mon Sep 17 00:00:00 2001 From: Tim Yung Date: Wed, 19 Aug 2015 15:42:27 -0700 Subject: [PATCH 070/139] RN: Style Tweaks to Chrome Debugger UI --- packager/debugger.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packager/debugger.html b/packager/debugger.html index 8d377f640..24f8aea55 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -23,7 +23,7 @@ window.localStorage.removeItem('sessionID'); window.onbeforeunload = function() { if (sessionID) { return 'If you reload this page, it is going to break the debugging session. ' + - 'You should ⌘+R in the iOS simulator to reload.'; + 'You should press ⌘R in simulator to reload.'; } }; @@ -76,10 +76,10 @@ function connectToDebuggerProxy() { ws.onopen = function() { if (sessionID) { - setStatus('Debugger session #' + sessionID + ' active'); + setStatus('Debugger session #' + sessionID + ' active.'); ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)})); } else { - setStatus('Waiting, press ⌘R in simulator to reload and connect'); + setStatus('Waiting, press ⌘R in simulator to reload and connect.'); } }; @@ -126,7 +126,8 @@ function loadScript(src, callback) { font-weight: 200; } .shortcut { - font-family: monospace; + font-family: "Monaco", monospace; + font-size: medium; color: #eee; background-color: #333; padding: 4px; @@ -175,10 +176,10 @@ function loadScript(src, callback) {

- React Native JS code runs inside this Chrome tab + React Native JS code runs inside this Chrome tab.

Press ⌘⌥J to open Developer Tools. Enable Pause On Caught Exceptions for a better debugging experience.

-

Status: Loading

+

Status: Loading...

From b4100b8332bff80025180a74d571dfcff8d4a44c Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 15:15:48 -0700 Subject: [PATCH 071/139] [react-packager] Wait for haste map before accepting any requests Summary: D2319999 introduced a regression where we stopped waiting for the "build haste map" step to finish before we accept any requests. This makes sure that we block on that. Need to unbreak with this, but will follow up with a test to catch this in the future. --- .../src/DependencyResolver/DependencyGraph/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 4cdf0c2ee..050ab214c 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -101,7 +101,7 @@ class DependencyGraph { this._fastfs.build() .then(() => { const hasteActivity = Activity.startEvent('Building Haste Map'); - this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); + return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); }), this._buildAssetMap_DEPRECATED(), ]); From d9bd4459406c1559c232c0e0b385ba9b23ee7848 Mon Sep 17 00:00:00 2001 From: Martin Bigio Date: Wed, 19 Aug 2015 16:25:00 -0400 Subject: [PATCH 072/139] Unbreak Catalyst --- Examples/UIExplorer/UIExplorerList.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerList.ios.js b/Examples/UIExplorer/UIExplorerList.ios.js index 3db7074d7..03e7778b1 100644 --- a/Examples/UIExplorer/UIExplorerList.ios.js +++ b/Examples/UIExplorer/UIExplorerList.ios.js @@ -74,7 +74,7 @@ var APIS = [ require('./StatusBarIOSExample'), require('./TimerExample'), require('./VibrationIOSExample'), - require('./XHRExample'), + require('./XHRExample.ios'), require('./ImageEditingExample'), ]; From 5a2d2007a4fcffa072a4bc3764622ac1baf30910 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 19 Aug 2015 15:36:36 -0700 Subject: [PATCH 073/139] [React Native] Fix infinite loop in css-layout Summary: Manual pick of https://github.com/facebook/css-layout/commit/9001a3d2 since we're still lagging behind the upstream. Fixes #1378. --- React/Layout/Layout.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index fb50d1fdc..11ca898ee 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -448,11 +448,12 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We want to execute the next two loops one per line with flex-wrap int startLine = 0; int endLine = 0; - int nextLine = 0; + // int nextOffset = 0; + int alreadyComputedNextLayout = 0; // We aggregate the total dimensions of the container in those two variables float linesCrossDim = 0; float linesMainDim = 0; - while (endLine != node->children_count) { + while (endLine < node->children_count) { // Layout non flexible children and count children by type // mainContentDim is accumulation of the dimensions and margin of all the @@ -496,7 +497,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { } // This is the main recursive call. We layout non flexible children. - if (nextLine == 0) { + if (alreadyComputedNextLayout == 0) { layoutNode(child, maxWidth); } @@ -512,12 +513,15 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // The element we are about to add would make us go to the next line if (isFlexWrap(node) && !isUndefined(node->layout.dimensions[dim[mainAxis]]) && - mainContentDim + nextContentDim > definedMainDim) { + mainContentDim + nextContentDim > definedMainDim && + // If there's only one element, then it's bigger than the content + // and needs its own line + i != startLine) { nonFlexibleChildrenCount--; - nextLine = i + 1; + alreadyComputedNextLayout = 1; break; } - nextLine = 0; + alreadyComputedNextLayout = 0; mainContentDim += nextContentDim; endLine = i + 1; } From e76e60f88d3e392c8cfc101fbc80eda1d1587ffa Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:11:50 -0700 Subject: [PATCH 074/139] [react-packager] Make the fs mock async --- packager/react-packager/src/__mocks__/fs.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packager/react-packager/src/__mocks__/fs.js b/packager/react-packager/src/__mocks__/fs.js index b5251a447..ced46a984 100644 --- a/packager/react-packager/src/__mocks__/fs.js +++ b/packager/react-packager/src/__mocks__/fs.js @@ -10,7 +10,14 @@ var fs = jest.genMockFromModule('fs'); +function asyncCallback(callback) { + return function() { + setImmediate(() => callback.apply(this, arguments)); + }; +} + fs.realpath.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); @@ -24,6 +31,7 @@ fs.realpath.mockImpl(function(filepath, callback) { }); fs.readdir.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); @@ -42,6 +50,7 @@ fs.readdir.mockImpl(function(filepath, callback) { }); fs.readFile.mockImpl(function(filepath, encoding, callback) { + callback = asyncCallback(callback); if (arguments.length === 2) { callback = encoding; encoding = null; @@ -60,6 +69,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) { }); fs.stat.mockImpl(function(filepath, callback) { + callback = asyncCallback(callback); var node; try { node = getToNode(filepath); From 24689006abd5043be9ca98bd1a686ebefbdeb418 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:19:31 -0700 Subject: [PATCH 075/139] [react-packager] Remove unnecessary 'chalk' module mock --- .../react-packager/src/Activity/__mocks__/chalk.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 packager/react-packager/src/Activity/__mocks__/chalk.js diff --git a/packager/react-packager/src/Activity/__mocks__/chalk.js b/packager/react-packager/src/Activity/__mocks__/chalk.js deleted file mode 100644 index 58bf03a6b..000000000 --- a/packager/react-packager/src/Activity/__mocks__/chalk.js +++ /dev/null @@ -1,13 +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'; - -module.exports = { - dim: s => s, -}; From c5c25c15eaa84b7ae06312323e6dd9a39cc47639 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 19 Aug 2015 16:31:21 -0700 Subject: [PATCH 076/139] [RN] Show only file name in RedBox --- React/Base/RCTRedBox.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 0af8ac819..b65caf5ea 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -206,10 +206,10 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) cell.textLabel.text = stackFrame[@"methodName"]; - NSString *fileAndLine = stackFrame[@"file"]; + NSString *fileAndLine = [stackFrame[@"file"] lastPathComponent]; if (fileAndLine) { fileAndLine = [fileAndLine stringByAppendingFormat:@":%@", stackFrame[@"lineNumber"]]; - cell.detailTextLabel.text = cell.detailTextLabel.text = fileAndLine; + cell.detailTextLabel.text = fileAndLine; } return cell; } From ec82ffa52b0c8e126f11d6b917263264b24556f8 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 19 Aug 2015 16:35:52 -0700 Subject: [PATCH 077/139] [react-packger] Add a timeout on transform jobs Summary: There's been a case where Babel can hang indefinitely on a file parse/transform. Possibly related to https://github.com/babel/babel/issues/2211 This adds a timeout to transform jobs and throws an error informing the user of the offending file. The timeout interval defaults to 10 seconds, but can be changed via an option. --- packager/react-packager/src/Bundler/index.js | 6 +- .../react-packager/src/JSTransformer/index.js | 64 ++++++++++++------- packager/react-packager/src/Server/index.js | 4 ++ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/packager/react-packager/src/Bundler/index.js b/packager/react-packager/src/Bundler/index.js index 854bd7148..c9842b728 100644 --- a/packager/react-packager/src/Bundler/index.js +++ b/packager/react-packager/src/Bundler/index.js @@ -71,7 +71,11 @@ const validateOpts = declareOpts({ assetServer: { type: 'object', required: true, - } + }, + transformTimeoutInterval: { + type: 'number', + required: false, + }, }); class Bundler { diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 52bb24ad1..5f5ca9509 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -17,6 +17,14 @@ const workerFarm = require('worker-farm'); const readFile = Promise.denodeify(fs.readFile); +// Avoid memory leaks caused in workers. This number seems to be a good enough number +// to avoid any memory leak while not slowing down initial builds. +// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. +const MAX_CALLS_PER_WORKER = 600; + +// Worker will timeout if one of the callers timeout. +const DEFAULT_MAX_CALL_TIME = 10000; + const validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -37,16 +45,15 @@ const validateOpts = declareOpts({ type: 'object', required: true, }, + transformTimeoutInterval: { + type: 'number', + default: DEFAULT_MAX_CALL_TIME, + } }); -// Avoid memory leaks caused in workers. This number seems to be a good enough number -// to avoid any memory leak while not slowing down initial builds. -// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. -const MAX_CALLS_PER_WORKER = 600; - class Transformer { constructor(options) { - const opts = validateOpts(options); + const opts = this._opts = validateOpts(options); this._cache = opts.cache; @@ -55,6 +62,7 @@ class Transformer { autoStart: true, maxConcurrentCallsPerWorker: 1, maxCallsPerWorker: MAX_CALLS_PER_WORKER, + maxCallTime: opts.transformTimeoutInterval, }, opts.transformModulePath); this._transform = Promise.denodeify(this._workers); @@ -74,21 +82,21 @@ class Transformer { return Promise.reject(new Error('No transfrom module')); } - var transform = this._transform; - return this._cache.get(filePath, 'transformedSource', function() { + return this._cache.get( + filePath, + 'transformedSource', // TODO: use fastfs to avoid reading file from disk again - return readFile(filePath) - .then(function(buffer) { - var sourceCode = buffer.toString(); + () => readFile(filePath).then( + buffer => { + const sourceCode = buffer.toString('utf8'); - return transform({ - sourceCode: sourceCode, - filename: filePath, - }).then( - function(res) { + return this._transform({ + sourceCode, + filename: filePath, + }).then(res => { if (res.error) { console.warn( - 'Error property on the result value form the transformer', + 'Error property on the result value from the transformer', 'module is deprecated and will be removed in future versions.', 'Please pass an error object as the first argument to the callback' ); @@ -101,15 +109,25 @@ class Transformer { sourcePath: filePath, sourceCode: sourceCode, }); - } - ); - }).catch(function(err) { - throw formatError(err, filePath); - }); - }); + }).catch(err => { + if (err.type === 'TimeoutError') { + const timeoutErr = new Error( + `TimeoutError: transforming ${filePath} took longer than ` + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + + `You can adjust timeout via the 'transformTimeoutInterval' option` + ); + timeoutErr.type = 'TimeoutError'; + throw timeoutErr; + } + + throw formatError(err, filePath); + }); + }) + ); } } + module.exports = Transformer; Transformer.TransformError = TransformError; diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 8f11be3c9..ba7e3d5fa 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -61,6 +61,10 @@ const validateOpts = declareOpts({ type: 'array', default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'], }, + transformTimeoutInterval: { + type: 'number', + required: false, + }, }); const bundleOpts = declareOpts({ From cc96962e3dc43bd1d042345de2d2154490f915bb Mon Sep 17 00:00:00 2001 From: Jing Chen Date: Wed, 19 Aug 2015 16:50:39 -0700 Subject: [PATCH 078/139] [react-native] Revert D2346396 to bring back JS stack traces --- React/Base/RCTLog.m | 85 +++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 50 deletions(-) diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 7e530664f..84dbf2d24 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -37,7 +37,7 @@ static RCTLogLevel RCTCurrentLogThreshold; __attribute__((constructor)) static void RCTLogSetup() { - RCTSetLogFunction(RCTDefaultLogFunction); + RCTCurrentLogFunction = RCTDefaultLogFunction; #if RCT_DEBUG RCTCurrentLogThreshold = RCTLogLevelInfo - 1; @@ -52,7 +52,8 @@ RCTLogFunction RCTDefaultLogFunction = ^( NSString *fileName, NSNumber *lineNumber, NSString *message -) { +) +{ NSString *log = RCTFormatLog( [NSDate date], level, fileName, lineNumber, message ); @@ -81,52 +82,7 @@ RCTLogFunction RCTDefaultLogFunction = ^( void RCTSetLogFunction(RCTLogFunction logFunction) { - -#if RCT_DEBUG // Red box is only available in debug mode - - RCTCurrentLogFunction = ^( - RCTLogLevel level, - NSString *fileName, - NSNumber *lineNumber, - NSString *message - ) { - // Log to red box - if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { - NSArray *stackSymbols = [NSThread callStackSymbols]; - NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)]; - [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) { - if (idx > 0) { // don't include the current frame - NSString *address = [[frameSymbols componentsSeparatedByString:@"0x"][1] componentsSeparatedByString:@" "][0]; - NSRange addressRange = [frameSymbols rangeOfString:address]; - NSString *methodName = [frameSymbols substringFromIndex:(addressRange.location + addressRange.length + 1)]; - if (idx == 1 && fileName && lineNumber) { - [stack addObject:@{ - @"methodName": methodName, - @"file": fileName.lastPathComponent, - @"lineNumber": lineNumber - }]; - } else { - [stack addObject:@{@"methodName": methodName}]; - } - } - }]; - [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; - } - - // Log to JS executor - [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; - - if (logFunction) { - logFunction(level, fileName, lineNumber, message); - } - }; - -#else - RCTCurrentLogFunction = logFunction; - -#endif - } RCTLogFunction RCTGetLogFunction() @@ -195,7 +151,8 @@ NSString *RCTFormatLog( NSString *fileName, NSNumber *lineNumber, NSString *message -) { +) +{ NSMutableString *log = [NSMutableString new]; if (timestamp) { static NSDateFormatter *formatter; @@ -231,8 +188,8 @@ void _RCTLogFormat( RCTLogLevel level, const char *fileName, int lineNumber, - NSString *format, ... -) { + NSString *format, ...) +{ RCTLogFunction logFunction = RCTGetLocalLogFunction(); BOOL log = RCT_DEBUG || (logFunction != nil); if (log && level >= RCTCurrentLogThreshold) { @@ -247,5 +204,33 @@ void _RCTLogFormat( if (logFunction) { logFunction(level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message); } + +#if RCT_DEBUG // Red box is only available in debug mode + + // Log to red box + if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) { + NSArray *stackSymbols = [NSThread callStackSymbols]; + NSMutableArray *stack = [NSMutableArray arrayWithCapacity:(stackSymbols.count - 1)]; + [stackSymbols enumerateObjectsUsingBlock:^(NSString *frameSymbols, NSUInteger idx, __unused BOOL *stop) { + if (idx > 0) { // don't include the current frame + NSString *address = [[frameSymbols componentsSeparatedByString:@"0x"][1] componentsSeparatedByString:@" "][0]; + NSRange addressRange = [frameSymbols rangeOfString:address]; + NSString *methodName = [frameSymbols substringFromIndex:(addressRange.location + addressRange.length + 1)]; + if (idx == 1) { + NSString *file = [[@(fileName) componentsSeparatedByString:@"/"] lastObject]; + [stack addObject:@{@"methodName": methodName, @"file": file, @"lineNumber": @(lineNumber)}]; + } else { + [stack addObject:@{@"methodName": methodName}]; + } + } + }]; + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + } + + // Log to JS executor + [RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"]; + +#endif + } } From dd7945ae3701e85cee438f7c2418709de41aef8d Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 19 Aug 2015 19:50:04 -0700 Subject: [PATCH 079/139] [OpenSource] Fix TicTacToe Summary: Needed to link RCTWebSocketManager to the project as instructed by the following issue: https://github.com/facebook/react-native/issues/2256 --- .../TicTacToe.xcodeproj/project.pbxproj | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj b/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj index 61e817e2b..ec0802a83 100644 --- a/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj +++ b/Examples/TicTacToe/TicTacToe.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 144C5F691AC3E5E300B004E7 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 144C5F681AC3E5D800B004E7 /* libReact.a */; }; 58C572511AA6229D00CDF9C8 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C5724D1AA6224400CDF9C8 /* libRCTText.a */; }; + 5FF8942E1B85571A007731BE /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FF8942D1B8556F8007731BE /* libRCTWebSocket.a */; }; 832044981B492C2500E297FC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832044951B492C1E00E297FC /* libRCTSettings.a */; }; /* End PBXBuildFile section */ @@ -39,6 +40,13 @@ remoteGlobalIDString = 58B5119B1A9E6C1200147676; remoteInfo = RCTText; }; + 5FF8942C1B8556F8007731BE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5FF894281B8556F8007731BE /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; 832044941B492C1E00E297FC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8320448F1B492C1E00E297FC /* RCTSettings.xcodeproj */; @@ -59,6 +67,7 @@ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = TicTacToe/main.m; sourceTree = ""; }; 144C5F631AC3E5D800B004E7 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = ""; }; 587650DA1A9EB0DB008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; + 5FF894281B8556F8007731BE /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 8320448F1B492C1E00E297FC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -67,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5FF8942E1B85571A007731BE /* libRCTWebSocket.a in Frameworks */, 144C5F691AC3E5E300B004E7 /* libReact.a in Frameworks */, 1341803E1AA91802003F314A /* libRCTImage.a in Frameworks */, 58C572511AA6229D00CDF9C8 /* libRCTText.a in Frameworks */, @@ -109,6 +119,7 @@ 58C572071AA6126D00CDF9C8 /* Libraries */ = { isa = PBXGroup; children = ( + 5FF894281B8556F8007731BE /* RCTWebSocket.xcodeproj */, 144C5F631AC3E5D800B004E7 /* React.xcodeproj */, 134180381AA917ED003F314A /* RCTImage.xcodeproj */, 8320448F1B492C1E00E297FC /* RCTSettings.xcodeproj */, @@ -125,6 +136,14 @@ name = Products; sourceTree = ""; }; + 5FF894291B8556F8007731BE /* Products */ = { + isa = PBXGroup; + children = ( + 5FF8942D1B8556F8007731BE /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; 832044901B492C1E00E297FC /* Products */ = { isa = PBXGroup; children = ( @@ -205,6 +224,10 @@ ProductGroup = 58C572481AA6224300CDF9C8 /* Products */; ProjectRef = 587650DA1A9EB0DB008B8F17 /* RCTText.xcodeproj */; }, + { + ProductGroup = 5FF894291B8556F8007731BE /* Products */; + ProjectRef = 5FF894281B8556F8007731BE /* RCTWebSocket.xcodeproj */; + }, { ProductGroup = 144C5F641AC3E5D800B004E7 /* Products */; ProjectRef = 144C5F631AC3E5D800B004E7 /* React.xcodeproj */; @@ -239,6 +262,13 @@ remoteRef = 58C5724C1AA6224400CDF9C8 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 5FF8942D1B8556F8007731BE /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 5FF8942C1B8556F8007731BE /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 832044951B492C1E00E297FC /* libRCTSettings.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; From cf617640f3f216a236e3a96d0a5d6146cb3bcf9d Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 19 Aug 2015 20:06:42 -0700 Subject: [PATCH 080/139] [OpenSource] Fix 2048 Summary: There were two issues: (1) it was using /Users/sahrens as a path (2) needed to remove RCTAnimationExperimental which doesn't exist anymore and add RCTWebSocket --- Examples/2048/2048.xcodeproj/project.pbxproj | 44 +++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/Examples/2048/2048.xcodeproj/project.pbxproj b/Examples/2048/2048.xcodeproj/project.pbxproj index 43fd7518b..48ebefe41 100644 --- a/Examples/2048/2048.xcodeproj/project.pbxproj +++ b/Examples/2048/2048.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 1461632D1AC3E23900C2F5AD /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1461632C1AC3E22900C2F5AD /* libReact.a */; }; - 58C1E40E1ACF54E9006D1A47 /* libRCTAnimationExperimental.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58C1E40B1ACF54B4006D1A47 /* libRCTAnimationExperimental.a */; }; + 5F82F1781B85785200FAE87E /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F82F1771B85784500FAE87E /* libRCTWebSocket.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -24,12 +24,12 @@ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; remoteInfo = React; }; - 58C1E40A1ACF54B4006D1A47 /* PBXContainerItemProxy */ = { + 5F82F1761B85784500FAE87E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = 13ACB66C1AC2113500FF4204 /* RCTAnimationExperimental.xcodeproj */; + containerPortal = 5F82F1721B85784500FAE87E /* RCTWebSocket.xcodeproj */; proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTAnimationExperimental; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; }; 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -41,7 +41,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 13ACB66C1AC2113500FF4204 /* RCTAnimationExperimental.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimationExperimental.xcodeproj; path = ../../Libraries/Animation/RCTAnimationExperimental.xcodeproj; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* 2048.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = 2048.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = 2048/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = 2048/AppDelegate.m; sourceTree = ""; }; @@ -50,6 +49,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = 2048/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = 2048/main.m; sourceTree = ""; }; 146163271AC3E22900C2F5AD /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../../React/React.xcodeproj; sourceTree = ""; }; + 5F82F1721B85784500FAE87E /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -58,8 +58,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 5F82F1781B85785200FAE87E /* libRCTWebSocket.a in Frameworks */, 1461632D1AC3E23900C2F5AD /* libReact.a in Frameworks */, - 58C1E40E1ACF54E9006D1A47 /* libRCTAnimationExperimental.a in Frameworks */, 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -88,10 +88,10 @@ name = Products; sourceTree = ""; }; - 58C1E4071ACF54B4006D1A47 /* Products */ = { + 5F82F1731B85784500FAE87E /* Products */ = { isa = PBXGroup; children = ( - 58C1E40B1ACF54B4006D1A47 /* libRCTAnimationExperimental.a */, + 5F82F1771B85784500FAE87E /* libRCTWebSocket.a */, ); name = Products; sourceTree = ""; @@ -99,9 +99,9 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 5F82F1721B85784500FAE87E /* RCTWebSocket.xcodeproj */, 146163271AC3E22900C2F5AD /* React.xcodeproj */, 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, - 13ACB66C1AC2113500FF4204 /* RCTAnimationExperimental.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -174,14 +174,14 @@ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; projectReferences = ( - { - ProductGroup = 58C1E4071ACF54B4006D1A47 /* Products */; - ProjectRef = 13ACB66C1AC2113500FF4204 /* RCTAnimationExperimental.xcodeproj */; - }, { ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; }, + { + ProductGroup = 5F82F1731B85784500FAE87E /* Products */; + ProjectRef = 5F82F1721B85784500FAE87E /* RCTWebSocket.xcodeproj */; + }, { ProductGroup = 146163281AC3E22900C2F5AD /* Products */; ProjectRef = 146163271AC3E22900C2F5AD /* React.xcodeproj */; @@ -202,11 +202,11 @@ remoteRef = 1461632B1AC3E22900C2F5AD /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 58C1E40B1ACF54B4006D1A47 /* libRCTAnimationExperimental.a */ = { + 5F82F1771B85784500FAE87E /* libRCTWebSocket.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; - path = libRCTAnimationExperimental.a; - remoteRef = 58C1E40A1ACF54B4006D1A47 /* PBXContainerItemProxy */; + path = libRCTWebSocket.a; + remoteRef = 5F82F1761B85784500FAE87E /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { @@ -266,10 +266,7 @@ ); INFOPLIST_FILE = "$(SRCROOT)/2048/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/Animation/build/Debug-iphoneos", - ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = 2048; @@ -287,10 +284,7 @@ ); INFOPLIST_FILE = "$(SRCROOT)/2048/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/Animation/build/Debug-iphoneos", - ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = 2048; From 059e605e49bb826a23a828f7dc8700b3aa8054c8 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 19 Aug 2015 20:06:22 -0700 Subject: [PATCH 081/139] [RN] Mark some internal animated stuff as protected --- Libraries/Animation/Animated/Animated.js | 90 ++++++++++++------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/Libraries/Animation/Animated/Animated.js b/Libraries/Animation/Animated/Animated.js index 221c153f9..ec4977f3b 100644 --- a/Libraries/Animation/Animated/Animated.js +++ b/Libraries/Animation/Animated/Animated.js @@ -33,13 +33,13 @@ type EndCallback = (result: EndResult) => void; // Note(vjeux): this would be better as an interface but flow doesn't // support them yet class Animated { - attach(): void {} - detach(): void {} + __attach(): void {} + __detach(): void {} __getValue(): any {} - getAnimatedValue(): any { return this.__getValue(); } - addChild(child: Animated) {} - removeChild(child: Animated) {} - getChildren(): Array { return []; } + __getAnimatedValue(): any { return this.__getValue(); } + __addChild(child: Animated) {} + __removeChild(child: Animated) {} + __getChildren(): Array { return []; } } // Important note: start() and stop() will only be called at most once. @@ -71,14 +71,14 @@ class AnimatedWithChildren extends Animated { this._children = []; } - addChild(child: Animated): void { + __addChild(child: Animated): void { if (this._children.length === 0) { - this.attach(); + this.__attach(); } this._children.push(child); } - removeChild(child: Animated): void { + __removeChild(child: Animated): void { var index = this._children.indexOf(child); if (index === -1) { console.warn('Trying to remove a child that doesn\'t exist'); @@ -86,11 +86,11 @@ class AnimatedWithChildren extends Animated { } this._children.splice(index, 1); if (this._children.length === 0) { - this.detach(); + this.__detach(); } } - getChildren(): Array { + __getChildren(): Array { return this._children; } } @@ -123,7 +123,7 @@ function _flush(rootNode: AnimatedValue): void { if (typeof node.update === 'function') { animatedStyles.add(node); } else { - node.getChildren().forEach(findAnimatedStyles); + node.__getChildren().forEach(findAnimatedStyles); } } findAnimatedStyles(rootNode); @@ -518,7 +518,7 @@ class AnimatedValue extends AnimatedWithChildren { this._listeners = {}; } - detach() { + __detach() { this.stopAnimation(); } @@ -584,7 +584,7 @@ class AnimatedValue extends AnimatedWithChildren { } stopTracking(): void { - this._tracking && this._tracking.detach(); + this._tracking && this._tracking.__detach(); this._tracking = null; } @@ -715,12 +715,12 @@ class AnimatedInterpolation extends AnimatedWithChildren { return new AnimatedInterpolation(this, Interpolation.create(config)); } - attach(): void { - this._parent.addChild(this); + __attach(): void { + this._parent.__addChild(this); } - detach(): void { - this._parent.removeChild(this); + __detach(): void { + this._parent.__removeChild(this); } } @@ -747,13 +747,13 @@ class AnimatedTransform extends AnimatedWithChildren { }); } - getAnimatedValue(): Array { + __getAnimatedValue(): Array { return this._transforms.map(transform => { var result = {}; for (var key in transform) { var value = transform[key]; if (value instanceof Animated) { - result[key] = value.getAnimatedValue(); + result[key] = value.__getAnimatedValue(); } else { // All transform components needed to recompose matrix result[key] = value; @@ -763,23 +763,23 @@ class AnimatedTransform extends AnimatedWithChildren { }); } - attach(): void { + __attach(): void { this._transforms.forEach(transform => { for (var key in transform) { var value = transform[key]; if (value instanceof Animated) { - value.addChild(this); + value.__addChild(this); } } }); } - detach(): void { + __detach(): void { this._transforms.forEach(transform => { for (var key in transform) { var value = transform[key]; if (value instanceof Animated) { - value.removeChild(this); + value.__removeChild(this); } } }); @@ -814,31 +814,31 @@ class AnimatedStyle extends AnimatedWithChildren { return style; } - getAnimatedValue(): Object { + __getAnimatedValue(): Object { var style = {}; for (var key in this._style) { var value = this._style[key]; if (value instanceof Animated) { - style[key] = value.getAnimatedValue(); + style[key] = value.__getAnimatedValue(); } } return style; } - attach(): void { + __attach(): void { for (var key in this._style) { var value = this._style[key]; if (value instanceof Animated) { - value.addChild(this); + value.__addChild(this); } } } - detach(): void { + __detach(): void { for (var key in this._style) { var value = this._style[key]; if (value instanceof Animated) { - value.removeChild(this); + value.__removeChild(this); } } } @@ -861,7 +861,7 @@ class AnimatedProps extends Animated { } this._props = props; this._callback = callback; - this.attach(); + this.__attach(); } __getValue(): Object { @@ -877,31 +877,31 @@ class AnimatedProps extends Animated { return props; } - getAnimatedValue(): Object { + __getAnimatedValue(): Object { var props = {}; for (var key in this._props) { var value = this._props[key]; if (value instanceof Animated) { - props[key] = value.getAnimatedValue(); + props[key] = value.__getAnimatedValue(); } } return props; } - attach(): void { + __attach(): void { for (var key in this._props) { var value = this._props[key]; if (value instanceof Animated) { - value.addChild(this); + value.__addChild(this); } } } - detach(): void { + __detach(): void { for (var key in this._props) { var value = this._props[key]; if (value instanceof Animated) { - value.removeChild(this); + value.__removeChild(this); } } } @@ -918,7 +918,7 @@ function createAnimatedComponent(Component: any): any { _propsAnimated: AnimatedProps; componentWillUnmount() { - this._propsAnimated && this._propsAnimated.detach(); + this._propsAnimated && this._propsAnimated.__detach(); } setNativeProps(props) { @@ -940,7 +940,7 @@ function createAnimatedComponent(Component: any): any { // forceUpdate. var callback = () => { if (this.refs[refName].setNativeProps) { - var value = this._propsAnimated.getAnimatedValue(); + var value = this._propsAnimated.__getAnimatedValue(); this.refs[refName].setNativeProps(value); } else { this.forceUpdate(); @@ -960,7 +960,7 @@ function createAnimatedComponent(Component: any): any { // This way the intermediate state isn't to go to 0 and trigger // this expensive recursive detaching to then re-attach everything on // the very next operation. - oldPropsAnimated && oldPropsAnimated.detach(); + oldPropsAnimated && oldPropsAnimated.__detach(); } componentWillReceiveProps(nextProps) { @@ -1000,19 +1000,19 @@ class AnimatedTracking extends Animated { this._animationClass = animationClass; this._animationConfig = animationConfig; this._callback = callback; - this.attach(); + this.__attach(); } __getValue(): Object { return this._parent.__getValue(); } - attach(): void { - this._parent.addChild(this); + __attach(): void { + this._parent.__addChild(this); } - detach(): void { - this._parent.removeChild(this); + __detach(): void { + this._parent.__removeChild(this); } update(): void { From 16ddb1962a043d99f704cffe95272aa1617e784f Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Wed, 19 Aug 2015 21:44:59 -0700 Subject: [PATCH 082/139] [ReactNative] Remove warning for Summary: require('./image.jpg') returns a number and therefore the propType is wrong. Adding it to the propType to fix the warning and dealing with flow which is completely broken for this. --- Examples/UIExplorer/ImageMocks.js | 20 ++++++++++---------- Libraries/Image/Image.ios.js | 10 +++++++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Examples/UIExplorer/ImageMocks.js b/Examples/UIExplorer/ImageMocks.js index 8335f93e5..670346373 100644 --- a/Examples/UIExplorer/ImageMocks.js +++ b/Examples/UIExplorer/ImageMocks.js @@ -15,37 +15,37 @@ */ 'use strict'; +/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing + * into a proptype shape */ declare module 'image!story-background' { - /* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing - * into a proptype shape */ declare var uri: string; declare var isStatic: boolean; } +/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing + * into a proptype shape */ declare module 'image!uie_comment_highlighted' { - /* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing - * into a proptype shape */ declare var uri: string; declare var isStatic: boolean; } +/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing + * into a proptype shape */ declare module 'image!uie_comment_normal' { - /* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing - * into a proptype shape */ declare var uri: string; declare var isStatic: boolean; } +/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing + * into a proptype shape */ declare module 'image!uie_thumb_normal' { - /* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing - * into a proptype shape */ declare var uri: string; declare var isStatic: boolean; } +/* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing + * into a proptype shape */ declare module 'image!uie_thumb_selected' { - /* $FlowIssue #7387208 - There's a flow bug preventing this type from flowing - * into a proptype shape */ declare var uri: string; declare var isStatic: boolean; } diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 7517b2e96..f641167c7 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -60,9 +60,13 @@ var Image = React.createClass({ * could be an http address, a local file path, or the name of a static image * resource (which should be wrapped in the `require('image!name')` function). */ - source: PropTypes.shape({ - uri: PropTypes.string, - }), + source: PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string, + }), + // Opaque type returned by require('./image.jpg') + PropTypes.number, + ]), /** * A static image to display while downloading the final image off the * network. From 81fdf3e5328f9b03204ecc906c5718bec874a4c7 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Wed, 19 Aug 2015 23:36:11 -0700 Subject: [PATCH 083/139] [ReactNative] Change RCTProfile API to be compatible with systrace --- Libraries/Utilities/BridgeProfiling.js | 10 +- React/Base/RCTBatchedBridge.m | 57 +++++----- React/Base/RCTProfile.h | 52 +++++---- React/Base/RCTProfile.m | 143 +++++++++++++++++++------ React/Executors/RCTContextExecutor.m | 29 +---- React/Modules/RCTUIManager.m | 8 +- 6 files changed, 187 insertions(+), 112 deletions(-) diff --git a/Libraries/Utilities/BridgeProfiling.js b/Libraries/Utilities/BridgeProfiling.js index 1b800901f..7f6b8860c 100644 --- a/Libraries/Utilities/BridgeProfiling.js +++ b/Libraries/Utilities/BridgeProfiling.js @@ -29,9 +29,9 @@ var BridgeProfiling = { } }, - profileEnd(profileName?: string) { + profileEnd() { if (GLOBAL.__BridgeProfilingIsProfiling) { - console.profileEnd(profileName); + console.profileEnd(); } }, @@ -41,14 +41,14 @@ var BridgeProfiling = { ReactPerf.measure = function (objName, fnName, func) { func = originalMeasure.call(ReactPerf, objName, fnName, func); return function (component) { - BridgeProfiling.profile(); - var ret = func.apply(this, arguments); if (GLOBAL.__BridgeProfilingIsProfiling) { var name = this._instance && this._instance.constructor && (this._instance.constructor.displayName || this._instance.constructor.name); - BridgeProfiling.profileEnd(`${objName}.${fnName}(${name})`); + BridgeProfiling.profile(`${objName}.${fnName}(${name})`); } + var ret = func.apply(this, arguments); + BridgeProfiling.profileEnd(); return ret; }; }; diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index c8387242c..d05625720 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -167,11 +167,11 @@ id RCTGetLatestExecutor(void) - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad { RCTPerformanceLoggerStart(RCTPLScriptDownload); - RCTProfileBeginEvent(); + int cookie = RCTProfileBeginAsyncEvent(0, @"JavaScript download", nil); RCTSourceLoadBlock onSourceLoad = ^(NSError *error, NSString *source) { RCTPerformanceLoggerEnd(RCTPLScriptDownload); - RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]); + RCTProfileEndAsyncEvent(0, @"init,download", cookie, @"JavaScript download", nil); if (error) { NSArray *stack = [error userInfo][@"stack"]; @@ -517,7 +517,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } -- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete +- (void)enqueueApplicationScript:(NSString *)script + url:(NSURL *)url + onComplete:(RCTJavaScriptCompleteBlock)onComplete { RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil"); @@ -531,20 +533,21 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL return; } - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"FetchApplicationScriptCallbacks", nil); [_javaScriptExecutor executeJSCall:@"BatchedBridge" method:@"flushedQueue" arguments:@[] - callback:^(id json, NSError *error) { - RCTProfileEndEvent(@"FetchApplicationScriptCallbacks", @"js_call,init", @{ - @"json": RCTNullIfNil(json), - @"error": RCTNullIfNil(error), - }); + callback:^(id json, NSError *error) + { + RCTProfileEndEvent(0, @"js_call,init", @{ + @"json": RCTNullIfNil(json), + @"error": RCTNullIfNil(error), + }); - [self _handleBuffer:json]; + [self _handleBuffer:json]; - onComplete(error); - }]; + onComplete(error); + }]; }]; } @@ -565,7 +568,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL __weak RCTBatchedBridge *weakSelf = self; [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"enqueue_call", nil); RCTBatchedBridge *strongSelf = weakSelf; if (!strongSelf.isValid || !strongSelf->_scheduledCallbacks || !strongSelf->_scheduledCalls) { @@ -588,11 +591,13 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [strongSelf->_scheduledCalls addObject:call]; } - RCTProfileEndEvent(@"enqueue_call", @"objc_call", call); + RCTProfileEndEvent(0, @"objc_call", call); }]; } -- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args +- (void)_actuallyInvokeAndProcessModule:(NSString *)module + method:(NSString *)method + arguments:(NSArray *)args { RCTAssertJSThread(); @@ -678,7 +683,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [moduleData dispatchBlock:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, RCTCurrentThreadName(), nil); NSOrderedSet *calls = [buckets objectForKey:moduleData]; @autoreleasepool { @@ -691,7 +696,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } - RCTProfileEndEvent(RCTCurrentThreadName(), @"objc_call,dispatch_async", @{ @"calls": @(calls.count) }); + RCTProfileEndEvent(0, @"objc_call,dispatch_async", @{ + @"calls": @(calls.count), + }); }]; } @@ -719,7 +726,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL return NO; } - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"Invoke callback", nil); RCTModuleData *moduleData = _moduleDataByID[moduleID]; if (RCT_DEBUG && !moduleData) { @@ -743,7 +750,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL } } - RCTProfileEndEvent(@"Invoke callback", @"objc_call", @{ + RCTProfileEndEvent(0, @"objc_call", @{ @"module": NSStringFromClass(method.moduleClass), @"method": method.JSMethodName, @"selector": NSStringFromSelector(method.selector), @@ -756,7 +763,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL - (void)_jsThreadUpdate:(CADisplayLink *)displayLink { RCTAssertJSThread(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"DispatchFrameUpdate", nil); RCTFrameUpdate *frameUpdate = [[RCTFrameUpdate alloc] initWithDisplayLink:displayLink]; for (RCTModuleData *moduleData in _frameUpdateObservers) { @@ -767,9 +774,9 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL [moduleData dispatchBlock:^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, name, nil); [observer didUpdateFrame:frameUpdate]; - RCTProfileEndEvent(name, @"objc_call,fps", nil); + RCTProfileEndEvent(0, @"objc_call,fps", nil); }]; } } @@ -777,7 +784,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL NSArray *calls = [_scheduledCallbacks.allObjects arrayByAddingObjectsFromArray:_scheduledCalls]; RCT_IF_DEV( - RCTProfileImmediateEvent(@"JS Thread Tick", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(0, @"JS Thread Tick", 'g'); for (NSDictionary *call in calls) { _RCTProfileEndFlowEvent(call[@"call_id"]); @@ -792,7 +799,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL arguments:@[[calls valueForKey:@"js_args"]]]; } - RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); + RCTProfileEndEvent(0, @"objc_call", nil); dispatch_async(dispatch_get_main_queue(), ^{ [self.perfStats.jsGraph onTick:displayLink.timestamp]; @@ -803,7 +810,7 @@ RCT_NOT_IMPLEMENTED(-initWithBundleURL:(__unused NSURL *)bundleURL { RCTAssertMainThread(); - RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g"); + RCTProfileImmediateEvent(0, @"VSYNC", 'g'); _modulesByName == nil ?: [self.perfStats.uiGraph onTick:displayLink.timestamp]; } diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 66cf40bf4..794f4a910 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -59,36 +59,42 @@ RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *); /** * Collects the initial event information for the event and returns a reference ID */ -RCT_EXTERN NSNumber *_RCTProfileBeginEvent(void); +RCT_EXTERN void RCTProfileBeginEvent(uint64_t tag, + NSString *name, + NSDictionary *args); /** * The ID returned by BeginEvent should then be passed into EndEvent, with the * rest of the event information. Just at this point the event will actually be * registered */ -RCT_EXTERN void _RCTProfileEndEvent(NSNumber *, NSString *, NSString *, id); +RCT_EXTERN void RCTProfileEndEvent(uint64_t tag, + NSString *category, + NSDictionary *args); /** - * This pair of macros implicitly handle the event ID when beginning and ending - * an event, for both simplicity and performance reasons, this method is preferred - * - * NOTE: The EndEvent call has to be either, in the same scope of BeginEvent, - * or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible - * for EndEvent, in this case you may want to use the actual C functions. + * Collects the initial event information for the event and returns a reference ID */ -#define RCTProfileBeginEvent() \ -_Pragma("clang diagnostic push") \ -_Pragma("clang diagnostic ignored \"-Wshadow\"") \ -NSNumber *__rct_profile_id = _RCTProfileBeginEvent(); \ -_Pragma("clang diagnostic pop") - -#define RCTProfileEndEvent(name, category, args...) \ -_RCTProfileEndEvent(__rct_profile_id, name, category, args) +RCT_EXTERN int RCTProfileBeginAsyncEvent(uint64_t tag, + NSString *name, + NSDictionary *args); +/** + * The ID returned by BeginEvent should then be passed into EndEvent, with the + * rest of the event information. Just at this point the event will actually be + * registered + */ +RCT_EXTERN void RCTProfileEndAsyncEvent(uint64_t tag, + NSString *category, + int cookie, + NSString *name, + NSDictionary *args); /** * An event that doesn't have a duration (i.e. Notification, VSync, etc) */ -RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString *); +RCT_EXTERN void RCTProfileImmediateEvent(uint64_t tag, + NSString *name, + char scope); /** * Helper to profile the duration of the execution of a block. This method uses @@ -96,11 +102,11 @@ RCT_EXTERN void RCTProfileImmediateEvent(NSString *, NSTimeInterval , NSString * * * NOTE: The block can't expect any argument */ -#define RCTProfileBlock(block, category, arguments) \ +#define RCTProfileBlock(block, tag, category, arguments) \ ^{ \ - RCTProfileBeginEvent(); \ + RCTProfileBeginEvent(tag, @(__PRETTY_FUNCTION__), nil); \ block(); \ - RCTProfileEndEvent([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd)], category, arguments); \ + RCTProfileEndEvent(tag, category, arguments); \ } /** @@ -125,12 +131,12 @@ RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *); #define RCTProfileInit(...) #define RCTProfileEnd(...) @"" -#define _RCTProfileBeginEvent(...) @0 #define RCTProfileBeginEvent(...) - -#define _RCTProfileEndEvent(...) #define RCTProfileEndEvent(...) +#define RCTProfileBeginAsyncEvent(...) 0 +#define RCTProfileEndAsyncEvent(...) + #define RCTProfileImmediateEvent(...) #define RCTProfileBlock(block, ...) block diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 2de44b206..85d5a799a 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -26,12 +26,6 @@ NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; #if RCT_DEV -#pragma mark - Prototypes - -NSNumber *RCTProfileTimestamp(NSTimeInterval); -NSString *RCTProfileMemory(vm_size_t); -NSDictionary *RCTProfileGetMemoryUsage(void); - #pragma mark - Constants NSString const *RCTProfileTraceEvents = @"traceEvents"; @@ -67,18 +61,18 @@ __VA_ARGS__ \ #pragma mark - Private Helpers -NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) +static NSNumber *RCTProfileTimestamp(NSTimeInterval timestamp) { return @((timestamp - RCTProfileStartTime) * 1e6); } -NSString *RCTProfileMemory(vm_size_t memory) +static NSString *RCTProfileMemory(vm_size_t memory) { double mem = ((double)memory) / 1024 / 1024; return [NSString stringWithFormat:@"%.2lfmb", mem]; } -NSDictionary *RCTProfileGetMemoryUsage(void) +static NSDictionary *RCTProfileGetMemoryUsage(void) { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); @@ -97,6 +91,22 @@ NSDictionary *RCTProfileGetMemoryUsage(void) } } +static NSDictionary *RCTProfileMergeArgs(NSDictionary *args0, NSDictionary *args1) +{ + args0 = RCTNilIfNull(args0); + args1 = RCTNilIfNull(args1); + + if (!args0 && args1) { + args0 = args1; + } else if (args0 && args1) { + NSMutableDictionary *d = [args0 mutableCopy]; + [d addEntriesFromDictionary:args1]; + args0 = [d copy]; + } + + return RCTNullIfNil(args0); +} + #pragma mark - Module hooks static const char *RCTProfileProxyClassName(Class); @@ -120,9 +130,9 @@ static void RCTProfileForwardInvocation(NSObject *self, __unused SEL cmd, NSInvo if ([object_getClass(self) instancesRespondToSelector:newSel]) { invocation.selector = newSel; - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, name, nil); [invocation invoke]; - RCTProfileEndEvent(name, @"objc_call,modules,auto", nil); + RCTProfileEndEvent(0, @"objc_call,modules,auto", nil); } else if ([self respondsToSelector:invocation.selector]) { [invocation invoke]; } else { @@ -248,45 +258,116 @@ NSString *RCTProfileEnd(RCTBridge *bridge) return log; } -NSNumber *_RCTProfileBeginEvent(void) +static NSMutableArray *RCTProfileGetThreadEvents(void) { - CHECK(@0); - RCTProfileLock( - NSNumber *eventID = @(++RCTProfileEventID); - RCTProfileOngoingEvents[eventID] = RCTProfileTimestamp(CACurrentMediaTime()); - ); - return eventID; + static NSString *const RCTProfileThreadEventsKey = @"RCTProfileThreadEventsKey"; + NSMutableArray *threadEvents = [NSThread currentThread].threadDictionary[RCTProfileThreadEventsKey]; + if (!threadEvents) { + threadEvents = [[NSMutableArray alloc] init]; + [NSThread currentThread].threadDictionary[RCTProfileThreadEventsKey] = threadEvents; + } + return threadEvents; } -void _RCTProfileEndEvent(NSNumber *eventID, NSString *name, NSString *categories, id args) +void RCTProfileBeginEvent(uint64_t tag, NSString *name, NSDictionary *args) { CHECK(); + NSMutableArray *events = RCTProfileGetThreadEvents(); + [events addObject:@[ + RCTProfileTimestamp(CACurrentMediaTime()), + @(tag), + name, + RCTNullIfNil(args), + ]]; +} + +void RCTProfileEndEvent( + __unused uint64_t tag, + NSString *category, + NSDictionary *args +) { + CHECK(); + + NSMutableArray *events = RCTProfileGetThreadEvents(); + NSArray *event = [events lastObject]; + [events removeLastObject]; + + if (!event) { + return; + } + + NSNumber *start = event[0]; + RCTProfileLock( - NSNumber *startTimestamp = RCTProfileOngoingEvents[eventID]; - if (startTimestamp) { + RCTProfileAddEvent(RCTProfileTraceEvents, + @"name": event[2], + @"cat": category, + @"ph": @"X", + @"ts": start, + @"dur": @(RCTProfileTimestamp(CACurrentMediaTime()).doubleValue - start.doubleValue), + @"args": RCTProfileMergeArgs(event[3], args), + ); + ); +} + +int RCTProfileBeginAsyncEvent( + __unused uint64_t tag, + NSString *name, + NSDictionary *args +) { + CHECK(0); + + static int eventID = 0; + + RCTProfileLock( + RCTProfileOngoingEvents[@(eventID)] = @[ + RCTProfileTimestamp(CACurrentMediaTime()), + name, + RCTNullIfNil(args), + ]; + ); + + return eventID++; +} + +void RCTProfileEndAsyncEvent( + __unused uint64_t tag, + NSString *category, + int cookie, + __unused NSString *name, + NSDictionary *args +) { + CHECK(); + RCTProfileLock( + NSArray *event = RCTProfileOngoingEvents[@(cookie)]; + if (event) { NSNumber *endTimestamp = RCTProfileTimestamp(CACurrentMediaTime()); RCTProfileAddEvent(RCTProfileTraceEvents, - @"name": name, - @"cat": categories, + @"name": event[1], + @"cat": category, @"ph": @"X", - @"ts": startTimestamp, - @"dur": @(endTimestamp.doubleValue - startTimestamp.doubleValue), - @"args": args ?: @[], + @"ts": event[0], + @"dur": @(endTimestamp.doubleValue - [event[0] doubleValue]), + @"args": RCTProfileMergeArgs(event[2], args), ); - [RCTProfileOngoingEvents removeObjectForKey:eventID]; + [RCTProfileOngoingEvents removeObjectForKey:@(cookie)]; } ); } -void RCTProfileImmediateEvent(NSString *name, NSTimeInterval timestamp, NSString *scope) -{ +void RCTProfileImmediateEvent( + __unused uint64_t tag, + NSString *name, + char scope +) { CHECK(); + RCTProfileLock( RCTProfileAddEvent(RCTProfileTraceEvents, @"name": name, - @"ts": RCTProfileTimestamp(timestamp), - @"scope": scope, + @"ts": RCTProfileTimestamp(CACurrentMediaTime()), + @"scope": @(scope), @"ph": @"i", @"args": RCTProfileGetMemoryUsage(), ); diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 9eb147dbf..95f22fe92 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -136,18 +136,10 @@ static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __u #if RCT_DEV -static NSMutableArray *profiles; - static JSValueRef RCTConsoleProfile(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], __unused JSValueRef *exception) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - profiles = [NSMutableArray new]; - }); - static int profileCounter = 1; NSString *profileName; - NSNumber *profileID = _RCTProfileBeginEvent(); if (argumentCount > 0) { profileName = RCTJSValueToNSString(context, arguments[0]); @@ -160,25 +152,14 @@ static JSValueRef RCTConsoleProfile(JSContextRef context, __unused JSObjectRef o profileInfo = @[RCTJSValueToNSString(context, arguments[1])]; } - [profiles addObjectsFromArray:@[profileName, profileID, profileInfo]]; + RCTProfileBeginEvent(0, profileName, profileInfo); return JSValueMakeUndefined(context); } static JSValueRef RCTConsoleProfileEnd(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception) { - NSString *profileInfo = [profiles lastObject]; - [profiles removeLastObject]; - NSNumber *profileID = [profiles lastObject]; - [profiles removeLastObject]; - NSString *profileName = [profiles lastObject]; - [profiles removeLastObject]; - - if (argumentCount > 0 && !JSValueIsUndefined(context, arguments[0])) { - profileName = RCTJSValueToNSString(context, arguments[0]); - } - - _RCTProfileEndEvent(profileID, profileName, @"console", profileInfo); + RCTProfileEndEvent(0, @"console", nil); return JSValueMakeUndefined(context); } @@ -461,7 +442,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(objcValue, nil); - }), @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; + }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))]; } - (void)executeApplicationScript:(NSString *)script @@ -493,7 +474,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } onComplete(error); } - }), @"js_call", (@{ @"url": sourceURL.absoluteString }))]; + }), 0, @"js_call", (@{ @"url": sourceURL.absoluteString }))]; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block @@ -555,7 +536,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) if (onComplete) { onComplete(nil); } - }), @"js_call,json_call", (@{@"objectName": objectName}))]; + }), 0, @"js_call,json_call", (@{@"objectName": objectName}))]; } RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name) diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 7f72a8074..811d661d6 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -825,7 +825,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po - (void)batchDidComplete { - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"[RCTUIManager batchDidComplete]", nil); // Gather blocks to be executed now that all view hierarchy manipulations have // been completed (note that these may still take place before layout has finished) @@ -857,7 +857,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po _nextLayoutAnimation = nil; } - RCTProfileEndEvent(@"[RCTUIManager batchDidComplete]", @"uimanager", @{ + RCTProfileEndEvent(0, @"uimanager", @{ @"view_count": @([_viewRegistry count]), }); [self flushUIBlocks]; @@ -877,7 +877,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po RCTProfileBeginFlowEvent(); dispatch_async(dispatch_get_main_queue(), ^{ RCTProfileEndFlowEvent(); - RCTProfileBeginEvent(); + RCTProfileBeginEvent(0, @"UIManager flushUIBlocks", nil); @try { for (dispatch_block_t block in previousPendingUIBlocks) { block(); @@ -886,7 +886,7 @@ RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)po @catch (NSException *exception) { RCTLogError(@"Exception thrown while executing UI block: %@", exception); } - RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{ + RCTProfileEndEvent(0, @"objc_call", @{ @"count": @(previousPendingUIBlocks.count), }); }); From ee82a195f733f9f74ee3b88c65a6a66619a0ea6b Mon Sep 17 00:00:00 2001 From: Alexsander Akers Date: Wed, 19 Aug 2015 12:37:53 -0100 Subject: [PATCH 084/139] Set padding on ModalFullscreenView to offset status bar frame --- React/Modules/RCTStatusBarManager.m | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index bf32f835c..3cb4f3120 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -9,6 +9,7 @@ #import "RCTStatusBarManager.h" +#import "RCTEventDispatcher.h" #import "RCTLog.h" @implementation RCTConvert (UIStatusBar) @@ -42,11 +43,53 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() RCT_EXPORT_MODULE() +@synthesize bridge = _bridge; + +- (instancetype)init +{ + if ((self = [super init])) { + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil]; + [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil]; + + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } +- (void)emitEvent:(NSString *)eventName forNotification:(NSNotification *)notification +{ + CGRect frame = [notification.userInfo[UIApplicationStatusBarFrameUserInfoKey] CGRectValue]; + NSDictionary *event = @{ + @"frame": @{ + @"x": @(frame.origin.x), + @"y": @(frame.origin.y), + @"width": @(frame.size.width), + @"height": @(frame.size.height), + }, + }; + [_bridge.eventDispatcher sendDeviceEventWithName:eventName body:event]; +} + +- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification +{ + [self emitEvent:@"statusBarFrameDidChange" forNotification:notification]; +} + +- (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification +{ + [self emitEvent:@"statusBarFrameWillChange" forNotification:notification]; +} + RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated) { From 0b5952a45454c208a0e4b99fd4d2ef86d2d094e2 Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 20 Aug 2015 08:33:54 -0100 Subject: [PATCH 085/139] [ReactNative][oss] Open source NetworkingModule --- Examples/UIExplorer/{XHRExample.js => XHRExample.ios.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Examples/UIExplorer/{XHRExample.js => XHRExample.ios.js} (100%) diff --git a/Examples/UIExplorer/XHRExample.js b/Examples/UIExplorer/XHRExample.ios.js similarity index 100% rename from Examples/UIExplorer/XHRExample.js rename to Examples/UIExplorer/XHRExample.ios.js From b888e8db40918c9d7c206784a03e7d7614959f24 Mon Sep 17 00:00:00 2001 From: Dorota Kapturkiewicz Date: Thu, 20 Aug 2015 07:43:29 -0700 Subject: [PATCH 086/139] [ReactNative] make sending accessibility events from JS possible and fix dialog --- Libraries/Portal/Portal.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Libraries/Portal/Portal.js b/Libraries/Portal/Portal.js index a029bb99f..7406fb63f 100644 --- a/Libraries/Portal/Portal.js +++ b/Libraries/Portal/Portal.js @@ -6,7 +6,9 @@ */ 'use strict'; +var Platform = require('Platform'); var React = require('React'); +var RCTUIManager = require('NativeModules').UIManager; var StyleSheet = require('StyleSheet'); var View = require('View'); @@ -72,7 +74,15 @@ var Portal = React.createClass({ return []; } return _portalRef._getOpenModals(); - } + }, + + notifyAccessibilityService: function() { + if (!_portalRef) { + console.error('Calling closeModal but no Portal has been rendered.'); + return; + } + _portalRef._notifyAccessibilityService(); + }, }, getInitialState: function() { @@ -106,6 +116,20 @@ var Portal = React.createClass({ return Object.keys(this.state.modals); }, + _notifyAccessibilityService: function() { + if (Platform.OS === 'android') { + // We need to send accessibility event in a new batch, as otherwise + // TextViews have no text set at the moment of populating event. + setTimeout(() => { + if (this._getOpenModals().length > 0) { + RCTUIManager.sendAccessibilityEvent( + React.findNodeHandle(this), + RCTUIManager.AccessibilityEventTypes.typeWindowStateChanged); + } + }, 0); + } + }, + render: function() { _portalRef = this; if (!this.state.modals) { @@ -119,7 +143,9 @@ var Portal = React.createClass({ return null; } return ( - + {modals} ); From 1ca1a44a077352291c95fb6b75f78eb1504ed936 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 20 Aug 2015 08:47:41 -0700 Subject: [PATCH 087/139] [Logs] Remove already registered warning --- React/Base/RCTBatchedBridge.m | 6 ------ 1 file changed, 6 deletions(-) diff --git a/React/Base/RCTBatchedBridge.m b/React/Base/RCTBatchedBridge.m index d05625720..3756717e2 100644 --- a/React/Base/RCTBatchedBridge.m +++ b/React/Base/RCTBatchedBridge.m @@ -246,12 +246,6 @@ id RCTGetLatestExecutor(void) "'%@', but name was already registered by class %@", moduleClass, moduleName, [modulesByName[moduleName] class]); } - if ([module class] != moduleClass) { - RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered " - "in the project, but name was already registered by class %@." - "That's fine if it's intentional - just letting you know.", - moduleClass, moduleName, [modulesByName[moduleName] class]); - } } else { // Module name hasn't been used before, so go ahead and instantiate module = [moduleClass new]; From ed9b8c8b3bf2cd7bab4be9f69baf695ccb066c85 Mon Sep 17 00:00:00 2001 From: Dorota Kapturkiewicz Date: Thu, 20 Aug 2015 09:11:16 -0700 Subject: [PATCH 088/139] [ReactNative] Disable accessibility of RootView when dialog appears. Apply it for AdsManager. --- Libraries/Portal/Portal.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Libraries/Portal/Portal.js b/Libraries/Portal/Portal.js index 7406fb63f..652e9ed6f 100644 --- a/Libraries/Portal/Portal.js +++ b/Libraries/Portal/Portal.js @@ -90,6 +90,11 @@ var Portal = React.createClass({ }, _showModal: function(tag: string, component: any) { + // We are about to open first modal, so Portal will appear. + // Let's disable accessibility for background view on Android. + if (this._getOpenModals().length === 0) { + this.props.onModalVisibilityChanged(true); + } // This way state is chained through multiple calls to // _showModal, _closeModal correctly. this.setState((state) => { @@ -103,6 +108,11 @@ var Portal = React.createClass({ if (!this.state.modals.hasOwnProperty(tag)) { return; } + // We are about to close last modal, so Portal will disappear. + // Let's enable accessibility for application view on Android. + if (this._getOpenModals().length === 1) { + this.props.onModalVisibilityChanged(false); + } // This way state is chained through multiple calls to // _showModal, _closeModal correctly. this.setState((state) => { From 5de079612609ea16a6ce0e98a94c68258fa06a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 20 Aug 2015 08:40:09 -0700 Subject: [PATCH 089/139] [react-packager] Introduce bundle IDs and keep track of parent/child Summary: Since JS doesn't have the guarantee that once a bundle is loaded it will stay in memory (and this is something we actually don't want to enforce to keep memmory usage low), we need to keep track of parent/child relationships on the packager to pass it down to native. As part of this diff, we also introduced an ID for each bundle. The ID for a child bundle is shynthetized as the bundleID of the parent module + an index which gets incremented every time a new bundle is created. For instance given this tree: a,b c f d e g the ID for `d` will be `bundle.0.1.2`, the one for e will be `bundle.0.1.3` and the one for `g` will be `bundle.0.5.6`. This information will be useful to figure out which bundles need to be loaded when a `require.ensure` is re-written. --- .../__tests__/BundlesLayout-test.js | 92 ++++-- .../BundlesLayoutIntegration-test.js | 298 +++++++++++++----- .../react-packager/src/BundlesLayout/index.js | 44 ++- 3 files changed, 322 insertions(+), 112 deletions(-) diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js index 2531b1151..3a792a446 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayout-test.js @@ -29,8 +29,15 @@ describe('BundlesLayout', () => { }); } + function isPolyfill() { + return false; + } + function dep(path) { - return {path}; + return { + path: path, + isPolyfill: isPolyfill, + }; } pit('should bundle sync dependencies', () => { @@ -52,9 +59,11 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js'), dep('/root/a.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js'), dep('/root/a.js')], + children: [], + }) ); }); @@ -77,10 +86,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id:'bundle.0.1', + modules: [dep('/root/a.js')], + children: [], + }], + }) ); }); @@ -108,11 +122,19 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js')], - [dep('/root/b.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/a.js')], + children: [{ + id: 'bundle.0.1.2', + modules: [dep('/root/b.js')], + children: [], + }], + }], + }) ); }); @@ -140,10 +162,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(bundles).toEqual([ - [dep('/root/index.js')], - [dep('/root/a.js'), dep('/root/b.js')], - ]) + expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/a.js'), dep('/root/b.js')], + children: [], + }], + }) ); }); @@ -171,10 +198,15 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then( - bundles => expect(bundles).toEqual([ - [dep('/root/index.js'), dep('/root/a.js')], - [dep('/root/b.js')], - ]) + bundles => expect(bundles).toEqual({ + id: 'bundle.0', + modules: [dep('/root/index.js'), dep('/root/a.js')], + children: [{ + id: 'bundle.0.1', + modules: [dep('/root/b.js')], + children: [], + }], + }) ); }); @@ -184,7 +216,7 @@ describe('BundlesLayout', () => { case '/root/index.js': return Promise.resolve({ dependencies: [dep('/root/index.js'), dep('/root/a.js')], - asyncDependencies: [['/root/b.js']], + asyncDependencies: [['/root/b.js'], ['/root/c.js']], }); case '/root/a.js': return Promise.resolve({ @@ -194,13 +226,18 @@ describe('BundlesLayout', () => { case '/root/b.js': return Promise.resolve({ dependencies: [dep('/root/b.js')], - asyncDependencies: [['/root/c.js']], + asyncDependencies: [['/root/d.js']], }); case '/root/c.js': return Promise.resolve({ dependencies: [dep('/root/c.js')], asyncDependencies: [], }); + case '/root/d.js': + return Promise.resolve({ + dependencies: [dep('/root/d.js')], + asyncDependencies: [], + }); default: throw 'Undefined path: ' + path; } @@ -208,10 +245,11 @@ describe('BundlesLayout', () => { var layout = newBundlesLayout(); return layout.generateLayout(['/root/index.js']).then(() => { - expect(layout.getBundleIDForModule('/root/index.js')).toBe(0); - expect(layout.getBundleIDForModule('/root/a.js')).toBe(0); - expect(layout.getBundleIDForModule('/root/b.js')).toBe(1); - expect(layout.getBundleIDForModule('/root/c.js')).toBe(2); + expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0'); + expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0'); + expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1'); + expect(layout.getBundleIDForModule('/root/c.js')).toBe('bundle.0.2'); + expect(layout.getBundleIDForModule('/root/d.js')).toBe('bundle.0.1.3'); }); }); }); diff --git a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js index 5ad4b523f..9ff0f4354 100644 --- a/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js +++ b/packager/react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js @@ -78,15 +78,37 @@ describe('BundlesLayout', () => { return new BundlesLayout({dependencyResolver: resolver}); } - function modulePaths(bundles) { - if (!bundles) { - return null; - } + function stripPolyfills(bundle) { + return Promise + .all([ + Promise.all( + bundle.modules.map(module => module + .getName() + .then(name => [module, name]) + ), + ), + Promise.all( + bundle.children.map(childModule => stripPolyfills(childModule)), + ), + ]) + .then(([modules, children]) => { + modules = modules + .filter(([module, name]) => { // filter polyfills + for (let p of polyfills) { + if (name.indexOf(p) !== -1) { + return false; + } + } + return true; + }) + .map(([module, name]) => module.path); - return bundles.map( - bundle => bundle.filter(module => !module.isPolyfill()) - .map(module => module.path) - ); + return { + id: bundle.id, + modules: modules, + children: children, + }; + }); } function setMockFilesystem(mockFs) { @@ -104,10 +126,12 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - modulePaths(bundles).then(paths => - expect(paths).toEqual([ - ['/root/index.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [], + }) ) ); }); @@ -128,9 +152,13 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js', '/root/a.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js', '/root/a.js'], + children: [], + }) + ) ); }); @@ -150,10 +178,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [], + }], + }) + ) ); }); @@ -178,11 +213,23 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ['/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [], + }, { + id: 'bundle.0.2', + modules: ['/root/b.js'], + children: [], + }, + ], + }) + ) ); }); @@ -206,10 +253,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -234,10 +288,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js', '/root/a.js'], - ['/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js', '/root/a.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -267,10 +328,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/b.js', '/root/c.js'], + children: [], + }], + }) + ) ); }); @@ -301,11 +369,24 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/c.js'], - ['/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/c.js'], + children: [], + }, + { + id: 'bundle.0.2', + modules: ['/root/b.js', '/root/c.js'], + children: [], + }, + ], + }) + ) ); }); @@ -335,11 +416,23 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js'], - ['/root/b.js', '/root/c.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js'], + children: [{ + id: 'bundle.0.1.2', + modules: ['/root/b.js', '/root/c.js'], + children: [], + }], + }, + ], + }) + ) ); }); @@ -369,10 +462,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/c.js', '/root/b.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/c.js', '/root/b.js'], + children: [], + }], + }) + ) ); }); @@ -394,10 +494,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -425,11 +532,24 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ['/root/b.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [ + { + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }, + { + id: 'bundle.0.2', + modules: ['/root/b.js', '/root/img.png'], + children: [], + }, + ], + }) + ) ); }); @@ -446,10 +566,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -471,10 +598,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/a.js', '/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/a.js', '/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -491,10 +625,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/img.png'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/img.png'], + children: [], + }], + }) + ) ); }); @@ -521,10 +662,17 @@ describe('BundlesLayout', () => { }); return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles => - expect(modulePaths(bundles)).toEqual([ - ['/root/index.js'], - ['/root/aPackage/client.js'], - ]) + stripPolyfills(bundles).then(resolvedBundles => + expect(resolvedBundles).toEqual({ + id: 'bundle.0', + modules: ['/root/index.js'], + children: [{ + id: 'bundle.0.1', + modules: ['/root/aPackage/client.js'], + children: [], + }], + }) + ) ); }); }); diff --git a/packager/react-packager/src/BundlesLayout/index.js b/packager/react-packager/src/BundlesLayout/index.js index 507155549..d7693a73d 100644 --- a/packager/react-packager/src/BundlesLayout/index.js +++ b/packager/react-packager/src/BundlesLayout/index.js @@ -18,6 +18,8 @@ const validateOpts = declareOpts({ }, }); +const BUNDLE_PREFIX = 'bundle'; + /** * Class that takes care of separating the graph of dependencies into * separate bundles @@ -31,16 +33,23 @@ class BundlesLayout { } generateLayout(entryPaths, isDev) { - const bundles = []; - var pending = [entryPaths]; + var currentBundleID = 0; + const rootBundle = { + id: BUNDLE_PREFIX + '.' + currentBundleID++, + modules: [], + children: [], + }; + var pending = [{paths: entryPaths, bundle: rootBundle}]; return promiseWhile( () => pending.length > 0, - () => bundles, + () => rootBundle, () => { + const {paths, bundle} = pending.shift(); + // pending sync dependencies we still need to explore for the current // pending dependency - let pendingSyncDeps = pending.shift(); + const pendingSyncDeps = paths; // accum variable for sync dependencies of the current pending // dependency we're processing @@ -51,22 +60,31 @@ class BundlesLayout { () => { const dependencies = _.values(syncDependencies); if (dependencies.length > 0) { - bundles.push(dependencies); + bundle.modules = dependencies; } }, - () => { + index => { const pendingSyncDep = pendingSyncDeps.shift(); return this._resolver .getDependencies(pendingSyncDep, {dev: isDev}) .then(deps => { deps.dependencies.forEach(dep => { - if (dep.path !== pendingSyncDep && !dep.isPolyfill) { + if (dep.path !== pendingSyncDep && !dep.isPolyfill()) { pendingSyncDeps.push(dep.path); } syncDependencies[dep.path] = dep; - this._moduleToBundle[dep.path] = bundles.length; + this._moduleToBundle[dep.path] = bundle.id; + }); + deps.asyncDependencies.forEach(asyncDeps => { + const childBundle = { + id: bundle.id + '.' + currentBundleID++, + modules: [], + children: [], + }; + + bundle.children.push(childBundle); + pending.push({paths: asyncDeps, bundle: childBundle}); }); - pending = pending.concat(deps.asyncDependencies); }); }, ); @@ -83,11 +101,17 @@ class BundlesLayout { // Once it's not satisfied anymore, it returns what the results callback // indicates function promiseWhile(condition, result, body) { + return _promiseWhile(condition, result, body, 0); +} + +function _promiseWhile(condition, result, body, index) { if (!condition()) { return Promise.resolve(result()); } - return body().then(() => promiseWhile(condition, result, body)); + return body(index).then(() => + _promiseWhile(condition, result, body, index + 1) + ); } module.exports = BundlesLayout; From 5f91d404e1d5aefd6b4b72e5d09e4b47abd4087b Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Thu, 20 Aug 2015 10:15:43 -0700 Subject: [PATCH 090/139] [RN] Fix Animated tests --- .../Animated/__tests__/Animated-test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Libraries/Animation/Animated/__tests__/Animated-test.js b/Libraries/Animation/Animated/__tests__/Animated-test.js index d27ff920c..5f24fc546 100644 --- a/Libraries/Animation/Animated/__tests__/Animated-test.js +++ b/Libraries/Animation/Animated/__tests__/Animated-test.js @@ -37,7 +37,7 @@ describe('Animated', () => { } }, callback); - expect(anim.getChildren().length).toBe(3); + expect(anim.__getChildren().length).toBe(3); expect(node.__getValue()).toEqual({ style: { @@ -65,8 +65,8 @@ describe('Animated', () => { }, }); - node.detach(); - expect(anim.getChildren().length).toBe(0); + node.__detach(); + expect(anim.__getChildren().length).toBe(0); anim.setValue(1); expect(callback.mock.calls.length).toBe(1); @@ -74,7 +74,7 @@ describe('Animated', () => { it('does not detach on updates', () => { var anim = new Animated.Value(0); - anim.detach = jest.genMockFunction(); + anim.__detach = jest.genMockFunction(); var c = new Animated.View(); c.props = { @@ -84,16 +84,16 @@ describe('Animated', () => { }; c.componentWillMount(); - expect(anim.detach).not.toBeCalled(); + expect(anim.__detach).not.toBeCalled(); c.componentWillReceiveProps({ style: { opacity: anim, }, }); - expect(anim.detach).not.toBeCalled(); + expect(anim.__detach).not.toBeCalled(); c.componentWillUnmount(); - expect(anim.detach).toBeCalled(); + expect(anim.__detach).toBeCalled(); }); @@ -442,7 +442,7 @@ describe('Animated Vectors', () => { }, }); - node.detach(); + node.__detach(); vec.setValue({x: 1, y: 1}); expect(callback.mock.calls.length).toBe(2); From c1f90c1ecf1faf812669504a251722ae558b24e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Thu, 20 Aug 2015 10:53:28 -0700 Subject: [PATCH 091/139] [react-packager] Integration test for `runServerHere.sh` --- .../src/DependencyResolver/DependencyGraph/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js index 050ab214c..2daab2271 100644 --- a/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -79,7 +79,8 @@ class DependencyGraph { return this._loading; } - const crawlActivity = Activity.startEvent('fs crawl'); + const depGraphActivity = Activity.startEvent('Building Dependency Graph'); + const crawlActivity = Activity.startEvent('Crawling File System'); const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED); this._crawling = crawl(allRoots, { ignore: this._opts.ignoreFilePath, @@ -104,7 +105,9 @@ class DependencyGraph { return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity)); }), this._buildAssetMap_DEPRECATED(), - ]); + ]).then(() => + Activity.endEvent(depGraphActivity) + ); return this._loading; } From a0168a8c62826d999753b67062141d78da913337 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Thu, 20 Aug 2015 12:46:23 -0700 Subject: [PATCH 092/139] [ReactNative] [SyncDiff] Create new non-coalesceable event for when native should take the responder lock --- .../react/browser/eventPlugins/ResponderEventPlugin.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js b/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js index 8ab4b866e..b84985e6b 100644 --- a/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js +++ b/Libraries/vendor/react/browser/eventPlugins/ResponderEventPlugin.js @@ -430,9 +430,13 @@ function setResponderAndExtractTransfer( * @param {string} topLevelType Record from `EventConstants`. * @return {boolean} True if a transfer of responder could possibly occur. */ -function canTriggerTransfer(topLevelType, topLevelTargetID) { +function canTriggerTransfer(topLevelType, topLevelTargetID, nativeEvent) { return topLevelTargetID && ( - topLevelType === EventConstants.topLevelTypes.topScroll || + // responderIgnoreScroll: We are trying to migrate away from specifically tracking native scroll + // events here and responderIgnoreScroll indicates we will send topTouchCancel to handle + // canceling touch events instead + (topLevelType === EventConstants.topLevelTypes.topScroll && + !nativeEvent.responderIgnoreScroll) || (trackedTouchCount > 0 && topLevelType === EventConstants.topLevelTypes.topSelectionChange) || isStartish(topLevelType) || @@ -509,7 +513,7 @@ var ResponderEventPlugin = { ResponderTouchHistoryStore.recordTouchTrack(topLevelType, nativeEvent, nativeEventTarget); - var extracted = canTriggerTransfer(topLevelType, topLevelTargetID) ? + var extracted = canTriggerTransfer(topLevelType, topLevelTargetID, nativeEvent) ? setResponderAndExtractTransfer( topLevelType, topLevelTargetID, From febc03a38b33a75d82cad06a5073adff975282d6 Mon Sep 17 00:00:00 2001 From: Christopher Chedeau Date: Thu, 20 Aug 2015 12:57:34 -0700 Subject: [PATCH 093/139] [Logs] Don't print flow error twice Summary: Flow errors are already throwing an exception in js, no need to also console.error in the packager --- packager/getFlowTypeCheckMiddleware.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js index 4ef2fd0c5..bc89b70ad 100644 --- a/packager/getFlowTypeCheckMiddleware.js +++ b/packager/getFlowTypeCheckMiddleware.js @@ -89,9 +89,6 @@ function doFlowTypecheck(res, flowroot, next) { type: 'FlowError', errors: errors, }; - console.error(chalk.yellow('flow: Error running command `' + flowCmd + - '`:\n' + JSON.stringify(error)) - ); res.writeHead(error.status, { 'Content-Type': 'application/json; charset=UTF-8', }); From 3bfa90e433585598dfa0d197bccc37df75a7698f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 20 Aug 2015 14:39:43 -0700 Subject: [PATCH 094/139] [react-packager] Fix error in template string and bump timeout Summary: Fix error in the template string (no plus, thinks it's a function). And bump the timeout to 30 seconds because a file is taking more than 10 seconds `js/RKJSModules/Libraries/FBComponents/FBFed/FeedStoryFragments.js` --- packager/react-packager/src/JSTransformer/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packager/react-packager/src/JSTransformer/index.js b/packager/react-packager/src/JSTransformer/index.js index 5f5ca9509..6906d69f4 100644 --- a/packager/react-packager/src/JSTransformer/index.js +++ b/packager/react-packager/src/JSTransformer/index.js @@ -23,7 +23,7 @@ const readFile = Promise.denodeify(fs.readFile); const MAX_CALLS_PER_WORKER = 600; // Worker will timeout if one of the callers timeout. -const DEFAULT_MAX_CALL_TIME = 10000; +const DEFAULT_MAX_CALL_TIME = 30000; const validateOpts = declareOpts({ projectRoots: { @@ -112,7 +112,7 @@ class Transformer { }).catch(err => { if (err.type === 'TimeoutError') { const timeoutErr = new Error( - `TimeoutError: transforming ${filePath} took longer than ` + `TimeoutError: transforming ${filePath} took longer than ` + `${this._opts.transformTimeoutInterval / 1000} seconds.\n` + `You can adjust timeout via the 'transformTimeoutInterval' option` ); From 4b28a847e6f20c4d020ba4bd833613dd4df20783 Mon Sep 17 00:00:00 2001 From: Emily Janzer Date: Thu, 20 Aug 2015 17:57:33 -0700 Subject: [PATCH 095/139] [android] fix crash if breadcrumb refs don't exist --- .../NavigatorBreadcrumbNavigationBar.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js index 7f8debc84..ddfd33266 100644 --- a/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js +++ b/Libraries/CustomComponents/Navigator/NavigatorBreadcrumbNavigationBar.js @@ -119,16 +119,16 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ } if (interpolate.Crumb(CRUMB_PROPS[index].style, amount)) { - this.refs['crumb_' + index].setNativeProps(CRUMB_PROPS[index]); + this._setPropsIfExists('crumb_' + index, CRUMB_PROPS[index]); } if (interpolate.Icon(ICON_PROPS[index].style, amount)) { - this.refs['icon_' + index].setNativeProps(ICON_PROPS[index]); + this._setPropsIfExists('icon_' + index, ICON_PROPS[index]); } if (interpolate.Separator(SEPARATOR_PROPS[index].style, amount)) { - this.refs['separator_' + index].setNativeProps(SEPARATOR_PROPS[index]); + this._setPropsIfExists('separator_' + index, SEPARATOR_PROPS[index]); } if (interpolate.Title(TITLE_PROPS[index].style, amount)) { - this.refs['title_' + index].setNativeProps(TITLE_PROPS[index]); + this._setPropsIfExists('title_' + index, TITLE_PROPS[index]); } var right = this.refs['right_' + index]; if (right && @@ -165,13 +165,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ renderToHardwareTextureAndroid: renderToHardwareTexture, }; - this.refs['icon_' + index].setNativeProps(props); - this.refs['separator_' + index].setNativeProps(props); - this.refs['title_' + index].setNativeProps(props); - var right = this.refs['right_' + index]; - if (right) { - right.setNativeProps(props); - } + this._setPropsIfExists('icon_' + index, props); + this._setPropsIfExists('separator_' + index, props); + this._setPropsIfExists('title_' + index, props); + this._setPropsIfExists('right_' + index, props); }, componentWillMount: function() { @@ -260,6 +257,11 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({ this._descriptors.right = this._descriptors.right.set(route, rightButtonDescriptor); return rightButtonDescriptor; }, + + _setPropsIfExists: function(ref, props) { + var ref = this.refs[ref]; + ref && ref.setNativeProps(props); + }, }); var styles = StyleSheet.create({ From 6f17dba39b447a540491ce7f8e6256a59ea2d4c5 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 20 Aug 2015 21:18:05 -0700 Subject: [PATCH 096/139] [ReactNative] Remove POPAnimation Summary: Thanks to @vjeux' work on Animated, we have transitioned away from POPAnimation entirely --- .../{Animation => }/Animated/Animated.js | 0 Libraries/{Animation => }/Animated/Easing.js | 0 .../{Animation => }/Animated/Interpolation.js | 0 .../{Animation => }/Animated/SpringConfig.js | 0 .../Animated/__tests__/Animated-test.js | 0 .../Animated/__tests__/Easing-test.js | 0 .../Animated/__tests__/Interpolation-test.js | 0 .../{Animation => }/Animated/package.json | 0 Libraries/Animation/POPAnimationMixin.js | 268 ------------------ .../BatchedBridgedModules/POPAnimation.js | 187 ------------ .../LayoutAnimation.js | 0 Libraries/ReactIOS/NativeMethodsMixin.js | 25 -- 12 files changed, 480 deletions(-) rename Libraries/{Animation => }/Animated/Animated.js (100%) rename Libraries/{Animation => }/Animated/Easing.js (100%) rename Libraries/{Animation => }/Animated/Interpolation.js (100%) rename Libraries/{Animation => }/Animated/SpringConfig.js (100%) rename Libraries/{Animation => }/Animated/__tests__/Animated-test.js (100%) rename Libraries/{Animation => }/Animated/__tests__/Easing-test.js (100%) rename Libraries/{Animation => }/Animated/__tests__/Interpolation-test.js (100%) rename Libraries/{Animation => }/Animated/package.json (100%) delete mode 100644 Libraries/Animation/POPAnimationMixin.js delete mode 100644 Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js rename Libraries/{Animation => LayoutAnimation}/LayoutAnimation.js (100%) diff --git a/Libraries/Animation/Animated/Animated.js b/Libraries/Animated/Animated.js similarity index 100% rename from Libraries/Animation/Animated/Animated.js rename to Libraries/Animated/Animated.js diff --git a/Libraries/Animation/Animated/Easing.js b/Libraries/Animated/Easing.js similarity index 100% rename from Libraries/Animation/Animated/Easing.js rename to Libraries/Animated/Easing.js diff --git a/Libraries/Animation/Animated/Interpolation.js b/Libraries/Animated/Interpolation.js similarity index 100% rename from Libraries/Animation/Animated/Interpolation.js rename to Libraries/Animated/Interpolation.js diff --git a/Libraries/Animation/Animated/SpringConfig.js b/Libraries/Animated/SpringConfig.js similarity index 100% rename from Libraries/Animation/Animated/SpringConfig.js rename to Libraries/Animated/SpringConfig.js diff --git a/Libraries/Animation/Animated/__tests__/Animated-test.js b/Libraries/Animated/__tests__/Animated-test.js similarity index 100% rename from Libraries/Animation/Animated/__tests__/Animated-test.js rename to Libraries/Animated/__tests__/Animated-test.js diff --git a/Libraries/Animation/Animated/__tests__/Easing-test.js b/Libraries/Animated/__tests__/Easing-test.js similarity index 100% rename from Libraries/Animation/Animated/__tests__/Easing-test.js rename to Libraries/Animated/__tests__/Easing-test.js diff --git a/Libraries/Animation/Animated/__tests__/Interpolation-test.js b/Libraries/Animated/__tests__/Interpolation-test.js similarity index 100% rename from Libraries/Animation/Animated/__tests__/Interpolation-test.js rename to Libraries/Animated/__tests__/Interpolation-test.js diff --git a/Libraries/Animation/Animated/package.json b/Libraries/Animated/package.json similarity index 100% rename from Libraries/Animation/Animated/package.json rename to Libraries/Animated/package.json diff --git a/Libraries/Animation/POPAnimationMixin.js b/Libraries/Animation/POPAnimationMixin.js deleted file mode 100644 index 0e109062c..000000000 --- a/Libraries/Animation/POPAnimationMixin.js +++ /dev/null @@ -1,268 +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 POPAnimationMixin - * @flow - */ -'use strict'; - -var POPAnimationOrNull = require('POPAnimation'); -var React = require('React'); - -if (!POPAnimationOrNull) { - // POP animation isn't available in the OSS fork - this is a temporary - // workaround to enable its availability to be determined at runtime. - module.exports = (null : ?{}); -} else { - -// At this point, POPAnimationOrNull is guaranteed to be -// non-null. Bring it local to preserve type refinement. -var POPAnimation = POPAnimationOrNull; - -var invariant = require('invariant'); -var warning = require('warning'); - -var POPAnimationMixin = { - /** - * Different ways to interpolate between beginning and end states - * of properties during animation, such as spring, linear, and decay. - */ - AnimationTypes: POPAnimation.Types, - AnimationProperties: POPAnimation.Properties, - - getInitialState: function(): Object { - this._popAnimationEnqueuedAnimationTimeouts = []; - return { - _currentAnimationsByNodeHandle: {}, - }; - }, - - _ensureBookkeepingSetup: function(nodeHandle: any) { - if (!this.state._currentAnimationsByNodeHandle[nodeHandle]) { - this.state._currentAnimationsByNodeHandle[nodeHandle] = []; - } - }, - - /** - * Start animating the View with ref `refKey`. - * - * @param {key} refKey The key to reference the View to be animated. - * - * @param {number|Object} anim Either the identifier returned by - * POPAnimation.create* or an object defining all the necessary - * properties of the animation you wish to start (including type, matching - * an entry in AnimationTypes). - * - * @param {func} doneCallback A callback fired when the animation is done, and - * is passed a `finished` param that indicates whether the animation - * completely finished, or was interrupted. - */ - startAnimation: function( - refKey: string, - anim: number | {type: number; property: number;}, - doneCallback: (finished: bool) => void - ) { - var animID: number = 0; - if (typeof anim === 'number') { - animID = anim; - } else { - invariant( - anim instanceof Object && - anim.type !== undefined && - anim.property !== undefined, - 'Animation definitions must specify a type of animation and a ' + - 'property to animate.' - ); - animID = POPAnimation.createAnimation(anim.type, anim); - } - invariant( - this.refs[refKey], - 'Invalid refKey ' + refKey + ' for anim:\n' + JSON.stringify(anim) + - '\nvalid refs: ' + JSON.stringify(Object.keys(this.refs)) - ); - var refNodeHandle = React.findNodeHandle(this.refs[refKey]); - this.startAnimationWithNodeHandle(refNodeHandle, animID, doneCallback); - }, - - /** - * Starts an animation on a native node. - * - * @param {NodeHandle} nodeHandle Handle to underlying native node. - * @see `startAnimation`. - */ - startAnimationWithNodeHandle: function( - nodeHandle: any, - animID: number, - doneCallback: (finished: bool) => void - ) { - this._ensureBookkeepingSetup(nodeHandle); - var animations = this.state._currentAnimationsByNodeHandle[nodeHandle]; - var animIndex = animations.length; - animations.push(animID); - var cleanupWrapper = (finished) => { - if (!this.isMounted()) { - return; - } - animations[animIndex] = 0; // zero it out so we don't try to stop it - var allDone = true; - for (var ii = 0; ii < animations.length; ii++) { - if (animations[ii]) { - allDone = false; - break; - } - } - if (allDone) { - this.state._currentAnimationsByNodeHandle[nodeHandle] = undefined; - } - doneCallback && doneCallback(finished); - }; - // Hack to aviod race condition in POP: - var animationTimeoutHandler = setTimeout(() => { - POPAnimation.addAnimation(nodeHandle, animID, cleanupWrapper); - }, 1); - this._popAnimationEnqueuedAnimationTimeouts.push(animationTimeoutHandler); - }, - - /** - * Starts multiple animations with one shared callback that is called when all - * animations complete. - * - * @param {Array(Object} animations Array of objects defining all the - * animations to start, each with shape `{ref|nodeHandle, anim}`. - * @param {func} onSuccess A callback fired when all animations have returned, - * and is passed a finished arg that is true if all animations finished - * completely. - * @param {func} onFailure Not supported yet. - */ - startAnimations: function( - animations: Array, - onSuccess: (finished: boolean) => void, - onFailure: () => void - ) { - var numReturned = 0; - var numFinished = 0; - var numAnimations = animations.length; - var metaCallback = (finished) => { - if (finished) { - ++numFinished; - } - if (++numReturned === numAnimations) { - onSuccess && onSuccess(numFinished === numAnimations); - } - }; - animations.forEach((anim) => { - warning( - anim.ref != null || anim.nodeHandle != null && - !anim.ref !== !anim.nodeHandle, - 'Animations must be specified with either ref xor nodeHandle' - ); - if (anim.ref) { - this.startAnimation(anim.ref, anim.anim, metaCallback); - } else if (anim.nodeHandle) { - this.startAnimationWithNodeHandle(anim.nodeHandle, anim.anim, metaCallback); - } - }); - }, - - /** - * Stop any and all animations operating on the View with native node handle - * `nodeHandle`. - * - * @param {NodeHandle} component The instance to stop animations - * on. Do not pass a composite component. - */ - stopNodeHandleAnimations: function(nodeHandle: any) { - if (!this.state._currentAnimationsByNodeHandle[nodeHandle]) { - return; - } - var anims = this.state._currentAnimationsByNodeHandle[nodeHandle]; - for (var i = 0; i < anims.length; i++) { - var anim = anims[i]; - if (anim) { - // Note: Converting the string key to a number `nodeHandle`. - POPAnimation.removeAnimation(+nodeHandle, anim); - } - } - this.state._currentAnimationsByNodeHandle[nodeHandle] = undefined; - }, - - /** - * Stop any and all animations operating on the View with ref `refKey`. - * - * @param {key} refKey The key to reference the View to be animated. - */ - stopAnimations: function(refKey: string) { - invariant(this.refs[refKey], 'invalid ref'); - this.stopNodeHandleAnimations(React.findNodeHandle(this.refs[refKey])); - }, - - /** - * Stop any and all animations created by this component on itself and - * subviews. - */ - stopAllAnimations: function() { - for (var nodeHandle in this.state._currentAnimationsByNodeHandle) { - this.stopNodeHandleAnimations(nodeHandle); - } - }, - - /** - * Animates size and position of a view referenced by `refKey` to a specific - * frame. - * - * @param {key} refKey ref key for view to animate. - * @param {Object} frame The frame to animate the view to, specified as {left, - * top, width, height}. - * @param {const} type What type of interpolation to use, selected from - * `inperpolationTypes`. - * @param {Object} event Event encapsulating synthetic and native data that - * may have triggered this animation. Velocity is extracted from it if - * possible and applied to the animation. - * @param {func} doneCallback A callback fired when the animation is done, and - * is passed a `finished` param that indicates whether the animation - * completely finished, or was interrupted. - */ - animateToFrame: function( - refKey: string, - frame: {left: number; top: number; width: number; height: number;}, - type: number, - velocity: number, - doneCallback: (finished: boolean) => void - ) { - var animFrame = { // Animations use a centered coordinate system. - x: frame.left + frame.width / 2, - y: frame.top + frame.height / 2, - w: frame.width, - h: frame.height - }; - var posAnim = POPAnimation.createAnimation(type, { - property: POPAnimation.Properties.position, - toValue: [animFrame.x, animFrame.y], - velocity: velocity || [0, 0], - }); - var sizeAnim = POPAnimation.createAnimation(type, { - property: POPAnimation.Properties.size, - toValue: [animFrame.w, animFrame.h] - }); - this.startAnimation(refKey, posAnim, doneCallback); - this.startAnimation(refKey, sizeAnim); - }, - - // Cleanup any potentially leaked animations. - componentWillUnmount: function() { - this.stopAllAnimations(); - this._popAnimationEnqueuedAnimationTimeouts.forEach(animationTimeoutHandler => { - clearTimeout(animationTimeoutHandler); - }); - this._popAnimationEnqueuedAnimationTimeouts = []; - } -}; - -module.exports = POPAnimationMixin; - -} diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js deleted file mode 100644 index c8e289431..000000000 --- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js +++ /dev/null @@ -1,187 +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 POPAnimation - */ -'use strict'; - -var RCTPOPAnimationManager = require('NativeModules').POPAnimationManager; -if (!RCTPOPAnimationManager) { - // POP animation isn't available in the OSS fork - this is a temporary - // workaround to enable its availability to be determined at runtime. - // For Flow let's pretend like we always export POPAnimation - // so all our users don't need to do null checks - module.exports = null; -} else { - -var ReactPropTypes = require('ReactPropTypes'); -var createStrictShapeTypeChecker = require('createStrictShapeTypeChecker'); -var getObjectValues = require('getObjectValues'); -var invariant = require('invariant'); -var merge = require('merge'); - -var RCTTypes = RCTPOPAnimationManager.Types; -var RCTProperties = RCTPOPAnimationManager.Properties; - -var Properties = { - bounds: RCTProperties.bounds, - opacity: RCTProperties.opacity, - position: RCTProperties.position, - positionX: RCTProperties.positionX, - positionY: RCTProperties.positionY, - zPosition: RCTProperties.zPosition, - rotation: RCTProperties.rotation, - rotationX: RCTProperties.rotationX, - rotationY: RCTProperties.rotationY, - scaleX: RCTProperties.scaleX, - scaleXY: RCTProperties.scaleXY, - scaleY: RCTProperties.scaleY, - shadowColor: RCTProperties.shadowColor, - shadowOffset: RCTProperties.shadowOffset, - shadowOpacity: RCTProperties.shadowOpacity, - shadowRadius: RCTProperties.shadowRadius, - size: RCTProperties.size, - subscaleXY: RCTProperties.subscaleXY, - subtranslationX: RCTProperties.subtranslationX, - subtranslationXY: RCTProperties.subtranslationXY, - subtranslationY: RCTProperties.subtranslationY, - subtranslationZ: RCTProperties.subtranslationZ, - translationX: RCTProperties.translationX, - translationXY: RCTProperties.translationXY, - translationY: RCTProperties.translationY, - translationZ: RCTProperties.translationZ, -}; - -var Types = { - decay: RCTTypes.decay, - easeIn: RCTTypes.easeIn, - easeInEaseOut: RCTTypes.easeInEaseOut, - easeOut: RCTTypes.easeOut, - linear: RCTTypes.linear, - spring: RCTTypes.spring, -}; - -type Attrs = { - type?: $Enum; - property?: $Enum; - fromValue?: any; - toValue?: any; - duration?: any; - velocity?: any; - deceleration?: any; - springBounciness?: any; - dynamicsFriction?: any; - dynamicsMass?: any; - dynamicsTension?: any; -} - -var POPAnimation = { - Types: Types, - Properties: Properties, - - attributeChecker: createStrictShapeTypeChecker({ - type: ReactPropTypes.oneOf(getObjectValues(Types)), - property: ReactPropTypes.oneOf(getObjectValues(Properties)), - fromValue: ReactPropTypes.any, - toValue: ReactPropTypes.any, - duration: ReactPropTypes.any, - velocity: ReactPropTypes.any, - deceleration: ReactPropTypes.any, - springBounciness: ReactPropTypes.any, - dynamicsFriction: ReactPropTypes.any, - dynamicsMass: ReactPropTypes.any, - dynamicsTension: ReactPropTypes.any, - }), - - lastUsedTag: 0, - allocateTagForAnimation: function(): number { - return ++this.lastUsedTag; - }, - - createAnimation: function(typeName: number, attrs: Attrs): number { - var tag = this.allocateTagForAnimation(); - - if (__DEV__) { - POPAnimation.attributeChecker( - {attrs}, - 'attrs', - 'POPAnimation.createAnimation' - ); - POPAnimation.attributeChecker( - {attrs: {type: typeName}}, - 'attrs', - 'POPAnimation.createAnimation' - ); - } - - RCTPOPAnimationManager.createAnimationInternal(tag, typeName, attrs); - return tag; - }, - - createSpringAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.spring, attrs); - }, - - createDecayAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.decay, attrs); - }, - - createLinearAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.linear, attrs); - }, - - createEaseInAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.easeIn, attrs); - }, - - createEaseOutAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.easeOut, attrs); - }, - - createEaseInEaseOutAnimation: function(attrs: Attrs): number { - return this.createAnimation(this.Types.easeInEaseOut, attrs); - }, - - addAnimation: function(nodeHandle: any, anim: number, callback: Function) { - RCTPOPAnimationManager.addAnimation(nodeHandle, anim, callback); - }, - - removeAnimation: function(nodeHandle: any, anim: number) { - RCTPOPAnimationManager.removeAnimation(nodeHandle, anim); - }, -}; - -// Make sure that we correctly propagate RCTPOPAnimationManager constants -// to POPAnimation -if (__DEV__) { - var allProperties = merge( - RCTPOPAnimationManager.Properties, - RCTPOPAnimationManager.Properties - ); - for (var key in allProperties) { - invariant( - POPAnimation.Properties[key] === RCTPOPAnimationManager.Properties[key], - 'POPAnimation doesn\'t copy property ' + key + ' correctly' - ); - } - - var allTypes = merge( - RCTPOPAnimationManager.Types, - RCTPOPAnimationManager.Types - ); - for (var key in allTypes) { - invariant( - POPAnimation.Types[key] === RCTPOPAnimationManager.Types[key], - 'POPAnimation doesn\'t copy type ' + key + ' correctly' - ); - } -} - -module.exports = POPAnimation; - -} diff --git a/Libraries/Animation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js similarity index 100% rename from Libraries/Animation/LayoutAnimation.js rename to Libraries/LayoutAnimation/LayoutAnimation.js diff --git a/Libraries/ReactIOS/NativeMethodsMixin.js b/Libraries/ReactIOS/NativeMethodsMixin.js index e31b0e43a..7a448ce40 100644 --- a/Libraries/ReactIOS/NativeMethodsMixin.js +++ b/Libraries/ReactIOS/NativeMethodsMixin.js @@ -12,7 +12,6 @@ 'use strict'; var NativeModules = require('NativeModules'); -var RCTPOPAnimationManager = NativeModules.POPAnimationManager; var RCTUIManager = NativeModules.UIManager; var TextInputState = require('TextInputState'); @@ -38,32 +37,8 @@ type MeasureLayoutOnSuccessCallback = ( height: number ) => void -var animationIDInvariant = function( - funcName: string, - anim: number -) { - invariant( - anim, - funcName + ' must be called with a valid animation ID returned from' + - ' POPAnimation.createAnimation, received: "' + anim + '"' - ); -}; var NativeMethodsMixin = { - addAnimation: function(anim: number, callback?: (finished: bool) => void) { - animationIDInvariant('addAnimation', anim); - RCTPOPAnimationManager.addAnimation( - findNodeHandle(this), - anim, - mountSafeCallback(this, callback) - ); - }, - - removeAnimation: function(anim: number) { - animationIDInvariant('removeAnimation', anim); - RCTPOPAnimationManager.removeAnimation(findNodeHandle(this), anim); - }, - measure: function(callback: MeasureOnSuccessCallback) { RCTUIManager.measure( findNodeHandle(this), From 6debfce3744678e9331e0b497a4818966320604f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 20 Aug 2015 12:57:05 -0700 Subject: [PATCH 097/139] [react-native] Update graceful-fs and use it in _build_bundle.js --- package.json | 2 +- packager/react-packager/index.js | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 637edd926..efbb95bcf 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "chalk": "1.0.0", "connect": "2.8.3", "debug": "2.1.0", - "graceful-fs": "3.0.6", + "graceful-fs": "4.1.2", "image-size": "0.3.5", "immutable": "^3.7.4", "joi": "5.1.0", diff --git a/packager/react-packager/index.js b/packager/react-packager/index.js index 23a15c095..21bb1d67c 100644 --- a/packager/react-packager/index.js +++ b/packager/react-packager/index.js @@ -58,14 +58,7 @@ exports.getDependencies = function(options, main) { function useGracefulFs() { var fs = require('fs'); var gracefulFs = require('graceful-fs'); - - // A bit sneaky but it's not straightforward to update all the - // modules we depend on. - Object.keys(fs).forEach(function(method) { - if (typeof fs[method] === 'function' && gracefulFs[method]) { - fs[method] = gracefulFs[method]; - } - }); + gracefulFs.gracefulify(fs); } function createServer(options) { From 46c6cde947b2bbfef30939e52eefa65993bbcf06 Mon Sep 17 00:00:00 2001 From: Harrison Harnisch Date: Fri, 21 Aug 2015 00:57:43 -0700 Subject: [PATCH 098/139] UI CPU and memory utilization graphs in Chrome debugging mode Summary: Chrome debugging UI is currently only showing connection state and logs in the console, leaving room for plenty of interesting information. I've pushed the UI (using the same convention set by FPS -- UI/JS) CPU and memory utilization data over the debug Websocket and tapped into the existing stream of JS calls that get ran in V8. The number of JS calls in a time interval is counted for all sub calls in a batch https://github.com/hharnisc/react-native/blob/master/packager/debugger.html#L150 The last 5 batches of JS calls are displayed in a list format. screen shot 2015-07-19 at 7 34 00 pm Charts are created with [Chart.JS](https://github.com/nnnick/Chart.js) (MIT licensed). Closes https://github.com/facebook/react-native/pull/2050 Github Author: Harrison Harnisch --- Libraries/WebSocket/RCTWebSocketExecutor.m | 34 +- React/Base/RCTProfile.h | 22 +- React/Base/RCTProfile.m | 70 +- packager/debugger.html | 302 +- packager/packager.js | 7 + packager/static/Chart.min.js | 11 + packager/static/JSXTransformer-0.13.3.js | 15919 +++++++++++++++++++ packager/static/react-0.13.3.min.js | 16 + 8 files changed, 16366 insertions(+), 15 deletions(-) create mode 100644 packager/static/Chart.min.js create mode 100644 packager/static/JSXTransformer-0.13.3.js create mode 100644 packager/static/react-0.13.3.min.js diff --git a/Libraries/WebSocket/RCTWebSocketExecutor.m b/Libraries/WebSocket/RCTWebSocketExecutor.m index bd9600d3c..22a63e83b 100644 --- a/Libraries/WebSocket/RCTWebSocketExecutor.m +++ b/Libraries/WebSocket/RCTWebSocketExecutor.m @@ -18,6 +18,7 @@ #import "RCTSparseArray.h" #import "RCTUtils.h" #import "RCTSRWebSocket.h" +#import "RCTProfile.h" typedef void (^RCTWSMessageCallback)(NSError *error, NSDictionary *reply); @@ -109,11 +110,19 @@ RCT_EXPORT_MODULE() - (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message { NSError *error = nil; - NSDictionary *reply = RCTJSONParse(message, &error); - NSNumber *messageID = reply[@"replyID"]; - RCTWSMessageCallback callback = _callbacks[messageID]; - if (callback) { - callback(error, reply); + NSDictionary *parsedMessage = RCTJSONParse(message, &error); + + if ([parsedMessage objectForKey:@"method"]) { + NSString *methodName = parsedMessage[@"method"]; + if ([methodName isEqual:@"requestMetrics"]) { + [self sendUsageMetrics]; + } + } else if ([parsedMessage objectForKey:@"replyID"]) { + NSNumber *messageID = parsedMessage[@"replyID"]; + RCTWSMessageCallback callback = _callbacks[messageID]; + if (callback) { + callback(error, parsedMessage); + } } } @@ -181,6 +190,21 @@ RCT_EXPORT_MODULE() }]; } +- (void)sendUsageMetrics +{ + NSDictionary *memoryUsage = RCTProfileGetMemoryUsage(YES); + NSNumber *cpuUsage = RCTProfileGetCPUUsage(); + + NSDictionary *message = @{ + @"method": @"usageMetrics", + @"memoryUsage": memoryUsage, + @"deviceCPUUsage": cpuUsage + }; + + // TODO: handle errors + [self sendMessage:message waitForReply:^(NSError *socketError, NSDictionary *reply) {}]; +} + - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { dispatch_async(_jsQueue, ^{ diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index 794f4a910..96ede77db 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -73,7 +73,24 @@ RCT_EXTERN void RCTProfileEndEvent(uint64_t tag, NSDictionary *args); /** - * Collects the initial event information for the event and returns a reference ID + * Exposes memory usage metrics + */ + +NSDictionary *RCTProfileGetMemoryUsage(BOOL); + +/** + * Exposes device cpu usage metrics - Note this does not include JS Runtime CPU usage + */ + +NSNumber *RCTProfileGetCPUUsage(void); + +/** + * This pair of macros implicitly handle the event ID when beginning and ending + * an event, for both simplicity and performance reasons, this method is preferred + * + * NOTE: The EndEvent call has to be either, in the same scope of BeginEvent, + * or in a sub-scope, otherwise the ID stored by BeginEvent won't be accessible + * for EndEvent, in this case you may want to use the actual C functions. */ RCT_EXTERN int RCTProfileBeginAsyncEvent(uint64_t tag, NSString *name, @@ -139,6 +156,9 @@ RCT_EXTERN void RCTProfileUnhookModules(RCTBridge *); #define RCTProfileImmediateEvent(...) +#define RCTProfileGetMemoryUsage(...) +#define RCTProfileGetCPUUsage(...) + #define RCTProfileBlock(block, ...) block #define RCTProfileHookModules(...) diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 85d5a799a..4e21eae2f 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -72,7 +72,7 @@ static NSString *RCTProfileMemory(vm_size_t memory) return [NSString stringWithFormat:@"%.2lfmb", mem]; } -static NSDictionary *RCTProfileGetMemoryUsage(void) +NSDictionary *RCTProfileGetMemoryUsage(BOOL raw) { struct task_basic_info info; mach_msg_type_number_t size = sizeof(info); @@ -81,14 +81,76 @@ static NSDictionary *RCTProfileGetMemoryUsage(void) (task_info_t)&info, &size); if( kerr == KERN_SUCCESS ) { + vm_size_t vs = info.virtual_size; + vm_size_t rs = info.resident_size; return @{ @"suspend_count": @(info.suspend_count), - @"virtual_size": RCTProfileMemory(info.virtual_size), - @"resident_size": RCTProfileMemory(info.resident_size), + @"virtual_size": raw ? @(vs) : RCTProfileMemory(vs), + @"resident_size": raw ? @(rs) : RCTProfileMemory(rs), }; } else { return @{}; } + +} + +NSNumber *RCTProfileGetCPUUsage(void) +{ + kern_return_t kr; + task_info_data_t tinfo; + mach_msg_type_number_t task_info_count; + + task_info_count = TASK_INFO_MAX; + kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)tinfo, &task_info_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + thread_array_t thread_list; + mach_msg_type_number_t thread_count; + + thread_info_data_t thinfo; + mach_msg_type_number_t thread_info_count; + + thread_basic_info_t basic_info_th; + + // get threads in the task + kr = task_threads(mach_task_self(), &thread_list, &thread_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + long tot_sec = 0; + long tot_usec = 0; + float tot_cpu = 0; + unsigned j; + + for (j = 0; j < thread_count; j++) { + thread_info_count = THREAD_INFO_MAX; + kr = thread_info(thread_list[j], THREAD_BASIC_INFO, + (thread_info_t)thinfo, &thread_info_count); + if (kr != KERN_SUCCESS) { + return nil; + } + + basic_info_th = (thread_basic_info_t)thinfo; + + if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { + tot_sec = tot_sec + basic_info_th->user_time.seconds + basic_info_th->system_time.seconds; + tot_usec = tot_usec + basic_info_th->system_time.microseconds + basic_info_th->system_time.microseconds; + tot_cpu = tot_cpu + basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0; + } + + } // for each thread + + kr = vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); + + if( kr == KERN_SUCCESS ) { + return @(tot_cpu); + } else { + return nil; + + } } static NSDictionary *RCTProfileMergeArgs(NSDictionary *args0, NSDictionary *args1) @@ -369,7 +431,7 @@ void RCTProfileImmediateEvent( @"ts": RCTProfileTimestamp(CACurrentMediaTime()), @"scope": @(scope), @"ph": @"i", - @"args": RCTProfileGetMemoryUsage(), + @"args": RCTProfileGetMemoryUsage(NO), ); ); } diff --git a/packager/debugger.html b/packager/debugger.html index 24f8aea55..f152d9e17 100644 --- a/packager/debugger.html +++ b/packager/debugger.html @@ -14,8 +14,197 @@ React Native Debugger - + + + - - -