CRN
源码地址: CRN
CRN bridge
CRNViewController, 对CRNView的封装
CRNView: makeCurrentBridge(通过CRNBridgeManager创建RCTBridge)->loadCRNViewWhenWorkDirExist创建RCTRootView
CRNBridgeManager RCTBridge的管理
通过bridge方法创建bridge,当创建一个bridge之后会缓存到cachedBridgeList,cachekey为businessURL或者bundleURL
bundleURL
/Documents/webapp_work_1.0/rn_common/common_ios.js
businessURL
Documents/webapp_work_1.0/rn_CRNDemo/main.js?CRNModuleName=CRNApp&CRNType=1
prepareBridgeIfNeed 提供预加载bridge功能
在收到RCTJavaScriptDidLoadNotification通知后,
emitRequirePackageEntryMessage,emit 到js 分包相关的信息,bridge会置为dirty->prepareBridgeIfNeed预加载下一个bridge
在一个rn页面销毁的时候,发出kCRNViewDidReleasedNotification通知,执行performLRUCheck,移除dirty的bridge(dirty bridge超过5个时)
bridge delegate相关
预加载
common js 预加载
启动的时候,会初始化bridge,加载common js加入cache 队列
运行时
打开分包,调用AppRegistry.runApplication("CRNApp", param),param如下
{
"rootTag":21,
"initialProps":{
"url":"file:///Users/xx/Library/Developer/CoreSimulator/Devices/D333DC7D-E4E4-488F-8C7C-06F5494DC158/data/Containers/Data/Application/E8743A9B-487D-4155-AD9A-82E4389B3688/Documents/webapp_work_1.0/rn_CRNDemo/main.js?CRNModuleName=CRNApp&CRNType=1"
}
}
加载包流程
- 加载common js,通过RCTCxxBridge 的executeApplicationScript 加载,最终调用到JSI Runtime 的 evaluateJavaScript
- 加载分包内的js-modules的某个/某多个js文件,这部分是运行JS时调用的,具体流程时js nativeRequire调用 ->JSIExecutor::CRNNativeRequire -> JSIExecutor CRNLoadModule -> JSI Runtime 的 evaluateJavaScript,最终调用到JSI Runtime 的 evaluateJavaScript
什么时候注入
RCTJavaScriptDidLoadNotification -> CRNBridgeManager emitRequirePackageEntryMessage-> RCTCxxBridge updateModuleIdConfig->JSIExecutor registerCRNNativeRequire
registerCRNNativeRequire: 赋值m_moduleIdConfig map(之后CRNLoadModule使用),并且注入nativeRequire函数
m_moduleIdConfig 示例
{
modulePath = "/Library/Developer/CoreSimulator/Devices/BB1A09D9-9CEE-4ADE-B0D7-9ABE5F1876BB/data/Containers/Data/Application/4048CA18-9F7C-4E52-B993-FC89F78C876C/Documents/webapp_work_1.0/rn_CRNDemo/js-modules";
}
最后在进入分包页面前,客户端会调用
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:moduleId forKey:@"moduleId"];
[userInfo setValue:bridge.rnProductName forKey:@"productName"];
[userInfo setValue:businessURL forKey:@"packagePath"];
[bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit" args:@[@"requirePackageEntry",userInfo]];
userInfo的内容
{
moduleId = 666666;
packagePath = "file:///Documents/webapp_work_1.0/rn_CRNDemo/main.js?CRNModuleName=CRNApp&CRNType=1";
productName = "rn_CRNDemo";
}
common js 侧
CRN_PACKAGE_PATH 赋值,这个是getSourceCodeScriptURL函数获取到的路径
crn的编译问题
Cannot initialize a parameter of type 'NSArray<Class> *' with an lvalue of type 'NSArray<id<RCTBridgeModule>> *__strong'
打包逻辑
首先对于cli以及metro的修改 见patchconfig.js
pack localPack.js -> build函数->doBuildBundle
localPack pack函数 会调用build函数 两次,通过buildCommon变量true false来决定是不是build公共包
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' });
}
这里是对官方的@react-native-community/cli进行了修改
local-cli/cli bundleWithOutput 主要是传递buildCommon 参数,最终调用metro build
打包是打common,entry file为crn_common_entry.js,后打business,entry file 为业务包的入口文件
metro
require.js的修改
nativeRequire只需要传入moduleId就可以
bundle
saveBundleAndMap函数有所修改
- 写pack.config文件,包括time和package.json的version
- 写baseMapping.json文件
- common,调用getBaseMapping函数
- business,调用getBUMapping函数
- 最后分别写bundle 和source map文件
crn重写了createModuleIdFactory函数,mappingContainer会保存common 的mapping(id到文件的映射),mappingContainerBU保存了business的mapping,这两个数组对象会保存传入的path和自增生成的module id,对于common module 从0开始自增,业务模块从666666开始。
business bundle
对于business bundle 使用的是RamBundle.js
save替换成了asAssets函数
- 写pack.config
- 写buMapping.json文件,调用getBUMapping函数
- 写包相关文件
- 写_crn_config_v2文件,内容是,main_module=666666,module_path=js-modules
- 写_crn_unbundle文件,写入magic-number
- 把js文件写入js-modules文件夹
- 这里会遍历调用writeModuleFile函数,把module code 写入,由于是分包,这里对于小于666666的module 不会写入
writeModuleFile如下,666666是区分common bundle 和business bundle的关键,因为business bundle是从666666开始的
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);
}
var nextModuleId = {
rn: -1,
business: 666665
};
/**
* 生成业务包模块ID和COMMON包模块ID
*/
function generateId() {
if (!global.CRN_BUILD_COMMON) {
return ++nextModuleId.business;
}
return ++nextModuleId.rn;
}
business bundle 打包为File RAM bundle,非Indexed RAM bundle
function save(bundle, options, log) {
// we fork here depending on the platform:
// while android is pretty good at loading individual assets, ios has a large
// overhead when reading hundreds pf assets from disk
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an error
* found when Flow v0.68 was deployed. To see the error delete this comment
* and run Flow. */
// return options.platform === 'android' && !options.indexedRamBundle
// ? asAssets(bundle, options, log)
// : asIndexedFile(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
}