Search code examples
react-nativereact-native-router-flux

Lazy Load screens with React Native Router Flux


There are several screens in my React Native application.

I am using React Native 0.56 and React Native Router Flux.

Is there a way for a screen to load all of its imports when the screen is actually needed, and not when the app is launched and bundle is parsed?

Because of this the initial load time of the app is increasing.

I came across RAM format in the React Native Performance Section. https://facebook.github.io/react-native/docs/next/performance.html#enable-the-ram-format

How can I reduce the number of modules in loadedModuleNames??

index.android.js

import { AppRegistry } from 'react-native';
import AppContainer from './src/';

const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds
  .filter(moduleId => modules[moduleId].isInitialized)
  .map(moduleId => modules[moduleId].verboseName);
const waitingModuleNames = moduleIds
  .filter(moduleId => !modules[moduleId].isInitialized)
  .map(moduleId => modules[moduleId].verboseName);

// make sure that the modules you expect to be waiting are actually waiting
console.log(
  'loaded:',
  loadedModuleNames.length,
  'waiting:',
  waitingModuleNames.length
);

// grab this text blob, and put it in a file named packager/modulePaths.js
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);

AppRegistry.registerComponent('Roots', () => AppContainer);

navigation/index.js

/**
 * App Navigation
 *
 */
import React from 'react';
import { Actions, Scene, ActionConst, Stack } from 'react-native-router-flux';

// Consts and Libs
import { AppConfig } from '@constants/';

// Components

// Scenes
import Placeholder from '@components/general/placeholder/index';
import ExperiencesScenes from '@navigation/experiences';

import AuthScenes from '@navigation/auth';
import MediateScene from '@navigation/mediate';
import HomeScene from '@navigation/home';
import SearchScene from '@navigation/search';
import NoticesScene from '@navigation/notices';
// import ShortlistedMapScene from '@navigation/shortlisted_map';

import IntroScene from '@navigation/intro';
import AssistantScene from '@navigation/assistant';
import NotificationSettingsScene from '@navigation/notification_settings';
import HighlightScene from '@navigation/highlight';
import TransportScene from '@navigation/transport';
import HelpScene from '@navigation/help';
import FaqScene from '@navigation/faq';
import DynamicListScene from '@navigation/component_list';
import BrandCoverScene from '@navigation/brand_cover';
import ListingsScene from '@navigation/listings';
import ShorlistedScene from '@navigation/shortlisted_list';
import SettingsScene from '@navigation/settings';
import OnTheWayScene from '@navigation/on_the_way';
import ItinerariesScene from '@navigation/itineraries';
import TempScene from '@navigation/temp';
import TipsScene from '@navigation/tips';
import SimpleSwipe from '@navigation/simple_swipe';
import Cities from '@navigation/cities';
// import ReferralScene from '@navigation/referral';

/* Routes ==================================================================== */
export default Actions.create(
    <Stack key={'root'}>

        {MediateScene}

        {IntroScene}

        {HomeScene}

        {AssistantScene}

        {SearchScene}

        {NoticesScene}

        {ShorlistedScene}

        {SettingsScene}

        {ListingsScene}

        {TipsScene}

        {BrandCoverScene}

        {AuthScenes}

        {ExperiencesScenes}

        {NotificationSettingsScene}

        {HighlightScene}

        {TransportScene}

        {HelpScene}

        {FaqScene}

        {DynamicListScene}

        {OnTheWayScene}

        {ItinerariesScene}

        {SimpleSwipe}

        {Cities}
    </Stack>
);

Navigation Code looks similar to this

import React from 'react';
import { Scene, ActionConst } from 'react-native-router-flux';

// Consts and Libs
import { AppConfig } from '@constants/';
import { AppStyles, AppSizes, AppColors } from '@theme/';
// Scenes
import BrandCoverScreen from '@screens/brand_cover/index';


const navbarPropsTabs = {
    ...AppConfig.navbarProps,
    hideNavBar: true,
};

