基于rollup的组件库打包体积优化小结
背景
前段时间对公司内部的组件库(类似element-ui)做了打包体积优化,现在抽点时间记录下。以前也做过构建速度的优化,具体可以看
一些存在的问题
最开始打包是基于webpack的,在按需加载上存在的体积冗余会比较大,如:
webpack
打包特有的模块加载器函数,这部分其实有些多余,最好去掉- 使用
babel
转码时,babel
带来的helper
函数全部是内联状态,需要转成import
或require
来引入 - 使用
transform-rumtime
对一些新特性添加polyfill
,也是内联状态,需要转成import
或require
来引入 vue-loader
带来的额外代码,如normalizeComponent
,不做处理也是内联transform-vue-jsx
带来的额外函数引入,如mergeJSXProps
,不做处理也是内联
以上几个问题,如果只是一份代码,那不会有太大问题,但是如果是按需加载,用户一旦引入多个组件,以上的代码就会出现多份,带来严重的影响
import { Button, Icon } from 'gs-ui'
以上代码会转成
import Button from 'gs-ui/lib/button.js' import Icon from 'gs-ui/lib/icon.js'
这样,就会出现多份相同的helper
函数代码,多份webpack
的模块加载器函数,而且还不好去重
寻找解决方案
讨论过后主要有以下几种选择
采用后编译
我们也认同这种方案,采用后编译可以解决上面的各种问题,也有组件库是这样做的,比如cube-ui,但是这样有些不方便,因为用户需要设置各种alias
,还要保证好各种编译环境,如jsx
,而且未来可能会引入flow
,会更加不方便,所以暂时不考虑
使用rollup打包,设置external(当然webpack也可以)外联helper函数
使用rollup
打包,可以直接解决问题1和问题4,设置external
可以解决transform-runtime
等带来的helper
,这取决于相关插件实现时是不是通过import
或require
来添加helper
的,如果是直接copy
的话,那就还得另找办法。最后决定就这种方案进行尝试
使用rollup对打包进行重构
使用rollup
打包可能某些习惯和webpack
有些出入,在这里很多事需要引入插件来完成,比如引入node_modules
中的模块的话,需要加入rollup-plugin-node-resolve
,加载commonjs
模块需要引入rollup-plugin-commonjs
等等。另外还有些比较麻烦的,比如经常会这样写
import xx from './xx-folder'
然后希望模块打包器可以识别成
import xx from './xx-folder/index.js'
在rollup
里还是需要用插件来完成这件事,找到的插件都没能满足各种需求,比如还需要对alias
也能识别然后加上index.js
,最后还是需要自己实现这个插件
基本的rollup配置应该差不多是这样的
{ output: { format: 'es', // file: xx, // paths: }, input: 'xxx', plugins: [ vue({ compileTemplate: true, htmlMinifier: { customAttrSurround: [[/@/, new RegExp('')], [/:/, new RegExp('')]], collapseWhitespace: true, removeComments: true } }), babel({ ...babelrc({ addModuleOptions: false, addExternalHelpersPlugin: false }), exclude: 'node_modules/**', runtimeHelpers: true }), localResolve({ components: path.resolve(__dirname, '../components') }), alias({ components: path.resolve(__dirname, '../components'), resolve: ['.js', '.vue'] }), replace({ 'process.env.NODE_ENV': JSON.stringify('development') }) ], // external }
这里采用的rollup-plugin-vue
的版本是v3.0.0
,不采用v4
,因为打包出来的体积更小,功能完全满足组件库需要。因为会存在各种约定,比如组件肯定是存在render
函数(不一定指的就是手写render
或jsx
,只是不会有在js
中使用template
这种情况,这样的好处是可以使用runtime-only
的vue
),组件肯定不存在style
部分等等。
babel
的配置上基本不会有改变,只是rollup-plugin-babel
加上了runtimeHelpers
,用来开启transform-runtme
的。可能你会觉得为了更精简体积,应该去掉transform-runtime
,这点我持保留意见,这里使用transform-runtime
的主要作用是为了接管babel-helpers
,因为这个babel-helpers
无法被external
。另外整个组件库用到的babel-runtime
其实也不多,主要是类似Object.assign
这样的函数,像这些函数,使用的话还是需要加上transform-runtime
的,或者需要自己实现,感觉没什么必要。类似Array.prototype.includes
这种无法被transform-runtime
处理的还是会避免使用的
localResolve
是自己实现的插件,用来添加index.js
,并且能支持alias
,
alias
插件用来添加alias
,并且需要设置后缀
replace
插件用来替换一些环境变量,比如开发环境会有错误提示,生成环境不会有,这里展示的是开发环境的配置。
配置external
所有优化的关键在于external
上,除了最基本的vue
需要external
外,还有比如Button
组件内部依赖了Icon
组件,那是需要把Icon
组件external
的
// Button 组件 import Icom from 'components/icon'
其实就是所有的组件和共用的util
函数都需要external
,当然这里本来就存在了,不是本次优化要做的
主要要处理的是babel-helper
等helper
函数,但是这里不能做到,我也没有去了解babel
是如何对这块进行处理的,最后还是需要transform-runtime
来接管它。
rollup
的external
配置是支持函数类型的,大概看tranform-runtime
这个插件源码可以找到addImport
这些方法,可以知道polyfill
是通过import
来引入的,可以被external
,所以只需要在rollup
配置的external
添加上类似函数就可以达到我们想要的效果
{ external (id) { // 对babel-runtime进行external return /^babel-runtime/.test(id) // 当然别忘了还有很多 比如vue等等,这里就不写了 } }
这里就可以解决问题2和问题3
另外问题5,这个是如何来的呢,比如在写jsx
时,可能会这样写
// xx组件 export default { render () { return ( <div> <ToolTip {...{props: tooltipProps}} /> {/* other *