基于rollup的組件庫(kù)打包體積優(yōu)化小結(jié)
背景
前段時(shí)間對(duì)公司內(nèi)部的組件庫(kù)(類似element-ui)做了打包體積優(yōu)化,現(xiàn)在抽點(diǎn)時(shí)間記錄下。以前也做過構(gòu)建速度的優(yōu)化,具體可以看組件庫(kù)的webpack構(gòu)建速度優(yōu)化
一些存在的問題
最開始打包是基于webpack的,在按需加載上存在的體積冗余會(huì)比較大,如:
webpack打包特有的模塊加載器函數(shù),這部分其實(shí)有些多余,最好去掉- 使用
babel轉(zhuǎn)碼時(shí),babel帶來的helper函數(shù)全部是內(nèi)聯(lián)狀態(tài),需要轉(zhuǎn)成import或require來引入 - 使用
transform-rumtime對(duì)一些新特性添加polyfill,也是內(nèi)聯(lián)狀態(tài),需要轉(zhuǎn)成import或require來引入 vue-loader帶來的額外代碼,如normalizeComponent,不做處理也是內(nèi)聯(lián)transform-vue-jsx帶來的額外函數(shù)引入,如mergeJSXProps,不做處理也是內(nèi)聯(lián)
以上幾個(gè)問題,如果只是一份代碼,那不會(huì)有太大問題,但是如果是按需加載,用戶一旦引入多個(gè)組件,以上的代碼就會(huì)出現(xiàn)多份,帶來嚴(yán)重的影響
import { Button, Icon } from 'gs-ui'
以上代碼會(huì)轉(zhuǎn)成
import Button from 'gs-ui/lib/button.js' import Icon from 'gs-ui/lib/icon.js'
這樣,就會(huì)出現(xiàn)多份相同的helper函數(shù)代碼,多份webpack的模塊加載器函數(shù),而且還不好去重
尋找解決方案
討論過后主要有以下幾種選擇
采用后編譯
我們也認(rèn)同這種方案,采用后編譯可以解決上面的各種問題,也有組件庫(kù)是這樣做的,比如cube-ui,但是這樣有些不方便,因?yàn)橛脩粜枰O(shè)置各種alias,還要保證好各種編譯環(huán)境,如jsx,而且未來可能會(huì)引入flow,會(huì)更加不方便,所以暫時(shí)不考慮
使用rollup打包,設(shè)置external(當(dāng)然webpack也可以)外聯(lián)helper函數(shù)
使用rollup打包,可以直接解決問題1和問題4,設(shè)置external可以解決transform-runtime等帶來的helper,這取決于相關(guān)插件實(shí)現(xiàn)時(shí)是不是通過import或require來添加helper的,如果是直接copy的話,那就還得另找辦法。最后決定就這種方案進(jìn)行嘗試
使用rollup對(duì)打包進(jìn)行重構(gòu)
使用rollup打包可能某些習(xí)慣和webpack有些出入,在這里很多事需要引入插件來完成,比如引入node_modules中的模塊的話,需要加入rollup-plugin-node-resolve,加載commonjs模塊需要引入rollup-plugin-commonjs等等。另外還有些比較麻煩的,比如經(jīng)常會(huì)這樣寫
import xx from './xx-folder'
然后希望模塊打包器可以識(shí)別成
import xx from './xx-folder/index.js'
在rollup里還是需要用插件來完成這件事,找到的插件都沒能滿足各種需求,比如還需要對(duì)alias也能識(shí)別然后加上index.js,最后還是需要自己實(shí)現(xiàn)這個(gè)插件
基本的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積更小,功能完全滿足組件庫(kù)需要。因?yàn)闀?huì)存在各種約定,比如組件肯定是存在render函數(shù)(不一定指的就是手寫render或jsx,只是不會(huì)有在js中使用template這種情況,這樣的好處是可以使用runtime-only的vue),組件肯定不存在style部分等等。
babel的配置上基本不會(huì)有改變,只是rollup-plugin-babel加上了runtimeHelpers,用來開啟transform-runtme的??赡苣銜?huì)覺得為了更精簡(jiǎn)體積,應(yīng)該去掉transform-runtime,這點(diǎn)我持保留意見,這里使用transform-runtime的主要作用是為了接管babel-helpers,因?yàn)檫@個(gè)babel-helpers無法被external。另外整個(gè)組件庫(kù)用到的babel-runtime其實(shí)也不多,主要是類似Object.assign這樣的函數(shù),像這些函數(shù),使用的話還是需要加上transform-runtime的,或者需要自己實(shí)現(xiàn),感覺沒什么必要。類似Array.prototype.includes這種無法被transform-runtime處理的還是會(huì)避免使用的
localResolve是自己實(shí)現(xiàn)的插件,用來添加index.js,并且能支持alias,
alias插件用來添加alias,并且需要設(shè)置后綴
replace插件用來替換一些環(huán)境變量,比如開發(fā)環(huán)境會(huì)有錯(cuò)誤提示,生成環(huán)境不會(huì)有,這里展示的是開發(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-helper等helper函數(shù),但是這里不能做到,我也沒有去了解babel是如何對(duì)這塊進(jìn)行處理的,最后還是需要transform-runtime來接管它。
rollup的external配置是支持函數(shù)類型的,大概看tranform-runtime這個(gè)插件源碼可以找到addImport這些方法,可以知道polyfill是通過import來引入的,可以被external,所以只需要在rollup配置的external添加上類似函數(shù)就可以達(dá)到我們想要的效果
{
external (id) {
// 對(duì)babel-runtime進(jìn)行external
return /^babel-runtime/.test(id) // 當(dāng)然別忘了還有很多 比如vue等等,這里就不寫了
}
}
這里就可以解決問題2和問題3
另外問題5,這個(gè)是如何來的呢,比如在寫jsx時(shí),可能會(huì)這樣寫
// xx組件
export default {
render () {
return (
<div>
<ToolTip {...{props: tooltipProps}} />
{/* other */}
</div>
)
}
}
在某個(gè)組件中依賴了另一個(gè)組件,考慮到擴(kuò)展性,是支持對(duì)另一個(gè)組件進(jìn)行props設(shè)置的,所以經(jīng)常會(huì)這樣寫,在template中的話就類似于v-bind="tolltipProps"
這個(gè)時(shí)候transform-vue-jsx插件是會(huì)引入一個(gè)helper函數(shù)的,也就是babel-helper-vue-jsx-merge-props,大概看看transform-vue-jsx源碼也可以得知,這個(gè)helper也是import進(jìn)來的,所以可以把external改成
{
external (id) {
return /^babel/.test(id)
}
}
這樣就可以做到對(duì)所有helper都使用import的形式來引入,而且使用rollup打包后的代碼更可讀,大概長(zhǎng)這樣
// 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)系,整個(gè)代碼看起來很清晰,很舒服,不像webpack,都會(huì)添加一個(gè)模塊加載函數(shù)...
優(yōu)化后和優(yōu)化前的體積對(duì)比
下面的截圖是生產(chǎn)環(huán)境的版本,也就是沒有了代碼提示,也已經(jīng)壓縮混淆后的代碼體積對(duì)比
左邊是優(yōu)化前,右邊是優(yōu)化后

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript輸出所選擇起始與結(jié)束日期的方法
這篇文章主要介紹了JavaScript輸出所選擇起始與結(jié)束日期的方法,涉及javascript結(jié)合HTML5元素操作日期運(yùn)算的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07
單元測(cè)試框架Jest搭配TypeScript的安裝與配置方式
這篇文章主要介紹了單元測(cè)試框架Jest搭配TypeScript的安裝與配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
ibm官方資料把應(yīng)用程序從 Internet Explorer 遷移到 Mozilla
使特定于 Internet Explorer 的 Web 應(yīng)用程序在 Mozilla 上運(yùn)行時(shí),您遇到過麻煩嗎?本文討論了將應(yīng)用程序遷移到基于開源 Mozilla 瀏覽器上時(shí)的常見問題。首先討論跨瀏覽器開發(fā)的基本技術(shù),然后介紹克服 Mozilla 和 Internet Explorer 之間差異的策略。2008-04-04
JS中創(chuàng)建自定義類型的常用模式總結(jié)【工廠模式,構(gòu)造函數(shù)模式,原型模式,動(dòng)態(tài)原型模式等】
這篇文章主要介紹了JS中創(chuàng)建自定義類型的常用模式,結(jié)合實(shí)例形式總結(jié)分析了工廠模式,構(gòu)造函數(shù)模式,原型模式,動(dòng)態(tài)原型模式等javascript創(chuàng)建自定義類型的常用模式與相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-01-01
微信小程序?qū)崿F(xiàn)滑動(dòng)側(cè)邊欄
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)滑動(dòng)側(cè)邊欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
JS無縫滾動(dòng)效果實(shí)現(xiàn)方法分析
這篇文章主要介紹了JS無縫滾動(dòng)效果實(shí)現(xiàn)方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了無縫滾動(dòng)的原理、實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-12-12

