- Only update Redbox when offscreen | Nick Lockwood
- [react-packager] Cleanup option passing and validation | Amjad Masad
- Unified bridge implementation between OSS and private React forks | Nick Lockwood
- [react-packager][cleanup options 1/2] add npm installed joi validation library | Amjad Masad
This commit is contained in:
Christopher Chedeau 2015-02-25 12:40:49 -08:00
Родитель 1f8740a9f8
Коммит c892d2c8d1
25 изменённых файлов: 650 добавлений и 163 удалений

Просмотреть файл

@ -4,6 +4,7 @@
#import "RCTInvalidating.h" #import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h" #import "RCTJavaScriptExecutor.h"
@class RCTBridge;
@class RCTEventDispatcher; @class RCTEventDispatcher;
@class RCTRootView; @class RCTRootView;
@ -26,6 +27,12 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
return @{@"apiError": msg ?: @""}; return @{@"apiError": msg ?: @""};
} }
/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge);
/** /**
* Async batched bridge used to communicate with the JavaScript application. * Async batched bridge used to communicate with the JavaScript application.
*/ */
@ -34,11 +41,13 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
/** /**
* The designated initializer. This creates a new bridge on top of the specified * The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication * executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. You can optionally pass in * with the JavaScript code running in the executor. Modules will be automatically
* a list of module instances to be used instead of the auto-instantiated versions. * instantiated using the default contructor, but you can optionally pass in a
* module provider block to manually instantiate modules that require additional
* init parameters or configuration.
*/ */
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleInstances:(NSArray *)moduleInstances NS_DESIGNATED_INITIALIZER; moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER;
/** /**
* This method is used to call functions in the JavaScript application context. * This method is used to call functions in the JavaScript application context.
@ -60,6 +69,11 @@ static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
*/ */
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher; @property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
/**
* A dictionary of all registered RCTBridgeModule instances, keyed by moduleName.
*/
@property (nonatomic, copy, readonly) NSDictionary *modules;
/** /**
* The shadow queue is used to execute callbacks from the JavaScript code. All * The shadow queue is used to execute callbacks from the JavaScript code. All
* native hooks (e.g. exported module methods) will be executed on the shadow * native hooks (e.g. exported module methods) will be executed on the shadow

Просмотреть файл

@ -9,7 +9,6 @@
#import <objc/runtime.h> #import <objc/runtime.h>
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTInvalidating.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
@ -101,6 +100,40 @@ static id<RCTBridgeModule> RCTCreateModuleInstance(Class cls, RCTBridge *bridge)
} }
} }
/**
* This function scans all classes available at runtime and returns an array
* of all JSMethods registered.
*/
static NSArray *RCTJSMethods(void)
{
static NSArray *JSMethods;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *uniqueMethods = [NSMutableSet set];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[uniqueMethods addObjectsFromArray:[cls JSMethods]];
}
}
free(classes);
JSMethods = [uniqueMethods allObjects];
});
return JSMethods;
}
/** /**
* This function scans all classes available at runtime and returns an array * This function scans all classes available at runtime and returns an array
* of all classes that implement the RTCBridgeModule protocol. * of all classes that implement the RTCBridgeModule protocol.
@ -264,7 +297,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID]; NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count]; NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) { [methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
methodsByName[method.JSMethodName] = @{ methodsByName[method.JSMethodName] = @{
@"methodID": @(methodID), @"methodID": @(methodID),
@"type": @"remote", @"type": @"remote",
@ -335,38 +368,16 @@ static NSDictionary *RCTLocalModulesConfig()
static NSMutableDictionary *localModules; static NSMutableDictionary *localModules;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init]; RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init]; RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
NSMutableArray *JSMethods = [[NSMutableArray alloc] init];
// Add globally used methods
[JSMethods addObjectsFromArray:@[
@"AppRegistry.runApplication",
@"RCTDeviceEventEmitter.emit",
@"RCTEventEmitter.receiveEvent",
@"RCTEventEmitter.receiveTouches",
]];
// NOTE: these methods are currently unused in the OSS project
// @"Dimensions.set",
// @"RCTNativeAppEventEmitter.emit",
// @"ReactIOS.unmountComponentAtNodeAndRemoveContainer",
// Register individual methods from modules
for (Class cls in RCTBridgeModuleClassesByModuleID()) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[JSMethods addObjectsFromArray:[cls JSMethods]];
}
}
localModules = [[NSMutableDictionary alloc] init]; localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in JSMethods) { for (NSString *moduleDotMethod in RCTJSMethods()) {
NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."]; NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod); RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
// Add module if it doesn't already exist // Add module if it doesn't already exist
NSString *moduleName = parts[0]; NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName]; NSDictionary *module = localModules[moduleName];
@ -377,7 +388,7 @@ static NSDictionary *RCTLocalModulesConfig()
}; };
localModules[moduleName] = module; localModules[moduleName] = module;
} }
// Add method if it doesn't already exist // Add method if it doesn't already exist
NSString *methodName = parts[1]; NSString *methodName = parts[1];
NSMutableDictionary *methods = module[@"methods"]; NSMutableDictionary *methods = module[@"methods"];
@ -387,27 +398,27 @@ static NSDictionary *RCTLocalModulesConfig()
@"type": @"local" @"type": @"local"
}; };
} }
// Add module and method lookup // Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"]; RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"]; RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
} }
}); });
return localModules; return localModules;
} }
@implementation RCTBridge @implementation RCTBridge
{ {
RCTSparseArray *_modulesByID; RCTSparseArray *_modulesByID;
NSMutableDictionary *_modulesByName; NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor; id<RCTJavaScriptExecutor> _javaScriptExecutor;
} }
static id<RCTJavaScriptExecutor> _latestJSExecutor; static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor - (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleInstances:(NSArray *)moduleInstances moduleProvider:(RCTBridgeModuleProviderBlock)block
{ {
if ((self = [super init])) { if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor; _javaScriptExecutor = javaScriptExecutor;
@ -417,31 +428,39 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Register passed-in module instances // Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init]; NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in moduleInstances) { if (block) {
preregisteredModules[RCTModuleNameForClass([module class])] = module; for (id<RCTBridgeModule> module in block(self)) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
}
} }
// Instantiate modules // Instantiate modules
_modulesByID = [[RCTSparseArray alloc] init]; _modulesByID = [[RCTSparseArray alloc] init];
_modulesByName = [[NSMutableDictionary alloc] initWithDictionary:preregisteredModules]; NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) { [RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSString *moduleName = RCTModuleNamesByID[moduleID]; NSString *moduleName = RCTModuleNamesByID[moduleID];
// Check if module instance has already been registered for this name // Check if module instance has already been registered for this name
if (_modulesByName[moduleName] != nil) { if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
// Preregistered instances takes precedence, no questions asked // Preregistered instances takes precedence, no questions asked
if (!preregisteredModules[moduleName]) { if (!preregisteredModules[moduleName]) {
// It's OK to have a name collision as long as the second instance is nil // It's OK to have a name collision as long as the second instance is nil
RCTAssert(RCTCreateModuleInstance(moduleClass, self) == nil, RCTAssert(RCTCreateModuleInstance(moduleClass, self) == nil,
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \ @"Attempted to register RCTBridgeModule class %@ for the name '%@', \
but name was already registered by class %@", moduleClass, but name was already registered by class %@", moduleClass,
moduleName, [_modulesByName[moduleName] class]); moduleName, [modulesByName[moduleName] class]);
} }
} else { } else {
// Module name hasn't been used before, so go ahead and instantiate // Module name hasn't been used before, so go ahead and instantiate
_modulesByID[moduleID] = _modulesByName[moduleName] = RCTCreateModuleInstance(moduleClass, self); id<RCTBridgeModule> module = RCTCreateModuleInstance(moduleClass, self);
if (module) {
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
} }
}]; }];
// Store modules
_modulesByName = [modulesByName copy];
// Inject module data into JS context // Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{ NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName), @"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@ -460,6 +479,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return self; return self;
} }
- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \
You may be trying to access a module too early in the startup procedure.");
return _modulesByName;
}
- (void)dealloc - (void)dealloc
{ {
RCTAssert(!self.valid, @"must call -invalidate before -dealloc"); RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
@ -516,7 +543,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[self _invokeAndProcessModule:@"BatchedBridge" [self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue" method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args]]; arguments:@[moduleID, methodID, args ?: @[]]];
} }
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete - (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
@ -699,8 +726,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
switch (argumentType[0]) { switch (argumentType[0]) {
case ':': case ':':
if ([param isKindOfClass:[NSString class]]) { if ([param isKindOfClass:[NSString class]]) {
SEL selector = NSSelectorFromString(param); SEL sel = NSSelectorFromString(param);
[invocation setArgument:&selector atIndex:argIdx]; [invocation setArgument:&sel atIndex:argIdx];
shouldSet = NO; shouldSet = NO;
} }
break; break;
@ -813,7 +840,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
+ (void)log:(NSArray *)objects level:(NSString *)level + (void)log:(NSArray *)objects level:(NSString *)level
{ {
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) { if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
RCTLogError(@"%@", RCTLogFormatString(@"ERROR: No valid JS executor to log %@.", objects)); RCTLogError(@"ERROR: No valid JS executor to log %@.", objects);
return; return;
} }
NSMutableArray *args = [NSMutableArray arrayWithObject:level]; NSMutableArray *args = [NSMutableArray arrayWithObject:level];

Просмотреть файл

@ -2,6 +2,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "RCTJSMethodRegistrar.h"
@class RCTBridge; @class RCTBridge;
/** /**
@ -11,9 +13,9 @@
typedef void (^RCTResponseSenderBlock)(NSArray *response); typedef void (^RCTResponseSenderBlock)(NSArray *response);
/** /**
* Provides minimal interface needed to register a bridge module * Provides the interface needed to register a bridge module.
*/ */
@protocol RCTBridgeModule <NSObject> @protocol RCTBridgeModule <RCTJSMethodRegistrar>
@optional @optional
/** /**
@ -47,15 +49,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
+ (NSDictionary *)constantsToExport; + (NSDictionary *)constantsToExport;
/** /**
* An array of JavaScript methods that the module will call via the * Some "constants" are not really constant, and need to be re-generated
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified * each time the bridge module is created. Support for this feature is
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a * deprecated and may be going away or changing, but for now you can use
* method that has not been registered will result in an error. If a method * the -constantsToExport instance method to register these "pseudo-constants".
* has already been regsistered by another module, it is not necessary to
* register it again, but it is good pratice. Registering the same method
* more than once is silently ignored and will not result in an error.
*/ */
+ (NSArray *)JSMethods; - (NSDictionary *)constantsToExport;
/** /**
* Notifies the module that a batch of JS method invocations has just completed. * Notifies the module that a batch of JS method invocations has just completed.

Просмотреть файл

@ -30,10 +30,16 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
- (instancetype)initWithBridge:(RCTBridge *)bridge; - (instancetype)initWithBridge:(RCTBridge *)bridge;
/** /**
* Send a device or application event that does not relate to a specific * Send an application-specific event that does not relate to a specific
* view, e.g. rotation, location, keyboard show/hide, background/awake, etc. * view, e.g. a navigation or data update notification.
*/ */
- (void)sendDeviceEventWithName:(NSString *)name body:(NSDictionary *)body; - (void)sendAppEventWithName:(NSString *)name body:(id)body;
/**
* Send a device or iOS event that does not relate to a specific view,
* e.g.rotation, location, keyboard show/hide, background/awake, etc.
*/
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body;
/** /**
* Send a user input event. The body dictionary must contain a "target" * Send a user input event. The body dictionary must contain a "target"

Просмотреть файл

@ -7,7 +7,7 @@
@implementation RCTEventDispatcher @implementation RCTEventDispatcher
{ {
RCTBridge *_bridge; RCTBridge __weak *_bridge;
} }
- (instancetype)initWithBridge:(RCTBridge *)bridge - (instancetype)initWithBridge:(RCTBridge *)bridge
@ -18,20 +18,34 @@
return self; return self;
} }
- (void)sendDeviceEventWithName:(NSString *)name body:(NSDictionary *)body + (NSArray *)JSMethods
{
return @[
@"RCTNativeAppEventEmitter.emit",
@"RCTDeviceEventEmitter.emit",
@"RCTEventEmitter.receiveEvent",
];
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
{
[_bridge enqueueJSCall:@"RCTNativeAppEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
{ {
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" [_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:body ? @[name, body] : @[name]]; args:body ? @[name, body] : @[name]];
} }
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body - (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{ {
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]], RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a react tag"); @"Event body dictionary must include a 'target' property containing a react tag");
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent" [_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:@[body[@"target"], name, body]]; args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];
} }
- (void)sendTextEventWithType:(RCTTextEventType)type - (void)sendTextEventWithType:(RCTTextEventType)type
@ -46,9 +60,11 @@
@"topEndEditing", @"topEndEditing",
}; };
[self sendInputEventWithName:events[type] body:@{ [self sendInputEventWithName:events[type] body:text ? @{
@"text": text, @"text": text,
@"target": reactTag @"target": reactTag
} : @{
@"target": reactTag
}]; }];
} }

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <Foundation/Foundation.h>
@class RCTBridge;
/**
* Provides an interface to register JS methods to be called via the bridge.
*/
@protocol RCTJSMethodRegistrar <NSObject>
@optional
/**
* An array of JavaScript methods that the class will call via the
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
* method that has not been registered will result in an error. If a method
* has already been registered by another class, it is not necessary to
* register it again, but it is good practice. Registering the same method
* more than once is silently ignored and will not result in an error.
*/
+ (NSArray *)JSMethods;
@end

