【React Native】文件翻譯閱讀紀錄 - 指南 - Performance(效能)

by - 上午9:00

Facebook Open Source React Native


Performance(效能)

使用React Native而不是基於WebView的工具的一個令人信服的理由是為您的應用程序實現每秒60幀和本機外觀。在可能的情況下,我們希望React Native做正確的事情,並幫助您專注於您的應用而不是性能優化,但有些領域我們還沒有完全在那裡,而其他地方React Native(類似於寫本機)代碼直接)無法確定為您優化的最佳方式,因此需要手動干預。默認情況下,我們會盡力提供白皙流暢的UI性能,但有時這是不可能的。

本指南旨在教您一些基礎知識,以幫助您解決性能問題,並討論常見的問題來源及其建議的解決方案。

關於框架您需要了解的內容

你的祖父母一代稱電影為“動態圖片”的原因是:視頻中的逼真動作是一種快速改變靜態圖像所產生的錯覺。我們將這些圖像中的每一個稱為幀。每秒顯示的幀數直接影響視頻(或用戶界面)的平滑程度和最終生活狀態。 iOS設備每秒顯示60幀,這為您和UI系統提供了大約16.67ms的時間來完成生成靜態圖像(幀)所需的所有工作,用戶將在屏幕上看到該間隔。如果您無法在分配的16.67ms內完成生成該幀所需的工作,那麼您將“刪除一幀”並且UI將顯示無響應。

現在要稍微混淆一下,打開應用程序中的開發人員菜單並切換Show Perf Monitor。您會注意到有兩種不同的幀速率。

JS幀率(JavaScript線程)

對於大多數React Native應用程序,您的業務邏輯將在JavaScript線程上運行。這是您的React應用程序所在的位置,進行API調用,處理觸摸事件等等...對本機支持的視圖的更新將在事件循環的每次迭代結束時進行批處理並發送到本機端。框架截止日期(如果一切順利)。如果JavaScript線程對幀沒有響應,則將其視為已刪除的幀。例如,如果要在復雜應用程序的根組件上調用this.setState並導致重新渲染計算量很大的組件子樹,則可以想像這可能需要200毫秒並導致丟棄12幀。由JavaScript控制的任何動畫在此期間似乎都會凍結。如果任何事情需要超過100毫秒,用戶將感受到它。

這通常發生在導航器轉換期間:當您推送新路徑時,JavaScript線程需要呈現場景所需的所有組件,以便通過適當的命令發送到本機端以創建支持視圖。這裡完成的工作通常會佔用幾幀並導致jank,因為轉換是由JavaScript線程控制的。有時組件會對componentDidMount執行額外的工作,這可能會導致轉換中出現第二個斷斷續續的情況。

另一個示例是響應觸摸:例如,如果您在JavaScript線程上跨多個幀進行工作,您可能會注意到響應TouchableOpacity的延遲。這是因為JavaScript線程忙,無法處理從主線程發送的原始觸摸事件。因此,TouchableOpacity無法對觸摸事件做出反應並命令本機視圖調整其不透明度。

UI幀率(主線程)

很多人都注意到NavigatorIOS的性能比Navigator更好。原因是轉換的動畫完全在主線程上完成,因此它們不會被JavaScript線程上的幀丟失中斷。

同樣,當JavaScript線程被鎖定時,您可以愉快地在ScrollView中上下滾動,因為ScrollView存在於主線程上。滾動事件被分派到JS線程,但滾動發生時不需要它們的接收。

性能問題的常見來源

在開發模式下運行 (dev=true)

在開發模式下運行時,JavaScript線程性能會受到很大影響。這是不可避免的:需要在運行時完成更多工作,以便為您提供良好的警告和錯誤消息,例如驗證propTypes和各種其他斷言。始終確保在發布版本中測試性能。

使用 console.log 描述

