React Native 打production包過濾掉測試代碼

React Native 打production包過濾掉測試代碼

先說結論:能夠經過 React Native 中提供的 __DEV__ 變量,在產出 production 環境的代碼包時,過濾掉一些測試的頁面。javascript

環境說明java

"react": "16.8.3",
"react-native": "0.59.3",
複製代碼

問題

咱們的RN代碼目錄大概是這樣:react

./
├── common    公共代碼目錄
├── entry     
├── index.js  
├── page      頁面代碼目錄
複製代碼

在咱們的RN代碼裏,有一些公共組件,好比 Button LoadingDialog 之類的,放置在 common 目錄下。組件開發完以後,通常會在頁面中,增長一個對應的測試頁面,好比要測試 LoadingDialog,通常會增長一個 page/test/loadingTest.tsx 文件,在這個頁面裏,能夠測試下組件的各類功能是否OK。android

page/index.ts 裏,以前會有工具,自動掃描 page/**/**.tsx 生成全部的頁面信息,固然裏面也會包含不少在開發中用到的測試頁面。git

這個 page/index.ts的內容以下:github

import accountBindCardBindCard from './account/bindCard/BindCard';
import testMainMain from './test/main/Main';
import testMarqueeMarquee from './test/marquee/Marquee';
import testStyleTestLoading from './test/styleTest/Loading';
import testStyleTestModal from './test/styleTest/Modal';
import testStyleTestTextAnimation from './test/styleTest/TextAnimation';
import testStyleTestTextTest from './test/styleTest/TextTest';
import testStyleTestWithdrawResult from './test/styleTest/WithdrawResult';
import uplanInvestTypeInvestType from './uplan/investType/InvestType';
import uplanJoinJoin from './uplan/join/Join';

export default {
    'account/bindCard/BindCard': accountBindCardBindCard,
    'Main': testMainMain,
  	 // 下面這些 test 目錄下的頁面,都是測試頁面
    'test/marquee/Marquee': testMarqueeMarquee,
    'test/styleTest/Loading': testStyleTestLoading,
    'test/styleTest/Modal': testStyleTestModal,
    'test/styleTest/TextAnimation': testStyleTestTextAnimation,
    'test/styleTest/TextTest': testStyleTestTextTest,
    'test/styleTest/WithdrawResult': testStyleTestWithdrawResult,
     // 目標就是,在打production包的時候,幹掉這些測試頁面,減少包大小
    'uplan/investType/InvestType': uplanInvestTypeInvestType,
    'uplan/join/Join': uplanJoinJoin,
};
複製代碼

以前的處理方式,是在打包時候,調用一個本身封裝的打包JS腳本,這個腳本會先掃描 page 目錄,若是當前是 production 模式,即 --dev false ,那麼在生成上面的 page/index.ts 文件內容時,會過濾掉 page/test 目錄下的頁面。shell

咱們準備在下一次RN升級的時候,幹掉本身開發的JS腳本,改回到直接調用RN官方的打包腳本。react-native

所以,須要有一種方式,可以在 developmentproduction 環境下,容許咱們有選擇地加載某些頁面。工具

使用 __DEV__

很容易想到,RN官方就提供了一個全局變量,__DEV__ 來代表當前是否production模式。測試

想法很簡單,不能直接在 page/index.tsimport 測試頁面了,須要根據 __DEV__ 來決定是否去import。可是在實際測試過程當中,仍是發現了一些問題,最後總結下來,我大概測試瞭如下幾種狀況:

export default {
    get 'account/bindCard/BindCard'() { return require('./account/bindCard/BindCard').default; },
    // 下面是幾種寫法
    get 'test/story/StoryDisplay'() {
        if (__DEV__) {
            return require('./test/story/StoryDisplay').default;
        }
        return null;
    },
    get 'test/styleTest/Loading'() {
        if (__DEV__ !== true) {
            return null;
        }
        return require('./test/styleTest/Loading').default;
    },
    get 'test/styleTest/Modal'() {
        if (!__DEV__) {
            return null;
        }
        return require('./test/styleTest/Modal').default;
    },
    get 'test/styleTest/TextAnimation'() {
        if (__DEV__) {
            return require('./test/styleTest/TextAnimation').default;
        } else {
            return null;
        }
    },
    get 'test/styleTest/TextTest'() {
        if (!__DEV__) {
            return null;
        } else {
            return require('./test/styleTest/TextTest').default;
        }
    },
    // 上面是測試幾種寫法的實際效果
    get 'test/styleTest/WithdrawResult'() { if (__DEV__) { return require('./test/styleTest/WithdrawResult').default; } return null; },
    get 'uplan/investType/InvestType'() { return require('./uplan/investType/InvestType').default; },
    get 'uplan/join/Join'() { return require('./uplan/join/Join').default; },
};

複製代碼

其實仔細看下來,就是兩種寫法,require 是寫在 if 內部仍是外面。

先來看下 development 模式下的bundle代碼,訪問本地URL http://localhost:8081/index.bundle?platform=android&dev=true&minify=false :

get 'test/story/StoryDisplay'() {
      if (__DEV__) {
        return _$$_REQUIRE(_dependencyMap[58], "./test/story/StoryDisplay").default;
      }
      return null;
    },
      
    get 'test/styleTest/Loading'() {
      if (__DEV__ !== true) {
        return null;
      }
      return _$$_REQUIRE(_dependencyMap[59], "./test/styleTest/Loading").default;
    },

    get 'test/styleTest/Modal'() {
      if (!__DEV__) {
        return null;
      }
      return _$$_REQUIRE(_dependencyMap[60], "./test/styleTest/Modal").default;
    },

    get 'test/styleTest/TextAnimation'() {
      if (__DEV__) {
        return _$$_REQUIRE(_dependencyMap[61], "./test/styleTest/TextAnimation").default;
      } else {
        return null;
      }
    },

    get 'test/styleTest/TextTest'() {
      if (!__DEV__) {
        return null;
      } else {
        return _$$_REQUIRE(_dependencyMap[62], "./test/styleTest/TextTest").default;
      }
    },

複製代碼

能夠看出,在 dev=true 模式下,全部的測試頁面都被RN打包了,上面幾種源碼的寫法,產出沒什麼差異。

下面再看看 production 模式的產出bundle,訪問URL http://localhost:8081/index.bundle?platform=android&dev=false&minify=false

get 'test/story/StoryDisplay'() {
      return null;
    },

    get 'test/styleTest/Loading'() {
      {
        return null;
      }
      return _$$_REQUIRE(_dependencyMap[57]).default;
    },

    get 'test/styleTest/Modal'() {
      {
        return null;
      }
      return _$$_REQUIRE(_dependencyMap[58]).default;
    },

    get 'test/styleTest/TextAnimation'() {
      {
        return null;
      }
    },
      
    get 'test/styleTest/TextTest'() {
      {
        return null;
      }
    },

複製代碼

Hmm, interesting🤔,能夠明顯看出,有的寫法最終打包的結果,並非咱們意料之中的。

顯然,上面的 test/styleTest/Loadingtest/styleTest/Modal有問題 的。這時候在bundle裏搜索這兩個頁面的代碼,能夠發現這兩個頁面代碼已經被打包到了bundle裏(廢話,均可以看到 _$$_REQUIRE(_dependencyMap[58]).default 這樣的字眼了…… )

仔細對比這兩種有問題的代碼,和其餘幾種寫法,很容易發現,有問題的代碼, require沒有 放到 if/else 的分支裏。

結論

在 React Native 中,能夠經過 __DEV__ 在不一樣的環境(development仍是 production)來有選擇地加載一些JS文件。可是 require 的分支,必須 放在對應的 if(__DEV__){}else{} 裏,這樣才能被RN打包的時候過濾掉。猜想RN打包時,是會刪除 if/else 裏的死碼;可是在 if/else 以外的 require,即便永遠不會被執行到,也會被 靜態分析 出來,從而將對應代碼打到最終的bundle文件裏。

關於 React (Native) 的死碼移出,感興趣的能夠看這篇譯文:github.com/sophister/2…

相關文章
相關標籤/搜索