preface
This article is based on React – Native version 0.55, React 16.3.1
purpose
- Reduce the size of business package (APP slimming)
- Save hot update traffic
- Improve module loading speed
implementation
- Use packages, including React-Native framework, code-push framework, common third-party framework, RNlib code, etc
- Load the full bundle
- Full package Output differential package based on common package (service package)
- Generic packages and business packages are introduced into the project at the same time
- The native side preloads the generic package and only loads the business package when entering an RN page
- Code-push updates the generic package and the business package respectively
Jsbundle Resolution (Full package)
var
__DEV__ = false,
__BUNDLE_START_TIME__ = this.nativePerformanceNow ? nativePerformanceNow() : Date.now(),
process = this.process || {};
process.env=process.env || {};
process.env.NODE_ENV = "production"; ! (function(r) {
"use strict"; r.__r = o, .... }) (); __d(function(r,o,t,i,n){t.exports=r.ErrorUtils},18,[]); . . __d(function(c,e,t,a,n){var r=e(n[0]); r(r.S,'Object'[1], {create: e (n)})}, 506, [431460]); require(46); require(11);Copy the code
It can be roughly divided into four parts
- The variables declared by var, the definition of the current running environment, the startup time of the bundle, and the information about the Process environment;
- (function() {})() closure, which defines support for define (__d), require (__r), clear (__c), Loading logic for modules (react-native and dependences);
- __d defined code block, including RN framework source JS part, custom JS code part, image resource information, for require to introduce use;
- The code block defined by require finds and executes the code block defined by __d, where the number in require is the last number in the __d definition line.
If each business is packaged separately, parts 1, 2 and a lot of part 3 code will be repeated, so we need to extract this code as a common part, common.jsbundle, and the business package will reuse this code
Unpack using Metro
In the jsbundle resolution above, each module defined in __d is represented by a number and called in the final require method (such as require(41)), Of these Numbers is createModuleIdFactory method in metro project generated (node_modules/metro/SRC/lib/createModuleIdFactory. Js); If a module is added, the ID is regenerated. To make a base package, the public module ID must be fixed. Therefore, 0.56+ RN can fix the module ID through this method interface. Just not exposed, you can modify the source code to achieve the same effect 0.56+ version
Source code changes are as follows
function createModuleIdFactory() {
if(process.env.NODE_ENV ! ='production') {// debug mode const fileToIdMap = new Map();let nextId = 0;
return path => {
let id = fileToIdMap.get(path);
if(typeof id ! = ='number') {
id = nextId++;
fileToIdMap.set(path, id);
}
return id;
};
} elseConst projectRootPath = 'const projectRootPath =' const projectRootPath =${process.cwd()}`; // path is the module path namereturn path => {
let moduleName = ' ';
if (path.indexOf('node_modules\\react-native\\Libraries\\') > 0) {
moduleName = path.substr(path.lastIndexOf('\ \') + 1);
} else if (path.indexOf(projectRootPath) == 0) {
moduleName = path.substr(projectRootPath.length + 1);
}
moduleName = moduleName.replace('.js'.' ');
moduleName = moduleName.replace('.png'.' ');
moduleName = moduleName.replace('.jpg'.' ');
moduleName = moduleName.replace(/\\/g, '_'); ModuleName = moduleName. Replace (/\//g,'_'); // MacOS platform path problemreturn moduleName;
};
}
} module.exports = createModuleIdFactory;
Copy the code
As can be seen from the above source code, the system uses an integer type, starting from 0 through all modules, and then increase the Id by 1. So we can change the logic here and use the module path name as the Id. Note: HERE I added a judgment of process.env.node_env, which means to use the same mode in development mode (because I found that the development mode will fail if it is changed globally).
Base package and business package packaging
Before packaging, we need to define the basic module and business module files respectively. The core code is as follows:
// base.js
require('react-native');
require('react');
require('@bwt/bwt-navigation');
require('./rnlib/RNKit');
require('./rnlib/UIKit');
import CodePush from "@bwt/bwt-code-push"; .// It is possible to introduce more third-party modules and its own public modules
Copy the code
// index_{{BundleName}}.js
import App_{{ModuleName}} from './App_{{ModuleName}}';
AppRegistry.registerComponent("{{ModuleName}}", () => App_{{ModuleName}});
Copy the code
Note: Base.js is the entry of the base module, and index_{{BundleName}}.js is the entry of the business module
The react-native bundle command is used to package the react-native bundle, which requires two different commands. The difference is that the package entry file parameter is different:
Base.js entry package
react-native bundle --platform ios --dev false --entry-file base.js --bundle-output $projectPath/ios/bundle/common/common.jsbundle --assets-dest $projectPath/ios/bundle/common/ --dev false
Copy the code
Common output. Jsbundle
Index_ {{BundleName}}.js entry package
react-native bundle --platform ios --dev false --entry-file index_{{BundleName}}.js --bundle-output $projectPath/ios/bundle/business.jsbundle --assets-dest $projectPath/ios/bundle/$jsbundleName/ --dev false
Copy the code
The output business. Jsbundle
Differential packaging
Business. Jsbundle Based on common. Jsbundle.
- Business.jsbundle is scanned line by line
- If the scanned content is not found in common.jsbundle, store it in an array
- Convert the array to data and save it in the differential patch.jsbundle
Conclusion: Find the difference set of the two files that contains only the business.jsbundle code
/ / $1: common. Jsbundle $2: business. Jsbundle $3: patch. Jsbundle sort $2 $1 $1 | uniq -u > $3 # write backCopy the code
Output patch.jsbundle as the final service package and import it into the project
conclusion
We split common.jsbundle and patch.jsbundle using the bundle structure. Common. jsbundle only needs to be introduced into the project once before it can be reused. There are several scenarios when the native runtime loads modules:
- Enter the page to dynamically merge the complete package after display
- Common.jsbundle is preloaded, and patch.jsbundle is loaded when entering the page. RN loads code containers using singletons
- Common. Jsbundle is preloaded, patch.jsbundle is loaded when entering the page, and common. Jsbundle is preloaded with another code container to be used when entering the page next time; This approach makes the code container lifecycle separate
This article describes the bundle unpacking scheme, so how to manage the native end? RN unpacking solution (2) Bundle loading