如何快速構建React組件庫

前言

俗話說:「麻雀雖小,五臟俱全」,搭建一個組件庫,知之非難,行之不易,涉及到的技術方方面面,猶如海面風平浪靜,實則暗礁險灘,到處驚險~

目前團隊內已經有較爲成熟的 Vue 技術棧的 NutUI 組件庫[1] 和 React 技術棧的 yep-react 組件庫[2]。然而這些組件庫大都從零開始搭建,包括 Webpack 的繁雜配置,Markdown 文件轉 Vue 文件功能的開發,單元測試功能的開發、按需加載的 Babel 插件開發等等,完成整個組件庫項目實屬不易,也是一個浩大的工程。若是咱們想快速搭建一個組件庫,大可沒必要如此耗費精力,能夠藉助業內專業的相關庫,通過拼裝調試,快速實現一個組件庫。
本篇文章就來給你們介紹一下使用 create-react-app 腳手架、docz 文檔生成器、node-sass、結合 Netlify 部署項目的整個開發組件庫的流程,本着包教包會,不會沒有退費的原則,來一場手摸手式教學,話很少說,讓咱們進入正題:css

首先看一下組件庫的最終效果:html

組件庫界面

本文將從如下步驟介紹如何搭建一個 React 組件庫:node

文章結構

1、構建本地開發環境

開發一個組件庫的首要步驟就是調試本地 React 環境,咱們直接使用 React 官方腳手架 create-react-app,能夠省去從底層配置 Webpack+TypeScript+React 的摧殘:react

一、使用 create-react-app 初始化腳手架,而且安裝 TypeScriptwebpack

npx create-react-app myapp --typescriptios

注意使用 node 爲較高版本 >10.15.0git

二、配置 eslint 進行格式化github

因爲安裝最新的 create-react-app 結合 VScode 編輯器便可支持 eslit,可是須要在項目根目錄中要添加 .env 這個配置文件,設置 EXTEND_ESLINT=true 這樣纔會啓用 eslint 檢測,注意要 重啓 vscodeweb

三、組件庫系統文件結構typescript

新建 styles 文件夾,包含了基本樣式文件,結構以下:

|-styles
| |-variables.scss // 各類變量以及可配置設置
| |-mixins.scss    // 全局 mixins
| |-index.scss    // 引入所有的 scss 文件,向外拋出樣式入口
|-components
| |-Button
|   |-button.scss // 組件的單獨樣式
|   |-button.mdx // 組件的文檔
|   |-button.tsx // 組件的核心代碼
|   |-button.test.tsx // 組件的單元測試文件
|  |-index.tsx  // 組件對外入口

四、安裝 node-sass 處理器

安裝 node-sass 用來編譯 SCSS 樣式文件:npm i node-sass -D

這樣最基本的 react 開發環境就完成了,能夠開心的開發組件了。

2、組件庫打包編譯

本地調試完組件庫以後,須要打包壓縮編譯代碼,供其餘用戶使用,這裏咱們用的 TypeScript 編寫的代碼,因此使用 Typescript 來編譯項目:
首先在每一個組件中新建 index.tsx 文件:

import Button from './button'
export default Button

修改 index.tsx 文件,導入導出各個模塊

export { default as Button } from './components/Button'

在根目錄新建 tsconfig.build.json,對 .tsx 文件進行編譯:

{
  "compilerOptions": {
    "outDir": "dist",// 生成目錄
    "module": "esnext",// 格式
    "target": "es5",// 版本
    "declaration": true,// 爲每個 ts 文件生成 .d.ts 文件
    "jsx": "react",
    "moduleResolution":"Node",// 規定尋找引入文件的路徑爲 node 標準
    "allowSyntheticDefaultImports": true,
  },
  "include": [// 要編譯哪些文件
    "src"
  ],
  "exclude": [// 排除不須要編譯的文件
    "src/**/*.test.tsx",
    "src/**/*.stories.tsx",
    "src/setupTests.ts",
  ]
}

對於樣式文件,使用 node-sass 編譯 SCSS,抽取全部 SCSS 文件生成 CSS 文件:

"script":{
    "build-css": "node-sass ./src/styles/index.scss ./dist/index.css",
}

而且修改 build 命令:

"script":{
    "clean": "rimraf ./dist",// 跨平臺的兼容
    "build": "npm run clean && npm run build-ts && npm run build-css",
}

這樣,執行 npm run build 以後,就能夠生成對應的組件 JS 和 CSS 文件,爲後面使用者按需加載和部署到 npm 上提供準備。

3、本地調試組件庫

本地完成組件庫的開發以後,在發佈到 npm 前,須要先在本地調試,避免帶着問題上傳到 npm 上。這時就須要使用 npm link 出馬了。

什麼是 npm link

在本地開發 npm 模塊的時候,咱們可使用 npm link 命令,將 npm 模塊連接到對應的運行項目中去,方便地對模塊進行調試和測試。

使用方法

假設組件庫是 reactui 文件夾,要在本地的 demo 項目中使用組件。則在組件庫中(要被 link 的地方)執行 npm link,則生成從本機的 node_modules/reactui 組件庫的路徑 / reactui 中的映射關係。
而後在要使用組件庫的文件夾 demo 中執行 npm link reactui 則生成如下對應鏈條:

在要使用組件的文件夾 demo 中 -[映射到]—> 本機的 node_modules/reactui —[映射到]-> 開發組件庫 reactui 的文件夾 /reactui

須要修改組件庫的 package.json 文件來設置入口:

{
  "name": "reactui",
  "main": "dist/index.js",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
}

而後在要使用組件的 demo 項目的依賴中添加:

"dependencies":{
  "reactui":"0.0.1"
}

注意,此時並不用安裝依賴,之因此寫上該依賴,是爲了方便在項目中使用的時候能夠有代碼提示功能。
而後在 demo 項目中使用:

import { Button } from 'reactui'

在 index.tsx 中引入 CSS 文件

import 'reactui/build/index.css'

正當覺得大功告成的時候,下面這個報錯猶如一盆冷水從天而降:

錯誤提示

通過各類問題排查,在 react 官方網站[3] 上查到如下說法:

🔴 Do not call Hooks in class components.
🔴 Do not call in event handlers.
🔴 Do not call Hooks inside functions passed to useMemo, useReducer, or useEffect.

說的很明白:

緣由 1: React 和 React DOM 的版本不同的問題
緣由 2: 可能打破了 Hooks 的規則
緣由 3: 在同一個項目中使用了多個版本的 React

官網很貼心,給出瞭解決方法:

This problem can also come up when you use npm link or an equivalent. In that case, your bundler might 「see」 two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib. This should make the library use the application’s React copy.

核心思想在組件庫中使用 npm link 方式,引到 demo 項目中的 react; 因此在組件庫中執行: npm link ../demo/node_modules/react

具體步驟以下:

  1. 在代碼庫 reactui 中執行 npm link
  2. 在代碼庫 reactui 中執行 npm link ../../demo/node_modules/react
  3. 在項目 demo 中執行 npm link reactui

如此能夠解決上面 react 衝突問題;因而能夠在本地一邊快樂的調試組件庫,一邊快樂的在使用組件的項目中看到最終效果了。

4、組件庫發佈到 npm

該過程必定要注意使用的是 npm 源!![很是重要]

首先肯定本身是否已經登陸了 npm:

npm adduser
// 填入用戶名;密碼;email
npm whoami // 查看當前登陸名

修改組件庫的 package.json ,注意 files 配置;以及 dependencies 文件的化簡:
react 依賴本來是要放在 dependencies 中的,可是可能會和用戶安裝的 react 版本衝突,因此放在了 devDependencies 中,可是這樣話用戶若是沒有安裝 react 則沒法使用組件庫,因此要在 peerDependencies 中定義前置依賴 peerDependencies,告訴用戶 react 和 react-dom 是必要的前置依賴:

"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [ // 把哪些文件上傳到 npm
  "dist"
],
"dependencies": {  // 執行 npm i 的時候會安裝這些依賴到 node_modules 中
  "axios": "^0.19.1",// 發送請求
  "classnames": "^2.2.6",//
  "react-transition-group": "^4.3.0"
},
"peerDependencies": { // 重要!!,提醒使用者,組件庫的核心依賴,必須先安裝這些依賴才能使用
  "react": ">=16.8.0",  // 在 16.8 以後 才引入了 hooks
  "react-dom": ">=16.8.0"
}

好了,整個組件庫通過上述過程,基本上各個功能已經有了,說起一句:因爲組件庫使用的是 create-react-app 腳手架,最新的版本已經集成了單元測試功能。還有配置 husky 等規範代碼提交,在這裏不在作贅述,讀者能夠自行配置。

5、生成說明文檔

目前生成說明文檔較好的工具備 storybook[4]、docz[5] 等工具,二者都是很優秀的文檔生成工具,可是尺有所短,寸有所長,通過認真調研比較,最終選擇了 docz。

工具名稱 區別一 區別二
storybook 使用特有的API開發文檔說明,能夠引入markdown文件 生成文檔的界面帶有storybook的痕跡較多一些
docz 完美的結合了react和markdown語法開發文檔 生成的文檔界面是常規的文檔界面

一、肯定選型

1)storybook 的經常使用編譯文檔規範相對 docz 而言,略有繁瑣

storybook 的編譯文檔規範以下所示:

//省略 import 引入的代碼
storiesOf('Buttons', module)
.addDecorator(storyFn => <div style={{ textAlign: 'center' }}>{storyFn()}</div>)
.add('with text', () => (
<Button onClick={action('clicked')}>Hello Button111</Button>
),{
notes:{markdown}   // 將會渲染 markdown 內容
})

對比 docz 的開發文檔:

# Button 組件

使用方式以下所示:
import { Playground, Props } from 'docz';
import Button from './index.tsx';

## 按鈕組件

<Playground>
    <Button btnWidth="100">我是按鈕</Button>
</Playground>

** 基本屬性 **

| 屬性名稱 | 說明 | 默認值 |
|--|--|--|
|btnType | 按鈕類型 |--|

衆所周知,Markdown 是一種輕量級標記語言,它容許人們使用易讀易寫的純文本格式編寫文檔。團隊成員在開發文檔時,熟練使用 markdown 語法,開發 docz 文檔的 mdx 文件,結合了 Markdown 和 React 語法,相比 storybook 要使用不少的 API 來編寫文檔的方式,無疑減小了不少的學習 storybook 語法的成本。

2)docz 生成的文檔樣式更加符合我的審美

storybook 生成的文檔樣式,帶有 storybook 的痕跡更爲嚴重一些, 其生成文檔界面以下所示:

storybook生成界面

docz 生成的文檔圖以下所示:

docz生成界面

由上圖對比能夠看出,docz 生成的界面更加簡介,較爲常規。
綜上,結合默認文檔開發習慣和界面風格,我選擇了docz,固然仁者見仁、智者見智,讀者也可使用同爲優秀的 storybook 嘗試,這都不是事兒~

二、使用 docz 開發

肯定了 docz 進行開發後,根據官網介紹,在 create-react-app 生成的組件庫中進行了安裝配置:

npm install docz

安裝成功後,就會向 package.json 文件中添加以下配置

{
  "scripts": {
    "docz:dev": "docz dev",
    "docz:build": "docz build",
    "docz:serve": "docz build && docz serve"
  }
}

這時還須要在項目的根目錄下新建 doczrc.js 文件,對 docz 進行配置:

