All Projects → MarcusMa → react-native-async-load-bundle

MarcusMa / react-native-async-load-bundle

Licence: MIT License
This is an example project to build the common bundle file and the differential bundle file using Metro, and load the differential bundle asynchronously in app. Compare with loading the official bundle file synchronously, there was 20% ~ 25%(20 ~ 200 ms) decrease in the load time of react view by using loading the differential bundle asynchronou…

Programming Languages

objective c
16641 projects - #2 most used programming language
java
68154 projects - #9 most used programming language
javascript
184084 projects - #8 most used programming language
ruby
36898 projects - #4 most used programming language
shell
77523 projects
Starlark
911 projects

Projects that are alternatives of or similar to react-native-async-load-bundle

Mahapps.metro
A framework that allows developers to cobble together a better UI for their own WPF applications with minimal effort.
Stars: ✭ 8,023 (+31992%)
Mutual labels:  metro
ngx-text-diff
A Text Diff component for Angular
Stars: ✭ 49 (+96%)
Mutual labels:  google-diff-match
Metro-Weapp
微信小程序-上海地铁Lite
Stars: ✭ 15 (-40%)
Mutual labels:  metro
tripreader-data
“读卡识途”项目公开数据
Stars: ✭ 58 (+132%)
Mutual labels:  metro
Metro.qml
Metro UI by qml 使用纯qml构建的Metro风格控件
Stars: ✭ 16 (-36%)
Mutual labels:  metro
ChatLogger
ChatLogger is a Steam Tool based on the SteamKit2 library, designed to save your and friends messages! [Metro Theme]
Stars: ✭ 39 (+56%)
Mutual labels:  metro
taro-playground
The Taro Playground App is a cross-platform application developed using Taro, to help developers develop and debug Taro applications.
Stars: ✭ 33 (+32%)
Mutual labels:  metro
archer-svgs
异步加载svg解决方案
Stars: ✭ 23 (-8%)
Mutual labels:  load-async
AdminRunasMenu
A WPF menu driven by powershell to perform administrator functions
Stars: ✭ 26 (+4%)
Mutual labels:  metro
citylines
Citylines.co is a collaborative platform for mapping the transit systems of the world!
Stars: ✭ 53 (+112%)
Mutual labels:  metro
Retiled
An attempt at creating a "desktop" environment mainly for Linux phones and tablets that's similar in function to some parts of Microsoft's Windows Phone 8.x, primarily the Start screen, Search app, navigation bar, Action Center, and the status bar. Development is mainly being done using the PinePhone, so that'll be the main supported device.
Stars: ✭ 41 (+64%)
Mutual labels:  metro
rn-bundles-demo
📱 react-native multi bundles demo
Stars: ✭ 17 (-32%)
Mutual labels:  metro
go-bsdiff
Golang wrapper for @mendsley's bsdiff C library.
Stars: ✭ 20 (-20%)
Mutual labels:  bsdiff
Sciter Sdk
Sciter is an embeddable HTML/CSS/scripting engine
Stars: ✭ 1,690 (+6660%)
Mutual labels:  metro
minibsdiff
A miniature, portable version of bsdiff.
Stars: ✭ 115 (+360%)
Mutual labels:  bsdiff
Metro Ui Css
Impressive component library for expressive web development! Build responsive projects on the web with the first front-end component library in Metro Style. And now there are even more opportunities every day!
Stars: ✭ 6,843 (+27272%)
Mutual labels:  metro
receipt-manager-webapp
Receipt parser webapplication written in javascript and python.
Stars: ✭ 37 (+48%)
Mutual labels:  metro
react-native-react-bridge
An easy way to integrate your React (or Preact) app into React Native app with WebView.
Stars: ✭ 84 (+236%)
Mutual labels:  metro
bsdiff-cross-platform
android bsdiff and bspatch which includes java source code and native source code
Stars: ✭ 16 (-36%)
Mutual labels:  bsdiff
deltaq
Fast and portable delta encoding for .NET in 100% safe, managed code.
Stars: ✭ 26 (+4%)
Mutual labels:  bsdiff

