RN拆包工具:Rabbit-bundler

作者: 乘着风 | 来源:发表于2018-06-17 11:53 被阅读238次

    rabbit-bundler是为了解决RN产出的bundler包文件过大问题的打包工具;可以按需将模块按照基础&业务生成两个文件。

    GitHub: https://github.com/zoupeng3/metro/tree/rabbit

    rabbit

    思路

    基于RN官方的打包工具使用unbundle platform android已经可以拆包,不过是一个模块一个文件,文件数量过多。

    #RN unbundle android 的使用 example:
    react-native unbundle --platform android --dev true --entry-file index.js --bundle-output dist/metro_android_bundle.js
    

    基础此命令进行修改,将多个单独的模块文件按照“需要”合并到一起;
    尝试按照注释或者模块属性,将模块划分到base.js bussiness.js中。

    分析

    我们先从几个角度了解一下RN的打包工具:

    unbundle android 与 unbundle ios 的区别

    ios是流文件, android是生成一个基础文件,
    模块文件夹js-modules,文件夹中包含以react-native为首的组件,包括业务组件

    unbundle android 与 bundle android 的区别

    unbundle 文件跟bundle文件开始都一摸一样,
    都是定义模块化方法和bable配置,
    但是在unbundle在定义模块的时候截止了,将React-native等模块的定义单独拿到了js-modules文件夹中以ID命名,一个模块一个文件,
    调用了require(11); 11.js中写的是入口文件index.js _reactNative.AppRegistry.registerComponent

    RN模块化

    打包之后JSBundle文件的结构,基本分为3部分 头部:全局定义,主要是define,require等全局模块的定义; 中间:模块定义,RN框架和业务的各个模块定义; 尾部:引擎初始化和入口函数执行;

    __d是RN自定义的define,符合CommonJS规范,__d后面的数字是模块的id,是在RN打包过程中,解析依赖关系,自增长生成的。

    如果所有业务代码,都遵照一个规则:入口JS文件首先require的都是react/react-native, 则打包生成的JSBundle里面react/react-native相关的模块id都是固定的。

    打包产物:Bunlde类

    RN的bundle在生成文件之前是一个对象,它的定义就在:metro/src/Bundler/index.js中。让我们看下它打印出来的结构:

    const bundle = {
      "groups": {},
      "startupModules": [{...},{...}], 
      "lazyModules": [
        {
          "id": 11,
          "code": "__d(function (global, _require, module, exports, _dependencyMap) {\n  var _reactNative = _require(_dependencyMap[0], \"react-native\");\n\n  var _App = _require(_dependencyMap[1], \"./App\");\n\n  var _App2 = babelHelpers.interopRequireDefault(_App);\n\n  _reactNative.AppRegistry.registerComponent('divider', function () {\n    return _App2.default;\n  });\n},11,[12,337],\"index.js\");",
          "map": {
            "version": 3,
            "file": "/Users/pengzou/workspace/exercise/rn/divider/index.js",
            "sources": [
              "/Users/pengzou/workspace/exercise/rn/divider/index.js"
            ],
            "sourcesContent": [
              "import { AppRegistry } from 'react-native';\nimport App from './App';\n\nAppRegistry.registerComponent('divider', () => App);\n"
            ],
            "names": [
              "AppRegistry",
              "registerComponent",
              "App"
            ],
            "mappings": ";AAAA;;AACA;;;;AAEAA,2BAAYC,iBAAZ,CAA8B,SAA9B,EAAyC;AAAA,WAAMC,aAAN;AAAA,GAAzC"
          },
          "name": "index.js",
          "sourcePath": "/Users/pengzou/workspace/exercise/rn/divider/index.js",
          "source": "import { AppRegistry } from 'react-native';\nimport App from './App';\n\nAppRegistry.registerComponent('divider', () => App);\n",
          "type": "module"
        },{...}]
    };
    

    其中startupModules, RN基础模块,模块化方法定义等;lazyModules是react,入口,业务等模块;
    每个模块对象的重要属性:id:模块ID, code:代码,name:文件名等等;

    RN的unbundle命令Android端是将startupModules模块合并到一个文件并在最后一行增加对入口的调用;将lazyModules数组中的模块单独输出到单独文件并以模块id命名保存到js-modules文件夹下。

    实践

    RN的官方打包工具是metro. 让我们在node_modules/metro 直接修改代码调试:

    1. 使用unbundle 将模块路径包含node_modules字符串的模块视为RN的模块,将这些模块从lazyModules移入startupModules;
    2. 将调用index.js的require(id),即原startupModules中最后一句,提取出来;
    3. 将lazyModules多个模块合并成一个文件;
    4. 将require(id)填入lazy.bundle;
    5. 将require(InitializeCore模块Id)填入lazy.bundle;
    6. 使得platform ios走Android的unbundle

    细节可以参考项目中两个核心文件:

    • rabbit/src/shared/output/unbundle/rabbit-util.js
    • rabbit/src/shared/output/unbundle/as-assets.js

    代码地址: https://github.com/zoupeng3/metro/tree/rabbit

    rabbit工程化步骤

    1. 获取RN打包工具代码:fork facebook/metro
    2. 创建特性分支:git checkout -b rabbit
    3. 编译metro的flow文件:babel packages/metro/ -d rabbit/
    4. 将rabbit/build文件作为新的工具rabbit-bunlder 发布的npm

    如何使用rabbit

    #1.安装
    npm i rabbit-bundler
    #2. 使用rabbit自带脚本修改react-native中对metro的调用
    node node_modules/rabbit-bundler/upgrade.js
    #3. 与RN自带的命令相同只是增加rabbit-id参数
    react-native unbundle … —rabbit-id 5858
    

    其中第二步的脚本对react-native做了两次小改动:

    //1.替换metro
    //react-native/local-cli/bundle/unbundle.js
     -- 
     const outputUnbundle = require('metro/src/shared/output/unbundle');
     ++
     const outputUnbundle = require('rabbit-bundler/src/shared/output/unbundle');
    
    //2.新增命令参数用于配置lazyId
    //react-native/local-cli/bundle/bundleCommandLineArgs.js
    ++
        {command: '--rabbit-id [string]',
        description: 'rabbit rabbit entry id'}
    

    这么做因为只希望尽量减少改动RN的工具,所以只对metro进行改造,react-native尽量不动,便于以后更新.
    rabbit只是动态执行拆包方案的前端部分,Native

    遇到的问题&解决办法

    metro代码库中flow文件是什么?

    flow是facebook公司的代码规范,需要将flow转移js才可以正常运行。
    在Babel中配置使用Flow需要创建一个配置文件.babelrc,并配置插件transform-flow-strip-types,让其它人也可能使用它:
    
     "presets": ["es2015"],
      "plugins": [
        "transform-flow-strip-types",
      ]
    }
    
    然后你可以告诉Babel你的输入文件夹src和输出文件夹lib:
    
    `babel src/ -d lib`
    
    通常,你会将lib目录添加到你的.gitignore中,因为我们不想提交我们编译过后的代码到Git上。
    
    

    RN打包的核心文件

    // metro unbundle
    metro/src/shared/output/unbundle/index.js
    // metro unbundle android
    metro/src/shared/output/unbundle/as-assets.js
    // rn local-cli
    react-native/local-cli/bundle/unbundle.js
    

    node调试,内容很多的对象如何打印?

    //使用格式化输出对象
    console.log(JSON.stringify(object,null,2));
    
    //或使用fs写入文件中;
    
    #首先npm上注册账号,别忘了去邮箱验证,而且126邮箱不行,qq可以。
    
    # 不能使用镜像,切换到原始地址:
    npm config set registry http://registry.npmjs.org
    
    npm adduser
    npm init
    npm publish
    
    #更新
    npm version <新的版本号>
    npm publish
    
    #发布完成之后,如果还想回到之前的cnpm,使用下面的命令
    npm config set registry https://registry.npm.taobao.org
    
    

    此打包工具实践的方案,需要Native对RN做相应的修改,具体内容请参考:

    本文的主旨是介绍rabbit的拆包思想,并不是推广这个工具,而是希望将这个思路讲清楚帮助读者优化react-native项目。

    相关文章

      网友评论

      • 86144ec89f24:博主,你好,公司最近一个项目一个app全部rn编写,但是有10几个定制版本,我想拆包然后,按需加载打包,这样就不用维护10套代码。但你的方法是将ios 的unbundle命令采用了android 的unbundle,实际意义不大啊。
        86144ec89f24:官方文档,ios的多文件io读写有性能瓶颈。这种拆包怎么再把它们合并起来?

      本文标题:RN拆包工具:Rabbit-bundler

      本文链接:https://www.haomeiwen.com/subject/qgcweftx.html