2018-06-17

基于rollup的組件庫打包體積優(yōu)化

背景

前段時間對公司內(nèi)部的組件庫(類似element-ui)做了打包體積優(yōu)化斩祭,現(xiàn)在抽點(diǎn)時間記錄下。以前也做過構(gòu)建速度的優(yōu)化乡话,具體可以看組件庫的webpack構(gòu)建速度優(yōu)化

一些存在的問題

最開始打包是基于webpack的摧玫,在按需加載上存在的體積冗余會比較大,如:

  • webpack打包特有的模塊加載器函數(shù)蚊伞,這部分其實(shí)有些多余席赂,最好去掉
  • 使用babel轉(zhuǎn)碼時吮铭,babel帶來的helper函數(shù)全部是內(nèi)聯(lián)狀態(tài)时迫,需要轉(zhuǎn)成importrequire來引入
  • 使用transform-rumtime對一些新特性添加polyfill,也是內(nèi)聯(lián)狀態(tài)谓晌,需要轉(zhuǎn)成importrequire來引入
  • vue-loader帶來的額外代碼掠拳,如normalizeComponent,不做處理也是內(nèi)聯(lián)
  • transform-vue-jsx帶來的額外函數(shù)引入纸肉,如mergeJSXProps溺欧,不做處理也是內(nèi)聯(lián)

以上幾個問題喊熟,如果只是一份代碼,那不會有太大問題姐刁,但是如果是按需加載芥牌,用戶一旦引入多個組件,以上的代碼就會出現(xiàn)多份聂使,帶來嚴(yán)重的影響

import { Button, Icon } from 'gs-ui'

以上代碼會轉(zhuǎn)成

import Button from 'gs-ui/lib/button.js'
import Icon from 'gs-ui/lib/icon.js'

這樣壁拉,就會出現(xiàn)多份相同的helper函數(shù)代碼,多份webpack的模塊加載器函數(shù)柏靶,而且還不好去重

尋找解決方案

討論過后主要有以下幾種選擇

采用后編譯

我們也認(rèn)同這種方案弃理,采用后編譯可以解決上面的各種問題,也有組件庫是這樣做的屎蜓,比如cube-ui痘昌,但是這樣有些不方便,因?yàn)橛脩粜枰O(shè)置各種alias炬转,還要保證好各種編譯環(huán)境辆苔,如jsx,而且未來可能會引入flow返吻,會更加不方便姑子,所以暫時不考慮

使用rollup打包,設(shè)置external(當(dāng)然webpack也可以)外聯(lián)helper函數(shù)

使用rollup打包测僵,可以直接解決問題1和問題4街佑,設(shè)置external可以解決transform-runtime等帶來的helper,這取決于相關(guān)插件實(shí)現(xiàn)時是不是通過importrequire來添加helper的捍靠,如果是直接copy的話沐旨,那就還得另找辦法。最后決定就這種方案進(jìn)行嘗試

使用rollup對打包進(jìn)行重構(gòu)

使用rollup打包可能某些習(xí)慣和webpack有些出入榨婆,在這里很多事需要引入插件來完成磁携,比如引入node_modules中的模塊的話,需要加入rollup-plugin-node-resolve良风,加載commonjs模塊需要引入rollup-plugin-commonjs等等谊迄。另外還有些比較麻煩的,比如經(jīng)常會這樣寫

import xx from './xx-folder'

然后希望模塊打包器可以識別成

import xx from './xx-folder/index.js'

rollup里還是需要用插件來完成這件事烟央,找到的插件都沒能滿足各種需求统诺,比如還需要對alias也能識別然后加上index.js,最后還是需要自己實(shí)現(xiàn)這個插件

基本的rollup配置應(yīng)該差不多是這樣的

{
  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粮呢,因?yàn)榇虬鰜淼捏w積更小,功能完全滿足組件庫需要。因?yàn)闀嬖诟鞣N約定啄寡,比如組件肯定是存在render函數(shù)(不一定指的就是手寫renderjsx豪硅,只是不會有在js中使用template這種情況,這樣的好處是可以使用runtime-onlyvue)挺物,組件肯定不存在style部分等等懒浮。

babel的配置上基本不會有改變,只是rollup-plugin-babel加上了runtimeHelpers识藤,用來開啟transform-runtme的嵌溢。可能你會覺得為了更精簡體積蹋岩,應(yīng)該去掉transform-runtime赖草,這點(diǎn)我持保留意見,這里使用transform-runtime的主要作用是為了接管babel-helpers剪个,因?yàn)檫@個babel-helpers無法被external秧骑。另外整個組件庫用到的babel-runtime其實(shí)也不多,主要是類似Object.assign這樣的函數(shù)扣囊,像這些函數(shù)乎折,使用的話還是需要加上transform-runtime的,或者需要自己實(shí)現(xiàn)侵歇,感覺沒什么必要骂澄。類似Array.prototype.includes這種無法被transform-runtime處理的還是會避免使用的

