CRN是由携程无线平台研发团队基于React Native框架优化,定制成稳定性和性能更佳、也更适合业务场景的的跨平台开发框架。CRN是一整套框架,包含了拆包、加载流程框架、UI框架、平台统一框架,CRN的初衷是提升RN页面加载速度,在不断地优化后形成了CRN框架。因此性能是CRN的核心,而需要做到性能优化,就离不开CRN的根本——拆包。
本次CRN开源的主要是性能优化部分, 也是规模化使用RN进行业务开发必须要做的优化。其内容包括以下:
所有的功能都是跟拆包密不可分,我自己也在CRN之前开源了一个拆包工具https://github.com/smallnew/react-native-multibundler,而CRN的拆包工具的名字叫crn-cli,在实现方式上两者有明显的不同,后面我会分几个部分解析crn-cli源码,并提出一些注意事项
有别与基于metro配置打包的react-native-multibundler,CRN由于有更多的定制功能,metro的配置根本不够用,因此crn-cli使用了patch方式直接修改了Metro、RN的源代码。打包的时候控制台会有以下的log:
crn-cli pack
[crn-cli]: remove publish
[crn-cli]: begin build...
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/shared/output/RamBundle/crn-as-assets.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/shared/output/crn-bundle.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/lib/createModuleIdFactory.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/shared/output/RamBundle/as-assets.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/shared/output/bundle.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/ModuleGraph/worker/collectDependencies.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro-config/src/defaults/index.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/shared/output/RamBundle.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/metro/src/lib/polyfills/require.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/react-native/Libraries/polyfills/lazyRequire.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/react-native/Libraries/Image/AssetSourceResolver.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/react-native/Libraries/Image/resolveAssetSource.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/@react-native-community/cli/build/commands/bundle/bundle.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/@react-native-community/cli/build/commands/bundle/buildBundle.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/@react-native-community/cli/build/commands/bundle/bundleCommandLineArgs.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/node_modules/react-native/rn-get-polyfills.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/rn-cli.config.js >文件
成功替换< /Users//Works/learn/CRN/crnprj002/crn_common_entry.js >文件
[crn-cli]: node node_modules/react-native/local-cli/cli.js bundle --config rn-cli.config.js --platform ios --bundle-output common_ios.js --build-common true --entry-file crn_common_entry.js --dev false
Loading dependency graph, done.
warn Assets destination folder is not set, skipping...
[crn-cli]: node node_modules/react-native/local-cli/cli.js bundle --config rn-cli.config.js --platform android --bundle-output common_android.js --build-common true --entry-file crn_common_entry.js --dev false
Loading dependency graph, done.
warn Assets destination folder is not set, skipping...
[crn-cli]: pack has done!
[crn-cli]: node node_modules/react-native/cli.js ram-bundle --config rn-cli.config.js --platform ios --bundle-output common_ios.js --build-common false --entry-file index.js --dev false --assets-dest bundle_output/publish
Loading dependency graph, done.
[crn-cli]: node node_modules/react-native/cli.js ram-bundle --config rn-cli.config.js --platform android --bundle-output common_android.js --build-common false --entry-file index.js --dev false --assets-dest bundle_output/publish
Loading dependency graph, done.
[crn-cli]: diff -q /Users//Works/learn/CRN/crnprj002/bundle_output/publish/js-modules /Users//Works/learn/CRN/crnprj002/bundle_output_other/publish/js-modules
[crn-cli]: mergeBundle finish
[crn-cli]: pack has done!
[crn-cli]: 打包完成,请拷贝当前工程目录下publish目录文件到app中进行测试
这里面有大量的文件替换,将已经修改后的相关文件替换react native原来的文件
//commands/pack/patch/index.js
const replacePathFiles = require('./replacePatchFiles');
const config = require('./patchconfig');
function patch() {
replacePathFiles(config);
}
module.exports = patch;
//commands/pack/patch/replacePatchFiles.js
'use strict';
const fs = require('fs');
const util = require('util');
const path = require('path');
const chalk = require('chalk');
function replaceFiles(changeList) {
changeList && changeList.forEach((item) => {
replaceFile(item);
});
}
function replaceFile(conf) {
var targetFile = path.resolve(process.cwd(),conf.to);
var modifiedFile = path.resolve(__dirname, conf.from);
var isTargetFileExist = fs.existsSync(targetFile);
var isModifiedFileExist = fs.existsSync(modifiedFile);
try{
if(isTargetFileExist && isModifiedFileExist){
fs.unlinkSync(targetFile);
fs.createReadStream(modifiedFile).pipe(fs.createWriteStream(targetFile));
console.log('成功替换< ' + targetFile + ' >文件');
}else{
var dir = path.resolve(targetFile,'..');
if(!fs.existsSync(dir)){
fs.mkdirSync(dir);
}
fs.createReadStream(modifiedFile).pipe(fs.createWriteStream(targetFile));
console.log('成功替换< ' + targetFile + ' >文件');
}
}catch(ex){
console.error(chalk.red("replace file error"))
console.error(ex);
}
}
module.exports = replaceFiles;
源码比较简单,就是根据配置替换文件
crn-cli打包的时候会先打rn_common,打common包的时候会在命令行中加入参数来标记这次打的包是common
//localPack.js
function localPack() {
return new Promise(resolve => {
var args = process.argv.slice(2);
if (args[1] == '--help' || args[1] == '-h' || args[1] == '-H') {
printHelp('pack');
resolve();
return;
}
var option = require('minimist')(process.argv.slice(2));
var packConfig = require(path.resolve(currentPath, 'package.json')).packConfig || {};
var bundleOutput = option['bundle-output'] || packConfig['bundleOutput'] || 'publish';
var packageName = option['package-name'] || packConfig['packageName'] || 'CRNDemo';
var entryFile = option['entry-file'] || packConfig['entryFile'] || 'index.js';
var dev = option['dev'] || packConfig['dev'] || false;
deleteFile(path.resolve(currentPath, bundleOutput));
logOutput.log('remove ' + bundleOutput);
logOutput.log('begin build...');
patch();
setTimeout(() => {
build(['--platform','ios','--entry-file','crn_common_entry.js',
'--build-common',
'true',//////标记这是打的common包
'--dev',
dev
]);
fs.copySync(path.resolve(currentPath, './bundle_output/publish/'), path.resolve(currentPath, bundleOutput, 'rn_common'));
fs.copySync(path.resolve(currentPath, './bundle_output/baseMapping.json'), path.resolve(currentPath, bundleOutput, 'rn_common/baseMapping.json'));
deleteFile(path.resolve(currentPath, './bundle_output/publish/'));
build([
'--platform',
'ios',
'--entry-file',
entryFile,
'--build-common',
'false',
'--dev',
dev
]);
fs.copySync(path.resolve(currentPath, './bundle_output/publish/'), path.resolve(currentPath, bundleOutput, 'rn_' + packageName));
fs.copySync(path.resolve(currentPath, './bundle_output/buMapping.json'), path.resolve(currentPath, bundleOutput, 'rn_' + packageName, 'buMapping.json'));
deleteFile(path.resolve(currentPath, './bundle_output'));
deleteFile(path.resolve(currentPath, './bundle_output_other'));
logOutput.log('打包完成,请拷贝当前工程目录下' + bundleOutput + '目录文件到app中进行测试');
resolve();
}, 100);
});
}
localPack为整个打包的大致流程:
1、解析命令行参数
2、patch修改过的文件
3、打包rn_commin
4、打包业务包
打包后还会记录moduleId的对应文件、打包版本信息文件记录
这里先简单介绍了crn打包的流程,整体来说还是比较清晰,下一篇介绍rn_common的打包流程