ivanmoskalev / react-native-compressed-jsbundle

Ship React Native .jsbundles compressed by Brotli algorithm.
https://www.npmjs.com/package/react-native-compressed-jsbundle
MIT License
43 stars 2 forks source link

Library is running on Full App but for AppClip returns exception and crash the app #15

Closed dawidzawada closed 2 years ago

dawidzawada commented 2 years ago

Hi, I was able to run compression library for the full App but the App Clip version(Release mode) app build successfully but crashes after a second and Xcode points to an error:

com.facebook.react.JavaScript (10): "Could not get BatchedBridge, make sure your bundle is packaged correctly"

Screenshot 2022-05-19 at 16 07 30

Also Clip's AppDelegate.m file shows warning at self keyword:

Screenshot 2022-05-19 at 16 08 47

My build phase config for RN Code & Images looks like this:

set -e

export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh index.clip.js
../node_modules/react-native-compressed-jsbundle/tool/compress-xcode.sh

And of course Im importing library in full app and clip AppDelegates: #import <react-native-compressed-jsbundle/IMOCompressedBundleLoader.h>

Also Xcode logs some errors in console:

2022-05-19 16:20:27.108080+0200 <NAME OF THE APP>Clip[30397:1937072] [native] Running application <NAME OF THE APP> ({
    initialProps =     {
    };
    rootTag = 1;
})
2022-05-19 16:20:27.446101+0200 <NAME OF THE APP>Clip[30397:1937250] [native] Could not get BatchedBridge, make sure your bundle is packaged correctly
2022-05-19 16:20:27.454660+0200 <NAME OF THE APP>Clip[30397:1937250] *** Terminating app due to uncaught exception 'RCTFatalException: Could not get BatchedBridge, make sure your bundle is packaged correctly', reason: 'Could not get BatchedBridge, make sure your bundle is packaged correctly'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010c106ba4 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x000000010a18cbe7 objc_exception_throw + 48
    2   <NAME OF THE APP>Clip                      0x0000000100d8e618 RCTFormatError + 0
    3   <NAME OF THE APP>Clip                      0x0000000100da7db7 -[RCTCxxBridge handleError:] + 530
    4   <NAME OF THE APP>Clip                      0x0000000100da606d __34-[RCTCxxBridge _initializeBridge:]_block_invoke + 65
    5   <NAME OF THE APP>Clip                      0x0000000100dbb3ca _ZN8facebook5react16RCTMessageThread7tryFuncERKNSt3__18functionIFvvEEE + 44
    6   <NAME OF THE APP>Clip                      0x0000000100dbb1e6 ___ZN8facebook5react16RCTMessageThread8runAsyncENSt3__18functionIFvvEEE_block_invoke + 33
    7   CoreFoundation                      0x000000010c074bab __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    8   CoreFoundation                      0x000000010c073fb1 __CFRunLoopDoBlocks + 443
    9   CoreFoundation                      0x000000010c06e952 __CFRunLoopRun + 892
    10  CoreFoundation                      0x000000010c06e0f3 CFRunLoopRunSpecific + 567
    11  <NAME OF THE APP>Clip                      0x0000000100da36fd +[RCTCxxBridge runRunLoop] + 274
    12  Foundation                          0x000000010aa19550 __NSThread__start__ + 1025
    13  libsystem_pthread.dylib             0x000000010da644e1 _pthread_start + 125
    14  libsystem_pthread.dylib             0x000000010da5ff6b thread_start + 15
)
libc++abi: terminating with uncaught exception of type NSException
dyld4 config: DYLD_ROOT_PATH=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot DYLD_LIBRARY_PATH=/Users/dawidzawada/Library/Developer/Xcode/DerivedData/<NAME OF THE APP>-agedixhwcxfkfkgfuzfzffkolhtk/Build/Products/Release-iphonesimulator:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libBacktraceRecording.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libMainThreadChecker.dylib:/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.2.simruntime/Contents/Resources/RuntimeRoot/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib DYLD_FRAMEWORK_PATH=/Users/dawidzawada/Library/Developer/Xcode/DerivedData/<NAME OF THE APP>-agedixhwcxfkfkgfuzfzffkolhtk/Build/Products/Release-iphonesimulator
terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'RCTFatalException: Could not get BatchedBridge, make sure your bundle is packaged correctly', reason: 'Could not get BatchedBridge, make sure your bundle is packaged correctly'
CoreSimulator 802.6 - Device: iPhone 12 (266C3B4B-2B02-4F0A-B7EE-DD5047F9AC96) - Runtime: iOS 15.2 (19C51) - DeviceType: iPhone 12
(lldb)

What might be the issue of the AppClip? Full app is working perfectly and library reduces size of jsbundle.

ivanmoskalev commented 2 years ago

Hi @dawidzawada!

Is this method (- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge) implemented in your App Clip's AppDelegate?

If it is not implemented, then React Native doesn't know the location of the JS Bundle.

dawidzawada commented 2 years ago

I've added the - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge method as well as:

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
....

code in didFinishLaunchingWithOptions I can see the Decompression Time log in Xcode console but the same error still occurs, I'm not experienced in React Native configuration but I'm suspecting that is related to my ViewController - app is builded successfully but error occurs at runtime - i can see just white background. Here's my ViewController code:

#import "ViewController.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

@implementation ViewController

- (void)loadView {
  #if DEBUG
  // When debugging we load the js bundle from the Metro Bundler
  // running on your development machine. "index" is the name of the
  // js file used as an entry point (don't include the extension).
  NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings]
