關(guān)于Vue組件庫開發(fā)詳析
前言
2017年是Vue.js大爆發(fā)的一年,React迎來了一個強(qiáng)有力的競爭對手,王者地位受到挑戰(zhàn)(撰寫此文時github上Vue與React的star數(shù)量已逼近)。我們團(tuán)隊這一年有十多個大型項目采用了Vue技術(shù)棧,在開發(fā)效率、頁面性能、可維護(hù)性等方面都有不錯的收效。 我們希望把這些項目中可復(fù)用的功能組件提取出來,給后續(xù)項目使用,以減少重復(fù)開發(fā),提高效率,同時也為了致敬前端界“出一個框架,造一遍輪子”的行規(guī), 一個基于Vue 2的移動端UI組件庫被提上日程。
組件庫的開發(fā)過程總的來說還是比較順利的,這里與大家分享一些問題與思考。
腳手架選擇
盡管我們團(tuán)隊的這些Vue技術(shù)棧項目的腳手架大都使用的是webpack,在為組件庫選擇腳手架的時候我們還是在webpack與Rollup中猶豫了一下。
Rollup看起來更適合組件庫的開發(fā),它把所有模塊構(gòu)建在一個函數(shù)內(nèi),執(zhí)行效率更高,它支持Tree Shaking,只打包需要的代碼,輸出文件更小(webpack后來也支持了)。但綜合考慮之后,我們還是選擇了webpack作為打包工具。首先,按照規(guī)劃,demo演示和文檔頁面也在這個腳手架中,所以對代碼分割、熱加載等功能是有需求的,而這方面能力Rollup遠(yuǎn)不及webpack。另外,這個組件庫由多人開發(fā)維護(hù),基于現(xiàn)有webpack腳手架開發(fā)成本更低、效率更高。選擇webpack,讓我們可以更專注于造輪子。
打包
即便選擇了webpack作為打包工具,我們也并不希望這個庫的使用場景局限在webpack項目中,通過AMD/CMD方式、甚至通過script標(biāo)簽直接引用等場景都應(yīng)該得到支持。為了達(dá)到這個目的,我們需要在webpack配置文件中設(shè)置輸出格式,需要配置的選項是output.libraryTarget,有以下可選值:
“var”(默認(rèn)值)輸出為一個變量
var MyLibrary = _entry_return_ ;
“this” 輸出為this的一個屬性
this["MyLibrary"] = _entry_return_ ;
“window” 輸出為window對象的一個屬性
window["MyLibrary"] = _entry_return_ ;
“global” 輸出為global對象的一個屬性
global["MyLibrary"] = _entry_return_ ;
“commonjs” 輸出為exports 的一個屬性
exports["MyLibrary"] = _entry_return_ ;
“commonjs2” 以module.exports形式輸出
module.exports = _entry_return_ ;
“amd” 輸出為AMD模塊
“umd” 暴露給所有模塊定義,允許它和CommonJS/AMD/全局變量一起工作
很顯然,我們需要把output.libraryTarget的值設(shè)為“umd”,以使我們的庫可以工作在各種場景下。
另一個與庫打包有關(guān)的設(shè)置項是output.umdNamedDefine,在output.libraryTarget 設(shè)為umd,且output.library 也設(shè)置了的情況下,把此選項值設(shè)為true,將會為AMD模塊命名。
webpackConfig.output = {
path: path.resolve(__dirname, 'dist'),
publicPath:"/",
filename: '[name].js',
library: 'xxx', //模塊名稱
libraryTarget: 'umd', //輸出格式
umdNamedDefine: true //是否把模塊名作為AMD輸出的命名空間
};
Vue組件庫只提供組件,Vue文件自身需要組件庫使用者在項目中自行引入,庫中無需打包。所以我們可以把Vue加到externals中。
externals: {
vue: 'vue'
}
這樣Vue就不會被打包。不過,有個問題,就是用script標(biāo)簽的形式引用Vue的時候,掛在window上的變量名是“Vue”,而不是我們需要的”vue”,因此使用時會報vue未定義的錯誤。

還好,webpack的externals配置項支持傳入一個對象,可以為不同導(dǎo)出形式指定不同名稱。所以下面這種寫法可以解決這個問題。

