RN拆包解析

一. 拆包動機

RN作為非常優(yōu)秀的移動端跨平臺開發(fā)框架,在近幾年得到眾多開發(fā)者的認可击罪。國內(nèi)各大廠采用在當前原生應(yīng)用內(nèi)集成RN的方式芋簿,使得App應(yīng)用的靈活性得到了很大的提升。在原生應(yīng)用內(nèi)嵌入RN,就是需要在原生應(yīng)用內(nèi)加載RN模塊(1個或多個JSBundle)骏全,并得以顯示苍柏。JSBundle中包含了當前RN模塊的js代碼。如果存在多個RN模塊需要被加載時姜贡,就需要分別打出多個JSBundle试吁,并且多個JSBundle包含了很多重復的代碼(例如:第三方依賴)。拆包的方式楼咳,就是將其中重復不變的代碼打成基礎(chǔ)包熄捍,動態(tài)變化的打成業(yè)務(wù)包。那么就做到了JSBundle的拆分母怜。JSBundle的拆分余耽,對降低內(nèi)存的占用,減少加載時間苹熏,減少熱更新時流量帶寬等碟贾,在優(yōu)化方面起到了非常大的作用。

二.bundle簡要分析

1.bundle命令

  • entry-file:即入口文件轨域,打包時以該文件作為入口袱耽,一步步進行模塊分析處理。
  • platform:用于區(qū)分打包什么平臺的 bundle
  • dev:用于區(qū)分 bundle 使用環(huán)境疙挺,非 dev 時扛邑,會對代碼進行 minified
  • bundle-output:打包產(chǎn)物輸出地址,即打包好的 bundle 存放地址
  • sourcemap-output:打包時生成對應(yīng)的 sourcemap 文件存放地址铐然,在跟蹤查找錯誤或崩潰時蔬崩,能幫助開發(fā)快速定位到代碼
  • assets-dest:bundle 中使用的靜態(tài)資源文件存放地址

1.結(jié)構(gòu)分析

var 
__DEV__ = false,
__BUNDLE_START_TIME__ = this.nativePerformanceNow ? nativePerformanceNow() : Date.now(),
process = this.process || {};
process.env=process.env || {};
process.env.NODE_ENV = "production";
 
!(function(r) {
    "use strict";
 
    r.__r = o, 
 
    r.__d = function(r,i,n) {
        if(null != e[i]) 
            return;
        e[i] = {
            dependencyMap:n,factory:r,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}
        }
    },
 
    r.__c = n;
 
   .... 代碼省略
   
})();
 
 
__d(function(g,r,i,a,m,e,d){var n=r(d[0]),t=r(d[1]),o=n(r(d[2])),u=r(d[3]);t.AppRegistry.registerComponent(u.name,function(){return o.default})},0,[1,2,328,330]);
 
....省略其他 __d 代碼
 
__d(function(g,r,i,a,m,e,d){m.exports=function(t){if(t&&t.__esModule)return t;var o={};if(null!=t)for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){var c=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(t,n):{};c.get||c.set?Object.defineProperty(o,n,c):o[n]=t[n]}return o.default=t,o}},329,[]);
 
__d(function(e,s,t,a,n,N,d){n.exports={name:"RNTest",displayName:"RNTest"}},330,[]);
 
 
 
__r(79);
__r(0);

以最基礎(chǔ)的RN項目的 bundle 為例,可以看到 bundle 文件中大致定義了四個模塊:

(1)var 聲明的變量搀暑,對當前運行環(huán)境的定義沥阳,bundle 的啟動時間、Process進程環(huán)境相關(guān)信息

(2)(function() { })() 閉包中定義的代碼塊自点,其中定義了對 define(__d)桐罕、 require(__r)、clear(__c) 的支持桂敛,以及 module(react-native及第三方dependences依賴的module) 的加載邏輯

(3)__d 定義的代碼塊功炮,包括RN框架源碼 js 部分、自定義js代碼部分术唬、圖片資源信息薪伏,供 require 引入使用

(4)__r 定義的代碼塊,找到 __d 定義的代碼塊 并執(zhí)行

最終歸納出以下結(jié)構(gòu)


image

polyfills : 預加載粗仓,最早執(zhí)行的一些function嫁怀,聲明es語法新增的接口设捐,定義模塊聲明方法等
module difinitations : 模塊聲明,以__d開頭塘淑,一般為每一個js文件或資源文件萝招,將其封裝成一個module對象,并進行標號
require calls : bundle文件尾部指定入口文件存捺,如如require(79)槐沼,最后一行require(0);

ps:79可以找到是InitializeCore,這個加載了js-c++-java三層的通信注冊類捌治,通信臨聽類等

三.拆包方案

其他方案對比

  1. moles-packer

簡介:攜程大廠推出母赵,穩(wěn)定可靠,針對react native0.44時代的版本

優(yōu)點:重寫了react native自帶的打包工具具滴,重寫就是為了分包凹嘲,為分包而生的項目,肯定可靠

缺點:不持續(xù)維護更新构韵,只適合rn老版本用戶了周蹭,0.5以上的rn版本全部撲街

  1. 自己修改打包代碼