export default {
  files: ['./src/components/**/*.mdx','./src/*.mdx'], 
  dest: 'docsite', // 打包 docz 文檔到哪一個文件夾下
  title: '組件庫左上角標題',  // 設置文檔的標題
  typescript: true, // 支持 typescript 語法
  themesDir: 'theme', // 主題樣式放在哪一個文件夾下,後面會講
  menu: ['快速上手', '業務組件'] // 生成文檔的左側菜單分類
}

其中 files 規定了 docz 去對哪些文件進行編譯生成文檔,若是不作限制,會搜索項目中全部的 md、mdx 爲後綴的文件生成文檔,所以我在該文件中作了範圍限制,避免一些 README.md 文件也被生成到文檔中。

此外還須要注意到兩點:

一、menu: ['快速上手', '業務組件'] 對應着組件庫左側的菜單欄分類,好比在 mdx 文檔中在最上面設置組件所屬的菜單 menu: 業務組件 , 則 Button 組件屬於 "業務組件" 的分類:

---
name: Button
route: /button
menu: 業務組件
---

在 src 中新建歡迎頁,路由爲跟路徑,所屬菜單爲「快速上手」;

---
name: 快速上手
route: /
---

執行 npm run docz:dev,就能夠打開

路由配置

介紹到這裏,估計有小夥伴會有疑問了,這樣生成的網站千篇一概,可否爲所欲爲的自定義網站的樣式和功能呢?當初我也有這種疑問,通過屢次嘗試,皇天不負苦心人,終於摸索出以下方法:

一、修改 docz 文檔自己的樣式

根據 docz 官方文檔中增長 logo 的方法[6],能夠經過自定義組件覆蓋原有組件的形式:

Example: If you're using our gatsby-theme-docz which has a Header component located at src/components/Header/index.js you can override the component by creating src/gatsby-theme-docz/components/Header/index.js. Cool right?

因此根據 docz 源代碼主題部分代碼: https://github.com/doczjs/docz/tree/master/core/gatsby-theme-docz/src,找到對應的文檔組件的代碼結構,在組件庫項目根目錄新建同名稱的文件夾:

|-theme
|  |-gatsby-theme-docz
|     |-components
|     |-Header
|       |-index.js // 在這裏修改自定義的文檔組件
|       |-styles.js // 在這裏修改生成的樣式文件

這樣在執行 npm run docz:dev 的時候,就會把自定義的代碼覆蓋原有樣式,實現文檔的多樣化。

二、修改 markdown 文檔樣式

事情到這裏就結束了嗎?不!咱們的目標不只如此,由於我發現自動生成的 markdown 格式,並不符合個人審美,好比生成的表格文字居左對齊,而且整個表格樣式單一,可是這裏屬於 markdown 樣式的範疇,修改上述文檔組件中並不包括這裏的代碼,那麼如何修改 markdown 生成文檔的樣式呢?

docz默認生成表格樣式

通過我靈機一動又一動,發現既然在上面修改文檔組件樣式的時候,重寫了 component/Header/styles.js 文件,是否能夠在該文件中引入自定義的樣式呢?文件結構以下:

|-theme
|  |-gatsby-theme-docz
|     |-components
|     |-Header
|       |-index.js // 在這裏修改自定義的文檔組件
|       |-styles.js // 在這裏修改生成的樣式文件
|       |-base.css  // 這裏修改 markdown 生成文檔的樣式

這樣修改後的表格樣式以下:

修改docz表格樣式

接下來各位小主能夠根據本身的審美或者視覺設計的要求自定義文檔的樣式了。

6、部署文檔到服務器

生成的組件庫文檔只在本地顯示是沒有意義的,因此須要部署到服務器上,因而第一時間想到的是放在 github 進行託管,打開 github 中的 setting 設置選項,GitHub Pages 設置配置的分支:

設置分支

這時默認打開的首頁路徑爲:

https://plusui.github.io/plusReact/

但實際上頁面有效的訪問地址是帶有文件夾 docsite 路徑的:

https://plusui.github.io/plusReact/docsite/button/index.html

此外,頁面引入的其餘資源路徑,都是絕對路徑,以下圖資源路徑所示:

引入資源路徑

因此直接把打包後的資源放在 github 上是沒法訪問各類資源的。
這時咱們只好把網站部署到雲服務器上了,考慮到服務器配置的繁瑣,這裏給你們提供一個簡便的部署網站:Netlify[7]

Netlify 是一個提供靜態網站託管的服務,提供 CI 服務,可以將託管 GitHub,GitLab 等網站上的 Jekyll,Hexo,Hugo 等靜態網站。

部署項目的過程也很簡單,傻瓜式的點擊選擇 github 網站中代碼路徑,以及配置文件夾跟路徑,以下圖所示:

配置文件夾路徑

而後就能夠點擊生成的網站 url,訪問到部署的網站了:

部署網站

並且很方便的是,一旦完成部署以後,以後再次向代碼庫中提交代碼,Netlify 會自動更新網站。
此外,若是想自定義 url,那麼就只能去申請域名了,在本身的雲服務器上,解析域名便可。下面簡單說一下配置步驟:

1)首先在 Netlify 網站上,選擇組件庫對應的 Domain settings 下 Custom domains,增長本身的域名:

配置域名

2)而後打開雲服務器中的域名解析中的解析設置,將該域名指向 Netlify:

雲服務器上增長域名

3)最後打開設置的網址,就能夠訪問到組件庫了:

最終效果

7、組件按需加載

好了,通過上面的流程,能夠在 demo 項目中使用組件庫了,可是在 demo 項目中,執行 npm run build ,就會發現生成的靜態資源中即便只使用了一個組件,也會把 reactui 組件庫中全部的組件打包進來。

因此如何進行按需加載呢?

按需加載首先映入腦海的是使用 babel-plugin-import 插件, 該插件能夠在 Babel 配置中針對組件庫進行按需加載.

用戶須要安裝 babel-plugin-impor 插件,而後在 plugins 中加入配置:

"plugins": [
  [
    "import",
    {
      "libraryName": "reactui", // 轉換組件庫的名字
      "libraryDirectory": "dist/components", // 轉換的路徑
      "camel2DashComponentName":false,  // 設置爲 false 來阻止組件名稱的轉換
      "style":true
    }
  ]
]

這樣在 demo 項目中使用以下方式:

import { Button } from 'reactui';

就會在 babel 中編譯成:

import { Button } from 'reactui/dist/components/Button';
require('reactui/dist/components/Button/style');

可是這樣還有些弊端:

一、 用戶在使用組件庫的時候還須要安裝 babel-plugin-import, 並作相關 plugins 配置;

二、 開發組件庫的時候組件對應的樣式文件還須要放在 style 文件夾下;

那有沒有更爲簡單的方法呢?在 ant-design 中尋找答案,發現這樣一句話 「antd 的 JS 代碼默認支持基於 ES modules 的 tree shaking」。 對呀!還可使用 webpack 的新技術「tree shaking」。

什麼是 tree shaking? AST 對 JS 代碼進行語法分析後得出的語法樹 (Abstract Syntax Tree)。AST 語法樹能夠把一段 JS 代碼的每個語句都轉化爲樹中的一個節點。DCE Dead Code Elimination,在保持代碼運行結果不變的前提下,去除無用的代碼。

webpack 4x 中已經使用了 tree shaking 技術,咱們只須要在 package.json 文件中配置參數 "sideEffects": false,來告訴 webpack 打包的時候能夠大膽的去掉沒有用到的模塊便可。這時用戶在 demo 項目中使用組件庫的時候不須要作任何處理,就能夠按需引用 JS 資源了。
不知道你們在看到這裏時,是否發現這樣配置仍是有問題的:即 sideEffects 配置成 false 是有問題的。
由於按照上述配置,就會發現組件的樣式不見了!!

樣式無效

通過排查,緣由是引入 CSS 樣式的代碼:import './button.scss',能夠看到至關於只是引入了樣式,並不像其餘 JS 模塊後面作了調用,在 tree shaking 的時候,會把 css 樣式去掉。因此在配置 sideEffects 就要把 CSS 文件排除掉:

