1. 程式人生 > >滴滴開源 Vue 元件庫— cube-ui

滴滴開源 Vue 元件庫— cube-ui

cube-ui 是滴滴去年底開源的一款基於 Vue.js 2.0 的移動端元件庫,主要核心目標是做到體驗極致、靈活性強、易擴充套件以及提供良好的周邊生態—後編譯。

自 17 年 11 月開源至今已有 5 個月,在這個過程中 cube-ui 受到了不少的關注,同時從社群中也收到了很多很好的反饋和建議。我們也一直在迭代更新,從最初的 1.0 版本到最近釋出的 1.7 的版本,除了對原有元件做一些增強優化,我們也提供了很多新的元件。此外,周邊後編譯技術生態也做了很多優化,滿足於更多場景需求,官網也做了一次升級。
接下來就重點介紹下 cube-ui 在這個過程中的有哪些成果以及一些設計細節。

關鍵成果

cube-ui 的元件數已經從最初的 14 個增長了 28 個,足足翻了一倍,已有的元件生態:
在這裡插入圖片描述

除了上述的元件外,cube-ui 還對外暴露了三個模組:
● style
● create-api
● better-scroll

而且 cube-ui 也已經支援瞭如下特性:
● 自定義主題
● rem 佈局
● SSR 支援

此外,cube-ui 的周邊生態也有了進一步豐富:
● vue-cli 腳手架模板 cube-template
● 快速上手教程 cube-application-guide
● 後編譯 webpack 外掛 webpack-post-compile-plugin
● 按需引用 webpack 外掛 webpack-transform-modules-plugin,依賴 babel 外掛 babel-plugin-transform-modules

設計細節

針對於上邊所介紹的關鍵成果,我們來聊聊具體設計上的細節。

元件模組

● 滾動 & Picker 類元件
在移動端,由於手機尺寸以及互動特性,我們需要處理很多滾動類需求:下拉重新整理、上拉更多、輪播等以及 Picker 選擇等場景。cube-ui 底層滾動類元件以及 Picker 類依賴於我們團隊的移動端利器 better-scroll 實現,基於其出色的體驗進而保證了我們上層封裝的滾動類、Picker 類元件的出色的互動體驗。
● 彈出層類元件
在實際開發中我們會遇到很多彈出層類元件,因為我們設計了一個基礎彈出層元件 Popup,它主要解決移動端最為常見的居中(Tip:文字換行位置也很重要哦)、置底以及是否有蒙層效果,藉助於它來實現絕大多數的彈出層元件。
另一個常見的痛點就是由於彈出類元件往往是全屏的狀態,如果我們按照 Vue 推薦的宣告式的語法在子元件裡使用彈出層元件,由於巢狀層級問題,很容易受到父級元素的樣式影響。為此我們單獨開發了 create-api 模組,通過 API 的形式將例項化的彈出層元件動態掛載到 Body 元素下,因此擺脫了父級元素樣式的影響,同時會隨著使用它的元件的銷燬而自動銷燬,且為了降低開銷成本,根據需要有些彈出層類元件都被設計成了單例模式。它是一個很通用的能力,我們把這樣的一個便捷的 API 對外暴露出去,開發者也可以根據實際場景將自己開發的元件通過 createAPI 進行註冊,進而也可解決上述痛點。
● 表單類元件
表單類需求往往特應性比較大,互動也很難做到統一,但是仍然可以有主流的表單設計互動,在 cube-ui 中表單可以設定 layout 來決定樣式甚至是互動,滿足日常場景需求。在表單設計中有兩個很重要的元件:Validator 和 Form。Validator 成為獨立的元件主要基於校驗場景不確定性,同時還需要滿足各種形式的校驗,所以 Validator 就只做了兩件核心的事情,對資料來源進行校驗以及對應的錯誤資訊的展示。考慮到開發者開發表單的便利性,我們參考 vue-form-generator 的設計,把表單設計成了根據 Schema 配置自動生成表單,這樣開發表單的成本就降低了很多;同時為了兼顧靈活性,也支援通過插槽來自定義開發者需要的結構互動。
後編譯

