您的位置:首页 > 其它

基于rollup的组件库打包体积优化小结

2018-06-18 09:37 519 查看

背景

前段时间对公司内部的组件库(类似element-ui)做了打包体积优化,现在抽点时间记录下。以前也做过构建速度的优化,具体可以看组件库的webpack构建速度优化

一些存在的问题

最开始打包是基于webpack的,在按需加载上存在的体积冗余会比较大,如:

  1. webpack
    打包特有的模块加载器函数,这部分其实有些多余,最好去掉
  2. 使用
    babel
    转码时,
    babel
    带来的
    helper
    函数全部是内联状态,需要转成
    import
    require
    来引入
  3. 使用
    transform-rumtime
    对一些新特性添加
    polyfill
    ,也是内联状态,需要转成
    import
    require
    来引入
  4. vue-loader
    带来的额外代码,如
    normalizeComponent
    ,不做处理也是内联
  5. 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 */}
</div>
)
}
}

在某个组件中依赖了另一个组件,考虑到扩展性,是支持对另一个组件进行

props
设置的,所以经常会这样写,在
template
中的话就类似于
v-bind="tolltipProps"

这个时候

transform-vue-jsx
插件是会引入一个
helper
函数的,也就是
babel-helper-vue-jsx-merge-props
,大概看看
transform-vue-jsx
源码也可以得知,这个
helper
也是
import
进来的,所以可以把
external
改成

{
external (id) {
return /^babel/.test(id)
}
}

这样就可以做到对所有

helper
都使用
import
的形式来引入,而且使用
rollup
打包后的代码更可读,大概长这样

// Alert组件
import _defineProperty from 'babel-runtime/helpers/defineProperty';
import Icon from 'gs-ui/lib/icon.js';
var Alert = { render: function render() {
var _class;
var _vm = this;var _h = _vm.$createElement;var _c = _vm._self._c || _h;return _c('transition', { attrs: { "name": "gs-zoom-in-top" } }, [_vm.show ? _c('div', { class: (_class = { 'gs-alert': true }, _defineProperty(_class, 'gs-alert-' + _vm.type, !!_vm.type), _defineProperty(_class, 'has-desc', _vm.desc || _vm.$slots.desc), _class) }, [_vm.showIcon ? _c('div', { staticClass: "gs-alert-icon", class: { "gs-alert-icon-top": !!_vm.desc } }, [_vm._t("icon", [_c('gs-icon', { attrs: { "name": _vm.icon } })])], 2) : _vm._e(), _vm._v(" "), _c('div', { staticClass: "gs-alert-content" }, [_vm.title || _vm.$slots.default ? _c('div', { staticClass: "gs-alert-title" }, [_vm._t("default", [_vm._v(_vm._s(_vm.title))])], 2) : _vm._e(), _vm._v(" "), _vm.desc || _vm.$slots.desc ? _c('div', { staticClass: "gs-alert-desc" }, [_vm._t("desc", [_vm._v(_vm._s(_vm.desc))])], 2) : _vm._e(), _vm._v(" "), _vm.closable ? _c('div', { staticClass: "gs-alert-close", on: { "click": _vm.close } }, [_vm._t("close", [_vm._v(" " + _vm._s(_vm.closeText) + " "), !_vm.closeText ? _c('gs-icon', { attrs: { "name": "close" } }) : _vm._e()])], 2) : _vm._e()])]) : _vm._e()]);
}, staticRenderFns: [],
name: 'GsAlert',
components: _defineProperty({}, Icon.name, Icon),
// props
// data
// methods
};
/* istanbul ignore next */
Alert.install = function (Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;

vue插件把vue组件中的

template
转成
render
函数,babel插件做语法转换,因为
external
的存在,保留了模块关系,整个代码看起来很清晰,很舒服,不像
webpack
,都会添加一个模块加载函数...

优化后和优化前的体积对比

下面的截图是生产环境的版本,也就是没有了代码提示,也已经压缩混淆后的代码体积对比
左边是优化前,右边是优化后

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  rollup 打包