Merge pull request #1375 from vjeux/Updates_Fri_22_May

Updates fri 22 may
This commit is contained in:
Christopher Chedeau 2015-05-22 10:09:08 -07:00
commit 4673dca0b0
31 changed files with 932 additions and 87 deletions

View File

@ -13,6 +13,7 @@
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
1353F5461B0E64F9009B4FAC /* ClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1353F5451B0E64F9009B4FAC /* ClippingTests.m */; };
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
@ -129,6 +130,7 @@
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
1353F5451B0E64F9009B4FAC /* ClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClippingTests.m; sourceTree = "<group>"; };
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
@ -179,6 +181,7 @@
isa = PBXGroup;
children = (
004D28A21AAF61C70097A701 /* UIExplorerTests.m */,
1353F5451B0E64F9009B4FAC /* ClippingTests.m */,
004D28A01AAF61C70097A701 /* Supporting Files */,
);
path = UIExplorerTests;
@ -575,6 +578,7 @@
buildActionMask = 2147483647;
files = (
004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */,
1353F5461B0E64F9009B4FAC /* ClippingTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,127 @@
/**
* 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 <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIView.h>
#import <XCTest/XCTest.h>
extern CGRect RCTClipRect(CGSize contentSize, CGFloat contentScale,
CGSize targetSize, CGFloat targetScale,
UIViewContentMode resizeMode);
#define RCTAssertEqualPoints(a, b) { \
XCTAssertEqual(a.x, b.x); \
XCTAssertEqual(a.y, b.y); \
}
#define RCTAssertEqualSizes(a, b) { \
XCTAssertEqual(a.width, b.width); \
XCTAssertEqual(a.height, b.height); \
}
#define RCTAssertEqualRects(a, b) { \
RCTAssertEqualPoints(a.origin, b.origin); \
RCTAssertEqualSizes(a.size, b.size); \
}
@interface ClippingTests : XCTestCase
@end
@implementation ClippingTests
- (void)testLandscapeSourceLandscapeTarget
{
CGSize content = {1000, 100};
CGSize target = {100, 20};
{
CGRect expected = {CGPointZero, {100, 20}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {100, 10}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{-50, 0}, {200, 20}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
RCTAssertEqualRects(expected, result);
}
}
- (void)testPortraitSourceLandscapeTarget
{
CGSize content = {10, 100};
CGSize target = {100, 20};
{
CGRect expected = {CGPointZero, {10, 20}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {2, 20}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{0, -49}, {10, 100}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
RCTAssertEqualRects(expected, result);
}
}
- (void)testPortraitSourcePortraitTarget
{
CGSize content = {10, 100};
CGSize target = {20, 50};
{
CGRect expected = {CGPointZero, {10, 50}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {CGPointZero, {5, 50}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
RCTAssertEqualRects(expected, result);
}
{
CGRect expected = {{0, -37.5}, {10, 100}};
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
RCTAssertEqualRects(expected, result);
}
}
- (void)testScaling
{
CGSize content = {2, 2};
CGSize target = {3, 3};
CGRect expected = {CGPointZero, {3, 3}};
CGRect result = RCTClipRect(content, 2, target, 1, UIViewContentModeScaleToFill);
RCTAssertEqualRects(expected, result);
}
@end

View File

@ -1298,6 +1298,14 @@ var Navigator = React.createClass({
if (i !== this.state.presentedIndex) {
disabledSceneStyle = styles.disabledScene;
}
var originalRef = child.ref;
if (originalRef != null && typeof originalRef !== 'function') {
console.warn(
'String refs are not supported for navigator scenes. Use a callback ' +
'ref instead. Ignoring ref: ' + originalRef
);
originalRef = null;
}
return (
<View
key={this.state.idStack[i]}
@ -1307,7 +1315,12 @@ var Navigator = React.createClass({
}}
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
{React.cloneElement(child, {
ref: this._handleItemRef.bind(null, this.state.idStack[i], route),
ref: component => {
this._handleItemRef(this.state.idStack[i], route, component);
if (originalRef) {
originalRef(component);
}
}
})}
</View>
);

View File

@ -33,6 +33,8 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
block:(RCTImageDownloadBlock)block;
/**

View File

@ -10,6 +10,7 @@
#import "RCTImageDownloader.h"
#import "RCTCache.h"
#import "RCTLog.h"
#import "RCTUtils.h"
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);
@ -121,34 +122,134 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
}];
}
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size
scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
/**
* Returns the optimal context size for an image drawn using the clip rect
* returned by RCTClipRect.
*/
CGSize RCTTargetSizeForClipRect(CGRect);
CGSize RCTTargetSizeForClipRect(CGRect clipRect)
{
return (CGSize){
clipRect.size.width + clipRect.origin.x * 2,
clipRect.size.height + clipRect.origin.y * 2
};
}
/**
* 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.
*/
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
CGSize destSize, CGFloat destScale,
UIViewContentMode resizeMode)
{
// Precompensate for scale
CGFloat scale = sourceScale / destScale;
sourceSize.width *= scale;
sourceSize.height *= scale;
// Calculate aspect ratios if needed (don't bother is resizeMode == stretch)
CGFloat aspect = 0.0, targetAspect = 0.0;
if (resizeMode != UIViewContentModeScaleToFill) {
aspect = sourceSize.width / sourceSize.height;
targetAspect = destSize.width / destSize.height;
if (aspect == targetAspect) {
resizeMode = UIViewContentModeScaleToFill;
}
}
switch (resizeMode) {
case UIViewContentModeScaleToFill: // stretch
sourceSize.width = MIN(destSize.width, sourceSize.width);
sourceSize.height = MIN(destSize.height, sourceSize.height);
return (CGRect){CGPointZero, sourceSize};
case UIViewContentModeScaleAspectFit: // contain
if (targetAspect <= aspect) { // target is taller than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.height = sourceSize.width / aspect;
} else { // target is wider than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.width = sourceSize.height * aspect;
}
return (CGRect){CGPointZero, sourceSize};
case UIViewContentModeScaleAspectFill: // cover
if (targetAspect <= aspect) { // target is taller than content
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
sourceSize.width = sourceSize.height * aspect;
destSize.width = destSize.height * targetAspect;
return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
} else { // target is wider than content
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
sourceSize.height = sourceSize.width / aspect;
destSize.height = destSize.width / targetAspect;
return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
}
default:
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
return (CGRect){CGPointZero, destSize};
}
}
- (id)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
backgroundColor:(UIColor *)backgroundColor
block:(RCTImageDownloadBlock)block
{
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
if (!data || error) {
block(nil, error);
return;
}
if (CGSizeEqualToSize(size, CGSizeZero)) {
// Target size wasn't available yet, so abort image drawing
block(nil, nil);
return;
}
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
// Resize (TODO: should we take aspect ratio into account?)
CGSize imageSize = size;
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
imageSize = image.size;
} else {
imageSize = (CGSize){
MIN(size.width, image.size.width),
MIN(size.height, image.size.height)
};
}
// Get scale and size
CGFloat destScale = scale ?: RCTScreenScale();
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
// Rescale image if required size is smaller
CGFloat imageScale = scale;
if (imageScale == 0 || imageScale < image.scale) {
imageScale = image.scale;
// Opacity optimizations
UIColor *blendColor = nil;
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
if (!opaque && backgroundColor) {
CGFloat alpha;
[backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha];
if (alpha > 0.999) { // no benefit to blending if background is translucent
opaque = YES;
blendColor = backgroundColor;
}
}
// Decompress image at required size
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
[image drawInRect:(CGRect){{0, 0}, imageSize}];
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
if (blendColor) {
[blendColor setFill];
UIRectFill((CGRect){CGPointZero, destSize});
}
[image drawInRect:imageRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

View File

@ -26,8 +26,6 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.rctImageLoader", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
});
return queue;

