前言
更新:
- 本地跑ange-ui專案的時候需要全域性安裝vuepress,目前vuepress有0.x和1.x版本(剛釋出,檢視1.x文件)系列,安裝最新的vupress無法正常執行專案(感謝 @_嗚啦啦啦火車笛 提出),本專案使用1.x vuepress會有以下問題:
- 授權拒絕錯誤
- 與最新版sas-loader(7.x)不相容
- 使用0.x可以避免
npm install vuepress@0.14.11 -g
幾天前官方釋出的vuepress 1.x版本:
謝謝大家支援!
即便是一個青銅,也要用王者的心去編碼!
Github上關於Vue的UI庫,大大小小不計其數,即便是已經被推廣使用的成熟庫,也有很多。很多時候,我們自研一套UI庫,不是想要做得多牛逼,競爭過別人(事實我們也幹不過人家,除非你不是一個人在戰鬥。畢竟這不僅是個技術活,還是個體力活),我們僅僅是源自一個青銅對王者的仰望或者是為滿足內心的需求。
這裡跟大家講一個一步一步自研UI庫的故事。
原文地址:github.com/qiud...
專案地址:Ange UI
開發一套UI庫,做成不難(這裡指的是半半半成品,全品也好難。。。),做好很難。但不慌,我們有祕籍:
- 一定的內功修為。所謂打鐵還需自身硬,要做高複用元件的開發工作,對
Vue.js
和CSS3
還是有一定的技術要求。那我要多牛逼才能寫好這個UI庫啊?這取決於你這套UI庫的實現高度。 - 一些招式套路。藏經閣(Github)上有很多關於Vue的UI庫:六脈神劍、獨孤九劍應有盡有。人家怎麼寫的,我們們跟著來就好了。有人可能意見很大,那不是在模仿嗎?這裡要嚴正宣告,我們不是在模仿,我們只是向標準靠攏。因為牛逼的就是標準的。
UI庫的必要架構
一套成熟的開源UI庫一般都具備以下幾個特點:
- 包含了元件原始碼
- 完備的說明文件
- 符合Eslint校驗標準
- 全面的單元測試
- 完整的構建生態
因此,它們的目錄架構也出現了主要的兩種形式:
& 有點大同小異,這裡我們按照第一種架構去開發。- build:存放構建配置檔案
- docs:官方說明文件
- src:元件原始碼
- test:單元測試用例(這裡不作闡述)
- eslintrc:基於eslint-plugin-vue的開發規範標準
搭建UI開發環境
觀察社群幾大UI庫發現,它們都是基於webpack
搭建了自己的構建配置,包括本地開發、生產環境構建、UI庫的打包等,建立一套自己的構建生態。我們就不同了,業餘一點(其實是善於應用開源工具),我們的docs文件是基於vuepress的,vuepress有自己一套的構建體系,所以我們只需要針對UI元件原始碼寫一份打包配置就好了。
下面開始搭建打包配置(其實就是很久以前我們做的基於webpack的構建,現在cli用得多了,配置也不會寫了,碼耶):
首先在根目錄建立config
和build
資料夾,然後往兩個目錄分別新建檔案,如下:
- build-lib.js:node執行的指令碼,讀取並執行lib的配置進行構建;
- webpack.base.conf.js:公共的構建配置;
- webpack.lib.conf.js:針對UI元件打包的配置;
- index.js:可變的配置資訊;
- prod.env.js:宣告當前構建環境為生產環境的配置;
不是說只有一份打包的配置檔案麼?咋還多了那麼多檔案呢?是這樣,雖然我們是業餘的,但我們也想做得專業一點對吧(有利於對配置進行擴充套件管理)!
我們看 config/index.js
有什麼?
webpack.lib.conf.js
中。
再看看build/webpack.base.conf.js
裡面的關鍵配置:
entry
和output
會被 webpack.lib.conf.js
覆寫;rules
定義一系列loader的轉換規則,其中eslint的校驗就是在這裡定義了一個eslint-loader
。
最後看一眼build/webpack.lib.conf.js
配置:
entry
的規則是,根據傳參值(components)分別走不同的入口檔案,一種只有一個./src/index.js
,這個是UI對外註冊的入口檔案(這使得我們可以引入整個UI);另一種是各個UI元件的註冊入口檔案(這使得我們可以按需引入元件);
先給大家貼圖直觀感受下原始碼的目錄結構:
那實際打包的時候走的是哪一種呢?真相是都走!在build-lib.js
中,執行了三次打包,打包輸出效果如下:
開發一個元件
前面紮好了馬步,終於到修煉招式的階段了! 我們都知道,在應用某個外掛的時候需要經過下面程式碼的呼叫:
import Ange from 'ange-ui'
Vue.use(Ange)
複製程式碼
好奇下Vue.use
在做什麼處理呢?它其實就是註冊/安裝這個外掛,根據use內部的定義,它通過呼叫install方法去註冊外掛,那麼,Ange就必須是一個Function
(會被use當做是install
方法呼叫)或者是一個包含了install
方法的Object
。
道理我都懂,可是install
方法裡面到底寫些什麼?
試想一下,我們希望在專案的任意位置都能引用這個外掛,那我們的每一個元件是不是要在全域性註冊?比如通過下面這種方式全域性註冊元件:
Vue.component('pagination', pagination)
複製程式碼
沒錯,install
方法內部就是批量地全域性註冊元件。
搭好目錄架構
首先我們按照下圖的方式新建目錄和檔案:
在src目錄的index.js檔案中定義install
方法:
import './scss/ange.scss' // 引入元件樣式表,也可以讓使用者在使用的時候自行引入
import components from './components'
function install(Vue, opts = {}) {
Object.values(components).forEach((each) => {
Vue.component(each.name, each)
})
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
version: '1.0.0',
install,
...components
}
複製程式碼
核心邏輯install就是對所有的元件迴圈註冊在全域性。components目錄的 index.js 則是逐個對外暴露元件物件,其次每一個元件也有一個 index.js ,它的作用是為當前元件注入install方法。理所當然地,install
裡面是將該元件註冊在全域性,於是我們可以按需引用元件。
開發Button元件
元件開發的通用模板
<template>
<component :is="'button'"></component>
</template>
<script>
export default {
name: 'ag-button',
props: {}
}
</script>
複製程式碼
component是vue的內建元件,is
引數設定成button
,表明最終渲染的html是button
標籤,我們也可以直接使用button標籤,但我們的按鈕元件不一定是button,還可能是a標籤,為了更好的擴充,這裡使用component。
宣告元件引數
export default {
name: 'ag-button',
props: {
// 按鈕類別
primary: Boolean,
secondary: Boolean,
dashed: Boolean,
link: Boolean,
// 按鈕狀態
color: {
type: String,
validator (val) {
return new Set(['success', 'warn', 'danger']).has(val)
}
},
// 按鈕尺寸
size: {
type: String,
validator (val) {
return new Set(['large', 'normal', 'small']).has(val)
}
},
// 圖示按鈕
icon: String,
// 圓形按鈕(一般結合圖示按鈕使用)
circle: Boolean,
// 外鏈按鈕
external: Boolean,
// 非同步按鈕
loading: Boolean
}
}
複製程式碼
完善元件模板
<template>
<component
:is="tag"
class="ange-btn"
:class="[ btnSize, color, {
'default': isDefault,
'primary': primary,
'secondary': secondary,
'dashed': dashed,
'link': link,
'icon': icon,
'circle': circle
}]"
@click="$emit('click', $event)"
:disabled="loading">
<span class="ange-btn-content">
<!-- 圖示按鈕依賴 ag-icon 元件 -->
<ag-icon
v-if="icon"
:icon="icon" />
<slot /> <!-- 插槽接收按鈕文字 -->
</span>
</component>
</template>
<script>
export default {
// ...
computed: {
tag () {
return this.external ? 'a' : 'button'
},
isDefault() {
const type = [this.primary, this.secondary, this.dashed, this.link]
return type.every((each) => !each)
},
btnSize() {
return this.size || 'normal'
}
}
}
</script>
複製程式碼
剩下的工作就是寫好樣式表了,你可以選擇直接寫在vue檔案裡面,也可以新建_scss/scss_樣式表。
元件應用及效果(線上檢視)
以上,便開發好一個button元件了,執行一下node build-lib.js
或者npm run build:lib
(現在package.json宣告script)就可以打包這個UI框架,然後再將其釋出到npm平臺(如果你想...)
寫好一份文件
線上檢視 Ange UI Docs 寫好文件是一個庫不可或缺的部分,寫的過程通過實際應用各元件,還可以對其進行測試校驗。前面說到,我們的文件要基於vuepress開發,簡潔的Markdown寫法,很是方便。這裡一篇 指南 可以很好地幫助你,它告訴了你如何搭建架構,寫好配置以及部署上線,或者參考我這個 倉庫 的配置。
假設docs/views/button.md
是你的button
元件的文件頁面,要如何引用你的元件?
button
元件:
import '@scss/ange.scss'
import Button from '@component/button'
Vue.use(Button)
複製程式碼
至此,文件也就寫好了!
最後的最後,按照 vuepress doc 部署到你的github倉庫上就可以了!
本文通過button元件從0到1的開發,深入淺出闡述了Vue UI框架的開發流程,你對Vue.js的理解越深,元件的功能越複雜,你就會用到更多的高階用法。通過自研UI框架,我們也有很大的收穫:
- 重新溫習webpack配置
- 深入理解Vue內部機制,掌握更多的vue高階用法
- 夯實js和css3基礎
- 掌握程式設計正規化和設計模式
PS:CSS其實是UI開發中佔比很重的部分,大家按照自己的風格元件化開發就好。給大家推薦幾個很棒的配色網站:
最後,希望大家也能多多去嘗試,這個青銅自研UI庫的故事到這裡就結束了。
The end.