Taro跨端開發(fā)之多業(yè)務(wù)模塊管理 React Native篇(終篇)

React Native 熱更新方案

rn的業(yè)務(wù)越來(lái)越龐大,同時(shí)協(xié)同的團(tuán)隊(duì)越來(lái)越多. rn的動(dòng)態(tài)化就必須提上日程了.
對(duì)于rn熱更新,首當(dāng)其沖的問(wèn)題就是分包.

rn的基礎(chǔ)庫(kù)很大,再加上我們依賴了很多的三方庫(kù).這些代碼就必須在分包的時(shí)候單獨(dú)剝離出來(lái).
業(yè)務(wù)包讓他比較純粹的只有業(yè)務(wù)代碼. 這樣就可以保證業(yè)務(wù)包的體積比較小,保證熱更新時(shí)候的速度.

使用metro分包

React Native 提供的 metro 自帶分包功能。metro我們本來(lái)就一直在用,只要在metro打包的時(shí)候,提供相應(yīng)的打包規(guī)則.
就可以實(shí)現(xiàn)rn的分包了.

示例: ios打包

node ./node_modules/react-native/local-cli/cli.js bundle --platform ios --dev false --entry-file rn入口文件.js --bundle-output ./xxx/ --assets-dest ./xxx/ --config /{你的絕對(duì)路徑}/你的metro配置文件.js

metro 關(guān)鍵api介紹

我們分包需要用的選項(xiàng)主要是兩個(gè):

  • createModuleIdFactory:這個(gè)函數(shù)傳入要打包的 module 文件的絕對(duì)路徑鳍征,返回這個(gè) module 在打包的時(shí)候生成的 id。

  • processModuleFilter:這個(gè)函數(shù)傳入 module 信息,返回一個(gè) boolean 值沙咏,false 則表示這個(gè)文件不打入當(dāng)前的包食茎。

主工程分包

之前我們有提到過(guò)我們有一個(gè)項(xiàng)目是主工程,里面沒(méi)有任何的業(yè)務(wù)代碼.只有一些代碼運(yùn)行需要的所有依賴.

我們需要將所有的依賴全部收集起來(lái),當(dāng)業(yè)務(wù)模塊打包的時(shí)候,發(fā)現(xiàn)本地有這個(gè)依賴就可以使用 processModuleFilter方法排除掉.

因?yàn)槲覀兊闹鞴こ膛c業(yè)務(wù)項(xiàng)目的依賴版本都是高度統(tǒng)一的.
所以我們node_modules下面的依賴包路徑都是完全一致的.

Taro跨端開發(fā)之依賴管理

主工程的metro配置文件示例:

function createModuleIdFactory() {
    return path => {
        // 在這里我們拿到依賴的文件路徑,
        // 我們需要在這個(gè)函數(shù)塊中,將路徑以收集并且將這些數(shù)據(jù)生成文件
        // 部署到我們內(nèi)網(wǎng)的服務(wù)器中
        // 當(dāng)業(yè)務(wù)模塊需要打包的時(shí)候,是否要將代碼打進(jìn)包中,將以這個(gè)文件為依據(jù)
      return path;
    };
}

module.exports = {
    serializer: {
      createModuleIdFactory:createModuleIdFactory
    }
};

主工程入口文件示例:

// 這個(gè)文件我們會(huì)引入所有我們要用到的rn依賴,因?yàn)檫@些不常更新.
// metro打包的時(shí)候,會(huì)收集這些依賴的路徑
// 保證業(yè)務(wù)包打包的時(shí)候,不會(huì)重復(fù)打入
import React, {Component} from'react';
import { DeviceEventEmitter,Platform, NativeEventEmitter, NativeModules } from 'react-native';
import { SmartAssets } from "react-native-smartassets";
import 'moment/locale/zh-cn';
import 'react-navigation';
import '@tarojs/components-rn';
import '@tarojs/taro-router-rn';
import '@tarojs/taro-rn';
import '@tarojs/async-await';
import "@tarojs/mobx-rn";
import "dayjs";
import "querystring";
import "react-native-check-box";
import "classnames";
import "lodash.orderby";
import "react-native-swipe-list-view";

SmartAssets.initSmartAssets();
DeviceEventEmitter.addListener('sm-bundle-changed',
    (bundlePath)=>{
        SmartAssets.setBundlePath(bundlePath);
    });

if(Platform.OS != 'android') {//ios
    const {BundleloadEventEmiter} = NativeModules;

    const bundleLoadEmitter = new NativeEventEmitter(BundleloadEventEmiter);

    // eslint-disable-next-line no-unused-vars
    const subscription = bundleLoadEmitter.addListener(
        'BundleLoad',
        (bundleInfo) => {
            console.log('BundleLoad==' + bundleInfo.path);
            SmartAssets.setBundlePath(bundleInfo.path);
        }
    );
}

require('react-native/Libraries/Core/checkNativeVersion');

業(yè)務(wù)模塊的分包

業(yè)務(wù)模塊打包主要是排除主模塊的依賴.

業(yè)務(wù)模塊的metro配置文件示例:

const pathSep = require('path').sep;
const platformMap = require('業(yè)務(wù)包的打包數(shù)據(jù)');

let entry;

