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的编译问题

  1. Cannot initialize a parameter of type 'NSArray<Class> *' with an lvalue of type 'NSArray<id<RCTBridgeModule>> *__strong'
    

Build failed after update Xcode 12.5 beta Cannot initialize a parameter of type 'NSArray> ' with an rvalue of type 'NSArray ' #31412

打包逻辑

首先对于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
}

results matching ""

    No results matching ""