Add `RCTDevSplitBundleLoader` native module

Reviewed By: ejanzer

Differential Revision: D21302418

fbshipit-source-id: a868f6dad3306190c7add26e8f9a976866c16aef
This commit is contained in:
Christoph Nakazawa 2020-06-08 09:04:56 -07:00 коммит произвёл Facebook GitHub Bot
Родитель fc2b538f0d
Коммит ad879e50bc
17 изменённых файлов: 343 добавлений и 12 удалений

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

@ -981,6 +981,26 @@ namespace facebook {
}
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeDevSplitBundleLoaderSpecJSI_loadBundle(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, PromiseKind, "loadBundle", @selector(loadBundle:resolve:reject:), args, count);
}
NativeDevSplitBundleLoaderSpecJSI::NativeDevSplitBundleLoaderSpecJSI(const ObjCTurboModule::InitParams &params)
: ObjCTurboModule(params) {
methodMap_["loadBundle"] = MethodMetadata {1, __hostFunction_NativeDevSplitBundleLoaderSpecJSI_loadBundle};
}
} // namespace react

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

@ -918,6 +918,26 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
@protocol NativeDevSplitBundleLoaderSpec <RCTBridgeModule, RCTTurboModule>
- (void)loadBundle:(NSString *)bundlePath
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject;
@end
namespace facebook {
namespace react {
/**
* ObjC++ class for module 'DevSplitBundleLoader'
*/
class JSI_EXPORT NativeDevSplitBundleLoaderSpecJSI : public ObjCTurboModule {
public:
NativeDevSplitBundleLoaderSpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace react
} // namespace facebook
@protocol NativeDeviceEventManagerSpec <RCTBridgeModule, RCTTurboModule>
- (void)invokeDefaultBackPressHandler;

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

@ -0,0 +1,20 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {TurboModule} from '../TurboModule/RCTExport';
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
export interface Spec extends TurboModule {
+loadBundle: (bundlePath: string) => Promise<void>;
}
export default (TurboModuleRegistry.get<Spec>('DevSplitBundleLoader'): ?Spec);

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

@ -33,6 +33,11 @@ RCT_EXTERN NSString *const RCTJavaScriptWillStartExecutingNotification;
*/
RCT_EXTERN NSString *const RCTJavaScriptDidLoadNotification;
/**
* This notification fires every time the bridge has finished loading an additional JS bundle.
*/
RCT_EXTERN NSString *const RCTAdditionalJavaScriptDidLoadNotification;
/**
* This notification fires when the bridge failed to load the JS bundle. The
* `error` key can be used to determine the error that occurred.
@ -135,6 +140,12 @@ RCT_EXTERN NSString *const RCTBridgeDidDownloadScriptNotificationBridgeDescripti
*/
typedef NSArray<id<RCTBridgeModule>> * (^RCTBridgeModuleListProvider)(void);
/**
* These blocks are used to report whether an additional bundle
* fails or succeeds loading.
*/
typedef void (^RCTLoadAndExecuteErrorBlock)(NSError *error);
/**
* This function returns the module name for a given class.
*/
@ -290,8 +301,15 @@ RCT_EXTERN void RCTEnableTurboModule(BOOL enabled);
- (void)requestReload __deprecated_msg("Use RCTReloadCommand instead");
/**
* Says whether bridge has started receiving calls from javascript.
* Says whether bridge has started receiving calls from JavaScript.
*/
- (BOOL)isBatchActive;
/**
* Loads and executes additional bundles in the VM for development.
*/
- (void)loadAndExecuteSplitBundleURL:(NSURL *)bundleURL
onError:(RCTLoadAndExecuteErrorBlock)onError
onComplete:(dispatch_block_t)onComplete;
@end

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

@ -15,6 +15,7 @@
#if RCT_ENABLE_INSPECTOR
#import "RCTInspectorDevServerHelper.h"
#endif
#import "RCTDevLoadingViewProtocol.h"
#import "RCTLog.h"
#import "RCTModuleData.h"
#import "RCTPerformanceLogger.h"
@ -22,10 +23,11 @@
#import "RCTReloadCommand.h"
#import "RCTUtils.h"
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTAdditionalJavaScriptDidLoadNotification = @"RCTAdditionalJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification";
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification";
NSString *const RCTJavaScriptWillStartLoadingNotification = @"RCTJavaScriptWillStartLoadingNotification";
NSString *const RCTDidInitializeModuleNotification = @"RCTDidInitializeModuleNotification";
NSString *const RCTDidSetupModuleNotification = @"RCTDidSetupModuleNotification";
NSString *const RCTDidSetupModuleNotificationModuleNameKey = @"moduleName";
@ -388,4 +390,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
[self.batchedBridge registerSegmentWithId:segmentId path:path];
}
- (void)loadAndExecuteSplitBundleURL:(NSURL *)bundleURL
onError:(RCTLoadAndExecuteErrorBlock)onError
onComplete:(dispatch_block_t)onComplete
{
[self.batchedBridge loadAndExecuteSplitBundleURL:bundleURL onError:onError onComplete:onComplete];
}
@end

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

@ -301,9 +301,23 @@ static void attemptAsynchronousLoadOfBundleAtURL(
NSString *contentType = headers[@"Content-Type"];
NSString *mimeType = [[contentType componentsSeparatedByString:@";"] firstObject];
if (![mimeType isEqualToString:@"application/javascript"] && ![mimeType isEqualToString:@"text/javascript"]) {
NSString *description = [NSString
stringWithFormat:@"Expected MIME-Type to be 'application/javascript' or 'text/javascript', but got '%@'.",
mimeType];
NSString *description;
if ([mimeType isEqualToString:@"application/json"]) {
NSError *parseError;
NSDictionary *jsonError = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (!parseError && [jsonError isKindOfClass:[NSDictionary class]] &&
[[jsonError objectForKey:@"message"] isKindOfClass:[NSString class]] &&
[[jsonError objectForKey:@"message"] length]) {
description = [jsonError objectForKey:@"message"];
} else {
description = [NSString stringWithFormat:@"Unknown error fetching '%@'.", scriptURL.absoluteString];
}
} else {
description = [NSString
stringWithFormat:
@"Expected MIME-Type to be 'application/javascript' or 'text/javascript', but got '%@'.", mimeType];
}
error = [NSError
errorWithDomain:@"JSServer"
code:NSURLErrorCannotParseResponse

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

@ -116,6 +116,9 @@ rn_apple_library(
) + react_module_plugin_providers(
name = "DevLoadingView",
native_class_func = "RCTDevLoadingViewCls",
) + react_module_plugin_providers(
name = "DevSplitBundleLoader",
native_class_func = "RCTDevSplitBundleLoaderCls",
),
plugins_header = "FBCoreModulesPlugins.h",
preprocessor_flags = OBJC_ARC_PREPROCESSOR_FLAGS + get_preprocessor_flags_for_build_mode() + rn_extra_build_flags() + [

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

@ -53,6 +53,7 @@ Class RCTTVNavigationEventEmitterCls(void) __attribute__((used));
Class RCTWebSocketExecutorCls(void) __attribute__((used));
Class RCTWebSocketModuleCls(void) __attribute__((used));
Class RCTDevLoadingViewCls(void) __attribute__((used));
Class RCTDevSplitBundleLoaderCls(void) __attribute__((used));
#ifdef __cplusplus
}

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

@ -42,6 +42,7 @@ Class RCTCoreModulesClassProvider(const char *name) {
{"WebSocketExecutor", RCTWebSocketExecutorCls},
{"WebSocketModule", RCTWebSocketModuleCls},
{"DevLoadingView", RCTDevLoadingViewCls},
{"DevSplitBundleLoader", RCTDevSplitBundleLoaderCls},
};
auto p = sCoreModuleClassMap.find(name);

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

@ -82,9 +82,14 @@
- (void)toggleElementInspector;
/**
* If loading bundle from metro, sets up HMRClient.
* Set up the HMRClient if loading the bundle from Metro.
*/
- (void)setupHotModuleReloadClientIfApplicableForURL:(NSURL *)bundleURL;
- (void)setupHMRClientWithBundleURL:(NSURL *)bundleURL;
/**
* Register additional bundles with the HMRClient.
*/
- (void)setupHMRClientWithAdditionalBundleURL:(NSURL *)bundleURL;
#if RCT_DEV_MENU
- (void)addHandler:(id<RCTPackagerClientMethod>)handler

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

@ -403,7 +403,7 @@ RCT_EXPORT_METHOD(addMenuItem : (NSString *)title)
#endif
}
- (void)setupHotModuleReloadClientIfApplicableForURL:(NSURL *)bundleURL
- (void)setupHMRClientWithBundleURL:(NSURL *)bundleURL
{
if (bundleURL && !bundleURL.fileURL) { // isHotLoadingAvailable check
NSString *const path = [bundleURL.path substringFromIndex:1]; // Strip initial slash.
@ -420,6 +420,20 @@ RCT_EXPORT_METHOD(addMenuItem : (NSString *)title)
}
}
- (void)setupHMRClientWithAdditionalBundleURL:(NSURL *)bundleURL
{
if (bundleURL && !bundleURL.fileURL) { // isHotLoadingAvailable check
if (self.bridge) {
[self.bridge enqueueJSCall:@"HMRClient"
method:@"registerBundle"
args:@[ [bundleURL absoluteString] ]
completion:NULL];
} else {
self.invokeJS(@"HMRClient", @"registerBundle", @[ [bundleURL absoluteString] ]);
}
}
}
#pragma mark - Internal
/**
@ -509,7 +523,10 @@ RCT_EXPORT_METHOD(addMenuItem : (NSString *)title)
- (void)toggleElementInspector
{
}
- (void)setupHotModuleReloadClientIfApplicableForURL:(NSURL *)bundleURL
- (void)setupHMRClientWithBundleURL:(NSURL *)bundleURL
{
}
- (void)setupHMRClientWithAdditionalBundleURL:(NSURL *)bundleURL
{
}
- (void)addMenuItem:(NSString *)title

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

@ -0,0 +1,12 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridgeModule.h>
#import <UIKit/UIKit.h>
@interface RCTDevSplitBundleLoader : NSObject <RCTBridgeModule>
@end

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

@ -0,0 +1,88 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTDevSplitBundleLoader.h>
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTUtils.h>
#import "CoreModulesPlugins.h"
using namespace facebook::react;
@interface RCTDevSplitBundleLoader () <NativeDevSplitBundleLoaderSpec>
@end
#if RCT_DEV_MENU
@implementation RCTDevSplitBundleLoader {
}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
}
RCT_EXPORT_METHOD(loadBundle
: (NSString *)bundlePath resolve
: (RCTPromiseResolveBlock)resolve reject
: (RCTPromiseRejectBlock)reject)
{
NSURL *sourceURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForSplitBundleRoot:bundlePath];
[_bridge loadAndExecuteSplitBundleURL:sourceURL
onError:^(NSError *error) {
reject(@"E_BUNDLE_LOAD_ERROR", [error localizedDescription], error);
}
onComplete:^() {
resolve(@YES);
}];
}
- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params
{
return std::make_shared<NativeDevSplitBundleLoaderSpecJSI>(params);
}
@end
#else
@implementation RCTDevSplitBundleLoader
+ (NSString *)moduleName
{
return nil;
}
- (void)loadBundle:(NSString *)bundlePath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
{
}
- (std::shared_ptr<TurboModule>)getTurboModule:(const ObjCTurboModule::InitParams &)params
{
return std::make_shared<NativeDevSplitBundleLoaderSpecJSI>(params);
}
@end
#endif
Class RCTDevSplitBundleLoaderCls(void)
{
return RCTDevSplitBundleLoader.class;
}

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

@ -966,9 +966,49 @@ struct RCTInstanceCallback : public InstanceCallback {
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
}
[self.devSettings setupHotModuleReloadClientIfApplicableForURL:self.bundleURL];
[self.devSettings setupHMRClientWithBundleURL:self.bundleURL];
}
#if RCT_DEV_MENU
- (void)loadAndExecuteSplitBundleURL:(NSURL *)bundleURL
onError:(RCTLoadAndExecuteErrorBlock)onError
onComplete:(dispatch_block_t)onComplete
{
__weak __typeof(self) weakSelf = self;
[RCTJavaScriptLoader loadBundleAtURL:bundleURL
onProgress:^(RCTLoadingProgress *progressData) {
#if (RCT_DEV_MENU | RCT_ENABLE_LOADING_VIEW) && __has_include(<React/RCTDevLoadingViewProtocol.h>)
id<RCTDevLoadingViewProtocol> loadingView = [weakSelf moduleForName:@"DevLoadingView"
lazilyLoadIfNecessary:YES];
[loadingView updateProgress:progressData];
#endif
}
onComplete:^(NSError *error, RCTSource *source) {
if (error) {
onError(error);
return;
}
[self enqueueApplicationScript:source.data
url:source.url
onComplete:^{
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTAdditionalJavaScriptDidLoadNotification
object:self->_parentBridge
userInfo:@{@"bridge" : self}];
[self.devSettings setupHMRClientWithAdditionalBundleURL:source.url];
onComplete();
}];
}];
}
#else
- (void)loadAndExecuteSplitBundleURL:(NSURL *)bundleURL
onError:(RCTLoadAndExecuteErrorBlock)onError
onComplete:(dispatch_block_t)onComplete
{
}
#endif
- (void)handleError:(NSError *)error
{
// This is generally called when the infrastructure throws an

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

@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*
* <p>Generated by an internal genrule from Flow types.
*
* @generated
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactModuleWithSpec;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
public abstract class NativeDevSplitBundleLoaderSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
public NativeDevSplitBundleLoaderSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public abstract void loadBundle(String bundlePath, Promise promise);
}

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

@ -819,6 +819,26 @@ namespace facebook {
}
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
static facebook::jsi::Value __hostFunction_NativeDevSplitBundleLoaderSpecJSI_loadBundle(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
return static_cast<JavaTurboModule&>(turboModule).invokeJavaMethod(rt, PromiseKind, "loadBundle", "(Ljava/lang/String;Lcom/facebook/react/bridge/Promise;)V", args, count);
}
NativeDevSplitBundleLoaderSpecJSI::NativeDevSplitBundleLoaderSpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
methodMap_["loadBundle"] = MethodMetadata {1, __hostFunction_NativeDevSplitBundleLoaderSpecJSI_loadBundle};
}
} // namespace react

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

@ -255,6 +255,20 @@ namespace facebook {
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
/**
* C++ class for module 'DevSplitBundleLoader'
*/
class JSI_EXPORT NativeDevSplitBundleLoaderSpecJSI : public JavaTurboModule {
public:
NativeDevSplitBundleLoaderSpecJSI(const JavaTurboModule::InitParams &params);
};
} // namespace react
} // namespace facebook
namespace facebook {
namespace react {
/**