Просмотреть файл

@ -87,11 +87,13 @@
{ {
_lastStackTrace = stack; _lastStackTrace = stack;
_lastErrorMessage = message; _lastErrorMessage = message;
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
[_stackTraceTableView reloadData];
[_stackTraceTableView setNeedsLayout];
if (self.hidden && shouldShow) { if (self.hidden && shouldShow) {
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
[_stackTraceTableView reloadData];
[_stackTraceTableView setNeedsLayout];
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop atScrollPosition:UITableViewScrollPositionTop
animated:NO]; animated:NO];

Просмотреть файл

@ -81,9 +81,20 @@ static Class _globalExecutorClass;
object:nil]; object:nil];
} }
+ (NSArray *)JSMethods
{
return @[
@"AppRegistry.runApplication",
@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
];
}
- (void)dealloc - (void)dealloc
{ {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
} }
- (void)bundleFinishedLoading:(NSError *)error - (void)bundleFinishedLoading:(NSError *)error
@ -125,7 +136,7 @@ static Class _globalExecutorClass;
// Choose local executor if specified, followed by global, followed by default // Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init]; _executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor moduleInstances:nil]; _bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor moduleProvider:nil];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler]; [self addGestureRecognizer:_touchHandler];

Просмотреть файл

@ -108,4 +108,9 @@
return [[[self class] allocWithZone:zone] initWithSparseArray:self]; return [[[self class] allocWithZone:zone] initWithSparseArray:self];
} }
- (NSString *)description
{
return [[super description] stringByAppendingString:[_storage description]];
}
@end @end

