Updates from Tue Feb 24
- 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:
Родитель
1f8740a9f8
Коммит
c892d2c8d1
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
Загрузка…
Ссылка в новой задаче