jsBundleURLForBundleRoot:@"index.clip" fallbackResource:nil];
  #else
  // For release builds we add the js bundle to the build.
  // By default the js bundler will create a file called
  // "main.jsbundle" for us.
  NSURL *jsCodeLocation = [[NSBundle mainBundle]
URLForResource:@"main" withExtension:@"jsbundle"];
  #endif

  // moduleName corresponds to the appName used for the
  // app entry point in "index.js"
  RCTRootView *rootView = [[RCTRootView alloc]
initWithBundleURL:jsCodeLocation moduleName:@“<NAME OF THE APP>”
initialProperties:nil launchOptions:nil];
  // Default to a white background.
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f
green:1.0f blue:1.0f alpha:1];
  self.view = rootView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}
@end

And my AppClip's AppDelegate:

#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <react-native-compressed-jsbundle/IMOCompressedBundleLoader.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
       RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
       RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                        moduleName:@“<NAME OF THE APP>”
                                                 initialProperties:nil];

       if (@available(iOS 13.0, *)) {
           rootView.backgroundColor = [UIColor systemBackgroundColor];
       } else {
           rootView.backgroundColor = [UIColor whiteColor];
       }

       self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
       UIViewController *rootViewController = [UIViewController new];
       rootViewController.view = rootView;
       self.window.rootViewController = rootViewController;
       [self.window makeKeyAndVisible];
       return YES;
}

#pragma mark - UISceneSession lifecycle

- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}

- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}

- (void)loadSourceForBridge:(RCTBridge *)bridge onProgress:(RCTSourceLoadProgressBlock)onProgress onComplete:(RCTSourceLoadBlock)loadCallback {
  [IMOCompressedBundleLoader loadSourceForBridge:bridge bridgeDelegate:self onProgress:onProgress onComplete:loadCallback];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end

I'm guessing that loadView from ViewController might cause some troubles with - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge. Is that the issue?

ivanmoskalev commented 2 years ago

Looking at the code, I don't see your custom ViewController class being used anywhere at all. So it is unlikely that it can cause any problems.

If your Main App works, and the App Clip doesn't, you can try to look for differences between the two. It is clearly some kind of misconfiguration of the Clip.

You can post your main app's App Delegate here, so we can look together.

dawidzawada commented 2 years ago

I've found the issue, both AppDelegate were almost identical but I've noticed some differences between Info.plist files - mine Clip version has these tags:

    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict>
            <key>UIWindowSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneConfigurationName</key>
                    <string>Default Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>SceneDelegate</string>
                    <key>UISceneStoryboardFile</key>
                    <string>Main</string>
                </dict>
            </array>
        </dict>
    </dict>

And full app does not - I've removed them from the app clip and app seems to be working and size reduced to exact 10MB after some additional other code clean-up, still some work ahead but this package helped a lot! Thanks for your work and support!

ivanmoskalev commented 2 years ago

Glad the issue is resolved! Feel free to contact me if the lib misbehaves somehow.

By the way, can you please send me the Decompression Time log lines from your console? I'm trying to find out how the compression algorithm works on real-world examples.

And also, stay tuned for the updates — I've found that using Apple's Compression framework improves the metrics, and I might provide it as an additional option instead of Brotli.

dawidzawada commented 2 years ago

Sure, here's my compression logs: App Clip:

[react-native-compressed-jsbundle] Decompression time overhead: 0.0186236 s
Original size: 2684.33 KB
Compressed size: 449.661 KB

Full App:

[react-native-compressed-jsbundle] Decompression time overhead: 0.0186168 s
Original size: 3140.17 KB
Compressed size: 525.125 KB

In my opinion these are pretty good results - especially for AppClip - after adding stripe-react-native package AppClip size was around 12/13 MB, now, after compression and removing a few pods it's 10.3 MB - that lead me closer to achieving planned functionalities. So this is definitely useful package for React Native AppClips. Out of curiosity, how much do you expect compression to gain by using Apple's Compression?

zeabdelkhalek commented 2 years ago

@dawidzawada @ivanmoskalev I'm running exactly on the same issue but I can't remove the code you mentioned from Info.plist file because I'm using SceneDelegate for URL handling, any advices?

dawidzawada commented 2 years ago

@dawidzawada @ivanmoskalev I'm running exactly on the same issue but I can't remove the code you mentioned from Info.plist file because I'm using SceneDelegate for URL handling, any advices?

Not really, I've removed SceneDelegate to test how much i can gain with the compression but I'm also using it for URL handling, issue should be reopened. @ivanmoskalev

ivanmoskalev commented 2 years ago

I don't see how this is an issue of this library. Please upload a minimal reproducible example

zeabdelkhalek commented 2 years ago

@ivanmoskalev Here is a minimal reproducible example: https://github.com/zeabdelkhalek/react-native-compressed-jsbundle-appclip-example To reproduce the issue just clone the repository, npm & pod install, run the app clip in debug mode, it will work fine, switch to release and it will crash with the error mentioned in this issue, if you remove the compress script from the build phase it will also work fine.

zeabdelkhalek commented 2 years ago

I resolved my issue by removing completely SceneDelegate and ViewController files and moved all the logic to AppDelegate, I also created a module for passing URL from AppDelegate to react-native side

ivanmoskalev commented 2 years ago

Hi, thanks for the example. Will investigate and try to fix the lib tonight.