打包 Taro-UI 踩过的坑

3,502 阅读4分钟

项目分析

最近在处理 taro 小程序的优化问题,通过小程序代码依赖分析,其中最大的就是vendors.js,而vendors.js中最大的要属taro-ui的包了。

然后去查看了下项目中的代码,只有两三个文件用到了taro-ui的组件,总共也就用到了 6 个组件,而且都是以官方按需引入的方式来处理的,如下图所示:

但是很显然在打包的时候taro-ui 被全部打包进去了,webpack并没有tree-shaking掉未引用的组件。

解决思路

Step One

首先去瞄了一眼官方文档,按需引入写的很简单,但是实际按照官方文档这样用并不能生效。

Step Two

然后查看了node_moduels/taro-ui/dist的文件夹,发现其实是存在多种index文件,但是打包的时候是自动引入了index.umd.js,而不是index.esm.js

为什么呢?大家可以参考下:package.json 中的 browser,module,main 字段优先级探索

于是改变taro-ui的引用路径,指向 taro-ui/dist/index.esm.js。打包效果如下图所示:

跟引用umd的文件大小没啥区别,甚至还有一丢丢大,所以这种方式也没有用。

Step Three

后面在taro-uiissue中找到两篇类似的文章:

  1. [Taro 3.0.0-rc.4] 如何在开发时启用 Tree Shaking 以引用 react-use 等第三方包
  2. 实现taro-ui组件的按需加载

可以引用借助babel-plugin-import来进行tree-shaking,先进行安装:

yarn add babel-plugin-import --dev

然后在babel.config.js中进行配置:

// babel.config.js,参考他人的配置
module.exports = {
  presets: [
    ['taro', {
      framework: 'react',
      ts: true
    }]
  ],
  plugins: [
    ["import", {
      libraryName: "taro-ui",
      customName: (name, file) => {
        const nameSection = name.split('-')
        if (nameSection.length === 4) {
          // 子组件的路径跟主组件一样
          nameSection.pop()
        }
        // 过滤掉前面的 at 字符
        const path = nameSection.slice(1).join('-');
        return `taro-ui/lib/components/${path}`;
      },
      style: (name) => {
        // name 是 customName 的 return
        const wholePath = name.split('/')
        const compName = wholePath[wholePath.length - 1]
        // XXX: 如果报错,就在这里映射
        const fix = {
          'tabs-pane': 'tabs'
        }[compName]
        return `taro-ui/dist/style/components/${fix || compName}.scss`
      }
    }]
  ]
}

然后进行编译打包,发现报错,如下图所示:

报的错误是找不到modal-action.scss文件,然后去node_modules/taro-ui/dist/style文件夹下找原因,如下图所示,其实AtModalAction组件是放在AtModal组件下的,引用的样式也是写在了modal.scss中,所以需要进行额外的处理。 image.png 这里需要在style方法中进行处理,处理之后:

const { includes } = require("lodash");
module.exports = {
  presets: [
    ['taro', {
      framework: 'react',
      ts: true
    }]
  ],
  plugins: [
    ["import", {
      libraryName: "taro-ui",
      customName: (name, file) => {
        const nameSection = name.split('-')
        if (nameSection.length === 4) {
          // 子组件的路径跟主组件一样
          nameSection.pop()
        }
        const path = nameSection.slice(1).join('-');
        return `taro-ui/lib/components/${path}`;
      },
      style: (name) => {
        // 这里的 name 是 customName 方法返回的,即经过上一步处理后的
        
        // 1、如果是 modal 相关的组件,则统一返回 modal.scss
        if (includes(name, '/modal')) {
          return 'taro-ui/dist/style/components/modal.scss'
        }
        
        const wholePath = name.split('/')
        const compName = wholePath[wholePath.length - 1]
        const fix = {
          'tabs-pane': 'tabs',
          // 2、或者在这里写映射
          // 'modal/action': 'modal',
          // 'modal/header': 'modal',
          // 'modal/content': 'modal',
        }[compName]
        return `taro-ui/dist/style/components/${fix || compName}.scss`
      }
    }]
  ]
}

再执行打包命令,还是会报错,找不到路径lib/components/modal-action,很明显从taro-ui的源码中就根本没有这个文件夹,有的是lib/components/modal/action,所以需要在customName方法中继续处理映射关系:

处理之后:

const { includes } = require("lodash");
module.exports = {
  presets: [
    ['taro', {
      framework: 'react',
      ts: true
    }]
  ],
  plugins: [
    ["import", {
      libraryName: "taro-ui",
      customName: (name, file) => {
        const nameSection = name.split('-')
        if (nameSection.length === 4) {
          // 子组件的路径跟主组件一样
          nameSection.pop()
        }
        // 指定组件做路径映射
        const pathMap = {
          'tabs/pane': 'tabs-pane',
          'modal-action': 'modal/action',
          'modal-content': 'modal/content',
          'modal-header': 'modal/header'
        }
        const path = nameSection.slice(1).join('-');
        return `taro-ui/lib/components/${pathMap[path] || path}`;
      },
      style: (name) => {
        // 这里的 name 是 customName 方法返回的,即经过上一步处理后的

        // 1、如果是 modal 相关的组件,则统一返回 modal.scss
        if (includes(name, '/modal')) {
          return 'taro-ui/dist/style/components/modal.scss'
        }

        const wholePath = name.split('/')
        const compName = wholePath[wholePath.length - 1]
        const fix = {
          'tabs-pane': 'tabs',
          // 2、或者在这里写映射,这里正好跟上面的映射相反
          // 'modal/action': 'modal',
          // 'modal/header': 'modal',
          // 'modal/content': 'modal',
        }[compName]
        return `taro-ui/dist/style/components/${fix || compName}.scss`
      }
    }]
  ]
}

这里再去进行打包编译就不会报错了,编译之后taro-ui的包体积大小减少了 130+kb,回归了它应有的模样。

其他的坑

  • 如果引用了taro官方推荐的优化插件taro-plugin-compiler-optimization,如果修改了babel.config.js中的配置,要重新编译打包的时候,记得每次都要删除掉node_modules/.cache文件夹,否则编译的时候是不会执行最新的配置的。
  • 按需加载编译成功后,原生的小程序组件navigator无法被解析出来,最后换成了TaroNavigator组件才生效。

附一张taro-ui组件的导出:

使用中产生的几个疑问:

  • 为什么要在组件前面加上前缀At?是为了跟Taro的组件区分开来吗?
  • 为什么对于AtModalHeader/AtModalContent/AtModalAction这些组件,不用const { Action, Content, Header } = Modal这样的形式导出?明明都是在同一个文件夹下
  • 为什么把AtTabPane写成一个单独的组件,又不把样式单独出来,样式引入的不是tab-pane.scss,而是tab.scss

使用感受

总的来说对taro-ui这块的整体感觉不是很好:

  • 官方的按需加载写了个寂寞,完整的方式没有附上;
  • 组件的命名让有强迫症的我看了感觉十分别扭;
  • 子模块的暴露方式,以及样式划分都不是很友好。
OSZAR »