運行捆綁應用程序時,這些語句可能會導致JavaScript線程出現大瓶頸。這包括來自調試庫(如redux-logger)的調用,因此請確保在捆綁之前刪除它們。您還可以使用此babel插件刪除所有控制台。*調用。您需要首先使用 npm i babel-plugin-transform-remove-console --save-dev 安裝它,然後在項目目錄下編輯 .babelrc 文件,如下所示:
{
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}
這將自動刪除項目的發布(生產)版本中的所有控制台。*調用。

ListView初始渲染太慢或滾動性能對大型列表不利

請改用新的 FlatList 或 SectionList 組件。除了簡化API之外,新的列表組件還具有顯著的性能增強,主要的是對任意數量的行幾乎恆定的內存使用。

如果 FlatList 渲染速度很慢,請確保已實現 getItemLayout 以通過跳過渲染項的測量來優化渲染速度。

當重新渲染幾乎沒有變化的視圖時,JS FPS 會急劇下降

如果您使用的是 ListView,則必須提供 rowHasChanged 函數,該函數可以通過快速確定是否需要重新呈現行來減少大量工作。如果您使用不可變數據結構,這將像引用相等性檢查一樣簡單。

同樣,您可以實現 shouldComponentUpdate 並指示您希望組件重新呈現的確切條件。如果編寫純組件(其中render函數的返回值完全依賴於props和state),則可以利用 PureComponent 為您執行此操作。再一次,不可變數據結構對於保持這種速度非常有用 - 如果你必須對大量對象進行深度比較,那麼重新渲染整個組件可能會更快,而且肯定需要更少的代碼。

刪除JS線程FPS因為同時在JavaScript線程上做了很多工作

“慢速導航器過渡”是這種情況最常見的表現形式,但還有其他時候會發生這種情況。使用 InteractionManager 可能是一種很好的方法,但如果用戶體驗成本太高而無法在動畫期間延遲工作,那麼您可能需要考慮 LayoutAnimation。

Animated API當前在JavaScript線程上按需計算每個關鍵幀,除非您設置useNativeDriver:true,而 LayoutAnimation 利用 Core Animation 並且不受JS線程和主線程幀丟失的影響。

我使用它的一個案例是在模式中進行動畫製作(從頂部向下滑動並在半透明覆蓋層中淡入淡出),同時初始化並可能接收多個網絡請求的響應,渲染模態的內容,以及更新模態的視圖從...開了。有關如何使用 LayoutAnimation 的詳細信息,請參閱“動畫”指南。
注意事項:
  • LayoutAnimation僅適用於即發即棄動畫(“靜態”動畫) - 如果它必須是可中斷的,則需要使用動畫。

在屏幕上移動視圖(滾動,平移,旋轉)會丟棄UI線程FPS

如果文本的透明背景位於圖像頂部,或者需要使用alpha合成來重新繪製每個幀上的視圖,則尤其如此。你會發現啟用shouldRasterizeIOS或renderToHardwareTextureAndroid 可以顯著地幫助解決這個問題。

小心不要過度使用這個或你的內存使用量可能會通過屋頂。使用這些道具時,分析您的性能和內存使用情況。如果您不打算再移動視圖,請關閉此屬性。

動畫圖像的大小會丟棄UI線程FPS

在iOS上,每次調整圖像組件的寬度或高度時,都會重新裁剪並從原始圖像縮放。這可能非常昂貴,特別是對於大圖像。而是使用transform:[{scale}] style屬性來設置大小的動畫。您可以執行此操作的示例是點擊圖像並將其放大到全屏。

我的 TouchableX 視圖響應不是很快

有時,如果我們在同一幀中執行操作以調整響應觸摸的組件的不透明度或突出顯示,則在onPress函數返回之後我們才會看到該效果。如果onPress執行的setState導致大量工作並且丟棄了一些幀,則可能會發生這種情況。解決方法是在requestAnimationFrame中將任何操作包裝在onPress處理程序中:
handleOnPress() {
  // Always use TimerMixin with requestAnimationFrame, setTimeout and
  // setInterval
  this.requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}

緩慢的導航過渡

