rn_common是crn所定义的框架包,rn_common包含了RN原来的框架和crn自己的基础框架。作为基础框架,rn_common会被上百个携程业务包共同使用,只有把框架代码抽取出来才能实现框架代码后台预加载功能,这样加载业务页面的时候只需要加载业务代码,大大减少加载时间。
上一节我们已经知道:打common包的时候会传递--build-common true。所以需要修改RN打包源码使打包代码认得这个参数,我们全局搜索CRN BEGIN可以快速找到CRN改动的地方,这方面CRN还是做得非常良心的。不过这里的build.js是crn自己的代码:
//build.js
function doBuildBundle(args) {
var options = require('minimist')(args);
var buildCommand = getBuildCommands(options);
var buildCommon = options['build-common'];
if (!util.isBoolean(buildCommon)) {
buildCommon = buildCommon === 'true';
}
var cmd = '';
if (buildCommon) {
cmd = 'node node_modules/react-native/local-cli/cli.js bundle --config rn-cli.config.js ' + buildCommand;
logOutPut.log(cmd);
execSync(cmd, { stdio: 'inherit' });
} else {
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' });
}
}
这里直接把参数透传给cli.js,再透传到bundle.js文件,这个时候crn改了代码:
///bundle.js
function bundleWithOutput(_, config, args, output) {
// BEGIN
//增加 设置全局的buildCommon
global.CRN_BUILD_COMMON = args.buildCommon;
//CRN END
return (0, _buildBundle.default)(args, config, output);
}
把buildCommon参数保存到全局变量,这样就不需要再传参数了
打common包一直都是比较简单直接的,你只需要把打包的入口设置成只包含框架库的js文件就可以了,crn-cli就指定打包入口为crn_common_entry。
//localPack.js 40行
build([
'--platform',
'ios',
'--entry-file',//这里指定了打包入口
'crn_common_entry.js',
'--build-common',
'true',
'--dev',
dev
]);
再看看crn_common_entry.js文件:
/**
* Common包入口文件
* 注册一个空壳App,预先加载。
* 进入到业务包页面时,监听native事件上报,加载业务模块,重新渲染页面。
*/
import React, { Component } from 'react';
import {
AppRegistry,
View,
DeviceEventEmitter
} from 'react-native';
var mainComponent = null;
var _component = null;
DeviceEventEmitter.removeAllListeners();
//native访问业务包,上报事件通知需要加载的模块ID
DeviceEventEmitter.addListener("requirePackageEntry", function (event) {
if (event && event.packagePath) {
global.CRN_PACKAGE_PATH = event.packagePath; //设置资源加载的路径
}
if (event && event.moduleId) {
mainComponent = require(event.moduleId);
if (_component) {
_component.setState({ trigger: true });
_component = null;
}
}
});
class CommonEntryComponent extends Component {
... ...
render() {
_component = this;
var _content = null;
if (mainComponent) {
_content = React.createElement(mainComponent, this.props);
}
return _content || ;
}}
AppRegistry.registerComponent('CRNApp', () => CommonEntryComponent); //CRNApp名字请勿修改
这里做了两件事:1、注册了一个叫CRNApp的空壳,这个空壳依赖了react native控件,打包的时候就会把react native打进去。原生端会预先加载这个空壳,这个名字是写死的不可更改 2、接收原生端加载业务模块的消息,加载业务模块并在空壳上展示
这里有个细节:mainComponent = require(event.moduleId); 要实现这句代码需要做许多工作
这句看起来平白无奇,但正常的js代码这么写是会有问题的,js代码不允许require一个变量。而为此crn魔改了代码。
在此之前要先知道打包的三大操作:遍历解析js依赖、转换js源码、序列化。
而需要支持require(event.moduleId)就要在转换js源码步骤中修改,因为require是需要转换的。
//collectDependencies.js
/**
* 特殊处理require(event.moduleId),添加lazyRequire识别
*/
......
function getModuleNameFromCallArgs(path) {
if (path.get("arguments").length !== 1) {
throw new InvalidRequireCallError(path);
}
//CRN BEGIN
let reqFirstName = null,
reqSecondName = null;
let node = path.node;
if (node.arguments[0].object) {
if (
node.arguments[0].object.name === "event" &&
node.arguments[0].property &&
node.arguments[0].property.name === "moduleId"
) {//判断require函数里的第一个参数是不是event.moduleId
reqFirstName = "event";
reqSecondName = "moduleId";
}
.... .... ....
}
const nameExpression = node.arguments[0];
if (reqSecondName && reqFirstName) {//支持这种格式
return [nameExpression, true, reqFirstName + "." + reqSecondName];
}
//CRN END
const result = path.get("arguments.0").evaluate();
if (result.confident && typeof result.value === "string") {
return result.value;
}
return null;
}
.....
mainComponent = require(event.moduleId);还有一个注意点,如果我们写的业务模块是这样的:
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
虽然这个是我们日常的写法,但require这个模块之后是不会返回App模块的,导致mainComponent不可用
这就是为什么官方文档强调“添加一行模块导出代码”
补充以下,rn_common不像react-native-multibundler可以定制化,这个逻辑是死的,如果要定制需要修改其入口文件crn_common_entry和业务打包逻辑源码。
到这里rn_common的打包已经差不多讲完,还有一些小细节留到后面慢慢分析,到现在官方所描述的功能已经明了了一些: