近期負責公司內部 React UI 組件庫的搭建,過程當中踩了很多坑,主要仍是對整個搭建過程沒有一個比較清晰的概念,本文章就做爲整個搭建過程的一個總結吧。前端
本文將詳細講述搭建一個前端 UI 組件庫須要涉及的流程和相關知識、工具,其中參考了一些主流開源組件庫的作法,閱讀大約須要15分鐘。vue
Monorepo
(Monolithic Repositories)是目前比較流行的一種將多個項目的代碼放在同一個庫統一管理的代碼管理組織方式,這種方式可以比較方便地進行版本管理和依賴管理,一般配合 lerna
管理多個 package。node
那麼,UI 組件庫需不須要使用 Monorepo
這種模式呢?webpack
縱觀目前各大開源項目,像 React
、babel
等生態較爲豐富的項目,都是以」一個主包,多個從包「構建的生態系統,比較適合採用 Monorepo
的方式管理複雜的依賴關係,例如 React 的 packages
:git
但對於 UI 組件庫來講,每一個組件做爲一個個獨立的單元存在,相互之間的依賴通常比較少,因此對於組件庫自身沒有必要採用 Monorepo
的方式拆分多個 package。那以組件庫爲主包、各類自研的工具庫做爲從包的方式可使用 Monorepo
進行管理嗎?答案是能夠的,目前有讚的 zent 就是採用這種方式:github
因此到底應不該該採用 Monorepo
的模式呢?個人建議是,若是僅僅是想造好組件庫這一個輪子,而且沒有依賴關係比較複雜的其餘庫須要統一管理,那就直接以一個 package 來管理便可,怎麼簡單怎麼來!web
在肯定組件庫的目錄結構以前,先大體捋一下組件庫須要有哪些組成部分:shell
組成的目錄結構大體以下:npm
├── build // 打包腳本
├── docs // 文檔部署目錄(Github Pages)
├── examples // 示例代碼(本地開發環境)
├── lib // 打包結果
├── scripts // 自動化腳本
├── site // 文檔靜態站點
├── src // 組件庫源碼
├── components // 全部組件
├── [componentName] // 單個組件
├── __tests__ // 組件測試文件
├── styles // 樣式
├── types // 類型聲明文件
├── tests // 測試
├── .babelrc // 插件
├── .eslintrc // eslint 配置
├── .publish-ci.yml // npm 包發佈、站點部署 CI 腳本
├── jest.config.js // Jest 配置文件
└── package.json // package.json
複製代碼
首先須要一套可運行的開發環境,以供咱們在本地調試、運行組件代碼。json
參考大多數 UI 組件庫的作法,能夠將 examples
下的示例代碼組織起來並暴露一個入口,使用 webpack 配置一個 dev-server,後續對組件的調試、運行都在此 dev-server 下進行。
或者也可使用腳手架工具搭一個單頁應用做爲本地開發環境,可省去配置 webpack 的麻煩。方法其實有不少種,目的是爲了讓組件在本地 Run 起來,以方便項目的 developers 和 contributors。
最後,記得將 dev-server 的啓動腳本加入 npm scripts 中,例如:
package.json:
在開發一個組件以前,須要建立組件目錄、建立組件文件、初始化組件模板、建立測試目錄/文件等一系列繁瑣又重複的工做,這些其實均可以使用腳本自動化實現。
能夠在 scripts
目錄下寫一個 node 腳本實現以上過程,並在 package.json
中添加 scripts
腳本加入到 npm 工做流中:
package.json:
scripts/new_component.js:
UI 組件做爲高度抽象的基礎公共組件,編寫單元測試是頗有必要的。一方面,單元測試可以覆蓋到一些端到端測試覆蓋不到的點,另外一方面,也能提升組件代碼的可維護性,保證代碼質量。
關於單元測試框架的選型,以 React UI 組件庫爲例,目前比較流行的組合是 Jest
+ Enzyme
。
Jest
是 Facebook 開源的一個前端測試框架,自帶斷言庫,配置簡易,提供了 JSDOM、Mock 系統、快照測試、異步代碼測試、靜態分析結果等測試功能。
Jest
會在如下幾個地方尋找測試文件:
__tests__
目錄下後綴爲 .js
的文件.test.js
的文件.spec.js
的文件通常會把測試文件放在對應的組件同級目錄下,這樣在語義上是有意義的,而且引入路徑也更短些。
Enzyme
是 Airbnb 開源的一個 React 測試類庫,提供了一套簡潔又強大的 DOM 處理 API。Enzyme
是對官方測試工具庫 ReactTestUtils
的二次封裝,並內置了 Cheerio
(一個號稱 「服務端 JQuery」 的爬蟲庫)。
如下是一個使用 Jest + Enzyme 編寫的單元測試示例:
package.json:
對於打包後的文件,統一放在 lib
目錄下,同時記得要在 .gitignore
中加上 lib
目錄,避免將打包結果提交到代碼庫中。
umd
兼容了 AMD
和 CommonJS
兩種模塊化規範,可同時支持瀏覽器、Node 兩種宿主環境,經過指定 Webpack 配置中的 output.libraryTarget
字段爲 umd
便可:
libraryTarget
還可根據須要設置爲 window
、global
、commonjs
等值應對不一樣的打包場景,具體參考 Webpack 配置文檔。
在 UI 組件庫的使用場景中,每每有時候只需引入個別組件而非全量組件,那麼就要求組件庫須要有可以按需加載的能力,支持相似以下方式的引入語法:
實現按需加載,通常有兩種方式: Tree Shaking 和單獨打包組件。
若是經過 Tree Shaking 實現按需加載,那麼通過 babel 編譯的 umd
包確定是沒法知足的,須要另外提供一份 ES6 Module
規範的包。
可是!Webpack 的 libraryTarget
不支持 ES6 Module
規範的包的導出。只能藉助於其餘打包工具例如 rollup
來導出 ES6 Module
規範的包,並配合 package.json
指定 module
字段爲 ES6 Module
規範的包的路徑,以實現使用 ES6 的方式引入包時讀取到的是提供的 ES6 Module
規範的包。
若沒法導出 ES6 Module
規範的包,也能夠採用目前大多數組件庫的作法:將組件單獨打包。
將組件單獨打包須要在 Webpack 中配置多個entry
,大體配置以下:
使用以上配置最終會將組件打包到不一樣的目錄下,這樣就能夠支持按需引入的方式加載組件了:
可是,要實現上文提到的 import { Alert, Button } from 'ui-library'
這種引入方式,還須要藉助第三方的 babel 插件來實現引入語句的轉換,目前使用比較多的有:
babel-plugin-component
babel-plugin-import
對於 umd 和按需加載的兩種打包方式,須要分別提供對應的 npm script,例如:
組件庫的文檔通常都是對外可訪問的,所以須要部署到服務器上,同時也需具有本地預覽的功能。
你能夠選擇本身搭一個用於展現文檔的站點,也可使用文檔生成器來生成文檔站點,比較推崇使用文檔生成器。能夠根據本身比較擅長的技術棧選擇特定的文檔生成器,目前主流的文檔生成器有:
以上幾種文檔生成器均支持在 markdown 文件中插入 js/jsx 或特定的組件標籤,區別在於語法風格不一樣。若是使用的是 React 技術棧,只能說,Docz 所引入的 MDX 語法,真香!甩個圖隨意感覺下:
若是項目代碼託管在 Github 上的話,能夠將文檔站點部署在 Github Pages 上。
Github Pages 提供了三種模式,你能夠將靜態站點放在如下三個位置:
例如在 Github 項目的 Settings 下,將 Github Pages 的 Source 改成 master branch/docs folder
:
經過訪問 <username>.github.io/<repository-name>
便可訪問到 docs 目錄下的資源。
關於文檔站點的持續集成和自動化部署,能夠藉助官方或第三方提供的 CI/CD 工具,例如:
組件庫的某個版本完成開發工做後,須要將包發佈到 npm 上。
package.json
中有幾個字段值得關注:
格式:英文小寫,中劃線或下劃線分隔。
版本號,符合語義化版本規則,即 major.minor.patch
:
major
:主版本號,不兼容的修改minor
:次版本號,向下兼容的新功能patch
:修訂號,向下兼容的問題修復main
是包的入口。例如,Node 環境下使用 import pkg from 'package-name'
導入的就是 main
定義的入口文件,能夠是 CommonJS
格式或者 umd
格式。
unpkg
定義瀏覽器環境使用的入口,通常格式爲 name.min.js
。
module
定義用 ES6 模塊打包的入口,通常格式爲 name.esm.js
。
示例:
description
和 keywords
分別定義包的描述和關鍵字,有助於包的檢索,並在檢索結果中顯示。
做者,通常格式是 ${name} ${email}
。
除了以上字段以外,根目錄下的 README.md
內容會在 npm 包的詳情頁展現。
在發佈以前,須要確保 UI 組件庫的代碼可以經過全部編寫的測試用例,運行先前加入工做流的 npm run test
命令便可。
在發佈 npm 包以前,須要執行打包構建,確保 lib
目錄下的內容包含當前須要發佈的內容。
使用 npm version
命令自動計算下一個版本號,並將該版本號更新到 package.json 文件中。能夠手動指定符合語義化版本規則版本號,也可使用 npm version
提供的一些快捷命令自動更新版本號。npm version
的語法以下:
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
複製代碼
若是在一個 Git 倉庫下執行 npm version
,會默認新增一個同名 Tag,這一默認行爲能夠用 npm --no-git-tag-version version
禁止。
更多關於 npm version
的用法能夠參考官方文檔。
npm adduser
註冊).npmrc
配置,確保 registry=https://registry.npmjs.org/
(可經過 npm config edit
查看或編輯)npm login
在本地登陸 npm 帳號npm publish
以上過程發佈的是一個 unscoped
包,當發佈的包跟 npm 上現有的包存在命名衝突時,就須要發佈一個特定做用域下的 scoped
包:
@scope/package-name
npm publish --access public
,以免被默認識別爲私包(私包是收費的T T)咱們須要爲特定的版本運行 git tag -a [tagname]
打上 Tag,並運行 git push origin [tagname]
顯式將該 Tag 推送至遠程。
這一過程能夠在 npm 包發佈以前,也能夠在 npm 發佈以後,在後面的自動化發佈中會涉及到這一點。
若是每次發佈都須要運行測試、打包構建、更新版本號、npm 包發佈等流程,未免有些繁瑣,須要藉助一些自動化的手段將上述流程串連起來,實現自動化發佈。
自動化發佈 npm 須要有一個觸發點,能夠是運行 npm script 時觸發 pre
、post
鉤子實現全流程的自動化,例如:定義 preversion
、postversion
鉤子腳本,在執行 npm version
先後會分別執行這兩個腳本。也能夠配合 CI/CD 工具,例如在 CI/CD 腳本中監聽 master 分支的 tags 推送,一旦有新 tag 推送至遠程,就執行上述的發佈流程,包括文檔站點的部署也能夠在這裏實現。
通常開源項目會將 Changelog 維護到一個 Markdown 文件裏,例如 CHANGELOG.md。
若是你的 commit message 使用的是 Angular 規範 ,那麼可使用 conventional-changelog
工具自動根據 commit message 生成 CHANGELOG.md,如下三種 type 的 commit 會被寫入 CHANGELOG.md:
feat
fix
Breaking change
一樣,須要將該流程加入到 npm script 工做流中:
以上命令會在 CHANGELOG.md 頭部追加上次發佈以來符合規範的 commit message。
若是你想讓更多的開發者參與到組件庫的共建,能夠以 Markdown 文檔的形式提供一份簡單的 Contributing Guide,命名爲 CONTRIBUTING.md,大體包含如下內容:
本文主要總結了搭建一個前端 UI 組件庫所涉及到各個步驟:
參考: