什么是JSBundle
JSBundle 是 JS代碼打包后的產(chǎn)物,在React-native里面主要是通過react-native-cli提供的命令進(jìn)行打包。跟網(wǎng)頁類似,一個RN項(xiàng)目除了代碼還會有資源文件,比如本地圖片、JSON等,這些都會放到跟JSbundle同級的assets目錄下。這些文件可以內(nèi)置在原生的工程里,在原生工程啟動時通過RN官方提供的方法加載JSbundle,并放到JS引擎中執(zhí)行。也可以打成壓縮包之后通過網(wǎng)絡(luò)下載(也就是熱更新)后再在本地執(zhí)行。
react-native-cli
React-native官方提供的命令行工具,里面包含了拉取react-native模版工程(init命令)、診斷運(yùn)行環(huán)境(doctor命令)、打包(bundle命令),以及遠(yuǎn)程調(diào)試(本地node服務(wù))所用到的代碼。默認(rèn)通過npm依賴的方式集成到react-native源碼中,也可以單獨(dú)下載(https://github.com/react-native-community/cli#documentation),具體命令可通過官方文檔或npx react-native --help 查閱
JSBundle格式
原始格式
原始格式是純文本的JS代碼,默認(rèn)情況下會進(jìn)行壓縮和混淆

當(dāng)打包時關(guān)閉掉-minify選項(xiàng)后,可以看到原始的代碼

JSbundle里每一行都是一個module,也就是一個文件的內(nèi)容,在每一行的末尾會有該module的ID和所依賴的其他module的ID

moduleId是根據(jù)編譯時的順序生成的,默認(rèn)是從0開始生成,按文件依次遞增。前面的內(nèi)容都是框架自帶的module,我們自己寫的module通常在后面。

本文是根據(jù)官方提供的awesome project模版拉取的代碼并生成的jsbunble,可以跟源碼對比看看有什么差異。
官方為了縮減JSbundle的大小,對很多函數(shù)做了簡化處理,例如declare變成了__d, require變成了__r。
Hermes二進(jìn)制格式
二進(jìn)制格式的本質(zhì)是字節(jié)碼,字節(jié)碼是JS轉(zhuǎn)成可執(zhí)行代碼的中間形式。由于JS代碼在 JS引擎里面需要編譯為字節(jié)碼或者機(jī)器碼才能執(zhí)行,這一階段比較耗時,而且每次啟動都是執(zhí)行,明顯是屬于重復(fù)工作。為了減少這個時間,官方推出了hermes二進(jìn)制格式,也就是我們說的字節(jié)碼,支持預(yù)編譯并且可以緩存在本地,減少二次編譯,甚至可以在生成JSbundle的時候就編譯為二進(jìn)制格式。關(guān)于字節(jié)碼的詳細(xì)解釋,可以參考我這篇博客(http://www.itdecent.cn/p/af772cc66428),這里就不詳細(xì)解釋了。
RAM格式
RAM也是一種二進(jìn)制格式,推出的目的是為了壓縮包大小,主要是將jsbundle按模塊拆分為單個的文件以支持按需加載,但是由于只支持iOS,并沒有真正推廣起來。想了解可以看官方介紹(https://facebook.github.io/metro/docs/bundling),不推薦深入研究。
打包腳本
我們使用react-native bundle命令來打包,假設(shè)打出來的包都放在 build 這個目錄下,我們需要執(zhí)行以下指令:
這生成index.android.bundle和index.android.bundle.packager.map,分別是JSbundle和sourceMap文件
npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./build/index.android.bundle --sourcemap-output ./build/index.android.bundle.packager.map生成hermes二進(jìn)制文件index.android.bundle.hbc及其與源碼的映射文件index.android.bundle.hbc.map(主要是記錄模塊的VLQ編碼與二進(jìn)制文件中對應(yīng)函數(shù)的偏移量的映射關(guān)系)。注意這里-output-source-map的值是上一步生成的JSBundle,并非我們通常所說的sourcemap文件。不同的版本hermesc的位置略有不同,可執(zhí)行
./node_modules/hermes-engine/osx-bin/hermesc -emit-binary -out ./build/index.android.bundle.hbc -output-source-map ./build/index.android.bundle
或
./node_modules/react-native/sdks/hermesc/osx-bin/hermesc -emit-binary -out ./build/index.android.bundle.hbc -output-source-map ./build/index.android.bundle根據(jù)第1步生成的sourcemap以及第2步生成的映射文件生成二進(jìn)制文件的sourcemap。這一步不是必須的,但是如果你想通過sentry之類的錯誤收集平臺來找到出錯的代碼,并且JSbundle是使用了hermes二進(jìn)制格式的,就一定要上傳這個sourceMap。
./node_modules/react-native/scripts/compose-source-maps.js ./build/index.android.bundle.packager.map ./build/index.android.bundle.hbc.map -o ./build/index.android.bundle.map
metro
metro是react-native專用的打包工具,有點(diǎn)類似web開發(fā)里面的webpack。前面說的react-native bundle命令背后就是用的metro(可參考代碼 https://github.com/react-native-community/cli/tree/main/packages/cli-plugin-metro),關(guān)于metro的使用可以參考官方文檔(https://facebook.github.io/metro/docs/concepts)
metro 大致可以分為resolver、transformer和Serialization三個階段,分別是解析源碼生成module的依賴圖、轉(zhuǎn)換module的格式已被目標(biāo)平臺所理解以及序列化生成最終產(chǎn)物,三個階段官方有提供默認(rèn)的實(shí)現(xiàn)(比如transformer是使用了babel),也提供了配置來替換一些關(guān)鍵函數(shù)。metro內(nèi)部會根據(jù)依賴圖的變化計(jì)算新增、修改和刪除的模塊,并且通過緩存transformer的結(jié)果來提升debug時的熱更新效率。關(guān)于metro有很多博客介紹,可以參考http://www.itdecent.cn/p/5730da61132f。

我們做拆包,主要是針對Serialization這個階段做修改,修改的函數(shù)包括createModuleIdFactory(自定義模塊ID的生成規(guī)則,確保唯一即可)、processModuleFilter(根據(jù)module信息判斷是否已經(jīng)處理過,打業(yè)務(wù)包需要)
hermesc
這是hermes的一個命令行工具,封裝了hemres用到的常用函數(shù),其中-emit-binary 功能是根據(jù)傳入的路徑找到j(luò)sbundle,加載內(nèi)容,然后一行一行的解析,將JS編譯為字節(jié)碼,同時生成映射文件。這個工具也支持dump字節(jié)碼、AST、IR以及解析JSX、ts等功能,可以說是非常全面了??梢暂斎?code>./node_modules/react-native/sdks/hermesc/osx-bin/hermesc --help 查看所有的命令