後編譯是 cube-ui 的一個重要的生態,藉助於後編譯,整個的 web 應用的開發都可以直接基於 ES2015+ 進行開發,而專案依賴的一些 NPM 包也是可以直接使用 ES2015+ 進行開發,並且無需編譯可直接釋出到 NPM 平臺上(也可以是自己 NPM 私服)。這樣,這些元件庫或者工具就可以有更多的想象空間、可以做更多有意思的事情。
cube-ui 支援的兩個特性自定義主題以及 rem 佈局都是基於我們主推的後編譯技術實現。
接下來一起來看下這兩個特性實現的細節。
自定義主題
一般而言,元件庫都是有預設主題的,而往往還會搭配有多套主題(PC 類元件庫比較常見)。現在藉助於 CSS 前處理器,我們可以給元件定義一些變數(一般都是顏色值),然後在元件對應的樣式中使用。
對於自定義主題這種需求,主流的做法有:樣式覆蓋和修改變數。

  1. 樣式覆蓋
    樣式覆蓋是最古老的做法,但是缺點也很明顯,第一就是樣式冗餘問題,預設主題樣式是一直存在的;第二就是開發者需要確切的知道樣式對應的優先順序去覆蓋,要麼是同級的優先順序樣式後置,要麼就是提升自身覆蓋的樣式優先順序。
    當然,樣式覆蓋的做法也是有優點的,那就是對於多主題同時存在,自由切換場景會比較合適。
  2. 修改變數
    現在有很多的 CSS 前處理器可以選擇,每一種 CSS 前處理器都提供了變數功能,藉助於變數,我們可以很容易建立一個主題檔案,裡邊包含元件依賴的變數定義。要實現自定義主題,開發者需要在自己專案下建立一個單獨的樣式檔案,定義賦值變數,同時引入元件庫自身原始碼下的主題檔案。
    本質上也是一種後編譯做法,這個編譯是利用 CSS 前處理器自身的變數能力達到目的。對於 Vue 元件庫而言,主流的也是推薦的做法是把樣式寫在 .vue 檔案中,這樣便於維護,比較符合元件化開發思維;但是為了方便的使用前處理器實現自定義主題,通常都會把樣式單獨拿出來,一般的做法是建立一個樣式資料夾,裡邊包含所有元件樣式,而在 .vue 檔案中則是沒有樣式的。
  3. cube-ui 做法
    核心點就是藉助於後編譯,我們可以按照原有我們習慣的方式去書寫元件,即在 .vue 檔案中包含模板、指令碼和樣式。如果需要自定義主題,就在自己專案下建立一個主題檔案,裡邊定義變數,這個做法和一般的修改變數做法一樣,但是不需要引入所有樣式入口檔案,因為也不存在這樣的一個檔案;同時藉助於 webpack,我們完全可以做到在不侵入原始碼的情況下,做到主題定製。
    接下來就看下具體做法,如果是新建立的專案,那麼推薦使用 Vue-cli + cube-template 模板生成;而如果是現成的專案,則具體參考官方文件 - 主題 中配置。主要有兩個核心點:
    ● 建立主題檔案 theme.styl,一般放在 src/ 目錄下
    ● 修改 webpack 中關於 stylus-loader 的配置項:新增 import 欄位用於依賴自定義主題檔案
    接下來就看一個簡單專案演示,假設建立了一個 demo 的專案,這個專案預設跑起來是這樣的:
    在這裡插入圖片描述

如果我們想要把專案中使用的按鈕的背景色該換掉,那麼可以修改 theme.styl 的檔案內容:
// 如果你需要使用 cube-ui 自帶的顏色值 需要 require 進來

@require "~cube-ui/src/common/stylus/var/color.styl"

// button
$btn-bgc := #409eff
$btn-bdc := #409eff
$btn-active-bgc := #66b1ff
$btn-active-bdc := #66b1ff

