組件庫rollup打包體積優化

基於rollup的組件庫打包體積優化

背景

前段時間對公司內部的組件庫(相似element-ui)作了打包體積優化,如今抽點時間記錄下。之前也作過構建速度的優化,具體能夠看組件庫webpack構建速度優化html

一些存在的問題

最開始打包是基於webpack的,在按需加載上存在的體積冗餘會比較大,如:vue

  • webpack打包特有的模塊加載器函數,這部分其實有些多餘,最好去掉
  • 使用babel轉碼時,babel帶來的helper函數所有是內聯狀態,須要轉成importrequire來引入
  • 使用transform-rumtime對一些新特性添加polyfill,也是內聯狀態,須要轉成importrequire來引入
  • vue-loader帶來的額外代碼,如normalizeComponent,不作處理也是內聯
  • transform-vue-jsx帶來的額外函數引入,如mergeJSXProps,不作處理也是內聯

以上幾個問題,若是隻是一份代碼,那不會有太大問題,可是若是是按需加載,用戶一旦引入多個組件,以上的代碼就會出現多份,帶來嚴重的影響node

import { Button, Icon } from 'gs-ui'
複製代碼

以上代碼會轉成webpack

import Button from 'gs-ui/lib/button.js'
import Icon from 'gs-ui/lib/icon.js'
複製代碼

這樣,就會出現多份相同的helper函數代碼,多份webpack的模塊加載器函數,並且還很差去重git

尋找解決方案

討論事後主要有如下幾種選擇github

採用後編譯web

咱們也認同這種方案,採用後編譯能夠解決上面的各類問題,也有組件庫是這樣作的,好比cube-ui,可是這樣有些不方便,由於用戶須要設置各類alias,還要保證好各類編譯環境,如jsx,並且將來可能會引入flow,會更加不方便,因此暫時不考慮element-ui

使用rollup打包,設置external(固然webpack也能夠)外聯helper函數bash

使用rollup打包,能夠直接解決問題1和問題4,設置external能夠解決transform-runtime等帶來的helper,這取決於相關插件實現時是否是經過importrequire來添加helper的,若是是直接copy的話,那就還得另找辦法。最後決定就這種方案進行嘗試babel

使用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,都會添加一個模塊加載函數...

優化後和優化前的體積對比

下面的截圖是生產環境的版本,也就是沒有了代碼提示,也已經壓縮混淆後的代碼體積對比 左邊是優化前,右邊是優化後

optimize.jpg

原文地址

github/linrui1994/note/2018-06-12__組件庫打包體積優化.md

相關文章
相關標籤/搜索