View File

@ -13,6 +13,7 @@
#import "RCTGIFImage.h"
#import "RCTImageDownloader.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@implementation RCTNetworkImageView
{
@ -26,8 +27,7 @@
- (instancetype)initWithFrame:(CGRect)frame imageDownloader:(RCTImageDownloader *)imageDownloader
{
self = [super initWithFrame:frame];
if (self) {
if ((self = [super initWithFrame:frame])) {
_deferSentinel = 0;
_imageDownloader = imageDownloader;
self.userInteractionEnabled = NO;
@ -37,20 +37,44 @@
- (NSURL *)imageURL
{
// We clear our backing layer's imageURL when we are not in a window for a while,
// We clear our imageURL when we are not in a window for a while,
// to make sure we don't consume network resources while offscreen.
// However we don't want to expose this hackery externally.
return _deferred ? _deferredImageURL : _imageURL;
}
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
super.backgroundColor = backgroundColor;
[self _updateImage];
}
- (void)reactSetFrame:(CGRect)frame
{
[super reactSetFrame:frame];
[self _updateImage];
}
- (void)_updateImage
{
[self setImageURL:_imageURL resetToDefaultImageWhileLoading:NO];
}
- (void)setImageURL:(NSURL *)imageURL resetToDefaultImageWhileLoading:(BOOL)reset
{
if (![_imageURL isEqual:imageURL] && _downloadToken) {
[_imageDownloader cancelDownload:_downloadToken];
_downloadToken = nil;
}
_imageURL = imageURL;
if (_deferred) {
_deferredImageURL = imageURL;
} else {
if (_downloadToken) {
[_imageDownloader cancelDownload:_downloadToken];
_downloadToken = nil;
if (!imageURL) {
self.layer.contents = nil;
return;
}
if (reset) {
self.layer.contentsScale = _defaultImage.scale;
@ -62,25 +86,35 @@
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
if (data) {
dispatch_async(dispatch_get_main_queue(), ^{
if (imageURL != self.imageURL) {
// Image has changed
return;
}
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
self.layer.contentsScale = 1.0;
self.layer.minificationFilter = kCAFilterLinear;
self.layer.magnificationFilter = kCAFilterLinear;
[self.layer addAnimation:animation forKey:@"contents"];
});
} else if (error) {
RCTLogWarn(@"Unable to download image data. Error: %@", error);
}
// TODO: handle errors
}];
} else {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() block:^(UIImage *image, NSError *error) {
_downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() resizeMode:self.contentMode backgroundColor:self.backgroundColor block:^(UIImage *image, NSError *error) {
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (imageURL != self.imageURL) {
// Image has changed
return;
}
[self.layer removeAnimationForKey:@"contents"];
self.layer.contentsScale = image.scale;
self.layer.contents = (__bridge id)image.CGImage;
});
} else if (error) {
RCTLogWarn(@"Unable to download image. Error: %@", error);
}
// TODO: handle errors
}];
}
}