React Native Async Load Bundle

This is an example project to build the common bundle file and the differential bundle file using Metro, and load the differential bundle asynchronously in app. Compare with loading the official bundle file synchronously, there was 20% ~ 25%(20 ~ 200 ms) decrease in the load time of react view by using loading the differential bundle asynchronously.

中文说明

📋 Contents

📋 Background

As we all known that there are three parts in a official bundle file: PolyfillModulesRequires. If you build two different official bundle files, you will find that there are many repeated content, which is close to 500K. In order to minimize the bundle file, we define a common bundle file, which only includes some basic modules(such as react and react-native). And we define a differential bundle file, which only includes your custom code.

Before React Native 0.55, we generally use google-diff-match-patch or BSDiff to build the differential bundle file, which needs the process of merging before your app loading the differential bundle file.

However, there is a new way to build the differential bundle file by using Metro.

ScreenShot

📋 Usage

  1. Modify the common.js like blew:
require("react-native");
require("react");
// Add other libs you want to add to the common bundle like this:
// require('other');
  1. Build the common bundle file with --config metro.config.common.js or use the command blew:
# For android:
npm run build_android_common_bundle
# For iOS:
npm run build_ios_common_bundle
  1. Build the differential bundle file with --config metro.config.diff.js or use the command blew:
# For android:
npm run build_android_index_diff_bundle
# For iOS:
npm run build_ios_index_diff_bundle
  1. Copy all output files to the dir of app project or use the command blew:
npm run copy_files_to_projects
  1. Run the app project by Android Studio or XCode.

NOTICE: There are two ways to start an activity with react native in android app: one as SYNC, the other as ASYNC. It is same with the official reference implementation when using SYNC. As for ASYNC, it will start a general Activity (or ViewController), which will load a common bundle file, after that it will start a custom Activity (or ViewController) using react native, which will ONLY load the differential bundle file. The load time of react view will display by log and toast.

📋 Experimental data

1. Compare the size of output file

Android File Size Size After gzip
common.android.bundle 637.0 K 175K
index.android.bundle (Original) 645.0 K 177K
diff.android.bundle (Using Metro) 8.3 K 2.5 K
diff.android.bundle (Using BSDiff) 3.9 K 3.9 K
diff.android.bundle (Using google-diff-match-patch) 11.0 K 3.0 K
iOS File Size Size After gzip
common.ios.bundle 629.0 K 173K
index.ios.bundle (Original) 637.0 K 176K
diff.ios.bundle (Using Metro) 8.3 K 2.5 K
diff.ios.bundle (Using BSDiff) 3.9 K 3.9 K
diff.ios.bundle (Using google-diff-match-patch) 11.0 K 3.0 K

You can find more information about google-diff-match-patch and BSDiff by visiting this.

2. Compare the load time of react view.

Loading Type Redmi 3 Huawei P20 iPhone 6s iPhone XS MAX
Synchronization 868.2 ms 337.8 ms 405.3 ms 109.2 ms
Asynchronization 643.4 ms 253.2 ms 300.2 ms 88.3 ms
-25.89% -25.04% -25.88% -18.68%

📋 How it works

1. Build a differential bundle file using Metro.

The key to build a differential bundle file is making the id of input module invariant during the process of bundling. It is noteworthy that the Metro provides two configuration items in metro.config.js file: createModuleIdFactory(path) and processModuleFilter(module).

By customizing createModuleIdFactory(path), we used the hash of the file as the key to allocate module id.

// See more code int metro.config.base.js
// ...
function getFindKey(path) {
  let md5 = crypto.createHash("md5");
  md5.update(path);
  let findKey = md5.digest("hex");
  return findKey;
}
// ...

In order to avoid duplication of allocation of module id, we use a local file (repo_for_module_id.json) to store the result of allocation during the process of building.