簡介:現(xiàn)在很多教程都是讓你去修改打包的源碼,在里面判斷分包疲恢,58的0.44版本就是這個方案

優(yōu)點:如果很懂打包源碼,這個做法靈活显拳,定制化強,100%沒問題

缺點:上手難杂数,需要完全理解打包源碼,網(wǎng)上的教程比較古老

  1. diff patch

簡介:大致的做法就是先打個正常的完整的jsbundle揍移,然后再打個只包含了基礎(chǔ)引用(react和第三方module)的基礎(chǔ)包,比對一下patch那伐,得出業(yè)務(wù)包,這樣基礎(chǔ)包和業(yè)務(wù)包都有了

優(yōu)點:簡單暴力罕邀,如果只是想簡單做下分包的可以嘗試下

缺點:1、不利于維護诉探,由于module后面都是rn生成數(shù)字,依賴變了數(shù)字也變阵具,導致基礎(chǔ)包變了所有包都需要變2、圖片沒法分包阳液,有的第三方庫是有圖片的,這個方法只處理jsbundle不處理圖片

Metro

在執(zhí)行 react-native bundle | unbundle 命令時帘皿,RN框架背后其實是依賴了 Metro-Bundler 來完成打包、加載任務(wù)鹰溜。Metro 作為一個獨立的打包工具,官方文檔 對于它的定義如下:

The JavaScript bundler for React Native.
Fast:Metro aims for sub-second reload cycles, fast startup and quick bundling speeds.
快:Metro旨在實現(xiàn)亞秒級重載循環(huán)曹动,快速啟動和快速捆綁速度。
Scalable:Works with thousands of modules in a single application.
可擴展:在單個應(yīng)用程序中使用數(shù)千個模塊墓陈。
Integrated:Supports every React Native project out of the box.
集成:支持開箱即用的每個React Native項目。

Metro 的高度可擴展性贡必,為我們提供了自由配置的打包方式。我們可以根據(jù)實際的需要來控制打包過程中的一些需求仔拟。官方為我們提供了很多種可配置的方式,可以使用以下三種方式創(chuàng)建Metro配置(按優(yōu)先級排序):

metro.config.js
metro.config.json
package.json中的 metro 字段
還可以通過在調(diào)用 CLI 時指定 --config <path / to / config> 來為配置提供自定義文件利花。

Metro中的常見配置結(jié)構(gòu)如下所示:

module.exports = { 
    resolver: { 
        /* resolver options */
    }, 
    transformer: { 
        /* transformer options */ 
    }, 
    serializer: { 
        /* serializer options */ 
    }, 
    server: {
        /* server options */
    }
    /* general options */ 
};

在打包過程中科侈,Metro-Bundler 幫助我們完成了全部工作,解析加載的過程如下:


image

項目中炒事,入口點文件(如 index.js)利用 import 依賴了其他組件兑徘。即組件間都是相互依賴的。

Resolution 代表 解析 的過程羡洛,負責梳理關(guān)聯(lián)js文件間的相互依賴關(guān)系挂脑。

Transformation 代表 轉(zhuǎn)換 的過程,負責將模塊文件轉(zhuǎn)換成平臺可理解的格式欲侮。

Serialization 代表 序列化 的過程崭闲,負責在完成轉(zhuǎn)換過程并將模塊轉(zhuǎn)換為可訪問的格式后,將其序列化威蕉。序列化程序?qū)⒛K組合在一起以生成一個或多個包刁俭。捆綁包實際上是一組模塊,組合成一個JavaScript文件韧涨。

更多關(guān)于配置的詳細信息可以查看(和諧翻墻):

(1) Configuring Metro

(2)Role of Metro Bundler in React native

核心修改項

拆包的核心思想就是將基礎(chǔ)包和業(yè)務(wù)包拆分牍戚。那么我們只需要使用如下兩個配置項即可:

createModuleIdFactory
用于生成 require 語句的模塊ID侮繁,配置 createModuleIdFactory 讓其每次打包的 module 使用固定的id(路徑相關(guān))。
參數(shù)是要打包的 module 文件的絕對路徑如孝,返回的是打包后的 module 的 id

processModuleFilter
起到過濾功能宪哩,用于從輸出中丟棄特定模塊。配置 processModuleFilter 過濾基礎(chǔ)包第晰,打出對應(yīng)業(yè)務(wù)包锁孟。
參數(shù)是 Module 信息,返回值是 boolean 類型 茁瘦,如果是 false 就過濾掉不進行打包

Metro Config 配置文件

在打包過程中品抽,我們需要依賴 createModuleIdFactory 、processModuleFilter 來幫助我們將JSBundle拆分為基礎(chǔ)包和業(yè)務(wù)模塊包甜熔。拆分的過程就需要我們通過配置 config 文件來完成圆恤。接下來我們來看看如何編寫 config 配置文件。

在編寫 config 配置文件之前腔稀,先來想個問題哑了,為什么要固定基礎(chǔ)包中的模塊ID( __r(id) )呢?

在上面我們貼出的bundle文件中烧颖,可以看到最底部有兩段代碼:

__r(79);
__r(0);

