React Native package volume optimization practice

Android Apk size and App performance have always been the main concerns of React Native Developers. Our application is written in React Native, and the Apk Size of the Android version has reached about 80MB. Therefore, it is a challenge to come up with a good solution to reduce Apk Size and improve application performance.
In this blog, we will gradually introduce the steps to reduce the apk size as well as performance improvement and application startup time reduction.

1. JSBundle and Asset

With the release of 0.59, a new version of JSC was also released. The biggest advantage of the new version 0.59 is that it can generate APKs for 64-bit architectures. However, the ensuing problem is the increase in package size. At present, the RN team is also working to delete several modules from React Native for optimization.

1. Import Cost, delete useless import

import cost as a Visual Studio plug-in, using it we can analyze the cost of each imported module. Then, we only import the required modules, not the entire library. And see the earth-shaking changes in the JS package visualizer, so we have changed almost every library that takes up a lot of space in the JS package. When managing imports, we encountered many unused import statements. Therefore, deleting these also helps us reduce the size of the JS package.

2. Delete useless third-party libraries

Check if there are extra third-party dependent packages under the package.json file, and delete them.

3. Package repetitive code

It is best practice to write code once and reuse it again and again, which can write code faster, expand the product scale, and also help prevent the size of the JS bundle from increasing.

4. Filter unused modules and resources in the Production environment

(1) Code block

__DEV__ is used to distinguish between testing and online environment. We can use it to filter unused code or resources in the Production environment. For example, there is a custom A module:

It only needs to be compiled into a jsbundle file in the test environment. We can do this

loadAModule() {
  if (__DEV__) {
	require('A.module');
  }
  return null;
}

⚠️ Note

loadAModule() {
  if (!__DEV__) {
  	return null;
  }
  require('A.module');
}

In the above code, we first determine whether the test environment returns null. Think carefully whether there is a problem with this code? After packaging the react-native bundle, you will find that the moduleId corresponding to the A module code can still be found. It also proved that the A module was broken into the jsbundle. Therefore, in React Native, you can selectively load some JS files in different environments (development or production) through __DEV__. However, the branch of require must be placed in the corresponding if (__ DEV __) {} else {} so that it can be filtered out by RN when it is packaged. When the RN is packaged, the dead code in the if / else will be deleted; but the require outside of the if / else, even if it will never be executed, will be statically parsed and entered into the bundle.

Want to learn more about the dead code of React (Native), you can read this translation: [translation] How to realize the development mode in JavaScript

(2) Image resources

We generally manage the picture resources in RN in a unified manner (recommended), and quote them in the function module. Similarly, we can use __DEV__ to realize whether to load the equivalent image resources

const inusrance = __DEV__ ? {
    insuranceAnswerBg: require('../Images/insurance/insurance_answer_bg.png'),
} : null;

5. Metro-Bundler filter code

After the RN0.57 version, the packaging tool was migrated from the RN-Ci by Package and unifiedly implemented by Metro. For more detailed usage and analysis of Metro, please refer to my previous article: 

At the same time, a new generation of Metro provides developers with a "plug-in" function usage. The functions are also richer, such as: transformer, serializer, etc., we can use the serializer to filter the corresponding code during the packaging process

// metro.config.js

const moduleArray = require('./metro.filter');

/**
 * 对未使用的文件模块进行过滤
 * return false 则过滤不编译
 * @param {*} module
 * @returns
 */
function postProcessModulesFilter(module) {
    if (moduleFilter(module.path) >= 0) {
        console.log(`代码过滤中: ${module.path}`);
        return false;
    }
    return true;
}

/**
 * 正则匹配module
 * @param {*} path
 * @returns
 */
function moduleFilter(path) {
    for (const i in moduleArray) {
        if (path.match(`/${moduleArray[i]}/`)) {
            return i;
        }
    }
    return -1;
}

/**
 * Metro 配置
 * @format
 */
module.exports = {
    serializer: {
        processModuleFilter: postProcessModulesFilter,
    }
};
// metro.filter.js

// 配置在Production需要过滤的模块文件
module.exports = [
    'Components/Insurance',
    'Containers/Insurance',
    'Components/ActivityEleven',
    'Containers/ActivityEleven',
];

In the above code, we use processModuleFilter to filter the code module declared in metro.filter.js

⚠️ Note

When using processModuleFilter for code filtering, it must be ensured that the filtered module code cannot be used anywhere, otherwise the packaged jsBundle will still contain the corresponding module pair import, and during parsing, because the corresponding pair moduleId cannot be found, the parsing fails Crash application

 

Second, Android

Now, after reducing the size of the JS package, we started to use the "APK Analyzer" to analyze the apk in Android Studio. We noticed that we have reduced the index.android.bundle file and a large number of files in the libs folder. Therefore, we began to further analyze the apk.

1. Try the new R8 code shrinker

android.enableR8=true

Code compression can help reduce the size of APKs by deleting unused code and resources. It shortens the code faster than ProGuard.

2. Enable resource reduction

shrinkResources true

It helps to use resource reduction to identify and delete unnecessary resources.

3. ResConfigs

resConfigs "zh"

When building the application, it will delete all other localized resources.

4. Compress the image

aaptOptions { 
     cruncherEnabled = false 
}

To compress the image resources used in the app, you can add this line to build.gradle in the android {} section

5. MinifyEnabled

Turn on proguard obfuscation. If you set the  minifyEnabled property  true, R8 will merge the rules from all available sources.

6. zipAlignEnabled

Enable zipAlign compression optimization

Published 214 original articles · praised 371 · 920,000 views

Guess you like

Origin blog.csdn.net/u013718120/article/details/105346652