組件類型
規(guī)劃中的Vue組件庫包含組件(Component)、指令(Directive)和過濾器(Filter)三種類型的存在。
比較特殊的是模態(tài)彈窗類(Modal)組件,如Dialog、Toast等等。頁面中可能存在很多個Modal,而很多場景下用戶的行為只會觸發(fā)其中一部分,如果把所有可能彈出的Modal(特別是異步的、結(jié)構(gòu)內(nèi)容復(fù)雜的Modal)全部寫在頁面上,是否妥當(dāng)?對于多頁面應(yīng)用,每個頁面都寫一遍或者再封裝一層組件是否繁瑣而冗余?這個問題在知乎上引發(fā)過討論,尤大(Vue.js作者尤雨溪)本人在參與討論時給出建議,組件多層嵌套時,應(yīng)該把Modal放在根組件里,然后在子組件里通過事件觸發(fā)。在具體應(yīng)用里,應(yīng)該這么用,這符合Vue提倡的“狀態(tài)驅(qū)動”。不過在組件庫里,我們還是希望提供一種更便捷更通用的方式來使用Modal類型的組件。
參考了Element UI等優(yōu)秀組件庫的做法,我們把Modal類型的組件掛到了Vue.prototype上,使之成為Vue的實例方法,一次安裝、全局調(diào)用。
this.$dialog(options);
因此,我們的組件庫組件類型還包括“實例方法”。
組件CSS作用域
對于一個組件,我們希望它的CSS只作用于當(dāng)前組件內(nèi)的元素,所以我們給每個組件的Vue單頁面文件的style標(biāo)簽加上了scoped屬性。編譯后,HTML標(biāo)簽會被自動添加一個隨機(jī)生成的唯一屬性 (比如 data-v-f3f3eg9) ,同時對應(yīng)的CSS選擇器也會增加同名的屬性選擇器(如.example[data-v-f3f3eg9]),這樣組件內(nèi)的 CSS 便指定了作用域。

編譯后:

通過scoped屬性的確能達(dá)到給組件樣式設(shè)置作用域的目的,基本能避免組件內(nèi)的樣式影響外部,但是它也帶來了另外一個問題,就是給外部覆蓋內(nèi)部樣式帶來了不便。無論組件功能多么通用,接口多么靈活,只要涉及到UI,就難免無法滿足所有項目樣式需求,所以應(yīng)該允許在具體的項目中根據(jù)需要覆蓋組件部分甚至全部樣式。而scoped隨機(jī)生成屬性名提高了覆蓋樣式的難度。
經(jīng)過權(quán)衡,我們在組件里移除了scoped屬性,改用class策略來避免組件內(nèi)樣式影響外部。當(dāng)然,scoped屬性也不是沒有存在的意義,它更適合在具體應(yīng)用中使用,對于復(fù)用性高的組件來說,不是最佳選擇。
按需使用與自定義構(gòu)建
隨著項目推進(jìn),組件庫里的組件越來越多,目前已超過40個,構(gòu)建之后的文件也越來越大。如果某個應(yīng)用只用到了庫里的少數(shù)幾個組件,完全沒有必要使用完整的構(gòu)建包,所以我們需要提供一種按需使用的方式。早期,我們是讓用戶通過私有npm安裝組件庫之后,根據(jù)應(yīng)用自身需要直接引用src目錄下組件源碼的方式來實現(xiàn)按需加載。這種方式有較大局限性,因為引用的源碼沒有經(jīng)過編譯,需要用戶自己去處理組件的依賴關(guān)系,ES6/SCSS/Vue模板等編譯工作也需要用戶在自己的項目里完成,繁瑣、易出錯,也難以支持webpack外的其他場景。 我們設(shè)想提供一種自定義構(gòu)建的方式,來實現(xiàn)按需打包。首先讓用戶選擇需要哪些組件,然后基于這些信息生成一個個性化的配置文件,再基于這個文件進(jìn)行構(gòu)建,最終只打包編譯用戶指定的這些組件。
那么,通過哪種方式與用戶交互,收集用戶指令呢?比較友好的方式是通過web,比如在項目主頁中提供一個頁面,讓用戶在線選擇組件,然后下載構(gòu)建之后的文件。而根據(jù)我們組件庫目前的定位,推薦的使用方式是通過私有npm安裝,所以我們首先推出的是通過命令行界面(CLI)方式來完成自定義構(gòu)建。
用戶只需要在終端執(zhí)行命令“npm run custom”,即可得到全部組件的列表,通過鍵盤選擇需要的組件,然后按下回車,腳手架便開始自動完成剩余的個性化構(gòu)建工作。

片刻之后,只包含用戶所選組件的構(gòu)建包會出現(xiàn)在dist目錄下,文件體積比完整版本小很多。

