This is the first personal blog

The title of this article may be a little big, but the author’s intention is to share with you the work related to React-Native during my time in Tencent and how to optimize the performance of React-Native (referred to as RN below). Perhaps this is the most comprehensive article related to RN performance optimization that you have ever seen.

Since THE author will be less involved in RN related work in the future, this article can be regarded as a summary of my rn work for so long, and I hope to inspire and help some students who are on the way.

One, foreword

Rn’s official default recommendation is to use RN as an independent app. However, considering the cost of framework migration and the skeptical attitude towards technology, people will not directly use RN to develop an independent APP. Rn is more of an internal set of separate modules/pages.

Those of you who are familiar with RN know that in order for an RN page to launch in your app you need to build in a bunch of underlying frameworks, or jsbundles of RN; Jsbundles built into RN are divided into two types, with obvious advantages and disadvantages.

  1. Jsbundle is fully built in

    • Advantages: Fast startup speed
    • Disadvantages: Each update of RN page needs to follow the app version, especially for major bugs on the page can not be timely updated;
  2. The JsBundle part is built in

    • Advantages: Built-in universal JsBundle, dynamically delivering service bundles; Pages can be dynamically updated
    • Disadvantages: loading speed compared to the built-in will be one more download time, start speed will be a little slow;

After looking at the above two kinds of JsBundle, our team (IVWeb) finally chose the mode of dynamic delivery of service resources built in jsBundle. The rest of the article will describe how we optimize Rn.

Analysis of performance optimization problems

Back to the jsBundle built-in dynamic resource delivery, let’s briefly introduce the overall idea:

  1. Rn framework startup
  2. Parse and execute the public JsBundle, download the service Jsbundle in parallel;
  3. Parse and execute the business JsBundle

Without considering the optimization of RN framework, we get the approximate time of downloading service Jsbundle to page rendering as follows:

Obviously the first screen takes a long time; After an overall analysis of the time-consuming time of RN, we mainly consider performance optimization from the following aspects:

  1. Optimized Bundle download time: Due to data privacy, only the overall optimized first screen time can be provided for the time being: 2046ms to 1176ms
  2. Optimize bundle resolution time
  3. Optimize the first screen data

Optimize bundle download time – optimize bundle volume

1. Problem analysis

After a series of statistics, we get the relationship between the bundle download time and the resource size:

Note: The download time of the bundle is proportional to the size of the resource. Let’s explore in detail how bundle volume can be optimized.

First, let’s look at the parts of an RN bundle: image resource + bundle;

A bundle consists of four parts: global variable declaration, polyfill, module definition, and require


// var declaration layer

var __BUNDLE_START_TIME__=this.nativePerformanceNow? nativePerformanceNow():Date.now(),__DEV__=false,process=this.process||{}; process.env=process.env||{}; process.env.NODE_ENV=process.env.NODE_ENV||"production";

/ / polyfill layer! (function(r){"use strict"; r.__r=o,r.__d=function(r,i,n){if(null! =e[i])return;var o={dependencyMap:n,factory:r,hasError:!1.importedAll:t,importedDefault:t,isInitialized:!1.publicModule: {exports: {}}}; e[i]=o} ...// Module definition layer
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]),o=n(r(d[2])),u=r(d[3]); t.AppRegistry.registerComponent(u.name,function(){return o.default})},0[1.2.402.403]); . __d(function(a,e,t,i,R,S,c){R.exports={name:"ReactNativeSSR".displayName:"ReactNativeSSR"}},403[]);/ / the require layer
__r(93);
__r(0);

Copy the code

Therefore, our optimization ideas are generally in the following directions:

  1. Reduce image resources and CDN image address: Image resources are not delivered together with the bundle but loaded in the CDN mode.
  2. Extract common packages: Extract common business resources/codes, such as React, React-Native, Redux, and some common business packages into the APP; The bundle described above would have no var declaration layer, no polyfill layer, and no partial modules
  3. Module Tree shaking: Remove useless code
  4. Module diff: Diff comparison is performed between the previous and previous codes, and only the diff part is delivered dynamically;

2. Reduce image resources and make the image address CDN

To reduce image resources, the essence of image address CDN is: images and other resources do not need to be built into the APP, but are loaded online, thus reducing the volume of the whole downstream packet sending

By default, we know that images packed by RN will not be hashed, so reduce image resources. The premise of CDN of image address is to hash images.

Here we recommend a hash library written by ourselves to be used in combination with Metro: react-native file-hash-plugin;

After the file is hashed, the image address needs to be changed from local address to remote address:

