跳到主要內容

RAM 捆綁和內聯需求

如果您有一個大型應用程式,您可能想要考慮使用隨機存取模組 (RAM) 捆綁格式,並使用內聯需求。這對於在典型應用程式使用期間可能永遠不會開啟的大量畫面應用程式很有用。一般來說,這對於在啟動後一段時間內不需要大量程式碼的應用程式很有用。例如,應用程式包含複雜的個人資料畫面或較少使用的功能,但大多數的會話只會拜訪應用程式的首頁以取得更新。我們可以使用 RAM 格式最佳化捆綁的載入,並在內聯中要求這些功能和畫面(當它們實際使用時)。

載入 JavaScript

在 react-native 可執行 JS 程式碼之前,必須將該程式碼載入記憶體並剖析。使用標準套件,如果您載入 50mb 套件,則必須載入並剖析所有 50mb,才能執行任何部分。RAM 套件背後的最佳化是,您只能在啟動時載入實際需要的 50mb 部分,並隨著需要逐漸載入更多套件。

內嵌需要

內嵌需要會延遲需要模組或檔案,直到實際需要該檔案。基本範例如下所示

VeryExpensive.tsx
import React, {Component} from 'react';
import {Text} from 'react-native';
// ... import some very expensive modules

// You may want to log at the file level to verify when this is happening
console.log('VeryExpensive component loaded');

export default class VeryExpensive extends Component {
// lots and lots of code
render() {
return <Text>Very Expensive Component</Text>;
}
}
Optimized.tsx
import React, {Component} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';

let VeryExpensive = null;

export default class Optimized extends Component {
state = {needsExpensive: false};

didPress = () => {
if (VeryExpensive == null) {
VeryExpensive = require('./VeryExpensive').default;
}

this.setState(() => ({
needsExpensive: true,
}));
};

render() {
return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={this.didPress}>
<Text>Load</Text>
</TouchableOpacity>
{this.state.needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
}

即使沒有 RAM 格式,內嵌需要也能改善啟動時間,因為 VeryExpensive.js 中的程式碼只會在第一次需要時執行。

啟用 RAM 格式

在 iOS 上使用 RAM 格式會建立一個索引檔案,react native 會一次載入一個模組。在 Android 上,預設會為每個模組建立一組檔案。您可以強制 Android 建立一個檔案,就像 iOS 一樣,但使用多個檔案效能可能較佳,且需要的記憶體較少。

透過編輯建置階段「Bundle React Native 程式碼和影像」來在 Xcode 中啟用 RAM 格式。在 ../node_modules/react-native/scripts/react-native-xcode.sh 之前,加入 export BUNDLE_COMMAND="ram-bundle"

export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh

在 Android 上,透過編輯 android/app/build.gradle 檔案來啟用 RAM 格式。在 apply from: "../../node_modules/react-native/react.gradle" 之前,加入或修改 project.ext.react 區塊

project.ext.react = [
bundleCommand: "ram-bundle",
]

如果您想使用單一索引檔案,請在 Android 上使用下列各行

project.ext.react = [
bundleCommand: "ram-bundle",
extraPackagerArgs: ["--indexed-ram-bundle"]
]
資訊

如果您使用 Hermes JS 引擎,則不應啟用 RAM 捆綁功能。在 Hermes 中,載入位元組碼時,mmap 可確保不會載入整個檔案。將 Hermes 與 RAM 捆綁一起使用可能會導致問題,因為這些機制彼此不相容。

設定預載入和內嵌需求

現在我們有了 RAM 捆綁,呼叫 require 會產生額外負擔。require 現在需要在遇到尚未載入的模組時透過橋接器傳送訊息。這將對啟動造成最大影響,因為在應用程式載入初始模組時,可能會在那裡進行大量的 require 呼叫。幸運的是,我們可以設定預載入部分模組。為執行此操作,您需要實作某種形式的內嵌需求。

調查已載入的模組

在您的根檔案 (index.(ios|android).js) 中,您可以在初始匯入後新增下列內容

const modules = require.getModules();
const moduleIds = Object.keys(modules);
const loadedModuleNames = moduleIds
.filter(moduleId => modules[moduleId].isInitialized)
.map(moduleId => modules[moduleId].verboseName);
const waitingModuleNames = moduleIds
.filter(moduleId => !modules[moduleId].isInitialized)
.map(moduleId => modules[moduleId].verboseName);

// make sure that the modules you expect to be waiting are actually waiting
console.log(
'loaded:',
loadedModuleNames.length,
'waiting:',
waitingModuleNames.length,
);

// grab this text blob, and put it in a file named packager/modulePaths.js
console.log(
`module.exports = ${JSON.stringify(
loadedModuleNames.sort(),
null,
2,
)};`,
);

執行應用程式時,您可以在主控台中查看已載入多少模組,以及有多少模組正在等待。您可能想要讀取 moduleNames 並查看是否有任何驚喜。請注意,內嵌需求會在首次參照匯入時呼叫。您可能需要調查並重新整理,以確保只在啟動時載入您想要的模組。請注意,您可以在 require 上變更 Systrace 物件,以協助偵錯有問題的需求。

require.Systrace.beginEvent = message => {
if (message.includes(problematicModule)) {
throw new Error();
}
};

每個應用程式都不同,但只載入您在第一個畫面所需的模組可能是合理的。當您滿意時,將 loadedModuleNames 的輸出放入名為 packager/modulePaths.js 的檔案中。

更新 metro.config.js

我們現在需要更新專案根目錄中的 metro.config.js,以使用我們新產生的 modulePaths.js 檔案

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const fs = require('fs');
const path = require('path');
const modulePaths = require('./packager/modulePaths');

const config = {
transformer: {
getTransformOptions: () => {
const moduleMap = {};
modulePaths.forEach(modulePath => {
if (fs.existsSync(modulePath)) {
moduleMap[path.resolve(modulePath)] = true;
}
});
return {
preloadedModules: moduleMap,
transform: {inlineRequires: {blockList: moduleMap}},
};
},
},
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

另請參閱 設定 Metro

設定中的 preloadedModules 項目表示在建置 RAM 捆綁時應將哪些模組標記為已預載入。載入捆綁時,這些模組會在任何 require 執行之前立即載入。blockList 項目表示不應內嵌需求這些模組。由於它們已預載入,因此使用內嵌需求不會有效能上的好處。事實上,產生的 JavaScript 會在每次參照匯入時花費額外時間來解析內嵌需求。

測試和測量改進

現在您應該可以使用 RAM 格式和內聯需求來建立您的應用程式。請務必測量啟動時間的前後差異。