除了rn_common的包含业务逻辑的代码会被CRN打成业务包。CRN希望把业务包做得尽量地可以按需加载,因此CRN使用了metro本来就有的rambundle功能。rambundle在之前叫做unbundle,这种打包方式能把bundle按模块分散成多个独立的js文件,加载页面的时候只会加载需要的js文件,大大提升了页面的加载速度。
cmd = 'node node_modules/react-native/cli.js ram-bundle --config rn-cli.config.js ' + buildCommand + ' --assets-dest bundle_output/publish';
logOutPut.log(cmd);
execSync(cmd, { stdio: 'inherit' });
这里明确指明使用ram-bundle方式打包,跟rn_common打包使用的bundle有明显区别。
iOS的ram-bundle是一个整体的问题,为了和Android统一,crn修改了RamBundle.js
//RamBundle.js
function save(bundle, options, log) {
//CRN BEGIN
//处理ios打包成多文件目标
return asAssets(bundle, options, log); //CRN END
//ORIGINAL:
//return options.platform === 'android' && !options.indexedRamBundle
// ? asAssets(bundle, options, log)
// : asIndexedFile(bundle, options, log);
//ORIGINAL-END
}
如果过滤,重写as-assets.js的写文件方法
//as-assets.js
function writeModuleFile(module, modulesDir, encoding) {
const code = module.code,
id = module.id;
//CRN BEGIN 输出剔除common包中的模块
if (id < 666666) {
return Promise.resolve();
} //CRN END
return writeFile(path.join(modulesDir, id + ".js"), code, encoding);
}
只要id小于666666就过滤掉,这过滤掉的就是rn_common的内容,因为rn_common模块id是从0开始的,而你依赖的模块再怎么上天也上不了666666这个数字。而业务id是从666666开始的,因此这里过滤后的js文件都是业务模块的文件。
以上的步骤已经完成了包括了资源的打包,因为资源的打包逻辑本来就不需要修改,换句话说:打业务包的时候不需要过滤rn_common的资源,因为crn框架中rn_common只有代码没有资源。而资源文件的加载需要修改。
crn为了让Android和iOS使用统一的资源文件,强行把本来不一样的资源打包方式改成一样,即改成iOS的资源格式
//AssetSourceResolver.js
defaultAsset(): ResolvedAssetSource {
if (this.isLoadedFromServer()) {
return this.assetServerURL();
}
//CRN BEGIN
//资源加载IOS和Android保持一致
// if (Platform.OS === 'android') {
// return this.isLoadedFromFileSystem()
// ? this.drawableFolderInBundle()
// : this.resourceIdentifierWithoutScale();
// } else {
return this.scaledAssetURLNearBundle();
// }
//CRN END
}
可以看到这里的改动直接把Android的逻辑去掉了,简单有效
这里crn为了能让支持让业务包放在多个目录下加载也改了资源的加载方式,按照rn原来的逻辑所有的代码和资源必须放在同一个目录。
//resolveAssetSource.js
function getSourceCodeScriptURL(): ?string {
//CRN BEGIN
//拆包后的路径解析相关
if (global.CRN_PACKAGE_PATH) {
return global.CRN_PACKAGE_PATH;
}
//CRN END
if (_sourceCodeScriptURL) {
return _sourceCodeScriptURL;
}
... ... ...
}
crn加载业务包的时候会将业务包路径存放到全局变量CRN_PACKAGE_PATH中,详见crn_common_entry。这里返回CRN_PACKAGE_PATH代表后面资源文件都在这个目录下查找,这就完成能在对应业务bundle目录下查询到对应的资源文件。
大家都知道由于Android和iOS平台的差异性,有一些js模块它是有分Android和iOS版本的,这也导致Android和iOS打出来的js包会有所不同,这里CRN对此做了处理。
//build.js
**
* 生成js-diffs文件夹,存储IOS和Android打包差异化代码
*/
function mergeBundle() {
var command = "diff -q " + path.resolve(currentPath, 'bundle_output/publish/js-modules/') + " " + path.resolve(currentPath, 'bundle_output_other/publish/js-modules/');
logOutPut.log(command);
try {
execSync(command);
} catch (error) {
var diff = error.stdout.toString()
logOutPut.log(diff);
var regexp = /6+[0-9]*.js/g;
var arr = diff.match(regexp)
arr = [...new Set(arr)];
logOutPut.log(arr);
var dest = path.resolve(currentPath, 'bundle_output_other/publish/js-diffs/');
fs.ensureDirSync(dest)
arr.forEach(function (item, i) {
var src = path.resolve(currentPath, 'bundle_output/publish/js-modules/' + item);
fs.copySync(src, path.resolve(dest, item));
});
} finally {
deleteFile(path.resolve(currentPath, './bundle_output/'));
fs.copySync('./bundle_output_other', './bundle_output');
logOutPut.log('mergeBundle finish');
}
}
这算出Android和iOS的差异的地方并把差异的js文件保存到js-diffs文件夹中,Android手机加载业务包的时候优先查找js-diffs文件下的js模块。
到这里crn的打包的90%的逻辑已经明了,我们需要注意的是所有的业务包都必须从666666 id开始算起,业务包就是剔除了的rn_common的打包,这个是没有react-native-multibundler灵活的。不过CRN的生态已经完善,这种不灵活的做法也够应对了。
crn的拆包已经基本讲完,总体来说CRN的优点很明显,提供了基本的拆包思路,使用patch方式修改源码高效方便,为了方便使用做了很多细节上的优化。不过还有优化的空间的,rn_common不够灵活,业务包之间的交互没有提供方案,可能这是为了多部门协作而做的妥协吧。
剩下的这两个功能算是对拆包的锦上添花,具体的实现我就不解析了,可以看看官方的介绍:
https://mp.weixin.qq.com/s/Z1GUJW3qBqDGH1jnGt5qAg