From fca3a39da5f1c31514e8969738e7b2c2d22bc230 Mon Sep 17 00:00:00 2001 From: Oleksandr Melnykov Date: Wed, 17 Jun 2020 09:08:57 -0700 Subject: [PATCH] Add native module for loading split JS bundles in development Reviewed By: mdvacca, cpojer Differential Revision: D22001709 fbshipit-source-id: 4e378fd6ae90268e7db9092a71628205b9f7c37d --- .../src/main/java/com/facebook/react/BUCK | 1 + .../facebook/react/CoreModulesPackage.java | 8 +- .../react/bridge/CatalystInstanceImpl.java | 5 + .../facebook/react/bridge/JSBundleLoader.java | 19 +++ .../react/bridge/JSBundleLoaderDelegate.java | 6 + .../react/common/DebugServerException.java | 9 ++ .../react/devsupport/BundleDownloader.java | 9 +- .../react/devsupport/DevServerHelper.java | 20 ++- .../devsupport/DevSupportManagerBase.java | 137 +++++++++++++----- .../devsupport/DisabledDevSupportManager.java | 4 + .../facebook/react/devsupport/HMRClient.java | 3 + .../interfaces/DevSplitBundleCallback.java | 16 ++ .../interfaces/DevSupportManager.java | 2 + .../facebook/react/modules/bundleloader/BUCK | 21 +++ .../NativeDevSplitBundleLoaderModule.java | 58 ++++++++ 15 files changed, 272 insertions(+), 46 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSplitBundleCallback.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/BUCK create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/NativeDevSplitBundleLoaderModule.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK index f18cc78f8f02c7..e2a345df18109c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK @@ -35,6 +35,7 @@ rn_android_library( react_native_target("java/com/facebook/react/module/model:model"), react_native_target("java/com/facebook/react/modules/appregistry:appregistry"), react_native_target("java/com/facebook/react/modules/appearance:appearance"), + react_native_target("java/com/facebook/react/modules/bundleloader:bundleloader"), react_native_target("java/com/facebook/react/modules/debug:debug"), react_native_target("java/com/facebook/react/modules/fabric:fabric"), react_native_target("java/com/facebook/react/modules/debug:interfaces"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 754d86ec08d5c4..36fb15b0a52d41 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -21,6 +21,7 @@ import com.facebook.react.module.annotations.ReactModuleList; import com.facebook.react.module.model.ReactModuleInfo; import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.modules.bundleloader.NativeDevSplitBundleLoaderModule; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.modules.core.ExceptionsManagerModule; @@ -56,6 +57,7 @@ SourceCodeModule.class, TimingModule.class, UIManagerModule.class, + NativeDevSplitBundleLoaderModule.class, }) public class CoreModulesPackage extends TurboReactPackage implements ReactPackageLogger { @@ -101,7 +103,8 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { HeadlessJsTaskSupportModule.class, SourceCodeModule.class, TimingModule.class, - UIManagerModule.class + UIManagerModule.class, + NativeDevSplitBundleLoaderModule.class, }; final Map reactModuleInfoMap = new HashMap<>(); @@ -158,6 +161,9 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) return createUIManager(reactContext); case DeviceInfoModule.NAME: return new DeviceInfoModule(reactContext); + case NativeDevSplitBundleLoaderModule.NAME: + return new NativeDevSplitBundleLoaderModule( + reactContext, mReactInstanceManager.getDevSupportManager()); default: throw new IllegalArgumentException( "In CoreModulesPackage, could not find Native module for " + name); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 0a9e52b8f958a1..e225306e68b99b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -240,6 +240,11 @@ public void loadScriptFromFile(String fileName, String sourceURL, boolean loadSy jniLoadScriptFromFile(fileName, sourceURL, loadSynchronously); } + @Override + public void loadSplitBundleFromFile(String fileName, String sourceURL) { + jniLoadScriptFromFile(fileName, sourceURL, false); + } + private native void jniSetSourceURL(String sourceURL); private native void jniRegisterSegment(int segmentId, String path); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java index d8d577b1aa6ea6..5ff5d4a305113c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoader.java @@ -73,6 +73,25 @@ public String loadScript(JSBundleLoaderDelegate delegate) { }; } + /** + * Same as {{@link JSBundleLoader#createCachedBundleFromNetworkLoader(String, String)}}, but for + * split bundles in development. + */ + public static JSBundleLoader createCachedSplitBundleFromNetworkLoader( + final String sourceURL, final String cachedFileLocation) { + return new JSBundleLoader() { + @Override + public String loadScript(JSBundleLoaderDelegate delegate) { + try { + delegate.loadSplitBundleFromFile(cachedFileLocation, sourceURL); + return sourceURL; + } catch (Exception e) { + throw DebugServerException.makeGeneric(sourceURL, e.getMessage(), e); + } + } + }; + } + /** * This loader is used when proxy debugging is enabled. In that case there is no point in fetching * the bundle from device as remote executor will have to do it anyway. diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoaderDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoaderDelegate.java index 7b2121f5a26438..f03cd14ae18502 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoaderDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/JSBundleLoaderDelegate.java @@ -33,6 +33,12 @@ public interface JSBundleLoaderDelegate { */ void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously); + /** + * Load a split JS bundle from the filesystem. See {@link + * JSBundleLoader#createCachedSplitBundleFromNetworkLoader(String, String)}. + */ + void loadSplitBundleFromFile(String fileName, String sourceURL); + /** * This API is used in situations where the JS bundle is being executed not on the device, but on * a host machine. In that case, we must provide two source URLs for the JS bundle: One to be used diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java b/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java index 7f8fe6d409902d..00f52a84c09a0b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/DebugServerException.java @@ -40,16 +40,25 @@ public static DebugServerException makeGeneric( return new DebugServerException(reason + message + extra, t); } + private final String mOriginalMessage; + private DebugServerException(String description, String fileName, int lineNumber, int column) { super(description + "\n at " + fileName + ":" + lineNumber + ":" + column); + mOriginalMessage = description; } public DebugServerException(String description) { super(description); + mOriginalMessage = description; } public DebugServerException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); + mOriginalMessage = detailMessage; + } + + public String getOriginalMessage() { + return mOriginalMessage; } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java index 17235dd9d46356..f2dae7301a3422 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java @@ -107,10 +107,7 @@ public void downloadBundleFromURL( Request.Builder requestBuilder) { final Request request = - requestBuilder - .url(formatBundleUrl(bundleURL)) - .addHeader("Accept", "multipart/mixed") - .build(); + requestBuilder.url(bundleURL).addHeader("Accept", "multipart/mixed").build(); mDownloadBundleFromURLCall = Assertions.assertNotNull(mClient.newCall(request)); mDownloadBundleFromURLCall.enqueue( new Callback() { @@ -165,10 +162,6 @@ public void onResponse(Call call, final Response response) throws IOException { }); } - private String formatBundleUrl(String bundleURL) { - return bundleURL; - } - private void processMultipartResponse( final String url, final Response response, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index b539282d635215..83e7ca48cb1558 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -419,15 +419,26 @@ private boolean getJSMinifyMode() { } private String createBundleURL(String mainModuleID, BundleType type, String host) { + return createBundleURL(mainModuleID, type, host, false, true); + } + + private String createSplitBundleURL(String mainModuleID, String host) { + return createBundleURL(mainModuleID, BundleType.BUNDLE, host, true, false); + } + + private String createBundleURL( + String mainModuleID, BundleType type, String host, boolean modulesOnly, boolean runModule) { return String.format( Locale.US, - "http://%s/%s.%s?platform=android&dev=%s&minify=%s&app=%s", + "http://%s/%s.%s?platform=android&dev=%s&minify=%s&app=%s&modulesOnly=%s&runModule=%s", host, mainModuleID, type.typeID(), getDevMode(), getJSMinifyMode(), - mPackageName); + mPackageName, + modulesOnly ? "true" : "false", + runModule ? "true" : "false"); } private String createBundleURL(String mainModuleID, BundleType type) { @@ -454,6 +465,11 @@ public String getDevServerBundleURL(final String jsModulePath) { mSettings.getPackagerConnectionSettings().getDebugServerHost()); } + public String getDevServerSplitBundleURL(String jsModulePath) { + return createSplitBundleURL( + jsModulePath, mSettings.getPackagerConnectionSettings().getDebugServerHost()); + } + public void isPackagerRunning(final PackagerStatusCallback callback) { String statusURL = createPackagerStatusURL(mSettings.getPackagerConnectionSettings().getDebugServerHost()); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index e93b645cb83bd5..0885f35ff94419 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -23,12 +23,14 @@ import android.widget.EditText; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import com.facebook.common.logging.FLog; import com.facebook.debug.holder.PrinterHolder; import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; +import com.facebook.react.bridge.JSBundleLoader; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactContext; @@ -43,6 +45,7 @@ import com.facebook.react.devsupport.DevServerHelper.PackagerCommandListener; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevOptionHandler; +import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.devsupport.interfaces.ErrorCustomizer; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; @@ -70,6 +73,7 @@ public abstract class DevSupportManagerBase private static final int JAVA_ERROR_COOKIE = -1; private static final int JSEXCEPTION_ERROR_COOKIE = -1; private static final String JS_BUNDLE_FILE_NAME = "ReactNativeDevBundle.js"; + private static final String JS_SPLIT_BUNDLES_DIR_NAME = "dev_js_split_bundles"; private static final String RELOAD_APP_ACTION_SUFFIX = ".RELOAD_APP_ACTION"; private static final String FLIPPER_DEBUGGER_URL = "flipper://null/Hermesdebuggerrn?device=React%20Native"; @@ -97,6 +101,7 @@ private enum ErrorType { private final ReactInstanceManagerDevHelper mReactInstanceManagerHelper; private final @Nullable String mJSAppBundleName; private final File mJSBundleTempFile; + private final File mJSSplitBundlesDir; private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler; private final DevLoadingViewController mDevLoadingViewController; @@ -104,6 +109,7 @@ private enum ErrorType { private @Nullable AlertDialog mDevOptionsDialog; private @Nullable DebugOverlayController mDebugOverlayController; private boolean mDevLoadingViewVisible = false; + private int mPendingJSSplitBundleRequests = 0; private @Nullable ReactContext mCurrentContext; private DevInternalSettings mDevSettings; private boolean mIsReceiverRegistered = false; @@ -113,7 +119,6 @@ private enum ErrorType { private @Nullable String mLastErrorTitle; private @Nullable StackFrame[] mLastErrorStack; private int mLastErrorCookie = 0; - private @Nullable ErrorType mLastErrorType; private @Nullable DevBundleDownloadListener mBundleDownloadListener; private @Nullable List mErrorCustomizers; private @Nullable PackagerLocationCustomizer mPackagerLocationCustomizer; @@ -204,6 +209,9 @@ public void onReceive(Context context, Intent intent) { // TODO(6418010): Fix readers-writers problem in debug reload from HTTP server mJSBundleTempFile = new File(applicationContext.getFilesDir(), JS_BUNDLE_FILE_NAME); + mJSSplitBundlesDir = + mApplicationContext.getDir(JS_SPLIT_BUNDLES_DIR_NAME, Context.MODE_PRIVATE); + mDefaultNativeModuleCallExceptionHandler = new DefaultNativeModuleCallExceptionHandler(); setDevSupportEnabled(enableOnCreate); @@ -776,26 +784,6 @@ public boolean hasUpToDateJSBundleInCache() { return false; } - /** - * @return {@code true} if JS bundle {@param bundleAssetName} exists, in that case {@link - * com.facebook.react.ReactInstanceManager} should use that file from assets instead of - * downloading bundle from dev server - */ - public boolean hasBundleInAssets(String bundleAssetName) { - try { - String[] assets = mApplicationContext.getAssets().list(""); - for (int i = 0; i < assets.length; i++) { - if (assets[i].equals(bundleAssetName)) { - return true; - } - } - } catch (IOException e) { - // Ignore this error and just fallback to downloading JS from devserver - FLog.e(ReactConstants.TAG, "Error while loading assets list"); - } - return false; - } - private void resetCurrentContext(@Nullable ReactContext reactContext) { if (mCurrentContext == reactContext) { // new context is the same as the old one - do nothing @@ -875,6 +863,82 @@ public void handleReloadJS() { } } + @Override + public void loadSplitBundleFromServer(String bundlePath, final DevSplitBundleCallback callback) { + final String bundleUrl = mDevServerHelper.getDevServerSplitBundleURL(bundlePath); + // The bundle path may contain the '/' character, which is not allowed in file names. + final File bundleFile = + new File(mJSSplitBundlesDir, bundlePath.replaceAll("/", "_") + ".jsbundle"); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + showSplitBundleDevLoadingView(bundleUrl); + mDevServerHelper.downloadBundleFromURL( + new DevBundleDownloadListener() { + @Override + public void onSuccess() { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + hideSplitBundleDevLoadingView(); + } + }); + + @Nullable ReactContext context = mCurrentContext; + if (context == null || !context.hasActiveCatalystInstance()) { + return; + } + + JSBundleLoader.createCachedSplitBundleFromNetworkLoader( + bundleUrl, bundleFile.getAbsolutePath()) + .loadScript(context.getCatalystInstance()); + context.getJSModule(HMRClient.class).registerBundle(bundleUrl); + + callback.onSuccess(); + } + + @Override + public void onProgress( + @Nullable String status, @Nullable Integer done, @Nullable Integer total) { + mDevLoadingViewController.updateProgress(status, done, total); + } + + @Override + public void onFailure(Exception cause) { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + hideSplitBundleDevLoadingView(); + } + }); + callback.onError(bundleUrl, cause); + } + }, + bundleFile, + bundleUrl, + null); + } + }); + } + + @UiThread + private void showSplitBundleDevLoadingView(String bundleUrl) { + mDevLoadingViewController.showForUrl(bundleUrl); + mDevLoadingViewVisible = true; + mPendingJSSplitBundleRequests++; + } + + @UiThread + private void hideSplitBundleDevLoadingView() { + if (--mPendingJSSplitBundleRequests == 0) { + mDevLoadingViewController.hide(); + mDevLoadingViewVisible = false; + } + } + @Override public void isPackagerRunning(final PackagerStatusCallback callback) { Runnable checkPackagerRunning = @@ -988,7 +1052,6 @@ private void updateLastErrorInfo( mLastErrorTitle = message; mLastErrorStack = stack; mLastErrorCookie = errorCookie; - mLastErrorType = errorType; } private void reloadJSInProxyMode() { @@ -1107,19 +1170,7 @@ public void onFailure(final Exception cause) { mBundleDownloadListener.onFailure(cause); } FLog.e(ReactConstants.TAG, "Unable to download JS bundle", cause); - UiThreadUtil.runOnUiThread( - new Runnable() { - @Override - public void run() { - if (cause instanceof DebugServerException) { - DebugServerException debugServerException = (DebugServerException) cause; - showNewJavaError(debugServerException.getMessage(), cause); - } else { - showNewJavaError( - mApplicationContext.getString(R.string.catalyst_reload_error), cause); - } - } - }); + reportBundleLoadingFailure(cause); } }, mJSBundleTempFile, @@ -1127,6 +1178,22 @@ public void run() { bundleInfo); } + private void reportBundleLoadingFailure(final Exception cause) { + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (cause instanceof DebugServerException) { + DebugServerException debugServerException = (DebugServerException) cause; + showNewJavaError(debugServerException.getMessage(), cause); + } else { + showNewJavaError( + mApplicationContext.getString(R.string.catalyst_reload_error), cause); + } + } + }); + } + @Override public void startInspector() { if (mIsDevSupportEnabled) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java index df0ca843ba22a2..298de68ce389f4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.devsupport.interfaces.DevOptionHandler; +import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.devsupport.interfaces.ErrorCustomizer; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; @@ -129,6 +130,9 @@ public void handleReloadJS() {} @Override public void reloadJSFromServer(String bundleURL) {} + @Override + public void loadSplitBundleFromServer(String bundlePath, DevSplitBundleCallback callback) {} + @Override public void isPackagerRunning(final PackagerStatusCallback callback) {} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java index 4837c0d06e5e0c..97469c319d92ba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/HMRClient.java @@ -29,6 +29,9 @@ public interface HMRClient extends JavaScriptModule { */ void setup(String platform, String bundleEntry, String host, int port, boolean isEnabled); + /** Registers an additional JS bundle with HMRClient. */ + void registerBundle(String bundleUrl); + /** * Sets up a connection to the packager when called the first time. Ensures code updates received * from the packager are applied. diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSplitBundleCallback.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSplitBundleCallback.java new file mode 100644 index 00000000000000..f9672ccf13efa0 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSplitBundleCallback.java @@ -0,0 +1,16 @@ +/* + * 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. + */ + +package com.facebook.react.devsupport.interfaces; + +/** Callback class for loading split JS bundles from Metro in development. */ +public interface DevSplitBundleCallback { + /** Called when the split JS bundle has been downloaded and evaluated. */ + void onSuccess(); + /** Called when the split JS bundle failed to load. */ + void onError(String url, Throwable cause); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java index 10684ce3d4f9b5..92181077ab4c4a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java @@ -69,6 +69,8 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler { void reloadJSFromServer(final String bundleURL); + void loadSplitBundleFromServer(String bundlePath, DevSplitBundleCallback callback); + void isPackagerRunning(PackagerStatusCallback callback); void setHotModuleReplacementEnabled(final boolean isHotModuleReplacementEnabled); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/BUCK new file mode 100644 index 00000000000000..64fb396b12a083 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/BUCK @@ -0,0 +1,21 @@ +load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") + +rn_android_library( + name = "bundleloader", + srcs = glob(["*.java"]), + is_androidx = True, + labels = ["supermodule:xplat/default/public.react_native.infra"], + provided_deps = [ + react_native_dep("third-party/android/androidx:annotation"), + ], + visibility = [ + "PUBLIC", + ], + deps = [ + react_native_target("java/com/facebook/react/bridge:bridge"), + react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/devsupport:interfaces"), + react_native_target("java/com/facebook/react/module/annotations:annotations"), + ], + exported_deps = [react_native_target("java/com/facebook/fbreact/specs:FBReactNativeSpec")], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/NativeDevSplitBundleLoaderModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/NativeDevSplitBundleLoaderModule.java new file mode 100644 index 00000000000000..0a491f172a6925 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/bundleloader/NativeDevSplitBundleLoaderModule.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package com.facebook.react.modules.bundleloader; + +import androidx.annotation.NonNull; +import com.facebook.fbreact.specs.NativeDevSplitBundleLoaderSpec; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.common.DebugServerException; +import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.module.annotations.ReactModule; + +@ReactModule(name = NativeDevSplitBundleLoaderModule.NAME) +public class NativeDevSplitBundleLoaderModule extends NativeDevSplitBundleLoaderSpec { + public static final String NAME = "DevSplitBundleLoader"; + private static final String REJECTION_CODE = "E_BUNDLE_LOAD_ERROR"; + + private final DevSupportManager mDevSupportManager; + + public NativeDevSplitBundleLoaderModule( + ReactApplicationContext reactContext, DevSupportManager devSupportManager) { + super(reactContext); + mDevSupportManager = devSupportManager; + } + + @Override + public void loadBundle(String bundlePath, final Promise promise) { + mDevSupportManager.loadSplitBundleFromServer( + bundlePath, + new DevSplitBundleCallback() { + @Override + public void onSuccess() { + promise.resolve(true); + } + + @Override + public void onError(String url, Throwable cause) { + String message = + cause instanceof DebugServerException + ? ((DebugServerException) cause).getOriginalMessage() + : "Unknown error fetching '" + url + "'."; + promise.reject(REJECTION_CODE, message, cause); + } + }); + } + + @NonNull + @Override + public String getName() { + return NAME; + } +}