閒魚前端組件庫的建設

背景

閒魚經過這些年發展已經變成了一個業務豐富,數量衆多且功能複雜的產品;而且業務高速發展,需求井噴,需要開發同學能夠快速做出響應。

同時這些產品線與閒魚的設計風格有很多的共性,但各自不同的定位也導致了更多的差異性;

制定一套各產品業務線通用的規範,並在各端提供一套靈活配置的組件物料庫,得到一些穩定且高複用性的內容對當前階段的閒魚前端來說是有極大的業務和技術價值的。

設計

組件庫定位

在確定了要做組件庫後我們對前端業務一段時間內的設計稿進行了收集,分析發現各個業務線有較大差異,尤其是在營銷活動場景,爲了實現更好的營銷效果往往需要新穎視覺和交互。在設計組件時如果爲了滿足所有的場景則勢必會導致較大的使用成本。所以最後我們確定定位:該組件庫解決 80% 的產品線業務。

需求分析

從上面的分析可以看出 UI 規範是實現該組件庫的一個十分關鍵的基礎,爲此我們建立了包括客戶端和設計師的虛擬小組,進行跨端的設計語言統一。

對於組件庫的目標整理如下

  1. 提升效率:不同業務線、不同項目之間工作有大量重複的內容,通過與設計師達成的 UI 規範爲基礎建設滿足各業務線的組件,減少重複建設。

  2. 提高質量:許多重複被踩的坑,光靠文檔效率低下,沉澱組件代碼最佳實踐。

  3. 統一的交互體驗:提供跨端的一致交互體驗。

  4. 解決兼容問題:減少重複的兼容性問題和測試的兼容成本。

結合現有的集團能力分析得出基礎的能力拓撲圖一

實現分析

下圖是我們在建設前期分析的實現上述組件庫的關鍵技術點,後面我將圍繞這個思維導圖進行具體的分析

關鍵點

整體架構

把所有的組件看成一個整體,一起打包發佈。

單個倉庫,單個包,統一維護統一管理,比如 Antd。

優點

  1. 可以通過相對路徑實現組件與組件間,公共代碼間的相互引用

缺點

  1. 他是完全耦合在一起的,哪怕一個組件的細小修改都需要對整個項目發一次包。

  2. 由於閒魚的業務特點決定了它不僅需要考慮源碼開發的項目更需要考慮搭建場景。而如果用單包則無法複用集團現有的能力,導致搭建場景下組件重複被打包進頁面各個模塊的情況。

  3. 公共代碼複用雖然有一定的收益,但是在長期維護的項目中也會大大增加維護成本

每個組件彼此獨立,單獨打包發佈,單個倉庫多個包,統一維護單獨管理。

例如下面的目錄結構,所有的組件均在 packages 下面管理如下圖四左圖所示,每個組件的目錄結構如圖三右圖所示。

優點

  1. 組件發佈靈活,並且天然支持按需使用

  2. 可以複用現在集團的項目管理流程,實現 bug 沉澱

  3. 可以複用現在集團的能力,實現組件發版的規範控制

  4. 解決搭建場景的依賴重複打包問題

缺點

  1. 組件與組件之間物理隔離。對於相互依賴,公共代碼抽象等場景,就只能通過 NPM 包引用的方式來實現。

  2. 組件相互依賴的調試問題。

    由於只能通過 NPM 包引用的方式來實現依賴,導致調試不變,這裏我們可以用軟鏈的方式解決

  3. 多依賴多版本問題

例如依賴 A,被組件庫的 a,b 組件依賴,如果使用單包不需要考慮 A 包的版本問題,一個項目只會有一個依賴,但是如果使用多包,a,b 組件依賴同一個組件 A 就會存在不同版本的可能性。

  1. 使用成本較高,每使用一個組件都要安裝並引入新包,業務開發同學需要一定的記憶成本。

根據我們項目的特點:多人蔘與、長期維護、組件分層,最後我們選擇基於管理工具 lerna 的多包管理方式。

但是由於我們的組件庫在分級上有「原子組件」和「通用業務組件」之分,考慮上面的使用成本問題,我們決定將原子組件以單包的形式管理,「原子組件」和「通用業務組件」同級,則以多包的形式放在 packages 下管理。

UI 樣式

  1. 由於歷史原因閒魚前端的項目一直是行內樣式,行內樣式的設計導致了較多 CSS 使用的限制,對 keyframes 動畫也不支持,所以經過調研我們確定了組件腳手架走非行內的技術方案

  2. 使用 css module 的 style.xxx 的方案雖然能夠用添加 hash 後綴的方式解決重名問題,但是也導致修改不夠靈活,經過綜合考慮最後決定採用添加統一前綴的方式,且 pre 作爲組件的 props 暴露給業務開發,這樣他可以進行樣式的配置。這裏我們也考慮過藉助社區的 babel-plugin-react-css-modules 插件添加統一的前綴,但是實踐發現他需要安裝在 dependence 下,原因爲:

When babel-plugin-react-css-modules cannot resolve CSS module at a compile time, it imports a helper function (read Runtime styleName resolution). Therefore, you must install babel-plugin-react-css-modules as a direct dependency of the project.

考慮我們提供的是基礎的組件,不希望引入過多的依賴所以最後放棄了社區插件的方案。

上面也提到不同的業務線存在主題定製、樣式差異化的需求,雖然我們在上面提供了 pre 最爲 props 的方式,但是其不夠靈活且修改多個組件時成本過高,所以我們又去參考了社區主題替換的方案,基於 css3 的 var() 能力輕易地就能實現想要的效果,修改樣式變量對應的值就可以實現該變量對應的樣式值的變化。

這裏我們定義了全局的樣式變量,其拆分的緯度如下:

統一 UI 樣式規範

  1. 色彩

  2. 字體

  3. 佈局

  4. icon

統一的交互動畫

  1. 動畫時長

  2. 動畫路徑

將拆分後的樣式變量放在: root 上定義並提供 global-css 的 cdn 地址到組件腳手架中,讓組件開發同學直接使用變量。經過一段時間實踐這裏我們也發現該方式會引入一定的開發成本,後期會考慮通過插件的方式關聯提醒消除樣式變量的使用成本。

組件質量

前端經過一段時間的發展對於低級 bug 的規避已經有了不少工具,這裏我們統一了開發語言 TypeScript,並且添加了各個 linter:

讓代碼在各種編輯器和 IDE 中保持風格一致

需要注意的是有的編輯器可以直接使用,比如:WebStorm ;而有的編輯器需要安裝對應的插件,比如大部分同學使用的編輯器 Visual Studio Code。
最後使用的. editorconfig 配置如下:

root = true
[*] # 匹配所有文件
charset = utf-8  # 文件編碼是utf-8
indent_style = space  # 空格縮進
indent_size = 2  # 縮進空格爲2
end_of_line = lf  # 使用Unix-style 換行符
trim_trailing_whitespace = true  # 除去換行行首的任意空白字符
insert_final_newline = true  # 每個文件以換行結束

Find and fix problems in your JavaScript code

ESLint 是針對 js 進行檢查,而我們日常開發時很多時候使用的是 ts,對應的工具是 TSLint,TypeScript 官方在 19 年放棄 TSLint 標記爲廢棄,全面採用 ESLint

const { getESLintConfig } = require('@iceworks/spec');
module.exports = getESLintConfig('rax-ts');

安裝 stylelint 插件,保存就會自動 fix,同時也會對有問題的樣式進行告警

詳見官網:https://stylelint.io/

安裝下面的插件,會調整你的樣式順序讓代碼更有可讀性

"stylelint-config-recess-order": "^2.5.0",
"stylelint-order": "^5.0.0"

添加了 Commitlint 檢查,其規範如下

/**
     * ● feat: 新增功能
     * ● fix: 修復 bug
     * ● docs: 文檔相關的改動
     * ● style: 對代碼的格式化改動,代碼邏輯並未產生任何變化(例如代碼縮進,分號的移除和添加)
     * ● test: 新增或修改測試用例
     * ● refactor: 重構代碼或其他優化舉措
     * ● chore: 項目工程方面的改動,代碼邏輯並未產生任何變化
     * ● revert: 恢復之前的提交
     * 
     * 提交格式:<type>(<scope>): <describe> 其中scope可忽略
     * 
     * 提交實例:git commit -am 'fix(location): 登錄接口地址修改'
     * 
     */

對組件進行分類最底層是原子組件,比如 button,它會被通用業務組件引用,不會經常輕易修改;在這上面是通用業務組件,它是基於 UI 規範的產物,是隨着設計需求實時豐富的;最上面是各個行業或者業務的通用業務組件,它不基於 UI 規範,只是某一時期各個業務線通用的交互能力。

組件開發

腳手架構建方面主要基於集團現有的能力不做過多介紹。我們知道 lerna 提供了不少 Lerna Script 供開發者使用,但是我們需要接入集團的腳手架、需要接入天馬組件的發佈流程等閒魚業務定製化的需求,所以在這裏我們對其在各個生命週期進行了一定修改,讓業務開發還是使用 Lerna Script 的指令,但是實現了我們需要定製化的各個需求。

在有了上述基礎後我們已經開發組件了,爲了保證組件的質量,我們制定了上面的組件開發流程。目前組件質量腳本檢查還不夠完善,需要人工 cr 保障。

s

demo 部分我們的技術方案是基於 Storybook:

Storybook 運行在主應用程序之外,用戶可以獨立地開發 UI 組件,而不必擔心應用程序特定的依賴關係和需求,使開發人員能夠獨立地創建組件,並在孤立的開發環境中交互地展示組件

Storybook 雖然好用但同時也引入了開發同學的學習成本他們不能像以前一樣寫 md,參考了 shopify 的 polaris 項目後我們決定採用將 md 文件轉化爲 storybook 的方式進行。

同時在實踐中我們也發現由於我們組件的整體基礎是 Rax,而 Storybook 對 Rax 的支持是有些一言難盡的,目前我們也遇到了不少問題後面有機會可以再詳細介紹。

總結

目前組件庫的還在進一步的豐富中,開發可擴展性強、上手成本低的組件也是我們這一段時間以來遇到的一大挑戰。

已有的部分組件已經在各個業務線使用,其提效的效果還是非常明顯的,減少了重複開發工作,業務開發可以無腦使用不用擔心 UI 還原問題,甚至還能對一些 UI 進行規範。

以上內容如有不對之處,歡迎指正。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/iKX50SZvcTIWES0B5JN-dQ