從0到1發佈一個Vue Collapse組件

需求背景

最近在項目中遇到了一個相似Collapse的交互需求,所以到github上找了一圈關於Vue Collapse的相關輪子,可是多少都有些問題。有的是實現問題,例如vue2-collapse,伸縮部分採用max-height指定動畫,存在缺陷;還有的是擴展性問題,遇到定製場景比較棘手。所以,決定本身擼一個Collapse組件。從項目中的一個需求,到目前已將它開源併發布到npm,仍是踩了許多坑的。代碼雖然簡單,可是過程卻不太容易。所以這篇文章不是安利這款組件r-collapse-vue,僅僅是想記錄一下整個開發生命週期,須要作什麼,以及遇到什麼問題。固然了,若是這個組件或是這篇文章對你有幫助,勞煩點進去給個star,萬分感謝~javascript

開發流程

咱們的整個開發流程,能夠簡單的總結以下:html

  1. 項目腳手架搭建(Vue CLI3)
  2. 組件功能開發
  3. 單元測試(Vue Test Utils + Jest)
  4. 文檔編寫(Vue Styleguidist + Github Pages)
  5. 發佈NPM
  6. 持續集成配置(TravisCI)

咱們來詳細聊一聊每一個過程是如何實施的,且遇到了哪些問題。前端

腳手架搭建

腳手架咱們直接使用Vue CLI來搭建便可,其已經提供了豐富的功能,而且能夠經過vue.config.js擴展webpack的能力。可是要注意的是,咱們的構建產物是一個模塊,而不是咱們平時在項目中構建出一個應用。咱們但願構建出來的模塊是一個兼容CommonJs或是UMD,以便於使用者在不一樣的環境中引用。所幸,Vue CLI3也給我提供了這樣一個功能,詳細可參考文檔vue

其次,本次開發我選擇了TypeScript,腳手架默認集成了vue-property-decorator。使用以後直觀的感覺就是,Vue的整個生態對TS的支持還不夠完善,但總體仍是比較爽的,期待官方在3.0中可以完全支持TS。本文主題不是討論TS,所以簡單羅列下使用時遇到的問題:java

  • 在template中沒法作到智能提示,須要智能提示只能使用tsx,這一點是比較痛苦的
  • 定義Prop時須要加非空斷言(!:),不然會報錯,例如:
@Prop({ required: true })
public value!: String;
  • 使用Vue Test Utils寫單測時,沒法對自定義的Vue組件進行類型推導,見下文
  • 使用Vue Styleguidist編寫文檔demo不支持TS,見下文

組件功能開發

在平常寫業務的時候,咱們可能會在組件當中耦合不少的業務邏輯。可是做爲一個通用組件,咱們在開發的時候要儘量保證它的擴展性,所以咱們但願達到的一個目標就是:在保證開發體驗的前提下提升擴展性。對於Collapse組件,UI方面通常都是按照各自的設計稿來自行編寫的,所以咱們只須要提供功能便可。更好的方式是提供默認的UI,但又能夠支持徹底定製,這個是目前r-collapse-vue能夠完善的一個點。
在進行功能設計的過程當中,咱們要先肯定咱們須要支持哪些功能,以r-collapse-vue舉例,須要提供的功能包括:webpack

  • 基本的展開/收縮(支持動畫)
  • 手風琴模式
  • 自定義點擊事件
  • Collapse嵌套

在實現的過程當中,咱們也須要思考不少細節,舉幾個例子:git

  1. 使用者如何控制每個Collapse的狀態?

最簡單的想法是傳遞一個相似叫作status的prop,在每個Collapse內部去維護這個狀態。可是這樣會有一個問題,咱們如何去支持手風琴模式,即一個展開另外的都須要收起。按照這種作法,須要用一個父組件包裹,去獲取每個Collapse子組件的實例,調用實例方法去控制。這樣作不是不行,vue2-collapse就是這麼作的,可是我認爲不夠優雅。所以咱們從新整理思路,每個Collapse之間的狀態可能會互相影響,咱們經常使用的解決方法是狀態提高,所以個人作法是抽象兩個組件,Collapse和CollapsePanel,Collapse便是父組件,提供狀態控制,將狀態傳遞給其內部嵌套的CollapsePanel,在內部消化掉全部的邏輯,這更加符合單向數據流的思想,站在使用者角度來看,寫法也可以相對統一,使用時咱們只需這麼寫:github

<r-collapse v-model="activeKeys">
    <r-collapse-panel name="a">xxxx</r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>
  1. 實際場景中常常會對展開和收縮進行樣式區分,如何幫助使用者提高開發體驗?

見上面的代碼,咱們在CollapsePanel中傳入了一個name屬性做爲惟一標識,此時使用者可結合activeKeys自行判斷當前panel是否展開:web

<r-collapse v-model="activeKeys">
    <r-collapse-panel
        name="a"
        :class="activeKeys.includes('a') ? 'active': ''"
    >
        xxxx
    </r-collapse-panel>
    <r-collapse-panel name="b">xxxx</r-collapse-panel>
</r-collapse>

這種方法雖然能夠,可是存在兩個問題:vue-cli

  • 用戶須要自行添加邏輯,體驗不夠友好
  • 每次從新渲染都會執行額外的邏輯判斷,性能不夠友好

所以,能夠提供一個activeClass的prop,讓使用者能夠自定義展開狀態的類名,就能夠避免以上的問題。

這些細節問題看似簡單,可是做爲一個通用組件的開發者,咱們應該常常站在使用者的角度看問題,才能不斷地提高組件的開發體驗。

單元測試

一個優秀的開源組件必定少不了單元測試,例如Ant Design等開源庫都有着很高的單測覆蓋率。一開始寫單測可能會以爲耗時、沒有必要,但其實單測可以帶來諸多的好處:

  1. 單測相較手動測試,可以減小bug率,覆蓋的場景更全,且測試較爲方便
  2. 開源的組件可能會有不少的維護者,單測可以下降模塊之間互相影響產生bug的機率
  3. 使用者通常都會選擇單測覆蓋率較高的輪子

所以,單測必不可少,目前前端常見的選擇包括:

  • Jest,FaceBook出品,配置簡單,使用JSDOM模擬測試環境,當遇到操做真實DOM的場景,如獲取scrollHeight等比較乏力
  • Karma + Mocha,Mocha同Jest都是測試框架,而Karma爲框架提供了真實的瀏覽器測試環境,若是代碼中對DOM操做較多,建議使用這種組合。可是Mocha配置較複雜,且須要自行安裝斷言庫

Vue當中已經給咱們提供了單測相關的工具Vue Test Utils,它提供了不少功能,如組件掛載,獲取實例等等,使用它配合Jest或者Mocha可以比較方便的完成單測,詳情參考文檔

在編寫單測時,咱們須要注意,對於UI組件來講,不該一味追求行級覆蓋率,應當只關注輸入輸出,避免涉及過多的實現細節,從而避免瑣碎的測試。例如,咱們測試展開功能,只須要觸發click,檢測status是否爲true便可,無需關注過程當中是觸發了xxx事件仍是發生了其餘事情,這樣當咱們的邏輯修改後可以保證單測還能有效。同時,在用TS編寫單測時,經過Vue Test Utils建立的wrapper是普通的Vue類型,所以自定義的Vue組件沒法進行類型推導,此時要獲取實例屬性時須要經過(wrapper.vm as any).xxx來獲取。經查閱資料,官方表示目前無法解決這個問題,只能使用這種方式。

文檔編寫

一個好的文檔可以方便使用者明白你的設計理念,所以咱們想要的文檔不只須要有完整的API描述,而且在展現demo時可以同時展現源碼,相似於在Ant Design或Element中那樣。咱們這邊使用的是Vue Styleguidist

它經過vue-docgen-api,可以將註釋轉換成屬性描述展示在頁面上。所以咱們只須要寫註釋,就可以生成組件屬性相關的文檔。而咱們的另外一個需求,在展現demo時可以同時展現源碼,它也可以作到。咱們能夠經過兩種方式:

  • 在Vue組件中使用<docs></docs>標籤來寫demo,這樣作對組件有侵入,感受不太好
  • 新建一個markdown文件,內部經過特殊的標記寫入vue代碼便可

咱們選用第二種方式,可是又遇到了許多坑。好比寫入md的Vue代碼不支持TS,試了不少的方法都沒有解決,後來仍是改爲了JS寫法;還有SCSS使用嵌套時,嵌套的內容未被正確解析,後改爲了CSS。其實這個東西的實現難度並不高,在md中寫Vue無非就是寫個webpack插件解析.md格式的文件,取出Vue的部分經過vue-loader處理,鑑於bug這麼多且樣式我認爲不夠美觀,以後有時間能夠再造個輪子玩一玩。

在Vue CLI3中使用Vue Styleguidist十分方便,只要運行:

vue add styleguidist

而後在package.json的scripts中添加:

"serve:doc": "vue-cli-service styleguidist",
"build:doc": "vue-cli-service styleguidist:build"

就能夠拆箱即用了。

文檔編寫完成,咱們執行yarn build:doc構建文檔,發現輸出的是一個html文件,此時咱們能夠選擇使用Github Pages來做爲咱們的靜態資源服務器展現文檔,由於它方便部署且免費。過程以下:

  1. 將styleguide.config.js中的styleguideDir選項改成"docs",即將build的目標目錄設置爲docs
  2. 在Github對應倉庫的settings中將GitHub Pages的Source選項設置爲master branch/docs folder,意味着會自動從倉庫的docs目錄獲取靜態資源

這樣每次更新docs會自動部署更新文檔,相似於這樣https://danceonbeat.github.io/r-collapse-vue/,惟一的缺點就是國內打開有點慢。

說完文檔,咱們還須要編寫在Github上展現的README,這裏推薦一個生成README的庫,readme-md-generator,格式很是簡潔且美觀。在README中,咱們能夠添加以下的小圖標:

這個可使用shields生成,它能關聯你的NPM、Github等等,實時更新icon信息,有了它文檔逼格瞬間高多了。

發佈NPM

要將包發佈到NPM,咱們須要作以下的準備工做:

  1. https://www.npmjs.com/上註冊一個NPM的帳號
  2. 本地執行
npm login --registry=https://registry.npmjs.org

注意,這邊加上registry爲了防止在全局或當前環境覆寫.npmrc,致使登陸的不是NPM源。

  1. 修改package.json的配置,能夠參考v-collapse-vue的部分配置:
{
  "name": "r-collapse-vue",
  "version": "1.0.0",
  "description": "a collapse component for VueJs",
  "author": {
    "name": "Ray",
    "email": "zhurui0904@gmail.com"
  },
  "main": "dist/r-collapse-vue.common.js",
  "files": [
    "dist"
  ],
  "keywords": [
    "Vue",
    "collapse"
  ],
  "publishConfig": {
    "registry": "https://registry.npmjs.org"
  },
  "repository": {
    "type": "git",
    "url": "git@github.com:DanceOnBeat/r-collapse-vue.git"
  }
}
  1. 最後執行npm publish便可完成發佈

每次發佈新版本以前,咱們能夠經過

npm version major/minor/patch -m 'xxx'

來修改版本號而且打上tag,此tag非NPM的dist-tag,而是Git的tag。一個版本對應一個tag,並經過

git push origin master --tags

將tag也推到遠程倉庫,這樣在倉庫中咱們就能清楚地看到發佈的記錄,方便往後回滾之類的操做。具體的版本規則能夠參考semver規範

持續集成(CI)

當開發結束後,咱們須要跑測試,測試經過後,還須要構建生成dist目錄,最後發佈到NPM。每次修改都作這樣一套操做實在繁瑣,而且容易遺漏步驟,這時候咱們就須要使用CI將咱們的流程自動化,我在這邊選擇了TravisCI。同時,咱們還能夠經過Codecov,將咱們的單測報告上傳至Codecov服務器,這樣就能同步更新Codecov的icon。

在配置CI時,我本來將生成docs的步驟也添加了進去,此時咱們在deploy中會有兩個步驟,以下:

deploy:
  - provider: npm
    email: zhurui0904@gmail.com
    api_key: $AUTH_TOKEN
    on:
      tags: true
      branch: master
    skip_cleanup: true
  - provider: pages
    skip_cleanup: true
    github_token: $GITHUB_TOKEN
    keep_history: true
    target_branch: master
    on:
      branch: master

這會形成一個問題是provider: pages會將CI服務器生成的新的docs目錄push到咱們的Github倉庫,這又會觸發一次CI,以致於無限循環。後來也沒找到合適的解決方案,又考慮到文檔不常常更新,就將文檔部署相關的部分從CI中移除了。若是你們有合適的解決方案,能夠留言告訴我一下,不勝感激。

以前咱們提到一個NPM發佈版本對應一個tag,所以咱們能夠在配置中添加

if: tag IS present

限定只在提交了tag才觸發一次自動化構建,這樣基本上就大功告成了。

總結

這是一次很是有趣的造輪體驗,代碼雖然不難,可是過程當中又學習到了不少新的東西,包括單元測試、文檔編寫等等,但願這篇文章能給準備造輪或想要造輪的小夥伴提供一點幫助。

相關文章
相關標籤/搜索