基于splitChunk的React-Native的分包與加載

摘要

對React-Native包進行劃分是優(yōu)化App啟動和內存占用的關鍵處理步驟飞袋,為此提出了一種基于splitChunk的分包方式嚎卫。對原始React-Native項目的多入口entryPoint進行分包,而這些多入口entryPoint之間的共同依賴通過設置splitChunk配置來提取到新的bundle中倒信,在加載一個entryPoint對應的bundle時钳踊,首先遞歸加載該bundle依賴的其他bundle,然后再加載entryPoint自身Bundle大莫。使用splitChunk進行分包管理蛉腌,可以便捷地管理多個Bundle之間的依賴引用關系,保證在加載Bundle時僅僅加載當前Bundle所依賴的模塊只厘,避免加載多余模塊烙丛。實驗結果表明,本方法能夠對React-Native包進行合理劃分羔味,并最小化App啟動時基礎包的體積河咽,提高App啟動速度,并減少App啟動時的內存占用赋元。

相關工作

React-Native在分包時主要工作集中在依賴管理忘蟹,目前項目的分包方案只拆分出了一部分業(yè)務模塊飒房,在打包啟動包之前,將這部分拆分出來的模塊的引用依賴添加到了啟動包中媚值,但是啟動包并不依賴這些引用情屹,所以App啟動時加載了本不需要加載的module;此外杂腰,項目還存在一個體積較大的老業(yè)務模塊垃你,由于手工拆分引用復雜,所以暫時也放在了啟動包中喂很,這也造成了啟動包過大的問題惜颇。

Webpack4自帶的SplitChunksPlugin插件實現(xiàn)了Bundle包之間的依賴管理,借助于這一工具少辣,可以方便地管理多Bundle之間的依賴關系凌摄,在分包的時候可以直接將entryPoint抽取出來,而entryPoint的依賴則由splitChunk去分析漓帅;而且SplitChunksPlugin提供的多種配置參數(shù)锨亏,為Bundle的管理提供更多的靈活性。

采用splitChunk進行分包忙干,分包體積總和由8063KB增加到8166KB器予,但啟動包體積占比由95%降低到了16%,啟動包加載時間降低了61%捐迫,啟動后在WebKit Malloc Zone上的resident size降低了約72%乾翔,在iPhone6 iOS 11.3真機測試中,內存降低了約85MB施戴。

splitChunk的分包與加載

splitChunk

先了解一下splitChunk的相關概念[1]

  • chunkGroup反浓,由chunk組成,一個chunkGroup可以包含多個chunk赞哗,在生成/優(yōu)化chunk graph時會用到雷则;
  • chunk,由module組成肪笋,一個chunk可以包含多個module月劈,它是編譯打包后輸出的最終文件;
  • module涂乌,就是不同的資源文件艺栈,包含了你的代碼中提供的例如:js/css/圖片等文件,在編譯環(huán)節(jié)湾盒,webpack會根據(jù)不同module之間的依賴關系去組合生成chunk湿右。
    splitchunk是webpack4中的SplitChunksPlugin插件,webpack4使用SplitChunksPlugin插件來分析罚勾,先來看一下通過SplitChunksPlugins可以實現(xiàn)的功能毅人,對于如下a.js吭狡,b.js,c.js丈莺,d.js腳本:
// a.js

import add from './b.js
add(1, 2)
import('./c').then(del => del(1, 2))
// b.js

import mod from './d.js'

export default function add(n1, n2) {
  return n1 + n2
}
mod(100, 11)
// c.js
import mod from './d.js'
mod(100, 11)

import('./b.js').then(add => add(1, 2))
export default function del(n1, n2) {
  return n1 - n2
}
// d.js
export default function mod(n1, n2) {
    return n1 % n2
}

當前設置splitChunk參數(shù)如下:

optimization: {
    runtimeChunk: {
      name: 'bundle'
    }
  }

如果以a.js為入口進行打包划煮,最后的分包結果如下所示:


chunkGroup關系圖.png

上述4個腳本文件在編譯之后,生成如圖所示的結果:

  • 生成了兩個chunkGroup缔俄,entryPoint和chunkGroup2弛秋;
  • entryPoint這個chunkGroup只包含一個chunk,該chunk中包含a.js俐载,b.js和d.js這3個module蟹略;
  • entryPoint依賴chunkGroup2,chunkGroup2只包含一個chunk遏佣,該chunk中包含c.js這個module挖炬。

最終結果就是a.js,b.js和c.js合并打包為bundle1状婶,c.js單獨打包為bundle2意敛,在進入entryPoint時,由于entryPoint依賴于chunkGroup2膛虫,所以需要先加載chunkGroup2的chunk草姻,即bundle2,然后再加載entryPoint的chunk走敌,即bundle1碴倾。

分包

在splitChunk編譯之后逗噩,可以得到chunkGroup之間的依賴關系掉丽,以及chunkGroup中的chunk的基本信息,其中"modules"字段為當前chunk所包含的所有module异雁。由于chunk是打包的最終輸出捶障,所以我們可以通過Metro對chunk包含的module信息進行打包。

// chunk中的module信息
{
    "id": 0,
    "modules": [
        {
            "id": 1,
            "name": "./abc_test/b.js",
        },
        {
            "id": 2,
            "name": "./abc_test/d.js",
        },
        {
            "id": 3,
            "name": "./abc_test/a.js",
        }
    ]
}

加載

加載entryPoint

React-Native的Bundle加載應該是以業(yè)務邏輯為單位的纲刀,所以加載時應該以entryPoint為單位项炼,而加載entryPoint則是通過加載其內部的chunks來實現(xiàn)的。

"entrypoint": {
    "chunks": [
        0
    ],
}

上述打包結果entryPoint只有1個chunk示绊,id為0锭部,所以就加載該chunk對應的bundle;當entryPoint包含多個chunk時面褐,按照順序從前往后加載chunk拌禾。

加載chunk

entryPoint之間的依賴關系體現(xiàn)在了chunk的"children"這一字段中,children里面是當前chunk所在的chunkGroup依賴的chunkGroup的chunks展哭,源代碼看起來更清晰一些:

const children = new Set();
const childIdByOrder = chunk.getChildIdsByOrders();
for (const chunkGroup of chunk.groupsIterable) {
    for (const childGroup of chunkGroup.childrenIterable) {
        for (const chunk of childGroup.chunks) {
            children.add(chunk.id);
        }
    }
}

所以在加載chunk時需要將children中包含的chunk先加載進來湃窍,所以加載chunk是一個遞歸加載的過程闻蛀。如下所示,chunk 0依賴于chunk 1您市,所以需要先加載chunk 1觉痛,再加載chunk 0。

{
    "id": 0,
    "children": [
        1
    ],
    "modules": [
        {
            "id": 1,
            "name": "./abc_test/b.js",
        },
        {
            "id": 2,
            "name": "./abc_test/d.js",
        },
        {
            "id": 3,
            "name": "./abc_test/a.js",
        }
    ]
}

實驗

我們在打包之前茵休,先打一個引用react和react-native的包薪棒,包名為platformBase.ios.bundle。

// platformBase.ios.bundle
import 'react';
import 'react-native';

啟動包打包

在原方案中榕莺,由于一個老業(yè)務模塊的引用關系管理比較復雜盗尸,直接將這個3.7MB左右的老業(yè)務模塊包含到了啟動包中。此外帽撑,一些新模塊的引用被直接提取出來放在了啟動包中泼各,而這些依賴并不是啟動包必須引用的。

首先我們將老業(yè)務模塊的引用和新模塊依賴的引用從啟動包中刪除掉亏拉,然后把啟動入口JS文件作為entryPoint進行打包扣蜻,因為這是啟動包,我們也不需要使用splitChunk去提取公共引用及塘,直接將結果打在一個包中莽使。此時打包結果只有1個chunkGroup,內部包含1個chunk笙僚,將該chunk的打包結果記為0.ios.bundle芳肌。所以App在啟動時需要加載platformBase.ios.bundle和0.ios.bundle兩個包。

Bundle 體積
platformBase.ios.bundle 645KB
0.ios.bundle 703KB

經(jīng)過實驗測試肋层,依次加載兩個Bundle比合并起來加載要耗費更多的時間亿笤,所以我們將platformBase.ios.bundle和0.ios.bundle合并起來作為啟動包,記為merge.ios.bundle栋猖,體積為約為1.3MB净薛。

業(yè)務包打包

我們?yōu)槔蠘I(yè)務模塊創(chuàng)建一個模塊注冊入口頁,

import { AppRegistry } from 'react-native';
import BBB from '../xxx/pages';

AppRegistry.registerComponent('AAAA', () => BBB);

剩余的模塊入口頁保持不變蒲拉,將這些入口頁分別作為entryPoint肃拜,進行打包,

config.entry = {
    xxxx_entry0: './xxxxx/entry0.js',
    xxxx_entry1: './xxxxx/entry1.js',
    xxxx_entry2: './xxxxx/entry2.ts',
    xxxx_entry3: './xxxxx/entry3.ts',
    xxxx_entry4: './xxxxx/entry4.ts'
},

同時配置splitChunk參數(shù)如下雌团,

splitChunks: {
    minSize: 0,
    cacheGroups: {
        commons: {
            name: 'commons',
            chunks: 'all',
            minChunks: 2,
            priority: -20
        }
    }
}

目的是將這些入口模塊中引用至少2次的模塊抽取的commons里燃领,單獨作為一個chunk,單獨打一個Bundle锦援。這時需要注意猛蔽,在commons chunk中可能會包含啟動包merge.ios.bundle中已經(jīng)引用的module,所以在啟動包打包時雨涛,需要記錄下啟動包中包含的module枢舶,后續(xù)commons chunk在打包時需要過濾掉這些module懦胞。業(yè)務包打包結果如下:

Bundle 體積
0.ios.bundle 2.3MB
1.ios.bundle 3.7MB
2.ios.bundle 364KB
3.ios.bundle 192KB
4.ios.bundle 135KB

其中0.ios.bundle為業(yè)務模塊的公用依賴包,1.ios.bundle為老業(yè)務包凉泄,其他包為新的業(yè)務包躏尉。

結果分析

啟動包體積

原打包方案打包結果如下,

Bundle 體積
a.ios.bundle 7.6MB
b.ios.bundle 41KB
c.ios.bundle 142KB
d.ios.bundle 98KB

所有分包加起來體積為8063KB后众,其中a.ios.bundle作為啟動包胀糜,體積有7.3MB;而新的分包方案總分包加起來體積為8166KB蒂誉,其啟動包merge.ios.bundle體積僅有1.3MB教藻,體積縮小了82%。

App啟動Bundle加載時間對比

在iOS 11.3系統(tǒng)下iPhone6真機上測試啟動包加載時間右锨,兩種方案各進行5次測試括堤,原分包方案平均加載時間為4.17s,新分包方案平均加載時間為1.62s绍移,將加載時間降低了61%悄窃。


啟動Bundle加載時間比較.png

App啟動內存占用對比

在iOS 11.3系統(tǒng)下iPhone6真機上,原方案在App啟動后首頁露出physical footprint為155MB蹂窖,而新分包方案physical footprint為69MB轧抗,所以由縮小啟動包直接帶來了約85MB的內存優(yōu)化。

再通過iPhone XS iOS13.5模擬器查看App啟動后首頁露出時的Memory Graph對比瞬测,

Physical footprint對比
splitChunk分包 原分包方案
Physical footprint 88.3M 141.8M
Physical footprint (peak) 129.7M 202.7M
MALLOC ZONE對比
新分包方案
MALLOC ZONE VIRTUAL SIZE RESIDENT SIZE DIRTY SIZE
DefaultMallocZone_0x1058fd000 128.0M 9060K 8948K
MallocHelperZone_0x1058eb000 79.6M 17.0M 17.0M
WebKit Malloc_0x1081d5000 26.0M 21.4M 20.2M
QuartzCore_0x107620000 16.0M 340K 340K
NWMallocZone_0x1081e1000 3072K 40K 40K
TOTAL 252.6M 47.5M 46.3M
原方案
MALLOC ZONE VIRTUAL SIZE RESIDENT SIZE DIRTY SIZE
DefaultMallocZone_0x109a9b000 128.0M 9868K 9668K
WebKit Malloc_0x118105000 80.0M 77.4M 69.6M
MallocHelperZone_0x1088a5000 79.6M 16.8M 16.7M
QuartzCore_0x10b7bd000 16.0M 348K 348K
NWMallocZone_0x1177d9000 3072K 36K 36K
TOTAL 306.0M 104.2M 96.2M

從MACLLOC ZONE的角度來看横媚,新分包方案減少的內存主要集中在WebKit Malloc Zone,RESIDENT SIZE減少了約72%月趟。

總結

splitChunk可以構建React-Native分包之間的依賴關系灯蝴,并提供了更多的分包配置選項,靈活控制地Bundle的拆分狮斗,最終實現(xiàn)降低啟動Bundle的體積绽乔,加快App啟動的目的,并且減少App啟動時非必要的內存分配碳褒,提高App的存活幾率。

參考文獻

[1]: webpack系列之六chunk圖生成

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末看疗,一起剝皮案震驚了整個濱河市沙峻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌两芳,老刑警劉巖摔寨,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異怖辆,居然都是意外死亡是复,警方通過查閱死者的電腦和手機删顶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淑廊,“玉大人逗余,你說我怎么就攤上這事〖境停” “怎么了录粱?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長画拾。 經(jīng)常有香客問我啥繁,道長,這世上最難降的妖魔是什么青抛? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任旗闽,我火速辦了婚禮,結果婚禮上蜜另,老公的妹妹穿的比我還像新娘宪睹。我一直安慰自己,他們只是感情好蚕钦,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布亭病。 她就那樣靜靜地躺著,像睡著了一般嘶居。 火紅的嫁衣襯著肌膚如雪罪帖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天邮屁,我揣著相機與錄音整袁,去河邊找鬼。 笑死佑吝,一個胖子當著我的面吹牛坐昙,可吹牛的內容都是我干的。 我是一名探鬼主播芋忿,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼炸客,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了戈钢?” 一聲冷哼從身側響起痹仙,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎殉了,沒想到半個月后开仰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年众弓,在試婚紗的時候發(fā)現(xiàn)自己被綠了恩溅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡谓娃,死狀恐怖脚乡,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情傻粘,我是刑警寧澤每窖,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站弦悉,受9級特大地震影響窒典,放射性物質發(fā)生泄漏。R本人自食惡果不足惜稽莉,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一瀑志、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧污秆,春花似錦劈猪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庸推,卻和暖如春常侦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贬媒。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工聋亡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人际乘。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓坡倔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脖含。 傳聞我的和親對象是個殘疾皇子罪塔,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353