資源包拆分背景
React Native 應(yīng)用默認(rèn)會(huì)將我們的 JS 代碼打包成一個(gè)文件,當(dāng)我們的 React Native 應(yīng)用變得很龐大了以后,一次性下載所有 JS 代碼往往耗時(shí)很長(zhǎng)。這時(shí)我們可能會(huì)想到可以通過(guò)按需加載來(lái)進(jìn)行優(yōu)化,而按需加載的首要任務(wù)就是對(duì)代碼進(jìn)行拆分。
通過(guò)分析ReactNative頁(yè)面的JSBundle文件, 可發(fā)現(xiàn)一個(gè)完整的ReactNative頁(yè)面代碼結(jié)構(gòu)可以分為模塊引用、模塊定義、模塊注冊(cè)三部分。
- 模塊引用:主要是全局模塊的定義,
- 模塊定義:主要是組件的定義(原生組件、自定義組件),
- 模塊注冊(cè):主要是初始化以及入口函數(shù)的執(zhí)行。
拆分方案
metro是 React Native 的官方打包工具,類似于 web 開發(fā)常用的打包工具 Webpack。在React Native 0.56版本 之后,metro開放了 --config <path/to/config> 參數(shù),可用于配置自定義的打包過(guò)程。本文的metro拆包方案是基于React Native 0.56以后的版本。
借助metro可以進(jìn)行JS Bundle 拆包:公共基礎(chǔ)包和業(yè)務(wù)包。多個(gè)業(yè)務(wù)共用一個(gè)公共基礎(chǔ)包,公共基礎(chǔ)包可以內(nèi)置在APP中并進(jìn)行預(yù)加載,用戶進(jìn)入某個(gè)業(yè)務(wù)時(shí),再進(jìn)行業(yè)務(wù)包的加載。

JS Bundle 拆包
公共基礎(chǔ)包
先建立一個(gè) common.js 文件,在里面引入了所有的基礎(chǔ)模塊。然后, metro 以這個(gè) common.js 為入口文件,打一個(gè) common.bundle 文件,同時(shí)要記錄所有的基礎(chǔ)模塊的 moduleId。
// common.js 示例
require('react');
require('react-native');
// ... 可以繼續(xù)引入更多的公共基礎(chǔ)模塊
為了避免 moduleId 重復(fù),目前業(yè)內(nèi)主流的做法是把模塊的文件路徑當(dāng)作 moduleId。metro 暴露了 createModuleIdFactory() 這個(gè)函數(shù),可以在這個(gè)函數(shù)里自定義moduleId 的生成邏輯。
// metro.common.config.js
const fs = require('fs');
function getModuleId(path) {
// 根據(jù)文件的相對(duì)路徑構(gòu)建 moduleId
const projectRootPath = __dirname;
let moduleId = path.substr(projectRootPath.length + 1);
return moduleId;
}
function createModuleIdFactory() {
return getModuleId
}
function processModuleFilter(module) {
const moduleId = getModuleId(module['path'])
// 把 moduleId 寫入 moduleIdList.txt 文件,記錄基礎(chǔ)模塊的moduleId
fs.appendFileSync('./moduleIdList.txt', `${moduleId}\n`);
return true
}
module.exports = {
serializer: {
createModuleIdFactory,
processModuleFilter,
},
};
然后運(yùn)行命令行命令打包即可:
# 打包平臺(tái):android 和 ios
# 打包配置文件:metro.common.config.js
# 打包入口文件:common.js
# 輸出路徑:bundle/common.android.bundle 和 bundle/common.ios.bundle
npx react-native bundle --platform android --config metro.common.config.js --dev false --entry-file common.js --bundle-output bundle/common.android.bundle
npx react-native bundle --platform ios --config metro.common.config.js --dev false --entry-file common.js --bundle-output bundle/common.ios.bundle
業(yè)務(wù)包構(gòu)建
對(duì)業(yè)務(wù)進(jìn)行打包,metro 的打包入口文件就是業(yè)務(wù)項(xiàng)目入口文件index.js。
// index.js
class App extends Component {
render() {
return (
<View>
<Text>
hello business
</Text>
</View>
)
}
}
AppRegistry.registerComponent("business", () => App);
注意要在打包過(guò)程中要過(guò)濾掉上一步記錄的基礎(chǔ)模塊的moduleId,這樣打包結(jié)果就只有業(yè)務(wù)代碼了。
metro 提供了 processModuleFilter() 這個(gè)方法,借助它可以實(shí)現(xiàn)模塊的過(guò)濾:
// metro.business.config.js
const fs = require('fs');
// 讀取 moduleIdList.txt,轉(zhuǎn)換為數(shù)組
const moduleIdList = fs.readFileSync('./moduleIdList.txt', 'utf8').toString().split('\n');
function getModuleId(path) {
// 根據(jù)文件的相對(duì)路徑構(gòu)建 moduleId
const projectRootPath = __dirname;
let moduleId = path.substr(projectRootPath.length + 1);
return moduleId;
}
function createModuleIdFactory() {
return getModuleId
}
function processModuleFilter(module) {
//const modulePath = getProjectPath(module['path'])
const moduleId = getModuleId(module['path'])
if (moduleIdList.indexOf(mouduleId) >= 0) {
// 過(guò)濾掉上一步記錄的基礎(chǔ)模塊的moduleId
return false;
}
return true;
}
module.exports = {
serializer: {
createModuleIdFactory,
processModuleFilter,
};
最后運(yùn)行命令行命令打包即可:
# 打包平臺(tái):android 和 ios
# 打包配置文件:metro.business.config.js
# 打包入口文件:index.js
# 輸出路徑:bundle/business.android.bundle 和 bundle/business.ios.bundle
npx react-native bundle --platform android --config metro.business.config.js --dev false --entry-file index.js --bundle-output bundle/business.android.bundle
npx react-native bundle --platform ios --config metro.business.config.js --dev false --entry-file index.js --bundle-output bundle/business.ios.bundle