View File

@ -19,6 +19,13 @@ var invariant = require('invariant');
var keyMirror = require('keyMirror');
var setImmediate = require('setImmediate');
type Handle = number;
/**
* Maximum time a handle can be open before warning in DEV.
*/
var DEV_TIMEOUT = 2000;
var _emitter = new EventEmitter();
var _interactionSet = new Set();
var _addInteractionSet = new Set();
@ -83,17 +90,25 @@ var InteractionManager = {
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): number {
createInteractionHandle(): Handle {
scheduleUpdate();
var handle = ++_inc;
_addInteractionSet.add(handle);
if (__DEV__) {
// Capture the stack trace of what created the handle.
var error = new Error(
'InteractionManager: interaction handle not cleared within ' +
DEV_TIMEOUT + ' ms.'
);
setDevTimeoutHandle(handle, error, DEV_TIMEOUT);
}
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: number) {
clearInteractionHandle(handle: Handle) {
invariant(
!!handle,
'Must provide a handle to clear.'
@ -151,4 +166,19 @@ function processUpdate() {
_deleteInteractionSet.clear();
}
/**
* Wait until `timeout` has passed and warn if the handle has not been cleared.
*/
function setDevTimeoutHandle(
handle: Handle,
error: Error,
timeout: number
): void {
setTimeout(() => {
if (_interactionSet.has(handle)) {
console.warn(error.message + '\n' + error.stack);
}
}, timeout);
}
module.exports = InteractionManager;

View File

@ -43,7 +43,6 @@ var JSTimers = {
var newID = JSTimersExecution.GUID++;
var freeIndex = JSTimers._getFreeIndex();
JSTimersExecution.timerIDs[freeIndex] = newID;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.callbacks[freeIndex] = function() {
return func.apply(undefined, args);
};
@ -60,12 +59,15 @@ var JSTimers = {
var newID = JSTimersExecution.GUID++;
var freeIndex = JSTimers._getFreeIndex();
JSTimersExecution.timerIDs[freeIndex] = newID;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.callbacks[freeIndex] = function() {
return func.apply(undefined, args);
var startTime = Date.now();
var ret = func.apply(undefined, args);
var endTime = Date.now();
RCTTiming.createTimer(newID, Math.max(0, duration - (endTime - startTime)), endTime, false);
return ret;
};
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.setInterval;
RCTTiming.createTimer(newID, duration, Date.now(), /** recurring */ true);
RCTTiming.createTimer(newID, duration, Date.now(), /** recurring */ false);
return newID;
},
@ -77,7 +79,6 @@ var JSTimers = {
var newID = JSTimersExecution.GUID++;
var freeIndex = JSTimers._getFreeIndex();
JSTimersExecution.timerIDs[freeIndex] = newID;
JSTimersExecution.callbacks[freeIndex] = func;
JSTimersExecution.callbacks[freeIndex] = function() {
return func.apply(undefined, args);
};

View File

@ -22,6 +22,7 @@
#import "RCTJavaScriptLoader.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
#import "RCTProfile.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
@ -930,6 +931,11 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
_queuesByID = [[RCTSparseArray alloc] init];
_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];
if (RCT_DEV) {
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
/**
* Initialize executor to allow enqueueing calls
*/
@ -1560,6 +1566,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
}
RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil);
[self.perfStats.jsGraph tick:displayLink.timestamp];
}
- (void)_mainThreadUpdate:(CADisplayLink *)displayLink
@ -1567,6 +1575,8 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
RCTAssertMainThread();
RCTProfileImmediateEvent(@"VSYNC", displayLink.timestamp, @"g");
[self.perfStats.uiGraph tick:displayLink.timestamp];
}
- (void)startProfiling
@ -1578,10 +1588,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
return;
}
[_mainDisplayLink invalidate];
_mainDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_mainThreadUpdate:)];
[_mainDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
RCTProfileInit();
}
@ -1589,8 +1595,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
{
RCTAssertMainThread();
[_mainDisplayLink invalidate];
NSString *log = RCTProfileEnd();
NSURL *bundleURL = _parentBridge.bundleURL;
NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port];