Image.resolveAssetSource.setCustomSourceTransformer((resolver) = > {
  // The appName of the resource, inserted at build time
  const imageAppName = resolver.asset && resolver.asset.appName;
  // Get the CDN address corresponding to app
  const bundleRoot = global.window[`${imageAppName}_cdnUrl`];
  let resolveAsset;
  if (bundleRoot) {
    resolver.jsbundleUrl = bundleRoot;
    if (Platform.OS === "android") {
      resolveAsset = resolver.drawableFolderInBundle();
    } else{ resolveAsset = resolver.scaledAssetURLNearBundle(); }}else {
    resolveAsset = resolver.defaultAsset();
  }
  return resolveAsset;
});
Copy the code

3. Remove the public bag

We can use Metro’s processModuleFilter to filter out modules that we don’t need to package into the business bundle.

const commonModules = ["react"."react-native"."redux"."Own business module"];
// A simple example
function processModuleFilter(module) {
  // Filter out modules whose path is base-modules
  if (module["path"].indexOf("base-modules") > =0) {
    return false;
  }
  // Filter out some generic modules in node_modules (already in base package)
  for (const ele of commonModules) {
    if (module["path"].indexOf("node_modules" + ele) > 0) {
      return false; }}// The rest is the application code
  return true;
}
Copy the code

3. module tree shaking

You can refer to another article I wrote before :ReactNative scheme for global minimum dependency analysis, eliminate useless modules;

4. module diff

The main idea of Module diff is to conduct Module diff for resource bundles that must be built twice; In this way, only the incremental portion of the Jsbundle is delivered. In this case, it is mainly used with offline packages. The following considerations should be taken into account:

  • How do I diff code?
  • How does the offline package code fit with the incremental part?
  • Do you load diff lazily?
  • Etc.

It’s a lot to do, but it’s useful.

Bundle download time optimization – optimizing HTTP

We all know that HTTP evolved from 1.0 to HTTP3.0; There are also emerging protocols like HTTP2.0 that support multiplexing; But HTTP2.0 does not solve TCP’s queue header blocking problem; This is determined by the TCP protocol.

So why can’t we try using the HTTP3.0, QUIC protocol to do the download?

QUIC (Quick UDP Internet Connections, Quick read) is an improved low latency Internet transport layer based on UDP proposed by Google.

QUIC has the following advantages over HTTP2.0:

  • Supports 0-RTT connections (up to 1-RTT)
  • Congestion control of user domains. Protocols can be rapidly deployed and updated
  • UDP natural no queue header blocking problem
  • Connect the migration
  • Forward error correction

For details, see: The use of QUIC on Android and iOS

Optimize bundle resolution time – parallel loading

Read my React Native and iOS communication trilogy

  1. ReactNative and iOS native communication principle analysis – initialization principle

  2. ReactNative and iOS native communication principle analysis -JS loading and implementation

  3. ReactNative and iOS native communication principle analysis – Communication chapter

Students of RN should have a general understanding of the entire process of RN resolution. Excluding the rewriting of the underlying layer of RN, the time that affects bundle resolution is as follows:

  • Jsbundle volume: We have optimized the bundle volume above
  • Common/Business package loading time;

The method we adopted is: the application starts with independent threads loading the Common package, and the bundle resources are loaded in parallel after the page is started

Optimize bundle resolution time – RN singleton/multiple instances

The singleton

The singleton pattern of RN means that app starts only one instance of RN, loads the common package once, and multiple business packages run in the same instance

  • Advantages of the singleton mode: Each service is loaded only once. If it has been loaded, it does not need to be loaded again, resulting in low memory consumption.

  • Disadvantages of the singleton pattern: One of the more important questions is how do you isolate data? If multiple business packages modify the same global variable, it is bound to cause data chaos; So we need to do data isolation;

The way we do data isolation in singleton mode is to use AST to overwrite some global variables

Many cases

The multi-instance pattern of RN refers to the fact that a business package runs in a separate INSTANCE of RN each time, creating a separate new RCTBridge each time

  • Advantage of multi-case pattern: natural data isolation

  • Disadvantages of the multi-example pattern: multiple page loads cause unnecessary memory consumption;

Seven, performance optimization – CGI parallel

The main idea here is to request the first screen data in parallel while downloading the Rn bundle

The detailed implementation depends on specific service scenarios.

Other performance optimization/RN-related techniques

There are a number of other performance tuning methods that this article does not list, but those who are interested can try them out:

  • Adding an Offline package
  • Resource preloading
  • Rn page isomorphism H5, do degradation processing
  • ReactNative do abtest
  • ReactNative does data straight out

The last

I’m sure some of you are wondering, why don’t we do low-level optimization of Rn? Our main consideration is: upgrade iteration. With our dynamic solution, rn upgrades are made less difficult, and RN officials have been working on low-level optimizations. Why not stand on the shoulders of giants?

The original address