拆包是React-Native項目不得不面臨的一個重要技術門檻。
為什么要拆包?
bundle文件過大: 一個Helloworld的App,如果使用0.53RN 官方命令react-native bundle打包出來的JSBundle文件大小大約為530KB,RN依賴模塊本身占了99.9%。
頁面加載慢: 如果使用熱更新,從網絡獲取整個包的下載時間很長,每次進入RN頁面都需要執行RN基礎模塊的定義。
如果只是一個單獨的新APP,純RN的,或者只有一兩個業務使用,這點大小算不了什么。
但是對于很多業務的公司項目,如果每個業務的JSBundle都需要這么大的一個RN框架,那將是沒必要的。
Facebook官方有沒有解決方案呢?
Facebook的打包工具是metro, metro自帶unbundle命令,不過,打包結果不符合預期;
Android端是將每一個模塊輸出到單個文件并以模塊ID命名;IOS端是將模塊以流形式打到一個文件中;
方案
調研的方案可以分三類:
1.手動拆分&合并后再加載
使用RN打包工具打包,手動將文件分開分成基礎包A.js, 業務包B.js;然后在APP加載該頁面的時候將A,B兩個文件合并再執行;
優點:成本低,不需要改打包工具;
缺點:性能沒有提升,不能優化執行時間,反而會增加;
2.動態更新
可以從編譯產物看到require(moduleID);這個方案的思路是:編寫一個空的React容器組件,利用DeviceEventEmitter監聽一個Native事件,在需要加載頁面時,Native下載將模塊保存下來,以對應模塊ID命名,然后通過DeviceEventEmitter對應模塊的id發給React來調用require(msg.id),比如:
export default class App extends Component {
constructor(){
super();
this.state = {
dynamicModule : null
}
}
componentDidMount(){
DeviceEventEmitter.addListener('LoadBundle',(events) =>{
let moduleId = parseInt(events.moduleID, 10))
let dynamicModule = require(moduleId);
self.setState({ dynamicModule:dynamicModule });
});
}
render() {
const { dynamicModule } = this.state;
if (dynamicModule) {
return React.createElement(dynamicModule, this.props);
} else {
return (
<View><Text>空空的容器</Text></View>
);
}
}
}
但是在編譯前使用require(moduleID)會報錯,因為編譯前根本就未生成模塊Id,所以需要改動打包工具。
優點:性能好,能優化執行時間, 加載時只是執行了React容器組件的render()方法;
缺點:成本很大,需要修改Naive的RN源碼來重寫require方法,android在unbundle調用的是assets目錄下,不能通過網絡動態獲取,而且需要改打包工具。
3.動態執行
將bundle分為兩部分,在內存中先執行基礎模塊的定義,然后再需要打開頁面時,執行該頁面的業務模塊文件;
優點:性能好,能優化執行時間;
缺點:成本大, 需要改Native加載方式,打包工具也許修改。
Rabbit-bundler是按照此方案實現的打包工具。
具體請參考:Rabbit-bundler介紹