function postProcessModulesFilter(module) {
  const projectRootPath = __dirname;
  // 如果業(yè)務(wù)包沒(méi)有數(shù)據(jù),進(jìn)程直接退出,
  // 避免打入不必要的代碼
  if (platformMap == null || platformMap.length == 0) {
    console.log('請(qǐng)先打基礎(chǔ)包');
    process.exit(1);
    return false;
  }
  const path = module['path']

  // 特殊的模塊也需要排除
  if (path.indexOf("__prelude__") >= 0 ||
    path.indexOf("/node_modules/react-native/Libraries/polyfills") >= 0 ||
    path.indexOf("source-map") >= 0 ||
    path.indexOf("/node_modules/metro/src/lib/polyfills/") >= 0) {
    return false;
  }

  if (module['path'].indexOf(pathSep + 'node_modules' + pathSep) > 0) {
    if ('js' + pathSep + 'script' + pathSep + 'virtual' == module['output'][0]['type']) {
      return true;
    }else if(platformMap.includes(name)){
     // 如果之前主工程已經(jīng)打過(guò)包的模塊,進(jìn)行排除 返回 false
      return false;
    }
  }
  // 沒(méi)有特殊情況,則可以正常打包
  return true;
}

function createModuleIdFactory() {
  const projectRootPath = __dirname;
  return path => {
    let name = getModuleId(projectRootPath,path,entry,true);
    return name;
  };
}

function getModulesRunBeforeMainModule(entryFilePath) {
  entry = entryFilePath;
  return [];
}

module.exports = {
  serializer: {
    createModuleIdFactory: createModuleIdFactory,
    processModuleFilter: postProcessModulesFilter,
    getModulesRunBeforeMainModule:getModulesRunBeforeMainModule
    /* serializer options */
  }
};


分包部署與下發(fā)客戶端

所有類型的包打完之后,我們會(huì)壓縮成zip包,部署到cdn上.
當(dāng)客戶端檢測(cè)到有模塊已經(jīng)更新,則會(huì)從cdn上拉取相對(duì)應(yīng)的代碼包.

客戶端相關(guān)的加載方案我們借鑒的是以下項(xiàng)目:
https://github.com/smallnew/react-native-multibundler

客戶端加載順序

客戶端必須加載主工程的代碼完成之后,才可以加載業(yè)務(wù)包.
這是必要條件,不然會(huì)直接閃退.

客戶端熱更新容錯(cuò)

為了保證客戶端在任何時(shí)候都可以正常運(yùn)行,我們會(huì)在客戶端發(fā)版的時(shí)候?qū)⑺蟹职蜻M(jìn)客戶端中.當(dāng)客戶端熱更新失敗的時(shí)候,將會(huì)回滾到最初打進(jìn)客戶端的代碼.

以下是可能會(huì)導(dǎo)致更新失敗的場(chǎng)景,我們都會(huì)回滾渲染:

  • 獲取代碼包超時(shí)

  • 當(dāng)md5不匹配時(shí)

  • 當(dāng)代碼解壓失敗時(shí)

  • 代碼包版本號(hào)不合法

  • 首次進(jìn)入就閃退

還有很多場(chǎng)景,這里就不一一列出了.
但是這一塊要絕對(duì)重視,一旦出錯(cuò),就會(huì)導(dǎo)致客戶端直接閃退.

尾巴

為了保證獨(dú)立開發(fā),獨(dú)立部署,我們的CI/CD根據(jù)這一套打包方案也做了很多的定制與開發(fā).這里就不展開篇幅來(lái)講了,分包其實(shí)很簡(jiǎn)單,最重要的還是客戶端的穩(wěn)定性.

在用戶手機(jī)上會(huì)出現(xiàn)非常多意想不到的極端環(huán)境.如何保證運(yùn)行時(shí)的穩(wěn)定性.

這里需要大家好好思考.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丹锹,一起剝皮案震驚了整個(gè)濱河市稀颁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楣黍,老刑警劉巖匾灶,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異租漂,居然都是意外死亡阶女,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門窜锯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)张肾,“玉大人芭析,你說(shuō)我怎么就攤上這事锚扎。” “怎么了馁启?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵驾孔,是天一觀的道長(zhǎng)芍秆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)翠勉,這世上最難降的妖魔是什么妖啥? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮对碌,結(jié)果婚禮上荆虱,老公的妹妹穿的比我還像新娘。我一直安慰自己朽们,他們只是感情好怀读,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骑脱,像睡著了一般菜枷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叁丧,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天啤誊,我揣著相機(jī)與錄音,去河邊找鬼拥娄。 笑死蚊锹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的稚瘾。 我是一名探鬼主播枫耳,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孟抗!你這毒婦竟也來(lái)了迁杨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤凄硼,失蹤者是張志新(化名)和其女友劉穎铅协,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摊沉,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狐史,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了说墨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骏全。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尼斧,靈堂內(nèi)的尸體忽然破棺而出姜贡,到底是詐尸還是另有隱情,我是刑警寧澤棺棵,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布楼咳,位于F島的核電站熄捍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏母怜。R本人自食惡果不足惜余耽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苹熏。 院中可真熱鬧碟贾,春花似錦、人聲如沸轨域。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疙挺。三九已至扛邑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐然,已是汗流浹背蔬崩。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搀暑,地道東北人沥阳。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像自点,于是被迫代替她去往敵國(guó)和親桐罕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355