配合我們的 webpack 配置,重新整理後的樣子為:
在這裡插入圖片描述
這樣我們就可以輕鬆做自己想要的主題定製,所要做的就是修改 cube-ui 已經定義好的變數值即可。對於 cube-ui 元件庫自身,則不會有任何修改,且對於應用開發者而言,用不用自定義主題,本身的原始碼不用修改,只需要建立一個主題檔案(無需手工引入)配合 webpack 外掛配置即可。

其實對於主題定製,還可以更進一步,未來 cube-ui 會考慮藉助於 CSS 自身支援的變數(自定義屬性)達到主題定製的目的,例如可以把處理器變數改為原生的變數,編譯的話可以通過 post-css-variables 外掛把預設變數值做替換,可以實現和現有編譯後功能相同的效果,同時在後編譯的情況下不失原生 CSS 變數的動態優勢。這樣,不僅可以做到主題定製,也可以做到多主題的自由切換,因為 CSS 原生變數可以直接修改變數值而不需要通過事先寫死然後切換 class 覆蓋的方式做多主題切換。

rem 佈局

在移動端還是有很多設計師、產品或者開發者偏愛用縮放來達到不同尺寸螢幕適配目的,而縮放的實現一般都是採用 rem 進行佈局,業內比較出名的方案就是手機淘寶前端團隊開源的 lib-flexible。
現在其實是不推薦使用 rem 進行佈局的,如果真的要縮放的效果,可以考慮 vw vh 等 CSS 單位來實現。
rem 佈局有兩個核心的點:

  1. 在執行時動態根據視口寬度更新 rem 的值,即修改根元素 HTML 的 font-size 的值
  2. 在編譯時(或開發時)需將設計稿的 px 單位轉換為 rem 單位
    對於元件庫而言,如果想要同時做到即支援普適的 px 又支援 rem 這種方式的話,社群貌似還沒見到。和後編譯搭配,則比較容易實現,在 cube-ui 中,已經提供了 rem 支援,主要採取的方案:
  3. 可選的 amfe-flexible, 也就是 lib-flexible 動態計算更新 rem 的值(注 2.x 版本)
  4. 選擇了 postcss 的外掛 postcss-px2rem 作為將 px 轉換為 rem 的庫
    這其實是對元件庫本身有了一定要求,和尺寸相關的儘量要用樣式控制,這樣才能通過處理工具 postcss-px2rem 將 px 單位處理成 rem 單位,進而實現動態縮放需求。
    來看下 cube-ui 使用 rem 的效果,預設 iPhone 5 尺寸效果:
    在這裡插入圖片描述
    當尺寸變大,例如為 iPhone 6 Plus 尺寸時效果:

可以看出整體的效果,當尺寸較小時,Button 和 Toast 都是比較小的,而當尺寸比較大時,相對應的都會更大,達到了縮放的目的。

上層擴充套件

這裡上層擴充套件主要是指基於元件庫進行二次封裝,例如在滴滴內部,我們的很多業務元件庫就是在開源的 cube-ui 元件庫之上做增強而來的。
這個能力是非常重要的,因為移動端元件庫和 PC 元件庫最大的區別是移動端多是 to C 的業務場景,不同的業務場景下的設計是不一樣的,所以 cube-ui 專注於通用元件和基礎能力的建設,並不會在佈局和業務元件方面大做文章;而 PC 元件庫一般都是用於 to B 的場景,如內部 MIS 類的系統,對於設計的要求並沒有特別苛刻,所以基礎的樣式,元件都是可以統一的。因此 cube-ui 的定位並不是要提供一個“大而全”的元件庫,而是提供了二次擴充套件的能力,目標是任何移動端的業務場景都可以基於 cube-ui 提供的能力做二次擴充套件。
以我們的快速上手教程為例,我們要開發如下圖的彈窗元件。
在這裡插入圖片描述
我們基於 cube-ui 提供的能力開發它就非常方便了。首先可以基於 Popup 元件開發一個 subscribe-dialog.vue 元件:

<template>
  <div class="subscribe-dialog-view">
    <cube-popup ref="popup" @mask-click="hide">
      <div class="subscribe-dialog-wrapper">
        <span class="close" @click="hide"><i class="cubeic-close"></i></span>
        <div class="title">開啟推送通知</div>
        <img src="./subscribe.png">
        <p class="desc">第一時間獲取最新鮮出爐的新聞攻略、賽事諮詢、資料專題、精彩視訊</p>
        <cube-button class="button" @click="start">現在開啟</cube-button>
      </div>
    </cube-popup>
  </div>
</template>

<script>
export default {
  name: 'subscribe-dialog',
  methods: {
    show () {
      this.$refs.popup.show()
      this.$emit('show')
    },
    hide () {
      this.$refs.popup.hide()
      this.$emit('hide')
    },
    // ...
  }
}
</script>

接著使用 createAPI 模組把它變成一個 API 式的元件:

import SubscribeDialog from './components/subscribe-dialog/subscribe-dialog'
createAPI(Vue, SubscribeDialog, [], true)

然後呼叫它就非常方便了:

this.subscribeDialog = this.$createSubscribeDialog()
this.subscribeDialog.show()

周邊生態
周邊生態有兩個核心:後編譯 + 按需引入。為此,我們開發了兩個 webpack 的外掛來幫助我們更好的去使用、開發。
● 後編譯 webpack 外掛 webpack-post-compile-plugin
● 按需引用 webpack 外掛 webpack-transform-modules-plugin
webpack-post-compile-plugin
這個外掛主要是讀取應用 package.json 中的 compileDependencies 欄位的值(用於指定應用需要後編譯哪些依賴包),而且還能解決巢狀後編譯包的問題,因為開發者只需要關注自己依賴需要後編譯的包,而不需要關注依賴的依賴包,這樣就能構成一條生態鏈。

為什麼不是一個 NPM 包自己宣告需不需要後編譯,而是由使用者去宣告?
主要考慮整個 NPM 生態,例如 lodash-es 並不在我們控制範圍之內,為了更好的使用整個 NPM 生態圈的包,我們決定由使用者去宣告需要後編譯的 NPM 包。
webpack-transform-modules-plugin
這個外掛主要解決更方便、友好地使用按需引入的問題,為了更好的統一應用使用後編譯和不使用的情況,我們在原本 babel-transform-imports 的基礎上做了升級優化產出了 babel-plugin-transform-modules 外掛,但是和後編譯的場景類似,這個是不能解決後編譯場景下 NPM 包巢狀按需引入的問題的,為此才開發了 webpack-transform-modules-plugin 這個外掛,和 compileDependencies 欄位類似,我們新增了 transformModules 欄位來宣告按需引入的 NPM 包的的轉換規則,例如:

"transformModules": {
  "cube-ui": {
    "transform": "cube-ui/src/modules/${member}",
    "kebabCase": true
  }
}

當然在後編譯的場景下,我們藉助於 webpack 4 Tree shaking 中新增的 side-effects 也可以達到目的,這個是未來我們去優化的方向。

腳手架 & 教程
任何的技術都是有成本的,我們新增了 webpack 外掛,也有一些需要配合的改動,所以為了降低開發者成本,我們提供了適用於 vue-cli 腳手架的模板 cube-template,當然對應的也會新增一些配置項,感興趣的可以瞭解下cube-template wiki。
同時為了初次使用 cube-ui 的開發者快速上手,我們還有一個簡單的上手教程 cube-application-guide。

展望
cube-ui 目前還處於初步的階段,還缺少很多元件,但是我們一直在努力,希望在很快的未來可以提供更多更好用的元件。不僅如此,我們希望的是除去元件庫本身,額外還會豐富周邊的整個生態建設,給開發者一個良好的生態環境,進一步提升開發體驗,提升應用效能等。當然,我們也希望社群的小夥伴也能參與進來,一塊共同建設,共同進步。
未來 cube-ui 會朝著如下方面繼續前行:
● 豐富元件
● 元件優化
● 文件優化
● 示例優化
● 周邊建設
希望感興趣的同學可以一起共建或者加入我們團隊,一起玩技術!