"sideEffects": [
  "*.scss"
]

經過上述 tree shaking 的方法,能夠實現組件庫的按需加載功能,打包的文件去除了沒有用到的組件代碼,同時省去了用戶的配置。

8、樣式按需加載

一般來講,組件庫的 JS 是按需加載的,可是樣式文件通常只輸出一個文件,即把組件庫中的全部文件打包編譯成一個 index.css 文件,用戶在項目中引入便可;可是若是就是想作按需加載組件的樣式文件,該如何去作呢?

這裏我提供一種思路,因爲 .tsx 文件是由 TS 編譯器打包編譯的,並無處理 SCSS,因此我使用了 node-sass 來編譯 SCSS 文件,若是須要按需加載 SCSS 文件,則每一個組件的 index.tsx 文件中就須要引入對應的 SCSS 文件:

import Button from './button';
import './button.scss';
export default Button;

生成的 SCSS 文件也須要打包到每一個組件中,而不是生成到一個文件中:

因此使用了 node-sass 中的 sass.render 函數,抽取每一個文件中的樣式文件,並打包編譯到對應的文件中,代碼以下所示:

//省略 import 引入,核心代碼以下
function createCss(name){
    const lowerName = name.toLowerCase();
    sass.render({ // 調用 node-sass 函數方法,編譯指定的 scss 文件到指定的路徑下
        file: currPath(`../src/components/${name}/${lowerName}.scss`),
        outputStyle: 'compressed', // 進行壓縮
        sourceMap: true,
    },(err,result)=>{
        if(err){
            console.log(err);
        }
        const stylePath = `../dist/components/${name}/`;
        fs.writeFile(currPath(stylePath+`/${lowerName}.scss`), result.css, function(err){
            if(err){
                console.log(err);
            }
        });
    });
}

這樣就在生成的 dist 文件中的每一個組件中增長了 SCSS 文件,用戶經過「按需加載小節」中的方法在引入組件的時候,會調用對應的 index 文件,在 index.js 文件中就會調用對應的 SCSS 文件,從而也實現了樣式文件的按需加載。

可是這樣還有一個問題,就是在開發組件庫的時候每一個組件中的 index.tsx 文件中引入的是 SCSS 文件 import './button.scss'; ,因此 node-sass 編譯後的文件須要是 SCSS 後綴的文件(雖然已是 CSS 格式),若是生成的是 CSS 文件,則用戶在使用組件的時候就會因找不到 SCSS 文件而報錯,也就是用戶在使用組件的時候,也須要安裝 node-sass 插件。
不知你們有沒有更好的辦法,在組件庫開發的時候使用的是 SCSS 文件,編譯後生成的是 CSS 後綴的文件,在用戶使用組件的中調用的也是 CSS 文件呢?歡迎在文末留言討論~

結語

以上就是整個搭建組件庫的過程,從一開始決定使用現有的 create-react-app 腳手架和 docz 來構成核心功能,到文檔的網站部署和 npm 資源的發佈,最初感受應該可以快速完成整個組件庫的搭建,實際上若是要想改動這些現有的庫來實現本身想要的效果,仍是經歷了一些探索,不過整個摸索過程也是一種收穫和樂趣所在,願走過路過的小夥伴能有所收穫~

參考文章

[1] NutUI 組件庫: http://nutui.jd.com/#/index

[2] yep-react 組件庫: http://yep-react.jd.com

[3] react 官方網站: https://reactjs.org/warnings/...

[4] storybook: https://storybook.js.org/

[5] docz: https://www.docz.site/

[6] docz 官方文檔: https://www.docz.site/docs/ga...

[7] Netlify: https://app.netlify.com/teams...

[8]基於 Storybook 5 打造組件庫開發與文檔站建設小結: http://jelly.jd.com/article/5...

相關文章
相關標籤/搜索