這種方式下,所選組件會經(jīng)歷組件庫腳手架完整的構(gòu)建流程,自動處理組件依賴關(guān)系,對ES6/SCSS/Vue等語法也進(jìn)行了編譯,構(gòu)建出的文件也支持AMD/CMD/script標(biāo)簽直接引用等場景,能比較好的滿足按需使用的需求。
圖標(biāo)
組件庫UI組件難免會包含一些小圖標(biāo),需要尋找一種合適的方式處理這些圖標(biāo)。
在應(yīng)用開發(fā)中有時會把一些圖片轉(zhuǎn)成Base64編碼放在代碼里,這會使數(shù)據(jù)量增大30%左右,所以這種方式不適合較大圖片。而對于小圖標(biāo)來說,增加的絕對數(shù)據(jù)量并不大,卻能減少一個http請求,也不失為一種優(yōu)化方案。不過,組件庫較普通應(yīng)用對數(shù)據(jù)量更為敏感,這種方式不是上策。
另一種處理小圖標(biāo)的經(jīng)典方案是雪碧圖(CSS Sprite),但這種基于精準(zhǔn)位置信息的圖標(biāo)引用方式在移動端基于rem的布局中并不是那么受歡迎,因為rem布局自身就難以精確,如果用于組件庫也會給按需引用帶來一些不便。
對于小圖標(biāo),在移動端更需要的是矢量方案,天然適配各種像素密度的屏幕。
在組件庫中,比較流行的是采用基于CSS3字體(@font-face)的ICON FONT方案,也就是把圖標(biāo)放在一個自定義字體文件中。有很多優(yōu)點,比如:
- ICON在字體中是矢量存在,受移動端歡迎
- 良好的瀏覽器兼容性,web字體并非CSS3發(fā)明,更早之前的瀏覽器(包括IE6)也都事實上支持,雖有些許差異,終歸是有辦法兼容的
- 可通過CSS控制ICON顏色和透明度等樣式,甚至可以實現(xiàn)顏色漸變效果

我們并沒有選擇ICON FONT方案,我們認(rèn)為SVG方案更適合移動端組件庫:
- SVG雖在PC端個別古董瀏覽器中兼容較差,但在移動端兼容良好
- ICON FONT被認(rèn)為是文本,所以一些瀏覽器會對其進(jìn)行抗鋸齒處理,這可能導(dǎo)致圖標(biāo)不那么銳利,清晰度打折扣
- SVG樣式控制比ICON FONT更靈活,甚至可以控制圖標(biāo)各個部分的顏色,實現(xiàn)彩色圖標(biāo)。而這對ICON FONT來說是不可能實現(xiàn)的
- ICON FONT通常是用偽對象或偽類插入頁面,其展示受到“l(fā)ine-height”、“vertical-align”、“l(fā)etter-spacing”、“word-spacing”及字體相關(guān)CSS屬性影響,也受到字體字符設(shè)計本身影響。而SVG在頁面中就是一個標(biāo)簽,更方便控制,語義化也更好
- 結(jié)合symbol元素可以實現(xiàn)所謂“SVG Sprite”,也就是把很多SVG圖標(biāo)整合在一起,通過ID引用指定圖標(biāo),可以復(fù)用。這種方式比CSS Sprite還要方便,因為不需要關(guān)心圖標(biāo)具體位置信息

SVG Sprite也不是必須手動去組合,借助webpack的 svg-sprite-loader 可以輕松實現(xiàn)SVG Sprite的動態(tài)生成,圖標(biāo)的按需加載不是夢。
擴(kuò)展閱讀
- NutUI組件庫 - https://nutui.jd.com
- webpack output.libraryTarget - https://webpack.js.org/configuration/output/#output-librarytarget
- umd - https://github.com/umdjs/umd
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
vue解析Json數(shù)據(jù)獲取Json里面的多個id問題
這篇文章主要介紹了vue解析Json數(shù)據(jù)獲取Json里面的多個id問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
Vue router傳遞參數(shù)并解決刷新頁面參數(shù)丟失問題
這篇文章主要介紹了Vue router傳遞參數(shù)并解決刷新頁面參數(shù)丟失問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Vue3使用Proxy實現(xiàn)數(shù)據(jù)監(jiān)聽的原因分析
在本篇文章里小編給大家整理的是一篇關(guān)于Vue3使用Proxy實現(xiàn)數(shù)據(jù)監(jiān)聽的原因分析內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)參考下。2021-11-11
詳解無限滾動插件vue-infinite-scroll源碼解析
這篇文章主要介紹了詳解無限滾動插件vue-infinite-scroll源碼解析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05
vue使用vue-json-viewer插件展示JSON格式數(shù)據(jù)的方法
這篇文章主要給大家介紹了關(guān)于vue使用vue-json-viewer插件展示JSON格式數(shù)據(jù)的相關(guān)資料,前端使用這個插件可以方便展現(xiàn)出json格式的數(shù)據(jù),下載引入使用代碼可直接使用,需要的朋友可以參考下2024-05-05