Просмотреть файл

@ -137,6 +137,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
} }
+ (NSArray *)JSMethods
{
return @[@"RCTEventEmitter.receiveTouches"];
}
/** /**
* Constructs information about touch events to send across the serialized * Constructs information about touch events to send across the serialized
* boundary. This data should be compliant with W3C `Touch` objects. This data * boundary. This data should be compliant with W3C `Touch` objects. This data

Просмотреть файл

@ -2,6 +2,7 @@
#import "RCTTiming.h" #import "RCTTiming.h"
#import "RCTAssert.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTSparseArray.h" #import "RCTSparseArray.h"
@ -11,7 +12,7 @@
@property (nonatomic, strong, readonly) NSDate *target; @property (nonatomic, strong, readonly) NSDate *target;
@property (nonatomic, assign, readonly) BOOL repeats; @property (nonatomic, assign, readonly) BOOL repeats;
@property (nonatomic, strong, readonly) NSNumber *callbackID; @property (nonatomic, copy, readonly) NSNumber *callbackID;
@property (nonatomic, assign, readonly) NSTimeInterval interval; @property (nonatomic, assign, readonly) NSTimeInterval interval;
@end @end
@ -172,12 +173,12 @@
if (jsSchedulingOverhead < 0) { if (jsSchedulingOverhead < 0) {
RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000)); RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
} }
NSTimeInterval targetTime = interval - jsSchedulingOverhead; NSTimeInterval targetTime = interval - jsSchedulingOverhead;
if (interval < 0.018) { // Make sure short intervals run each frame if (interval < 0.018) { // Make sure short intervals run each frame
interval = 0; interval = 0;
} }
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
interval:interval interval:interval
targetTime:targetTime targetTime:targetTime

Просмотреть файл

@ -137,6 +137,7 @@
13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ReactKit.h"; sourceTree = "<group>"; }; 13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ReactKit.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = "<group>"; }; 13E067541A70F44B002CDEE1 /* UIView+ReactKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ReactKit.m"; sourceTree = "<group>"; };
13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; }; 13ED13891A80C9D40050A8F9 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
@ -320,6 +321,7 @@
137029521A69923600575408 /* RCTImageDownloader.m */, 137029521A69923600575408 /* RCTImageDownloader.m */,
83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */, 83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */,
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */, 83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */,
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */,
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */, 13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */,
13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */, 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */,
83CBBA4D1A601E3B00E9B192 /* RCTLog.h */, 83CBBA4D1A601E3B00E9B192 /* RCTLog.h */,