/* Routes ==================================================================== */
const scenes = (
    <Scene 
        {...navbarPropsTabs}
        key={"brand_cover"} 
        component={BrandCoverScreen} >
    </Scene>
);

export default scenes;

Solution

  • That's a bit more complicated then it seems - and it's not related to react-native-router-flux only - it would require support from metro to split the JS bundler into smaller pieces and load it in the right way.

    It seems that Expo might do something like that to assets, but for the JS code I only saw something close to it hacking the react-native code in this article:

    Lazy Bundle Loading in React Native by Karan Thakkar https://medium.com/react-native-training/lazy-bundle-loading-in-react-native-5f717b65482a

    And here the GitHub repository with the code used in the article: https://github.com/karanjthakkar/RNLazyBundleLoading

    In case the article or the repository goes offline, here is the diff he also showed in the article (this is a diff between original react-native and his fork:

    diff --git a/React/Base/RCTBridge+Private.h b/React/Base/RCTBridge+Private.h
    index 1995801ffcaa..cf1609bc9614 100644
    --- a/React/Base/RCTBridge+Private.h
    +++ b/React/Base/RCTBridge+Private.h
    @@ -90,6 +90,12 @@ RCT_EXTERN void RCTVerifyAllModulesExported(NSArray *extraModules);
      */
     - (void)start;
    
    +/**
    + * Called on the child bridge to run the executor and start loading
    + * the partial bundle
    + */
    +- (void)lazyStart;
    +
     /**
      * Used by RCTModuleData to register the module for frame updates after it is
      * lazily initialized.
    diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h
    index 5222a15b5ca7..d0be4652f4f5 100644
    --- a/React/Base/RCTBridge.h
    +++ b/React/Base/RCTBridge.h
    @@ -223,4 +223,9 @@ RCT_EXTERN NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
      */
     - (BOOL)isBatchActive;
    
    +/**
    + * Load a custom bundle into an existing bridge instance
    + */
    +- (void)loadCustomBundle:(NSString *)bundleName;
    +
     @end
    diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m
    index cee9aa0463e1..c1b3ae107599 100644
    --- a/React/Base/RCTBridge.m
    +++ b/React/Base/RCTBridge.m
    @@ -319,6 +319,13 @@ - (void)setUp
       RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
     }
    
    +-(void)loadCustomBundle:(NSString *)bundleName
    +{
    +  _bundleURL = [NSBundle.mainBundle URLForResource:bundleName withExtension:@"jsbundle"];
    +
    +  [self.batchedBridge lazyStart];
    +}
    +
     - (BOOL)isLoading
     {
       return self.batchedBridge.loading;
    diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm
    index 4b7aaf375e5b..32df68ac43ec 100644
    --- a/React/CxxBridge/RCTCxxBridge.mm
    +++ b/React/CxxBridge/RCTCxxBridge.mm
    @@ -382,6 +382,29 @@ - (void)start
       RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
     }
    
    +- (void)lazyStart
    +{
    +  __weak RCTCxxBridge *weakSelf = self;
    +
    +  __block NSData *sourceCode;
    +  [self loadSource:^(NSError *error, RCTSource *source) {
    +    if (error) {
    +      [weakSelf handleError:error];
    +    }
    +
    +    sourceCode = source.data;
    +    RCTCxxBridge *strongSelf = weakSelf;
    +    if (sourceCode) {
    +      [strongSelf executeSourceCode:sourceCode sync:NO];
    +    }
    +  } onProgress:^(RCTLoadingProgress *progressData) {
    +#if RCT_DEV && __has_include("RCTDevLoadingView.h")
    +    RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
    +    [loadingView updateProgress:progressData];
    +#endif
    +  }];
    +}
    +
     - (void)loadSource:(RCTSourceLoadBlock)_onSourceLoad onProgress:(RCTSourceLoadProgressBlock)onProgress
     {
       NSNotificationCenter *center = [NSNotificationCenter defaultCenter];