不同文件打出的 bundle弱左,最底部都為__r(0); 而上面的會隨著順序依次增加,例如以 index.js 文件打出的 bundle id 為 79炕淮,以 CustomComponent.js 打出的為 80拆火。

基礎(chǔ)包(common.bundle)
在打基礎(chǔ)包的時候,我們會把RN的基礎(chǔ)文件以及第三方的依賴打進去涂圆。當我們在打業(yè)務(wù)包的時候们镜,可能會做修改模狭,例如導入組件的順序發(fā)生變化嚼鹉,或者依賴版本做了更新等等驱富。都有可能導致ID發(fā)生變化,造成基礎(chǔ)包中不能找到對應(yīng)的模塊ID线脚,導致基礎(chǔ)包失效姊舵。所以需要將ID固定寓落。一種簡單的方式就是以模塊名稱作為 require 即可零如。所以配置 createModuleIdFactory 讓其每次打包的 module 使用固定的模塊名稱即可考蕾。

業(yè)務(wù)包 (bussiness.bundle)
在打業(yè)務(wù)包時肖卧,需要結(jié)合 createModuleIdFactory塞帐、processModuleFilter 同時進行葵姥。createModuleIdFactory負責固定 module 的ID句携。processModuleFilter 負責過濾掉基礎(chǔ)包的內(nèi)容模塊矮嫉。

createModuleIdFactory 源代碼

//node_modules/metro/src/lib/createModuleIdFactory.js 
"use strict";

function createModuleIdFactory() {
  const fileToIdMap = new Map();
  let nextId = 0;
  return path => {
    let id = fileToIdMap.get(path);
    if (typeof id !== "number") {
      id = nextId++;
      fileToIdMap.set(path, id);
    }
    return id;
  };
}

module.exports = createModuleIdFactory;

我們知道蠢笋,createModuleIdFactory 用于生成 require 語句的模塊ID,從上述源碼也可以看出瞻惋,系統(tǒng)使用整數(shù)型的方式熟史,從0開始遍歷所有模塊窄俏,并依次使 Id 增加 1凹蜈。所以我們可以修改此處邏輯,以模塊路徑名稱的方式作為Id即可计雌。

參考文檔

https://blog.csdn.net/u013718120/article/details/84571326

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凿滤,一起剝皮案震驚了整個濱河市庶近,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鼻种,老刑警劉巖叉钥,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枫疆,死亡現(xiàn)場離奇詭異敷鸦,居然都是意外死亡,警方通過查閱死者的電腦和手機钞螟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蟆淀,“玉大人熔任,你說我怎么就攤上這事疑苔。” “怎么了抢韭?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵刻恭,是天一觀的道長鳍贾。 經(jīng)常有香客問我交洗,道長藕筋,這世上最難降的妖魔是什么梳码? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任掰茶,我火速辦了婚禮濒蒋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓮顽。我一直安慰自己暖混,他們只是感情好翁授,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布贮配。 她就那樣靜靜地躺著泪勒,像睡著了一般酣藻。 火紅的嫁衣襯著肌膚如雪辽剧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音阐斜,去河邊找鬼谒出。 笑死笤喳,一個胖子當著我的面吹牛碌宴,可吹牛的內(nèi)容都是我干的贰镣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼休玩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起楼入,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤哥捕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嘉熊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遥赚,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年阐肤,在試婚紗的時候發(fā)現(xiàn)自己被綠了凫佛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讲坎。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖愧薛,靈堂內(nèi)的尸體忽然破棺而出晨炕,到底是詐尸還是另有隱情,我是刑警寧澤瓮栗,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布愿阐,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏腾供。R本人自食惡果不足惜节值,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一豌汇、第九天 我趴在偏房一處隱蔽的房頂上張望宛徊。 院中可真熱鬧苞氮,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搜贤,卻和暖如春耕陷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工尊浪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留誉结,地道東北人以舒。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像方妖,于是被迫代替她去往敵國和親镐牺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 引言 React Native以其獨到的特性,吸引著互聯(lián)網(wǎng)公司紛紛為之投入或多或少的人力风响。在實際的開發(fā)過程中辛友,開發(fā)...
    Jason景閱讀 10,775評論 4 25
  • 自從Facebook于2015年在React Conf大會上推出React Native,移動開發(fā)領(lǐng)域就掀起了一股...
    廚子閱讀 2,541評論 0 10
  • RN中掖看,發(fā)布js代碼時归榕,會打包成jsbundle形式外里。隨著業(yè)務(wù)的增大贼穆,jsbundle體積也會逐漸增大戴甩,特別是多M...
    生光閱讀 9,548評論 3 12
  • RN拆包工具是為了解決RN產(chǎn)出的bundler包文件過大問題的打包工具;可以按需將模塊按照基礎(chǔ)&業(yè)務(wù)生成兩個文件闪彼。...
    乘著風閱讀 5,418評論 2 22
  • 這周的桃子很乖巧甜孤,早早的做完了所有作業(yè),還認真的上了興趣班畏腕,得了十分的積分卡缴川,很棒哦,因為老師夸她最近有...
    桃寶媽閱讀 146評論 0 0