Tuesday, January 17, 2023
HomeWeb DevelopmentUtilizing Re.pack for large-scale React Native initiatives

Utilizing Re.pack for large-scale React Native initiatives


Re.pack, a webpack-based toolkit to construct your React Native utility with full help of the webpack ecosystem, has been round for some time and is fixing an enormous drawback for large-scale apps by bringing code splitting, module federation, and multi-bundle help to React Native.

On this article, we are going to check out what Re.pack has to supply for React Native apps and likewise conduct a small experiment of its options.

Leap forward:

Bundling JavaScript and React Native

Bundlers function a cornerstone know-how for all JavaScript apps. So is the case for React Native apps; as we developed extra refined apps in React Native, concepts started to emerge about effectively bundling our tons of JavaScript.

Fb has not too long ago invested within the Metro bundler and achieved vital outcomes with the neighborhood’s assist. However for large-scale apps serving tens of modules with numerous options, there’s a want for a extra dynamic and feature-rich bundler. The place else is there to look when you have webpack? 🙂

Re.pack brings all the nice of webpack that the neighborhood has developed throughout the last decade to React Native, which implies we are able to now leverage fashionable concepts, like module federation and dynamic code splitting, to our React Native apps.

Re.pack options

Higher code splitting

Code splitting has been the discuss of the city because the emergence of SPAs once we began transport MBs of JavaScript to render even the tiniest of UI. For React Native apps, it’s a bit of totally different; right here, it’s about loading solely the required JS on the primary thread to make our apps sooner in addition and optimized in reminiscence consumption. Although React Suspense code splitting is now quite simple for React apps, it’s nonetheless a problem for RN apps.

Re.pack permits the creation of suspense-based chunks of your utility, with every chunk solely loaded on the primary thread when required. We are going to discover an in depth implementation of this characteristic later on this article.

Module federation

A number of patterns have not too long ago emerged in frontend engineering, and probably the most distinguished is module federation — right here is how webpack defines the idea:

A number of separate builds ought to kind a single utility. These separate builds act like containers and might expose and eat code between builds, making a unified utility.

This allows intensive code re-usability throughout apps with out dealing with the effort of package deal administration. Re.pack permits RN apps to make use of module federation with native and distant chunks, making it tremendous simple to ship bug fixes and upgrades to a number of apps concurrently.

Plugin ecosystem

Over the previous decade, the neighborhood has developed tons of plugins for webpack, bringing some actually cool concepts to the bundling area. Enabling webpack for React Native bundling means we are able to leverage these plugins and create extra optimized and environment friendly JS bundles.

Let’s create a small demo app to check the options we mentioned above.

Constructing our demo React Native app

Initializing our React Native app

We are going to start by organising a React Native app through CLI with the TypeScript template:

npx react-native init RepackDemo --template react-native-template-typescript

Including dependencies

As soon as the undertaking is up and working, add Re.pack-related dependencies to our app utilizing both of the next instructions:

npm i -D @callstack/repack babel-loader swc-loader terser-webpack-plugin webpack @sorts/react

Or:

yarn add -D @callstack/repack babel-loader swc-loader terser-webpack-plugin webpack @sorts/react

Configuring instructions

Utilizing Re.pack means we’re transferring away from the default metro bundler of React Native, therefore the default react-native begin command shouldn’t be helpful anymore. As a substitute we are going to use react-native webpack-start to run our JS server.

To make this command obtainable in React Native, we have now to replace our react-native.config.js with the next:

module.exports = {
 instructions: require('@callstack/repack/instructions'),
};

It will hook Re.pack instructions react-native webpack-start and react-native webpack-bundle with React Native seamlessly. Now, to make our improvement sooner, we are going to replace our begin script in package deal.json to react-native webpack-start:

{
  “scripts”: {
    “begin”: “react-native webpack-start”
  }
}

Configuring webpack

To make use of our new begin command, we’d like a webpack config file. For demo functions, we’re utilizing off-the-shelf configs. You may at all times dive deeper and replace configs in keeping with your necessities. You may copy these configs from our demo undertaking, and place it in webpack.config.mjs on the root of the undertaking.

Configure iOS Native

For launch construct, our JavaScript is bundled utilizing Xcode’s construct part duties. To allow Re.pack there, we are going to replace the construct part. Add the next line to the Bundle React Native code and pictures job:

export BUNDLE_COMMAND=webpack-bundle

The Bundle React Native Code And Images Task In Xcode

Configure Android Native

Identical to in an iOS app, we are going to replace buildCommand to construct a command for an Android app.

Replace the next traces in app/construct.gradle in our undertaking:

undertaking.ext.react = [
    enableHermes: true,  // clean and rebuild if changing
    bundleCommand: "webpack-bundle",
]