如上所述,Navigator動畫由JavaScript線程控制。想像一下“從右側推動”場景轉換:每一幀,新場景從右向左移動,從屏幕外開始(假設在x偏移為320)並最終在場景位於x偏移處時穩定下來
  1. 在此過渡期間的每個幀中,JavaScript線程需要向主線程發送新的x偏移量。如果JavaScript線程被鎖定,則無法執行此操作,因此該幀上不會發生更新,並且動畫會斷斷續續。
對此的一個解決方案是允許將基於JavaScript的動畫卸載到主線程。如果我們使用這種方法做上述示例中的相同操作,我們可以在開始轉換時計算新場景的所有x偏移的列表,並將它們發送到主線程以優化的方式執行。既然JavaScript線程已經擺脫了這個責任,如果它在渲染場景時掉落幾幀也不是什麼大問題 - 你可能甚至都不會注意到,因為你會因為漂亮的過渡而分心。

解決這個問題是新React Navigation庫背後的主要目標之一。 React Navigation中的視圖使用本機組件和Animated庫來提供在本機線程上運行的60 FPS動畫。

剖析

使用內置的分析器可以獲得有關JavaScript線程和主線程並行完成的工作的詳細信息。通過從“調試”菜單中選擇“Perf Monitor”來訪問它。

對於iOS,Instruments是一個非常寶貴的工具,在Android上你應該學會使用systrace。

但首先,確保開發模式關閉!您應該看到__DEV__ === false,開發級警告為OFF,應用程序日誌中的性能優化為ON。

另一種分析JavaScript的方法是在調試時使用Chrome分析器。這不會為您提供準確的結果,因為代碼在Chrome中運行,但會讓您大致了解瓶頸可能出現的位置。在Chrome的效果標籤下運行探查器。用戶計時下將顯示火焰圖。要以表格格式查看更多詳細信息,請單擊下方的“自下而上”選項卡,然後選擇左上方菜單中的“DedicatedWorker Thread”。

使用 systrace 分析 Android UI 性能

Android支持10k +不同的手機,並且可以支持軟件渲染:框架體系結構以及需要在許多硬件目標中進行概括,這意味著相對於iOS而言,您獲得的免費更少。但有時,有些事情你可以改進 - 而且很多時候根本不是本機代碼的錯!

調試此jank的第一步是回答在每個16ms幀中花費時間的基本問題。為此,我們將使用名為systrace的標準Android分析工具。

systrace是一種基於Android標記的標準分析工具(安裝Android平台工具包時會安裝)。已分析的代碼塊由開始/結束標記包圍,然後以彩色圖表格式顯示。 Android SDK和React Native框架都提供了可以顯示的標準標記。

1. 收集痕跡

首先,通過USB將顯示您要調查的口吃的設備連接到您的計算機,然後將其放到您想要配置的導航/動畫之前的位置。運行systrace如下:
$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>
快速分解此命令:
  • time是以秒為單位收集跟踪的時間長度
  • schedgfx, 和view 是我們關心的android SDK標籤(標記集合):sched為您提供有關手機每個核心上運行的內容的信息,gfx為您提供圖形信息,如框架邊界,以及視圖為您提供有關測量,佈局和繪製過程
  • -a <your_package_name>啟用特定於應用程序的標記,特別是React Native框架中內置的標記。 your_package_name可以在app的AndroidManifest.xml中找到,看起來像com.example.app
跟踪開始收集後,執行您關心的動畫或交互。在跟踪結束時,systrace將為您提供指向您可以在瀏覽器中打開的跟踪的鏈接。

2.閱讀踪跡

在瀏覽器(最好是Chrome)中打開跟踪後,您應該看到如下內容:
Example
提示:使用WASD鍵進行掃描和縮放
如果您的跟踪.html文件未正確打開,請檢查您的瀏覽器控制台以獲取以下信息:
ObjectObserveError
由於最近的瀏覽器不推薦使用Object.observe,因此您可能需要通過Google Chrome跟踪工具打開該文件。你可以這樣做:
  • 打開 chrome 的 chrome://tracing  頁籤
  • 選擇負載
  • 選擇從上一個命令生成的html文件。
啟用VSync突出顯示
選中屏幕右上角的此復選框以突出顯示16ms幀邊界:
Enable VSync Highlighting
您應該看到斑馬條紋,如上面的屏幕截圖所示。如果不這樣做,請嘗試在其他設備上進行性能分析:已知三星在顯示vsyncs時遇到問題,而Nexus系列通常非常可靠。

3. 找到你的過程

滾動,直到看到(部分)包名稱。在這種情況下,我正在分析com.facebook.adsmanager,它顯示為book.adsmanager,因為內核中存在愚蠢的線程名稱限制。

在左側,您將看到一組與右側時間軸行對應的線程。我們關心的幾個線程用於我們的目的:UI線程(具有您的包名稱或名稱UI Thread),mqt_js和mqt_native_modules。如果你在Android 5+上運行,我們也關心渲染線程。
  • UI Thread。這是標準的android測量/佈局/繪製發生的地方。右側的線程名稱將是您的包名稱(在我的情況下是book.adsmanager)或UI Thread。您在此線程上看到的事件看起來應該是這樣的,並且與Choreographer,遍歷和DispatchUI有關:
    UI Thread Example
  • JS Thread. 這是執行JavaScript的地方。線程名稱將是mqt_js或<...>,具體取決於設備上內核的協作程度。要識別它沒有名稱,請查找JSCall,Bridge.executeJSCall等內容:
    JS Thread Example
  • Native Modules Thread. 這是執行本機模塊調用(例如UIManager)的地方。線程名稱將是mqt_native_modules或<...>。要在後一種情況下識別它,請查找NativeCall,callJavaModuleMethod和onBatchComplete等內容:
    Native Modules Thread Example
  • Bonus: Render Thread. 如果你正在使用Android L(5.0)及更高版本,你的應用程序中也會有一個渲染線程。該線程生成用於繪製UI的實際OpenGL命令。線程名稱將是RenderThread或<...>。要在後一種情況下識別它,請查找DrawFrame和queueBuffer之類的內容:
    Render Thread Example

找出罪魁禍首

平滑的動畫應如下所示:
Smooth Animation
每種顏色的變化都是一個框架 - 請記住,為了顯示一個框架,我們所有的UI工作都需要在16ms週期結束時完成。請注意,沒有線程正在靠近幀邊界工作。像這樣渲染的應用程序渲染為60 FPS。

但是,如果你注意到了剁,你可能會看到這樣的東西:
Choppy Animation from JS
請注意,JS線程基本上一直在執行,跨越幀邊界!此應用程序無法以60 FPS呈現。在這種情況下,問題在於JS。

您可能還會看到以下內容:
Choppy Animation from UI
在這種情況下,UI和渲染線程是跨越框架邊界的工作。我們試圖在每個幀上呈現的UI需要做太多工作。在這種情況下,問題在於呈現的本機視圖。

此時,您將獲得一些非常有用的信息,以告知您的後續步驟。

解決JavaScript問題

如果您發現了JS問題,請在您正在執行的特定JS中查找線索。在上面的場景中,我們看到每幀多次調用RCTEventEmitter。這是從上面的跟踪中放大JS線程:
Too much JS
這似乎不對。為什麼經常這麼叫?他們真的是不同的事件嗎?這些問題的答案可能取決於您的產品代碼。很多時候,你會想要研究一下shouldComponentUpdate。

解決本機UI問題

如果您發現了本機UI問題,通常有兩種情況:
  1. 您嘗試繪製每個幀的UI涉及GPU上的太多工作,或者
  2. Y您正在動畫/交互期間構建新UI(例如,在滾動期間加載新內容)。
GPU 工作太多了
在第一個場景中,您將看到具有UI線程和/或渲染線程的跟踪,如下所示:
Overloaded GPU
請注意在DrawFrame中花費的大量時間跨越幀邊界。這是等待GPU從前一幀中耗盡其命令緩衝區所花費的時間。

要緩解這種情況,您應該:
  • 調查使用renderToHardwareTextureAndroid進行動畫/轉換的複雜靜態內容(例如Navigator  slide/ alpha animations)
  • 確保您沒有使用needsOffscreenAlphaCompositing,默認情況下禁用,因為它在大多數情況下會大大增加GPU上的每幀負載。
如果這些沒有幫助,並且您想深入了解GPU實際上在做什麼,您可以查看 Tracer for OpenGL ES.
在UI線程上創建新視圖
在第二個場景中,您將看到更像這樣的內容:
Creating Views
請注意,首先JS線程會考慮一下,然後您會看到在本機模塊線程上完成了一些工作,然後在UI線程上進行了昂貴的遍歷。

除非您能夠在交互之後推遲創建新UI,或者您能夠簡化您正在創建的UI,否則沒有一種簡單的方法可以緩解這種情況。 react本機團隊正在為此開發基礎架構級解決方案,允許在主線程之外創建和配置新UI,從而使交互順利進行。

分拆+內聯需要

如果您有一個大型應用程序,您可能需要考慮分拆和使用內聯需求。這對於具有大量屏幕的應用程序非常有用,這些應用程序在典型應用程序中可能無法打開。通常,對於具有大量代碼的應用程序非常有用,這些代碼在啟動後一段時間內不需要。例如,應用程序包括複雜的配置文件屏幕或較少使用的功能,但大多數會話僅涉及訪問應用程序的主屏幕以進行更新。我們可以通過使用打包器的unbundle功能來優化捆綁的加載,並且需要內聯的這些功能和屏幕(當它們實際使用時)。

加載 JavaScript

在react-native可以執行JS代碼之前,必須將該代碼加載到內存中並進行解析。如果加載50mb軟件包,則使用標準軟件包,必須先加載並解析所有50mb,然後才能執行任何50mb。分拆後的優化是您只能加載啟動時實際需要的50mb部分,並逐步加載更多的捆綁,因為需要這些部分。

內聯需求

內聯需要延遲模塊或文件的需求,直到實際需要該文件。一個基本的例子如下:

VeryExpensive.js

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.js

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>
    );
  }
}
即使沒有非捆綁內聯需求也可能導致啟動時間的改進,因為VeryExpensive.js中的代碼只會在第一次需要時執行。

啟用分拆

在iOS上,分拆將創建一個索引文件,本機將一次加載一個模塊。在Android上,默認情況下它會為每個模塊創建一組文件。您可以強制Android創建單個文件,例如iOS,但使用多個文件可以提高性能並且需要更少的內存。

通過編輯構建階段“Bundle React Native code and images”,在Xcode中啟用非捆綁。在../node_modules/react-native/packager/react-native-xcode.sh之前添加導出BUNDLE_COMMAND =“unbundle”:
export BUNDLE_COMMAND="unbundle"
export NODE_BINARY=node
../node_modules/react-native/packager/react-native-xcode.sh
在Android上通過編輯你的android / app / build.gradle文件啟用分拆。在該行申請之前:“../../ node_modules/react-native/react.gradle”添加或修改project.ext.react塊:
project.ext.react = [
  bundleCommand: "unbundle",
]
如果要使用單個索引文件,請在Android上使用以下行:
project.ext.react = [
  bundleCommand: "unbundle",
  extraPackagerArgs: ["--indexed-unbundle"]
]

配置預加載和內聯需求

既然我們已經解開了代碼,那麼調用require會有開銷。現在需要在遇到尚未加載的模塊時通過網橋發送消息。這將最大程度地影響啟動,因為這是應用程序加載初始模塊時可能發生的最大數量的require調用。幸運的是,我們可以配置一部分模塊進行預加載。為此,您需要實現某種形式的內聯需求。

添加打包程序配置文件

在項目中創建一個名為packager的文件夾,並創建一個名為config.js的文件。添加以下內容:
const config = {
  getTransformOptions: () => {
    return {
      transform: { inlineRequires: true },
    };
  },
};

module.exports = config;
在Xcode中,在構建階段,包括export BUNDLE_CONFIG="packager/config.js".
export BUNDLE_COMMAND="unbundle"
export BUNDLE_CONFIG="packager/config.js"
export NODE_BINARY=node
../node_modules/react-native/packager/react-native-xcode.sh
編輯你的 android/app/build.gradle 文件以包含 bundleConfig: "packager/config.js",.
project.ext.react = [
  bundleCommand: "unbundle",
  bundleConfig: "packager/config.js"
]
最後,您可以在package.json上的“scripts”下更新“start”以使用配置:
"start": "node node_modules/react-native/local-cli/cli.js start --config ../../../../packager/config.js",
使用 npm start 啟動包服務器。請注意,當開發包裝程序通過 xcode 和 react-native run-android 等自動啟動時,它不會使用 npm start,因此它不會使用配置。

調查加載的模塊

在根文件 (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/moduleNames.js
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);
運行應用程序時,您可以在控制台中查看已加載的模塊數量以及等待的數量。您可能想要閱讀moduleNames並查看是否有任何意外。請注意,在第一次引用導入時會調用內聯需求。您可能需要調查和重構以確保在啟動時僅加載所需的模塊。請注意,您可以在require上更改Systrace對象,以幫助調試有問題的需求。
require.Systrace.beginEvent = (message) => {
  if(message.includes(problematicModule)) {
    throw new Error();
  }
}
每個應用程序都不同,但只加載第一個屏幕所需的模塊可能是有意義的。滿意後,將 loadedModuleNames 的輸出放入名為 packager/moduleNames.js 的文件中。

轉換為模塊路徑

加載的模塊名稱使我們成為那裡的一部分,但我們實際上需要絕對模塊路徑,因此下一個腳本將設置它。使用以下命令將 packager/generateModulePaths.js 添加到項目中:
// @flow
/* eslint-disable no-console */
const execSync = require('child_process').execSync;
const fs = require('fs');
const moduleNames = require('./moduleNames');

const pjson = require('../package.json');
const localPrefix = `${pjson.name}/`;

const modulePaths = moduleNames.map(moduleName => {
  if (moduleName.startsWith(localPrefix)) {
    return `./${moduleName.substring(localPrefix.length)}`;
  }
  if (moduleName.endsWith('.js')) {
    return `./node_modules/${moduleName}`;
  }
  try {
    const result = execSync(
      `grep "@providesModule ${moduleName}" $(find . -name ${moduleName}\\\\.js) -l`
    )
      .toString()
      .trim()
      .split('\n')[0];
    if (result != null) {
      return result;
    }
  } catch (e) {
    return null;
  }
  return null;
});

const paths = modulePaths
  .filter(path => path != null)
  .map(path => `'${path}'`)
  .join(',\n');

const fileData = `module.exports = [${paths}];`;

fs.writeFile('./packager/modulePaths.js', fileData, err => {
  if (err) {
    console.log(err);
  }

  console.log('Done');
});
您可以通過 node packager/generateModulePaths.js 運行.
此腳本嘗試從模塊名稱映射到模塊路徑。但它並非萬無一失,例如,它忽略了平台特定的文件(* ios.js和* .android.js)。然而,基於初始測試,它處理95%的案例。運行時,一段時間後它應該完成並輸出一個名為packager / modulePaths.js的文件。它應包含與項目根目錄相關的模塊文件的路徑。您可以將modulePaths.js提交到您的倉庫,以便它可以運輸。

更新 config.js

返回 packager/config.js 我們應該更新它以使用我們新生成的 modulePaths.js 文件。
const modulePaths = require('./modulePaths');
const resolve = require('path').resolve;
const fs = require('fs');

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

module.exports = config;
配置中的preloadedModules條目指示哪些模塊應標記為由unbundler預加載。 加載bundle時,在任何需求甚至執行之前,會立即加載這些模塊。 黑名單條目表示不應該內聯這些模塊。 因為它們是預加載的,所以使用內聯需求沒有性能優勢。 事實上,每次引用導入時,javascript都會花費額外的時間來解析內聯需求。

測試和測量改進

您現在應該準備好使用非捆綁和內聯需求來構建您的應用。 確保測量啟動前後的時間。




You May Also Like

0 意見