"8b055b854fd2345d343b6618c9b71f7e":
{
    "id": 5,
    "type": "common"
}

By customizing processModuleFilter(module), we compare the hash of input module with local-storage. If input module is included by common bundle file, it will be filtered and will not be written to the output bundle file.

// See more code int metro.config.base.js
// ...
buildProcessModuleFilter = function(buildConfig) {
  return moduleObj => {
    let path = moduleObj.path;
    if (!fs.existsSync(path)) {
      return true;
    }
    if (buildConfig.type == BUILD_TYPE_DIFF) {
      let findKey = getFindKey(path);
      let storeObj = moduleIdsJsonObj[findKey];
      if (storeObj != null && storeObj.type == BUILD_TYPE_COMMON) {
        return false;
      }
      return true;
    }
    return true;
  };
};
// ...

However, the polyfills is also written in the output bundle file after running Metro, we should remove those code by ourselves.

For example, we made a script file call removePolyfill.js in the dir __async_load_shell__, you can use it by run:

node ./__async_load_shell__/removePolyfill.js  {your_different_bundle_file_path}

2. Load the differential bundle file asynchronously in android.

Because the common bundle file includes all basic codes, we should make sure a good timing to load the common bundle file before loading the differential bundle file.

In the demo app, a guide activity is created to load the common bundle file, which is also used to simulate a PARENT activity of the activity using react native. Sometimes, The guide activity can also usually be displayed the entrance of your business which was builded by react native in your official app.

All related code was organized in package com.marcus.rn.async. There are some key points about the implementation:

  1. We use the ReactNativeHost to point the path of common bundle file, and call createReactContextInBackground() to initialize the context of React Native and load the common bundle file.
  2. In order to get approximate finish time of loading common bundle file, we use addReactInstanceEventListener() of ReactInstanceManager to add custom listener and monitor the event onReactContextInitialized to indicate the finish of loading common bundle file.
  3. We redefine ReactActivityDelegate class to suit the scene of loading asynchronously. which can be found by name with AsyncLoadActivityDelegate.java.
  4. Because the guide activity and the container activity of react native MUST shared the same AsyncLoadActivityDelegate object, we build a singleton class called AsyncLoadManager to provider the object.
  5. The load time of react view will be displayed by log and toast, which records time period from onCreate() of the activity to monitor the event called CONTENT_APPEARED.
  6. As for the global variable problem in javascript, we should clear the context of react native before reused it. we provides a simple way to fix the problem by rebuild the AsyncLoadActivityDelegate object, you can see the code in prepareReactNativeEnv() in AsyncLoadManager.

3. Load the differential bundle file asynchronously in iOS.

Because the common bundle file includes all basic codes, we should make sure a good timing to load the common bundle file before loading the differential bundle file.

In the demo app, a guide view-controller is created to load the common bundle file, which is also used to simulate a PARENT view-controller of the view-controller using react native. Sometimes, The guide view-controller can also usually be displayed the entrance of your business which was builded by react native in your official app.

There are some key points about the implementation:

  1. We expose the executeSourceCode in RCTBridge like this:
#import <Foundation/Foundation.h>

@interface RCTBridge (RnLoadJS)

 - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync;

@end
  1. We use the sourceURLForBridge of RCTBridgeDelegate to point the path of common bundle file, and call [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions] to initialize the context of React Native and load the common bundle file.
  2. Because the guide view-controller and the container view-controller of react native MUST shared the same RCTBridge object, we build a singleton class called MMAsyncLoadManager to manage the object.
  3. The load time of react view will be displayed by log and toast, which records time period from viewDidLoad of the view-controller to monitor the notification called RCTContentDidAppearNotification.
  4. As for the global variable problem in javascript, we should clear the context of react native before reused it. we provides a simple way to fix the problem by rebuild the RCTBridge object, you can see the code in prepareReactNativeEnv method in MMAsyncLoadManager.

📋 Contributing

PRs accepted.

📋 License

MIT © Marcus Ma.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].