localResolve是自己實(shí)現(xiàn)的插件,用來添加index.js惕虑,并且能支持alias坟冲,

alias插件用來添加alias,并且需要設(shè)置后綴

replace插件用來替換一些環(huán)境變量溃蔫,比如開發(fā)環(huán)境會有錯誤提示健提,生成環(huán)境不會有,這里展示的是開發(fā)環(huán)境的配置伟叛。

配置external

所有優(yōu)化的關(guān)鍵在于external上私痹,除了最基本的vue需要external外,還有比如Button組件內(nèi)部依賴了Icon組件统刮,那是需要把Icon組件external

// Button 組件
import Icom from 'components/icon'

其實(shí)就是所有的組件和共用的util函數(shù)都需要external紊遵,當(dāng)然這里本來就存在了,不是本次優(yōu)化要做的

主要要處理的是babel-helperhelper函數(shù)侥蒙,但是這里不能做到暗膜,我也沒有去了解babel是如何對這塊進(jìn)行處理的,最后還是需要transform-runtime來接管它辉哥。

rollupexternal配置是支持函數(shù)類型的桦山,大概看tranform-runtime這個插件源碼可以找到addImport這些方法,可以知道polyfill是通過import來引入的醋旦,可以被external恒水,所以只需要在rollup配置的external添加上類似函數(shù)就可以達(dá)到我們想要的效果

{
  external (id) {
    // 對babel-runtime進(jìn)行external
    return /^babel-runtime/.test(id) // 當(dāng)然別忘了還有很多 比如vue等等,這里就不寫了
  }
}

這里就可以解決問題2和問題3

另外問題5饲齐,這個是如何來的呢钉凌,比如在寫jsx時,可能會這樣寫

// xx組件
export default {
  render () {
    return (
      <div>
        <ToolTip {...{props: tooltipProps}} />
        {/* other */}
      </div>
    )
  }
}

在某個組件中依賴了另一個組件捂人,考慮到擴(kuò)展性御雕,是支持對另一個組件進(jìn)行props設(shè)置的,所以經(jīng)常會這樣寫滥搭,在template中的話就類似于v-bind="tolltipProps"

這個時候transform-vue-jsx插件是會引入一個helper函數(shù)的酸纲,也就是babel-helper-vue-jsx-merge-props,大概看看transform-vue-jsx源碼也可以得知,這個helper也是import進(jìn)來的瑟匆,所以可以把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轉(zhuǎn)成render函數(shù)愁溜,babel插件做語法轉(zhuǎn)換疾嗅,因?yàn)?code>external的存在,保留了模塊關(guān)系冕象,整個代碼看起來很清晰代承,很舒服,不像webpack渐扮,都會添加一個模塊加載函數(shù)...

優(yōu)化后和優(yōu)化前的體積對比

下面的截圖是生產(chǎn)環(huán)境的版本论悴,也就是沒有了代碼提示,也已經(jīng)壓縮混淆后的代碼體積對比
左邊是優(yōu)化前墓律,右邊是優(yōu)化后

optimize.jpg

原文地址

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末意荤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子只锻,更是在濱河造成了極大的恐慌玖像,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐饮,死亡現(xiàn)場離奇詭異捐寥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祖驱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門握恳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捺僻,你說我怎么就攤上這事乡洼〕绮茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵束昵,是天一觀的道長拔稳。 經(jīng)常有香客問我,道長锹雏,這世上最難降的妖魔是什么巴比? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮礁遵,結(jié)果婚禮上轻绞,老公的妹妹穿的比我還像新娘。我一直安慰自己佣耐,他們只是感情好政勃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兼砖,像睡著了一般稼病。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掖鱼,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天然走,我揣著相機(jī)與錄音,去河邊找鬼戏挡。 笑死芍瑞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的褐墅。 我是一名探鬼主播拆檬,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼妥凳!你這毒婦竟也來了竟贯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逝钥,失蹤者是張志新(化名)和其女友劉穎屑那,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艘款,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡持际,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哗咆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜘欲。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晌柬,靈堂內(nèi)的尸體忽然破棺而出姥份,到底是詐尸還是另有隱情郭脂,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布澈歉,位于F島的核電站展鸡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闷祥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一傲诵、第九天 我趴在偏房一處隱蔽的房頂上張望凯砍。 院中可真熱鬧,春花似錦拴竹、人聲如沸悟衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽座泳。三九已至,卻和暖如春幕与,著一層夾襖步出監(jiān)牢的瞬間挑势,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工啦鸣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留潮饱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓诫给,卻偏偏與公主長得像香拉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子中狂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容