View File

@ -35,6 +35,11 @@
*/
@property (nonatomic, assign) BOOL liveReloadEnabled;
/**
* Shows the FPS monitor for the JS and Main threads
*/
@property (nonatomic, assign) BOOL showFPS;
/**
* Manually show the dev menu (can be called from JS).
*/

View File

@ -13,6 +13,7 @@
#import "RCTDefines.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTPerfStats.h"
#import "RCTProfile.h"
#import "RCTRootView.h"
#import "RCTSourceCode.h"
@ -145,6 +146,7 @@ RCT_EXPORT_MODULE()
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
self.showFPS = [_settings[@"showFPS"] ?: @NO boolValue];
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
}
@ -230,13 +232,14 @@ RCT_EXPORT_METHOD(show)
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, nil];
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
if (_liveReloadURL) {
@ -293,10 +296,14 @@ RCT_EXPORT_METHOD(reload)
break;
}
case 3: {
self.liveReloadEnabled = !_liveReloadEnabled;
self.showFPS = !_showFPS;
break;
}
case 4: {
self.liveReloadEnabled = !_liveReloadEnabled;
break;
}
case 5: {
self.profilingEnabled = !_profilingEnabled;
break;
}
@ -368,6 +375,21 @@ RCT_EXPORT_METHOD(reload)
}
}
- (void)setShowFPS:(BOOL)showFPS
{
if (_showFPS != showFPS) {
_showFPS = showFPS;
if (showFPS) {
[_bridge.perfStats show];
} else {
[_bridge.perfStats hide];
}
[self updateSetting:@"showFPS" value:@(showFPS)];
}
}
- (void)checkForUpdates
{
if (!_jsLoaded || !_liveReloadEnabled || !_liveReloadURL) {

23
React/Base/RCTFPSGraph.h Normal file
View File

@ -0,0 +1,23 @@
/**
* 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 <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, RCTFPSGraphPosition) {
RCTFPSGraphPositionLeft = 1,
RCTFPSGraphPositionRight = 2
};
@interface RCTFPSGraph : UIView
- (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color NS_DESIGNATED_INITIALIZER;
- (void)tick:(NSTimeInterval)timestamp;
@end

132
React/Base/RCTFPSGraph.m Normal file
View File

@ -0,0 +1,132 @@
/**
* 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 "RCTFPSGraph.h"
#import "RCTDefines.h"
#if RCT_DEV
@implementation RCTFPSGraph
{
CAShapeLayer *_graph;
NSString *_name;
NSTimeInterval _prevTime;
RCTFPSGraphPosition _position;
UILabel *_label;
float *_frames;
int _frameCount;
int _maxFPS;
int _minFPS;
int _length;
int _margin;
int _height;
}
- (instancetype)initWithFrame:(CGRect)frame graphPosition:(RCTFPSGraphPosition)position name:(NSString *)name color:(UIColor *)color
{
if (self = [super initWithFrame:frame]) {
_margin = 2;
_prevTime = -1;
_maxFPS = 0;
_minFPS = 60;
_length = (frame.size.width - 2 * _margin) / 2;
_height = frame.size.height - 2 * _margin;
_frames = malloc(sizeof(float) * _length);
memset(_frames, 0, sizeof(float) * _length);
_name = name;
_position = position;
_graph = [self createGraph:color];
_label = [self createLabel:color];
[self addSubview:_label];
[self.layer addSublayer:_graph];
}
return self;
}
- (void)dealloc
{
free(_frames);
}
- (void)layoutSubviews
{
[super layoutSubviews];
}
- (CAShapeLayer *)createGraph:(UIColor *)color
{
CGFloat left = _position & RCTFPSGraphPositionLeft ? 0 : _length;
CAShapeLayer *graph = [[CAShapeLayer alloc] init];
graph.frame = CGRectMake(left, 0, 2 * _margin + _length, self.frame.size.height);
graph.backgroundColor = [[color colorWithAlphaComponent:.2] CGColor];
graph.fillColor = [color CGColor];
return graph;
}
- (UILabel *)createLabel:(UIColor *)color
{
CGFloat left = _position & RCTFPSGraphPositionLeft ? 2 * _margin + _length : 0;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(left, 0, _length, self.frame.size.height)];
label.textColor = color;
label.font = [UIFont systemFontOfSize:9];
label.minimumScaleFactor = .5;
label.adjustsFontSizeToFitWidth = YES;
label.numberOfLines = 3;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentCenter;
return label;
}
- (void)tick:(NSTimeInterval)timestamp
{
_frameCount++;
if (_prevTime == -1) {
_prevTime = timestamp;
} else if (timestamp - _prevTime > 1) {
float fps = round(_frameCount / (timestamp - _prevTime));
_minFPS = MIN(_minFPS, fps);
_maxFPS = MAX(_maxFPS, fps);
_label.text = [NSString stringWithFormat:@"%@\n%d FPS\n(%d - %d)", _name, (int)fps, _minFPS, _maxFPS];
float scale = 60.0 / _height;
for (int i = 0; i < _length - 1; i++) {
_frames[i] = _frames[i + 1];
}
_frames[_length - 1] = fps / scale;
CGMutablePathRef path = CGPathCreateMutable();
if (_position & RCTFPSGraphPositionLeft) {
CGPathMoveToPoint(path, NULL, _margin, _margin + _height);
for (int i = 0; i < _length; i++) {
CGPathAddLineToPoint(path, NULL, _margin + i, _margin + _height - _frames[i]);
}
CGPathAddLineToPoint(path, NULL, _margin + _length - 1, _margin + _height);
} else {
CGPathMoveToPoint(path, NULL, _margin + _length - 1, _margin + _height);
for (int i = 0; i < _length; i++) {
CGPathAddLineToPoint(path, NULL, _margin + _length - i - 1, _margin + _height - _frames[i]);
}
CGPathAddLineToPoint(path, NULL, _margin, _margin + _height);
}
_graph.path = path;
CGPathRelease(path);
_prevTime = timestamp;
_frameCount = 0;
}
}
@end
#endif

27
React/Base/RCTPerfStats.h Normal file
View File

@ -0,0 +1,27 @@
/**
* 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 "RCTBridge.h"
#import "RCTFPSGraph.h"
@interface RCTPerfStats : NSObject
@property (nonatomic, strong) RCTFPSGraph *jsGraph;
@property (nonatomic, strong) RCTFPSGraph *uiGraph;
- (void)show;
- (void)hide;
@end
@interface RCTBridge (RCTPerfStats)
@property (nonatomic, strong, readonly) RCTPerfStats *perfStats;
@end

133
React/Base/RCTPerfStats.m Normal file
View File

@ -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.
*/
#import "RCTPerfStats.h"
#import "RCTDefines.h"
#if RCT_DEV
@interface RCTPerfStats() <RCTBridgeModule>
@end
@implementation RCTPerfStats
{
UIView *_container;
}
RCT_EXPORT_MODULE()
- (void)dealloc
{
[self hide];
}
- (UIView *)container
{
if (!_container) {
_container = [[UIView alloc] init];
_container.backgroundColor = [UIColor colorWithRed:0 green:0 blue:34/255.0 alpha:1];
_container.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
}
return _container;
}
- (RCTFPSGraph *)jsGraph
{
if (!_jsGraph) {
UIColor *jsColor = [UIColor colorWithRed:0 green:1 blue:0 alpha:1];
_jsGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34)
graphPosition:RCTFPSGraphPositionRight
name:@"[ JS ]"
color:jsColor];
_jsGraph.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
}
return _jsGraph;
}
- (RCTFPSGraph *)uiGraph
{
if (!_uiGraph) {
UIColor *uiColor = [UIColor colorWithRed:0 green:1 blue:1 alpha:1];
_uiGraph = [[RCTFPSGraph alloc] initWithFrame:CGRectMake(2, 2, 124, 34)
graphPosition:RCTFPSGraphPositionLeft
name:@"[ UI ]"
color:uiColor];
}
return _uiGraph;
}
- (void)show
{
UIView *targetView = [[[[[UIApplication sharedApplication] delegate] window] rootViewController] view];
targetView.frame = (CGRect){
targetView.frame.origin,
{
targetView.frame.size.width,
targetView.frame.size.height - 38,
}
};
self.container.frame = (CGRect){{0, targetView.frame.size.height}, {targetView.frame.size.width, 38}};
self.jsGraph.frame = (CGRect){
{
targetView.frame.size.width - self.uiGraph.frame.size.width - self.uiGraph.frame.origin.x,
self.uiGraph.frame.origin.x,
},
self.uiGraph.frame.size,
};
[self.container addSubview:self.jsGraph];
[self.container addSubview:self.uiGraph];
[targetView addSubview:self.container];
}
- (void)hide
{
UIView *targetView = _container.superview;
targetView.frame = (CGRect){
targetView.frame.origin,
{
targetView.frame.size.width,
targetView.frame.size.height + _container.frame.size.height
}
};
[_container removeFromSuperview];
}
- (dispatch_queue_t)methodQueue
{
return dispatch_get_main_queue();
}
@end
#else
@implementation RCTPerfStats
- (void)show {}
- (void)hide {}
@end
#endif
@implementation RCTBridge (RCTPerfStats)
- (RCTPerfStats *)perfStats
{
return self.modules[RCTBridgeModuleNameForClass([RCTPerfStats class])];
}
@end

