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
在 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/