项目分析
最近在处理 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-ui
的issue
中找到两篇类似的文章:
可以引用借助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
中,所以需要进行额外的处理。
这里需要在
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
无法被解析出来,最后换成了Taro
的Navigator
组件才生效。
附一张taro-ui
组件的导出:
使用中产生的几个疑问:
- 为什么要在组件前面加上前缀
At
?是为了跟Taro
的组件区分开来吗? - 为什么对于
AtModalHeader/AtModalContent/AtModalAction
这些组件,不用const { Action, Content, Header } = Modal
这样的形式导出?明明都是在同一个文件夹下 - 为什么把
AtTabPane
写成一个单独的组件,又不把样式单独出来,样式引入的不是tab-pane.scss
,而是tab.scss
?
使用感受
总的来说对taro-ui
这块的整体感觉不是很好:
- 官方的按需加载写了个寂寞,完整的方式没有附上;
- 组件的命名让有强迫症的我看了感觉十分别扭;
- 子模块的暴露方式,以及样式划分都不是很友好。