Native 运行时如何处理 Bundle

v0.67.3

RCTSource

RCTSource 管理bundle资源,通过RCTJavaScriptLoader来加载资源,只是自己实现获取source资源的loader,实现-loadSourceForBridge:协议即可。

loader支持同步和异步载入source,异步通过RCTMultipartDataTask 处理

对于异步网络加载的,Content-Type需要为application/javascript,text/javascript,application/x-metro-bytecode-bundle

RCTBridge 支持再次加载其他的包,通过loadAndExecuteSplitBundleURL方法

处理包

load 包有三种模式

auto reactInstance = self->_reactInstance;
if (reactInstance && RCTIsBytecodeBundle(script)) {
  UInt32 offset = 8;
  while (offset < script.length) {
    UInt32 fileLength = RCTReadUInt32LE(script, offset);
    NSData *unit = [script subdataWithRange:NSMakeRange(offset + 4, fileLength)];
    reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(unit), sourceUrlStr.UTF8String, false);
    offset += ((fileLength + RCT_BYTECODE_ALIGNMENT - 1) & ~(RCT_BYTECODE_ALIGNMENT - 1)) + 4;
  }
} else if (isRAMBundle(script)) {
  [self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
  auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
  std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
  [self->_performanceLogger markStopForTag:RCTPLRAMBundleLoad];
  [self->_performanceLogger setValue:scriptStr->size() forTag:RCTPLRAMStartupCodeSize];
  if (reactInstance) {
    auto registry =
        RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
    reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async);
  }
} else if (reactInstance) {
  reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(script), sourceUrlStr.UTF8String, !async);
} else {
  std::string methodName = async ? "loadBundle" : "loadBundleSync";
  throw std::logic_error("Attempt to call " + methodName + ": on uninitialized bridge");
}

RAM

RAM Bundles 和内联引用优化

在 react-native 执行 JS 代码之前,必须将代码加载到内存中并进行解析。如果你加载了一个 50MB 的 bundle,那么所有的 50mb 都必须被加载和解析才能被执行。RAM 格式的 bundle 则对此进行了优化,即启动时只加载 50MB 中实际需要的部分,之后再逐渐按需加载更多的包。

在 iOS 上使用 RAM 格式将创建一个简单的索引文件,React Native 将根据此文件一次加载一个模块。

对于native侧来说有两点,第一执行ram bundle的启动代码,第二是提供nativeRequire函数

auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
if (reactInstance) {
  auto registry =
      RAMBundleRegistry::multipleBundlesRegistry(std::move(ramBundle), JSIndexedRAMBundle::buildFactory());
  reactInstance->loadRAMBundle(std::move(registry), std::move(scriptStr), sourceUrlStr.UTF8String, !async);
}

ram bundle 启动代码的逻辑,读取header,从header里读取启动代码的大小,然后再读出startupcode

nativeRequire的运行时引入逻辑,解析moduleId和bundleId参数,然后从bundleRegistry_获取到Module 的代码,从m_bundles获取到JSModulesUnbundle,然后从对应的Unbundle上获取到Module

Value JSIExecutor::nativeRequire(const Value *args, size_t count) {
  if (count > 2 || count == 0) {
    throw std::invalid_argument("Got wrong number of args");
  }

  uint32_t moduleId = folly::to<uint32_t>(args[0].getNumber());
  uint32_t bundleId = count == 2 ? folly::to<uint32_t>(args[1].getNumber()) : 0;
  auto module = bundleRegistry_->getModule(bundleId, moduleId);

  runtime_->evaluateJavaScript(
      std::make_unique<StringBuffer>(module.code), module.name);
  return facebook::jsi::Value();
}

RAMBundleRegistry

负责管理ram bundle

  • 初始化,在载入ram bundle的时候,会初始化RAMBundleRegistry,注入mainbundle,key为MAIN_BUNDLE_ID(0),所有的bundles通过m_bundles map处理
  • 根据bundleid 获取unbundle,然后根据moduleid获取module
  • register bundle

registerSegmentWithId(可以动态注入分段bundle)->registerBundle

对于registerBundle来说,如果支持RAMBundleRegistry,则注入,如果不支持,则直接evaluateJavaScript

JSModulesUnbundle

  • 根据moduleid 获取module

具体的实现是JSIndexedRAMBundle,继承自JSModulesUnbundle,传入moduleid可以读取出code,init的时候会读取header 获取 startupcode,ModuleTable(保存moduleid和其offset),startupcode, module的代码是以header+moduletable为基线开始读取数据

js侧 nativeRequire

具体在metro的nativeRequire

内联引用优化

内联引用(require 代替 import)可以延迟模块或文件的加载,直到实际需要该文件。即便不使用 RAM 格式,内联引用也会使启动时间减少,因为优化后的代码只有在第一次 require 时才会执行。

https://blog.logrocket.com/whats-new-in-react-native-0-64/#inlinerequiresenabledbydefault

// Before
import { TestFunction } from 'test-module';

const TestComponent = (props) => {
  const result = TestFunction();

  return <Text>{result}</Text>;
};

//After
const TestComponent = (props) => {
  const result = require('test-module').TestFunction();

  return <Text>{result}</Text>;
};

开启inline在metro.config.js

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  }
}

打包

打包命令

npx react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios/

results matching ""

    No results matching ""