携程CRN源码详解之拆包(一)——总览

1、CRN开源

    CRN是由携程无线平台研发团队基于React Native框架优化,定制成稳定性和性能更佳、也更适合业务场景的的跨平台开发框架。CRN是一整套框架,包含了拆包、加载流程框架、UI框架、平台统一框架,CRN的初衷是提升RN页面加载速度,在不断地优化后形成了CRN框架。因此性能是CRN的核心,而需要做到性能优化,就离不开CRN的根本——拆包。

    本次CRN开源的主要是性能优化部分, 也是规模化使用RN进行业务开发必须要做的优化。其内容包括以下:

  • 打包支持框架和业务代码拆分
  • 支持框架代码后台预加载
  • 打包支持增量编译(同一模块,两次打包模块ID不变)
  • iOS&Android统一一套打包产物
  • 首屏加载性能统计
  • LazyRequire

所有的功能都是跟拆包密不可分,我自己也在CRN之前开源了一个拆包工具https://github.com/smallnew/react-native-multibundler,而CRN的拆包工具的名字叫crn-cli,在实现方式上两者有明显的不同,后面我会分几个部分解析crn-cli源码,并提出一些注意事项

2、CRN-CLI的PATCH

   有别与基于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;

源码比较简单,就是根据配置替换文件

3、打包整体流程

    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的对应文件、打包版本信息文件记录

4、后续

   这里先简单介绍了crn打包的流程,整体来说还是比较清晰,下一篇介绍rn_common的打包流程

你可能感兴趣的:(react,naitve)