本文是基于react-native 0.55, react 16.3.1版本展开
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',{create:e(n[1])})},506,[431,460]);
require(46);
require(11);
如果每个业务都单独打全量包,那第一、二部分和大量第三部分代码将会重复,因此我们需要提取这部分代码做为通用部分,common.jsbundle,业务包将会复用这些代码
在上文的jsbundle解析中,__d中定义的各个module后都有一个数字表示,并在最后的require方法中进行调用(如require(41)),这其中的数字就是metro项目中createModuleIdFactory方法生成的(node_modules/metro/src/lib/createModuleIdFactory.js);如果添加了module,那么ID会重新生成,如果要做一个基础包,那么公共module的ID必须是固定的,因此0.56+版本的RN可以通过此方法的接口来将module的ID固定,0.52~0.55的RN依赖的metro也用到了这个方法,只是没暴露出来,可以通过修改源码的方式来实现0.56+版本相同的效果
function createModuleIdFactory() {
if (process.env.NODE_ENV != 'production') { // debug模式
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;
};
} else { // 生产打包使用具体包路径代替id,方便拆包处理
// 定义项目根目录路径
const projectRootPath = `${process.cwd()}`;
// path 为模块路径名称
return 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, '_'); // 适配Windows平台路径问题
moduleName = moduleName.replace(/\//g, '_'); // 适配macos平台路径问题
return moduleName;
};
}
} module.exports = createModuleIdFactory;
从上述源码也可以看出,系统使用整数型的方式,从0开始遍历所有模块,并依次使 Id 增加 1。所以我们可以修改此处逻辑,以模块路径名称的方式作为Id即可。
注意:这里我加了一个process.env.NODE_ENV的判断,即在开发模式下依旧使用原模式(因为我发现全局改的话开发模式会运行失败)
打包之前,需要我们分别定义好基础模块与业务模块文件,核心代码如下:
// base.js
require('react-native');
require('react');
require('@bwt/bwt-navigation');
require('./rnlib/RNKit');
require('./rnlib/UIKit');
import CodePush from "@bwt/bwt-code-push";
...//这里可以引入更多的第三方模块及自己的公共模块
// index_{{BundleName}}.js
import App_{{ModuleName}} from './App_{{ModuleName}}';
AppRegistry.registerComponent("{{ModuleName}}", () => App_{{ModuleName}});
注:base.js为基础模块入口,index_{{BundleName}}.js为业务业务模块入口
接下来就是通过react-native bundle命令来进行打包了,需要两个不同的命令,区别在于打包入口文件参数(–entry-file)不一样:
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
输出common.jsbundle
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
输出business.jsbundle
business.jsbundle基于common.jsbundle打差异包,实现思路:
结论:求出两个文件的差集,且只包含business.jsbundle的代码
//$1:common.jsbundle $2:business.jsbundle $3:patch.jsbundle
sort $2 $1 $1 | uniq -u > $3 #回写
输出patch.jsbundle作为最终业务包,引入到工程中
我们利用bundle的结构拆分出common.jsbundle和patch.jsbundle,common.jsbundle只需要引入到工程一次,就可以被复用;当原生运行时加载模块的时候有几种方案:
本文讲述的是bundle文件拆包方案,那么原生端应该如何管理呢?且看下篇,原生多bundle加载方案,RN拆包解决方案(二) bundle加载