View File

@ -52,3 +52,6 @@ RCT_EXTERN NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, N
// Returns YES if React is running in a test environment
RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
// Return YES if image has an alpha component
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);

View File

@ -261,3 +261,15 @@ BOOL RCTRunningInTestEnvironment(void)
});
return _isTestEnvironment;
}
BOOL RCTImageHasAlpha(CGImageRef image)
{
switch (CGImageGetAlphaInfo(image)) {
case kCGImageAlphaNone:
case kCGImageAlphaNoneSkipLast:
case kCGImageAlphaNoneSkipFirst:
return NO;
default:
return YES;
}
}

View File

@ -44,9 +44,11 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */ = {isa = PBXBuildFile; fileRef = 1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */; };
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */; };
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */ = {isa = PBXBuildFile; fileRef = 146459251B06C49500B389AA /* RCTFPSGraph.m */; };
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
@ -163,6 +165,8 @@
13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = "<group>"; };
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPerfStats.h; sourceTree = "<group>"; };
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPerfStats.m; sourceTree = "<group>"; };
14200DA81AC179B3008EE6BA /* RCTJavaScriptLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptLoader.h; sourceTree = "<group>"; };
14200DA91AC179B3008EE6BA /* RCTJavaScriptLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJavaScriptLoader.m; sourceTree = "<group>"; };
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTFrameUpdate.h; sourceTree = "<group>"; };
@ -170,6 +174,8 @@
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
146459241B06C49500B389AA /* RCTFPSGraph.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFPSGraph.h; sourceTree = "<group>"; };
146459251B06C49500B389AA /* RCTFPSGraph.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFPSGraph.m; sourceTree = "<group>"; };
14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; };
14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; };
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
@ -415,6 +421,10 @@
1436DD071ADE7AA000A5ED7D /* RCTFrameUpdate.h */,
14F4D3891AE1B7E40049C042 /* RCTProfile.h */,
14F4D38A1AE1B7E40049C042 /* RCTProfile.m */,
146459241B06C49500B389AA /* RCTFPSGraph.h */,
146459251B06C49500B389AA /* RCTFPSGraph.m */,
1403F2B11B0AE60700C2A9A4 /* RCTPerfStats.h */,
1403F2B21B0AE60700C2A9A4 /* RCTPerfStats.m */,
);
path = Base;
sourceTree = "<group>";
@ -514,6 +524,7 @@
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
146459261B06C49500B389AA /* RCTFPSGraph.m in Sources */,
14200DAA1AC179B3008EE6BA /* RCTJavaScriptLoader.m in Sources */,
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
@ -549,6 +560,7 @@
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
1403F2B31B0AE60700C2A9A4 /* RCTPerfStats.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,

View File

@ -127,7 +127,6 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
_borderBottomRightRadius = -1;
_backgroundColor = [super backgroundColor];
[super setBackgroundColor:[UIColor clearColor]];
}
return self;
@ -443,6 +442,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
{
static const CGFloat threshold = 0.001;
const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width);
const CGFloat radius = MAX(0, _borderRadius);
const CGFloat topLeftRadius = MIN(_borderTopLeftRadius >= 0 ? _borderTopLeftRadius : radius, maxRadius);
@ -456,6 +457,17 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
if (topLeftRadius < threshold &&
topRightRadius < threshold &&
bottomLeftRadius < threshold &&
bottomRightRadius < threshold &&
topWidth < threshold &&
rightWidth < threshold &&
bottomWidth < threshold &&
leftWidth < threshold) {
return nil;
}
const CGFloat innerTopLeftRadiusX = MAX(0, topLeftRadius - leftWidth);
const CGFloat innerTopLeftRadiusY = MAX(0, topLeftRadius - topWidth);
@ -657,22 +669,21 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (void)displayLayer:(CALayer *)layer
{
CGRect contentsCenter;
CGRect contentsCenter = (CGRect){CGPointZero, {1, 1}};
UIImage *image = [self generateBorderImage:&contentsCenter];
if (RCTRunningInTestEnvironment()) {
if (image && RCTRunningInTestEnvironment()) {
const CGSize size = self.bounds.size;
UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
[image drawInRect:(CGRect){CGPointZero, size}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
contentsCenter = CGRectMake(0, 0, 1, 1);
}
layer.backgroundColor = [image ? [UIColor clearColor] : _backgroundColor CGColor];
layer.contents = (id)image.CGImage;
layer.contentsCenter = contentsCenter;
layer.contentsScale = image.scale;
layer.contentsScale = image.scale ?: 1.0;
layer.magnificationFilter = kCAFilterNearest;
}

View File

@ -63,7 +63,7 @@
"stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638",
"uglify-js": "~2.4.16",
"underscore": "1.7.0",
"worker-farm": "^1.3.0",
"worker-farm": "^1.3.1",
"ws": "0.4.31",
"yargs": "1.3.2"
},

View File

@ -24,12 +24,21 @@ function parseCommandLine(config) {
// optimist default API requires you to write the command name three time
// This is a small wrapper to accept an object instead
for (var i = 0; i < config.length; ++i) {
if (config[i].type === 'string') {
optimist.string(config[i].command);
} else {
optimist.boolean(config[i].command);
}
optimist
.boolean(config[i].command)
.default(config[i].command, config[i].default)
.describe(config[i].command, config[i].description);
if (config[i].required) {
optimist.demand(config[i].command);
}
var argv = optimist.argv;
}
var argv = optimist.parse(process.argv);
// optimist doesn't have support for --dev=false, instead it returns 'false'
for (var i = 0; i < config.length; ++i) {
@ -43,6 +52,15 @@ function parseCommandLine(config) {
if (argv[command] === 'false') {
argv[command] = false;
}
if (config[i].type === 'string') {
// According to https://github.com/substack/node-optimist#numbers,
// every argument that looks like a number should be converted to one.
var strValue = argv[command];
var numValue = strValue ? Number(strValue) : undefined;
if (typeof numValue === 'number' && !isNaN(numValue)) {
argv[command] = numValue;
}
}
}
// Show --help

View File

@ -47,6 +47,32 @@ describe('AssetServer', function() {
});
});
pit('should work for the simple case with jpg', function() {
var server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpg'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b.png': 'png image',
'b.jpg': 'jpeg image',
}
}
});
return Promise.all([
server.get('imgs/b.jpg'),
server.get('imgs/b.png'),
]).then(function(data) {
expect(data).toEqual([
'jpeg image',
'png image',
]);
});
});
pit('should pick the bigger one', function() {
var server = new AssetServer({
projectRoots: ['/root'],
@ -136,5 +162,45 @@ describe('AssetServer', function() {
});
});
});
pit('should get assetData for non-png images', function() {
var hash = {
update: jest.genMockFn(),
digest: jest.genMockFn(),
};
hash.digest.mockImpl(function() {
return 'wow such hash';
});
crypto.createHash.mockImpl(function() {
return hash;
});
var server = new AssetServer({
projectRoots: ['/root'],
assetExts: ['png', 'jpeg'],
});
fs.__setMockFilesystem({
'root': {
imgs: {
'b@1x.jpg': 'b1 image',
'b@2x.jpg': 'b2 image',
'b@4x.jpg': 'b4 image',
'b@4.5x.jpg': 'b4.5 image',
}
}
});
return server.getAssetData('imgs/b.jpg').then(function(data) {
expect(hash.update.mock.calls.length).toBe(4);
expect(data).toEqual({
type: 'jpg',
name: 'b',
scales: [1, 2, 4, 4.5],
hash: 'wow such hash',
});
});
});
});
});

View File

@ -28,7 +28,7 @@ var validateOpts = declareOpts({
},
assetExts: {
type: 'array',
default: ['png'],
required: true,
},
});
@ -90,7 +90,7 @@ AssetServer.prototype.getAssetData = function(assetPath) {
var nameData = getAssetDataFromName(assetPath);
var data = {
name: nameData.name,
type: 'png',
type: nameData.type,
};
return this._getAssetRecord(assetPath).then(function(record) {

View File

@ -55,7 +55,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -91,7 +92,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -121,7 +123,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -161,6 +164,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
assetRoots_DEPRECATED: ['/root/imgs'],
});
return dgraph.load().then(function() {
@ -199,6 +203,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -246,6 +251,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -308,6 +314,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
assetRoots_DEPRECATED: ['/root/imgs'],
});
return dgraph.load().then(function() {
@ -358,7 +365,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -391,7 +399,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -421,7 +430,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -455,7 +465,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -489,7 +500,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -519,7 +531,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -552,7 +565,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -595,7 +609,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js'))
@ -641,7 +656,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -674,7 +690,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -712,7 +729,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -755,7 +773,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -798,7 +817,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -847,7 +867,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -888,7 +909,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -931,7 +953,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -974,7 +997,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -1027,7 +1051,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -1092,7 +1117,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
@ -1158,7 +1184,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
filesystem.root['index.js'] =
@ -1209,7 +1236,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
filesystem.root['index.js'] =
@ -1260,7 +1288,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
delete filesystem.root.foo;
@ -1310,7 +1339,8 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
filesystem.root['bar.js'] = [
@ -1367,7 +1397,7 @@ describe('DependencyGraph', function() {
roots: [root],
assetRoots_DEPRECATED: [root],
assetExts: ['png'],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
});
return dgraph.load().then(function() {
@ -1419,7 +1449,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
assetExts: ['png'],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
});
return dgraph.load().then(function() {
@ -1482,6 +1512,7 @@ describe('DependencyGraph', function() {
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
ignoreFilePath: function(filePath) {
if (filePath === '/root/bar.js') {
return true;
@ -1550,7 +1581,8 @@ describe('DependencyGraph', function() {
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
return dgraph.load().then(function() {
triggerFileChange('change', 'aPackage', '/root', {

View File

@ -44,7 +44,7 @@ var validateOpts = declareOpts({
},
assetExts: {
type: 'array',
default: ['png'],
required: true,
}
});

View File

@ -54,6 +54,10 @@ var validateOpts = declareOpts({
type: 'object',
required: true,
},
assetExts: {
type: 'array',
required: true,
}
});
function HasteDependencyResolver(options) {
@ -62,6 +66,7 @@ function HasteDependencyResolver(options) {
this._depGraph = new DependencyGraph({
roots: opts.projectRoots,
assetRoots_DEPRECATED: opts.assetRoots,
assetExts: opts.assetExts,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath));

View File

@ -249,6 +249,15 @@ Package.prototype._getMappings = function() {
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 [
'<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',

View File

@ -204,8 +204,28 @@ describe('Package', function() {
expect(p.getAssets()).toEqual([asset1, asset2]);
});
});
describe('getJSModulePaths()', function() {
it('should return module paths', function() {
var p = new Package('test_url');
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
p.addModule(new ModuleTransport({
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
}));
expect(p.getJSModulePaths()).toEqual(['foo path']);
});
});
});
function genSourceMap(modules) {
var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
var packageLineNo = 0;

View File

@ -86,6 +86,7 @@ function Packager(options) {
moduleFormat: opts.moduleFormat,
assetRoots: opts.assetRoots,
fileWatcher: opts.fileWatcher,
assetExts: opts.assetExts,
});
this._transformer = new Transformer({

View File

@ -60,7 +60,7 @@ var validateOpts = declareOpts({
},
assetExts: {
type: 'array',
default: ['png'],
default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
},
});