🥳 We are actually prepared to begin leveraging Re.pack for JavaScript bundling in React Native.

How code splitting works with Re.pack

Let’s begin exploring how code splitting works with Re.pack. As we beforehand mentioned, Re.pack provides each native and distant chunks. To reiterate, native chunks are shipped with the app whereas distant chunks are served over the community and aren’t a part of the app’s construct. For our instance, we are going to create each native and distant chunks to get a greater understanding.

Here’s what our app’s repo seems like:

Apps' Repo

We created two modules: LocalModule and Distant Module.

Right here is our module wrapper to encapsulate dynamic loading:

/**
 * path: src/modules/RemoteModule/index.tsx
 * description:
 *    It is a distant module that's loaded asynchronously.
 *    This file encapsulate React.lazy and React.Suspense over the distant module.
 */
import React from 'react';
import { Textual content } from '../../parts/Textual content';
const Part = React.lazy(() => import(/* webpackChunkName: "remoteModule" */ './RemoteModule'));
export default () => (
  <React.Suspense fallback={<Textual content>Loading Distant Module...</Textual content>}>
    <Part />
  </React.Suspense>
);

Here’s what the module’s code seems like:

/**
z * path: src/modules/RemoteModule/RemoteModule.tsx
 * description:
 *   It is a distant module that's loaded asynchronously.
 *   This file is the precise module that's loaded asynchronously.
 */
import React from 'react';
import { useColorScheme, View } from 'react-native';
import { Colours } from 'react-native/Libraries/NewAppScreen';
import { Part } from '../../parts/Part';
import { Textual content } from '../../parts/Textual content';
perform RemoteModule() {
  const isDarkMode = useColorScheme() === 'darkish';
  return (
    <View
      model={{
        backgroundColor: isDarkMode ? Colours.black : Colours.white,
      }}
    >
      <Part title="Distant Module">
        <Textual content>
          This module is loading dynamically for execution and isn't shipped with the app. It's a distant module.
        </Textual content>
      </Part>
      <Part title="Particulars">
        <Textual content>
          This won't be a part of app's preliminary bundle measurement. This shall be loaded in app after consuming community
          bandwidth.
        </Textual content>
      </Part>
    </View>
  );
}
export default RemoteModule;

As soon as our modules are prepared, we are going to arrange our webpack configs, app configs, and root recordsdata to allow dynamic loading of those modules.

On the app configs aspect, we are going to outline modules that we wish to load dynamically. For our instance we are going to outline the next modules:

// app.json
{
  "localChunks": ["src_modules_LocalModule_LocalModule_tsx"],
  "remoteChunks": ["src_modules_RemoteModule_RemoteModule_tsx"],
}

Right here, localChunks are the modules which are shipped with the app and remoteChunks are the modules which are loaded dynamically. These modules are handed to webpack configs to allow dynamic loading of those modules.

On the webpack configs aspect we are going to outline entry factors for our app. For our instance we are going to outline the next entry factors:

// webpack.config.mjs#222
new Repack.RepackPlugin({
  ...
  extraChunks: [
    {
      include: appJson.localChunks,
      type: 'local',
    },
    {
      include: appJson.remoteChunks,
      type: 'remote',
      outputPath: path.join('build/output', platform, 'remote'),
    },
  ],
  ...
}),

Right here we have now outlined two further chunks beside the primary bundle — one for native module and one for distant module. Now we have additionally outlined an output path for distant chunks. That is the place distant chunks shall be saved on the finish of the construct course of.

On the foundation file aspect, we are going to outline how we wish to load these modules. For our instance, let’s outline the next root file:

// index.js
import { AppRegistry, Platform } from 'react-native';
import { ScriptManager, Script } from '@callstack/repack/shopper';
import App from './src/App';
import { identify as appName, localChunks, remoteChunkUrl, remoteChunkPort } from './app.json';

/**
 * We have to set storage for the ScriptManager to allow caching. This allows us to keep away from downloading the identical script a number of occasions.
 */
import AsyncStorage from '@react-native-async-storage/async-storage';
ScriptManager.shared.setStorage(AsyncStorage);

/**
 * We have to set a resolver for the ScriptManager to allow loading scripts from the distant server.
 * The resolver is a perform that takes a scriptId and returns a promise that resolves to a script object.
 * The script object has the next form:
 */
ScriptManager.shared.addResolver(async (scriptId) => {
  // For improvement we wish to load scripts from the dev server.
  if (__DEV__) {
    return {
      url: Script.getDevServerURL(scriptId),
      cache: false,
    };
  }

  // For manufacturing we wish to load native chunks from from the file system.
  if (localChunks.contains(scriptId)) {
    return {
      url: Script.getFileSystemURL(scriptId),
    };
  }

  /**
   * For manufacturing we wish to load distant chunks from the distant server.
   *
   * Now we have create a small http server that serves the distant chunks.
   * The server is began by the `begin:distant` script. It serves the chunks from the `construct/output` listing.
   * For customizing server see `./serve-remote-bundles.js`
   */
  const scriptUrl = Platform.choose({
    ios: `http://localhost:${remoteChunkPort}/construct/output/ios/distant/${scriptId}`,
    android: `${remoteChunkUrl}:${remoteChunkPort}/construct/output/android/distant/${scriptId}`,
  });

  return {
    url: Script.getRemoteURL(scriptUrl),
  };
});

/**
 * We are able to additionally add a listener to the ScriptManager to get notified concerning the loading course of. That is helpful for debugging.
 *
 * That is non-compulsory and will be eliminated.
 */
ScriptManager.shared.on('resolving', (...args) => {
  console.log('DEBUG/resolving', ...args);
});

ScriptManager.shared.on('resolved', (...args) => {
  console.log('DEBUG/resolved', ...args);
});

ScriptManager.shared.on('prefetching', (...args) => {
  console.log('DEBUG/prefetching', ...args);
});

ScriptManager.shared.on('loading', (...args) => {
  console.log('DEBUG/loading', ...args);
});

ScriptManager.shared.on('loaded', (...args) => {
  console.log('DEBUG/loaded', ...args);
});

ScriptManager.shared.on('error', (...args) => {
  console.log('DEBUG/error', ...args);
});

/**
 * We have to register the foundation part of the app with the AppRegistry.
 * Identical to within the default React Native setup.
 */
AppRegistry.registerComponent(appName, () => App);

This makes our app able to load distant modules. We are able to now run our app and see the outcomes. As a result of modules are loaded from the dev server in debug mode, it’s not a lot totally different from the default setup. However in manufacturing mode, we are able to see that distant modules are created beside the primary bundle and are loaded dynamically.

For a greater understanding, we created a launch APK and positioned it below the APK evaluation instrument in Android Studio. We are able to see that the native module shouldn’t be a part of the primary bundle whereas the distant module is nowhere contained in the APK; quite, it’s created within the construct/output/android/distant listing in our app’s repo:

Bundle Files Created Under Assets Directory

Remote Bundles Are Generated In The Build Folder

We began bundle serving the HTTP server for testing functions and examined our app in manufacturing mode. We are able to see that the distant module is loaded from the HTTP server:

WhatsApp Video 2022-12-26 at 3.27.11 PM.mp4

Dropbox is a free service that permits you to carry your photographs, docs, and movies wherever and share them simply. By no means electronic mail your self a file once more!


Extra nice articles from LogRocket:


A number of entry factors

One of many fundamental benefits of webpack is that it permits us to create a number of entry factors for our app. That is helpful for large-scale and Brownfield apps the place we wish to break up our app into a number of bundles.

Particularly for Brownfield apps the place React Native is powering sure options, every characteristic will be handled as a separate bundle with sure native dependencies shared throughout them. This part will present how we are able to use Re.pack to create a number of entry factors for our app.

In our app, we have now created a smaller and less complicated entry level, Bitsy; it’s loaded from a distinct entry level, /bitsy.js.

We up to date the webpack config as follows:

    // webpack.config.js#L70
    entry: [
      ...
      path.join(dirname, 'bitsy.js'),
      ...
    ]

To launch Bitsy from the native aspect, you’ll be able to replace MainActivity.java as follows:

    // MainActivity.java#L15
    @Override
    protected String getMainComponentName() {
        return "RepackBitsy";
    }

Or AppDelegate.m as follows:

    // AppDelegate.m#L47
    UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"RepackBitsy", initProps);

Operating the app will launch the Bitsy module as an alternative of the RepackDemo app.

Conclusion

webpack has been round in frontend improvement because the begin of the previous decade. Now we have seen many plugins developed round it to unravel complicated issues in bundling and optimization areas for large-scale apps. Bringing all that energy to React Native will assist us simply preserve large-scale cell apps. This could additionally assist RN apps change into safer and performant.

Try this pattern undertaking with Re.pack. You might need to tweak the webpack configs in your initiatives to make sure you get the optimum outcomes. Please consult with the feedback within the configs file and webpack docs for particulars on every choice.

LogRocket: Immediately recreate points in your React Native apps.

LogRocket is a React Native monitoring answer that helps you reproduce points immediately, prioritize bugs, and perceive efficiency in your React Native apps.

LogRocket additionally helps you improve conversion charges and product utilization by exhibiting you precisely how customers are interacting together with your app. LogRocket’s product analytics options floor the explanation why customers do not full a specific move or do not undertake a brand new characteristic.

Begin proactively monitoring your React Native apps — .

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments