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


Posted in Javascript onJune 18, 2018

背景

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

一些存在的问题

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

  1. webpack打包特有的模块加载器函数,这部分其实有些多余,最好去掉
  2. 使用babel转码时,babel带来的helper函数全部是内联状态,需要转成importrequire来引入
  3. 使用transform-rumtime对一些新特性添加polyfill,也是内联状态,需要转成importrequire来引入
  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,这取决于相关插件实现时是不是通过importrequire来添加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函数(不一定指的就是手写renderjsx,只是不会有在js中使用template这种情况,这样的好处是可以使用runtime-onlyvue),组件肯定不存在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-helperhelper函数,但是这里不能做到,我也没有去了解babel是如何对这块进行处理的,最后还是需要transform-runtime来接管它。

rollupexternal配置是支持函数类型的,大概看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的组件库打包体积优化小结

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

Javascript 相关文章推荐
textarea 控制输入字符字节数(示例代码)
Dec 27 Javascript
JavaScript拆分字符串时产生空字符的解决方案
Sep 26 Javascript
jQuery插件slicebox实现3D动画图片轮播切换特效
Apr 12 Javascript
简述Jquery与DOM对象
Jul 10 Javascript
详解React开发中使用require.ensure()按需加载ES6组件
May 12 Javascript
Kindeditor单独调用单图上传增加预览功能的实例
Jul 31 Javascript
vue-cli 3.x配置跨域代理的实现方法
Apr 12 Javascript
JavaScript生成随机验证码代码实例
Sep 28 Javascript
vue与django集成打包的实现方法
Nov 11 Javascript
three.js 如何制作魔方
Jul 31 Javascript
请求时token过期自动刷新token操作
Sep 11 Javascript
webpack介绍使用配置教程详解webpack介绍和使用
Jun 25 Javascript
详解组件库的webpack构建速度优化
Jun 18 #Javascript
Javascript实现异步编程的过程
Jun 18 #Javascript
详解JS函数stack size计算方法
Jun 18 #Javascript
jQuery使用动画队列自定义动画操作示例
Jun 16 #jQuery
node.js自动上传ftp的脚本分享
Jun 16 #Javascript
Vue中props的使用详解
Jun 15 #Javascript
基于jQuery实现的设置文本区域的光标位置
Jun 15 #jQuery
You might like
在PHP中使用与Perl兼容的正则表达式
2006/11/26 PHP
php中使用addslashes函数报错问题的解决方法
2013/02/06 PHP
PHP实现扎金花游戏之大小比赛的方法
2015/03/10 PHP
微信access_token的获取开发示例
2015/04/16 PHP
PHP学习笔记之session
2018/05/06 PHP
laravel框架中视图的基本使用方法分析
2019/11/23 PHP
初探jquery——表单应用范例
2007/02/20 Javascript
Extjs学习过程中新手容易碰到的低级错误积累
2010/02/11 Javascript
jQuery自动切换/点击切换选项卡效果的小例子
2013/08/12 Javascript
jquery提取元素里的纯文本不包含span等里的内容
2013/09/30 Javascript
在Ubuntu系统上安装Ghost博客平台的教程
2015/06/17 Javascript
简单实现jQuery进度条轮播实例代码
2016/06/20 Javascript
Angular.JS中的指令引用template与指令当做属性详解
2017/03/30 Javascript
JavaScript简单计算人的年龄示例
2017/04/15 Javascript
浅谈Angular HttpClient简单入门
2018/05/04 Javascript
超出JavaScript安全整数限制的数字计算BigInt详解
2018/06/24 Javascript
JavaScript函数、闭包、原型、面向对象学习笔记
2018/09/06 Javascript
layui 实现表单和文件上传一起传到后台的例子
2019/09/16 Javascript
Vue自定义render统一项目组弹框功能
2020/06/07 Javascript
[01:14]2014DOTA2展望TI 剑指西雅图newbee战队专访
2014/06/30 DOTA
[55:03]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第二场 11.20
2020/11/20 DOTA
python实现ip查询示例
2014/03/26 Python
Python入门_浅谈数据结构的4种基本类型
2017/05/16 Python
python机器学习案例教程——K最近邻算法的实现
2017/12/28 Python
opencv python 基于KNN的手写体识别的实例
2018/08/03 Python
Django中多种重定向方法使用详解
2019/07/17 Python
曼联官方网上商店:Manchester United Direct
2017/07/28 全球购物
美国第二大连锁书店:Books-A-Million
2017/12/28 全球购物
Banana Republic欧盟:美国都市简约风格的代表品牌
2018/05/09 全球购物
Theo + George官方网站:都柏林时尚品牌
2019/04/08 全球购物
医学院毕业生自荐信
2013/11/08 职场文书
项目投资建议书
2014/05/16 职场文书
写给医生的感谢信
2015/01/22 职场文书
Go语言中的UTF-8实现
2021/04/26 Golang
Nginx速查手册及常见问题
2022/04/07 Servers
Python数据可视化之Seaborn的安装及使用
2022/04/19 Python