前端之路:RN拆包demo(js部分)

RN 拆包 demo(基於 react-native bundle 命令)

我不懂原生,只作 js 部分,有懂這方面的大佬,也能夠補充原生部分,git地址: github.com/Mr-jiangzhi…前端

環境:node

macOS: 10.14.4;react

node(nvm): 10.15.3;android

react-native-cli: 2.0.1;ios

react-native: 0.60.0;git

目錄結構

.
├── .babelrc
├── .buckconfig
├── .editorconfig
├── .eslintrc.js
├── .flowconfig
├── .git
├── .gitattributes
├── .gitignore
├── .patch (存放增量文件)
├── .watchmanconfig
├── README.md
├── __tests__
├── android (原生代碼,原生開發維護)
├── app.json
├── babel.config.js
├── bundles (bundle輸出目錄)
├── business.config.js (打包業務模塊的配置)
├── common.config.js (打包公共基礎模塊的配置)
├── common.js (公共基礎模塊的入口文件,將第三方依賴/shim等文件引入進來)
├── config (配置文件目錄)
├── index.js (開發模式的入口文件)
├── ios (原生代碼,原生開發維護)
├── jsconfig.json
├── package.json
├── scripts (腳本目錄)
├── src (前端頁面,前端開發維護)
├── upload (存放需上傳服務器的文件)
└── yarn.lock
複製代碼

js 部分

一般,咱們將一個大的 jsbundle 包拆分爲基礎包和業務包:github

  • 基礎包:承接 react,react-native 和一些第三方依賴,這些依賴通常不會有太多變更(畢竟 rn 開發大都鎖定依賴版本)
  • 業務包:承接業務模塊,通常有多個模塊(好比登陸模塊,我的中心模塊,。。。)

基於 react-native bundle 命令拆包(實際上是基於metro) 命令爲咱們提供了 --config 參數,讓咱們能夠本身指定配置文件,這樣咱們就能夠經過兩個配置文件(common 和 business)來分別進行打包了:npm

  • react-native bundle [其餘配置] --config common:打公共基礎包
  • react-native bundle [其餘配置] --config business:打業務包

下面咱們來看看 metro 的這個 config 文件:json

module.exports = {
  resolver: {
    /* resolver options */
  },
  transformer: {
    /* transformer options */
  },
  serializer: {
    /* serializer options */
  },
  server: {
    /* server options */
  },

  /* general options */
};
複製代碼

對於拆包咱們能用到的就是 serializer 中的 createModuleIdFactorypostProcessBundleSourcemap 這兩個方法:react-native

  • createModuleIdFactory

    • Used to generate the module id for require statements.
    • 用來生成模塊 id 的
  • postProcessBundleSourcemap

    • An optional function that can modify the code and source map of the bundle before it is written. Applied once for the entire bundle.
    • 可選函數,能夠在寫入以前修改包的代碼和源映射,用來過濾是否編譯

1. 在項目config目錄新建 createModuleIdFactory.jspostProcessBundleSourcemap.js

createModuleIdFactory.js:

/** * 生成模塊Id * 基礎打包配置 */
const path = require('path');
const pathSep = path.posix.sep;

function createModuleIdFactory() {
  const projectRootPath = process.cwd(); //獲取命令行執行的目錄

  return modulePath => {
    // console.log(modulePath)
    let moduleName = '';
    if (modulePath.indexOf(`node_modules${pathSep}react-native${pathSep}Libraries${pathSep}`) > 0) {
      //這裏是去除路徑中的'node_modules/react-native/Libraries/‘以前(包括)的字符串,能夠減小包大小,無關緊要
      moduleName = modulePath.substr(modulePath.lastIndexOf(pathSep) + 1);
    } else if (modulePath.indexOf(projectRootPath) === 0) {
      //這裏是取相對路徑,不這麼弄的話就會打出_user_smallnew_works_....這麼長的路徑,還會把計算機名打進去
      moduleName = modulePath.substr(projectRootPath.length + 1);
    }
    moduleName = moduleName.replace('.js', ''); //js png字符串不必打進去
    moduleName = moduleName.replace('.png', '');
    let regExp = pathSep === '\\' ? new RegExp('\\\\', 'gm') : new RegExp(pathSep, 'gm');
    moduleName = moduleName.replace(regExp, '_'); //把path中的/換成下劃線(適配Windows平臺路徑問題)

    // console.log(moduleName);

    return moduleName;
  };
}
module.exports = createModuleIdFactory;
複製代碼

postProcessBundleSourcemap.js:

// 業務代碼
const path = require('path');
const pathSep = path.posix.sep;
// 這裏簡單暴力地吧preclude和node_modules目錄下的文件所有過濾掉,只打本身寫的代碼。
// 只有本身寫的纔算是業務代碼
function postProcessModulesFilter(module) {
  //返回false則過濾不編譯

  if (module.path.indexOf('__prelude__') >= 0) {
    return false;
  }
  if (module.path.indexOf(pathSep + 'node_modules' + pathSep) > 0) {
    if (`js${pathSep}script${pathSep}virtual` === module.output[0].type) {
      return true;
    }
    if (
      module.path.indexOf(
        `${pathSep}node_modules${pathSep}@babel${pathSep}runtime${pathSep}helpers`
      ) > 0
    ) {
      //添加這個判斷,讓@babel/runtime打進包去
      return true;
    }
    return false;
  }
  return true;
}
module.exports = postProcessModulesFilter;
複製代碼

2. 在項目根目錄新建 common.config.jsbusiness.config.js

common.config.js:

// 基礎打包配置
const createModuleIdFactory = require('./config/createModuleIdFactory');

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: true,
        inlineRequires: false,
      },
    }),
  },
  serializer: {
    // 全部模塊一經轉換就會被序列化,Serialization會組合這些模塊來生成一個或多個包,包就是將模塊組合成一個JavaScript文件的包。
    // 函數傳入的是你要打包的module文件的絕對路徑返回的是這個module的id
    // 配置createModuleIdFactory讓其每次打包都module們使用固定的id(路徑相關)
    createModuleIdFactory: createModuleIdFactory,
    /* serializer options */
  },
};
複製代碼

business.config.js:

// 業務代碼
const createModuleIdFactory = require('./config/createModuleIdFactory');
const postProcessModulesFilter = require('./config/postProcessModulesFilter');

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: true,
        inlineRequires: false,
      },
    }),
  },
  serializer: {
    // 函數傳入的是你要打包的module文件的絕對路徑返回的是這個module的id
    createModuleIdFactory: createModuleIdFactory,
    // A filter function to discard specific modules from the output.
    // 數傳入的是module信息,返回是boolean值,若是是false就過濾不打包
    // 配置processModuleFilter過濾基礎包打出業務包
    processModuleFilter: postProcessModulesFilter,
    /* serializer options */
  },
};
複製代碼

3. 在項目config目錄新建 index.js,用於設置打包配置

index.js:

// 基礎打包配置
/** * version - js bundle版本,初始值是1,每次更新請手動加1 * common - 公共基礎包bundle * bundles - 業務模塊基礎包bundle * { "animate": true, // ios平臺會使用, 當從A頁面轉場到B頁面的時候,控制[self.navigationControllersetNavigationBarHidden: animated:];中的animate "statusBgColor": "#408EF5", //導航狀態欄的背景色 "type": "push", // 進入rn的形式(2種, push和present) "source": "Login", // 用於拆包打包時的入口entry_file "moduleName": "platform", // 模塊名稱,和 AppRegistry.registerComponent('platform', () => App);一一對應 "bundleName": "platform.bundle" // 此模塊打包出來的bundle名稱, 生產環境使用 } */
module.exports = {
  version: 1,
  common: {
    moduleName: 'platform',
    bundleName: 'platform.bundle',
  },
  bundles: [
    {
      animate: false,
      statusBgColor: '#408EF5',
      type: 'push',
      source: 'mine',
      moduleName: 'rbd_mine',
      bundleName: 'rbd_mine.bundle',
    },
    {
      animate: true,
      statusBgColor: '#ffffff',
      type: 'push',
      source: 'discover',
      moduleName: 'rbd_discover',
      bundleName: 'rbd_discover.bundle',
    },
  ],
};
複製代碼

4. 編寫node腳本,進行打包,包含熱更新(文件級別增量升級)

注:<> 表示必選,[] 表示可選,-v 默認取 config/index.js 的 version

目前是文件級別增量升級,下一步是內容級別增量升級(diff-match-patch

package.json 增長

"bin": {
  "rn": "./scripts/command.js"
},
複製代碼

在項目目錄執行 npm link,以後就能夠用 rn 腳手架命令了:

  • rn pack <platform> <type> [-v version]: 打包 android/ios 的 version 版本的 common/business 包,並壓縮至 upload 目錄
  • rn patch <platform> [-v version]: 打包 android/ios 的 version 版本的增量包,並壓縮至 upload 目錄
  • rn hash <target> [-v version]: target 能夠是文件路徑(此時無需-v 參數)、ios、android,獲取文件指紋(md5)

腳本內容有點多,請直接看代碼吧: command

爲了保證 jsbundle 版本和原生 native 版本保持同步,須要前端和原生人員共同維護一個配置文件,例如放到 firebase:

remote-config

Android 部分(等大佬補充)

Ios 部分(等大佬補充)

附:repo

===🧐🧐 文中不足,歡迎指正 🤪🤪===

相關文章
相關標籤/搜索