Просмотреть файл

@ -12,7 +12,9 @@
"/node_modules/", "/node_modules/",
"/packager/" "/packager/"
], ],
"testFileExtensions": ["js"] "testFileExtensions": [
"js"
]
}, },
"scripts": { "scripts": {
"test": "jest", "test": "jest",
@ -45,7 +47,8 @@
"underscore": "1.7.0", "underscore": "1.7.0",
"wordwrap": "0.0.2", "wordwrap": "0.0.2",
"worker-farm": "1.1.0", "worker-farm": "1.1.0",
"yargs": "1.3.2" "yargs": "1.3.2",
"joi": "~5.1.0"
}, },
"devDependencies": { "devDependencies": {
"jest-cli": "0.2.1", "jest-cli": "0.2.1",

Просмотреть файл

@ -4,7 +4,11 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"jest": { "jest": {
"unmockedModulePathPatterns": ["source-map"], "unmockedModulePathPatterns": [
"testPathIgnorePatterns": ["JSAppServer/node_modules"] "source-map"
],
"testPathIgnorePatterns": [
"JSAppServer/node_modules"
]
} }
} }

Просмотреть файл

@ -8,15 +8,35 @@ var path = require('path');
var isAbsolutePath = require('absolute-path'); var isAbsolutePath = require('absolute-path');
var debug = require('debug')('DependecyGraph'); var debug = require('debug')('DependecyGraph');
var util = require('util'); var util = require('util');
var declareOpts = require('../../../lib/declareOpts');
var readFile = q.nfbind(fs.readFile); var readFile = q.nfbind(fs.readFile);
var readDir = q.nfbind(fs.readdir); var readDir = q.nfbind(fs.readdir);
var lstat = q.nfbind(fs.lstat); var lstat = q.nfbind(fs.lstat);
var realpath = q.nfbind(fs.realpath); var realpath = q.nfbind(fs.realpath);
var validateOpts = declareOpts({
roots: {
type: 'array',
required: true,
},
ignoreFilePath: {
type: 'function',
default: function(){}
},
fileWatcher: {
type: 'object',
required: true,
},
});
function DependecyGraph(options) { function DependecyGraph(options) {
this._roots = options.roots; var opts = validateOpts(options);
this._ignoreFilePath = options.ignoreFilePath || function(){};
this._roots = opts.roots;
this._ignoreFilePath = opts.ignoreFilePath;
this._fileWatcher = options.fileWatcher;
this._loaded = false; this._loaded = false;
this._queue = this._roots.slice(); this._queue = this._roots.slice();
this._graph = Object.create(null); this._graph = Object.create(null);
@ -24,7 +44,6 @@ function DependecyGraph(options) {
this._packagesById = Object.create(null); this._packagesById = Object.create(null);
this._moduleById = Object.create(null); this._moduleById = Object.create(null);
this._debugUpdateEvents = []; this._debugUpdateEvents = [];
this._fileWatcher = options.fileWatcher;
// Kick off the search process to precompute the dependency graph. // Kick off the search process to precompute the dependency graph.
this._init(); this._init();

Просмотреть файл

@ -24,7 +24,8 @@ describe('HasteDependencyResolver', function() {
var deps = [module]; var deps = [module];
var depResolver = new HasteDependencyResolver({ var depResolver = new HasteDependencyResolver({
projectRoot: '/root' projectRoot: '/root',
dev: false,
}); });
// Is there a better way? How can I mock the prototype instead? // Is there a better way? How can I mock the prototype instead?
@ -79,13 +80,75 @@ describe('HasteDependencyResolver', function() {
}); });
}); });
pit('should get dependencies with polyfills', function() {
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
var deps = [module];
var depResolver = new HasteDependencyResolver({
projectRoot: '/root',
dev: true,
});
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return deps;
});
depGraph.load.mockImpl(function() {
return q();
});
return depResolver.getDependencies('/root/index.js')
.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'
],
},
module
]);
});
});
pit('should pass in more polyfills', function() { pit('should pass in more polyfills', function() {
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']}; var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
var deps = [module]; var deps = [module];
var depResolver = new HasteDependencyResolver({ var depResolver = new HasteDependencyResolver({
projectRoot: '/root', projectRoot: '/root',
polyfillModuleNames: ['some module'] polyfillModuleNames: ['some module'],
dev: false,
}); });
// Is there a better way? How can I mock the prototype instead? // Is there a better way? How can I mock the prototype instead?
@ -155,7 +218,8 @@ describe('HasteDependencyResolver', function() {
describe('wrapModule', function() { describe('wrapModule', function() {
it('should ', function() { it('should ', function() {
var depResolver = new HasteDependencyResolver({ var depResolver = new HasteDependencyResolver({
projectRoot: '/root' projectRoot: '/root',
dev: false,
}); });
var depGraph = depResolver._depGraph; var depGraph = depResolver._depGraph;

Просмотреть файл

@ -4,6 +4,7 @@ var path = require('path');
var FileWatcher = require('../../FileWatcher'); var FileWatcher = require('../../FileWatcher');
var DependencyGraph = require('./DependencyGraph'); var DependencyGraph = require('./DependencyGraph');
var ModuleDescriptor = require('../ModuleDescriptor'); var ModuleDescriptor = require('../ModuleDescriptor');
var declareOpts = require('../../lib/declareOpts');
var DEFINE_MODULE_CODE = var DEFINE_MODULE_CODE =
'__d(' + '__d(' +
@ -18,22 +19,50 @@ var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi; var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
function HasteDependencyResolver(config) { var validateOpts = declareOpts({
this._fileWatcher = config.nonPersistent projectRoots: {
type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
polyfillModuleNames: {
type: 'array',
default: [],
},
dev: {
type: 'boolean',
default: true,
},
nonPersistent: {
type: 'boolean',
default: false,
},
moduleFormat: {
type: 'string',
default: 'haste',
},
});
function HasteDependencyResolver(options) {
var opts = validateOpts(options);
this._fileWatcher = opts.nonPersistent
? FileWatcher.createDummyWatcher() ? FileWatcher.createDummyWatcher()
: new FileWatcher(config.projectRoots); : new FileWatcher(opts.projectRoots);
this._depGraph = new DependencyGraph({ this._depGraph = new DependencyGraph({
roots: config.projectRoots, roots: opts.projectRoots,
ignoreFilePath: function(filepath) { ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 || return filepath.indexOf('__tests__') !== -1 ||
(config.blacklistRE && config.blacklistRE.test(filepath)); (opts.blacklistRE && opts.blacklistRE.test(filepath));
}, },
fileWatcher: this._fileWatcher fileWatcher: this._fileWatcher
}); });
this._polyfillModuleNames = [ this._polyfillModuleNames = [
config.dev opts.dev
? path.join(__dirname, 'polyfills/prelude_dev.js') ? path.join(__dirname, 'polyfills/prelude_dev.js')
: path.join(__dirname, 'polyfills/prelude.js'), : path.join(__dirname, 'polyfills/prelude.js'),
path.join(__dirname, 'polyfills/require.js'), path.join(__dirname, 'polyfills/require.js'),
@ -41,7 +70,7 @@ function HasteDependencyResolver(config) {
path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/console.js'),
path.join(__dirname, 'polyfills/error-guard.js'), path.join(__dirname, 'polyfills/error-guard.js'),
].concat( ].concat(
config.polyfillModuleNames || [] opts.polyfillModuleNames || []
); );
} }

Просмотреть файл

@ -4,19 +4,36 @@ var path = require('path');
var version = require('../../package.json').version; var version = require('../../package.json').version;
var tmpdir = require('os').tmpDir(); var tmpdir = require('os').tmpDir();
var pathUtils = require('../fb-path-utils'); var pathUtils = require('../fb-path-utils');
var declareOpts = require('../lib/declareOpts');
var fs = require('fs'); var fs = require('fs');
var _ = require('underscore'); var _ = require('underscore');
var q = require('q'); var q = require('q');
var Promise = q.Promise; var Promise = q.Promise;
var validateOpts = declareOpts({
resetCache: {
type: 'boolean',
default: false,
},
cacheVersion: {
type: 'string',
default: '1.0',
},
projectRoots: {
type: 'array',
required: true,
},
});
module.exports = Cache; module.exports = Cache;
function Cache(projectConfig) { function Cache(options) {
this._cacheFilePath = cacheFilePath(projectConfig); var opts = validateOpts(options);
this._cacheFilePath = cacheFilePath(opts);
var data; var data;
if (!projectConfig.resetCache) { if (!opts.resetCache) {
data = loadCacheSync(this._cacheFilePath); data = loadCacheSync(this._cacheFilePath);
} else { } else {
data = Object.create(null); data = Object.create(null);
@ -63,7 +80,7 @@ Cache.prototype.invalidate = function(filepath){
if(this._has(filepath)) { if(this._has(filepath)) {
delete this._data[filepath]; delete this._data[filepath];
} }
} };
Cache.prototype.end = function() { Cache.prototype.end = function() {
return this._persistCache(); return this._persistCache();
@ -114,9 +131,9 @@ function loadCacheSync(cacheFilepath) {
return ret; return ret;
} }
function cacheFilePath(projectConfig) { function cacheFilePath(options) {
var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-'); var roots = options.projectRoots.join(',').split(path.sep).join('-');
var cacheVersion = projectConfig.cacheVersion || '0'; var cacheVersion = options.cacheVersion || '0';
return path.join( return path.join(
tmpdir, tmpdir,
[ [

Просмотреть файл

@ -1,28 +1,69 @@
'use strict'; 'use strict';
var os = require('os');
var fs = require('fs'); var fs = require('fs');
var q = require('q'); var q = require('q');
var Cache = require('./Cache'); var Cache = require('./Cache');
var _ = require('underscore'); var _ = require('underscore');
var workerFarm = require('worker-farm'); var workerFarm = require('worker-farm');
var declareOpts = require('../lib/declareOpts');
var readFile = q.nfbind(fs.readFile); var readFile = q.nfbind(fs.readFile);
module.exports = Transformer; module.exports = Transformer;
Transformer.TransformError = TransformError; Transformer.TransformError = TransformError;
function Transformer(projectConfig) { var validateOpts = declareOpts({
this._cache = projectConfig.nonPersistent projectRoots: {
? new DummyCache() : new Cache(projectConfig); type: 'array',
required: true,
},
blacklistRE: {
type: 'object', // typeof regex is object
},
polyfillModuleNames: {
type: 'array',
default: [],
},
cacheVersion: {
type: 'string',
default: '1.0',
},
resetCache: {
type: 'boolean',
default: false,
},
dev: {
type: 'boolean',
default: true,
},
transformModulePath: {
type:'string',
required: true,
},
nonPersistent: {
type: 'boolean',
default: false,
},
});
if (projectConfig.transformModulePath == null) { function Transformer(options) {
var opts = validateOpts(options);
this._cache = opts.nonPersistent
? new DummyCache()
: new Cache({
resetCache: options.resetCache,
cacheVersion: options.cacheVersion,
projectRoots: options.projectRoots,
});
if (options.transformModulePath == null) {
this._failedToStart = q.Promise.reject(new Error('No transfrom module')); this._failedToStart = q.Promise.reject(new Error('No transfrom module'));
} else { } else {
this._workers = workerFarm( this._workers = workerFarm(
{autoStart: true}, {autoStart: true},
projectConfig.transformModulePath options.transformModulePath
); );
} }
} }

95
packager/react-packager/src/Packager/index.js поставляемый
Просмотреть файл

@ -10,44 +10,69 @@ var DependencyResolver = require('../DependencyResolver');
var _ = require('underscore'); var _ = require('underscore');
var Package = require('./Package'); var Package = require('./Package');
var Activity = require('../Activity'); var Activity = require('../Activity');
var declareOpts = require('../lib/declareOpts');
var DEFAULT_CONFIG = { var validateOpts = declareOpts({
/** projectRoots: {
* RegExp used to ignore paths when scanning the filesystem to calculate the type: 'array',
* dependency graph. required: true,
*/ },
blacklistRE: null, 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,
},
dev: {
type: 'boolean',
default: true,
},
transformModulePath: {
type:'string',
required: true,
},
nonPersistent: {
type: 'boolean',
default: false,
},
});
/** function Packager(options) {
* The kind of module system/transport wrapper to use for the modules bundled var opts = this._opts = validateOpts(options);
* in the package.
*/
moduleFormat: 'haste',
/** opts.projectRoots.forEach(verifyRootExists);
* An ordered list of module names that should be considered as dependencies
* of every module in the system. The list is ordered because every item in
* the list will have an implicit dependency on all items before it.
*
* (This ordering is necessary to build, for example, polyfills that build on
* each other)
*/
polyfillModuleNames: [],
nonPersistent: false, this._resolver = new DependencyResolver({
}; projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames,
dev: opts.dev,
nonPersistent: opts.nonPersistent,
moduleFormat: opts.moduleFormat
});
function Packager(projectConfig) { this._transformer = new Transformer({
projectConfig.projectRoots.forEach(verifyRootExists); projectRoots: opts.projectRoots,
blacklistRE: opts.blacklistRE,
this._config = Object.create(DEFAULT_CONFIG); cacheVersion: opts.cacheVersion,
for (var key in projectConfig) { resetCache: opts.resetCache,
this._config[key] = projectConfig[key]; dev: opts.dev,
} transformModulePath: opts.transformModulePath,
nonPersistent: opts.nonPersistent,
this._resolver = new DependencyResolver(this._config); });
this._transformer = new Transformer(projectConfig);
} }
Packager.prototype.kill = function() { Packager.prototype.kill = function() {
@ -92,7 +117,7 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl) {
Packager.prototype.invalidateFile = function(filePath) { Packager.prototype.invalidateFile = function(filePath) {
this._transformer.invalidateFile(filePath); this._transformer.invalidateFile(filePath);
} };
Packager.prototype.getDependencies = function(main) { Packager.prototype.getDependencies = function(main) {
return this._resolver.getDependencies(main); return this._resolver.getDependencies(main);
@ -103,7 +128,7 @@ Packager.prototype._transformModule = function(module) {
return this._transformer.loadFileAndTransform( return this._transformer.loadFileAndTransform(
['es6'], ['es6'],
path.resolve(module.path), path.resolve(module.path),
this._config.transformer || {} this._opts.transformer || {}
).then(function(transformed) { ).then(function(transformed) {
return _.extend( return _.extend(
{}, {},

Просмотреть файл

@ -6,8 +6,6 @@ jest.setMock('worker-farm', function(){ return function(){}; })
.dontMock('url') .dontMock('url')
.dontMock('../'); .dontMock('../');
var server = require('../');
var q = require('q'); var q = require('q');
describe('processRequest', function(){ describe('processRequest', function(){
@ -45,17 +43,17 @@ describe('processRequest', function(){
beforeEach(function(){ beforeEach(function(){
Activity = require('../../Activity'); Activity = require('../../Activity');
Packager = require('../../Packager'); Packager = require('../../Packager');
FileWatcher = require('../../FileWatcher') FileWatcher = require('../../FileWatcher');
Packager.prototype.package = function(main, runModule, sourceMapUrl) { Packager.prototype.package = function() {
return q({ return q({
getSource: function(){ getSource: function() {
return "this is the source" return 'this is the source';
}, },
getSourceMap: function(){ getSourceMap: function(){
return "this is the source map" return 'this is the source map';
} },
}) });
}; };
FileWatcher.prototype.on = watcherFunc; FileWatcher.prototype.on = watcherFunc;

60
packager/react-packager/src/Server/index.js поставляемый
Просмотреть файл

@ -1,26 +1,56 @@
var url = require('url'); var url = require('url');
var path = require('path'); var path = require('path');
var FileWatcher = require('../FileWatcher') var declareOpts = require('../lib/declareOpts');
var FileWatcher = require('../FileWatcher');
var Packager = require('../Packager'); var Packager = require('../Packager');
var Activity = require('../Activity'); var Activity = require('../Activity');
var q = require('q'); var q = require('q');
module.exports = Server; module.exports = Server;
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,
},
dev: {
type: 'boolean',
default: true,
},
transformModulePath: {
type:'string',
required: true,
},
nonPersistent: {
type: 'boolean',
default: false,
},
});
function Server(options) { function Server(options) {
this._projectRoots = options.projectRoots; var opts = validateOpts(options);
this._projectRoots = opts.projectRoots;
this._packages = Object.create(null); this._packages = Object.create(null);
this._packager = new Packager({ this._packager = new Packager(opts);
projectRoots: options.projectRoots,
blacklistRE: options.blacklistRE,
polyfillModuleNames: options.polyfillModuleNames || [],
runtimeCode: options.runtimeCode,
cacheVersion: options.cacheVersion,
resetCache: options.resetCache,
dev: options.dev,
transformModulePath: options.transformModulePath,
nonPersistent: options.nonPersistent,
});
this._fileWatcher = options.nonPersistent this._fileWatcher = options.nonPersistent
? FileWatcher.createDummyWatcher() ? FileWatcher.createDummyWatcher()
@ -35,10 +65,10 @@ Server.prototype._onFileChange = function(type, filepath, root) {
this._packager.invalidateFile(absPath); this._packager.invalidateFile(absPath);
// Make sure the file watcher event runs through the system before // Make sure the file watcher event runs through the system before
// we rebuild the packages. // we rebuild the packages.
setImmediate(this._rebuildPackages.bind(this, absPath)) setImmediate(this._rebuildPackages.bind(this, absPath));
}; };
Server.prototype._rebuildPackages = function(filepath) { Server.prototype._rebuildPackages = function() {
var buildPackage = this._buildPackage.bind(this); var buildPackage = this._buildPackage.bind(this);
var packages = this._packages; var packages = this._packages;
Object.keys(packages).forEach(function(key) { Object.keys(packages).forEach(function(key) {

10
packager/react-packager/src/lib/__mocks__/declareOpts.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
module.exports = function(declared) {
return function(opts) {
for (var p in declared) {
if (opts[p] == null && declared[p].default != null){
opts[p] = declared[p].default;
}
}
return opts;
};
};

82
packager/react-packager/src/lib/__tests__/declareOpts-test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,82 @@
jest.autoMockOff();
var declareOpts = require('../declareOpts');
describe('declareOpts', function() {
it('should declare and validate simple opts', function() {
var validate = declareOpts({
name: {
required: true,
type: 'string',
},
age: {
type: 'number',
default: 21,
}
});
var opts = validate({ name: 'fooer' });
expect(opts).toEqual({
name: 'fooer',
age: 21
});
});
it('should work with complex types', function() {
var validate = declareOpts({
things: {
required: true,
type: 'array',
},
stuff: {
type: 'object',
required: true,
}
});
var opts = validate({ things: [1, 2, 3], stuff: {hai: 1} });
expect(opts).toEqual({
things: [1,2,3],
stuff: {hai: 1},
});
});
it('should throw when a required option is not present', function() {
var validate = declareOpts({
foo: {
required: true,
type: 'number',
}
});
expect(function() {
validate({});
}).toThrow('Error validating module options: foo is required');
});
it('should throw on invalid type', function() {
var validate = declareOpts({
foo: {
required: true,
type: 'number'
}
});
expect(function() {
validate({foo: 'lol'});
}).toThrow('Error validating module options: foo must be a number');
});
it('should throw on extra options', function() {
var validate = declareOpts({
foo: {
required: true,
type: 'number',
}
});
expect(function() {
validate({foo: 1, lol: 1});
}).toThrow('Error validating module options: lol is not allowed');
});
});

53
packager/react-packager/src/lib/declareOpts.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,53 @@
/**
* Declares, validates and defaults options.
* var validate = declareOpts({
* foo: {
* type: 'bool',
* required: true,
* }
* });
*
* var myOptions = validate(someOptions);
*/
var Joi = require('joi');
module.exports = function(descriptor) {
var joiKeys = {};
Object.keys(descriptor).forEach(function(prop) {
var record = descriptor[prop];
if (record.type == null) {
throw new Error('Type is required');
}
if (record.type === 'function') {
record.type = 'func';
}
var propValidator = Joi[record.type]();
if (record.required) {
propValidator = propValidator.required();
}
if (record.default) {
propValidator = propValidator.default(record.default);
}
joiKeys[prop] = propValidator;
});
var schema = Joi.object().keys(joiKeys);
return function(opts) {
var res = Joi.validate(opts, schema, {
abortEarly: true,
allowUnknown: false,
});
if (res.error) {
throw new Error('Error validating module options: ' + res.error.message);
}
return res.value;
};
};